Skip to content

Commit 34d81b3

Browse files
committed
WIP: Support multi-root workspace
This time in the server.
1 parent 422e2cd commit 34d81b3

File tree

7 files changed

+66
-43
lines changed

7 files changed

+66
-43
lines changed

src/PowerShellEditorServices/Extensions/EditorWorkspace.cs

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public sealed class EditorWorkspace
1919

2020
/// <summary>
2121
/// Gets the current workspace path if there is one or null otherwise.
22+
/// TODO: This API needs to be deprecated for new `Paths` instead.
2223
/// </summary>
2324
public string Path => editorOperations.GetWorkspacePath();
2425

src/PowerShellEditorServices/Extensions/FileContext.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ public sealed class FileContext
5858
/// <summary>
5959
/// Gets the workspace-relative path of the file.
6060
/// </summary>
61-
public string WorkspacePath => editorOperations.GetWorkspaceRelativePath(
62-
scriptFile.FilePath);
61+
public string WorkspacePath => editorOperations.GetWorkspaceRelativePath(scriptFile);
6362

6463
#endregion
6564

src/PowerShellEditorServices/Extensions/IEditorOperations.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,8 @@ internal interface IEditorOperations
2828
/// <summary>
2929
/// Resolves the given file path relative to the current workspace path.
3030
/// </summary>
31-
/// <param name="filePath">The file path to be resolved.</param>
3231
/// <returns>The resolved file path.</returns>
33-
string GetWorkspaceRelativePath(string filePath);
32+
string GetWorkspaceRelativePath(ScriptFile scriptFile);
3433

3534
/// <summary>
3635
/// Causes a new untitled file to be created in the editor.

src/PowerShellEditorServices/Server/PsesLanguageServer.cs

+12-12
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using System.IO;
5+
using System.Linq;
56
using System.Threading.Tasks;
67
using Microsoft.Extensions.DependencyInjection;
78
using Microsoft.Extensions.Logging;
@@ -13,7 +14,6 @@
1314
using Microsoft.PowerShell.EditorServices.Services.Template;
1415
using Newtonsoft.Json.Linq;
1516
using OmniSharp.Extensions.JsonRpc;
16-
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
1717
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
1818
using OmniSharp.Extensions.LanguageServer.Server;
1919
using Serilog;
@@ -130,12 +130,7 @@ public async Task StartAsync()
130130
WorkspaceService workspaceService = languageServer.Services.GetService<WorkspaceService>();
131131
if (initializeParams.WorkspaceFolders is not null)
132132
{
133-
// TODO: Support multi-workspace.
134-
foreach (WorkspaceFolder workspaceFolder in initializeParams.WorkspaceFolders)
135-
{
136-
workspaceService.WorkspacePath = workspaceFolder.Uri.GetFileSystemPath();
137-
break;
138-
}
133+
workspaceService.WorkspaceFolders.AddRange(initializeParams.WorkspaceFolders);
139134
}
140135

141136
// Parse initialization options.
@@ -149,13 +144,18 @@ public async Task StartAsync()
149144
//
150145
// NOTE: The keys start with a lowercase because OmniSharp's client
151146
// (used for testing) forces it to be that way.
152-
LoadProfiles = initializationOptions?.GetValue("enableProfileLoading")?.Value<bool>() ?? true,
153-
// TODO: Consider deprecating the setting which sets this and
154-
// instead use WorkspacePath exclusively.
155-
InitialWorkingDirectory = initializationOptions?.GetValue("initialWorkingDirectory")?.Value<string>() ?? workspaceService.WorkspacePath,
156-
ShellIntegrationEnabled = initializationOptions?.GetValue("shellIntegrationEnabled")?.Value<bool>() ?? false
147+
LoadProfiles = initializationOptions?.GetValue("enableProfileLoading")?.Value<bool>()
148+
?? true,
149+
InitialWorkingDirectory = initializationOptions?.GetValue("initialWorkingDirectory")?.Value<string>()
150+
?? workspaceService.WorkspaceFolders.FirstOrDefault()?.Uri.GetFileSystemPath(),
151+
ShellIntegrationEnabled = initializationOptions?.GetValue("shellIntegrationEnabled")?.Value<bool>()
152+
?? false
157153
};
158154

