Merge remote-tracking branch 'upstream/main' into dev/dawengie/CodeActionsPart5

This commit is contained in:
David Wengier 2024-11-06 14:41:19 +11:00
Родитель 61b5d93d9e 57966530bf
Коммит 04da68965e
37 изменённых файлов: 887 добавлений и 107 удалений

Просмотреть файл

@ -6,9 +6,9 @@
<Sha>839cdfb0ecca5e0be3dbccd926e7651ef50fdf10</Sha>
</Dependency>
<!-- Intermediate is necessary for source build. -->
<Dependency Name="Microsoft.SourceBuild.Intermediate.source-build-reference-packages" Version="10.0.0-alpha.1.24521.1">
<Dependency Name="Microsoft.SourceBuild.Intermediate.source-build-reference-packages" Version="10.0.0-alpha.1.24530.1">
<Uri>https://github.com/dotnet/source-build-reference-packages</Uri>
<Sha>ccd0927e3823fb178c7151594f5d2eaba81bba81</Sha>
<Sha>136e43e45e20bd58bf86eeabba0a0fa7e1a4b3ae</Sha>
<SourceBuild RepoName="source-build-reference-packages" ManagedOnly="true" />
</Dependency>
<Dependency Name="Microsoft.Net.Compilers.Toolset" Version="4.13.0-2.24554.8">

Просмотреть файл

@ -49,7 +49,7 @@
<PropertyGroup Label="Automated">
<MicrosoftNETCoreBrowserDebugHostTransportPackageVersion>6.0.2-servicing.22064.6</MicrosoftNETCoreBrowserDebugHostTransportPackageVersion>
<MicrosoftNETCorePlatformsPackageVersion>6.0.1</MicrosoftNETCorePlatformsPackageVersion>
<MicrosoftSourceBuildIntermediatesourcebuildreferencepackagesPackageVersion>10.0.0-alpha.1.24521.1</MicrosoftSourceBuildIntermediatesourcebuildreferencepackagesPackageVersion>
<MicrosoftSourceBuildIntermediatesourcebuildreferencepackagesPackageVersion>10.0.0-alpha.1.24530.1</MicrosoftSourceBuildIntermediatesourcebuildreferencepackagesPackageVersion>
<MicrosoftSourceBuildIntermediatearcadePackageVersion>9.0.0-beta.24516.2</MicrosoftSourceBuildIntermediatearcadePackageVersion>
<MicrosoftDotNetXliffTasksPackageVersion>1.0.0-beta.23475.1</MicrosoftDotNetXliffTasksPackageVersion>
<MicrosoftSourceBuildIntermediatexlifftasksPackageVersion>1.0.0-beta.23475.1</MicrosoftSourceBuildIntermediatexlifftasksPackageVersion>

Просмотреть файл

@ -33,6 +33,7 @@
<ServiceHubService Include="Microsoft.VisualStudio.Razor.GoToImplementation" ClassName="Microsoft.CodeAnalysis.Remote.Razor.RemoteGoToImplementationService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.Razor.SpellCheck" ClassName="Microsoft.CodeAnalysis.Remote.Razor.RemoteSpellCheckService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.Razor.Diagnostics" ClassName="Microsoft.CodeAnalysis.Remote.Razor.RemoteDiagnosticsService+Factory" />
<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" />
</ItemGroup>

Просмотреть файл

@ -130,7 +130,6 @@ internal partial class RazorLanguageServer : SystemTextJsonLanguageServer<RazorR
services.AddDocumentManagementServices(featureOptions);
services.AddFormattingServices(featureOptions);
services.AddOptionsServices(_lspOptions);
services.AddHoverServices();
services.AddTextDocumentServices(featureOptions);
if (!featureOptions.UseRazorCohostServer)
@ -157,6 +156,9 @@ internal partial class RazorLanguageServer : SystemTextJsonLanguageServer<RazorR
services.AddSingleton<IRazorFoldingRangeProvider, UsingsFoldingRangeProvider>();
services.AddSingleton<IFoldingRangeService, FoldingRangeService>();
// Hover
services.AddHoverServices();
}
// Other

Просмотреть файл

@ -0,0 +1,31 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
namespace Microsoft.VisualStudio.LanguageServer.Protocol;
internal static class ClientCapabilitiesExtensions
{
public static MarkupKind GetMarkupKind(this ClientCapabilities clientCapabilities)
{
// If MarkDown is supported, we'll use that.
if (clientCapabilities.TextDocument?.Hover?.ContentFormat is MarkupKind[] contentFormat &&
Array.IndexOf(contentFormat, MarkupKind.Markdown) >= 0)
{
return MarkupKind.Markdown;
}
return MarkupKind.PlainText;
}
public static bool SupportsMarkdown(this ClientCapabilities clientCapabilities)
{
return clientCapabilities.GetMarkupKind() == MarkupKind.Markdown;
}
public static bool SupportsVisualStudioExtensions(this ClientCapabilities clientCapabilities)
{
return (clientCapabilities as VSInternalClientCapabilities)?.SupportsVisualStudioExtensions ?? false;
}
}

Просмотреть файл

