diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/Completion/CohostDocumentCompletionEndpoint.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/Completion/CohostDocumentCompletionEndpoint.cs index 85ecd8dfd36..8bb25539202 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/Completion/CohostDocumentCompletionEndpoint.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/Completion/CohostDocumentCompletionEndpoint.cs @@ -46,7 +46,7 @@ internal sealed class CohostDocumentCompletionEndpoint( CompletionListCache completionListCache, ITelemetryReporter telemetryReporter, ILoggerFactory loggerFactory) - : AbstractCohostDocumentEndpoint(incompatibleProjectService), IDynamicRegistrationProvider + : AbstractCohostDocumentEndpoint(incompatibleProjectService), IDynamicRegistrationProvider { private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker; private readonly IClientSettingsManager _clientSettingsManager = clientSettingsManager; @@ -82,13 +82,12 @@ public ImmutableArray GetRegistrations(VSInternalClientCapabilitie return []; } - protected override RazorTextDocumentIdentifier? GetRazorTextDocumentIdentifier(CompletionParams request) + protected override RazorTextDocumentIdentifier? GetRazorTextDocumentIdentifier(RazorVSInternalCompletionParams request) => request.TextDocument?.ToRazorTextDocumentIdentifier(); - protected override async Task HandleRequestAsync(CompletionParams request, TextDocument razorDocument, CancellationToken cancellationToken) + protected override async Task HandleRequestAsync(RazorVSInternalCompletionParams request, TextDocument razorDocument, CancellationToken cancellationToken) { - if (request.Context is null || - JsonHelpers.Convert(request.Context) is not { } completionContext) + if (request.Context is not { } completionContext) { _logger.LogError("Completion request context is null"); return null; @@ -218,23 +217,23 @@ public ImmutableArray GetRegistrations(VSInternalClientCapabilitie } private async Task GetHtmlCompletionListAsync( - CompletionParams request, + RazorVSInternalCompletionParams completionParams, TextDocument razorDocument, RazorCompletionOptions razorCompletionOptions, Guid correlationId, CancellationToken cancellationToken) { - var result = await _requestInvoker.MakeHtmlLspRequestAsync( + var result = await _requestInvoker.MakeHtmlLspRequestAsync( razorDocument, Methods.TextDocumentCompletionName, - request, + completionParams, TelemetryThresholds.CompletionSubLSPTelemetryThreshold, correlationId, cancellationToken).ConfigureAwait(false); var rewrittenResponse = DelegatedCompletionHelper.RewriteHtmlResponse(result, razorCompletionOptions); - var razorDocumentIdentifier = new TextDocumentIdentifierAndVersion(request.TextDocument, Version: 0); + var razorDocumentIdentifier = new TextDocumentIdentifierAndVersion(completionParams.TextDocument, Version: 0); var resolutionContext = new DelegatedCompletionResolutionContext(razorDocumentIdentifier, RazorLanguageKind.Html, rewrittenResponse.Data ?? rewrittenResponse.ItemDefaults?.Data); var resultId = _completionListCache.Add(rewrittenResponse, resolutionContext); rewrittenResponse.SetResultId(resultId, _clientCapabilitiesService.ClientCapabilities); @@ -285,7 +284,7 @@ ref builder.AsRef(), internal readonly struct TestAccessor(CohostDocumentCompletionEndpoint instance) { public Task HandleRequestAsync( - CompletionParams request, + RazorVSInternalCompletionParams request, TextDocument razorDocument, CancellationToken cancellationToken) => instance.HandleRequestAsync(request, razorDocument, cancellationToken); diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/HtmlRequestInvoker.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/HtmlRequestInvoker.cs index e9ccbe3c897..09a5f6f4a49 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/HtmlRequestInvoker.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/HtmlRequestInvoker.cs @@ -68,7 +68,7 @@ internal sealed class HtmlRequestInvoker( _logger.LogDebug($"Making LSP request for {method} from {htmlDocument.Uri}{(request is ITextDocumentPositionParams positionParams ? $" at {positionParams.Position}" : "")}, checksum {syncResult.Checksum}."); // Passing Guid.Empty to this method will mean no tracking - using var _ = _telemetryReporter.TrackLspRequest(Methods.TextDocumentCodeActionName, RazorLSPConstants.HtmlLanguageServerName, threshold, correlationId); + using var _ = _telemetryReporter.TrackLspRequest(method, RazorLSPConstants.HtmlLanguageServerName, threshold, correlationId); var result = await _requestInvoker.ReinvokeRequestOnServerAsync( htmlDocument.Snapshot.TextBuffer, diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Cohosting/TestHtmlRequestInvoker.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Cohosting/TestHtmlRequestInvoker.cs index 005ee2b56bc..67268af9c0a 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Cohosting/TestHtmlRequestInvoker.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Cohosting/TestHtmlRequestInvoker.cs @@ -13,19 +13,29 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost; internal class TestHtmlRequestInvoker : IHtmlRequestInvoker { - private readonly Dictionary _htmlResponses; + private readonly Dictionary> _getResponses; + + public TestHtmlRequestInvoker() + : this(Array.Empty<(string, Func)>()) + { + } public TestHtmlRequestInvoker(params (string method, object? response)[] htmlResponses) + : this(htmlResponses.Select<(string method, object? response), (string, Func)>(kvp => (kvp.method, _ => kvp.response)).ToArray()) + { + } + + public TestHtmlRequestInvoker(params (string method, Func getResponse)[] htmlResponses) { - _htmlResponses = htmlResponses.ToDictionary(kvp => kvp.method, kvp => kvp.response); + _getResponses = htmlResponses.ToDictionary(kvp => kvp.method, kvp => kvp.getResponse); } public Task MakeHtmlLspRequestAsync(TextDocument razorDocument, string method, TRequest request, TimeSpan threshold, Guid correlationId, CancellationToken cancellationToken) where TRequest : notnull { - if (_htmlResponses is not null && - _htmlResponses.TryGetValue(method, out var response)) + if (_getResponses is not null && + _getResponses.TryGetValue(method, out var getResponse)) { - return Task.FromResult((TResponse?)response); + return Task.FromResult((TResponse?)getResponse(request)); } return Task.FromResult(default); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/Shared/CohostDocumentCompletionEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/Shared/CohostDocumentCompletionEndpointTest.cs index 58ef811555a..4511c2e159c 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/Shared/CohostDocumentCompletionEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/Shared/CohostDocumentCompletionEndpointTest.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -12,6 +13,7 @@ using Microsoft.AspNetCore.Razor.PooledObjects; using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.Razor.Completion; using Microsoft.CodeAnalysis.Razor.Protocol; @@ -346,6 +348,23 @@ This is a Razor document. unexpectedItemLabels: ["snippet1", "snippet2"]); } + [Fact] + public async Task HtmlElementNamesCompletion_UsesRazorVSInternalCompletionParams() + { + await VerifyCompletionListParamsTypeAsync( + input: """ + This is a Razor document. + + <$$1 + """, + completionContext: new VSInternalCompletionContext() + { + InvokeKind = VSInternalCompletionInvokeKind.Typing, + TriggerCharacter = "<", + TriggerKind = CompletionTriggerKind.TriggerCharacter + }); + } + [Fact] public async Task HtmlElementDoNotCommitWithSpace() { @@ -792,7 +811,7 @@ private async Task VerifyCompletionListAsync( NoOpTelemetryReporter.Instance, LoggerFactory); - var request = new CompletionParams() + var request = new RazorVSInternalCompletionParams() { TextDocument = new TextDocumentIdentifier() { @@ -826,7 +845,7 @@ private async Task VerifyCompletionListAsync( if (!commitElementsWithSpace) { - Assert.False(result.Items.Any(item => item.CommitCharacters?.First().Contains(" ") ?? false)); + Assert.False(result.Items.Any(item => item.CommitCharacters?.First().Contains(' ') ?? false)); } if (!autoInsertAttributeQuotes) @@ -855,6 +874,51 @@ private async Task VerifyCompletionListAsync( return result; } + private async Task VerifyCompletionListParamsTypeAsync( + TestCode input, + VSInternalCompletionContext completionContext, + RazorFileKind? fileKind = null) + { + var document = CreateProjectAndRazorDocument(input.Text, fileKind); + var sourceText = await document.GetTextAsync(DisposalToken); + + // Assert the request invoker is passed a RazorVSInternalCompletionParams + var requestInvoker = new TestHtmlRequestInvoker((Methods.TextDocumentCompletionName, ValidateArgType)); + + var languageServerFeatureOptions = new TestLanguageServerFeatureOptions(); + + var completionListCache = new CompletionListCache(); + var endpoint = new CohostDocumentCompletionEndpoint( + IncompatibleProjectService, + RemoteServiceInvoker, + ClientSettingsManager, + ClientCapabilitiesService, + snippetCompletionItemProvider: null, + languageServerFeatureOptions, + requestInvoker, + completionListCache, + NoOpTelemetryReporter.Instance, + LoggerFactory); + + var request = new RazorVSInternalCompletionParams() + { + TextDocument = new TextDocumentIdentifier() + { + DocumentUri = document.CreateDocumentUri() + }, + Position = sourceText.GetPosition(input.Position), + Context = completionContext + }; + + await endpoint.GetTestAccessor().HandleRequestAsync(request, document, DisposalToken); + + static RazorVSInternalCompletionList? ValidateArgType(object arg) + { + Assert.Equal(typeof(RazorVSInternalCompletionParams), arg.GetType()); + return default; + } + } + private async Task VerifyCompletionResolveAsync(CodeAnalysis.TextDocument document, CompletionListCache completionListCache, VSInternalCompletionItem item, string? expected, string expectedResolvedItemDescription) { // We expect data to be a JsonElement, so for tests we have to _not_ strongly type @@ -917,7 +981,7 @@ private async Task VerifyCompletionResolveAsync(CodeAnalysis.TextDocument docume } } - private string? FlattenDescription(ClassifiedTextElement? description) + private static string? FlattenDescription(ClassifiedTextElement? description) { if (description is null) { diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/Shared/CohostRenameEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/Shared/CohostRenameEndpointTest.cs index 9a00ef5b74c..ad499c0a07a 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/Shared/CohostRenameEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/Shared/CohostRenameEndpointTest.cs @@ -178,7 +178,7 @@ private async Task VerifyRenamesAsync( var inputText = await document.GetTextAsync(DisposalToken); var position = inputText.GetPosition(cursorPosition); - var requestInvoker = new TestHtmlRequestInvoker([(Methods.TextDocumentRenameName, null)]); + var requestInvoker = new TestHtmlRequestInvoker([(Methods.TextDocumentRenameName, (object?)null)]); var endpoint = new CohostRenameEndpoint(IncompatibleProjectService, RemoteServiceInvoker, requestInvoker); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/Shared/CohostSignatureHelpEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/Shared/CohostSignatureHelpEndpointTest.cs index b424fc43dbc..70412af6d4e 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/Shared/CohostSignatureHelpEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/Shared/CohostSignatureHelpEndpointTest.cs @@ -94,7 +94,7 @@ private async Task VerifySignatureHelpAsync(string input, string expected, bool ClientSettingsManager.Update(ClientCompletionSettings.Default with { AutoListParams = autoListParams }); - var requestInvoker = new TestHtmlRequestInvoker([(Methods.TextDocumentSignatureHelpName, null)]); + var requestInvoker = new TestHtmlRequestInvoker([(Methods.TextDocumentSignatureHelpName, (object?)null)]); var endpoint = new CohostSignatureHelpEndpoint(IncompatibleProjectService, RemoteServiceInvoker, ClientSettingsManager, requestInvoker);