-
Notifications
You must be signed in to change notification settings - Fork 196
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Cohost: Support signature help (#10595)
Requires dotnet/roslyn#74280, and won't even compile without it. Part of #9519 This brings signature help to Cohosting. It's a pretty simply PR, for a pretty simple endpoint, as we just delegate, and there is no translation of delegated info. The interesting part here is that we use `System.Text.Json` for the remote signature help service, because it makes more sense to take advantage of the existing Json converters for the potential complexity of the `SignatureHelp` result type.
- Loading branch information
Showing
15 changed files
with
439 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteJsonService.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT license. See License.txt in the project root for license information. | ||
|
||
namespace Microsoft.CodeAnalysis.Razor.Remote; | ||
|
||
/// <summary> | ||
/// Marker interface to indicate that an OOP service should use Json for communication | ||
/// </summary> | ||
internal interface IRemoteJsonService | ||
{ | ||
} |
16 changes: 16 additions & 0 deletions
16
src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteSignatureHelpService.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT license. See License.txt in the project root for license information. | ||
|
||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis.ExternalAccess.Razor; | ||
using Roslyn.LanguageServer.Protocol; | ||
|
||
namespace Microsoft.CodeAnalysis.Razor.Remote; | ||
|
||
using SignatureHelp = Roslyn.LanguageServer.Protocol.SignatureHelp; | ||
|
||
internal interface IRemoteSignatureHelpService : IRemoteJsonService | ||
{ | ||
ValueTask<SignatureHelp?> GetSignatureHelpAsync(JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo, JsonSerializableDocumentId documentId, Position linePosition, CancellationToken cancellationToken); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteWorkspaceAccessor.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT license. See License.txt in the project root for license information. | ||
|
||
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Api; | ||
|
||
namespace Microsoft.CodeAnalysis.Remote.Razor; | ||
|
||
internal static class RemoteWorkspaceAccessor | ||
{ | ||
/// <summary> | ||
/// Gets the remote workspace used in the Roslyn OOP process | ||
/// </summary> | ||
/// <remarks> | ||
/// Normally getting a workspace is possible from a document, project or solution snapshot but in the Roslyn OOP | ||
/// process that is explicitly denied via an exception. This method serves as a workaround when a workspace is | ||
/// needed (eg, the Go To Definition API requires one). | ||
/// | ||
/// This should be used sparingly nad carefully, and no updates should be made to the workspace. | ||
/// </remarks> | ||
public static Workspace GetWorkspace() | ||
=> RazorBrokeredServiceImplementation.GetWorkspace(); | ||
} |
53 changes: 53 additions & 0 deletions
53
...Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SignatureHelp/RemoteSignatureHelpService.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT license. See License.txt in the project root for license information. | ||
|
||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Razor.Language; | ||
using Microsoft.CodeAnalysis.ExternalAccess.Razor; | ||
using Microsoft.CodeAnalysis.Razor.DocumentMapping; | ||
using Microsoft.CodeAnalysis.Razor.Remote; | ||
using Microsoft.CodeAnalysis.Razor.Workspaces; | ||
using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; | ||
using Microsoft.CodeAnalysis.Text; | ||
using Roslyn.LanguageServer.Protocol; | ||
using ExternalHandlers = Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers; | ||
|
||
namespace Microsoft.CodeAnalysis.Remote.Razor; | ||
|
||
using SignatureHelp = Roslyn.LanguageServer.Protocol.SignatureHelp; | ||
|
||
internal sealed class RemoteSignatureHelpService(in ServiceArgs args) : RazorDocumentServiceBase(in args), IRemoteSignatureHelpService | ||
{ | ||
internal sealed class Factory : FactoryBase<IRemoteSignatureHelpService> | ||
{ | ||
protected override IRemoteSignatureHelpService CreateService(in ServiceArgs args) | ||
=> new RemoteSignatureHelpService(in args); | ||
} | ||
|
||
private readonly IFilePathService _filePathService = args.ExportProvider.GetExportedValue<IFilePathService>(); | ||
private readonly IRazorDocumentMappingService _documentMappingService = args.ExportProvider.GetExportedValue<IRazorDocumentMappingService>(); | ||
|
||
public ValueTask<SignatureHelp?> GetSignatureHelpAsync(JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo, JsonSerializableDocumentId documentId, Position position, CancellationToken cancellationToken) | ||
=> RunServiceAsync( | ||
solutionInfo, | ||
documentId, | ||
context => GetSignatureHelpsAsync(context, position, cancellationToken), | ||
cancellationToken); | ||
|
||
private async ValueTask<SignatureHelp?> GetSignatureHelpsAsync(RemoteDocumentContext context, Position position, CancellationToken cancellationToken) | ||
{ | ||
var codeDocument = await context.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false); | ||
var linePosition = new LinePosition(position.Line, position.Character); | ||
var absoluteIndex = linePosition.GetRequiredAbsoluteIndex(codeDocument.Source.Text, logger: null); | ||
|
||
var generatedDocument = await context.GetGeneratedDocumentAsync(_filePathService, cancellationToken).ConfigureAwait(false); | ||
|
||
if (_documentMappingService.TryMapToGeneratedDocumentPosition(codeDocument.GetCSharpDocument(), absoluteIndex, out var mappedPosition, out _)) | ||
{ | ||
return await ExternalHandlers.SignatureHelp.GetSignatureHelpAsync(generatedDocument, mappedPosition, supportsVisualStudioExtensions: true, cancellationToken).ConfigureAwait(false); | ||
} | ||
|
||
return null; | ||
} | ||
} |
144 changes: 144 additions & 0 deletions
144
....VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostSignatureHelpEndpoint.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT license. See License.txt in the project root for license information. | ||
|
||
using System.Composition; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Razor; | ||
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; | ||
using Microsoft.CodeAnalysis.Razor.Logging; | ||
using Microsoft.CodeAnalysis.Razor.Remote; | ||
using Microsoft.VisualStudio.LanguageServer.ContainedLanguage; | ||
using Microsoft.VisualStudio.LanguageServer.Protocol; | ||
using Microsoft.VisualStudio.Razor.LanguageClient; | ||
using Microsoft.VisualStudio.Razor.LanguageClient.Cohost; | ||
using Microsoft.VisualStudio.Razor.LanguageClient.Extensions; | ||
using Microsoft.VisualStudio.Razor.Settings; | ||
using RLSP = Roslyn.LanguageServer.Protocol; | ||
|
||
namespace Microsoft.VisualStudio.LanguageServices.Razor.LanguageClient.Cohost; | ||
|
||
#pragma warning disable RS0030 // Do not use banned APIs | ||
[Shared] | ||
[CohostEndpoint(Methods.TextDocumentSignatureHelpName)] | ||
[Export(typeof(IDynamicRegistrationProvider))] | ||
[ExportCohostStatelessLspService(typeof(CohostSignatureHelpEndpoint))] | ||
[method: ImportingConstructor] | ||
#pragma warning restore RS0030 // Do not use banned APIs | ||
internal class CohostSignatureHelpEndpoint( | ||
IRemoteServiceInvoker remoteServiceInvoker, | ||
IClientSettingsManager clientSettingsManager, | ||
IHtmlDocumentSynchronizer htmlDocumentSynchronizer, | ||
LSPRequestInvoker requestInvoker, | ||
ILoggerFactory loggerFactory) | ||
: AbstractRazorCohostDocumentRequestHandler<SignatureHelpParams, SumType<SignatureHelp, RLSP.SignatureHelp>?>, IDynamicRegistrationProvider | ||
{ | ||
private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker; | ||
private readonly IClientSettingsManager _clientSettingsManager = clientSettingsManager; | ||
private readonly IHtmlDocumentSynchronizer _htmlDocumentSynchronizer = htmlDocumentSynchronizer; | ||
private readonly LSPRequestInvoker _requestInvoker = requestInvoker; | ||
private readonly ILogger _logger = loggerFactory.GetOrCreateLogger<CohostFoldingRangeEndpoint>(); | ||
|
||
protected override bool MutatesSolutionState => false; | ||
|
||
protected override bool RequiresLSPSolution => true; | ||
|
||
public Registration? GetRegistration(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext) | ||
{ | ||
if (clientCapabilities.TextDocument?.SignatureHelp?.DynamicRegistration == true) | ||
{ | ||
return new Registration() | ||
{ | ||
Method = Methods.TextDocumentSignatureHelpName, | ||
RegisterOptions = new SignatureHelpRegistrationOptions() | ||
{ | ||
DocumentSelector = filter | ||
}.EnableSignatureHelp() | ||
}; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
protected override RazorTextDocumentIdentifier? GetRazorTextDocumentIdentifier(SignatureHelpParams request) | ||
=> new RazorTextDocumentIdentifier(request.TextDocument.Uri, (request.TextDocument as VSTextDocumentIdentifier)?.ProjectContext?.Id); | ||
|
||
// NOTE: The use of SumType here is a little odd, but it allows us to return Roslyn LSP types from the Roslyn call, and VS LSP types from the Html | ||
// call. It works because both sets of types are attributed the right way, so the Json ends up looking the same and the client doesn't | ||
// care. Ideally eventually we will be able to move all of this to just Roslyn LSP types, but we might have to wait for Web Tools | ||
protected override Task<SumType<SignatureHelp, RLSP.SignatureHelp>?> HandleRequestAsync(SignatureHelpParams request, RazorCohostRequestContext context, CancellationToken cancellationToken) | ||
=> HandleRequestAsync(request, context.TextDocument.AssumeNotNull(), cancellationToken); | ||
|
||
private async Task<SumType<SignatureHelp, RLSP.SignatureHelp>?> HandleRequestAsync(SignatureHelpParams request, TextDocument razorDocument, CancellationToken cancellationToken) | ||
{ | ||
// Return nothing if "Parameter Information" option is disabled unless signature help is invoked explicitly via command as opposed to typing or content change | ||
if (request.Context is { TriggerKind: not SignatureHelpTriggerKind.Invoked } && | ||
!_clientSettingsManager.GetClientSettings().ClientCompletionSettings.AutoListParams) | ||
{ | ||
return null; | ||
} | ||
|
||
var data = await _remoteServiceInvoker.TryInvokeAsync<IRemoteSignatureHelpService, RLSP.SignatureHelp?>( | ||
razorDocument.Project.Solution, | ||
(service, solutionInfo, cancellationToken) => service.GetSignatureHelpAsync(solutionInfo, razorDocument.Id, new RLSP.Position(request.Position.Line, request.Position.Character), cancellationToken), | ||
cancellationToken) | ||
.ConfigureAwait(false); | ||
|
||
// If we got a response back, then either Razor or C# wants to do something with this, so we're good to go | ||
if (data is { } signatureHelp) | ||
{ | ||
return signatureHelp; | ||
} | ||
|
||
// If we didn't get anything from Razor or Roslyn, lets ask Html what they want to do | ||
var htmlDocument = await _htmlDocumentSynchronizer.TryGetSynchronizedHtmlDocumentAsync(razorDocument, cancellationToken).ConfigureAwait(false); | ||
if (htmlDocument is null) | ||
{ | ||
return null; | ||
} | ||
|
||
request.TextDocument = request.TextDocument.WithUri(htmlDocument.Uri); | ||
|
||
var result = await _requestInvoker.ReinvokeRequestOnServerAsync<SignatureHelpParams, SignatureHelp?>( | ||
htmlDocument.Buffer, | ||
Methods.TextDocumentSignatureHelpName, | ||
RazorLSPConstants.HtmlLanguageServerName, | ||
request, | ||
cancellationToken) | ||
.ConfigureAwait(false); | ||
|
||
return result?.Response; | ||
} | ||
|
||
internal TestAccessor GetTestAccessor() => new(this); | ||
|
||
internal readonly struct TestAccessor(CohostSignatureHelpEndpoint instance) | ||
{ | ||
internal async Task<string[]?> HandleRequestAndGetLabelsAsync(SignatureHelpParams request, TextDocument document, CancellationToken cancellationToken) | ||
{ | ||
// Our tests don't have IVT to Roslyn.LanguageServer.Protocol (yet!?) so we can't expose the return from HandleRequestAsync directly, | ||
// but rather need to do a little test code here. | ||
var result = await instance.HandleRequestAsync(request, document, cancellationToken); | ||
|
||
if (result is not { } signatureHelp) | ||
{ | ||
return null; | ||
} | ||
|
||
if (signatureHelp.TryGetFirst(out var sigHelp1)) | ||
{ | ||
return sigHelp1.Signatures.Select(s => s.Label).ToArray(); | ||
} | ||
else if (signatureHelp.TryGetSecond(out var sigHelp2)) | ||
{ | ||
return sigHelp2.Signatures.Select(s => s.Label).ToArray(); | ||
} | ||
|
||
Assumed.Unreachable(); | ||
return null; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.