diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 1966e0ef10d..cf1ad4498bd 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -7,27 +7,27 @@ This file should be imported by eng/Versions.props - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 + 5.0.0-2.25406.1 + 5.0.0-2.25406.1 + 5.0.0-2.25406.1 + 5.0.0-2.25406.1 + 5.0.0-2.25406.1 + 5.0.0-2.25406.1 + 5.0.0-2.25406.1 + 5.0.0-2.25406.1 + 5.0.0-2.25406.1 + 5.0.0-2.25406.1 + 5.0.0-2.25406.1 + 5.0.0-2.25406.1 + 5.0.0-2.25406.1 + 5.0.0-2.25406.1 + 5.0.0-2.25406.1 + 5.0.0-2.25406.1 + 5.0.0-2.25406.1 + 5.0.0-2.25406.1 + 5.0.0-2.25406.1 + 5.0.0-2.25406.1 + 5.0.0-2.25406.1 9.0.0-beta.25255.5 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 0165310fb90..2c7386660b0 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -2,89 +2,89 @@ - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + 5759a992418f0fd3e206cc6f369b6f33e873dfca - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + 5759a992418f0fd3e206cc6f369b6f33e873dfca - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + 5759a992418f0fd3e206cc6f369b6f33e873dfca - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + 5759a992418f0fd3e206cc6f369b6f33e873dfca - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + 5759a992418f0fd3e206cc6f369b6f33e873dfca - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + 5759a992418f0fd3e206cc6f369b6f33e873dfca - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + 5759a992418f0fd3e206cc6f369b6f33e873dfca - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + 5759a992418f0fd3e206cc6f369b6f33e873dfca - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + 5759a992418f0fd3e206cc6f369b6f33e873dfca - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + 5759a992418f0fd3e206cc6f369b6f33e873dfca - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + 5759a992418f0fd3e206cc6f369b6f33e873dfca - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + 5759a992418f0fd3e206cc6f369b6f33e873dfca - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + 5759a992418f0fd3e206cc6f369b6f33e873dfca - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + 5759a992418f0fd3e206cc6f369b6f33e873dfca - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + 5759a992418f0fd3e206cc6f369b6f33e873dfca - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + 5759a992418f0fd3e206cc6f369b6f33e873dfca - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + 5759a992418f0fd3e206cc6f369b6f33e873dfca - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + 5759a992418f0fd3e206cc6f369b6f33e873dfca - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + 5759a992418f0fd3e206cc6f369b6f33e873dfca - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + 5759a992418f0fd3e206cc6f369b6f33e873dfca - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + 5759a992418f0fd3e206cc6f369b6f33e873dfca diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/CohostStartupService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/CohostStartupService.cs index e9e40205ad0..1ae7f851898 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/CohostStartupService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/CohostStartupService.cs @@ -5,48 +5,73 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel.Composition; -using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; using Microsoft.CodeAnalysis.Razor.Logging; -using Microsoft.CodeAnalysis.Razor.Protocol; +using Microsoft.CodeAnalysis.Razor.Remote; +using Microsoft.CodeAnalysis.Razor.Workspaces; +using Microsoft.VisualStudio.Threading; namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost; -[Export(typeof(ICohostStartupService))] +[Export(typeof(AbstractRazorCohostLifecycleService))] [method: ImportingConstructor] internal sealed class CohostStartupService( [ImportMany] IEnumerable> lazyStartupServices, - ILoggerFactory loggerFactory) : ICohostStartupService + IRemoteServiceInvoker remoteServiceInvoker, + LanguageServerFeatureOptions featureOptions, + ILoggerFactory loggerFactory) : AbstractRazorCohostLifecycleService { private readonly ImmutableArray> _lazyStartupServices = [.. lazyStartupServices]; - private readonly ILogger _logger = loggerFactory.GetOrCreateLogger(); + private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker; + private readonly LanguageServerFeatureOptions _featureOptions = featureOptions; - public async Task StartupAsync(string clientCapabilitiesString, RazorCohostRequestContext requestContext, CancellationToken cancellationToken) + public override Task LspServerIntializedAsync(CancellationToken cancellationToken) { - var clientCapabilities = JsonSerializer.Deserialize(clientCapabilitiesString, JsonHelpers.JsonSerializerOptions) ?? new(); + // If cohosting is on, we have to intialize it early so we can un-suppress the source generator. This can be removed + // when the suppression system is removed once cohosting is fully enabled. + // Without this operations that might affect Razor files won't work until a Razor file is opened in the editor. + if (_featureOptions.UseRazorCohostServer) + { + return _remoteServiceInvoker.InitializeAsync().AsTask(); + } + return Task.CompletedTask; + } + + public override async Task RazorActivatedAsync(ClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext, CancellationToken cancellationToken) + { + // Normally loggers are fields, but this service gets created early so it's better to avoid the work in case Razor + // never gets activated. + var logger = loggerFactory.GetOrCreateLogger(); + + var capabilities = clientCapabilities.ToVSInternalClientCapabilities(); var providers = _lazyStartupServices.SelectAndOrderByAsArray(p => p.Value, p => p.Order); foreach (var provider in providers) { if (cancellationToken.IsCancellationRequested) { - _logger.LogInformation($"Razor extension startup cancelled."); + logger.LogInformation($"Razor extension startup cancelled."); return; } try { - await provider.StartupAsync(clientCapabilities, requestContext, cancellationToken).ConfigureAwait(false); + await provider.StartupAsync(capabilities, requestContext, cancellationToken).ConfigureAwait(false); } catch (Exception ex) when (ex is not OperationCanceledException) { - _logger.LogError(ex, $"Error initializing Razor startup service '{provider.GetType().Name}'"); + logger.LogError(ex, $"Error initializing Razor startup service '{provider.GetType().Name}'"); } } - _logger.LogInformation($"Razor extension startup finished."); + logger.LogInformation($"Razor extension startup finished."); + } + + public override void Dispose() + { + _remoteServiceInvoker.UninitializeLspAsync().Forget(); } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AbstractClientCapabilitiesService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AbstractClientCapabilitiesService.cs index 08e8bcefe70..8694308d38a 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AbstractClientCapabilitiesService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/AbstractClientCapabilitiesService.cs @@ -13,7 +13,7 @@ internal abstract class AbstractClientCapabilitiesService : IClientCapabilitiesS public VSInternalClientCapabilities ClientCapabilities => _clientCapabilities ?? throw new InvalidOperationException("Client capabilities requested before initialized."); - public void SetCapabilities(VSInternalClientCapabilities clientCapabilities) + public void SetCapabilities(VSInternalClientCapabilities? clientCapabilities) { _clientCapabilities = clientCapabilities; } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteClientInitializationService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteClientInitializationService.cs index 140697bb826..81e3289155a 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteClientInitializationService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteClientInitializationService.cs @@ -10,5 +10,7 @@ internal interface IRemoteClientInitializationService : IRemoteJsonService { ValueTask InitializeAsync(RemoteClientInitializationOptions initializationOptions, CancellationToken cancellationToken); - ValueTask InitializeLSPAsync(RemoteClientLSPInitializationOptions lspInitializationOptions, CancellationToken cancellationToken); + ValueTask InitializeLspAsync(RemoteClientLSPInitializationOptions lspInitializationOptions, CancellationToken cancellationToken); + + ValueTask UninitializeLspAsync(CancellationToken cancellationToken); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteServiceInvoker.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteServiceInvoker.cs index 091a08cf469..45be598679a 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteServiceInvoker.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteServiceInvoker.cs @@ -11,6 +11,10 @@ namespace Microsoft.CodeAnalysis.Razor.Remote; internal interface IRemoteServiceInvoker { + ValueTask InitializeAsync(); + + ValueTask UninitializeLspAsync(); + ValueTask TryInvokeAsync( Solution solution, Func> invocation, diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Completion/RemoteCompletionService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Completion/RemoteCompletionService.cs index 48a304b5a52..e1ac66d9494 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Completion/RemoteCompletionService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Completion/RemoteCompletionService.cs @@ -36,6 +36,7 @@ protected override IRemoteCompletionService CreateService(in ServiceArgs args) private readonly RazorCompletionListProvider _razorCompletionListProvider = args.ExportProvider.GetExportedValue(); private readonly CompletionListCache _completionListCache = args.ExportProvider.GetExportedValue(); + private readonly RoslynCompletionListCacheWrapper _roslynCompletionListCacheWrapper = args.ExportProvider.GetExportedValue(); private readonly IClientCapabilitiesService _clientCapabilitiesService = args.ExportProvider.GetExportedValue(); private readonly CompletionTriggerAndCommitCharacters _triggerAndCommitCharacters = args.ExportProvider.GetExportedValue(); private readonly IRazorFormattingService _formattingService = args.ExportProvider.GetExportedValue(); @@ -210,6 +211,7 @@ private async ValueTask GetCompletionAsync( completionContext, clientCapabilities.SupportsVisualStudioExtensions, completionSetting, + _roslynCompletionListCacheWrapper.GetCache(), cancellationToken) .ConfigureAwait(false); } @@ -333,6 +335,7 @@ private async ValueTask ResolveCSharpCompletionItemAsy generatedDocument, clientCapabilities.SupportsVisualStudioExtensions, completionListSetting ?? new(), + _roslynCompletionListCacheWrapper.GetCache(), cancellationToken).ConfigureAwait(false); var item = JsonHelpers.Convert(result).AssumeNotNull(); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Completion/RoslynCompletionListCacheWrapper.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Completion/RoslynCompletionListCacheWrapper.cs new file mode 100644 index 00000000000..e02a379b7e2 --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Completion/RoslynCompletionListCacheWrapper.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Composition; +using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers; +using Microsoft.CodeAnalysis.Razor.Remote; + +namespace Microsoft.CodeAnalysis.Remote.Razor; + +[Shared] +[Export(typeof(ILspLifetimeService))] +[Export(typeof(RoslynCompletionListCacheWrapper))] +internal class RoslynCompletionListCacheWrapper : ILspLifetimeService +{ + private CompletionListCacheWrapper? _cacheWrapper; + + public CompletionListCacheWrapper GetCache() + { + _cacheWrapper ??= new(); + return _cacheWrapper; + } + + void ILspLifetimeService.OnLspInitialized(RemoteClientLSPInitializationOptions options) + { + } + + void ILspLifetimeService.OnLspUninitialized() + { + _cacheWrapper = null; + } +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/ILspLifetimeService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/ILspLifetimeService.cs new file mode 100644 index 00000000000..0e42d0890f0 --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/ILspLifetimeService.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.CodeAnalysis.Razor.Remote; + +namespace Microsoft.CodeAnalysis.Remote.Razor; + +internal interface ILspLifetimeService +{ + void OnLspInitialized(RemoteClientLSPInitializationOptions options); + void OnLspUninitialized(); +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientCapabilitiesService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientCapabilitiesService.cs index 03909391ec7..cb671e3db2e 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientCapabilitiesService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientCapabilitiesService.cs @@ -3,10 +3,22 @@ using System.Composition; using Microsoft.CodeAnalysis.Razor.Protocol; +using Microsoft.CodeAnalysis.Razor.Remote; namespace Microsoft.CodeAnalysis.Remote.Razor; [Shared] [Export(typeof(IClientCapabilitiesService))] -[Export(typeof(RemoteClientCapabilitiesService))] -internal sealed class RemoteClientCapabilitiesService : AbstractClientCapabilitiesService; +[Export(typeof(ILspLifetimeService))] +internal sealed class RemoteClientCapabilitiesService : AbstractClientCapabilitiesService, ILspLifetimeService +{ + public void OnLspInitialized(RemoteClientLSPInitializationOptions options) + { + SetCapabilities(options.ClientCapabilities); + } + + public void OnLspUninitialized() + { + SetCapabilities(null); + } +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientInitializationService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientInitializationService.cs index 9179d952be5..7be6a2978d7 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientInitializationService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Initialization/RemoteClientInitializationService.cs @@ -1,10 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Razor.Remote; -using Microsoft.CodeAnalysis.Remote.Razor.SemanticTokens; namespace Microsoft.CodeAnalysis.Remote.Razor; @@ -16,9 +16,8 @@ protected override IRemoteClientInitializationService CreateService(in ServiceAr => new RemoteClientInitializationService(in args); } - private readonly RemoteClientCapabilitiesService _remoteClientCapabilitiesService = args.ExportProvider.GetExportedValue(); private readonly RemoteLanguageServerFeatureOptions _remoteLanguageServerFeatureOptions = args.ExportProvider.GetExportedValue(); - private readonly RemoteSemanticTokensLegendService _remoteSemanticTokensLegendService = args.ExportProvider.GetExportedValue(); + private readonly IEnumerable _lspLifetimeServices = args.ExportProvider.GetExportedValues(); public ValueTask InitializeAsync(RemoteClientInitializationOptions options, CancellationToken cancellationToken) => RunServiceAsync(ct => @@ -28,11 +27,26 @@ public ValueTask InitializeAsync(RemoteClientInitializationOptions options, Canc }, cancellationToken); - public ValueTask InitializeLSPAsync(RemoteClientLSPInitializationOptions options, CancellationToken cancellationToken) + public ValueTask InitializeLspAsync(RemoteClientLSPInitializationOptions options, CancellationToken cancellationToken) => RunServiceAsync(ct => { - _remoteSemanticTokensLegendService.SetLegend(options.TokenTypes, options.TokenModifiers); - _remoteClientCapabilitiesService.SetCapabilities(options.ClientCapabilities); + foreach (var service in _lspLifetimeServices) + { + service.OnLspInitialized(options); + } + + return default; + }, + cancellationToken); + + public ValueTask UninitializeLspAsync(CancellationToken cancellationToken) + => RunServiceAsync(ct => + { + foreach (var service in _lspLifetimeServices) + { + service.OnLspUninitialized(); + } + return default; }, cancellationToken); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RemoteInlayHintService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RemoteInlayHintService.cs index 40b123d2fb9..5b50bbfc9fd 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RemoteInlayHintService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RemoteInlayHintService.cs @@ -26,6 +26,8 @@ protected override IRemoteInlayHintService CreateService(in ServiceArgs args) => new RemoteInlayHintService(in args); } + private readonly RoslynInlayHintCacheWrapper _cacheWrapper = args.ExportProvider.GetExportedValue(); + public ValueTask GetInlayHintsAsync(JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo, JsonSerializableDocumentId razorDocumentId, InlayHintParams inlayHintParams, bool displayAllOverride, CancellationToken cancellationToken) => RunServiceAsync( solutionInfo, @@ -67,8 +69,7 @@ protected override IRemoteInlayHintService CreateService(in ServiceArgs args) var textDocument = inlayHintParams.TextDocument.WithUri(generatedDocument.CreateUri()); var range = projectedLinePositionSpan.ToRange(); - var hints = await InlayHints.GetInlayHintsAsync(generatedDocument, textDocument, range, displayAllOverride, cancellationToken).ConfigureAwait(false); - + var hints = await InlayHints.GetInlayHintsAsync(generatedDocument, textDocument, range, displayAllOverride, _cacheWrapper.GetCache(), cancellationToken).ConfigureAwait(false); if (hints is null) { return null; @@ -130,6 +131,6 @@ private async ValueTask ResolveInlayHintAsync(RemoteDocumentContext c .GetGeneratedDocumentAsync(cancellationToken) .ConfigureAwait(false); - return await InlayHints.ResolveInlayHintAsync(generatedDocument, inlayHint, cancellationToken).ConfigureAwait(false); + return await InlayHints.ResolveInlayHintAsync(generatedDocument, inlayHint, _cacheWrapper.GetCache(), cancellationToken).ConfigureAwait(false); } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RoslynInlayHintCacheWrapper.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RoslynInlayHintCacheWrapper.cs new file mode 100644 index 00000000000..d86e5b5c7a7 --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RoslynInlayHintCacheWrapper.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Composition; +using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers; +using Microsoft.CodeAnalysis.Razor.Remote; + +namespace Microsoft.CodeAnalysis.Remote.Razor; + +[Shared] +[Export(typeof(ILspLifetimeService))] +[Export(typeof(RoslynInlayHintCacheWrapper))] +internal class RoslynInlayHintCacheWrapper : ILspLifetimeService +{ + private InlayHintCacheWrapper? _inlayHintCacheWrapper; + + public InlayHintCacheWrapper GetCache() + { + _inlayHintCacheWrapper ??= new(); + return _inlayHintCacheWrapper; + } + + void ILspLifetimeService.OnLspInitialized(RemoteClientLSPInitializationOptions options) + { + } + + void ILspLifetimeService.OnLspUninitialized() + { + _inlayHintCacheWrapper = null; + } +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteSemanticTokensLegendService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteSemanticTokensLegendService.cs index 817fdb62f22..08edcbc6c7e 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteSemanticTokensLegendService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteSemanticTokensLegendService.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Composition; +using Microsoft.CodeAnalysis.Razor.Remote; using Microsoft.CodeAnalysis.Razor.SemanticTokens; using SemanticTokenModifiers = Microsoft.CodeAnalysis.Razor.SemanticTokens.SemanticTokenModifiers; using SemanticTokenTypes = Microsoft.CodeAnalysis.Razor.SemanticTokens.SemanticTokenTypes; @@ -10,8 +11,8 @@ namespace Microsoft.CodeAnalysis.Remote.Razor.SemanticTokens; [Shared] [Export(typeof(ISemanticTokensLegendService))] -[Export(typeof(RemoteSemanticTokensLegendService))] -internal sealed class RemoteSemanticTokensLegendService : ISemanticTokensLegendService +[Export(typeof(ILspLifetimeService))] +internal sealed class RemoteSemanticTokensLegendService : ISemanticTokensLegendService, ILspLifetimeService { private SemanticTokenModifiers _tokenModifiers = null!; private SemanticTokenTypes _tokenTypes = null!; @@ -20,9 +21,13 @@ internal sealed class RemoteSemanticTokensLegendService : ISemanticTokensLegendS public SemanticTokenTypes TokenTypes => _tokenTypes; - public void SetLegend(string[] tokenTypes, string[] tokenModifiers) + public void OnLspInitialized(RemoteClientLSPInitializationOptions options) + { + _tokenTypes = new SemanticTokenTypes(options.TokenTypes); + _tokenModifiers = new SemanticTokenModifiers(options.TokenModifiers); + } + + public void OnLspUninitialized() { - _tokenTypes = new SemanticTokenTypes(tokenTypes); - _tokenModifiers = new SemanticTokenModifiers(tokenModifiers); } } diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/RazorConstants.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/RazorConstants.cs index df9493b182a..ef6112182ab 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/RazorConstants.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/RazorConstants.cs @@ -17,6 +17,8 @@ internal static class RazorConstants public const string RazorCohostingUIContext = "6d5b86dc-6b8a-483b-ae30-098a3c7d6774"; + public const string RazorCapabilityPresentUIContext = "2077a158-ee71-484c-be76-350a1d49eaea"; + public static readonly Guid RazorLanguageServiceGuid = new(RazorLanguageServiceString); public const string VSProjectItemsIdentifier = "CF_VSSTGPROJECTITEMS"; diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Remote/RemoteServiceInvoker.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Remote/RemoteServiceInvoker.cs index 6e4b6469085..177718b8aa6 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Remote/RemoteServiceInvoker.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Remote/RemoteServiceInvoker.cs @@ -48,6 +48,7 @@ internal sealed class RemoteServiceInvoker( private readonly object _gate = new(); private Task? _initializeOOPTask; private Task? _initializeLspTask; + private CancellationTokenSource? _uninitializeLspTokenSource; public void Dispose() { @@ -130,7 +131,7 @@ private async static Task GetJsonClientAsync(IWorkspacePr ?? throw new InvalidOperationException($"Couldn't retrieve {nameof(RazorRemoteHostClient)} for JSON serialization."); } - private ValueTask InitializeAsync() + public ValueTask InitializeAsync() { var oopInitialized = _initializeOOPTask is { Status: TaskStatus.RanToCompletion }; var lspInitialized = _initializeLspTask is { Status: TaskStatus.RanToCompletion }; @@ -162,6 +163,8 @@ async Task InitializeCoreAsync(bool oopInitialized, bool lspInitialized) { lock (_gate) { + _uninitializeLspTokenSource?.Cancel(); + _initializeLspTask ??= InitializeLspAsync(remoteClient); } @@ -210,10 +213,40 @@ Task InitializeLspAsync(RazorRemoteHostClient remoteClient) return remoteClient .TryInvokeAsync( - (s, ct) => s.InitializeLSPAsync(initParams, ct), + (s, ct) => s.InitializeLspAsync(initParams, ct), _disposeTokenSource.Token) .AsTask(); } } } + + public ValueTask UninitializeLspAsync() + { + var lspInitialized = _initializeLspTask is { Status: TaskStatus.RanToCompletion }; + + lock (_gate) + { + _uninitializeLspTokenSource?.Cancel(); + + _initializeLspTask = null; + _uninitializeLspTokenSource = new(); + } + + return lspInitialized + ? new(UninitializeCoreAsync(_uninitializeLspTokenSource.Token)) + : default; + + async Task UninitializeCoreAsync(CancellationToken cancellationToken) + { + // Note: IRemoteClientInitializationService is an IRemoteJsonService + var remoteClient = await _lazyJsonClient + .GetValueAsync(cancellationToken) + .ConfigureAwait(false); + + await remoteClient + .TryInvokeAsync( + (s, ct) => s.UninitializeLspAsync(ct), + cancellationToken); + } + } } diff --git a/src/Razor/src/Microsoft.VisualStudio.RazorExtension/RazorPackage.cs b/src/Razor/src/Microsoft.VisualStudio.RazorExtension/RazorPackage.cs index 1b6524be1d9..bddaaecaf19 100644 --- a/src/Razor/src/Microsoft.VisualStudio.RazorExtension/RazorPackage.cs +++ b/src/Razor/src/Microsoft.VisualStudio.RazorExtension/RazorPackage.cs @@ -44,6 +44,15 @@ namespace Microsoft.VisualStudio.RazorExtension; expression: "RazorContentType", termNames: ["RazorContentType"], termValues: [$"ActiveEditorContentType:{RazorConstants.RazorLSPContentTypeName}"])] +// We need to do some initialization when a Razor capability exists in a project, since the source generator +// is key to cohosting. The "ContainsRazorFile" capability doesn't exist yet, but I'm hedging :) +[ProvideUIContextRule( + contextGuid: RazorConstants.RazorCapabilityPresentUIContext, + name: "Razor Capability Present", + expression: "DotNetCoreWeb | DotNetCoreRazor | ContainsRazorFile", + termNames: ["DotNetCoreWeb", "DotNetCoreRazor", "ContainsRazorFile"], + termValues: ["SolutionHasProjectCapability:DotNetCoreWeb", "SolutionHasProjectCapability:DotNetCoreRazor", "SolutionHasProjectCapability:ContainsRazorFile"])] + internal sealed class RazorPackage : AsyncPackage { public const string PackageGuidString = "13b72f58-279e-49e0-a56d-296be02f0805"; diff --git a/src/Razor/src/Microsoft.VisualStudioCode.RazorExtension/Services/VSCodeRemoteServiceInvoker.cs b/src/Razor/src/Microsoft.VisualStudioCode.RazorExtension/Services/VSCodeRemoteServiceInvoker.cs index aece9c3e03b..2866193061f 100644 --- a/src/Razor/src/Microsoft.VisualStudioCode.RazorExtension/Services/VSCodeRemoteServiceInvoker.cs +++ b/src/Razor/src/Microsoft.VisualStudioCode.RazorExtension/Services/VSCodeRemoteServiceInvoker.cs @@ -29,6 +29,10 @@ internal class VSCodeRemoteServiceInvoker( private readonly Lock _serviceLock = new(); private readonly VSCodeBrokeredServiceInterceptor _serviceInterceptor = new(); + public ValueTask InitializeAsync() => ValueTask.CompletedTask; // Initialization in VS Code is handled in VSCodeRemoteServicesInitializer + + public ValueTask UninitializeLspAsync() => ValueTask.CompletedTask; // When VS Code wants to un-initialize a language server, it just kills the process + public async ValueTask TryInvokeAsync( Solution solution, Func> invocation, diff --git a/src/Razor/src/Microsoft.VisualStudioCode.RazorExtension/Services/VSCodeRemoteServicesInitializer.cs b/src/Razor/src/Microsoft.VisualStudioCode.RazorExtension/Services/VSCodeRemoteServicesInitializer.cs index 9d4ef8bafdc..d4b2e25a9f9 100644 --- a/src/Razor/src/Microsoft.VisualStudioCode.RazorExtension/Services/VSCodeRemoteServicesInitializer.cs +++ b/src/Razor/src/Microsoft.VisualStudioCode.RazorExtension/Services/VSCodeRemoteServicesInitializer.cs @@ -63,7 +63,7 @@ await service.InitializeAsync(new RemoteClientInitializationOptions UseVsCodeCompletionTriggerCharacters = _featureOptions.UseVsCodeCompletionTriggerCharacters, }, cancellationToken).ConfigureAwait(false); - await service.InitializeLSPAsync(new RemoteClientLSPInitializationOptions + await service.InitializeLspAsync(new RemoteClientLSPInitializationOptions { ClientCapabilities = clientCapabilities, TokenTypes = _semanticTokensLegendService.TokenTypes.All, diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostEndpointTestBase.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostEndpointTestBase.cs index e56f8fcf7df..34ef6728d4a 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostEndpointTestBase.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostEndpointTestBase.cs @@ -16,11 +16,11 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.CodeAnalysis.Razor.Protocol; using Microsoft.CodeAnalysis.Razor.Remote; using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Remote.Razor; using Microsoft.CodeAnalysis.Remote.Razor.Logging; -using Microsoft.CodeAnalysis.Remote.Razor.SemanticTokens; using Microsoft.CodeAnalysis.Text; using Microsoft.NET.Sdk.Razor.SourceGenerators; using Microsoft.VisualStudio.Composition; @@ -43,8 +43,7 @@ public abstract class CohostEndpointTestBase(ITestOutputHelper testOutputHelper) private protected TestRemoteServiceInvoker RemoteServiceInvoker => _remoteServiceInvoker.AssumeNotNull(); private protected IFilePathService FilePathService => _filePathService.AssumeNotNull(); private protected RemoteLanguageServerFeatureOptions FeatureOptions => OOPExportProvider.GetExportedValue(); - private protected RemoteClientCapabilitiesService ClientCapabilitiesService => OOPExportProvider.GetExportedValue(); - private protected RemoteSemanticTokensLegendService SemanticTokensLegendService => OOPExportProvider.GetExportedValue(); + private protected RemoteClientCapabilitiesService ClientCapabilitiesService => (RemoteClientCapabilitiesService)OOPExportProvider.GetExportedValue(); /// /// The export provider for Razor OOP services (not Roslyn) @@ -145,8 +144,23 @@ private protected void UpdateClientInitializationOptions(Func mutation) { _clientLSPInitializationOptions = mutation(_clientLSPInitializationOptions); - ClientCapabilitiesService.SetCapabilities(_clientLSPInitializationOptions.ClientCapabilities); - SemanticTokensLegendService.SetLegend(_clientLSPInitializationOptions.TokenTypes, _clientLSPInitializationOptions.TokenModifiers); + + var lifetimeServices = OOPExportProvider.GetExportedValues(); + foreach (var service in lifetimeServices) + { + service.OnLspInitialized(_clientLSPInitializationOptions); + } + } + + protected override Task DisposeAsync() + { + var lifetimeServices = OOPExportProvider.GetExportedValues(); + foreach (var service in lifetimeServices) + { + service.OnLspUninitialized(); + } + + return Task.CompletedTask; } private protected virtual TestComposition ConfigureRoslynDevenvComposition(TestComposition composition) diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRoslynRenameTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRoslynRenameTest.cs index 48cbf70b466..820e06fadef 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRoslynRenameTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRoslynRenameTest.cs @@ -202,7 +202,12 @@ internal void SetInvoker(TestRemoteServiceInvoker remoteServiceInvoker) _remoteServiceInvoker = remoteServiceInvoker; } + public ValueTask InitializeAsync() => throw new NotImplementedException(); + + public ValueTask UninitializeLspAsync() => throw new NotImplementedException(); + public ValueTask TryInvokeAsync(Solution solution, Func> invocation, CancellationToken cancellationToken, [CallerFilePath] string? callerFilePath = null, [CallerMemberName] string? callerMemberName = null) where TService : class => _remoteServiceInvoker.AssumeNotNull().TryInvokeAsync(solution, invocation, cancellationToken, callerFilePath, callerMemberName); + } } diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/HtmlDocumentSynchronizerTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/HtmlDocumentSynchronizerTest.cs index f5759050146..396ebd4470b 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/HtmlDocumentSynchronizerTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/HtmlDocumentSynchronizerTest.cs @@ -326,6 +326,10 @@ private class RemoteServiceInvoker(TextDocument document, Func? generateTa public bool OOPReturnsNull { get; set; } public Func? GenerateTask { get; set; } = generateTask; + public ValueTask InitializeAsync() => throw new NotImplementedException(); + + public ValueTask UninitializeLspAsync() => throw new NotImplementedException(); + public async ValueTask TryInvokeAsync(Solution solution, Func> invocation, CancellationToken cancellationToken, [CallerFilePath] string? callerFilePath = null, [CallerMemberName] string? callerMemberName = null) where TService : class { Assert.Equal(typeof(string), typeof(TResult)); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/HtmlRequestInvokerTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/HtmlRequestInvokerTest.cs index a67e5bc8404..aac0ead0636 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/HtmlRequestInvokerTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/HtmlRequestInvokerTest.cs @@ -111,6 +111,10 @@ private async Task MakeHtmlRequestAsync(TextDocument document, Uri htm private class RemoteServiceInvoker : IRemoteServiceInvoker { + public ValueTask InitializeAsync() => throw new NotImplementedException(); + + public ValueTask UninitializeLspAsync() => throw new NotImplementedException(); + public ValueTask TryInvokeAsync(Solution solution, Func> invocation, CancellationToken cancellationToken, [CallerFilePath] string? callerFilePath = null, [CallerMemberName] string? callerMemberName = null) where TService : class { return new((TResult?)(object)""); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestRemoteServiceInvoker.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestRemoteServiceInvoker.cs index 129983f4829..4bdeeaccdd9 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestRemoteServiceInvoker.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestRemoteServiceInvoker.cs @@ -28,6 +28,10 @@ internal sealed class TestRemoteServiceInvoker( private readonly Dictionary _services = []; private readonly ReentrantSemaphore _reentrantSemaphore = ReentrantSemaphore.Create(initialCount: 1, joinableTaskContext); + public ValueTask InitializeAsync() => throw new NotImplementedException(); + + public ValueTask UninitializeLspAsync() => throw new NotImplementedException(); + private async Task GetOrCreateServiceAsync() where TService : class {