155+
// NOTE: We're still maintaining a root path while things transition to
156+
// support multi-root workspaces.
157+
workspaceService.WorkspacePath = hostStartOptions.InitialWorkingDirectory;
158+
159159
_psesHost = languageServer.Services.GetService<PsesInternalHost>();
160160
return _psesHost.TryStartAsync(hostStartOptions, cancellationToken);
161161
});

src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ public async Task SaveFileAsync(string currentPath, string newSavePath)
193193

194194
public string GetWorkspacePath() => _workspaceService.WorkspacePath;
195195

196-
public string GetWorkspaceRelativePath(string filePath) => _workspaceService.GetRelativePath(filePath);
196+
public string GetWorkspaceRelativePath(ScriptFile scriptFile) => _workspaceService.GetRelativePath(scriptFile);
197197

198198
public async Task ShowInformationMessageAsync(string message)
199199
{

src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs

+44-23
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Microsoft.PowerShell.EditorServices.Services.Workspace;
1414
using Microsoft.PowerShell.EditorServices.Utility;
1515
using OmniSharp.Extensions.LanguageServer.Protocol;
16+
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
1617

1718
namespace Microsoft.PowerShell.EditorServices.Services
1819
{
@@ -58,10 +59,15 @@ internal class WorkspaceService
5859
#region Properties
5960

6061
/// <summary>
61-
/// Gets or sets the root path of the workspace.
62+
/// Deprecated! Multi-root workspace support requires multiple workspace paths.
6263
/// </summary>
6364
public string WorkspacePath { get; set; }
6465

66+
/// <summary>
67+
/// Gets or sets the folders of the workspace.
68+
/// </summary>
69+
public List<WorkspaceFolder> WorkspaceFolders { get; set; }
70+
6571
/// <summary>
6672
/// Gets or sets the default list of file globs to exclude during workspace searches.
6773
/// </summary>
@@ -83,6 +89,7 @@ public WorkspaceService(ILoggerFactory factory)
8389
{
8490
powerShellVersion = VersionUtils.PSVersion;
8591
logger = factory.CreateLogger<WorkspaceService>();
92+
WorkspaceFolders = new List<WorkspaceFolder>();
8693
ExcludeFilesGlob = new List<string>();
8794
FollowSymlinks = true;
8895
}
@@ -293,18 +300,23 @@ public void CloseFile(ScriptFile scriptFile)
293300
/// <summary>
294301
/// Gets the workspace-relative path of the given file path.
295302
/// </summary>
296-
/// <param name="filePath">The original full file path.</param>
297303
/// <returns>A relative file path</returns>
298-
public string GetRelativePath(string filePath)
304+
public string GetRelativePath(ScriptFile scriptFile)
299305
{
300-
string resolvedPath = filePath;
306+
string resolvedPath = scriptFile.FilePath;
301307

302-
if (!IsPathInMemory(filePath) && !string.IsNullOrEmpty(WorkspacePath))
308+
if (!scriptFile.IsInMemory)
303309
{
304-
Uri workspaceUri = new(WorkspacePath);
305-
Uri fileUri = new(filePath);
306-
307-
resolvedPath = workspaceUri.MakeRelativeUri(fileUri).ToString();
310+
Uri fileUri = scriptFile.DocumentUri.ToUri();
311+
foreach (WorkspaceFolder workspaceFolder in WorkspaceFolders)
312+
{
313+
Uri workspaceUri = workspaceFolder.Uri.ToUri();
314+
if (workspaceUri.IsBaseOf(fileUri))
315+
{
316+
resolvedPath = workspaceUri.MakeRelativeUri(fileUri).ToString();
317+
break;
318+
}
319+
}
308320

309321
// Convert the directory separators if necessary
310322
if (Path.DirectorySeparatorChar == '\\')
@@ -341,7 +353,7 @@ public IEnumerable<string> EnumeratePSFiles(
341353
bool ignoreReparsePoints
342354
)
343355
{
344-
if (WorkspacePath is null || !Directory.Exists(WorkspacePath))
356+
if (WorkspaceFolders.Count == 0)
345357
{
346358
yield break;
347359
}
@@ -350,20 +362,29 @@ bool ignoreReparsePoints
350362
foreach (string pattern in includeGlobs) { matcher.AddInclude(pattern); }
351363
foreach (string pattern in excludeGlobs) { matcher.AddExclude(pattern); }
352364

353-
WorkspaceFileSystemWrapperFactory fsFactory = new(
354-
WorkspacePath,
355-
maxDepth,
356-
VersionUtils.IsNetCore ? s_psFileExtensionsCoreFramework : s_psFileExtensionsFullFramework,
357-
ignoreReparsePoints,
358-
logger
359-
);
360-
PatternMatchingResult fileMatchResult = matcher.Execute(fsFactory.RootDirectory);
361-
foreach (FilePatternMatch item in fileMatchResult.Files)
365+
foreach (WorkspaceFolder workspaceFolder in WorkspaceFolders)
362366
{
363-
// item.Path always contains forward slashes in paths when it should be backslashes on Windows.
364-
// Since we're returning strings here, it's important to use the correct directory separator.
365-
string path = VersionUtils.IsWindows ? item.Path.Replace('/', Path.DirectorySeparatorChar) : item.Path;
366-
yield return Path.Combine(WorkspacePath, path);
367+
string rootPath = workspaceFolder.Uri.GetFileSystemPath();
368+
if (!Directory.Exists(rootPath))
369+
{
370+
continue;
371+
}
372+
373+
WorkspaceFileSystemWrapperFactory fsFactory = new(
374+
rootPath,
375+
maxDepth,
376+
VersionUtils.IsNetCore ? s_psFileExtensionsCoreFramework : s_psFileExtensionsFullFramework,
377+
ignoreReparsePoints,
378+
logger);
379+
380+
PatternMatchingResult fileMatchResult = matcher.Execute(fsFactory.RootDirectory);
381+
foreach (FilePatternMatch item in fileMatchResult.Files)
382+
{
383+
// item.Path always contains forward slashes in paths when it should be backslashes on Windows.
384+
// Since we're returning strings here, it's important to use the correct directory separator.
385+
string path = VersionUtils.IsWindows ? item.Path.Replace('/', Path.DirectorySeparatorChar) : item.Path;
386+
yield return Path.Combine(rootPath, path);
387+
}
367388
}
368389
}
369390

test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs

+6-3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
using Microsoft.PowerShell.EditorServices.Test.Shared.SymbolDetails;
2424
using Microsoft.PowerShell.EditorServices.Test.Shared.Symbols;
2525
using Microsoft.PowerShell.EditorServices.Utility;
26+
using OmniSharp.Extensions.LanguageServer.Protocol;
27+
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
2628
using Xunit;
2729

2830
namespace PowerShellEditorServices.Test.Language
@@ -38,10 +40,11 @@ public class SymbolsServiceTests : IDisposable
3840
public SymbolsServiceTests()
3941
{
4042
psesHost = PsesHostFactory.Create(NullLoggerFactory.Instance);
41-
workspace = new WorkspaceService(NullLoggerFactory.Instance)
43+
workspace = new WorkspaceService(NullLoggerFactory.Instance);
44+
workspace.WorkspaceFolders.Add(new WorkspaceFolder()
4245
{
43-
WorkspacePath = TestUtilities.GetSharedPath("References")
44-
};
46+
Uri = DocumentUri.FromFileSystemPath(TestUtilities.GetSharedPath("References"))
47+
});
4548
symbolsService = new SymbolsService(
4649
NullLoggerFactory.Instance,
4750
psesHost,

0 commit comments

Comments
 (0)