Skip to content

Commit

Permalink
Expose inlay hints to Razor cohosting
Browse files Browse the repository at this point in the history
  • Loading branch information
davidwengier committed Jul 25, 2024
1 parent 4996324 commit 57ba04b
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 12 deletions.
18 changes: 12 additions & 6 deletions src/LanguageServer/Protocol/Handler/InlayHint/InlayHintHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,24 @@ public InlayHintHandler(IGlobalOptionService optionsService)
public TextDocumentIdentifier GetTextDocumentIdentifier(InlayHintParams request)
=> request.TextDocument;

public async Task<LSP.InlayHint[]?> HandleRequestAsync(InlayHintParams request, RequestContext context, CancellationToken cancellationToken)
public Task<LSP.InlayHint[]?> HandleRequestAsync(InlayHintParams request, RequestContext context, CancellationToken cancellationToken)
{
var document = context.GetRequiredDocument();
var inlayHintCache = context.GetRequiredLspService<InlayHintCache>();
var options = _optionsService.GetInlineHintsOptions(document.Project.Language);

return GetInlayHintsAsync(document, request.TextDocument, request.Range, options, displayAllOverride: false, inlayHintCache, cancellationToken);
}

internal static async Task<LSP.InlayHint[]?> GetInlayHintsAsync(Document document, TextDocumentIdentifier textDocumentIdentifier, LSP.Range range, InlineHintsOptions options, bool displayAllOverride, InlayHintCache inlayHintCache, CancellationToken cancellationToken)
{
var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false);
var textSpan = ProtocolConversions.RangeToTextSpan(request.Range, text);
var textSpan = ProtocolConversions.RangeToTextSpan(range, text);

var inlineHintService = document.GetRequiredLanguageService<IInlineHintsService>();
var options = _optionsService.GetInlineHintsOptions(document.Project.Language);
var hints = await inlineHintService.GetInlineHintsAsync(document, textSpan, options, displayAllOverride: false, cancellationToken).ConfigureAwait(false);
var hints = await inlineHintService.GetInlineHintsAsync(document, textSpan, options, displayAllOverride, cancellationToken).ConfigureAwait(false);

var syntaxVersion = await document.GetSyntaxVersionAsync(cancellationToken).ConfigureAwait(false);
var inlayHintCache = context.GetRequiredLspService<InlayHintCache>();

// Store the members in the resolve cache so that when we get a resolve request for a particular
// member we can re-use the inline hint.
Expand Down Expand Up @@ -83,7 +89,7 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(InlayHintParams request)
ToolTip = null,
PaddingLeft = leftPadding,
PaddingRight = rightPadding,
Data = new InlayHintResolveData(resultId, i, request.TextDocument)
Data = new InlayHintResolveData(resultId, i, textDocumentIdentifier)
};

inlayHints[i] = inlayHint;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.InlineHints;
using Roslyn.LanguageServer.Protocol;
using Roslyn.Utilities;
using StreamJsonRpc;
using LSP = Roslyn.LanguageServer.Protocol;
using System.Text.Json;

