зеркало из https://github.com/dotnet/razor.git
Making resolve request handler callable in cohosting
- Making the resolve completion use document for cohosting - Serializing TextDocument property into Data member of completion items so that Roslyn will forward the request to us - Basic sanity test (shows we are getting called now)
This commit is contained in:
Родитель
87e93c7f8a
Коммит
2058042b79
|
@ -25,6 +25,7 @@ using Microsoft.VisualStudio.Razor.Snippets;
|
||||||
using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse<Microsoft.VisualStudio.LanguageServer.Protocol.VSInternalCompletionList?>;
|
using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse<Microsoft.VisualStudio.LanguageServer.Protocol.VSInternalCompletionList?>;
|
||||||
using RoslynCompletionParams = Roslyn.LanguageServer.Protocol.CompletionParams;
|
using RoslynCompletionParams = Roslyn.LanguageServer.Protocol.CompletionParams;
|
||||||
using RoslynLspExtensions = Roslyn.LanguageServer.Protocol.RoslynLspExtensions;
|
using RoslynLspExtensions = Roslyn.LanguageServer.Protocol.RoslynLspExtensions;
|
||||||
|
using RoslynTextDocumentIdentifier = Roslyn.LanguageServer.Protocol.TextDocumentIdentifier;
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
|
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
|
||||||
|
|
||||||
|
@ -64,7 +65,7 @@ internal sealed class CohostDocumentCompletionEndpoint(
|
||||||
Method = Methods.TextDocumentCompletionName,
|
Method = Methods.TextDocumentCompletionName,
|
||||||
RegisterOptions = new CompletionRegistrationOptions()
|
RegisterOptions = new CompletionRegistrationOptions()
|
||||||
{
|
{
|
||||||
ResolveProvider = true, // TODO - change to true when Resolve is implemented
|
ResolveProvider = false, // TODO - change to true when Resolve is implemented
|
||||||
TriggerCharacters = CompletionTriggerAndCommitCharacters.AllTriggerCharacters,
|
TriggerCharacters = CompletionTriggerAndCommitCharacters.AllTriggerCharacters,
|
||||||
AllCommitCharacters = CompletionTriggerAndCommitCharacters.AllCommitCharacters
|
AllCommitCharacters = CompletionTriggerAndCommitCharacters.AllCommitCharacters
|
||||||
}
|
}
|
||||||
|
@ -88,8 +89,11 @@ internal sealed class CohostDocumentCompletionEndpoint(
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save as it may be modified if we forward request to HTML language server
|
||||||
|
var originalTextDocumentIdentifier = request.TextDocument;
|
||||||
|
|
||||||
// Return immediately if this is auto-shown completion but auto-shown completion is disallowed in settings
|
// Return immediately if this is auto-shown completion but auto-shown completion is disallowed in settings
|
||||||
var clientSettings = _clientSettingsManager.GetClientSettings();
|
var clientSettings = _clientSettingsManager.GetClientSettings();
|
||||||
var autoShownCompletion = completionContext.TriggerKind != CompletionTriggerKind.Invoked;
|
var autoShownCompletion = completionContext.TriggerKind != CompletionTriggerKind.Invoked;
|
||||||
if (autoShownCompletion && !clientSettings.ClientCompletionSettings.AutoShowCompletion)
|
if (autoShownCompletion && !clientSettings.ClientCompletionSettings.AutoShowCompletion)
|
||||||
{
|
{
|
||||||
|
@ -187,6 +191,11 @@ internal sealed class CohostDocumentCompletionEndpoint(
|
||||||
completionContext.TriggerCharacter);
|
completionContext.TriggerCharacter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (combinedCompletionList != null)
|
||||||
|
{
|
||||||
|
AddResolutionParams(combinedCompletionList, originalTextDocumentIdentifier);
|
||||||
|
}
|
||||||
|
|
||||||
return combinedCompletionList;
|
return combinedCompletionList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,6 +282,15 @@ internal sealed class CohostDocumentCompletionEndpoint(
|
||||||
return completionList;
|
return completionList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void AddResolutionParams(VSInternalCompletionList completionList, RoslynTextDocumentIdentifier textDocumentIdentifier)
|
||||||
|
{
|
||||||
|
foreach (var item in completionList.Items)
|
||||||
|
{
|
||||||
|
var resolutionParams = CohostDocumentCompletionResolveParams.Create(textDocumentIdentifier);
|
||||||
|
item.Data = JsonSerializer.SerializeToElement(resolutionParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal TestAccessor GetTestAccessor() => new(this);
|
internal TestAccessor GetTestAccessor() => new(this);
|
||||||
|
|
||||||
internal readonly struct TestAccessor(CohostDocumentCompletionEndpoint instance)
|
internal readonly struct TestAccessor(CohostDocumentCompletionEndpoint instance)
|
||||||
|
|
|
@ -17,7 +17,7 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
|
||||||
[Export(typeof(IDynamicRegistrationProvider))]
|
[Export(typeof(IDynamicRegistrationProvider))]
|
||||||
[ExportCohostStatelessLspService(typeof(CohostDocumentCompletionResolveEndpoint))]
|
[ExportCohostStatelessLspService(typeof(CohostDocumentCompletionResolveEndpoint))]
|
||||||
#pragma warning restore RS0030 // Do not use banned APIs
|
#pragma warning restore RS0030 // Do not use banned APIs
|
||||||
internal sealed class CohostDocumentCompletionResolveEndpoint : AbstractRazorCohostRequestHandler<RoslynVSInternalCompletionItem, RoslynVSInternalCompletionItem>, IDynamicRegistrationProvider
|
internal sealed class CohostDocumentCompletionResolveEndpoint : AbstractRazorCohostDocumentRequestHandler<RoslynVSInternalCompletionItem, RoslynVSInternalCompletionItem>, IDynamicRegistrationProvider
|
||||||
{
|
{
|
||||||
protected override bool MutatesSolutionState => false;
|
protected override bool MutatesSolutionState => false;
|
||||||
|
|
||||||
|
@ -29,18 +29,45 @@ internal sealed class CohostDocumentCompletionResolveEndpoint : AbstractRazorCoh
|
||||||
{
|
{
|
||||||
return [new Registration()
|
return [new Registration()
|
||||||
{
|
{
|
||||||
Method = Methods.TextDocumentCompletionResolveName
|
Method = Methods.TextDocumentCompletionResolveName,
|
||||||
|
RegisterOptions = new CompletionRegistrationOptions()
|
||||||
|
{
|
||||||
|
ResolveProvider = true
|
||||||
|
}
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Task<RoslynVSInternalCompletionItem> HandleRequestAsync(RoslynVSInternalCompletionItem request, RazorCohostRequestContext context, CancellationToken cancellationToken)
|
protected override RazorTextDocumentIdentifier? GetRazorTextDocumentIdentifier(RoslynVSInternalCompletionItem request)
|
||||||
=> HandleRequestAsync(request);
|
|
||||||
|
|
||||||
private Task<RoslynVSInternalCompletionItem> HandleRequestAsync(RoslynVSInternalCompletionItem request)
|
|
||||||
{
|
{
|
||||||
|
var completionResolveParams = CohostDocumentCompletionResolveParams.GetCohostDocumentCompletionResolveParams(request);
|
||||||
|
return Roslyn.LanguageServer.Protocol.RoslynLspExtensions.ToRazorTextDocumentIdentifier(completionResolveParams.TextDocument);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task<RoslynVSInternalCompletionItem> HandleRequestAsync(RoslynVSInternalCompletionItem request, RazorCohostRequestContext context, CancellationToken cancellationToken)
|
||||||
|
=> HandleRequestAsync(request, cancellationToken);
|
||||||
|
|
||||||
|
private Task<RoslynVSInternalCompletionItem> HandleRequestAsync(RoslynVSInternalCompletionItem request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return Task.FromResult(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: actual request processing code
|
||||||
|
|
||||||
return Task.FromResult(request);
|
return Task.FromResult(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal TestAccessor GetTestAccessor() => new(this);
|
||||||
|
|
||||||
|
internal readonly struct TestAccessor(CohostDocumentCompletionResolveEndpoint instance)
|
||||||
|
{
|
||||||
|
public Task<RoslynVSInternalCompletionItem> HandleRequestAsync(
|
||||||
|
RoslynVSInternalCompletionItem request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
=> instance.HandleRequestAsync(request, cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Text.Json;
|
||||||
|
using System;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Roslyn.LanguageServer.Protocol;
|
||||||
|
|
||||||
|
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
|
||||||
|
|
||||||
|
// Data that's getting sent with each completion item so that we can provide document ID
|
||||||
|
// to Roslyn language server which will use the URI to figure out that language of the request
|
||||||
|
// and forward the request to us. It gets serialized as Data member of the completion item.
|
||||||
|
// Without it, Roslyn won't forward the completion resolve request to us.
|
||||||
|
internal sealed class CohostDocumentCompletionResolveParams
|
||||||
|
{
|
||||||
|
// NOTE: Capital T here is required to match Roslyn's DocumentResolveData structure, so that the Roslyn
|
||||||
|
// language server can correctly route requests to us in cohosting. In future when we normalize
|
||||||
|
// on to Roslyn types, we should inherit from that class so we don't have to remember to do this.
|
||||||
|
[JsonPropertyName("TextDocument")]
|
||||||
|
public required VSTextDocumentIdentifier TextDocument { get; set; }
|
||||||
|
|
||||||
|
public static CohostDocumentCompletionResolveParams Create(TextDocumentIdentifier textDocumentIdentifier)
|
||||||
|
{
|
||||||
|
var vsTextDocumentIdentifier = textDocumentIdentifier is VSTextDocumentIdentifier vsTextDocumentIdentifierValue
|
||||||
|
? vsTextDocumentIdentifierValue
|
||||||
|
: new VSTextDocumentIdentifier() { Uri = textDocumentIdentifier.Uri };
|
||||||
|
|
||||||
|
var resolutionParams = new CohostDocumentCompletionResolveParams()
|
||||||
|
{
|
||||||
|
TextDocument = vsTextDocumentIdentifier
|
||||||
|
};
|
||||||
|
|
||||||
|
return resolutionParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CohostDocumentCompletionResolveParams GetCohostDocumentCompletionResolveParams(VSInternalCompletionItem request)
|
||||||
|
{
|
||||||
|
if (request.Data is not JsonElement paramsObj)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Invalid Completion Resolve Request Received");
|
||||||
|
}
|
||||||
|
|
||||||
|
var resolutionParams = paramsObj.Deserialize<CohostDocumentCompletionResolveParams>();
|
||||||
|
if (resolutionParams is null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"request.Data should be convertible to {nameof(CohostDocumentCompletionResolveParams)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolutionParams;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Razor.Test.Common;
|
||||||
|
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using RoslynTextDocumentIdentifier = Roslyn.LanguageServer.Protocol.TextDocumentIdentifier;
|
||||||
|
using RoslynVSInternalCompletionItem = Roslyn.LanguageServer.Protocol.VSInternalCompletionItem;
|
||||||
|
|
||||||
|
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
|
||||||
|
|
||||||
|
public class CohostDocumentCompletionResolveEndpointTest(ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper)
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task ResolveReturnsSelf()
|
||||||
|
{
|
||||||
|
await VerifyCompletionItemResolveAsync(
|
||||||
|
input: """
|
||||||
|
This is a Razor document.
|
||||||
|
|
||||||
|
<div st$$></div>
|
||||||
|
|
||||||
|
The end.
|
||||||
|
""",
|
||||||
|
initialItemLabel: "TestItem1",
|
||||||
|
expectedItemLabel: "TestItem1");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task VerifyCompletionItemResolveAsync(
|
||||||
|
TestCode input,
|
||||||
|
string initialItemLabel,
|
||||||
|
string expectedItemLabel)
|
||||||
|
{
|
||||||
|
var document = await CreateProjectAndRazorDocumentAsync(input.Text);
|
||||||
|
|
||||||
|
var endpoint = new CohostDocumentCompletionResolveEndpoint();
|
||||||
|
|
||||||
|
var textDocumentIdentifier = new RoslynTextDocumentIdentifier()
|
||||||
|
{
|
||||||
|
Uri = document.CreateUri()
|
||||||
|
};
|
||||||
|
|
||||||
|
var resolutionParams = CohostDocumentCompletionResolveParams.Create(textDocumentIdentifier);
|
||||||
|
|
||||||
|
var request = new RoslynVSInternalCompletionItem()
|
||||||
|
{
|
||||||
|
Data = JsonSerializer.SerializeToElement(resolutionParams),
|
||||||
|
Label = initialItemLabel
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await endpoint.GetTestAccessor().HandleRequestAsync(request, DisposalToken);
|
||||||
|
|
||||||
|
Assert.Equal(result.Label, expectedItemLabel);
|
||||||
|
}
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче