Skip to content

Cherry pick PR 1750 to legacy/1.x branch, fix more issues #880

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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,
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public async Task<CodeLens> ResolveCodeLensAsync(
GetReferenceCountHeader(referenceLocations.Length),
new object[]
{
codeLens.File.ClientFilePath,
codeLens.File.DocumentUri,
codeLens.ScriptExtent.ToRange().Start,
referenceLocations,
}
Expand Down Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1738,15 +1738,15 @@ 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.
await eventSender(
PublishDiagnosticsNotification.Type,
new PublishDiagnosticsNotification
{
Uri = scriptFile.ClientFilePath,
Uri = scriptFile.DocumentUri,
Diagnostics = diagnostics.ToArray()
});
}
Expand Down
16 changes: 15 additions & 1 deletion src/PowerShellEditorServices/Workspace/ScriptFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -52,6 +53,19 @@ public string Id
/// </summary>
public string ClientFilePath { get; private set; }

/// <summary>
/// Gets the file path in LSP DocumentUri form. The ClientPath property must not be null.
/// </summary>
public string DocumentUri
{
get
{
return this.ClientFilePath == null
? string.Empty
: Workspace.ConvertPathToDocumentUri(this.ClientFilePath);
}
}

/// <summary>
/// Gets or sets a boolean that determines whether
/// semantic analysis should be enabled for this file.
Expand Down
63 changes: 61 additions & 2 deletions src/PowerShellEditorServices/Workspace/Workspace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ private void RecursivelyEnumerateFiles(string folderPath, ref List<string> found
this.logger.WriteHandledException(
$"Could not enumerate files in the path '{folderPath}' due to an exception",
e);

continue;
}

Expand Down Expand Up @@ -399,7 +399,7 @@ private void RecursivelyEnumerateFiles(string folderPath, ref List<string> found
this.logger.WriteHandledException(
$"Could not enumerate directories in the path '{folderPath}' due to an exception",
e);

return;
}

Expand Down Expand Up @@ -624,6 +624,65 @@ private static string UnescapeDriveColon(string fileUri)
return sb.ToString();
}

/// <summary>
/// Converts a file system path into a DocumentUri required by Language Server Protocol.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="path">
/// A file system path. Note: if the path is already a DocumentUri, it will be returned unmodified.
/// </param>
/// <returns>The file system path encoded as a DocumentUri.</returns>
public 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;
}

string escapedPath = Uri.EscapeDataString(path);
var docUriStrBld = new StringBuilder(escapedPath);

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// VSCode file URIs on Windows need the drive letter lowercase.
if (path.Contains(':'))
{
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;
}
}
}

// Uri.EscapeDataString goes a bit far, encoding \ chars. Besides VSCode wants / instead of \.
docUriStrBld.Replace("%5C", "/");
}
else
{
// Uri.EscapeDataString goes a bit far, encoding / chars.
docUriStrBld.Replace("%2F", "", 0, 3).Replace("%2F", "/");
}

// ' is not always encoded. I've seen this in Windows PowerShell.
return docUriStrBld.Replace("'", "%27").Insert(0, fileUriPrefix).ToString();
}

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public static IEnumerable<object[]> 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
Expand Down
39 changes: 39 additions & 0 deletions test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -578,5 +579,43 @@ 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);

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
{
// 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/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);

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);
}
}
}
}