namespace Microsoft.CodeAnalysis.LanguageServer.Handler.InlayHint
{
Expand All @@ -30,11 +30,16 @@ public InlayHintResolveHandler(InlayHintCache inlayHintCache)
public TextDocumentIdentifier GetTextDocumentIdentifier(LSP.InlayHint request)
=> GetInlayHintResolveData(request).TextDocument;

public async Task<LSP.InlayHint> HandleRequestAsync(LSP.InlayHint request, RequestContext context, CancellationToken cancellationToken)
public Task<LSP.InlayHint> HandleRequestAsync(LSP.InlayHint request, RequestContext context, CancellationToken cancellationToken)
{
var document = context.GetRequiredDocument();
return ResolveInlayHintAsync(document, request, _inlayHintCache, cancellationToken);
}

internal static async Task<LSP.InlayHint> ResolveInlayHintAsync(Document document, LSP.InlayHint request, InlayHintCache inlayHintCache, CancellationToken cancellationToken)
{
var resolveData = GetInlayHintResolveData(request);
var (cacheEntry, inlineHintToResolve) = GetCacheEntry(resolveData);
var (cacheEntry, inlineHintToResolve) = GetCacheEntry(resolveData, inlayHintCache);

var currentSyntaxVersion = await document.GetSyntaxVersionAsync(cancellationToken).ConfigureAwait(false);
var cachedSyntaxVersion = cacheEntry.SyntaxVersion;
Expand All @@ -53,9 +58,9 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(LSP.InlayHint request)
return request;
}

private (InlayHintCache.InlayHintCacheEntry CacheEntry, InlineHint InlineHintToResolve) GetCacheEntry(InlayHintResolveData resolveData)
private static (InlayHintCache.InlayHintCacheEntry CacheEntry, InlineHint InlineHintToResolve) GetCacheEntry(InlayHintResolveData resolveData, InlayHintCache inlayHintCache)
{
var cacheEntry = _inlayHintCache.GetCachedEntry(resolveData.ResultId);
var cacheEntry = inlayHintCache.GetCachedEntry(resolveData.ResultId);
Contract.ThrowIfNull(cacheEntry, "Missing cache entry for inlay hint resolve request");
return (cacheEntry, cacheEntry.InlayHintMembers[resolveData.ListIndex]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,12 @@
<!-- Full IVT is through ExternalAccess for functionality -->
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.ExternalAccess.Razor" />
<!-- Restricted IVT is direct for protocol types only -->
<RestrictedInternalsVisibleTo Include="Microsoft.CodeAnalysis.Remote.Razor" Namespace="Roslyn.LanguageServer.Protocol" Partner="Razor" Key="$(RazorKey)" />
<RestrictedInternalsVisibleTo Include="Microsoft.CodeAnalysis.Razor.Workspaces" Namespace="Roslyn.LanguageServer.Protocol" Partner="Razor" Key="$(RazorKey)" />
<RestrictedInternalsVisibleTo Include="Microsoft.CodeAnalysis.Remote.Razor" Namespace="Roslyn.LanguageServer.Protocol" Partner="Razor" Key="$(RazorKey)" />
<RestrictedInternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServices.Razor" Namespace="Roslyn.LanguageServer.Protocol" Partner="Razor" Key="$(RazorKey)" />
<!-- Restricted IVT for protocol types for Razor tests -->
<RestrictedInternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServices.Razor.Test" Namespace="Roslyn.LanguageServer.Protocol" Partner="Razor" Key="$(RazorKey)" />
<!-- Full IVT is through ExternalAccess for functionality -->
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.ExternalAccess.Xaml" />
<!-- Restricted IVT is direct for protocol types only -->
<RestrictedInternalsVisibleTo Include="Microsoft.VisualStudio.DesignTools.CodeAnalysis" Namespace="Roslyn.LanguageServer.Protocol" Partner="Xaml" Key="$(VisualStudioKey)" />
Expand Down
49 changes: 49 additions & 0 deletions src/Tools/ExternalAccess/Razor/Cohost/Handlers/InlayHints.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.InlineHints;
using Microsoft.CodeAnalysis.LanguageServer.Handler.InlayHint;
using Roslyn.LanguageServer.Protocol;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers
{
internal static class InlayHints
{
// In the Roslyn LSP server this cache has the same lifetime as the LSP server. For Razor, running OOP, we don't have
// that same lifetime anywhere, everything is just static. This is likely not ideal, but the inlay hint cache has a
// max size of 3 items, so it's not a huge deal.
private static InlayHintCache? s_resolveCache;

public static Task<InlayHint[]?> GetInlayHintsAsync(Document document, TextDocumentIdentifier textDocumentIdentifier, Range range, bool displayAllOverride, CancellationToken cancellationToken)
{
s_resolveCache ??= new();

// Currently Roslyn options don't sync to OOP so trying to get the real options out of IGlobalOptionsService will
// always just result in the defaults, which for inline hints are to not show anything. However, the editor has a
// setting for LSP inlay hints, so we can assume that if we get a request from the client, the user wants hints.
// When overriding however, Roslyn does a nicer job if type hints are off.
var options = InlineHintsOptions.Default;
if (!displayAllOverride)
{
options = options with
{
TypeOptions = options.TypeOptions with { EnabledForTypes = true },
ParameterOptions = options.ParameterOptions with { EnabledForParameters = true },
};
}

return InlayHintHandler.GetInlayHintsAsync(document, textDocumentIdentifier, range, options, displayAllOverride, s_resolveCache, cancellationToken);
}

public static Task<InlayHint> ResolveInlayHintAsync(Document document, InlayHint request, CancellationToken cancellationToken)
{
Contract.ThrowIfNull(s_resolveCache, "Cache should never be null for resolve, since it should have been created by the original request");

return InlayHintResolveHandler.ResolveInlayHintAsync(document, request, s_resolveCache, cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.Razor.Workspaces" Key="$(RazorKey)" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.Razor.Workspaces.Test" Key="$(RazorKey)" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.Remote.Razor" Key="$(RazorKey)" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.Remote.Razor.Test" Key="$(RazorKey)" />
<InternalsVisibleTo Include="Microsoft.VisualStudio.Editor.Razor" Key="$(RazorKey)" />
<InternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServerClient.Razor" Key="$(RazorKey)" />
<InternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServerClient.Razor.Test" Key="$(RazorKey)" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using Microsoft.CodeAnalysis.Remote;

namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Api
{
internal static class TestHelpers
{
/// <summary>
/// Allows tests to create a new remote workspace, rather than use RemoteWorkspaceManager.Default, so things can be isolated
/// </summary>
public static Workspace CreateTestWorkspace()
=> new RemoteWorkspaceManager(workspace => new SolutionAssetCache(workspace, cleanupInterval: TimeSpan.FromSeconds(30), purgeAfter: TimeSpan.FromMinutes(1))).GetWorkspace();
}
}

0 comments on commit 57ba04b

Please sign in to comment.