From 7f13a1456e7c4fcb244f30bfbf39cb849530b055 Mon Sep 17 00:00:00 2001
From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com>
Date: Wed, 16 Aug 2023 18:53:48 -0700
Subject: [PATCH 1/4] Fix up extension API
Receive a `EditorOperationResponse` (not yet otherwise used). Delete
dead code. Add optional `content` parameter to `NewFile`. Expose
`CloseFile` and `SaveFile`. Fix an outdated warning message.
---
.../Extensions/EditorRequests.cs | 6 +--
.../Extensions/EditorWindow.cs | 3 --
.../Extensions/EditorWorkspace.cs | 34 +++++++++++++----
.../Extensions/IEditorOperations.cs | 8 ++--
.../Extension/EditorOperationsService.cs | 37 +++++++++----------
5 files changed, 52 insertions(+), 36 deletions(-)
diff --git a/src/PowerShellEditorServices/Extensions/EditorRequests.cs b/src/PowerShellEditorServices/Extensions/EditorRequests.cs
index 446cd2dfc..34dca2928 100644
--- a/src/PowerShellEditorServices/Extensions/EditorRequests.cs
+++ b/src/PowerShellEditorServices/Extensions/EditorRequests.cs
@@ -25,10 +25,10 @@ internal class ExtensionCommandRemovedNotification
internal class GetEditorContextRequest
{ }
- internal enum EditorCommandResponse
+ internal enum EditorOperationResponse
{
- Unsupported,
- OK
+ Completed,
+ Failed
}
internal class InsertTextRequest
diff --git a/src/PowerShellEditorServices/Extensions/EditorWindow.cs b/src/PowerShellEditorServices/Extensions/EditorWindow.cs
index 428c192ea..8c9048c3c 100644
--- a/src/PowerShellEditorServices/Extensions/EditorWindow.cs
+++ b/src/PowerShellEditorServices/Extensions/EditorWindow.cs
@@ -39,8 +39,6 @@ internal EditorWindow(IEditorOperations editorOperations)
#endregion
#region Public Methods
- #pragma warning disable VSTHRD002 // These are public APIs that use async internal methods.
-
///
/// Shows an informational message to the user.
///
@@ -72,7 +70,6 @@ internal EditorWindow(IEditorOperations editorOperations)
/// A timeout in milliseconds for how long the message should remain visible.
public void SetStatusBarMessage(string message, int timeout) => editorOperations.SetStatusBarMessageAsync(message, timeout).Wait();
- #pragma warning restore VSTHRD002
#endregion
}
}
diff --git a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs b/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs
index aa5802564..d12de06e3 100644
--- a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs
+++ b/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs
@@ -18,7 +18,8 @@ public sealed class EditorWorkspace
#region Properties
///
- /// Gets the current workspace path if there is one for the open editor or null otherwise.
+ /// Gets the server's initial working directory, since the extension API doesn't have a
+ /// multi-root workspace concept.
///
public string Path => editorOperations.GetWorkspacePath();
@@ -31,22 +32,23 @@ public sealed class EditorWorkspace
#endregion
#region Public Methods
- #pragma warning disable VSTHRD002 // These are public APIs that use async internal methods.
+ // TODO: Consider returning bool instead of void to indicate success?
///
- /// Creates a new file in the editor
+ /// Creates a new file in the editor.
///
- public void NewFile() => editorOperations.NewFileAsync().Wait();
+ /// The content to place in the new file.
+ public void NewFile(string content = "") => editorOperations.NewFileAsync(content).Wait();
///
- /// Opens a file in the workspace. If the file is already open
+ /// Opens a file in the workspace. If the file is already open
/// its buffer will be made active.
///
/// The path to the file to be opened.
public void OpenFile(string filePath) => editorOperations.OpenFileAsync(filePath).Wait();
///
- /// Opens a file in the workspace. If the file is already open
+ /// Opens a file in the workspace. If the file is already open
/// its buffer will be made active.
/// You can specify whether the file opens as a preview or as a durable editor.
///
@@ -54,7 +56,25 @@ public sealed class EditorWorkspace
/// Determines wether the file is opened as a preview or as a durable editor.
public void OpenFile(string filePath, bool preview) => editorOperations.OpenFileAsync(filePath, preview).Wait();
- #pragma warning restore VSTHRD002
+ ///
+ /// Closes a file in the workspace.
+ ///
+ /// The path to the file to be closed.
+ public void CloseFile(string filePath) => editorOperations.CloseFileAsync(filePath).Wait();
+
+ ///
+ /// Saves an open file in the workspace.
+ ///
+ /// The path to the file to be saved.
+ public void SaveFile(string filePath) => editorOperations.SaveFileAsync(filePath).Wait();
+
+ ///
+ /// Saves a file with a new name AKA a copy.
+ ///
+ /// The file to copy.
+ /// The file to create.
+ public void SaveFile(string oldFilePath, string newFilePath) => editorOperations.SaveFileAsync(oldFilePath, newFilePath).Wait();
+
#endregion
}
}
diff --git a/src/PowerShellEditorServices/Extensions/IEditorOperations.cs b/src/PowerShellEditorServices/Extensions/IEditorOperations.cs
index 6f282eeea..07472a487 100644
--- a/src/PowerShellEditorServices/Extensions/IEditorOperations.cs
+++ b/src/PowerShellEditorServices/Extensions/IEditorOperations.cs
@@ -20,9 +20,10 @@ internal interface IEditorOperations
Task GetEditorContextAsync();
///
- /// Gets the path to the editor's active workspace.
+ /// Gets the server's initial working directory, since the extension API doesn't have a
+ /// multi-root workspace concept.
///
- /// The workspace path or null if there isn't one.
+ /// The server's initial working directory.
string GetWorkspacePath();
///
@@ -35,8 +36,9 @@ internal interface IEditorOperations
///
/// Causes a new untitled file to be created in the editor.
///
+ /// The content to insert into the new file.
/// A task that can be awaited for completion.
- Task NewFileAsync();
+ Task NewFileAsync(string content = "");
///
/// Causes a file to be opened in the editor. If the file is
diff --git a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs
index fa6da7f90..949c2321e 100644
--- a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs
+++ b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs
@@ -13,11 +13,8 @@ namespace Microsoft.PowerShell.EditorServices.Services.Extension
{
internal class EditorOperationsService : IEditorOperations
{
- private const bool DefaultPreviewSetting = true;
-
private readonly PsesInternalHost _psesHost;
private readonly WorkspaceService _workspaceService;
-
private readonly ILanguageServerFacade _languageServer;
public EditorOperationsService(
@@ -72,7 +69,7 @@ public async Task InsertTextAsync(string filePath, string text, BufferRange inse
Character = insertRange.End.Column - 1
}
}
- }).ReturningVoid(CancellationToken.None).ConfigureAwait(false);
+ }).Returning(CancellationToken.None).ConfigureAwait(false);
}
public async Task SetSelectionAsync(BufferRange selectionRange)
@@ -98,7 +95,7 @@ public async Task SetSelectionAsync(BufferRange selectionRange)
Character = selectionRange.End.Column - 1
}
}
- }).ReturningVoid(CancellationToken.None).ConfigureAwait(false);
+ }).Returning(CancellationToken.None).ConfigureAwait(false);
}
public EditorContext ConvertClientEditorContext(
@@ -123,15 +120,15 @@ public EditorContext ConvertClientEditorContext(
clientContext.CurrentFileLanguage);
}
- public async Task NewFileAsync()
+ public async Task NewFileAsync(string content = "")
{
if (!TestHasLanguageServer())
{
return;
}
- await _languageServer.SendRequest("editor/newFile", null)
- .ReturningVoid(CancellationToken.None)
+ await _languageServer.SendRequest("editor/newFile", content)
+ .Returning(CancellationToken.None)
.ConfigureAwait(false);
}
@@ -145,8 +142,8 @@ public async Task OpenFileAsync(string filePath)
await _languageServer.SendRequest("editor/openFile", new OpenFileDetails
{
FilePath = filePath,
- Preview = DefaultPreviewSetting
- }).ReturningVoid(CancellationToken.None).ConfigureAwait(false);
+ Preview = true
+ }).Returning(CancellationToken.None).ConfigureAwait(false);
}
public async Task OpenFileAsync(string filePath, bool preview)
@@ -160,7 +157,7 @@ public async Task OpenFileAsync(string filePath, bool preview)
{
FilePath = filePath,
Preview = preview
- }).ReturningVoid(CancellationToken.None).ConfigureAwait(false);
+ }).Returning(CancellationToken.None).ConfigureAwait(false);
}
public async Task CloseFileAsync(string filePath)
@@ -171,7 +168,7 @@ public async Task CloseFileAsync(string filePath)
}
await _languageServer.SendRequest("editor/closeFile", filePath)
- .ReturningVoid(CancellationToken.None)
+ .Returning(CancellationToken.None)
.ConfigureAwait(false);
}
@@ -188,11 +185,11 @@ public async Task SaveFileAsync(string currentPath, string newSavePath)
{
FilePath = currentPath,
NewPath = newSavePath
- }).ReturningVoid(CancellationToken.None).ConfigureAwait(false);
+ }).Returning(CancellationToken.None).ConfigureAwait(false);
}
- // TODO: This should get the current editor's context and use it to determine which
- // workspace it's in.
+ // NOTE: This name is now outdated since we don't have a way to distinguish one workspace
+ // from another for the extension API.
public string GetWorkspacePath() => _workspaceService.InitialWorkingDirectory;
public string GetWorkspaceRelativePath(string filePath) => _workspaceService.GetRelativePath(filePath);
@@ -205,7 +202,7 @@ public async Task ShowInformationMessageAsync(string message)
}
await _languageServer.SendRequest("editor/showInformationMessage", message)
- .ReturningVoid(CancellationToken.None)
+ .Returning(CancellationToken.None)
.ConfigureAwait(false);
}
@@ -217,7 +214,7 @@ public async Task ShowErrorMessageAsync(string message)
}
await _languageServer.SendRequest("editor/showErrorMessage", message)
- .ReturningVoid(CancellationToken.None)
+ .Returning(CancellationToken.None)
.ConfigureAwait(false);
}
@@ -229,7 +226,7 @@ public async Task ShowWarningMessageAsync(string message)
}
await _languageServer.SendRequest("editor/showWarningMessage", message)
- .ReturningVoid(CancellationToken.None)
+ .Returning(CancellationToken.None)
.ConfigureAwait(false);
}
@@ -244,7 +241,7 @@ public async Task SetStatusBarMessageAsync(string message, int? timeout)
{
Message = message,
Timeout = timeout
- }).ReturningVoid(CancellationToken.None).ConfigureAwait(false);
+ }).Returning(CancellationToken.None).ConfigureAwait(false);
}
public void ClearTerminal()
@@ -267,7 +264,7 @@ private bool TestHasLanguageServer(bool warnUser = true)
if (warnUser)
{
_psesHost.UI.WriteWarningLine(
- "Editor operations are not supported in temporary consoles. Re-run the command in the main PowerShell Intergrated Console.");
+ "Editor operations are not supported in temporary consoles. Re-run the command in the main Extension Terminal.");
}
return false;
From 88dbfc9df7e4c0db6c4aab25c2cd8024dcd10749 Mon Sep 17 00:00:00 2001
From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com>
Date: Thu, 17 Aug 2023 12:56:30 -0700
Subject: [PATCH 2/4] Support multi-root workspaces for
`CurrentFile.RelativePath`
---
.../Extensions/FileContext.cs | 3 +-
.../Extensions/IEditorOperations.cs | 3 +-
.../Extension/EditorOperationsService.cs | 2 +-
.../Handlers/GetCommentHelpHandler.cs | 4 +-
.../Services/TextDocument/ScriptFile.cs | 18 +---
.../Services/Workspace/WorkspaceService.cs | 68 ++++-----------
.../Session/WorkspaceTests.cs | 83 +++++++++----------
7 files changed, 63 insertions(+), 118 deletions(-)
diff --git a/src/PowerShellEditorServices/Extensions/FileContext.cs b/src/PowerShellEditorServices/Extensions/FileContext.cs
index 770cc33f7..c8c32e58e 100644
--- a/src/PowerShellEditorServices/Extensions/FileContext.cs
+++ b/src/PowerShellEditorServices/Extensions/FileContext.cs
@@ -58,8 +58,7 @@ public sealed class FileContext
///
/// Gets the workspace-relative path of the file.
///
- public string WorkspacePath => editorOperations.GetWorkspaceRelativePath(
- scriptFile.FilePath);
+ public string WorkspacePath => editorOperations.GetWorkspaceRelativePath(scriptFile);
#endregion
diff --git a/src/PowerShellEditorServices/Extensions/IEditorOperations.cs b/src/PowerShellEditorServices/Extensions/IEditorOperations.cs
index 07472a487..63bae4574 100644
--- a/src/PowerShellEditorServices/Extensions/IEditorOperations.cs
+++ b/src/PowerShellEditorServices/Extensions/IEditorOperations.cs
@@ -29,9 +29,8 @@ internal interface IEditorOperations
///
/// 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);
+ string GetWorkspaceRelativePath(ScriptFile scriptFile);
///
/// Causes a new untitled file to be created in the editor.
diff --git a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs
index 949c2321e..a187533c5 100644
--- a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs
+++ b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs
@@ -192,7 +192,7 @@ public async Task SaveFileAsync(string currentPath, string newSavePath)
// from another for the extension API.
public string GetWorkspacePath() => _workspaceService.InitialWorkingDirectory;
- public string GetWorkspaceRelativePath(string filePath) => _workspaceService.GetRelativePath(filePath);
+ public string GetWorkspaceRelativePath(ScriptFile scriptFile) => _workspaceService.GetRelativePath(scriptFile);
public async Task ShowInformationMessageAsync(string message)
{
diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommentHelpHandler.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommentHelpHandler.cs
index 27288ce90..10013a09b 100644
--- a/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommentHelpHandler.cs
+++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/GetCommentHelpHandler.cs
@@ -51,7 +51,7 @@ public async Task Handle(CommentHelpRequestParams requ
{
// check if the previous character is `<` because it invalidates
// the param block the follows it.
- IList lines = ScriptFile.GetLinesInternal(funcText);
+ IList lines = ScriptFile.GetLines(funcText);
int relativeTriggerLine0b = triggerLine - funcExtent.StartLineNumber;
if (relativeTriggerLine0b > 0 && lines[relativeTriggerLine0b].IndexOf("<", StringComparison.OrdinalIgnoreCase) > -1)
{
@@ -68,7 +68,7 @@ public async Task Handle(CommentHelpRequestParams requ
return result;
}
- List helpLines = ScriptFile.GetLinesInternal(helpText);
+ List helpLines = ScriptFile.GetLines(helpText);
if (helpLocation?.Equals("before", StringComparison.OrdinalIgnoreCase) == false)
{
diff --git a/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs b/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs
index 3e52e4938..4909d1020 100644
--- a/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs
+++ b/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs
@@ -31,13 +31,6 @@ internal sealed class ScriptFile
#region Properties
- ///
- /// Gets a unique string that identifies this file. At this time,
- /// this property returns a normalized version of the value stored
- /// in the FilePath property.
- ///
- public string Id => FilePath.ToLower();
-
///
/// Gets the path at which this file resides.
///
@@ -173,14 +166,7 @@ internal ScriptFile(
///
/// Input string to be split up into lines.
/// The lines in the string.
- internal static IList GetLines(string text) => GetLinesInternal(text);
-
- ///
- /// Get the lines in a string.
- ///
- /// Input string to be split up into lines.
- /// The lines in the string.
- internal static List GetLinesInternal(string text)
+ internal static List GetLines(string text)
{
if (text == null)
{
@@ -520,7 +506,7 @@ internal void SetFileContents(string fileContents)
{
// Split the file contents into lines and trim
// any carriage returns from the strings.
- FileLines = GetLinesInternal(fileContents);
+ FileLines = GetLines(fileContents);
// Parse the contents to get syntax tree and errors
ParseFileContents();
diff --git a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs
index f705101f8..fce8cab20 100644
--- a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs
+++ b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs
@@ -306,27 +306,31 @@ public void CloseFile(ScriptFile scriptFile)
///
/// Gets the workspace-relative path of the given file path.
///
- /// The original full file path.
/// A relative file path
- public string GetRelativePath(string filePath)
+ public string GetRelativePath(ScriptFile scriptFile)
{
- string resolvedPath = filePath;
-
- if (!IsPathInMemory(filePath) && !string.IsNullOrEmpty(InitialWorkingDirectory))
+ Uri fileUri = scriptFile.DocumentUri.ToUri();
+ if (!scriptFile.IsInMemory)
{
- Uri workspaceUri = new(InitialWorkingDirectory);
- Uri fileUri = new(filePath);
-
- resolvedPath = workspaceUri.MakeRelativeUri(fileUri).ToString();
-
- // Convert the directory separators if necessary
- if (Path.DirectorySeparatorChar == '\\')
+ // Support calculating out-of-workspace relative paths in the common case of a
+ // single workspace folder. Otherwise try to get the matching folder.
+ foreach (WorkspaceFolder workspaceFolder in WorkspaceFolders)
{
- resolvedPath = resolvedPath.Replace('/', '\\');
+ Uri workspaceUri = workspaceFolder.Uri.ToUri();
+ if (WorkspaceFolders.Count == 1 || workspaceUri.IsBaseOf(fileUri))
+ {
+ return workspaceUri.MakeRelativeUri(fileUri).ToString();
+ }
}
}
- return resolvedPath;
+ // Default to the absolute file path if possible, otherwise just return the URI. This
+ // removes the scheme and initial slash when possible.
+ if (fileUri.IsAbsoluteUri)
+ {
+ return fileUri.AbsolutePath;
+ }
+ return fileUri.ToString();
}
///
@@ -407,42 +411,6 @@ internal static string ReadFileContents(DocumentUri uri)
return reader.ReadToEnd();
}
- internal static bool IsPathInMemory(string filePath)
- {
- bool isInMemory = false;
-
- // In cases where a "virtual" file is displayed in the editor,
- // we need to treat the file differently than one that exists
- // on disk. A virtual file could be something like a diff
- // view of the current file or an untitled file.
- try
- {
- // File system absolute paths will have a URI scheme of file:.
- // Other schemes like "untitled:" and "gitlens-git:" will return false for IsFile.
- Uri uri = new(filePath);
- isInMemory = !uri.IsFile;
- }
- catch (UriFormatException)
- {
- // Relative file paths cause a UriFormatException.
- // In this case, fallback to using Path.GetFullPath().
- try
- {
- Path.GetFullPath(filePath);
- }
- catch (Exception ex) when (ex is ArgumentException or NotSupportedException)
- {
- isInMemory = true;
- }
- catch (PathTooLongException)
- {
- // If we ever get here, it should be an actual file so, not in memory
- }
- }
-
- return isInMemory;
- }
-
internal string ResolveWorkspacePath(string path) => ResolveRelativeScriptPath(InitialWorkingDirectory, path);
internal string ResolveRelativeScriptPath(string baseFilePath, string relativePath)
diff --git a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs
index 2c7a44279..1fff5eb3a 100644
--- a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs
+++ b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs
@@ -10,6 +10,9 @@
using Microsoft.PowerShell.EditorServices.Test.Shared;
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
using Xunit;
+using Microsoft.PowerShell.EditorServices.Utility;
+using OmniSharp.Extensions.LanguageServer.Protocol.Models;
+using OmniSharp.Extensions.LanguageServer.Protocol;
namespace PowerShellEditorServices.Test.Session
{
@@ -22,27 +25,51 @@ public class WorkspaceTests
? s_lazyDriveLetter.Value
: string.Empty;
+ internal static ScriptFile CreateScriptFile(string path) => new(path, "", VersionUtils.PSVersion);
+
+
[Fact]
public void CanResolveWorkspaceRelativePath()
{
- string workspacePath = TestUtilities.NormalizePath("c:/Test/Workspace/");
- string testPathInside = TestUtilities.NormalizePath("c:/Test/Workspace/SubFolder/FilePath.ps1");
- string testPathOutside = TestUtilities.NormalizePath("c:/Test/PeerPath/FilePath.ps1");
- string testPathAnotherDrive = TestUtilities.NormalizePath("z:/TryAndFindMe/FilePath.ps1");
+ string workspacePath = "c:/Test/Workspace/";
+ ScriptFile testPathInside = CreateScriptFile("c:/Test/Workspace/SubFolder/FilePath.ps1");
+ ScriptFile testPathOutside = CreateScriptFile("c:/Test/PeerPath/FilePath.ps1");
+ ScriptFile testPathAnotherDrive = CreateScriptFile("z:/TryAndFindMe/FilePath.ps1");
WorkspaceService workspace = new(NullLoggerFactory.Instance);
- // Test without a workspace path
- Assert.Equal(testPathOutside, workspace.GetRelativePath(testPathOutside));
+ // Test with zero workspace folders
+ Assert.Equal(
+ testPathOutside.DocumentUri.ToUri().AbsolutePath,
+ workspace.GetRelativePath(testPathOutside));
- string expectedInsidePath = TestUtilities.NormalizePath("SubFolder/FilePath.ps1");
- string expectedOutsidePath = TestUtilities.NormalizePath("../PeerPath/FilePath.ps1");
+ string expectedInsidePath = "SubFolder/FilePath.ps1";
+ string expectedOutsidePath = "../PeerPath/FilePath.ps1";
+
+ // Test with a single workspace folder
+ workspace.WorkspaceFolders.Add(new WorkspaceFolder
+ {
+ Uri = DocumentUri.FromFileSystemPath(workspacePath)
+ });
- // Test with a workspace path
- workspace.InitialWorkingDirectory = workspacePath;
Assert.Equal(expectedInsidePath, workspace.GetRelativePath(testPathInside));
Assert.Equal(expectedOutsidePath, workspace.GetRelativePath(testPathOutside));
- Assert.Equal(testPathAnotherDrive, workspace.GetRelativePath(testPathAnotherDrive));
+ Assert.Equal(
+ testPathAnotherDrive.DocumentUri.ToUri().AbsolutePath,
+ workspace.GetRelativePath(testPathAnotherDrive));
+
+ // Test with two workspace folders
+ string anotherWorkspacePath = "c:/Test/AnotherWorkspace/";
+ ScriptFile anotherTestPathInside = CreateScriptFile("c:/Test/AnotherWorkspace/DifferentFolder/FilePath.ps1");
+ string anotherExpectedInsidePath = "DifferentFolder/FilePath.ps1";
+
+ workspace.WorkspaceFolders.Add(new WorkspaceFolder
+ {
+ Uri = DocumentUri.FromFileSystemPath(anotherWorkspacePath)
+ });
+
+ Assert.Equal(expectedInsidePath, workspace.GetRelativePath(testPathInside));
+ Assert.Equal(anotherExpectedInsidePath, workspace.GetRelativePath(anotherTestPathInside));
}
internal static WorkspaceService FixturesWorkspace()
@@ -143,40 +170,6 @@ public void CanRecurseDirectoryTreeWithGlobs()
}, actual);
}
- [Fact]
- public void CanDetermineIsPathInMemory()
- {
- string tempDir = Path.GetTempPath();
- string shortDirPath = Path.Combine(tempDir, "GitHub", "PowerShellEditorServices");
- string shortFilePath = Path.Combine(shortDirPath, "foo.ps1");
- const string shortUriForm = "git:/c%3A/Users/Keith/GitHub/dahlbyk/posh-git/src/PoshGitTypes.ps1?%7B%22path%22%3A%22c%3A%5C%5CUsers%5C%5CKeith%5C%5CGitHub%5C%5Cdahlbyk%5C%5Cposh-git%5C%5Csrc%5C%5CPoshGitTypes.ps1%22%2C%22ref%22%3A%22~%22%7D";
- const string longUriForm = "gitlens-git:c%3A%5CUsers%5CKeith%5CGitHub%5Cdahlbyk%5Cposh-git%5Csrc%5CPoshGitTypes%3Ae0022701.ps1?%7B%22fileName%22%3A%22src%2FPoshGitTypes.ps1%22%2C%22repoPath%22%3A%22c%3A%2FUsers%2FKeith%2FGitHub%2Fdahlbyk%2Fposh-git%22%2C%22sha%22%3A%22e0022701fa12e0bc22d0458673d6443c942b974a%22%7D";
-
- string[] inMemoryPaths = new[] {
- // Test short non-file paths
- "untitled:untitled-1",
- shortUriForm,
- "inmemory://foo.ps1",
- // Test long non-file path
- longUriForm
- };
-
- Assert.All(inMemoryPaths, (p) => Assert.True(WorkspaceService.IsPathInMemory(p)));
-
- string[] notInMemoryPaths = new[] {
- // Test short file absolute paths
- shortDirPath,
- shortFilePath,
- new Uri(shortDirPath).ToString(),
- new Uri(shortFilePath).ToString(),
- // Test short file relative paths
- "foo.ps1",
- Path.Combine(new[] { "..", "foo.ps1" })
- };
-
- Assert.All(notInMemoryPaths, (p) => Assert.False(WorkspaceService.IsPathInMemory(p)));
- }
-
[Fact]
public void CanOpenAndCloseFile()
{
From 3f2e1427f2dd3e0701769e03a0cc4a6a74f340f1 Mon Sep 17 00:00:00 2001
From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com>
Date: Thu, 17 Aug 2023 13:17:14 -0700
Subject: [PATCH 3/4] Add `Paths` to workspace API for multi-root workspaces
Since `Path` now refers to initial working directory.
---
.../Extensions/EditorWorkspace.cs | 5 +++++
.../Extensions/IEditorOperations.cs | 6 ++++++
.../Services/Extension/EditorOperationsService.cs | 3 +++
.../Services/Workspace/WorkspaceService.cs | 10 +++++-----
.../Session/WorkspaceTests.cs | 8 ++++++++
5 files changed, 27 insertions(+), 5 deletions(-)
diff --git a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs b/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs
index d12de06e3..46930cda6 100644
--- a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs
+++ b/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs
@@ -23,6 +23,11 @@ public sealed class EditorWorkspace
///
public string Path => editorOperations.GetWorkspacePath();
+ ///
+ /// Get all the workspace folders' paths.
+ ///
+ public string[] Paths => editorOperations.GetWorkspacePaths();
+
#endregion
#region Constructors
diff --git a/src/PowerShellEditorServices/Extensions/IEditorOperations.cs b/src/PowerShellEditorServices/Extensions/IEditorOperations.cs
index 63bae4574..c349bcae1 100644
--- a/src/PowerShellEditorServices/Extensions/IEditorOperations.cs
+++ b/src/PowerShellEditorServices/Extensions/IEditorOperations.cs
@@ -26,6 +26,12 @@ internal interface IEditorOperations
/// The server's initial working directory.
string GetWorkspacePath();
+ ///
+ /// Get all the workspace folders' paths.
+ ///
+ ///
+ string[] GetWorkspacePaths();
+
///
/// Resolves the given file path relative to the current workspace path.
///
diff --git a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs
index a187533c5..4ff235d94 100644
--- a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs
+++ b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs
@@ -6,6 +6,7 @@
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -192,6 +193,8 @@ public async Task SaveFileAsync(string currentPath, string newSavePath)
// from another for the extension API.
public string GetWorkspacePath() => _workspaceService.InitialWorkingDirectory;
+ public string[] GetWorkspacePaths() => _workspaceService.WorkspacePaths.ToArray();
+
public string GetWorkspaceRelativePath(ScriptFile scriptFile) => _workspaceService.GetRelativePath(scriptFile);
public async Task ShowInformationMessageAsync(string message)
diff --git a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs
index fce8cab20..e8f702321 100644
--- a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs
+++ b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs
@@ -104,6 +104,10 @@ public WorkspaceService(ILoggerFactory factory)
#region Public Methods
+ public IEnumerable WorkspacePaths => WorkspaceFolders.Count == 0
+ ? new List { InitialWorkingDirectory }
+ : WorkspaceFolders.Select(i => i.Uri.GetFileSystemPath());
+
///
/// Gets an open file in the workspace. If the file isn't open but exists on the filesystem, load and return it.
/// IMPORTANT: Not all documents have a backing file e.g. untitled: scheme documents. Consider using
@@ -358,15 +362,11 @@ public IEnumerable EnumeratePSFiles(
int maxDepth,
bool ignoreReparsePoints)
{
- IEnumerable rootPaths = WorkspaceFolders.Count == 0
- ? new List { InitialWorkingDirectory }
- : WorkspaceFolders.Select(i => i.Uri.GetFileSystemPath());
-
Matcher matcher = new();
foreach (string pattern in includeGlobs) { matcher.AddInclude(pattern); }
foreach (string pattern in excludeGlobs) { matcher.AddExclude(pattern); }
- foreach (string rootPath in rootPaths)
+ foreach (string rootPath in WorkspacePaths)
{
if (!Directory.Exists(rootPath))
{
diff --git a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs
index 1fff5eb3a..a25e4e8db 100644
--- a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs
+++ b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs
@@ -80,6 +80,14 @@ internal static WorkspaceService FixturesWorkspace()
};
}
+ [Fact]
+ public void HasDefaultForWorkspacePaths()
+ {
+ WorkspaceService workspace = FixturesWorkspace();
+ string actual = Assert.Single(workspace.WorkspacePaths);
+ Assert.Equal(workspace.InitialWorkingDirectory, actual);
+ }
+
// These are the default values for the EnumeratePSFiles() method
// in Microsoft.PowerShell.EditorServices.Workspace class
private static readonly string[] s_defaultExcludeGlobs = Array.Empty();
From f6245f4bbd870432c30a2cb3b8db7e67e6d56067 Mon Sep 17 00:00:00 2001
From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com>
Date: Tue, 22 Aug 2023 12:05:23 -0700
Subject: [PATCH 4/4] Use separate overload instead of optional argument
So as to now add a binary breaking change.
Co-authored-by: Patrick Meinecke
---
.../Extensions/EditorWorkspace.cs | 7 ++++++-
.../Extensions/IEditorOperations.cs | 8 +++++++-
.../Services/Extension/EditorOperationsService.cs | 4 +++-
3 files changed, 16 insertions(+), 3 deletions(-)
diff --git a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs b/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs
index 46930cda6..b01c6eca7 100644
--- a/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs
+++ b/src/PowerShellEditorServices/Extensions/EditorWorkspace.cs
@@ -39,11 +39,16 @@ public sealed class EditorWorkspace
#region Public Methods
// TODO: Consider returning bool instead of void to indicate success?
+ ///
+ /// Creates a new file in the editor.
+ ///
+ public void NewFile() => editorOperations.NewFileAsync(string.Empty).Wait();
+
///
/// Creates a new file in the editor.
///
/// The content to place in the new file.
- public void NewFile(string content = "") => editorOperations.NewFileAsync(content).Wait();
+ public void NewFile(string content) => editorOperations.NewFileAsync(content).Wait();
///
/// Opens a file in the workspace. If the file is already open
diff --git a/src/PowerShellEditorServices/Extensions/IEditorOperations.cs b/src/PowerShellEditorServices/Extensions/IEditorOperations.cs
index c349bcae1..3ec33ebc6 100644
--- a/src/PowerShellEditorServices/Extensions/IEditorOperations.cs
+++ b/src/PowerShellEditorServices/Extensions/IEditorOperations.cs
@@ -38,12 +38,18 @@ internal interface IEditorOperations
/// The resolved file path.
string GetWorkspaceRelativePath(ScriptFile scriptFile);
+ ///
+ /// Causes a new untitled file to be created in the editor.
+ ///
+ /// A task that can be awaited for completion.
+ Task NewFileAsync();
+
///
/// Causes a new untitled file to be created in the editor.
///
/// The content to insert into the new file.
/// A task that can be awaited for completion.
- Task NewFileAsync(string content = "");
+ Task NewFileAsync(string content);
///
/// Causes a file to be opened in the editor. If the file is
diff --git a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs
index 4ff235d94..7a7c6e6e7 100644
--- a/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs
+++ b/src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs
@@ -121,7 +121,9 @@ public EditorContext ConvertClientEditorContext(
clientContext.CurrentFileLanguage);
}
- public async Task NewFileAsync(string content = "")
+ public async Task NewFileAsync() => await NewFileAsync(string.Empty).ConfigureAwait(false);
+
+ public async Task NewFileAsync(string content)
{
if (!TestHasLanguageServer())
{