@@ -351,7 +351,7 @@ private void RecursivelyEnumerateFiles(string folderPath, ref List<string> found
351
351
this . logger . WriteHandledException (
352
352
$ "Could not enumerate files in the path '{ folderPath } ' due to an exception",
353
353
e ) ;
354
-
354
+
355
355
continue ;
356
356
}
357
357
@@ -400,7 +400,7 @@ private void RecursivelyEnumerateFiles(string folderPath, ref List<string> found
400
400
this . logger . WriteHandledException (
401
401
$ "Could not enumerate directories in the path '{ folderPath } ' due to an exception",
402
402
e ) ;
403
-
403
+
404
404
return ;
405
405
}
406
406
@@ -625,6 +625,69 @@ private static string UnescapeDriveColon(string fileUri)
625
625
return sb . ToString ( ) ;
626
626
}
627
627
628
+ /// <summary>
629
+ /// Converts a file system path into a DocumentUri required by Language Server Protocol.
630
+ /// </summary>
631
+ /// <remarks>
632
+ /// When sending a document path to a LSP client, the path must be provided as a
633
+ /// DocumentUri in order to features like the Problems window or peek definition
634
+ /// to be able to open the specified file.
635
+ /// </remarks>
636
+ /// <param name="path">
637
+ /// A file system path. Note: if the path is already a DocumentUri, it will be returned unmodified.
638
+ /// </param>
639
+ /// <returns>The file system path encoded as a DocumentUri.</returns>
640
+ public static string ConvertPathToDocumentUri ( string path )
641
+ {
642
+ const string fileUriPrefix = "file:///" ;
643
+ const string untitledUriPrefix = "untitled:" ;
644
+
645
+ // If path is already in document uri form, there is nothing to convert.
646
+ if ( path . StartsWith ( untitledUriPrefix , StringComparison . Ordinal ) ||
647
+ path . StartsWith ( fileUriPrefix , StringComparison . Ordinal ) )
648
+ {
649
+ return path ;
650
+ }
651
+
652
+ string escapedPath = Uri . EscapeDataString ( path ) ;
653
+ var docUriStrBld = new StringBuilder ( escapedPath ) ;
654
+
655
+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
656
+ {
657
+ // VSCode file URIs on Windows need the drive letter lowercase.
658
+ // Search original path for colon since a char search (no string culture involved)
659
+ // is faster than a string search.
660
+ if ( path . Contains ( ':' ) )
661
+ {
662
+ // Start at index 1 to avoid an index out of range check when accessing index - 1.
663
+ // Also, if the colon is at index 0 there is no drive letter before it to lower case.
664
+ for ( int i = 1 ; i < docUriStrBld . Length - 2 ; i ++ )
665
+ {
666
+ if ( ( docUriStrBld [ i ] == '%' ) && ( docUriStrBld [ i + 1 ] == '3' ) && ( docUriStrBld [ i + 2 ] == 'A' ) )
667
+ {
668
+ int driveLetterIndex = i - 1 ;
669
+ char driveLetter = char . ToLowerInvariant ( docUriStrBld [ driveLetterIndex ] ) ;
670
+ docUriStrBld . Replace ( path [ driveLetterIndex ] , driveLetter , driveLetterIndex , 1 ) ;
671
+ break ;
672
+ }
673
+ }
674
+ }
675
+
676
+ // Uri.EscapeDataString goes a bit far, encoding \ chars. Also, VSCode wants / instead of \.
677
+ docUriStrBld . Replace ( "%5C" , "/" ) ;
678
+ }
679
+ else
680
+ {
681
+ // Because we will prefix later with file:///, remove the initial encoded / if this is an absolute path.
682
+ // See https://docs.microsoft.com/en-us/dotnet/api/system.uri?view=netframework-4.7.2#implicit-file-path-support
683
+ // Uri.EscapeDataString goes a bit far, encoding / chars.
684
+ docUriStrBld . Replace ( "%2F" , string . Empty , 0 , 3 ) . Replace ( "%2F" , "/" ) ;
685
+ }
686
+
687
+ // ' is not always encoded. I've seen this in Windows PowerShell.
688
+ return docUriStrBld . Replace ( "'" , "%27" ) . Insert ( 0 , fileUriPrefix ) . ToString ( ) ;
689
+ }
690
+
628
691
#endregion
629
692
}
630
693
}
0 commit comments