@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.CodeAnalysis.Razor.Hover;
@ -10,16 +9,8 @@ internal readonly record struct HoverDisplayOptions(MarkupKind MarkupKind, bool
{
public static HoverDisplayOptions From(ClientCapabilities clientCapabilities)
{
var markupKind = MarkupKind.PlainText;
// If MarkDown is supported, we'll use that.
if (clientCapabilities.TextDocument?.Hover?.ContentFormat is MarkupKind[] contentFormat &&
Array.IndexOf(contentFormat, MarkupKind.Markdown) >= 0)
{
markupKind = MarkupKind.Markdown;
}
var supportsVisualStudioExtensions = (clientCapabilities as VSInternalClientCapabilities)?.SupportsVisualStudioExtensions ?? false;
var markupKind = clientCapabilities.GetMarkupKind();
var supportsVisualStudioExtensions = clientCapabilities.SupportsVisualStudioExtensions();
return new(markupKind, supportsVisualStudioExtensions);
}

Просмотреть файл

@ -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 RoslynHover = Roslyn.LanguageServer.Protocol.Hover;
using RoslynPosition = Roslyn.LanguageServer.Protocol.Position;
namespace Microsoft.CodeAnalysis.Razor.Remote;
internal interface IRemoteHoverService : IRemoteJsonService
{
ValueTask<RemoteResponse<RoslynHover?>> GetHoverAsync(
JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo,
JsonSerializableDocumentId documentId,
RoslynPosition position,
CancellationToken cancellationToken);
}

Просмотреть файл

@ -30,6 +30,7 @@ internal static class RazorServices
[
(typeof(IRemoteClientInitializationService), null),
(typeof(IRemoteGoToDefinitionService), null),
(typeof(IRemoteHoverService), null),
(typeof(IRemoteSignatureHelpService), null),
(typeof(IRemoteInlayHintService), null),
(typeof(IRemoteDocumentSymbolService), null),

Просмотреть файл

@ -0,0 +1,215 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
using Microsoft.CodeAnalysis.Razor.Hover;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Remote;
using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem;
using Roslyn.LanguageServer.Protocol;
using static Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse<Roslyn.LanguageServer.Protocol.Hover?>;
using static Microsoft.VisualStudio.LanguageServer.Protocol.ClientCapabilitiesExtensions;
using static Microsoft.VisualStudio.LanguageServer.Protocol.VsLspExtensions;
using static Roslyn.LanguageServer.Protocol.RoslynLspExtensions;
using ExternalHandlers = Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers;
using Range = Roslyn.LanguageServer.Protocol.Range;
using VsLsp = Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.CodeAnalysis.Remote.Razor;
internal sealed class RemoteHoverService(in ServiceArgs args) : RazorDocumentServiceBase(in args), IRemoteHoverService
{
internal sealed class Factory : FactoryBase<IRemoteHoverService>
{
protected override IRemoteHoverService CreateService(in ServiceArgs args)
=> new RemoteHoverService(in args);
}
private readonly IClientCapabilitiesService _clientCapabilitiesService = args.ExportProvider.GetExportedValue<IClientCapabilitiesService>();
protected override IDocumentPositionInfoStrategy DocumentPositionInfoStrategy => PreferAttributeNameDocumentPositionInfoStrategy.Instance;
public ValueTask<RemoteResponse<Hover?>> GetHoverAsync(
JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo,
JsonSerializableDocumentId documentId,
Position position,
CancellationToken cancellationToken)
=> RunServiceAsync(
solutionInfo,
documentId,
context => GetHoverAsync(context, position, cancellationToken),
cancellationToken);
private async ValueTask<RemoteResponse<Hover?>> GetHoverAsync(
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 clientCapabilities = _clientCapabilitiesService.ClientCapabilities;
var positionInfo = GetPositionInfo(codeDocument, hostDocumentIndex, preferCSharpOverHtml: true);
if (positionInfo.LanguageKind == RazorLanguageKind.CSharp)
{
var generatedDocument = await context.Snapshot
.GetGeneratedDocumentAsync(cancellationToken)
.ConfigureAwait(false);
var csharpHover = await ExternalHandlers.Hover
.GetHoverAsync(
generatedDocument,
positionInfo.Position.ToLinePosition(),
clientCapabilities.SupportsVisualStudioExtensions(),
clientCapabilities.SupportsMarkdown(),
cancellationToken)
.ConfigureAwait(false);
// Roslyn couldn't provide a hover, so we're done.
if (csharpHover is null)
{
return NoFurtherHandling;
}
// Map the hover range back to the host document
if (csharpHover.Range is { } range &&
DocumentMappingService.TryMapToHostDocumentRange(codeDocument.GetCSharpDocument(), range.ToLinePositionSpan(), out var hostDocumentSpan))
{
csharpHover.Range = RoslynLspFactory.CreateRange(hostDocumentSpan);
}
return Results(csharpHover);
}
if (positionInfo.LanguageKind is not (RazorLanguageKind.Html or RazorLanguageKind.Razor))
{
Debug.Fail($"Encountered an unexpected {nameof(RazorLanguageKind)}: {positionInfo.LanguageKind}");
return NoFurtherHandling;
}
// If this is Html or Razor, try to retrieve a hover from Razor.
var options = HoverDisplayOptions.From(clientCapabilities);
var razorHover = await HoverFactory
.GetHoverAsync(codeDocument, hostDocumentIndex, options, context.GetSolutionQueryOperations(), cancellationToken)
.ConfigureAwait(false);
// Roslyn couldn't provide a hover, so we're done.
if (razorHover is null)
{
return CallHtml;
}
// Ensure that we convert our Hover to a Roslyn Hover.
var resultHover = ConvertHover(razorHover);
return Results(resultHover);
}
/// <summary>
/// Converts a <see cref="VsLsp.Hover"/> to a <see cref="Hover"/>.
/// </summary>
/// <remarks>
/// Once Razor moves wholly over to Roslyn.LanguageServer.Protocol, this method can be removed.
/// </remarks>
private Hover ConvertHover(VsLsp.Hover hover)
{
// Note: Razor only ever produces a Hover with MarkupContent or a VSInternalHover with RawContents.
// Both variants return a Range.
return hover switch
{
VsLsp.VSInternalHover { Range: var range, RawContent: { } rawContent } => new VSInternalHover()
{
Range = ConvertRange(range),
Contents = string.Empty,
RawContent = ConvertVsContent(rawContent)
},
VsLsp.Hover { Range: var range, Contents.Fourth: VsLsp.MarkupContent contents } => new Hover()
{
Range = ConvertRange(range),
Contents = ConvertMarkupContent(contents)
},
_ => Assumed.Unreachable<Hover>(),
};
static Range? ConvertRange(VsLsp.Range? range)
{
return range is not null
? RoslynLspFactory.CreateRange(range.ToLinePositionSpan())
: null;
}
static object ConvertVsContent(object obj)
{
return obj switch
{
VisualStudio.Core.Imaging.ImageId imageId => ConvertImageId(imageId),
VisualStudio.Text.Adornments.ImageElement element => ConvertImageElement(element),
VisualStudio.Text.Adornments.ClassifiedTextRun run => ConvertClassifiedTextRun(run),
VisualStudio.Text.Adornments.ClassifiedTextElement element => ConvertClassifiedTextElement(element),
VisualStudio.Text.Adornments.ContainerElement element => ConvertContainerElement(element),
_ => Assumed.Unreachable<object>()
};
static Roslyn.Core.Imaging.ImageId ConvertImageId(VisualStudio.Core.Imaging.ImageId imageId)
{
return new(imageId.Guid, imageId.Id);
}
static Roslyn.Text.Adornments.ImageElement ConvertImageElement(VisualStudio.Text.Adornments.ImageElement element)
{
return new(ConvertImageId(element.ImageId), element.AutomationName);
}
static Roslyn.Text.Adornments.ClassifiedTextRun ConvertClassifiedTextRun(VisualStudio.Text.Adornments.ClassifiedTextRun run)
{
return new(
run.ClassificationTypeName,
run.Text,
(Roslyn.Text.Adornments.ClassifiedTextRunStyle)run.Style,
run.MarkerTagType,
run.NavigationAction,
run.Tooltip);
}
static Roslyn.Text.Adornments.ClassifiedTextElement ConvertClassifiedTextElement(VisualStudio.Text.Adornments.ClassifiedTextElement element)
{
return new(element.Runs.Select(ConvertClassifiedTextRun));
}
static Roslyn.Text.Adornments.ContainerElement ConvertContainerElement(VisualStudio.Text.Adornments.ContainerElement element)
{
return new((Roslyn.Text.Adornments.ContainerElementStyle)element.Style, element.Elements.Select(ConvertVsContent));
}
}
static MarkupContent ConvertMarkupContent(VsLsp.MarkupContent value)
{
return new()
{
Kind = ConvertMarkupKind(value.Kind),
Value = value.Value
};
}
static MarkupKind ConvertMarkupKind(VsLsp.MarkupKind value)
{
return value == VsLsp.MarkupKind.Markdown
? MarkupKind.Markdown
: MarkupKind.PlainText;
}
}
}

Просмотреть файл

@ -32,7 +32,7 @@ internal class CohostDocumentColorEndpoint(
protected override bool RequiresLSPSolution => true;
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext)
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
{
if (clientCapabilities.SupportsVisualStudioExtensions)
{
@ -40,9 +40,6 @@ internal class CohostDocumentColorEndpoint(
{
Method = Methods.TextDocumentDocumentColorName,
RegisterOptions = new DocumentColorRegistrationOptions()
{
DocumentSelector = filter
}
}];
}

Просмотреть файл

@ -55,7 +55,7 @@ internal sealed class CohostDocumentCompletionEndpoint(
protected override bool RequiresLSPSolution => true;
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext)
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
{
if (clientCapabilities.TextDocument?.Completion?.DynamicRegistration is true)
{
@ -66,7 +66,6 @@ internal sealed class CohostDocumentCompletionEndpoint(
{
ResolveProvider = false, // TODO - change to true when Resolve is implemented
TriggerCharacters = CompletionTriggerAndCommitCharacters.AllTriggerCharacters,
DocumentSelector = filter,
AllCommitCharacters = CompletionTriggerAndCommitCharacters.AllCommitCharacters
}
}];
@ -188,7 +187,7 @@ internal sealed class CohostDocumentCompletionEndpoint(
completionContext.TriggerCharacter);
}
return combinedCompletionList;
return combinedCompletionList;
}
private async Task<VSInternalCompletionList?> GetHtmlCompletionListAsync(
@ -219,7 +218,7 @@ internal sealed class CohostDocumentCompletionEndpoint(
return rewrittenResponse;
}
private static T? ToVsLSP<T>(object source) where T : class
private static T? ToVsLSP<T>(object source) where T : class
{
// This is, to say the least, not ideal. In future we're going to normalize on to Roslyn LSP types, and this can go.
var options = new JsonSerializerOptions();

Просмотреть файл

@ -45,7 +45,7 @@ internal sealed class CohostDocumentFormattingEndpoint(
protected override bool RequiresLSPSolution => true;
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext)
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
{
if (clientCapabilities.TextDocument?.Formatting?.DynamicRegistration is true)
{
@ -53,9 +53,6 @@ internal sealed class CohostDocumentFormattingEndpoint(
{
Method = Methods.TextDocumentFormattingName,
RegisterOptions = new DocumentFormattingRegistrationOptions()
{
DocumentSelector = filter
}
}];
}

Просмотреть файл

@ -38,7 +38,7 @@ internal class CohostDocumentHighlightEndpoint(
protected override bool RequiresLSPSolution => true;
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext)
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
{
if (clientCapabilities.SupportsVisualStudioExtensions)
{
@ -46,9 +46,6 @@ internal class CohostDocumentHighlightEndpoint(
{
Method = Methods.TextDocumentDocumentHighlightName,
RegisterOptions = new DocumentHighlightRegistrationOptions()
{
DocumentSelector = filter
}
}];
}

Просмотреть файл

@ -50,7 +50,7 @@ internal class CohostDocumentPullDiagnosticsEndpoint(
protected override bool RequiresLSPSolution => true;
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext)
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
{
// TODO: if (clientCapabilities.TextDocument?.Diagnostic?.DynamicRegistration is true)
{
@ -59,7 +59,6 @@ internal class CohostDocumentPullDiagnosticsEndpoint(
Method = VSInternalMethods.DocumentPullDiagnosticName,
RegisterOptions = new VSInternalDiagnosticRegistrationOptions()
{
DocumentSelector = filter,
DiagnosticKinds = [VSInternalDiagnosticKind.Syntax]
}
}];

Просмотреть файл

@ -31,7 +31,7 @@ internal sealed class CohostDocumentSpellCheckEndpoint(
protected override bool RequiresLSPSolution => true;
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext)
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
{
if (clientCapabilities.SupportsVisualStudioExtensions)
{
@ -39,9 +39,6 @@ internal sealed class CohostDocumentSpellCheckEndpoint(
{
Method = VSInternalMethods.TextDocumentSpellCheckableRangesName,
RegisterOptions = new TextDocumentRegistrationOptions()
{
DocumentSelector = filter
}
}];
}

Просмотреть файл

@ -30,7 +30,7 @@ internal class CohostDocumentSymbolEndpoint(IRemoteServiceInvoker remoteServiceI
protected override bool RequiresLSPSolution => true;
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext)
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
{
if (clientCapabilities.TextDocument?.DocumentSymbol?.DynamicRegistration == true)
{
@ -40,9 +40,6 @@ internal class CohostDocumentSymbolEndpoint(IRemoteServiceInvoker remoteServiceI
{
Method = Methods.TextDocumentDocumentSymbolName,
RegisterOptions = new DocumentSymbolRegistrationOptions()
{
DocumentSelector = filter
}
}];
}

Просмотреть файл

@ -18,7 +18,7 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
#pragma warning restore RS0030 // Do not use banned APIs
internal sealed class CohostEndpointRegistration : IDynamicRegistrationProvider
{
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext)
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
{
return [
// Project Context, for the nav bar
@ -26,25 +26,18 @@ internal sealed class CohostEndpointRegistration : IDynamicRegistrationProvider
{
Method = VSMethods.GetProjectContextsName,
RegisterOptions = new TextDocumentRegistrationOptions()
{
DocumentSelector = filter
}
},
// DidOpen, DidChange, DidClose, for document synchronization
new Registration
{
Method = Methods.TextDocumentDidOpenName,
RegisterOptions = new TextDocumentRegistrationOptions()
{
DocumentSelector = filter
}
},
new Registration
{
Method = Methods.TextDocumentDidChangeName,
RegisterOptions = new TextDocumentChangeRegistrationOptions()
{
DocumentSelector = filter,
SyncKind = TextDocumentSyncKind.Incremental
}
},
@ -52,9 +45,6 @@ internal sealed class CohostEndpointRegistration : IDynamicRegistrationProvider
{
Method = Methods.TextDocumentDidCloseName,
RegisterOptions = new TextDocumentRegistrationOptions()
{
DocumentSelector = filter
}
},
];
}

Просмотреть файл

@ -41,7 +41,7 @@ internal class CohostFoldingRangeEndpoint(
protected override bool RequiresLSPSolution => true;
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext)
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
{
if (clientCapabilities.TextDocument?.FoldingRange?.DynamicRegistration is true)
{
@ -49,9 +49,6 @@ internal class CohostFoldingRangeEndpoint(
{
Method = Methods.TextDocumentFoldingRangeName,
RegisterOptions = new FoldingRangeRegistrationOptions()
{
DocumentSelector = filter
}
}];
}

Просмотреть файл

@ -45,14 +45,14 @@ internal sealed class CohostGoToDefinitionEndpoint(
protected override bool RequiresLSPSolution => true;
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext)
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
{
if (clientCapabilities.TextDocument?.Definition?.DynamicRegistration == true)
{
return [new Registration
{
Method = Methods.TextDocumentDefinitionName,
RegisterOptions = new DefinitionOptions()
RegisterOptions = new DefinitionRegistrationOptions()
}];
}

Просмотреть файл

@ -41,14 +41,14 @@ internal sealed class CohostGoToImplementationEndpoint(
protected override bool RequiresLSPSolution => true;
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext)
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
{
if (clientCapabilities.TextDocument?.Implementation?.DynamicRegistration == true)
{
return [new Registration
{
Method = Methods.TextDocumentImplementationName,
RegisterOptions = new ImplementationOptions()
RegisterOptions = new ImplementationRegistrationOptions()
}];
}

Просмотреть файл

@ -0,0 +1,119 @@
// 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 Microsoft.VisualStudio.LanguageServer.ContainedLanguage;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using RoslynHover = Roslyn.LanguageServer.Protocol.Hover;
using RoslynLspFactory = Roslyn.LanguageServer.Protocol.RoslynLspFactory;
using VsHover = Microsoft.VisualStudio.LanguageServer.Protocol.Hover;
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
#pragma warning disable RS0030 // Do not use banned APIs
[Shared]
[CohostEndpoint(Methods.TextDocumentHoverName)]
[Export(typeof(IDynamicRegistrationProvider))]
[ExportCohostStatelessLspService(typeof(CohostHoverEndpoint))]
[method: ImportingConstructor]
#pragma warning restore RS0030 // Do not use banned APIs
internal sealed class CohostHoverEndpoint(
IRemoteServiceInvoker remoteServiceInvoker,
IHtmlDocumentSynchronizer htmlDocumentSynchronizer,
LSPRequestInvoker requestInvoker)
: AbstractRazorCohostDocumentRequestHandler<TextDocumentPositionParams, SumType<RoslynHover, VsHover>?>, IDynamicRegistrationProvider
{
private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker;
private readonly IHtmlDocumentSynchronizer _htmlDocumentSynchronizer = htmlDocumentSynchronizer;
private readonly LSPRequestInvoker _requestInvoker = requestInvoker;
protected override bool MutatesSolutionState => false;
protected override bool RequiresLSPSolution => true;
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
{
if (clientCapabilities.TextDocument?.Hover?.DynamicRegistration == true)
{
return [new Registration
{
Method = Methods.TextDocumentHoverName,
RegisterOptions = new HoverRegistrationOptions()
}];
}
return [];
}
protected override RazorTextDocumentIdentifier? GetRazorTextDocumentIdentifier(TextDocumentPositionParams request)
=> request.TextDocument.ToRazorTextDocumentIdentifier();
protected override Task<SumType<RoslynHover, VsHover>?> HandleRequestAsync(TextDocumentPositionParams request, RazorCohostRequestContext context, CancellationToken cancellationToken)
=> HandleRequestAsync(
request,
context.TextDocument.AssumeNotNull(),
cancellationToken);
private async Task<SumType<RoslynHover, VsHover>?> HandleRequestAsync(TextDocumentPositionParams request, TextDocument razorDocument, CancellationToken cancellationToken)
{
var position = RoslynLspFactory.CreatePosition(request.Position.ToLinePosition());
var response = await _remoteServiceInvoker
.TryInvokeAsync<IRemoteHoverService, RemoteResponse<RoslynHover?>>(
razorDocument.Project.Solution,
(service, solutionInfo, cancellationToken) =>
service.GetHoverAsync(solutionInfo, razorDocument.Id, position, cancellationToken),
cancellationToken)
.ConfigureAwait(false);
if (response.Result is RoslynHover hover)
{
return hover;
}
if (response.StopHandling)
{
return null;
}
return await GetHtmlHoverAsync(request, razorDocument, cancellationToken).ConfigureAwait(false);
}
private async Task<VsHover?> GetHtmlHoverAsync(TextDocumentPositionParams request, TextDocument razorDocument, CancellationToken cancellationToken)
{
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<TextDocumentPositionParams, VsHover?>(
htmlDocument.Buffer,
Methods.TextDocumentHoverName,
RazorLSPConstants.HtmlLanguageServerName,
request,
cancellationToken)
.ConfigureAwait(false);
return result?.Response;
}
internal TestAccessor GetTestAccessor() => new(this);
internal readonly struct TestAccessor(CohostHoverEndpoint instance)
{
public Task<SumType<RoslynHover, VsHover>?> HandleRequestAsync(
TextDocumentPositionParams request, TextDocument razorDocument, CancellationToken cancellationToken)
=> instance.HandleRequestAsync(request, razorDocument, cancellationToken);
}
}

Просмотреть файл

@ -30,7 +30,7 @@ internal class CohostInlayHintEndpoint(IRemoteServiceInvoker remoteServiceInvoke
protected override bool RequiresLSPSolution => true;
public ImmutableArray<VSLSP.Registration> GetRegistrations(VSLSP.VSInternalClientCapabilities clientCapabilities, VSLSP.DocumentFilter[] filter, RazorCohostRequestContext requestContext)
public ImmutableArray<VSLSP.Registration> GetRegistrations(VSLSP.VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
{
if (clientCapabilities.TextDocument?.InlayHint?.DynamicRegistration == true)
{
@ -38,9 +38,6 @@ internal class CohostInlayHintEndpoint(IRemoteServiceInvoker remoteServiceInvoke
{
Method = Methods.TextDocumentInlayHintName,
RegisterOptions = new VSLSP.InlayHintRegistrationOptions()
{
DocumentSelector = filter
}
}];
}

Просмотреть файл

@ -32,7 +32,7 @@ internal class CohostLinkedEditingRangeEndpoint(IRemoteServiceInvoker remoteServ
protected override bool RequiresLSPSolution => true;
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext)
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
{
if (clientCapabilities.TextDocument?.LinkedEditingRange?.DynamicRegistration == true)
{
@ -40,9 +40,6 @@ internal class CohostLinkedEditingRangeEndpoint(IRemoteServiceInvoker remoteServ
{
Method = Methods.TextDocumentLinkedEditingRangeName,
RegisterOptions = new LinkedEditingRangeRegistrationOptions()
{
DocumentSelector = filter
}
}];
}

Просмотреть файл

@ -67,14 +67,14 @@ internal class CohostOnAutoInsertEndpoint(
protected override bool RequiresLSPSolution => true;
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext)
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
{
if (clientCapabilities.SupportsVisualStudioExtensions)
{
return [new Registration
{
Method = VSInternalMethods.OnAutoInsertName,
RegisterOptions = new VSInternalDocumentOnAutoInsertOptions()
RegisterOptions = new VSInternalDocumentOnAutoInsertRegistrationOptions()
.EnableOnAutoInsert(_triggerCharacters)
}];
}

Просмотреть файл

@ -46,7 +46,7 @@ internal sealed class CohostOnTypeFormattingEndpoint(
protected override bool RequiresLSPSolution => true;
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext)
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
{
if (clientCapabilities.TextDocument?.Formatting?.DynamicRegistration is true)
{
@ -54,9 +54,7 @@ internal sealed class CohostOnTypeFormattingEndpoint(
{
Method = Methods.TextDocumentOnTypeFormattingName,
RegisterOptions = new DocumentOnTypeFormattingRegistrationOptions()
{
DocumentSelector = filter,
}.EnableOnTypeFormattingTriggerCharacters()
.EnableOnTypeFormattingTriggerCharacters()
}];
}

Просмотреть файл

@ -45,7 +45,7 @@ internal sealed class CohostRangeFormattingEndpoint(
protected override bool RequiresLSPSolution => true;
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext)
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
{
if (clientCapabilities.TextDocument?.Formatting?.DynamicRegistration is true)
{
@ -53,9 +53,6 @@ internal sealed class CohostRangeFormattingEndpoint(
{
Method = Methods.TextDocumentRangeFormattingName,
RegisterOptions = new DocumentRangeFormattingRegistrationOptions()
{
DocumentSelector = filter
}
}];
}

Просмотреть файл

@ -35,7 +35,7 @@ internal class CohostRenameEndpoint(
protected override bool RequiresLSPSolution => true;
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext)
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
{
if (clientCapabilities.TextDocument?.Rename?.DynamicRegistration == true)
{
@ -44,7 +44,6 @@ internal class CohostRenameEndpoint(
Method = Methods.TextDocumentRenameName,
RegisterOptions = new RenameRegistrationOptions()
{
DocumentSelector = filter,
PrepareProvider = false
}
}];

Просмотреть файл

@ -43,7 +43,7 @@ internal sealed class CohostSemanticTokensRangeEndpoint(
protected override bool MutatesSolutionState => false;
protected override bool RequiresLSPSolution => true;
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext)
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
{
if (clientCapabilities.TextDocument?.SemanticTokens?.DynamicRegistration == true)
{
@ -55,9 +55,7 @@ internal sealed class CohostSemanticTokensRangeEndpoint(
{
Method = Methods.TextDocumentSemanticTokensRangeName,
RegisterOptions = new SemanticTokensRegistrationOptions()
{
DocumentSelector = filter,
}.EnableSemanticTokens(_semanticTokensLegendService)
.EnableSemanticTokens(_semanticTokensLegendService)
}];
}

Просмотреть файл

@ -46,7 +46,7 @@ internal class CohostSignatureHelpEndpoint(
protected override bool RequiresLSPSolution => true;
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext)
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
{
if (clientCapabilities.TextDocument?.SignatureHelp?.DynamicRegistration == true)
{
@ -54,9 +54,7 @@ internal class CohostSignatureHelpEndpoint(
{
Method = Methods.TextDocumentSignatureHelpName,
RegisterOptions = new SignatureHelpRegistrationOptions()
{
DocumentSelector = filter
}.EnableSignatureHelp()
.EnableSignatureHelp()
}];
}

Просмотреть файл

@ -35,7 +35,7 @@ internal class CohostTextPresentationEndpoint(
protected override bool RequiresLSPSolution => true;
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext)
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
{
if (clientCapabilities.SupportsVisualStudioExtensions)
{
@ -43,9 +43,6 @@ internal class CohostTextPresentationEndpoint(
{
Method = VSInternalMethods.TextDocumentTextPresentationName,
RegisterOptions = new TextDocumentRegistrationOptions()
{
DocumentSelector = filter
}
}];
}

Просмотреть файл

@ -39,7 +39,7 @@ internal class CohostUriPresentationEndpoint(
protected override bool RequiresLSPSolution => true;
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext)
public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext)
{
if (clientCapabilities.SupportsVisualStudioExtensions)
{
@ -47,9 +47,6 @@ internal class CohostUriPresentationEndpoint(
{
Method = VSInternalMethods.TextDocumentUriPresentationName,
RegisterOptions = new TextDocumentRegistrationOptions()
{
DocumentSelector = filter
}
}];
}

Просмотреть файл

@ -9,5 +9,5 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
internal interface IDynamicRegistrationProvider
{
ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext);
ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext);
}

Просмотреть файл

@ -23,7 +23,7 @@ internal class RazorCohostDynamicRegistrationService(
Lazy<RazorCohostClientCapabilitiesService> lazyRazorCohostClientCapabilitiesService)
: IRazorCohostDynamicRegistrationService
{
private readonly DocumentFilter[] _filter = [new DocumentFilter()
private static readonly DocumentFilter[] s_filter = [new DocumentFilter()
{
Language = Constants.RazorLanguageName,
Pattern = "**/*.{razor,cshtml}"
@ -50,10 +50,15 @@ internal class RazorCohostDynamicRegistrationService(
foreach (var provider in _lazyRegistrationProviders)
{
foreach (var registration in provider.Value.GetRegistrations(clientCapabilities, _filter, requestContext))
foreach (var registration in provider.Value.GetRegistrations(clientCapabilities, requestContext))
{
// We don't unregister anything, so we don't need to do anything interesting with Ids
registration.Id = Guid.NewGuid().ToString();
if (registration.RegisterOptions is ITextDocumentRegistrationOptions options)
{
options.DocumentSelector = s_filter;
}
registrations.Add(registration);
}
}

Просмотреть файл

@ -0,0 +1,186 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Test.Common;
using Microsoft.AspNetCore.Razor.Test.Common.Mef;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
using Microsoft.CodeAnalysis.Razor.Workspaces;
using Microsoft.VisualStudio.LanguageServer.Client;
using Microsoft.VisualStudio.LanguageServer.ContainedLanguage;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Microsoft.VisualStudio.Razor;
using Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Threading;
using Newtonsoft.Json.Linq;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.VisualStudio.LanguageServices.Razor.Test.Cohost;
public class CohostEndpointTest(ITestOutputHelper testOutputHelper) : ToolingTestBase(testOutputHelper)
{
[Fact]
public void EndpointsHaveUniqueLSPMethods()
{
var methods = new Dictionary<string, Type>();
var endpoints = typeof(CohostColorPresentationEndpoint).Assembly.GetTypes()
.Where(t => t.GetCustomAttribute<CohostEndpointAttribute>() != null)
.Select(t => (t, t.GetCustomAttribute<CohostEndpointAttribute>().Method));
foreach (var (endpointType, method) in endpoints)
{
if (methods.TryGetValue(method, out var existing))
{
Assert.Fail($"Could not register {endpointType.Name} for {method} because {existing.Name} already has.");
}
methods.Add(method, endpointType);
}
}
[Fact]
public void RegistrationsProvideFilter()
{
var testComposition = TestComposition.Roslyn
.Add(TestComposition.Editor)
.AddAssemblies(typeof(CohostLinkedEditingRangeEndpoint).Assembly)
.AddAssemblies(typeof(DefaultLSPRequestInvoker).Assembly)
.AddAssemblies(typeof(LanguageServerFeatureOptions).Assembly)
.AddParts(typeof(TestILanguageServiceBroker2))
.AddExcludedPartTypes(typeof(IWorkspaceProvider))
.AddParts(typeof(TestWorkspaceProvider))
.AddExcludedPartTypes(typeof(ILspEditorFeatureDetector))
.AddParts(typeof(TestLspEditorFeatureDetector));
using var exportProvider = testComposition.ExportProviderFactory.CreateExportProvider();
var providers = exportProvider.GetExportedValues<IDynamicRegistrationProvider>().ToList();
// First we verify that the MEF composition above is correct, otherwise this test will be invalid
var actualProviders = typeof(CohostLinkedEditingRangeEndpoint).Assembly.GetTypes().Where(t => !t.IsInterface && typeof(IDynamicRegistrationProvider).IsAssignableFrom(t)).ToList();
Assert.Equal(actualProviders.OrderBy(a => a.Name).Select(r => r.Name).ToArray(), providers.OrderBy(e => e.GetType().Name).Select(r => r.GetType().Name).ToArray());
var clientCapabilities = new VSInternalClientCapabilities()
{
SupportsVisualStudioExtensions = true,
TextDocument = new TextDocumentClientCapabilities()
{
CodeAction = new() { DynamicRegistration = true },
CodeLens = new() { DynamicRegistration = true },
Completion = new() { DynamicRegistration = true },
Definition = new() { DynamicRegistration = true },
Diagnostic = new() { DynamicRegistration = true },
DocumentHighlight = new() { DynamicRegistration = true },
DocumentLink = new() { DynamicRegistration = true },
DocumentSymbol = new() { DynamicRegistration = true },
FoldingRange = new() { DynamicRegistration = true },
Formatting = new() { DynamicRegistration = true },
Hover = new() { DynamicRegistration = true },
Implementation = new() { DynamicRegistration = true },
InlayHint = new() { DynamicRegistration = true },
LinkedEditingRange = new() { DynamicRegistration = true },
OnTypeFormatting = new() { DynamicRegistration = true },
RangeFormatting = new() { DynamicRegistration = true },
References = new() { DynamicRegistration = true },
Rename = new() { DynamicRegistration = true },
SemanticTokens = new() { DynamicRegistration = true },
SignatureHelp = new() { DynamicRegistration = true },
Synchronization = new() { DynamicRegistration = true },
TypeDefinition = new() { DynamicRegistration = true }
},
};
foreach (var endpoint in providers)
{
if (endpoint is CohostSemanticTokensRangeEndpoint)
{
// We can't currently test this, as the GetRegistrations method calls requestContext.GetRequiredService
// and we can't create a request context ourselves
continue;
}
var registrations = endpoint.GetRegistrations(clientCapabilities, requestContext: new());
// If we didn't get any registrations then the test is probably invalid, and we need to update client capabilities above
if (registrations.Length == 0)
{
Assert.Fail($"Did not get any registrations from {endpoint.GetType().Name}. Client capabilities might be wrong?");
}
foreach (var registration in registrations)
{
var options = registration.RegisterOptions as ITextDocumentRegistrationOptions;
if (options is null)
{
Assert.Fail($"Could not convert registration options from {endpoint.GetType().Name} to {nameof(ITextDocumentRegistrationOptions)}. It was {registration.RegisterOptions?.GetType().Name}");
}
}
}
}
[Export(typeof(ILanguageServiceBroker2))]
private class TestILanguageServiceBroker2 : ILanguageServiceBroker2
{
public IEnumerable<ILanguageClientInstance> ActiveLanguageClients => throw new NotImplementedException();
public IEnumerable<Lazy<ILanguageClient, IContentTypeMetadata>> FactoryLanguageClients => throw new NotImplementedException();
public IEnumerable<Lazy<ILanguageClient, IContentTypeMetadata>> LanguageClients => throw new NotImplementedException();
public event EventHandler<LanguageClientLoadedEventArgs> LanguageClientLoaded { add { } remove { } }
public event AsyncEventHandler<LanguageClientNotifyEventArgs> ClientNotifyAsync { add { } remove { } }
public void AddCustomBufferContentTypes(IEnumerable<string> contentTypes) => throw new NotImplementedException();
public void AddLanguageClients(IEnumerable<Lazy<ILanguageClient, IContentTypeMetadata>> items) => throw new NotImplementedException();
public Task LoadAsync(IContentTypeMetadata contentType, ILanguageClient client) => throw new NotImplementedException();
public void Notify<T>(Notification<T> notification, CancellationToken cancellationToken) => throw new NotImplementedException();
public Task NotifyAsync(ILanguageClient languageClient, string method, JToken parameters, CancellationToken cancellationToken) => throw new NotImplementedException();
public Task OnDidChangeTextDocumentAsync(ITextSnapshot before, ITextSnapshot after, IEnumerable<ITextChange> textChanges) => throw new NotImplementedException();
public Task OnDidCloseTextDocumentAsync(ITextSnapshot snapShot) => throw new NotImplementedException();
public Task OnDidOpenTextDocumentAsync(ITextSnapshot snapShot) => throw new NotImplementedException();
public Task OnDidSaveTextDocumentAsync(ITextDocument document) => throw new NotImplementedException();
public void RemoveCustomBufferContentTypes(IEnumerable<string> contentTypes) => throw new NotImplementedException();
public void RemoveLanguageClients(IEnumerable<Lazy<ILanguageClient, IContentTypeMetadata>> items) => throw new NotImplementedException();
public IAsyncEnumerable<(string client, TResponse? response)> RequestAllAsync<TRequest, TResponse>(Request<TRequest, TResponse> request, CancellationToken cancellationToken) => throw new NotImplementedException();
public Task<(ILanguageClient?, JToken?)> RequestAsync(string[] contentTypes, Func<JToken, bool> capabilitiesFilter, string method, JToken parameters, CancellationToken cancellationToken) => throw new NotImplementedException();
public Task<(ILanguageClient?, JToken?)> RequestAsync(string[] contentTypes, Func<JToken, bool> capabilitiesFilter, string clientName, string method, JToken parameters, CancellationToken cancellationToken) => throw new NotImplementedException();
public Task<ManualInvocationResponse?> RequestAsync(ITextBuffer textBuffer, Func<JToken, bool> capabilitiesFilter, string languageServerName, string method, Func<ITextSnapshot, JToken> parameterFactory, CancellationToken cancellationToken) => throw new NotImplementedException();
public Task<JToken?> RequestAsync(ILanguageClient languageClient, string method, JToken parameters, CancellationToken cancellationToken) => throw new NotImplementedException();
public Task<TResponse?> RequestAsync<TRequest, TResponse>(Request<TRequest, TResponse> request, CancellationToken cancellationToken) => throw new NotImplementedException();
public Task<(ILanguageClient?, TOut)> RequestAsync<TIn, TOut>(string[] contentTypes, Func<ServerCapabilities, bool> capabilitiesFilter, LspRequest<TIn, TOut> method, TIn parameters, CancellationToken cancellationToken) => throw new NotImplementedException();
public Task<TOut> RequestAsync<TIn, TOut>(ILanguageClient languageClient, LspRequest<TIn, TOut> method, TIn parameters, CancellationToken cancellationToken) => throw new NotImplementedException();
public Task<IEnumerable<(ILanguageClient, JToken?)>> RequestMultipleAsync(string[] contentTypes, Func<JToken, bool> capabilitiesFilter, string method, JToken parameters, CancellationToken cancellationToken) => throw new NotImplementedException();
public IAsyncEnumerable<ManualInvocationResponse> RequestMultipleAsync(ITextBuffer textBuffer, Func<JToken, bool> capabilitiesFilter, string method, Func<ITextSnapshot, JToken> parameterFactory, CancellationToken cancellationToken) => throw new NotImplementedException();
public Task<IEnumerable<(ILanguageClient, TOut)>> RequestMultipleAsync<TIn, TOut>(string[] contentTypes, Func<ServerCapabilities, bool> capabilitiesFilter, LspRequest<TIn, TOut> method, TIn parameters, CancellationToken cancellationToken) => throw new NotImplementedException();
}
[Export(typeof(IWorkspaceProvider))]
private class TestWorkspaceProvider : IWorkspaceProvider
{
public CodeAnalysis.Workspace GetWorkspace() => throw new NotImplementedException();
}
[Export(typeof(ILspEditorFeatureDetector))]
private class TestLspEditorFeatureDetector : ILspEditorFeatureDetector
{
public bool IsLiveShareHost() => throw new NotImplementedException();
public bool IsLspEditorEnabled() => throw new NotImplementedException();
public bool IsLspEditorSupported(string documentFilePath) => throw new NotImplementedException();
public bool IsRemoteClient() => throw new NotImplementedException();
}
[Export(typeof(IRazorSemanticTokensRefreshQueue))]
private class TestRazorSemanticTokensRefreshQueue : IRazorSemanticTokensRefreshQueue
{
public void Initialize(string clientCapabilitiesString) => throw new NotImplementedException();
public Task TryEnqueueRefreshComputationAsync(Project project, CancellationToken cancellationToken) => throw new NotImplementedException();
}
}

Просмотреть файл

@ -0,0 +1,148 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Test.Common;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Xunit;
using Xunit.Abstractions;
using RoslynHover = Roslyn.LanguageServer.Protocol.Hover;
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
using static HoverAssertions;
public class CohostHoverEndpointTest(ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper)
{
[Fact]
public async Task Razor()
{
TestCode code = """
<[|PageTi$$tle|]></PageTitle>
<div></div>
@{
var myVariable = "Hello";
var length = myVariable.Length;
}
""";
await VerifyHoverAsync(code, async (hover, document) =>
{
await hover.VerifyRangeAsync(code.Span, document);
hover.VerifyRawContent(
Container(
Container(
Image,
ClassifiedText(
Text("Microsoft"),
Punctuation("."),
Text("AspNetCore"),
Punctuation("."),
Text("Components"),
Punctuation("."),
Text("Web"),
Punctuation("."),
Type("PageTitle")))));
});
}
[Fact]
public async Task Html()
{
TestCode code = """
<PageTitle></PageTitle>
<div$$></div>
@{
var myVariable = "Hello";
var length = myVariable.Length;
}
""";
var htmlResponse = new VSInternalHover();
await VerifyHoverAsync(code, htmlResponse, h => Assert.Same(htmlResponse, h));
}
[Fact]
public async Task CSharp()
{
TestCode code = """
<PageTitle></PageTitle>
<div></div>
@{
var $$[|myVariable|] = "Hello";
var length = myVariable.Length;
}
""";
await VerifyHoverAsync(code, async (hover, document) =>
{
await hover.VerifyRangeAsync(code.Span, document);
hover.VerifyRawContent(
Container(
Container(
Image,
ClassifiedText(
Punctuation("("),
Text("local variable"),
Punctuation(")"),
WhiteSpace(" "),
Keyword("string"),
WhiteSpace(" "),
LocalName("myVariable")))));
});
}
private async Task VerifyHoverAsync(TestCode input, Func<RoslynHover, TextDocument, Task> verifyHover)
{
var document = await CreateProjectAndRazorDocumentAsync(input.Text);
var result = await GetHoverResultAsync(document, input);
Assert.NotNull(result);
var value = result.GetValueOrDefault();
Assert.True(value.TryGetFirst(out var hover));
await verifyHover(hover, document);
}
private async Task VerifyHoverAsync(TestCode input, Hover htmlResponse, Action<Hover?> verifyHover)
{
var document = await CreateProjectAndRazorDocumentAsync(input.Text);
var result = await GetHoverResultAsync(document, input, htmlResponse);
Assert.NotNull(result);
var value = result.GetValueOrDefault();
Assert.True(value.TryGetSecond(out var hover));
verifyHover(hover);
}
private async Task<SumType<RoslynHover, Hover>?> GetHoverResultAsync(TextDocument document, TestCode input, Hover? htmlResponse = null)
{
var inputText = await document.GetTextAsync(DisposalToken);
var linePosition = inputText.GetLinePosition(input.Position);
var requestInvoker = new TestLSPRequestInvoker([(Methods.TextDocumentHoverName, htmlResponse)]);
var endpoint = new CohostHoverEndpoint(RemoteServiceInvoker, TestHtmlDocumentSynchronizer.Instance, requestInvoker);
var textDocumentPositionParams = new TextDocumentPositionParams
{
Position = VsLspFactory.CreatePosition(linePosition),
TextDocument = new TextDocumentIdentifier { Uri = document.CreateUri() },
};
return await endpoint.GetTestAccessor().HandleRequestAsync(textDocumentPositionParams, document, DisposalToken);
}
}

Просмотреть файл

@ -0,0 +1,99 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Razor.Tooltip;
using Microsoft.CodeAnalysis.Text;
using Roslyn.LanguageServer.Protocol;
using Roslyn.Text.Adornments;
using Xunit;
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
internal static class HoverAssertions
{
public static async Task VerifyRangeAsync(this Hover hover, TextSpan expected, TextDocument document)
{
var text = await document.GetTextAsync();
Assert.NotNull(hover.Range);
Assert.Equal(text.GetLinePositionSpan(expected), hover.Range.ToLinePositionSpan());
}
public static void VerifyRawContent(this Hover hover, Action<object?> verifier)
{
var vsHover = Assert.IsType<VSInternalHover>(hover);
verifier(vsHover.RawContent);
}
public static Action<object?> Container(params ImmutableArray<Action<object?>> elements)
=> o =>
{
Assert.NotNull(o);
var container = Assert.IsType<ContainerElement>(o);
var allElements = container.Elements.ToArray();
Assert.Equal(elements.Length, allElements.Length);
for (var i = 0; i < elements.Length; i++)
{
elements[i](allElements[i]);
}
};
public static Action<object?> Image
=> o =>
{
Assert.NotNull(o);
Assert.IsType<ImageElement>(o);
};
public static Action<object?> ClassifiedText(params ImmutableArray<Action<ClassifiedTextRun>> runs)
=> o =>
{
Assert.NotNull(o);
var classifiedText = Assert.IsType<ClassifiedTextElement>(o);
var allRuns = classifiedText.Runs.ToArray();
Assert.Equal(runs.Length, allRuns.Length);
for (var i = 0; i < runs.Length; i++)
{
runs[i](allRuns[i]);
}
};
public static Action<ClassifiedTextRun> Run(string text, string? classificationTypeName = null)
=> run =>
{
if (classificationTypeName is not null)
{
Assert.Equal(classificationTypeName, run.ClassificationTypeName);
}
Assert.Equal(text, run.Text);
};
public static Action<ClassifiedTextRun> Keyword(string text)
=> Run(text, ClassificationTypeNames.Keyword);
public static Action<ClassifiedTextRun> LocalName(string text)
=> Run(text, ClassificationTypeNames.LocalName);
public static Action<ClassifiedTextRun> Punctuation(string text)
=> Run(text, ClassificationTypeNames.Punctuation);
public static Action<ClassifiedTextRun> Text(string text)
=> Run(text, ClassificationTypeNames.Text);
public static Action<ClassifiedTextRun> Type(string text)
=> Run(text, ClassifiedTagHelperTooltipFactory.TypeClassificationName);
public static Action<ClassifiedTextRun> WhiteSpace(string text)
=> Run(text, ClassificationTypeNames.WhiteSpace);
}

Просмотреть файл

@ -40,22 +40,37 @@ internal static class Assumed
public static void Unreachable([CallerFilePath] string? path = null, [CallerLineNumber] int line = 0)
=> ThrowInvalidOperation(SR.This_program_location_is_thought_to_be_unreachable, path, line);
/// <summary>
/// Can be called at points that are assumed to be unreachable at runtime.
/// </summary>
/// <exception cref="InvalidOperationException"/>
[DoesNotReturn]
public static void Unreachable(string message, [CallerFilePath] string? path = null, [CallerLineNumber] int line = 0)
=> ThrowInvalidOperation(message, path, line);
/// <summary>
/// Can be called at points that are assumed to be unreachable at runtime.
/// </summary>
/// <exception cref="InvalidOperationException"/>
[DoesNotReturn]
public static T Unreachable<T>([CallerFilePath] string? path = null, [CallerLineNumber] int line = 0)
{
ThrowInvalidOperation(SR.This_program_location_is_thought_to_be_unreachable, path, line);
return default;
}
=> ThrowInvalidOperation<T>(SR.This_program_location_is_thought_to_be_unreachable, path, line);
/// <summary>
/// Can be called at points that are assumed to be unreachable at runtime.
/// </summary>
/// <exception cref="InvalidOperationException"/>
[DoesNotReturn]
public static T Unreachable<T>(string message, [CallerFilePath] string? path = null, [CallerLineNumber] int line = 0)
=> ThrowInvalidOperation<T>(message, path, line);
[DebuggerHidden]
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowInvalidOperation(string message, string? path, int line)
{
throw new InvalidOperationException(message);
}
=> ThrowHelper.ThrowInvalidOperationException(message + Environment.NewLine + SR.FormatFile_0_Line_1(path, line));
[DebuggerHidden]
[DoesNotReturn]
private static T ThrowInvalidOperation<T>(string message, string? path, int line)
=> ThrowHelper.ThrowInvalidOperationException<T>(message + Environment.NewLine + SR.FormatFile_0_Line_1(path, line));
}