diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index eafe3cd7ad..c3c60c45d9 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -6,9 +6,9 @@
839cdfb0ecca5e0be3dbccd926e7651ef50fdf10
-
+
https://github.com/dotnet/source-build-reference-packages
- ccd0927e3823fb178c7151594f5d2eaba81bba81
+ 136e43e45e20bd58bf86eeabba0a0fa7e1a4b3ae
diff --git a/eng/Versions.props b/eng/Versions.props
index 281e8d7b83..904a29e3ea 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -49,7 +49,7 @@
6.0.2-servicing.22064.6
6.0.1
- 10.0.0-alpha.1.24521.1
+ 10.0.0-alpha.1.24530.1
9.0.0-beta.24516.2
1.0.0-beta.23475.1
1.0.0-beta.23475.1
diff --git a/eng/targets/Services.props b/eng/targets/Services.props
index 63c92b28ef..72a31d7a3f 100644
--- a/eng/targets/Services.props
+++ b/eng/targets/Services.props
@@ -33,6 +33,7 @@
+
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs
index a2178feaad..b82abc2936 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs
@@ -130,7 +130,6 @@ internal partial class RazorLanguageServer : SystemTextJsonLanguageServer();
services.AddSingleton();
+
+ // Hover
+ services.AddHoverServices();
}
// Other
diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/ClientCapabilitiesExtensions.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/ClientCapabilitiesExtensions.cs
new file mode 100644
index 0000000000..e3d51e5a63
--- /dev/null
+++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/ClientCapabilitiesExtensions.cs
@@ -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;
+ }
+}
diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Hover/HoverDisplayOptions.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Hover/HoverDisplayOptions.cs
index a038979a05..6d56f33edd 100644
--- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Hover/HoverDisplayOptions.cs
+++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Hover/HoverDisplayOptions.cs
@@ -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);
}
diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteHoverService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteHoverService.cs
new file mode 100644
index 0000000000..9a47dc792b
--- /dev/null
+++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteHoverService.cs
@@ -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> GetHoverAsync(
+ JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo,
+ JsonSerializableDocumentId documentId,
+ RoslynPosition position,
+ CancellationToken cancellationToken);
+}
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 5fcf2e65f3..d4888b7931 100644
--- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/RazorServices.cs
+++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/RazorServices.cs
@@ -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),
diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Hover/RemoteHoverService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Hover/RemoteHoverService.cs
new file mode 100644
index 0000000000..60a9807605
--- /dev/null
+++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Hover/RemoteHoverService.cs
@@ -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;
+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
+ {
+ protected override IRemoteHoverService CreateService(in ServiceArgs args)
+ => new RemoteHoverService(in args);
+ }
+
+ private readonly IClientCapabilitiesService _clientCapabilitiesService = args.ExportProvider.GetExportedValue();
+
+ protected override IDocumentPositionInfoStrategy DocumentPositionInfoStrategy => PreferAttributeNameDocumentPositionInfoStrategy.Instance;
+
+ public ValueTask> GetHoverAsync(
+ JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo,
+ JsonSerializableDocumentId documentId,
+ Position position,
+ CancellationToken cancellationToken)
+ => RunServiceAsync(
+ solutionInfo,
+ documentId,
+ context => GetHoverAsync(context, position, cancellationToken),
+ cancellationToken);
+
+ private async ValueTask> 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);
+ }
+
+ ///
+ /// Converts a to a .
+ ///
+ ///
+ /// Once Razor moves wholly over to Roslyn.LanguageServer.Protocol, this method can be removed.
+ ///
+ 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(),
+ };
+
+ 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