Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,13 @@ public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilitie
// results we don't want to show. So we want to call HTML LSP only if we know we are in HTML content.
if (documentPositionInfo.LanguageKind == RazorLanguageKind.Html)
{
htmlCompletionList = await GetHtmlCompletionListAsync(request, razorDocument, razorCompletionOptions, correlationId, cancellationToken).ConfigureAwait(false);
var completionParams = new RazorVSInternalCompletionParams()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can/should this endpoint just use RazorVSCompletionParams in its definition directly? They look like they'd be serialization-compatible?

Could be a follow up though, if this needs to get into P2 under M2. We also need to make sure this doesn't do anything weird in VS Code, but that can also wait since we control insertions there more directly.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can/should this endpoint just use RazorVSCompletionParams in its definition directly? They look like they'd be serialization-compatible?

I'll take a look

if this needs to get into P2 under M2

I'm not too concerned about getting this into P2

We also need to make sure this doesn't do anything weird in VS Code

At some point, I'm going to have to get comfortable doing this, but for now, I think I'd still like to lean on you for that verification

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll take a look

Seems to work, updated in commit 2

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At some point, I'm going to have to get comfortable doing this, but for now, I think I'd still like to lean on you for that verification

Sure thing, but I'm sure I'll forget by the week after next when I get back to do it 😛

It's pretty easy though, just create a .vscode folder under your project root, and a settings.json file in there, and put in:

{
    "dotnet.server.componentPaths": {
        "razorExtension": "C:\\[Path to your Razor working folder]\artifacts\\bin\\Microsoft.VisualStudioCode.RazorExtension\\Debug\\net9.0\\"
    }
}

{
Context = completionContext,
Position = request.Position,
TextDocument = request.TextDocument,
};
htmlCompletionList = await GetHtmlCompletionListAsync(completionParams, razorDocument, razorCompletionOptions, correlationId, cancellationToken).ConfigureAwait(false);

if (htmlCompletionList is not null)
{
Expand Down Expand Up @@ -218,23 +224,23 @@ public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilitie
}

private async Task<RazorVSInternalCompletionList?> GetHtmlCompletionListAsync(
CompletionParams request,
RazorVSInternalCompletionParams completionParams,
TextDocument razorDocument,
RazorCompletionOptions razorCompletionOptions,
Guid correlationId,
CancellationToken cancellationToken)
{
var result = await _requestInvoker.MakeHtmlLspRequestAsync<CompletionParams, RazorVSInternalCompletionList>(
var result = await _requestInvoker.MakeHtmlLspRequestAsync<RazorVSInternalCompletionParams, RazorVSInternalCompletionList>(
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Comment thread
ToddGrun marked this conversation as resolved.
using var _ = _telemetryReporter.TrackLspRequest(method, RazorLSPConstants.HtmlLanguageServerName, threshold, correlationId);

var result = await _requestInvoker.ReinvokeRequestOnServerAsync<TRequest, TResponse?>(
htmlDocument.Snapshot.TextBuffer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,29 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;

internal class TestHtmlRequestInvoker : IHtmlRequestInvoker
{
private readonly Dictionary<string, object?> _htmlResponses;
private readonly Dictionary<string, Func<object, object?>> _getResponses;

public TestHtmlRequestInvoker()
: this(Array.Empty<(string, Func<object, object?>)>())
{
}

public TestHtmlRequestInvoker(params (string method, object? response)[] htmlResponses)
: this(htmlResponses.Select<(string method, object? response), (string, Func<object, object?>)>(kvp => (kvp.method, _ => kvp.response)).ToArray())
{
}

public TestHtmlRequestInvoker(params (string method, Func<object, object?> getResponse)[] htmlResponses)
{
_htmlResponses = htmlResponses.ToDictionary(kvp => kvp.method, kvp => kvp.response);
_getResponses = htmlResponses.ToDictionary(kvp => kvp.method, kvp => kvp.getResponse);
}

public Task<TResponse?> MakeHtmlLspRequestAsync<TRequest, TResponse>(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<TResponse?>(default);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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()
{
Expand Down Expand Up @@ -826,7 +845,7 @@ private async Task<RazorVSInternalCompletionList> 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)
Expand Down Expand Up @@ -855,6 +874,51 @@ private async Task<RazorVSInternalCompletionList> 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 CompletionParams()
{
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
Expand Down Expand Up @@ -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)
{
Expand Down
Loading