|
| 1 | +// Licensed to the .NET Foundation under one or more agreements. |
| 2 | +// The .NET Foundation licenses this file to you under the MIT license. |
| 3 | +// See the LICENSE file in the project root for more information. |
| 4 | + |
| 5 | +using System.Collections.Immutable; |
| 6 | +using Microsoft.CodeAnalysis.Diagnostics; |
| 7 | +using Microsoft.CodeAnalysis.Host; |
| 8 | +using Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.VSCode.API; |
| 9 | +using Microsoft.CodeAnalysis.MSBuild; |
| 10 | +using Microsoft.Extensions.Logging; |
| 11 | +using Microsoft.VisualStudio.Composition; |
| 12 | + |
| 13 | +namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; |
| 14 | +internal class LanguageServerWorkspace : Workspace |
| 15 | +{ |
| 16 | + /// <summary> |
| 17 | + /// Set of assemblies to look for host installed analyzers. |
| 18 | + /// Similar to https://github.com/dotnet/roslyn/blob/9fee6f5461baae5152c956c3c3024ca15b85feb9/src/VisualStudio/Setup/source.extension.vsixmanifest#L51 |
| 19 | + /// except only include dlls applicable to VSCode. |
| 20 | + /// </summary> |
| 21 | + private static readonly ImmutableArray<string> s_hostAnalyzerDlls = ImmutableArray.Create( |
| 22 | + "Microsoft.CodeAnalysis.CSharp.dll", |
| 23 | + "Microsoft.CodeAnalysis.VisualBasic.dll", |
| 24 | + "Microsoft.CodeAnalysis.Features.dll", |
| 25 | + "Microsoft.CodeAnalysis.Workspaces.dll", |
| 26 | + "Microsoft.CodeAnalysis.CSharp.Workspaces.dll", |
| 27 | + "Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll", |
| 28 | + "Microsoft.CodeAnalysis.CSharp.Features.dll", |
| 29 | + "Microsoft.CodeAnalysis.VisualBasic.Features.dll"); |
| 30 | + |
| 31 | + public LanguageServerWorkspace(Solution solution, HostServices host, VSCodeAnalyzerLoader vsCodeAnalyzerLoader, ILogger logger, string? workspaceKind) : base(host, workspaceKind) |
| 32 | + { |
| 33 | + SetCurrentSolution(solution); |
| 34 | + InitializeDiagnostics(vsCodeAnalyzerLoader, logger); |
| 35 | + } |
| 36 | + |
| 37 | + internal static async Task<Workspace> CreateWorkspaceAsync(string solutionPath, ExportProvider exportProvider, HostServices hostServices, ILoggerFactory loggerFactory) |
| 38 | + { |
| 39 | + var logger = loggerFactory.CreateLogger(nameof(LanguageServerWorkspace)); |
| 40 | + try |
| 41 | + { |
| 42 | + // This is weird. Really we don't need a workspace other than it is a useful tool to keep track of LSP changes. |
| 43 | + // But no changes should ever be applied from the LSP host (instead the client should be applying them). |
| 44 | + // |
| 45 | + // So we use the MSBuildWorkspace type to create the solution. But we can't use the MSBuild workspace itself |
| 46 | + // because it doesn't support adding analyzers to the solution (and generallly we shouldn't be calling TryApplyChanges). |
| 47 | + // Instead we just take the solution and it put in this workspace type where we can call SetCurrentSolution. |
| 48 | + // |
| 49 | + // This is all going to get refactored anyway when we do more project system stuff. |
| 50 | + using var msbuildWorkspace = MSBuildWorkspace.Create(hostServices); |
| 51 | + var solution = await msbuildWorkspace.OpenSolutionAsync(solutionPath).ConfigureAwait(false); |
| 52 | + |
| 53 | + var vscodeAnalyzerLoader = exportProvider.GetExportedValue<VSCodeAnalyzerLoader>(); |
| 54 | + var hostWorkspace = new LanguageServerWorkspace(solution, hostServices, vscodeAnalyzerLoader, logger, WorkspaceKind.Host); |
| 55 | + // SetCurrentSolution does raise workspace events. For now manually register until we figure out how workspaces will work. |
| 56 | + exportProvider.GetExportedValue<LspWorkspaceRegistrationService>().Register(hostWorkspace); |
| 57 | + |
| 58 | + return hostWorkspace; |
| 59 | + } |
| 60 | + catch (Exception ex) |
| 61 | + { |
| 62 | + logger.LogError(ex, $"Failed to load workspace for {solutionPath}"); |
| 63 | + throw; |
| 64 | + } |
| 65 | + } |
| 66 | + |
| 67 | + internal void InitializeDiagnostics(VSCodeAnalyzerLoader vscodeAnalyzerLoader, ILogger logger) |
| 68 | + { |
| 69 | + var baseDirectory = AppContext.BaseDirectory; |
| 70 | + var references = new List<AnalyzerFileReference>(); |
| 71 | + var analyzerLoader = VSCodeAnalyzerLoader.CreateAnalyzerAssemblyLoader(); |
| 72 | + foreach (var assemblyName in s_hostAnalyzerDlls) |
| 73 | + { |
| 74 | + var path = Path.Combine(baseDirectory, assemblyName); |
| 75 | + if (!File.Exists(path)) |
| 76 | + continue; |
| 77 | + |
| 78 | + references.Add(new AnalyzerFileReference(path, analyzerLoader)); |
| 79 | + } |
| 80 | + |
| 81 | + var newSolution = this.CurrentSolution.WithAnalyzerReferences(references); |
| 82 | + SetCurrentSolution(newSolution); |
| 83 | + logger.LogDebug($"Loaded host analyzers:{Environment.NewLine}{string.Join(Environment.NewLine, references.Select(r => r.FullPath))}"); |
| 84 | + |
| 85 | + vscodeAnalyzerLoader.InitializeDiagnosticsServices(this); |
| 86 | + } |
| 87 | +} |
0 commit comments