From 511baec195049a3a16d166b161dc2564fcdc683a Mon Sep 17 00:00:00 2001 From: Keith Hill Date: Mon, 11 Mar 2019 16:45:27 -0600 Subject: [PATCH 1/4] Cherry pick commit for #1750 fix --- .../CodeLens/CodeLensFeature.cs | 2 +- .../CodeLens/PesterCodeLensProvider.cs | 18 +++-- .../CodeLens/ReferencesCodeLensProvider.cs | 2 +- .../Server/LanguageServer.cs | 4 +- .../Workspace/ScriptFile.cs | 16 ++++- .../Workspace/Workspace.cs | 67 ++++++++++++++++++- .../Debugging/DebugServiceTests.cs | 2 +- .../Session/ScriptFileTests.cs | 31 +++++++++ 8 files changed, 128 insertions(+), 14 deletions(-) diff --git a/src/PowerShellEditorServices.Host/CodeLens/CodeLensFeature.cs b/src/PowerShellEditorServices.Host/CodeLens/CodeLensFeature.cs index ac5f388a3..9a0174cdc 100644 --- a/src/PowerShellEditorServices.Host/CodeLens/CodeLensFeature.cs +++ b/src/PowerShellEditorServices.Host/CodeLens/CodeLensFeature.cs @@ -126,7 +126,7 @@ private async Task HandleCodeLensRequest( codeLensResponse[i] = codeLensResults[i].ToProtocolCodeLens( new CodeLensData { - Uri = codeLensResults[i].File.ClientFilePath, + Uri = codeLensResults[i].File.DocumentUri, ProviderId = codeLensResults[i].Provider.ProviderId }, _jsonSerializer); diff --git a/src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs b/src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs index 619ca0a49..97711c0e3 100644 --- a/src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs +++ b/src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs @@ -3,13 +3,11 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using Microsoft.PowerShell.EditorServices.Commands; -using Microsoft.PowerShell.EditorServices.Symbols; -using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.PowerShell.EditorServices.Commands; +using Microsoft.PowerShell.EditorServices.Symbols; namespace Microsoft.PowerShell.EditorServices.CodeLenses { @@ -54,7 +52,11 @@ private CodeLens[] GetPesterLens( new ClientCommand( "PowerShell.RunPesterTests", "Run tests", - new object[] { scriptFile.ClientFilePath, false /* No debug */, pesterSymbol.TestName })), + new object[] { + scriptFile.DocumentUri, + false /* No debug */, + pesterSymbol.TestName, + pesterSymbol.ScriptRegion?.StartLineNumber })), new CodeLens( this, @@ -63,7 +65,11 @@ private CodeLens[] GetPesterLens( new ClientCommand( "PowerShell.RunPesterTests", "Debug tests", - new object[] { scriptFile.ClientFilePath, true /* Run in debugger */, pesterSymbol.TestName })), + new object[] { + scriptFile.DocumentUri, + true /* Run in the debugger */, + pesterSymbol.TestName, + pesterSymbol.ScriptRegion?.StartLineNumber })), }; return codeLensResults; diff --git a/src/PowerShellEditorServices.Host/CodeLens/ReferencesCodeLensProvider.cs b/src/PowerShellEditorServices.Host/CodeLens/ReferencesCodeLensProvider.cs index c833aec27..223232499 100644 --- a/src/PowerShellEditorServices.Host/CodeLens/ReferencesCodeLensProvider.cs +++ b/src/PowerShellEditorServices.Host/CodeLens/ReferencesCodeLensProvider.cs @@ -118,7 +118,7 @@ public async Task ResolveCodeLensAsync( GetReferenceCountHeader(referenceLocations.Length), new object[] { - codeLens.File.ClientFilePath, + codeLens.File.DocumentUri, codeLens.ScriptExtent.ToRange().Start, referenceLocations, } diff --git a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs index 0432fa2ee..af228029d 100644 --- a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs +++ b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs @@ -1738,7 +1738,7 @@ private static async Task PublishScriptDiagnostics( diagnostics.Add(markerDiagnostic); } - correctionIndex[scriptFile.ClientFilePath] = fileCorrections; + correctionIndex[scriptFile.DocumentUri] = fileCorrections; // Always send syntax and semantic errors. We want to // make sure no out-of-date markers are being displayed. @@ -1746,7 +1746,7 @@ await eventSender( PublishDiagnosticsNotification.Type, new PublishDiagnosticsNotification { - Uri = scriptFile.ClientFilePath, + Uri = scriptFile.DocumentUri, Diagnostics = diagnostics.ToArray() }); } diff --git a/src/PowerShellEditorServices/Workspace/ScriptFile.cs b/src/PowerShellEditorServices/Workspace/ScriptFile.cs index 0e0d4d6e7..08e9c6a00 100644 --- a/src/PowerShellEditorServices/Workspace/ScriptFile.cs +++ b/src/PowerShellEditorServices/Workspace/ScriptFile.cs @@ -3,13 +3,14 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using Microsoft.PowerShell.EditorServices.Utility; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Management.Automation; using System.Management.Automation.Language; +using System.Runtime.InteropServices; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices { @@ -52,6 +53,19 @@ public string Id /// public string ClientFilePath { get; private set; } + /// + /// Gets the file path in LSP DocumentUri form. The ClientPath property must not be null. + /// + public string DocumentUri + { + get + { + return this.ClientFilePath == null + ? string.Empty + : Workspace.ConvertPathToDocumentUri(this.ClientFilePath); + } + } + /// /// Gets or sets a boolean that determines whether /// semantic analysis should be enabled for this file. diff --git a/src/PowerShellEditorServices/Workspace/Workspace.cs b/src/PowerShellEditorServices/Workspace/Workspace.cs index 6f9cb8beb..99b029dea 100644 --- a/src/PowerShellEditorServices/Workspace/Workspace.cs +++ b/src/PowerShellEditorServices/Workspace/Workspace.cs @@ -350,7 +350,7 @@ private void RecursivelyEnumerateFiles(string folderPath, ref List found this.logger.WriteHandledException( $"Could not enumerate files in the path '{folderPath}' due to an exception", e); - + continue; } @@ -399,7 +399,7 @@ private void RecursivelyEnumerateFiles(string folderPath, ref List found this.logger.WriteHandledException( $"Could not enumerate directories in the path '{folderPath}' due to an exception", e); - + return; } @@ -624,6 +624,69 @@ private static string UnescapeDriveColon(string fileUri) return sb.ToString(); } + /// + /// Converts a file system path into a DocumentUri required by Language Server Protocol. + /// + /// + /// When sending a document path to a LSP client, the path must be provided as a + /// DocumentUri in order to features like the Problems window or peek definition + /// to be able to open the specified file. + /// + /// + /// A file system path. Note: if the path is already a DocumentUri, it will be returned unmodified. + /// + /// The file system path encoded as a DocumentUri. + internal static string ConvertPathToDocumentUri(string path) + { + const string fileUriPrefix = "file:///"; + + if (path.StartsWith("untitled:", StringComparison.Ordinal)) + { + return path; + } + + if (path.StartsWith(fileUriPrefix, StringComparison.Ordinal)) + { + return path; + } + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // On a Linux filesystem, you can have multiple colons in a filename e.g. foo:bar:baz.txt + string absoluteUri = new Uri(path).AbsoluteUri; + + // First colon is part of the protocol scheme, see if there are other colons in the path + int firstColonIndex = absoluteUri.IndexOf(':'); + if (absoluteUri.IndexOf(':', firstColonIndex + 1) > firstColonIndex) + { + absoluteUri = new StringBuilder(absoluteUri) + .Replace( + oldValue: ":", + newValue: "%3A", + startIndex: firstColonIndex + 1, + count: absoluteUri.Length - firstColonIndex - 1) + .ToString(); + } + + return absoluteUri; + } + + // VSCode file URIs on Windows need the drive letter lowercase, and the colon + // URI encoded. System.Uri won't do that, so we manually create the URI. + var newUri = new StringBuilder(System.Web.HttpUtility.UrlPathEncode(path)); + int colonIndex = path.IndexOf(':'); + if (colonIndex > 0) + { + int driveLetterIndex = colonIndex - 1; + char driveLetter = char.ToLowerInvariant(path[driveLetterIndex]); + newUri + .Replace(path[driveLetterIndex], driveLetter, driveLetterIndex, 1) + .Replace(":", "%3A", colonIndex, 1); + } + + return newUri.Replace('\\', '/').Insert(0, fileUriPrefix).ToString(); + } + #endregion } } diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 18ce3cf30..c259b44be 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -97,7 +97,7 @@ public static IEnumerable DebuggerAcceptsScriptArgsTestData } [Theory] - [MemberData("DebuggerAcceptsScriptArgsTestData")] + [MemberData(nameof(DebuggerAcceptsScriptArgsTestData))] public async Task DebuggerAcceptsScriptArgs(string[] args) { // The path is intentionally odd (some escaped chars but not all) because we are testing diff --git a/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs index 993e56999..dc781e06b 100644 --- a/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs +++ b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs @@ -570,6 +570,7 @@ public void PropertiesInitializedCorrectlyForUntitled() Assert.Equal(path, scriptFile.FilePath); Assert.Equal(path, scriptFile.ClientFilePath); + Assert.Equal(path, scriptFile.DocumentUri); Assert.True(scriptFile.IsAnalysisEnabled); Assert.True(scriptFile.IsInMemory); Assert.Empty(scriptFile.ReferencedFiles); @@ -578,5 +579,35 @@ public void PropertiesInitializedCorrectlyForUntitled() Assert.Equal(3, scriptFile.FileLines.Count); } } + + [Fact] + public void DocumentUriRetunsCorrectStringForAbsolutePath() + { + string path; + ScriptFile scriptFile; + var emptyStringReader = new StringReader(""); + + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + path = @"C:\Users\AmosBurton\projects\Rocinate\ProtoMolecule.ps1"; + scriptFile = new ScriptFile(path, path, emptyStringReader, PowerShellVersion); + Assert.Equal("file:///c%3A/Users/AmosBurton/projects/Rocinate/ProtoMolecule.ps1", scriptFile.DocumentUri); + } + else + { + // Test the following only on Linux and macOS. + path = "/home/AlexKamal/projects/Rocinate/ProtoMolecule.ps1"; + scriptFile = new ScriptFile(path, path, emptyStringReader, PowerShellVersion); + Assert.Equal("file:///home/AlexKamal/projects/Rocinate/ProtoMolecule.ps1", scriptFile.DocumentUri); + + path = "/home/NaomiNagata/projects/Rocinate/Proto:Mole:cule.ps1"; + scriptFile = new ScriptFile(path, path, emptyStringReader, PowerShellVersion); + Assert.Equal("file:///home/NaomiNagata/projects/Rocinate/Proto%3AMole%3Acule.ps1", scriptFile.DocumentUri); + + path = "/home/JamesHolden/projects/Rocinate/Proto:Mole\\cule.ps1"; + scriptFile = new ScriptFile(path, path, emptyStringReader, PowerShellVersion); + Assert.Equal("file:///home/JamesHolden/projects/Rocinate/Proto%3AMole%5Ccule.ps1", scriptFile.DocumentUri); + } + } } } From d1e257922bcb3d5ef61a05e258a9589cdc4d1714 Mon Sep 17 00:00:00 2001 From: Keith Hill Date: Sun, 17 Mar 2019 22:21:22 -0600 Subject: [PATCH 2/4] Fix docUri issue with Reference CodeLens provider Encode more chars like %, &, ' , etc --- .../CodeLens/ReferencesCodeLensProvider.cs | 2 +- .../Workspace/Workspace.cs | 52 +++++++++---------- .../Session/ScriptFileTests.cs | 8 +++ 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/src/PowerShellEditorServices.Host/CodeLens/ReferencesCodeLensProvider.cs b/src/PowerShellEditorServices.Host/CodeLens/ReferencesCodeLensProvider.cs index 223232499..3c961742d 100644 --- a/src/PowerShellEditorServices.Host/CodeLens/ReferencesCodeLensProvider.cs +++ b/src/PowerShellEditorServices.Host/CodeLens/ReferencesCodeLensProvider.cs @@ -151,7 +151,7 @@ private static string GetFileUri(string filePath) // If the file isn't untitled, return a URI-style path return !filePath.StartsWith("untitled") && !filePath.StartsWith("inmemory") - ? new Uri("file://" + filePath).AbsoluteUri + ? Workspace.ConvertPathToDocumentUri(filePath) : filePath; } diff --git a/src/PowerShellEditorServices/Workspace/Workspace.cs b/src/PowerShellEditorServices/Workspace/Workspace.cs index 99b029dea..a2fc2b881 100644 --- a/src/PowerShellEditorServices/Workspace/Workspace.cs +++ b/src/PowerShellEditorServices/Workspace/Workspace.cs @@ -636,7 +636,7 @@ private static string UnescapeDriveColon(string fileUri) /// A file system path. Note: if the path is already a DocumentUri, it will be returned unmodified. /// /// The file system path encoded as a DocumentUri. - internal static string ConvertPathToDocumentUri(string path) + public static string ConvertPathToDocumentUri(string path) { const string fileUriPrefix = "file:///"; @@ -650,41 +650,37 @@ internal static string ConvertPathToDocumentUri(string path) return path; } - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // On a Linux filesystem, you can have multiple colons in a filename e.g. foo:bar:baz.txt - string absoluteUri = new Uri(path).AbsoluteUri; + string escapedPath = Uri.EscapeDataString(path); + var docUriStrBld = new StringBuilder(escapedPath); - // First colon is part of the protocol scheme, see if there are other colons in the path - int firstColonIndex = absoluteUri.IndexOf(':'); - if (absoluteUri.IndexOf(':', firstColonIndex + 1) > firstColonIndex) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // VSCode file URIs on Windows need the drive letter lowercase. + if (path.Contains(':')) { - absoluteUri = new StringBuilder(absoluteUri) - .Replace( - oldValue: ":", - newValue: "%3A", - startIndex: firstColonIndex + 1, - count: absoluteUri.Length - firstColonIndex - 1) - .ToString(); + for (int i = 1; i < docUriStrBld.Length - 2; i++) + { + if ((docUriStrBld[i] == '%') && (docUriStrBld[i + 1] == '3') && (docUriStrBld[i + 2] == 'A')) + { + int driveLetterIndex = i - 1; + char driveLetter = char.ToLowerInvariant(docUriStrBld[driveLetterIndex]); + docUriStrBld.Replace(path[driveLetterIndex], driveLetter, driveLetterIndex, 1); + break; + } + } } - return absoluteUri; + // Uri.EscapeDataString goes a bit far, encoding \ chars. Besides VSCode wants / instead of \. + docUriStrBld.Replace("%5C", "/"); } - - // VSCode file URIs on Windows need the drive letter lowercase, and the colon - // URI encoded. System.Uri won't do that, so we manually create the URI. - var newUri = new StringBuilder(System.Web.HttpUtility.UrlPathEncode(path)); - int colonIndex = path.IndexOf(':'); - if (colonIndex > 0) + else { - int driveLetterIndex = colonIndex - 1; - char driveLetter = char.ToLowerInvariant(path[driveLetterIndex]); - newUri - .Replace(path[driveLetterIndex], driveLetter, driveLetterIndex, 1) - .Replace(":", "%3A", colonIndex, 1); + // Uri.EscapeDataString goes a bit far, encoding / chars. + docUriStrBld.Replace("%2F", "", 0, 3).Replace("%2F", "/"); } - return newUri.Replace('\\', '/').Insert(0, fileUriPrefix).ToString(); + // ' is not always encoded. I've seen this in Windows PowerShell. + return docUriStrBld.Replace("'", "%27").Insert(0, fileUriPrefix).ToString(); } #endregion diff --git a/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs index dc781e06b..f9555c40a 100644 --- a/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs +++ b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs @@ -592,6 +592,10 @@ public void DocumentUriRetunsCorrectStringForAbsolutePath() path = @"C:\Users\AmosBurton\projects\Rocinate\ProtoMolecule.ps1"; scriptFile = new ScriptFile(path, path, emptyStringReader, PowerShellVersion); Assert.Equal("file:///c%3A/Users/AmosBurton/projects/Rocinate/ProtoMolecule.ps1", scriptFile.DocumentUri); + + path = @"c:\Users\BobbyDraper\projects\Rocinate\foo's_~#-[@] +,;=%.ps1"; + scriptFile = new ScriptFile(path, path, emptyStringReader, PowerShellVersion); + Assert.Equal("file:///c%3A/Users/BobbyDraper/projects/Rocinate/foo%27s_~%23-%5B%40%5D%20%2B%2C%3B%3D%25.ps1", scriptFile.DocumentUri); } else { @@ -600,6 +604,10 @@ public void DocumentUriRetunsCorrectStringForAbsolutePath() scriptFile = new ScriptFile(path, path, emptyStringReader, PowerShellVersion); Assert.Equal("file:///home/AlexKamal/projects/Rocinate/ProtoMolecule.ps1", scriptFile.DocumentUri); + path = "/home/BobbyDraper/projects/Rocinate/foo's_~#-[@] +,;=%.ps1"; + scriptFile = new ScriptFile(path, path, emptyStringReader, PowerShellVersion); + Assert.Equal("file:///home/BobbyDraper/projects/Rocinate/foo%27s_~%23-%5B%40%5D%20%2B%2C%3B%3D%25.ps1", scriptFile.DocumentUri); + path = "/home/NaomiNagata/projects/Rocinate/Proto:Mole:cule.ps1"; scriptFile = new ScriptFile(path, path, emptyStringReader, PowerShellVersion); Assert.Equal("file:///home/NaomiNagata/projects/Rocinate/Proto%3AMole%3Acule.ps1", scriptFile.DocumentUri); From dc8547377a76301a1830aa3a4e7561f7e7f608f0 Mon Sep 17 00:00:00 2001 From: Keith Hill Date: Mon, 18 Mar 2019 13:01:30 -0600 Subject: [PATCH 3/4] Address PR feedback --- .../CodeLens/PesterCodeLensProvider.cs | 18 ++++++------------ .../Workspace/Workspace.cs | 18 +++++++++++------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs b/src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs index 97711c0e3..619ca0a49 100644 --- a/src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs +++ b/src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs @@ -3,11 +3,13 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using Microsoft.PowerShell.EditorServices.Commands; +using Microsoft.PowerShell.EditorServices.Symbols; +using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Commands; -using Microsoft.PowerShell.EditorServices.Symbols; namespace Microsoft.PowerShell.EditorServices.CodeLenses { @@ -52,11 +54,7 @@ private CodeLens[] GetPesterLens( new ClientCommand( "PowerShell.RunPesterTests", "Run tests", - new object[] { - scriptFile.DocumentUri, - false /* No debug */, - pesterSymbol.TestName, - pesterSymbol.ScriptRegion?.StartLineNumber })), + new object[] { scriptFile.ClientFilePath, false /* No debug */, pesterSymbol.TestName })), new CodeLens( this, @@ -65,11 +63,7 @@ private CodeLens[] GetPesterLens( new ClientCommand( "PowerShell.RunPesterTests", "Debug tests", - new object[] { - scriptFile.DocumentUri, - true /* Run in the debugger */, - pesterSymbol.TestName, - pesterSymbol.ScriptRegion?.StartLineNumber })), + new object[] { scriptFile.ClientFilePath, true /* Run in debugger */, pesterSymbol.TestName })), }; return codeLensResults; diff --git a/src/PowerShellEditorServices/Workspace/Workspace.cs b/src/PowerShellEditorServices/Workspace/Workspace.cs index a2fc2b881..b90799703 100644 --- a/src/PowerShellEditorServices/Workspace/Workspace.cs +++ b/src/PowerShellEditorServices/Workspace/Workspace.cs @@ -639,13 +639,11 @@ private static string UnescapeDriveColon(string fileUri) public static string ConvertPathToDocumentUri(string path) { const string fileUriPrefix = "file:///"; + const string untitledUriPrefix = "untitled:"; - if (path.StartsWith("untitled:", StringComparison.Ordinal)) - { - return path; - } - - if (path.StartsWith(fileUriPrefix, StringComparison.Ordinal)) + // If path is already in document uri form, there is nothing to convert. + if (path.StartsWith(untitledUriPrefix, StringComparison.Ordinal) || + path.StartsWith(fileUriPrefix, StringComparison.Ordinal)) { return path; } @@ -656,8 +654,12 @@ public static string ConvertPathToDocumentUri(string path) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { // VSCode file URIs on Windows need the drive letter lowercase. + // Search original path for colon since a char search (no string culture involved) + // is faster than a string search. if (path.Contains(':')) { + // Start at index 1 to avoid an index out of range check when accessing index - 1. + // Also, if the colon is at index 0 there is no drive letter before it to lower case. for (int i = 1; i < docUriStrBld.Length - 2; i++) { if ((docUriStrBld[i] == '%') && (docUriStrBld[i + 1] == '3') && (docUriStrBld[i + 2] == 'A')) @@ -670,11 +672,13 @@ public static string ConvertPathToDocumentUri(string path) } } - // Uri.EscapeDataString goes a bit far, encoding \ chars. Besides VSCode wants / instead of \. + // Uri.EscapeDataString goes a bit far, encoding \ chars. Also, VSCode wants / instead of \. docUriStrBld.Replace("%5C", "/"); } else { + // Because we will prefix later with file:///, remove the initial encoded / if this is an absolute path. + // See https://docs.microsoft.com/en-us/dotnet/api/system.uri?view=netframework-4.7.2#implicit-file-path-support // Uri.EscapeDataString goes a bit far, encoding / chars. docUriStrBld.Replace("%2F", "", 0, 3).Replace("%2F", "/"); } From e084c45c759f555c91025508c7c46822c4d649a0 Mon Sep 17 00:00:00 2001 From: Patrick Meinecke Date: Tue, 19 Mar 2019 18:48:00 -0600 Subject: [PATCH 4/4] Update src/PowerShellEditorServices/Workspace/Workspace.cs Co-Authored-By: rkeithhill --- src/PowerShellEditorServices/Workspace/Workspace.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Workspace/Workspace.cs b/src/PowerShellEditorServices/Workspace/Workspace.cs index b90799703..f2fb07b98 100644 --- a/src/PowerShellEditorServices/Workspace/Workspace.cs +++ b/src/PowerShellEditorServices/Workspace/Workspace.cs @@ -680,7 +680,7 @@ public static string ConvertPathToDocumentUri(string path) // Because we will prefix later with file:///, remove the initial encoded / if this is an absolute path. // See https://docs.microsoft.com/en-us/dotnet/api/system.uri?view=netframework-4.7.2#implicit-file-path-support // Uri.EscapeDataString goes a bit far, encoding / chars. - docUriStrBld.Replace("%2F", "", 0, 3).Replace("%2F", "/"); + docUriStrBld.Replace("%2F", string.Empty, 0, 3).Replace("%2F", "/"); } // ' is not always encoded. I've seen this in Windows PowerShell.