зеркало из https://github.com/dotnet/razor.git
Support FAR in cohosting
This commit is contained in:
Родитель
f3aaba93ee
Коммит
e347343f19
|
@ -36,5 +36,6 @@
|
|||
<ServiceHubService Include="Microsoft.VisualStudio.Razor.Hover" ClassName="Microsoft.CodeAnalysis.Remote.Razor.RemoteHoverService+Factory" />
|
||||
<ServiceHubService Include="Microsoft.VisualStudio.Razor.Completion" ClassName="Microsoft.CodeAnalysis.Remote.Razor.RemoteCompletionService+Factory" />
|
||||
<ServiceHubService Include="Microsoft.VisualStudio.Razor.CodeActions" ClassName="Microsoft.CodeAnalysis.Remote.Razor.RemoteCodeActionsService+Factory" />
|
||||
<ServiceHubService Include="Microsoft.VisualStudio.Razor.FindAllReferences" ClassName="Microsoft.CodeAnalysis.Remote.Razor.RemoteFindAllReferencesService+Factory" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
// 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;
|
||||
using LspLocation = Roslyn.LanguageServer.Protocol.Location;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.Remote;
|
||||
|
||||
internal interface IRemoteFindAllReferencesService : IRemoteJsonService
|
||||
{
|
||||
ValueTask<RemoteResponse<SumType<VSInternalReferenceItem, LspLocation>[]?>> FindAllReferencesAsync(
|
||||
JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo,
|
||||
JsonSerializableDocumentId razorDocumentId,
|
||||
Position position,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
|
@ -39,6 +39,7 @@ internal static class RazorServices
|
|||
(typeof(IRemoteDiagnosticsService), null),
|
||||
(typeof(IRemoteCompletionService), null),
|
||||
(typeof(IRemoteCodeActionsService), null),
|
||||
(typeof(IRemoteFindAllReferencesService), null),
|
||||
];
|
||||
|
||||
private const string ComponentName = "Razor";
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
// 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 Microsoft.CodeAnalysis.Razor.DocumentMapping;
|
||||
using Microsoft.CodeAnalysis.Razor.Protocol;
|
||||
using Microsoft.CodeAnalysis.Razor.Remote;
|
||||
using Microsoft.CodeAnalysis.Remote.Razor.DocumentMapping;
|
||||
using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Roslyn.LanguageServer.Protocol;
|
||||
using static Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse<Roslyn.LanguageServer.Protocol.SumType<Roslyn.LanguageServer.Protocol.VSInternalReferenceItem, Roslyn.LanguageServer.Protocol.Location>[]?>;
|
||||
using ExternalHandlers = Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers;
|
||||
using LspLocation = Roslyn.LanguageServer.Protocol.Location;
|
||||
using VsLspExtensions = Microsoft.VisualStudio.LanguageServer.Protocol.VsLspExtensions;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Remote.Razor;
|
||||
|
||||
internal sealed class RemoteFindAllReferencesService(in ServiceArgs args) : RazorDocumentServiceBase(in args), IRemoteFindAllReferencesService
|
||||
{
|
||||
internal sealed class Factory : FactoryBase<IRemoteFindAllReferencesService>
|
||||
{
|
||||
protected override IRemoteFindAllReferencesService CreateService(in ServiceArgs args)
|
||||
=> new RemoteFindAllReferencesService(in args);
|
||||
}
|
||||
|
||||
private readonly IClientCapabilitiesService _clientCapabilitiesService = args.ExportProvider.GetExportedValue<IClientCapabilitiesService>();
|
||||
|
||||
protected override IDocumentPositionInfoStrategy DocumentPositionInfoStrategy => PreferAttributeNameDocumentPositionInfoStrategy.Instance;
|
||||
|
||||
public ValueTask<RemoteResponse<SumType<VSInternalReferenceItem, LspLocation>[]?>> FindAllReferencesAsync(
|
||||
JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo,
|
||||
JsonSerializableDocumentId documentId,
|
||||
Position position,
|
||||
CancellationToken cancellationToken)
|
||||
=> RunServiceAsync(
|
||||
solutionInfo,
|
||||
documentId,
|
||||
context => GetImplementationAsync(context, position, cancellationToken),
|
||||
cancellationToken);
|
||||
|
||||
private async ValueTask<RemoteResponse<SumType<VSInternalReferenceItem, LspLocation>[]?>> GetImplementationAsync(
|
||||
RemoteDocumentContext context,
|
||||
Position position,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var codeDocument = await context.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!codeDocument.Source.Text.TryGetAbsoluteIndex(position, out var hostDocumentIndex))
|
||||
{
|
||||
return NoFurtherHandling;
|
||||
}
|
||||
|
||||
var positionInfo = GetPositionInfo(codeDocument, hostDocumentIndex, preferCSharpOverHtml: true);
|
||||
|
||||
if (positionInfo.LanguageKind is not RazorLanguageKind.CSharp)
|
||||
{
|
||||
return NoFurtherHandling;
|
||||
}
|
||||
|
||||
// Finally, call into C#.
|
||||
var generatedDocument = await context.Snapshot
|
||||
.GetGeneratedDocumentAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var results = await ExternalHandlers.FindAllReferences
|
||||
.FindReferencesAsync(
|
||||
RemoteWorkspaceAccessor.GetWorkspace(),
|
||||
generatedDocument,
|
||||
VsLspExtensions.ToLinePosition(positionInfo.Position),
|
||||
_clientCapabilitiesService.ClientCapabilities.SupportsVisualStudioExtensions,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (results is null and not [])
|
||||
{
|
||||
// C# didn't return anything, so we're done.
|
||||
return NoFurtherHandling;
|
||||
}
|
||||
|
||||
// Map the C# locations back to the Razor file.
|
||||
foreach (var result in results)
|
||||
{
|
||||
var location = result.TryGetFirst(out var referenceItem)
|
||||
? referenceItem.Location
|
||||
: result.Second;
|
||||
|
||||
var (mappedUri, mappedRange) = await DocumentMappingService.MapToHostDocumentUriAndRangeAsync(context.Snapshot, location.Uri, location.Range.ToLinePositionSpan(), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (referenceItem is not null)
|
||||
{
|
||||
// Indicates the reference item is directly available in the code
|
||||
referenceItem.Origin = VSInternalItemOrigin.Exact;
|
||||
|
||||
// If we're going to change the Uri, then also override the file paths
|
||||
if (mappedUri != location.Uri)
|
||||
{
|
||||
referenceItem.DisplayPath = mappedUri.AbsolutePath;
|
||||
referenceItem.DocumentName = mappedUri.AbsolutePath;
|
||||
}
|
||||
}
|
||||
|
||||
location.Uri = mappedUri;
|
||||
location.Range = mappedRange.ToRange();
|
||||
}
|
||||
|
||||
return Results(results);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Composition;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
|
||||
using Microsoft.CodeAnalysis.Razor.Remote;
|
||||
using Roslyn.LanguageServer.Protocol;
|
||||
using LspLocation = Roslyn.LanguageServer.Protocol.Location;
|
||||
using VSLSP = Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
|
||||
|
||||
#pragma warning disable RS0030 // Do not use banned APIs
|
||||
[Shared]
|
||||
[CohostEndpoint(Methods.TextDocumentReferencesName)]
|
||||
[Export(typeof(IDynamicRegistrationProvider))]
|
||||
[ExportCohostStatelessLspService(typeof(CohostFindAllReferencesEndpoint))]
|
||||
[method: ImportingConstructor]
|
||||
#pragma warning restore RS0030 // Do not use banned APIs
|
||||
internal sealed class CohostFindAllReferencesEndpoint(
|
||||
IRemoteServiceInvoker remoteServiceInvoker)
|
||||
: AbstractRazorCohostDocumentRequestHandler<ReferenceParams, SumType<VSInternalReferenceItem, LspLocation>[]?>, IDynamicRegistrationProvider
|
||||
{
|
||||
private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker;
|
||||
|
||||
protected override bool MutatesSolutionState => false;
|
||||
|
||||
protected override bool RequiresLSPSolution => true;
|
||||
|
||||
public ImmutableArray<VSLSP.Registration> GetRegistrations(VSLSP.VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
|
||||
{
|
||||
if (clientCapabilities.TextDocument?.References?.DynamicRegistration == true)
|
||||
{
|
||||
return [new VSLSP.Registration
|
||||
{
|
||||
Method = Methods.TextDocumentReferencesName,
|
||||
RegisterOptions = new VSLSP.ReferenceRegistrationOptions()
|
||||
}];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
protected override RazorTextDocumentIdentifier? GetRazorTextDocumentIdentifier(ReferenceParams request)
|
||||
=> request.TextDocument.ToRazorTextDocumentIdentifier();
|
||||
|
||||
protected override Task<SumType<VSInternalReferenceItem, LspLocation>[]?> HandleRequestAsync(ReferenceParams request, RazorCohostRequestContext context, CancellationToken cancellationToken)
|
||||
=> HandleRequestAsync(
|
||||
context.TextDocument.AssumeNotNull(),
|
||||
request.Position,
|
||||
cancellationToken);
|
||||
|
||||
private async Task<SumType<VSInternalReferenceItem, LspLocation>[]?> HandleRequestAsync(TextDocument razorDocument, Position position, CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await _remoteServiceInvoker
|
||||
.TryInvokeAsync<IRemoteFindAllReferencesService, RemoteResponse<SumType<VSInternalReferenceItem, LspLocation>[]?>>(
|
||||
razorDocument.Project.Solution,
|
||||
(service, solutionInfo, cancellationToken) =>
|
||||
service.FindAllReferencesAsync(solutionInfo, razorDocument.Id, position, cancellationToken),
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (response.Result is SumType<VSInternalReferenceItem, LspLocation>[] results)
|
||||
{
|
||||
return results;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal TestAccessor GetTestAccessor() => new(this);
|
||||
|
||||
internal readonly struct TestAccessor(CohostFindAllReferencesEndpoint instance)
|
||||
{
|
||||
public Task<SumType<VSInternalReferenceItem, LspLocation>[]?> HandleRequestAsync(TextDocument razorDocument, Position position, CancellationToken cancellationToken)
|
||||
=> instance.HandleRequestAsync(razorDocument, position, cancellationToken);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,240 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Test.Common;
|
||||
using Microsoft.AspNetCore.Razor.Utilities;
|
||||
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Roslyn.LanguageServer.Protocol;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
|
||||
|
||||
public class CohostFindAllReferencesEndpointTest(ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper)
|
||||
{
|
||||
[Theory]
|
||||
[CombinatorialData]
|
||||
public Task FindCSharpMember(bool supportsVSExtensions)
|
||||
=> VerifyFindAllReferencesAsync("""
|
||||
@{
|
||||
string M()
|
||||
{
|
||||
return [|MyName|];
|
||||
}
|
||||
}
|
||||
|
||||
<p>@[|MyName|]</p>
|
||||
|
||||
@code {
|
||||
private const string [|$$MyName|] = "David";
|
||||
}
|
||||
""",
|
||||
supportsVSExtensions);
|
||||
|
||||
[Theory]
|
||||
[CombinatorialData]
|
||||
public async Task ComponentAttribute(bool supportsVSExtensions)
|
||||
{
|
||||
TestCode input = """
|
||||
<SurveyPrompt [|Ti$$tle|]="InputValue" />
|
||||
""";
|
||||
|
||||
TestCode surveyPrompt = """
|
||||
@namespace SomeProject
|
||||
|
||||
<div></div>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public string [|Title|] { get; set; }
|
||||
}
|
||||
""";
|
||||
|
||||
TestCode surveyPromptGeneratedCode = """
|
||||
// <auto-generated/>
|
||||
#pragma warning disable 1591
|
||||
namespace SomeProject
|
||||
{
|
||||
#line default
|
||||
using global::System;
|
||||
using global::System.Collections.Generic;
|
||||
using global::System.Linq;
|
||||
using global::System.Threading.Tasks;
|
||||
#nullable restore
|
||||
#line 1 "c:\users\example\src\SomeProject\_Imports.razor"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
#nullable disable
|
||||
#nullable restore
|
||||
#line 2 "c:\users\example\src\SomeProject\_Imports.razor"
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
|
||||
#nullable disable
|
||||
#nullable restore
|
||||
#line 3 "c:\users\example\src\SomeProject\_Imports.razor"
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
|
||||
#nullable disable
|
||||
#nullable restore
|
||||
#line 4 "c:\users\example\src\SomeProject\_Imports.razor"
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
|
||||
#nullable disable
|
||||
#nullable restore
|
||||
#line 5 "c:\users\example\src\SomeProject\_Imports.razor"
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
#nullable disable
|
||||
#nullable restore
|
||||
public partial class SurveyPrompt : global::Microsoft.AspNetCore.Components.ComponentBase
|
||||
#nullable disable
|
||||
{
|
||||
#pragma warning disable 219
|
||||
private void __RazorDirectiveTokenHelpers__() {
|
||||
((global::System.Action)(() => {
|
||||
#nullable restore
|
||||
#line 1 "c:\users\example\src\SomeProject\SurveyPrompt.razor"
|
||||
global::System.Object __typeHelper = nameof(SomeProject);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
#nullable disable
|
||||
}
|
||||
))();
|
||||
}
|
||||
#pragma warning restore 219
|
||||
#pragma warning disable 0414
|
||||
private static object __o = null;
|
||||
#pragma warning restore 0414
|
||||
#pragma warning disable 1998
|
||||
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
|
||||
{
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
#nullable restore
|
||||
#line 6 "c:\users\example\src\SomeProject\SurveyPrompt.razor"
|
||||
|
||||
[Parameter]
|
||||
public string Title { get; set; }
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
#nullable disable
|
||||
}
|
||||
}
|
||||
#pragma warning restore 1591
|
||||
""";
|
||||
|
||||
await VerifyFindAllReferencesAsync(input, supportsVSExtensions,
|
||||
(FilePath("SurveyPrompt.razor"), surveyPrompt),
|
||||
(FilePath("SurveyPrompt.razor.g.cs"), surveyPromptGeneratedCode));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[CombinatorialData]
|
||||
public async Task OtherCSharpFile(bool supportsVSExtensions)
|
||||
{
|
||||
TestCode input = """
|
||||
@code
|
||||
{
|
||||
public void M()
|
||||
{
|
||||
var x = new OtherClass();
|
||||
x.[|D$$ate|].ToString();
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
TestCode otherClass = """
|
||||
using System;
|
||||
|
||||
namespace SomeProject;
|
||||
|
||||
public class OtherClass
|
||||
{
|
||||
public DateTime [|Date|] => DateTime.Now;
|
||||
}
|
||||
""";
|
||||
|
||||
await VerifyFindAllReferencesAsync(input, supportsVSExtensions,
|
||||
(FilePath("OtherClass.cs"), otherClass));
|
||||
}
|
||||
|
||||
private async Task VerifyFindAllReferencesAsync(TestCode input, bool supportsVSExtensions, params (string fileName, TestCode testCode)[]? additionalFiles)
|
||||
{
|
||||
UpdateClientLSPInitializationOptions(c =>
|
||||
{
|
||||
c.ClientCapabilities.SupportsVisualStudioExtensions = supportsVSExtensions;
|
||||
return c;
|
||||
});
|
||||
|
||||
var document = await CreateProjectAndRazorDocumentAsync(input.Text, additionalFiles: additionalFiles.Select(f => (f.fileName, f.testCode.Text)).ToArray());
|
||||
var inputText = await document.GetTextAsync(DisposalToken);
|
||||
var position = inputText.GetPosition(input.Position);
|
||||
|
||||
var endpoint = new CohostFindAllReferencesEndpoint(RemoteServiceInvoker);
|
||||
|
||||
var textDocumentPositionParams = new TextDocumentPositionParams
|
||||
{
|
||||
Position = position,
|
||||
TextDocument = new TextDocumentIdentifier { Uri = document.CreateUri() },
|
||||
};
|
||||
|
||||
var results = await endpoint.GetTestAccessor().HandleRequestAsync(document, position, DisposalToken);
|
||||
|
||||
Assumes.NotNull(results);
|
||||
|
||||
var totalSpans = input.Spans.Length + additionalFiles.Sum(f => f.testCode.TryGetNamedSpans("", out var spans) ? spans.Length : 0);
|
||||
Assert.Equal(totalSpans, results.Length);
|
||||
|
||||
var razorDocumentUri = document.CreateUri();
|
||||
|
||||
foreach (var result in results)
|
||||
{
|
||||
if (result.TryGetFirst(out var referenceItem))
|
||||
{
|
||||
Assert.True(supportsVSExtensions);
|
||||
if (referenceItem.DisplayPath is not null)
|
||||
{
|
||||
Assert.False(referenceItem.DisplayPath.EndsWith(".g.cs"));
|
||||
}
|
||||
|
||||
if (referenceItem.DocumentName is not null)
|
||||
{
|
||||
Assert.False(referenceItem.DocumentName.EndsWith(".g.cs"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.False(supportsVSExtensions);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var location in results.Select(GetLocation))
|
||||
{
|
||||
if (razorDocumentUri.Equals(location.Uri))
|
||||
{
|
||||
Assert.Single(input.Spans.Where(s => inputText.GetRange(s).Equals(location.Range)));
|
||||
}
|
||||
else
|
||||
{
|
||||
var additionalFile = Assert.Single(additionalFiles.Where(f => FilePathNormalizingComparer.Instance.Equals(f.fileName, location.Uri.AbsolutePath)));
|
||||
var text = SourceText.From(additionalFile.testCode.Text);
|
||||
Assert.Single(additionalFile.testCode.Spans.Where(s => text.GetRange(s).Equals(location.Range)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Location GetLocation(SumType<VSInternalReferenceItem, Location> r)
|
||||
{
|
||||
return r.TryGetFirst(out var refItem)
|
||||
? refItem.Location
|
||||
: r.Second;
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче