Skip to content

Commit 9033536

Browse files
Simplify workspace initialization in the LSP server
Right now we have the logic for which analyzers get added to a workspace in Program.cs, which calls a special method that specifically initializes the main host workspace. This refactors it so that could be used to initialize any workspace, and removes any tricky ordering problems that migth happen by using cleaner MEF imports when we have them.
1 parent cdcc3c4 commit 9033536

File tree

2 files changed

+22
-19
lines changed

2 files changed

+22
-19
lines changed

src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerWorkspaceFactory.cs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,20 @@
88
using Microsoft.CodeAnalysis.Host;
99
using Microsoft.CodeAnalysis.Host.Mef;
1010
using Microsoft.CodeAnalysis.LanguageServer.Handler.DebugConfiguration;
11+
using Microsoft.CodeAnalysis.LanguageServer.Services;
1112
using Microsoft.CodeAnalysis.ProjectSystem;
1213
using Microsoft.CodeAnalysis.Workspaces.ProjectSystem;
1314
using Microsoft.Extensions.Logging;
1415
using Microsoft.VisualStudio.Composition;
16+
using Roslyn.Utilities;
1517

1618
namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace;
1719

1820
[Export(typeof(LanguageServerWorkspaceFactory)), Shared]
1921
internal sealed class LanguageServerWorkspaceFactory
2022
{
2123
private readonly ILogger _logger;
24+
private readonly ImmutableArray<string> _solutionLevelAnalyzerPaths;
2225

2326
[ImportingConstructor]
2427
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
@@ -28,12 +31,24 @@ public LanguageServerWorkspaceFactory(
2831
[ImportMany] IEnumerable<Lazy<IDynamicFileInfoProvider, FileExtensionsMetadata>> dynamicFileInfoProviders,
2932
ProjectTargetFrameworkManager projectTargetFrameworkManager,
3033
ServerConfigurationFactory serverConfigurationFactory,
34+
ExtensionAssemblyManager extensionManager,
3135
ILoggerFactory loggerFactory)
3236
{
3337
_logger = loggerFactory.CreateLogger(nameof(LanguageServerWorkspaceFactory));
3438

39+
// Before we can create the workspace, let's figure out the solution-level analyzers; we'll pull in analyzers from our own binaries
40+
// as well as anything coming from extensions.
41+
_solutionLevelAnalyzerPaths = new DirectoryInfo(AppContext.BaseDirectory).GetFiles("*.dll")
42+
.Where(f => f.Name.StartsWith("Microsoft.CodeAnalysis.", StringComparison.Ordinal) && !f.Name.Contains("LanguageServer", StringComparison.Ordinal))
43+
.Select(f => f.FullName)
44+
.Concat(extensionManager.ExtensionAssemblyPaths)
45+
.ToImmutableArray();
46+
47+
// Create the workspace and set analyzer references for it
3548
var workspace = new LanguageServerWorkspace(hostServicesProvider.HostServices);
49+
workspace.SetCurrentSolution(s => s.WithAnalyzerReferences(CreateSolutionLevelAnalyzerReferencesForWorkspace(workspace)), WorkspaceChangeKind.SolutionChanged);
3650
Workspace = workspace;
51+
3752
ProjectSystemProjectFactory = new ProjectSystemProjectFactory(
3853
Workspace, fileChangeWatcher, static (_, _) => Task.CompletedTask, _ => { },
3954
CancellationToken.None); // TODO: do we need to introduce a shutdown cancellation token for this?
@@ -54,17 +69,17 @@ public LanguageServerWorkspaceFactory(
5469
public ProjectSystemHostInfo ProjectSystemHostInfo { get; }
5570
public ProjectTargetFrameworkManager TargetFrameworkManager { get; }
5671

57-
public async Task InitializeSolutionLevelAnalyzersAsync(ImmutableArray<string> analyzerPaths)
72+
public ImmutableArray<AnalyzerFileReference> CreateSolutionLevelAnalyzerReferencesForWorkspace(Workspace workspace)
5873
{
59-
var references = new List<AnalyzerFileReference>();
60-
var loaderProvider = Workspace.Services.GetRequiredService<IAnalyzerAssemblyLoaderProvider>();
74+
var references = ImmutableArray.CreateBuilder<AnalyzerFileReference>();
75+
var loaderProvider = workspace.Services.GetRequiredService<IAnalyzerAssemblyLoaderProvider>();
6176

6277
// Load all analyzers into a fresh shadow copied load context. In the future, if we want to support reloading
6378
// of solution-level analyzer references, we should just need to listen for changes to those analyzer paths and
6479
// then call back into this method to update the solution accordingly.
6580
var analyzerLoader = loaderProvider.CreateNewShadowCopyLoader();
6681

67-
foreach (var analyzerPath in analyzerPaths)
82+
foreach (var analyzerPath in _solutionLevelAnalyzerPaths)
6883
{
6984
if (File.Exists(analyzerPath))
7085
{
@@ -77,9 +92,6 @@ public async Task InitializeSolutionLevelAnalyzersAsync(ImmutableArray<string> a
7792
}
7893
}
7994

80-
await ProjectSystemProjectFactory.ApplyChangeToWorkspaceAsync(w =>
81-
{
82-
w.SetCurrentSolution(s => s.WithAnalyzerReferences(references), WorkspaceChangeKind.SolutionChanged);
83-
});
95+
return references.ToImmutableAndClear();
8496
}
8597
}

src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -116,19 +116,10 @@ static async Task RunAsync(ServerConfiguration serverConfiguration, Cancellation
116116
var telemetryReporter = exportProvider.GetExports<ITelemetryReporter>().SingleOrDefault()?.Value;
117117
RoslynLogger.Initialize(telemetryReporter, serverConfiguration.TelemetryLevel, serverConfiguration.SessionId);
118118

119-
// Create the workspace first, since right now the language server will assume there's at least one Workspace
119+
// Create the workspace first, since right now the language server will assume there's at least one Workspace. This as a side effect creates the actual workspace
120+
// object which is registered by the LspWorkspaceRegistrationEventListener.
120121
var workspaceFactory = exportProvider.GetExportedValue<LanguageServerWorkspaceFactory>();
121122

122-
var analyzerPaths = new DirectoryInfo(AppContext.BaseDirectory).GetFiles("*.dll")
123-
.Where(f => f.Name.StartsWith("Microsoft.CodeAnalysis.", StringComparison.Ordinal) && !f.Name.Contains("LanguageServer", StringComparison.Ordinal))
124-
.Select(f => f.FullName)
125-
.ToImmutableArray();
126-
127-
// Include analyzers from extension assemblies.
128-
analyzerPaths = analyzerPaths.AddRange(extensionManager.ExtensionAssemblyPaths);
129-
130-
await workspaceFactory.InitializeSolutionLevelAnalyzersAsync(analyzerPaths);
131-
132123
var serviceBrokerFactory = exportProvider.GetExportedValue<ServiceBrokerFactory>();
133124
StarredCompletionAssemblyHelper.InitializeInstance(serverConfiguration.StarredCompletionsPath, extensionManager, loggerFactory, serviceBrokerFactory);
134125
// TODO: Remove, the path should match exactly. Workaround for https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1830914.

0 commit comments

Comments
 (0)