diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerWorkspaceFactory.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerWorkspaceFactory.cs index 2461b9b2c136c..2bbe0368ccbc8 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerWorkspaceFactory.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerWorkspaceFactory.cs @@ -8,10 +8,12 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler.DebugConfiguration; +using Microsoft.CodeAnalysis.LanguageServer.Services; using Microsoft.CodeAnalysis.ProjectSystem; using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.Composition; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; @@ -19,6 +21,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; internal sealed class LanguageServerWorkspaceFactory { private readonly ILogger _logger; + private readonly ImmutableArray _solutionLevelAnalyzerPaths; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -28,12 +31,24 @@ public LanguageServerWorkspaceFactory( [ImportMany] IEnumerable> dynamicFileInfoProviders, ProjectTargetFrameworkManager projectTargetFrameworkManager, ServerConfigurationFactory serverConfigurationFactory, + ExtensionAssemblyManager extensionManager, ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger(nameof(LanguageServerWorkspaceFactory)); + // Before we can create the workspace, let's figure out the solution-level analyzers; we'll pull in analyzers from our own binaries + // as well as anything coming from extensions. + _solutionLevelAnalyzerPaths = new DirectoryInfo(AppContext.BaseDirectory).GetFiles("*.dll") + .Where(f => f.Name.StartsWith("Microsoft.CodeAnalysis.", StringComparison.Ordinal) && !f.Name.Contains("LanguageServer", StringComparison.Ordinal)) + .Select(f => f.FullName) + .Concat(extensionManager.ExtensionAssemblyPaths) + .ToImmutableArray(); + + // Create the workspace and set analyzer references for it var workspace = new LanguageServerWorkspace(hostServicesProvider.HostServices); + workspace.SetCurrentSolution(s => s.WithAnalyzerReferences(CreateSolutionLevelAnalyzerReferencesForWorkspace(workspace)), WorkspaceChangeKind.SolutionChanged); Workspace = workspace; + ProjectSystemProjectFactory = new ProjectSystemProjectFactory( Workspace, fileChangeWatcher, static (_, _) => Task.CompletedTask, _ => { }, CancellationToken.None); // TODO: do we need to introduce a shutdown cancellation token for this? @@ -54,17 +69,17 @@ public LanguageServerWorkspaceFactory( public ProjectSystemHostInfo ProjectSystemHostInfo { get; } public ProjectTargetFrameworkManager TargetFrameworkManager { get; } - public async Task InitializeSolutionLevelAnalyzersAsync(ImmutableArray analyzerPaths) + public ImmutableArray CreateSolutionLevelAnalyzerReferencesForWorkspace(Workspace workspace) { - var references = new List(); - var loaderProvider = Workspace.Services.GetRequiredService(); + var references = ImmutableArray.CreateBuilder(); + var loaderProvider = workspace.Services.GetRequiredService(); // Load all analyzers into a fresh shadow copied load context. In the future, if we want to support reloading // of solution-level analyzer references, we should just need to listen for changes to those analyzer paths and // then call back into this method to update the solution accordingly. var analyzerLoader = loaderProvider.CreateNewShadowCopyLoader(); - foreach (var analyzerPath in analyzerPaths) + foreach (var analyzerPath in _solutionLevelAnalyzerPaths) { if (File.Exists(analyzerPath)) { @@ -77,9 +92,6 @@ public async Task InitializeSolutionLevelAnalyzersAsync(ImmutableArray a } } - await ProjectSystemProjectFactory.ApplyChangeToWorkspaceAsync(w => - { - w.SetCurrentSolution(s => s.WithAnalyzerReferences(references), WorkspaceChangeKind.SolutionChanged); - }); + return references.ToImmutableAndClear(); } } diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs index a7fe81642d7c9..05745fb0f0544 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs @@ -116,19 +116,10 @@ static async Task RunAsync(ServerConfiguration serverConfiguration, Cancellation var telemetryReporter = exportProvider.GetExports().SingleOrDefault()?.Value; RoslynLogger.Initialize(telemetryReporter, serverConfiguration.TelemetryLevel, serverConfiguration.SessionId); - // Create the workspace first, since right now the language server will assume there's at least one Workspace + // 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 + // object which is registered by the LspWorkspaceRegistrationEventListener. var workspaceFactory = exportProvider.GetExportedValue(); - var analyzerPaths = new DirectoryInfo(AppContext.BaseDirectory).GetFiles("*.dll") - .Where(f => f.Name.StartsWith("Microsoft.CodeAnalysis.", StringComparison.Ordinal) && !f.Name.Contains("LanguageServer", StringComparison.Ordinal)) - .Select(f => f.FullName) - .ToImmutableArray(); - - // Include analyzers from extension assemblies. - analyzerPaths = analyzerPaths.AddRange(extensionManager.ExtensionAssemblyPaths); - - await workspaceFactory.InitializeSolutionLevelAnalyzersAsync(analyzerPaths); - var serviceBrokerFactory = exportProvider.GetExportedValue(); StarredCompletionAssemblyHelper.InitializeInstance(serverConfiguration.StarredCompletionsPath, extensionManager, loggerFactory, serviceBrokerFactory); // TODO: Remove, the path should match exactly. Workaround for https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1830914.