Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move Quick Info content production to the Features layer and add Razor EA co-hosting APIs for Hover and Completion #75226

Merged
merged 28 commits into from
Sep 30, 2024

Conversation

DustinCampbell
Copy link
Member

Razor is moving toward an architecture where its LSP handlers are loaded within the Roslyn LSP and are implemented via services that run in Roslyn OOP within VS Service Hub. Of the handlers that Razor needs to implement, it turns out that 'hover' and 'completion' (and likely, 'documentSymbol' and 'references') present a special challenge. In Roslyn, these handler implementations rely on host-provided services to provide the results for a default LSP client or richer results for the VS LSP client. However, the VS-targeted services are implemented in the EditorFeatures layer, which is never loaded in the Roslyn OOP.

This pull requests decouples these services from their VS dependencies and pushes the service logic into the Features, or into the Roslyn language server itself.

  • A small data model has been added to the Features layer to represent Quick Info content.
  • An abstraction has been added to handle the production of navigation action links for Quick Info content.
  • The relevant GUID/ids for VS images have been copied to the Features layer to avoid any VS dependencies. (Note: these are defined in Microsoft.VisualStudio.Imaging.dll as constants, so VS can never change them without breaking consumers.)
  • Logic for producing Quick Info content has been moved to the Features layer and refactored to reduce allocations.
  • All the "ILspFeatureNameResultCreationService" logic has been moved from the EditorFeatures layer to the Roslyn language server.
  • HoverHandler, CompletionHandler, and CompletionResolveHandler have been refactored to provide static methods called from the Razor EA.

While this change is needed to support Razor co-hosting, it would also be needed if Roslyn ever wanted to move its VS language server out-of-process.

cc @dibarbet and @davidwengier

This is necessary to produce rich VS adornment-based hover tooltips in Roslyn OOP. Currently this logic lives in the EditorFeatures layer.
IntellisenseQuickInfoBuilderContext has four(!) properties that are used to create navigation Actions. I've wrapped this in NavigationActionFactory class.
IntellisenseQuickInfoBuilderContext renamed to QuickInfoContentBuilderContext
EditorFeaturesOnTheFlyDocsElement renamed to QuickInfoOnTheFlyDocsElement
This change is a rewrite of BuildInteractiveTextElements to reduce allocations and prepare it to be moved to the Features layer. In addition, it is no exposed as an extension method on ImmutableArray<TaggedText> named ToInteractiveTextElements(...).
With the logic to produce rich Quick Info content in the Features layer, this service is no longer and its logic can be moved into HoverHandler.
@DustinCampbell DustinCampbell requested a review from a team as a code owner September 24, 2024 23:25
@dotnet-issue-labeler dotnet-issue-labeler bot added Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead labels Sep 24, 2024
@CyrusNajmabadi
Copy link
Member

I can review the tomorrow!

{
try
{
using var token = asyncListener.BeginAsyncOperation(nameof(NavigateToTargetAsync));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for the big blocks of code, can you link to the place it got moved from?

navigationTarget, workspace, documentId, threadingContext, operationExecutor, asyncListener, streamingPresenter.Value).Forget();
}

private static async Task NavigateToTargetAsync(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 This method was moved from

private static async Task NavigateToQuickInfoTargetAsync(
string navigationTarget,
Workspace workspace,
DocumentId documentId,
IThreadingContext threadingContext,
IUIThreadOperationExecutor operationExecutor,
IAsynchronousOperationListener asyncListener,
IStreamingFindUsagesPresenter streamingPresenter)
{
try
{
using var token = asyncListener.BeginAsyncOperation(nameof(NavigateToQuickInfoTargetAsync));
using var context = operationExecutor.BeginExecute(EditorFeaturesResources.IntelliSense, EditorFeaturesResources.Navigating, allowCancellation: true, showProgress: false);
var cancellationToken = context.UserCancellationToken;
var solution = workspace.CurrentSolution;
SymbolKeyResolution resolvedSymbolKey;
try
{
var project = solution.GetRequiredProject(documentId.ProjectId);
var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
resolvedSymbolKey = SymbolKey.ResolveString(navigationTarget, compilation, cancellationToken: cancellationToken);
}
catch
{
// Ignore symbol resolution failures. It likely is just a badly formed URI.
return;
}
if (resolvedSymbolKey.GetAnySymbol() is { } symbol)
{
var location = await GoToDefinitionHelpers.GetDefinitionLocationAsync(
symbol, solution, threadingContext, streamingPresenter, cancellationToken).ConfigureAwait(false);
await location.TryNavigateToAsync(threadingContext, new NavigationOptions(PreferProvisionalTab: true, ActivateTab: true), cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
}
catch (Exception ex) when (FatalError.ReportAndCatch(ex, ErrorSeverity.Critical))
{
}
}
.


namespace Microsoft.CodeAnalysis.QuickInfo.Presentation;

internal static class TaggedTextExtensions
Copy link
Member Author

@DustinCampbell DustinCampbell Sep 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


namespace Microsoft.CodeAnalysis.QuickInfo.Presentation;

internal static class QuickInfoContentBuilder
Copy link
Member Author

@DustinCampbell DustinCampbell Sep 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


namespace Microsoft.CodeAnalysis.QuickInfo.Presentation;

internal sealed class QuickInfoOnTheFlyDocsElement(Document document, OnTheFlyDocsInfo info) : QuickInfoElement
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


namespace Microsoft.CodeAnalysis.QuickInfo.Presentation;

internal sealed class QuickInfoContentBuilderContext(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using Roslyn.Utilities;
using LSP = Roslyn.LanguageServer.Protocol;

namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Completion
{
internal abstract class AbstractLspCompletionResultCreationService : ILspCompletionResultCreationService
internal static class CompletionResultFactory
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per code review feedback, this extension shouldn't be defined in the Features layer. Instead, it can move to LanguageServer.Protocol and be leveraged from the EditorFeatures layer.
Copy link
Contributor

@davidwengier davidwengier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EA bits look great, thanks for the extra work!
Skimmed the rest and the broad strokes made sense, I'll leave Roslyn folks to comment on the Roslyn-isms.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead VSCode
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants