diff --git a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs index 60261d492..86aefa5ee 100644 --- a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs +++ b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs @@ -141,6 +141,13 @@ protected async Task HandleInitializeRequest( // Grab the workspace path from the parameters editorSession.Workspace.WorkspacePath = initializeParams.RootPath; + // Set the working directory of the PowerShell session to the workspace path + if (editorSession.Workspace.WorkspacePath != null) + { + editorSession.PowerShellContext.SetWorkingDirectory( + editorSession.Workspace.WorkspacePath); + } + await requestContext.SendResult( new InitializeResult { diff --git a/src/PowerShellEditorServices.Protocol/Server/LanguageServerEditorOperations.cs b/src/PowerShellEditorServices.Protocol/Server/LanguageServerEditorOperations.cs index f60772527..b8c1da92c 100644 --- a/src/PowerShellEditorServices.Protocol/Server/LanguageServerEditorOperations.cs +++ b/src/PowerShellEditorServices.Protocol/Server/LanguageServerEditorOperations.cs @@ -7,7 +7,6 @@ using Microsoft.PowerShell.EditorServices.Protocol.LanguageServer; using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; using System.Threading.Tasks; -using System; namespace Microsoft.PowerShell.EditorServices.Protocol.Server { @@ -111,6 +110,16 @@ public Task OpenFile(string filePath) true); } + public string GetWorkspacePath() + { + return this.editorSession.Workspace.WorkspacePath; + } + + public string GetWorkspaceRelativePath(string filePath) + { + return this.editorSession.Workspace.GetRelativePath(filePath); + } + public Task ShowInformationMessage(string message) { return diff --git a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs b/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs index a9598f09c..68d113523 100644 --- a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs +++ b/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs @@ -17,6 +17,18 @@ public class EditorWorkspace #endregion + #region Properties + + /// + /// Gets the current workspace path if there is one or null otherwise. + /// + public string Path + { + get { return this.editorOperations.GetWorkspacePath(); } + } + + #endregion + #region Constructors internal EditorWorkspace(IEditorOperations editorOperations) diff --git a/src/PowerShellEditorServices/Extensions/FileContext.cs b/src/PowerShellEditorServices/Extensions/FileContext.cs index 9b7f7f8d7..8eb8d2812 100644 --- a/src/PowerShellEditorServices/Extensions/FileContext.cs +++ b/src/PowerShellEditorServices/Extensions/FileContext.cs @@ -32,6 +32,19 @@ public string Path get { return this.scriptFile.FilePath; } } + /// + /// Gets the workspace-relative path of the file. + /// + public string WorkspacePath + { + get + { + return + this.editorOperations.GetWorkspaceRelativePath( + this.scriptFile.FilePath); + } + } + /// /// Gets the parsed abstract syntax tree for the file. /// diff --git a/src/PowerShellEditorServices/Extensions/IEditorOperations.cs b/src/PowerShellEditorServices/Extensions/IEditorOperations.cs index 7f6845d6e..8e9358e1b 100644 --- a/src/PowerShellEditorServices/Extensions/IEditorOperations.cs +++ b/src/PowerShellEditorServices/Extensions/IEditorOperations.cs @@ -20,6 +20,19 @@ public interface IEditorOperations /// A new EditorContext object. Task GetEditorContext(); + /// + /// Gets the path to the editor's active workspace. + /// + /// The workspace path or null if there isn't one. + string GetWorkspacePath(); + + /// + /// Resolves the given file path relative to the current workspace path. + /// + /// The file path to be resolved. + /// The resolved file path. + string GetWorkspaceRelativePath(string filePath); + /// /// Causes a file to be opened in the editor. If the file is /// already open, the editor must switch to the file. diff --git a/src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs b/src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs index 7ffda9c2a..ea35dd4a1 100644 --- a/src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs +++ b/src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs @@ -32,7 +32,8 @@ public FindDotSourcedVisitor() /// or a decision to continue if it wasn't found public override AstVisitAction VisitCommand(CommandAst commandAst) { - if (commandAst.InvocationOperator.Equals(TokenKind.Dot)) + if (commandAst.InvocationOperator.Equals(TokenKind.Dot) && + commandAst.CommandElements[0] is StringConstantExpressionAst) { // Strip any quote characters off of the string string fileName = commandAst.CommandElements[0].Extent.Text.Trim('\'', '"'); diff --git a/src/PowerShellEditorServices/Session/SessionPSHostUserInterface.cs b/src/PowerShellEditorServices/Session/SessionPSHostUserInterface.cs index 921120c85..18eba2735 100644 --- a/src/PowerShellEditorServices/Session/SessionPSHostUserInterface.cs +++ b/src/PowerShellEditorServices/Session/SessionPSHostUserInterface.cs @@ -320,10 +320,8 @@ private void WaitForPromptCompletion( try { // This will synchronously block on the prompt task - // method which gets run on another thread. Use a - // 30 second timeout so that everything doesn't get - // backed up if the user doesn't respond. - promptTask.Wait(15000); + // method which gets run on another thread. + promptTask.Wait(); if (promptTask.Status == TaskStatus.WaitingForActivation) { diff --git a/src/PowerShellEditorServices/Workspace/Workspace.cs b/src/PowerShellEditorServices/Workspace/Workspace.cs index 24ca0932e..8e66f4599 100644 --- a/src/PowerShellEditorServices/Workspace/Workspace.cs +++ b/src/PowerShellEditorServices/Workspace/Workspace.cs @@ -176,6 +176,32 @@ public ScriptFile[] ExpandScriptReferences(ScriptFile scriptFile) return expandedReferences.ToArray(); } + /// + /// Gets the workspace-relative path of the given file path. + /// + /// The original full file path. + /// A relative file path + public string GetRelativePath(string filePath) + { + string resolvedPath = filePath; + + if (!IsPathInMemory(filePath) && !string.IsNullOrEmpty(this.WorkspacePath)) + { + Uri workspaceUri = new Uri(this.WorkspacePath); + Uri fileUri = new Uri(filePath); + + resolvedPath = workspaceUri.MakeRelativeUri(fileUri).ToString(); + + // Convert the directory separators if necessary + if (System.IO.Path.DirectorySeparatorChar == '\\') + { + resolvedPath = resolvedPath.Replace('/', '\\'); + } + } + + return resolvedPath; + } + #endregion #region Private Methods @@ -264,10 +290,13 @@ internal static bool IsPathInMemory(string filePath) // When viewing PowerShell files in the Git diff viewer, VS Code // sends the contents of the file at HEAD with a URI that starts // with 'inmemory'. Untitled files which have been marked of - // type PowerShell have a path starting with 'untitled'. + // type PowerShell have a path starting with 'untitled'. Files + // opened from the find/replace view will be prefixed with + // 'private'. return filePath.StartsWith("inmemory") || - filePath.StartsWith("untitled"); + filePath.StartsWith("untitled") || + filePath.StartsWith("private"); } private string GetBaseFilePath(string filePath) diff --git a/test/PowerShellEditorServices.Test/Extensions/ExtensionServiceTests.cs b/test/PowerShellEditorServices.Test/Extensions/ExtensionServiceTests.cs index 84475829a..3ac6a22f3 100644 --- a/test/PowerShellEditorServices.Test/Extensions/ExtensionServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Extensions/ExtensionServiceTests.cs @@ -168,6 +168,16 @@ await this.extensionEventQueue.EnqueueAsync( public class TestEditorOperations : IEditorOperations { + public string GetWorkspacePath() + { + throw new NotImplementedException(); + } + + public string GetWorkspaceRelativePath(string filePath) + { + throw new NotImplementedException(); + } + public Task OpenFile(string filePath) { throw new NotImplementedException(); diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 5a9a43cd9..3d5531cda 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -73,6 +73,7 @@ + diff --git a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs new file mode 100644 index 000000000..16b58987f --- /dev/null +++ b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.PowerShell.EditorServices; +using System; +using System.IO; +using System.Linq; +using Xunit; + +namespace Microsoft.PowerShell.EditorServices.Test.Session +{ + public class WorkspaceTests + { + private static readonly Version PowerShellVersion = new Version("5.0"); + + [Fact] + public void CanResolveWorkspaceRelativePath() + { + string workspacePath = @"c:\Test\Workspace\"; + string testPathInside = @"c:\Test\Workspace\SubFolder\FilePath.ps1"; + string testPathOutside = @"c:\Test\PeerPath\FilePath.ps1"; + string testPathAnotherDrive = @"z:\TryAndFindMe\FilePath.ps1"; + + Workspace workspace = new Workspace(PowerShellVersion); + + // Test without a workspace path + Assert.Equal(testPathOutside, workspace.GetRelativePath(testPathOutside)); + + // Test with a workspace path + workspace.WorkspacePath = workspacePath; + Assert.Equal(@"SubFolder\FilePath.ps1", workspace.GetRelativePath(testPathInside)); + Assert.Equal(@"..\PeerPath\FilePath.ps1", workspace.GetRelativePath(testPathOutside)); + Assert.Equal(testPathAnotherDrive, workspace.GetRelativePath(testPathAnotherDrive)); + } + } +}