From b2cbdc2009db36b29bccaa5c56c8f18ec0741060 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 20 Oct 2025 10:09:03 +1100 Subject: [PATCH 1/2] Init semantic token refresh, and queue on request --- .../CohostSemanticTokensInitializeService.cs | 32 +++++++++++++++++++ .../CohostSemanticTokensRangeEndpoint.cs | 16 ++++++++++ 2 files changed, 48 insertions(+) create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/SemanticTokens/CohostSemanticTokensInitializeService.cs diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/SemanticTokens/CohostSemanticTokensInitializeService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/SemanticTokens/CohostSemanticTokensInitializeService.cs new file mode 100644 index 00000000000..015645930f5 --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/SemanticTokens/CohostSemanticTokensInitializeService.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; +using Microsoft.CodeAnalysis.Razor.Workspaces; + +namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost; + +[Export(typeof(IRazorCohostStartupService))] +[method: ImportingConstructor] +internal class CohostSemanticTokensInitializeService( + LanguageServerFeatureOptions languageServerFeatureOptions) : IRazorCohostStartupService +{ + private readonly LanguageServerFeatureOptions _options = languageServerFeatureOptions; + + public int Order => WellKnownStartupOrder.Default; + + public Task StartupAsync(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext, CancellationToken cancellationToken) + { + if (_options.UseRazorCohostServer) + { + // Normally, Roslyn triggers semantic tokens refreshes for additional document changes, but not for Razor documents. + // In cohosting, we want it to consider Razor documents too. + CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers.SemanticTokensRange.RegisterRefresh(requestContext); + } + + return Task.CompletedTask; + } +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/SemanticTokens/CohostSemanticTokensRangeEndpoint.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/SemanticTokens/CohostSemanticTokensRangeEndpoint.cs index 1a3a14bec25..5d4afd20260 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/SemanticTokens/CohostSemanticTokensRangeEndpoint.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/SemanticTokens/CohostSemanticTokensRangeEndpoint.cs @@ -39,6 +39,22 @@ internal sealed class CohostSemanticTokensRangeEndpoint( protected override RazorTextDocumentIdentifier? GetRazorTextDocumentIdentifier(SemanticTokensRangeParams request) => request.TextDocument.ToRazorTextDocumentIdentifier(); + protected override async Task HandleRequestAsync(SemanticTokensRangeParams request, RazorCohostRequestContext context, TextDocument razorDocument, CancellationToken cancellationToken) + { + var result = await HandleRequestAsync(request, razorDocument, cancellationToken).ConfigureAwait(false); + + if (result is not null) + { + // Roslyn uses frozen semantics for semantic tokens, so it could return results from an older project state. + // Every time they get a request they queue up a refresh, which will check the project checksums, and if there + // hasn't been any changes, will no-op. We call into that same logic here to ensure everything is up to date. + // See: https://github.com/dotnet/roslyn/blob/bb57f4643bb3d52eb7626f9863da177d9e219f1e/src/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs#L48-L52 + await CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers.SemanticTokensRange.TryEnqueueRefreshComputationAsync(context, razorDocument.Project, cancellationToken).ConfigureAwait(false); + } + + return result; + } + protected override Task HandleRequestAsync(SemanticTokensRangeParams request, TextDocument razorDocument, CancellationToken cancellationToken) => HandleRequestAsync(razorDocument, request.Range.ToLinePositionSpan(), cancellationToken); From 56d700972a0ee9219eebd5f9b6f24c197278970d Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 22 Oct 2025 08:52:44 +1100 Subject: [PATCH 2/2] Simplify --- .../CohostSemanticTokensInitializeService.cs | 32 ------------------- .../CohostSemanticTokensRangeEndpoint.cs | 3 +- 2 files changed, 2 insertions(+), 33 deletions(-) delete mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/SemanticTokens/CohostSemanticTokensInitializeService.cs diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/SemanticTokens/CohostSemanticTokensInitializeService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/SemanticTokens/CohostSemanticTokensInitializeService.cs deleted file mode 100644 index 015645930f5..00000000000 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/SemanticTokens/CohostSemanticTokensInitializeService.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.ComponentModel.Composition; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; -using Microsoft.CodeAnalysis.Razor.Workspaces; - -namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost; - -[Export(typeof(IRazorCohostStartupService))] -[method: ImportingConstructor] -internal class CohostSemanticTokensInitializeService( - LanguageServerFeatureOptions languageServerFeatureOptions) : IRazorCohostStartupService -{ - private readonly LanguageServerFeatureOptions _options = languageServerFeatureOptions; - - public int Order => WellKnownStartupOrder.Default; - - public Task StartupAsync(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext, CancellationToken cancellationToken) - { - if (_options.UseRazorCohostServer) - { - // Normally, Roslyn triggers semantic tokens refreshes for additional document changes, but not for Razor documents. - // In cohosting, we want it to consider Razor documents too. - CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers.SemanticTokensRange.RegisterRefresh(requestContext); - } - - return Task.CompletedTask; - } -} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/SemanticTokens/CohostSemanticTokensRangeEndpoint.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/SemanticTokens/CohostSemanticTokensRangeEndpoint.cs index 5d4afd20260..edccbc6fb50 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/SemanticTokens/CohostSemanticTokensRangeEndpoint.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/SemanticTokens/CohostSemanticTokensRangeEndpoint.cs @@ -49,7 +49,8 @@ internal sealed class CohostSemanticTokensRangeEndpoint( // Every time they get a request they queue up a refresh, which will check the project checksums, and if there // hasn't been any changes, will no-op. We call into that same logic here to ensure everything is up to date. // See: https://github.com/dotnet/roslyn/blob/bb57f4643bb3d52eb7626f9863da177d9e219f1e/src/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs#L48-L52 - await CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers.SemanticTokensRange.TryEnqueueRefreshComputationAsync(context, razorDocument.Project, cancellationToken).ConfigureAwait(false); + var semanticTokensWrapperService = context.GetRequiredService(); + await semanticTokensWrapperService.TryEnqueueRefreshComputationAsync(razorDocument.Project, cancellationToken).ConfigureAwait(false); } return result;