diff --git a/eng/targets/Services.props b/eng/targets/Services.props
index 068c5f2e0ab..ede89dc2165 100644
--- a/eng/targets/Services.props
+++ b/eng/targets/Services.props
@@ -23,5 +23,6 @@
+
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs
index d28cb50e47c..f6c83b1a43a 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs
@@ -178,7 +178,6 @@ static void AddHandlers(IServiceCollection services, LanguageServerFeatureOption
services.AddTransient(sp => sp.GetRequiredService());
services.AddHandlerWithCapabilities();
- services.AddHandlerWithCapabilities();
services.AddHandlerWithCapabilities();
services.AddHandlerWithCapabilities();
@@ -186,6 +185,7 @@ static void AddHandlers(IServiceCollection services, LanguageServerFeatureOption
if (!featureOptions.UseRazorCohostServer)
{
+ services.AddHandlerWithCapabilities();
services.AddHandlerWithCapabilities();
services.AddHandlerWithCapabilities();
services.AddHandlerWithCapabilities();
diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/LinePositionExtensions.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/LinePositionExtensions.cs
index 0e547fe85a1..e8d877c4ff5 100644
--- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/LinePositionExtensions.cs
+++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/LinePositionExtensions.cs
@@ -4,6 +4,7 @@
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServer.Protocol;
+using RLSP = Roslyn.LanguageServer.Protocol;
namespace Microsoft.CodeAnalysis.Razor.Workspaces;
@@ -12,6 +13,9 @@ internal static class LinePositionExtensions
public static Position ToPosition(this LinePosition linePosition)
=> new Position(linePosition.Line, linePosition.Character);
+ public static RLSP.Position ToRLSPPosition(this LinePosition linePosition)
+ => new RLSP.Position(linePosition.Line, linePosition.Character);
+
public static bool TryGetAbsoluteIndex(this LinePosition position, SourceText sourceText, ILogger logger, out int absoluteIndex)
=> sourceText.TryGetAbsoluteIndex(position.Line, position.Character, logger, out absoluteIndex);
diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/LinePositionSpanExtensions.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/LinePositionSpanExtensions.cs
index ba6e6f19b29..b446affd1be 100644
--- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/LinePositionSpanExtensions.cs
+++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/LinePositionSpanExtensions.cs
@@ -3,6 +3,7 @@
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServer.Protocol;
+using RLSP = Roslyn.LanguageServer.Protocol;
namespace Microsoft.CodeAnalysis.Razor.Workspaces;
@@ -15,6 +16,13 @@ public static Range ToRange(this LinePositionSpan linePositionSpan)
End = linePositionSpan.End.ToPosition()
};
+ public static RLSP.Range ToRLSPRange(this LinePositionSpan linePositionSpan)
+ => new RLSP.Range
+ {
+ Start = linePositionSpan.Start.ToRLSPPosition(),
+ End = linePositionSpan.End.ToRLSPPosition()
+ };
+
public static TextSpan ToTextSpan(this LinePositionSpan linePositionSpan, SourceText sourceText)
=> sourceText.GetTextSpan(linePositionSpan.Start.Line, linePositionSpan.Start.Character, linePositionSpan.End.Line, linePositionSpan.End.Character);
diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/PositionExtensions.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/PositionExtensions.cs
index 767b310176a..dc71fc530b5 100644
--- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/PositionExtensions.cs
+++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/PositionExtensions.cs
@@ -7,6 +7,7 @@
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServer.Protocol;
+using RLSP = Roslyn.LanguageServer.Protocol;
namespace Microsoft.CodeAnalysis.Razor.Workspaces;
@@ -15,6 +16,9 @@ internal static class PositionExtensions
public static LinePosition ToLinePosition(this Position position)
=> new LinePosition(position.Line, position.Character);
+ public static LinePosition ToLinePosition(this RLSP.Position position)
+ => new LinePosition(position.Line, position.Character);
+
public static bool TryGetAbsoluteIndex(this Position position, SourceText sourceText, ILogger? logger, out int absoluteIndex)
{
if (position is null)
diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RangeExtensions.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RangeExtensions.cs
index 66ac2168234..4f3504b6f8f 100644
--- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RangeExtensions.cs
+++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RangeExtensions.cs
@@ -5,6 +5,7 @@
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Range = Microsoft.VisualStudio.LanguageServer.Protocol.Range;
+using RLSP = Roslyn.LanguageServer.Protocol;
namespace Microsoft.CodeAnalysis.Razor.Workspaces;
@@ -110,6 +111,9 @@ public static TextSpan ToTextSpan(this Range range, SourceText sourceText)
public static LinePositionSpan ToLinePositionSpan(this Range range)
=> new LinePositionSpan(range.Start.ToLinePosition(), range.End.ToLinePosition());
+ public static LinePositionSpan ToLinePositionSpan(this RLSP.Range range)
+ => new LinePositionSpan(range.Start.ToLinePosition(), range.End.ToLinePosition());
+
public static bool IsUndefined(this Range range)
{
if (range is null)
diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/DocumentHighlight/RemoteDocumentHighlight.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/DocumentHighlight/RemoteDocumentHighlight.cs
new file mode 100644
index 00000000000..a0494059d38
--- /dev/null
+++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/DocumentHighlight/RemoteDocumentHighlight.cs
@@ -0,0 +1,28 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT license. See License.txt in the project root for license information.
+
+using System.Runtime.Serialization;
+using Microsoft.CodeAnalysis.Razor.Workspaces;
+using Microsoft.CodeAnalysis.Text;
+using Microsoft.VisualStudio.LanguageServer.Protocol;
+using RLSP = Roslyn.LanguageServer.Protocol;
+
+namespace Microsoft.CodeAnalysis.Razor.Protocol.DocumentHighlight;
+
+using DocumentHighlight = VisualStudio.LanguageServer.Protocol.DocumentHighlight;
+
+[DataContract]
+internal readonly record struct RemoteDocumentHighlight(
+ [property: DataMember(Order = 0)] LinePositionSpan Position,
+ [property: DataMember(Order = 1)] DocumentHighlightKind Kind)
+{
+ public static RemoteDocumentHighlight FromRLSPDocumentHighlight(RLSP.DocumentHighlight h)
+ => new RemoteDocumentHighlight(h.Range.ToLinePositionSpan(), (DocumentHighlightKind)h.Kind);
+
+ public static DocumentHighlight ToLspDocumentHighlight(RemoteDocumentHighlight r)
+ => new DocumentHighlight
+ {
+ Range = r.Position.ToRange(),
+ Kind = r.Kind
+ };
+}
diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteDocumentHighlightService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteDocumentHighlightService.cs
new file mode 100644
index 00000000000..b49b4b86889
--- /dev/null
+++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteDocumentHighlightService.cs
@@ -0,0 +1,15 @@
+// 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.Protocol.DocumentHighlight;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.Razor.Remote;
+
+internal interface IRemoteDocumentHighlightService
+{
+ ValueTask> GetHighlightsAsync(RazorPinnedSolutionInfoWrapper solutionInfo, DocumentId documentId, LinePosition position, CancellationToken cancellationToken);
+}
diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteUriPresentationService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteUriPresentationService.cs
index b5273b77ccb..0c0716604a9 100644
--- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteUriPresentationService.cs
+++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteUriPresentationService.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
-using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
@@ -12,10 +11,6 @@ namespace Microsoft.CodeAnalysis.Razor.Remote;
internal interface IRemoteUriPresentationService
{
- ValueTask GetPresentationAsync(RazorPinnedSolutionInfoWrapper solutionInfo, DocumentId razorDocumentId, LinePositionSpan span, Uri[]? uris, CancellationToken cancellationToken);
+ ValueTask> GetPresentationAsync(RazorPinnedSolutionInfoWrapper solutionInfo, DocumentId razorDocumentId, LinePositionSpan span, Uri[]? uris, CancellationToken cancellationToken);
- [DataContract]
- public record struct Response(
- [property: DataMember(Order = 0)] bool CallHtml,
- [property: DataMember(Order = 1)] TextChange? TextChange);
}
diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/RazorServices.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/RazorServices.cs
index 5b79b2b5005..644881de001 100644
--- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/RazorServices.cs
+++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/RazorServices.cs
@@ -1,6 +1,8 @@
// 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 Microsoft.AspNetCore.Razor.Serialization.MessagePack.Resolvers;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
@@ -8,14 +10,8 @@ namespace Microsoft.CodeAnalysis.Razor.Remote;
internal static class RazorServices
{
- private const string ComponentName = "Razor";
-
- public static readonly RazorServiceDescriptorsWrapper Descriptors = new(
- ComponentName,
- featureDisplayNameProvider: feature => $"Razor {feature} Feature",
- additionalFormatters: [],
- additionalResolvers: TopLevelResolvers.All,
- interfaces:
+ // Internal for testing
+ internal static readonly IEnumerable<(Type, Type?)> MessagePackServices =
[
(typeof(IRemoteLinkedEditingRangeService), null),
(typeof(IRemoteTagHelperProviderService), null),
@@ -23,15 +19,33 @@ internal static class RazorServices
(typeof(IRemoteSemanticTokensService), null),
(typeof(IRemoteHtmlDocumentService), null),
(typeof(IRemoteUriPresentationService), null),
- (typeof(IRemoteFoldingRangeService), null)
- ]);
+ (typeof(IRemoteFoldingRangeService), null),
+ (typeof(IRemoteDocumentHighlightService), null),
+ ];
+
+ // Internal for testing
+ internal static readonly IEnumerable<(Type, Type?)> JsonServices =
+ [
+ (typeof(IRemoteSignatureHelpService), null),
+ ];
+
+ private const string ComponentName = "Razor";
+
+ public static readonly RazorServiceDescriptorsWrapper Descriptors = new(
+ ComponentName,
+ featureDisplayNameProvider: GetFeatureDisplayName,
+ additionalFormatters: [],
+ additionalResolvers: TopLevelResolvers.All,
+ interfaces: MessagePackServices);
public static readonly RazorServiceDescriptorsWrapper JsonDescriptors = new(
ComponentName, // Needs to match the above because so much of our ServiceHub infrastructure is convention based
- featureDisplayNameProvider: feature => $"Razor {feature} Feature",
+ featureDisplayNameProvider: GetFeatureDisplayName,
jsonConverters: RazorServiceDescriptorsWrapper.GetLspConverters(),
- interfaces:
- [
- (typeof(IRemoteSignatureHelpService), null),
- ]);
+ interfaces: JsonServices);
+
+ private static string GetFeatureDisplayName(string feature)
+ {
+ return $"Razor {feature} Feature";
+ }
}
diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/RemoteResponse.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/RemoteResponse.cs
new file mode 100644
index 00000000000..b22ade06a3b
--- /dev/null
+++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/RemoteResponse.cs
@@ -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.Runtime.Serialization;
+
+namespace Microsoft.CodeAnalysis.Razor.Remote;
+
+[DataContract]
+internal record struct RemoteResponse(
+ [property: DataMember(Order = 0)] bool StopHandling,
+ [property: DataMember(Order = 1)] T Result)
+{
+ public static RemoteResponse CallHtml => new(StopHandling: false, Result: default!);
+ public static RemoteResponse NoFurtherHandling => new(StopHandling: true, Result: default!);
+ public static RemoteResponse Results(T result) => new(StopHandling: false, Result: result);
+}
diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentHighlight/RemoteDocumentHighlightService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentHighlight/RemoteDocumentHighlightService.cs
new file mode 100644
index 00000000000..5c940f5dbb8
--- /dev/null
+++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentHighlight/RemoteDocumentHighlightService.cs
@@ -0,0 +1,92 @@
+// 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.AspNetCore.Razor.PooledObjects;
+using Microsoft.CodeAnalysis.ExternalAccess.Razor;
+using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers;
+using Microsoft.CodeAnalysis.Razor.DocumentMapping;
+using Microsoft.CodeAnalysis.Razor.Protocol;
+using Microsoft.CodeAnalysis.Razor.Protocol.DocumentHighlight;
+using Microsoft.CodeAnalysis.Razor.Remote;
+using Microsoft.CodeAnalysis.Razor.Workspaces;
+using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem;
+using Microsoft.CodeAnalysis.Text;
+using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse;
+
+namespace Microsoft.CodeAnalysis.Remote.Razor;
+
+internal sealed partial class RemoteDocumentHighlightService(in ServiceArgs args) : RazorDocumentServiceBase(in args), IRemoteDocumentHighlightService
+{
+ internal sealed class Factory : FactoryBase
+ {
+ protected override IRemoteDocumentHighlightService CreateService(in ServiceArgs args)
+ => new RemoteDocumentHighlightService(in args);
+ }
+
+ private readonly IRazorDocumentMappingService _documentMappingService = args.ExportProvider.GetExportedValue();
+ private readonly IFilePathService _filePathService = args.ExportProvider.GetExportedValue();
+
+ public ValueTask GetHighlightsAsync(
+ RazorPinnedSolutionInfoWrapper solutionInfo,
+ DocumentId razorDocumentId,
+ LinePosition position,
+ CancellationToken cancellationToken)
+ => RunServiceAsync(
+ solutionInfo,
+ razorDocumentId,
+ context => GetHighlightsAsync(context, position, cancellationToken),
+ cancellationToken);
+
+ private async ValueTask GetHighlightsAsync(
+ RemoteDocumentContext context,
+ LinePosition position,
+ CancellationToken cancellationToken)
+ {
+ var sourceText = await context.GetSourceTextAsync(cancellationToken).ConfigureAwait(false);
+ if (!sourceText.TryGetAbsoluteIndex(position.Line, position.Character, out var index))
+ {
+ return Response.NoFurtherHandling;
+ }
+
+ var codeDocument = await context.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false);
+
+ var languageKind = _documentMappingService.GetLanguageKind(codeDocument, index, rightAssociative: true);
+ if (languageKind is RazorLanguageKind.Html)
+ {
+ return Response.CallHtml;
+ }
+ else if (languageKind is RazorLanguageKind.Razor)
+ {
+ return Response.NoFurtherHandling;
+ }
+
+ var csharpDocument = codeDocument.GetCSharpDocument();
+ if (_documentMappingService.TryMapToGeneratedDocumentPosition(csharpDocument, index, out var mappedPosition, out _))
+ {
+ var generatedDocument = await context.GetGeneratedDocumentAsync(_filePathService, cancellationToken).ConfigureAwait(false);
+
+ var highlights = await DocumentHighlights.GetHighlightsAsync(generatedDocument, mappedPosition, cancellationToken).ConfigureAwait(false);
+
+ if (highlights is not null)
+ {
+ using var results = new PooledArrayBuilder();
+
+ foreach (var highlight in highlights)
+ {
+ if (_documentMappingService.TryMapToHostDocumentRange(csharpDocument, highlight.Range.ToLinePositionSpan(), out var mappedRange))
+ {
+ highlight.Range = mappedRange.ToRLSPRange();
+ results.Add(RemoteDocumentHighlight.FromRLSPDocumentHighlight(highlight));
+ }
+ }
+
+ return Response.Results(results.ToArray());
+ }
+ }
+
+ return Response.NoFurtherHandling;
+ }
+}
diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/UriPresentation/RemoteUriPresentationService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/UriPresentation/RemoteUriPresentationService.cs
index 290c61a0a5b..632f4b59e83 100644
--- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/UriPresentation/RemoteUriPresentationService.cs
+++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/UriPresentation/RemoteUriPresentationService.cs
@@ -12,6 +12,7 @@
using Microsoft.CodeAnalysis.Razor.Workspaces;
using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem;
using Microsoft.CodeAnalysis.Text;
+using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse;
namespace Microsoft.CodeAnalysis.Remote.Razor;
@@ -25,7 +26,7 @@ protected override IRemoteUriPresentationService CreateService(in ServiceArgs ar
private readonly IRazorDocumentMappingService _documentMappingService = args.ExportProvider.GetExportedValue();
- public ValueTask GetPresentationAsync(
+ public ValueTask GetPresentationAsync(
RazorPinnedSolutionInfoWrapper solutionInfo,
DocumentId razorDocumentId,
LinePositionSpan span,
@@ -37,11 +38,7 @@ protected override IRemoteUriPresentationService CreateService(in ServiceArgs ar
context => GetPresentationAsync(context, span, uris, cancellationToken),
cancellationToken);
- private static IRemoteUriPresentationService.Response CallHtml => new(CallHtml: true, TextChange: null);
- private static IRemoteUriPresentationService.Response NoFurtherHandling => new(CallHtml: false, TextChange: null);
- private static IRemoteUriPresentationService.Response TextChange(TextChange textChange) => new(CallHtml: false, TextChange: textChange);
-
- private async ValueTask GetPresentationAsync(
+ private async ValueTask GetPresentationAsync(
RemoteDocumentContext context,
LinePositionSpan span,
Uri[]? uris,
@@ -51,7 +48,7 @@ protected override IRemoteUriPresentationService CreateService(in ServiceArgs ar
if (!sourceText.TryGetAbsoluteIndex(span.Start.Line, span.Start.Character, out var index))
{
// If the position is invalid then we shouldn't expect to be able to handle a Html response
- return NoFurtherHandling;
+ return Response.NoFurtherHandling;
}
var codeDocument = await context.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false);
@@ -63,13 +60,13 @@ protected override IRemoteUriPresentationService CreateService(in ServiceArgs ar
// our support for Uri presentation is to insert a Html tag, so we only support Html
// If Roslyn add support in future then this is where it would go.
- return NoFurtherHandling;
+ return Response.NoFurtherHandling;
}
var razorFileUri = UriPresentationHelper.GetComponentFileNameFromUriPresentationRequest(uris, Logger);
if (razorFileUri is null)
{
- return CallHtml;
+ return Response.CallHtml;
}
var solution = context.TextDocument.Project.Solution;
@@ -79,14 +76,14 @@ protected override IRemoteUriPresentationService CreateService(in ServiceArgs ar
var ids = solution.GetDocumentIdsWithFilePath(uriToFind);
if (ids.Length == 0)
{
- return CallHtml;
+ return Response.CallHtml;
}
// We assume linked documents would produce the same component tag so just take the first
var otherDocument = solution.GetAdditionalDocument(ids[0]);
if (otherDocument is null)
{
- return CallHtml;
+ return Response.CallHtml;
}
var otherSnapshot = DocumentSnapshotFactory.GetOrCreate(otherDocument);
@@ -94,15 +91,15 @@ protected override IRemoteUriPresentationService CreateService(in ServiceArgs ar
if (descriptor is null)
{
- return CallHtml;
+ return Response.CallHtml;
}
var tag = descriptor.TryGetComponentTag();
if (tag is null)
{
- return CallHtml;
+ return Response.CallHtml;
}
- return TextChange(new TextChange(span.ToTextSpan(sourceText), tag));
+ return Response.Results(new TextChange(span.ToTextSpan(sourceText), tag));
}
}
diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostDocumentHighlightPresentationEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostDocumentHighlightPresentationEndpoint.cs
new file mode 100644
index 00000000000..568382eaa3d
--- /dev/null
+++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostDocumentHighlightPresentationEndpoint.cs
@@ -0,0 +1,109 @@
+// 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.CodeAnalysis;
+using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
+using Microsoft.CodeAnalysis.Razor.Protocol.DocumentHighlight;
+using Microsoft.CodeAnalysis.Razor.Remote;
+using Microsoft.CodeAnalysis.Razor.Workspaces;
+using Microsoft.VisualStudio.LanguageServer.ContainedLanguage;
+using Microsoft.VisualStudio.LanguageServer.Protocol;
+using Microsoft.VisualStudio.Razor.LanguageClient.Extensions;
+
+namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
+
+#pragma warning disable RS0030 // Do not use banned APIs
+[Shared]
+[CohostEndpoint(Methods.TextDocumentDocumentHighlightName)]
+[Export(typeof(IDynamicRegistrationProvider))]
+[ExportCohostStatelessLspService(typeof(CohostDocumentHighlightEndpoint))]
+[method: ImportingConstructor]
+#pragma warning restore RS0030 // Do not use banned APIs
+internal class CohostDocumentHighlightEndpoint(
+ IRemoteServiceInvoker remoteServiceInvoker,
+ IHtmlDocumentSynchronizer htmlDocumentSynchronizer,
+ LSPRequestInvoker requestInvoker)
+ : AbstractRazorCohostDocumentRequestHandler, 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 Registration? GetRegistration(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext)
+ {
+ if (clientCapabilities.SupportsVisualStudioExtensions)
+ {
+ return new Registration
+ {
+ Method = Methods.TextDocumentDocumentHighlightName,
+ RegisterOptions = new DocumentHighlightRegistrationOptions()
+ {
+ DocumentSelector = filter
+ }
+ };
+ }
+
+ return null;
+ }
+
+ protected override RazorTextDocumentIdentifier? GetRazorTextDocumentIdentifier(DocumentHighlightParams request)
+ => request.TextDocument.ToRazorTextDocumentIdentifier();
+
+ protected override Task HandleRequestAsync(DocumentHighlightParams request, RazorCohostRequestContext context, CancellationToken cancellationToken)
+ => HandleRequestAsync(request, context.TextDocument.AssumeNotNull(), cancellationToken);
+
+ private async Task HandleRequestAsync(DocumentHighlightParams request, TextDocument razorDocument, CancellationToken cancellationToken)
+ {
+ var csharpResult = await _remoteServiceInvoker.TryInvokeAsync>(
+ razorDocument.Project.Solution,
+ (service, solutionInfo, cancellationToken) => service.GetHighlightsAsync(solutionInfo, razorDocument.Id, request.Position.ToLinePosition(), 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 (csharpResult.Result is { } highlights)
+ {
+ return highlights.Select(RemoteDocumentHighlight.ToLspDocumentHighlight).ToArray();
+ }
+
+ if (csharpResult.StopHandling)
+ {
+ return null;
+ }
+
+ // 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(
+ htmlDocument.Buffer,
+ Methods.TextDocumentDocumentHighlightName,
+ RazorLSPConstants.HtmlLanguageServerName,
+ request,
+ cancellationToken).ConfigureAwait(false);
+
+ // Since we don't need to map positions in Html, and document highlight results don't have Uri's, we can return these results directly
+ return result?.Response;
+ }
+
+ internal TestAccessor GetTestAccessor() => new(this);
+
+ internal readonly struct TestAccessor(CohostDocumentHighlightEndpoint instance)
+ {
+ public Task HandleRequestAsync(DocumentHighlightParams request, TextDocument razorDocument, CancellationToken cancellationToken)
+ => instance.HandleRequestAsync(request, razorDocument, cancellationToken);
+ }
+}
diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostUriPresentationEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostUriPresentationEndpoint.cs
index ac01c73ecbd..9dc498ad081 100644
--- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostUriPresentationEndpoint.cs
+++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostUriPresentationEndpoint.cs
@@ -9,6 +9,7 @@
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
using Microsoft.CodeAnalysis.Razor.Remote;
using Microsoft.CodeAnalysis.Razor.Workspaces;
+using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServer.ContainedLanguage;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Microsoft.VisualStudio.Razor.LanguageClient.Extensions;
@@ -63,13 +64,13 @@ internal class CohostUriPresentationEndpoint(
private async Task HandleRequestAsync(VSInternalUriPresentationParams request, TextDocument razorDocument, CancellationToken cancellationToken)
{
- var data = await _remoteServiceInvoker.TryInvokeAsync(
+ var data = await _remoteServiceInvoker.TryInvokeAsync>(
razorDocument.Project.Solution,
(service, solutionInfo, cancellationToken) => service.GetPresentationAsync(solutionInfo, razorDocument.Id, request.Range.ToLinePositionSpan(), request.Uris, cancellationToken),
cancellationToken).ConfigureAwait(false);
// If we got a response back, then we're good to go
- if (data.TextChange is { } textChange)
+ if (data.Result is { } textChange)
{
var sourceText = await razorDocument.GetTextAsync(cancellationToken).ConfigureAwait(false);
@@ -90,7 +91,7 @@ internal class CohostUriPresentationEndpoint(
}
// If we didn't get anything from our logic, we might need to go and ask Html, but we also might have determined not to
- if (!data.CallHtml)
+ if (data.StopHandling)
{
return null;
}
diff --git a/src/Razor/test/Microsoft.CodeAnalysis.Remote.Razor.Test/RazorServicesTest.cs b/src/Razor/test/Microsoft.CodeAnalysis.Remote.Razor.Test/RazorServicesTest.cs
new file mode 100644
index 00000000000..40215a782ac
--- /dev/null
+++ b/src/Razor/test/Microsoft.CodeAnalysis.Remote.Razor.Test/RazorServicesTest.cs
@@ -0,0 +1,115 @@
+// 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.IO;
+using System.Linq;
+using System.Xml;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.AspNetCore.Razor.Test.Common;
+using Microsoft.CodeAnalysis.Remote.Razor;
+using Roslyn.Test.Utilities;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.CodeAnalysis.Razor.Remote;
+
+public class RazorServicesTest(ITestOutputHelper testOutputHelper) : ToolingTestBase(testOutputHelper)
+{
+ private readonly static XmlDocument s_servicesFile = LoadServicesFile();
+
+ [Theory]
+ [MemberData(nameof(MessagePackServices))]
+ public void MessagePackServicesAreListedProperly(Type serviceType, Type? callbackType)
+ {
+ VerifyService(serviceType, callbackType);
+ }
+
+ [Theory]
+ [MemberData(nameof(JsonServices))]
+ public void JsonServicesAreListedProperly(Type serviceType, Type? callbackType)
+ {
+ Assert.True(typeof(IRemoteJsonService).IsAssignableFrom(serviceType));
+ VerifyService(serviceType, callbackType);
+ }
+
+ [Fact]
+ public void RazorServicesContainsAllServices()
+ {
+ var services = new HashSet(RazorServices.MessagePackServices.Select(s => s.Item1.Name));
+ services.UnionWith(RazorServices.JsonServices.Select(s => s.Item1.Name));
+ var serviceNodes = s_servicesFile.SelectNodes("/Project/ItemGroup/ServiceHubService");
+ Assert.NotNull(serviceNodes);
+ foreach (XmlNode serviceNode in serviceNodes)
+ {
+ Assert.NotNull(serviceNode);
+ Assert.NotNull(serviceNode.Attributes);
+
+ var serviceEntry = serviceNode.Attributes["Include"]!.Value;
+ var factoryName = serviceNode.Attributes["ClassName"]!.Value;
+
+ var factoryType = typeof(ServiceArgs).Assembly.GetType(factoryName);
+ AssertEx.NotNull(factoryType, $"Could not load type for factory '{factoryName}'");
+
+ var interfaceType = factoryType.BaseType!.GetGenericArguments()[0];
+ Assert.True(services.Contains(interfaceType.Name), $"Service '{interfaceType.Name}' is not listed in RazorServices");
+ }
+ }
+
+ public static IEnumerable