Skip to content

Commit

Permalink
Merge pull request #75226 from DustinCampbell/razor-cohosting-hover
Browse files Browse the repository at this point in the history
Move Quick Info content production to the Features layer and add Razor EA co-hosting APIs for Hover and Completion
  • Loading branch information
DustinCampbell authored Sep 30, 2024
2 parents 5bf36f9 + fc5687b commit 7c66b29
Show file tree
Hide file tree
Showing 66 changed files with 2,057 additions and 1,648 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.QuickInfo;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Editor.UnitTests.QuickInfo;
Expand Down Expand Up @@ -551,14 +552,19 @@ protected override async Task AssertContentIsAsync(
Assert.NotEqual(0, info.RelatedSpans.Length);

var trackingSpan = new Mock<ITrackingSpan>(MockBehavior.Strict);
var threadingContext = workspace.ExportProvider.GetExportedValue<IThreadingContext>();
var operationExecutor = workspace.ExportProvider.GetExportedValue<IUIThreadOperationExecutor>();
var streamingPresenter = workspace.ExportProvider.GetExport<IStreamingFindUsagesPresenter>();

var navigationActionFactory = new NavigationActionFactory(
document,
threadingContext: workspace.ExportProvider.GetExportedValue<IThreadingContext>(),
operationExecutor: workspace.ExportProvider.GetExportedValue<IUIThreadOperationExecutor>(),
AsynchronousOperationListenerProvider.NullListener,
streamingPresenter: workspace.ExportProvider.GetExport<IStreamingFindUsagesPresenter>());

var quickInfoItem = await IntellisenseQuickInfoBuilder.BuildItemAsync(
trackingSpan.Object, info, document,
ClassificationOptions.Default, LineFormattingOptions.Default, threadingContext, operationExecutor,
AsynchronousOperationListenerProvider.NullListener,
streamingPresenter, CancellationToken.None);
ClassificationOptions.Default, LineFormattingOptions.Default,
navigationActionFactory, CancellationToken.None);

var containerElement = quickInfoItem.Item as ContainerElement;

var textElements = containerElement.Elements.OfType<ClassifiedTextElement>();
Expand Down
44 changes: 18 additions & 26 deletions src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// 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 System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
Expand All @@ -13,12 +11,9 @@
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.InlineHints;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
Expand Down Expand Up @@ -97,30 +92,27 @@ public static InlineHintsTag Create(
textView, span, hint, taggerProvider);
}

public async Task<IReadOnlyCollection<object>> CreateDescriptionAsync(CancellationToken cancellationToken)
public async Task<ImmutableArray<object>> CreateDescriptionAsync(CancellationToken cancellationToken)
{
var document = _span.Snapshot.GetOpenDocumentInCurrentContextWithChanges();
if (document != null)
if (_span.Snapshot.GetOpenDocumentInCurrentContextWithChanges() is not Document document)
{
var taggedText = await _hint.GetDescriptionAsync(document, cancellationToken).ConfigureAwait(false);
if (!taggedText.IsDefaultOrEmpty)
{
var classificationOptions = _taggerProvider.EditorOptionsService.GlobalOptions.GetClassificationOptions(document.Project.Language);
var lineFormattingOptions = _span.Snapshot.TextBuffer.GetLineFormattingOptions(_taggerProvider.EditorOptionsService, explicitFormat: false);

var context = new IntellisenseQuickInfoBuilderContext(
document,
classificationOptions,
lineFormattingOptions,
_taggerProvider.ThreadingContext,
_taggerProvider.OperationExecutor,
_taggerProvider.AsynchronousOperationListener,
_taggerProvider.StreamingFindUsagesPresenter);
return Implementation.IntelliSense.Helpers.BuildInteractiveTextElements(taggedText, context);
}
return [];
}

var taggedText = await _hint.GetDescriptionAsync(document, cancellationToken).ConfigureAwait(false);
if (taggedText.IsDefaultOrEmpty)
{
return [];
}

return Array.Empty<object>();
var navigationActionFactory = new NavigationActionFactory(
document,
_taggerProvider.ThreadingContext,
_taggerProvider.OperationExecutor,
_taggerProvider.AsynchronousOperationListener,
_taggerProvider.StreamingFindUsagesPresenter);

return taggedText.ToInteractiveVsTextAdornments(navigationActionFactory);
}

private static FrameworkElement CreateElement(
Expand Down
18 changes: 9 additions & 9 deletions src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
using System.Windows.Controls;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Copilot;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.QuickInfo;
using Microsoft.CodeAnalysis.QuickInfo.Presentation;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.Language.Intellisense;
Expand All @@ -37,7 +37,7 @@ internal sealed partial class OnTheFlyDocsView : UserControl, INotifyPropertyCha
private readonly IAsyncQuickInfoSession _asyncQuickInfoSession;
private readonly IThreadingContext _threadingContext;
private readonly Document _document;
private readonly OnTheFlyDocsElement _onTheFlyDocsElement;
private readonly OnTheFlyDocsInfo _onTheFlyDocsInfo;
private readonly ContentControl _responseControl = new();
private readonly CancellationTokenSource _cancellationTokenSource = new();

Expand All @@ -58,15 +58,15 @@ internal sealed partial class OnTheFlyDocsView : UserControl, INotifyPropertyCha
public string OnTheFlyDocumentation => EditorFeaturesResources.On_the_fly_documentation;
#pragma warning restore CA1822 // Mark members as static

public OnTheFlyDocsView(ITextView textView, IViewElementFactoryService viewElementFactoryService, IAsynchronousOperationListenerProvider listenerProvider, IAsyncQuickInfoSession asyncQuickInfoSession, IThreadingContext threadingContext, EditorFeaturesOnTheFlyDocsElement editorFeaturesOnTheFlyDocsElement)
public OnTheFlyDocsView(ITextView textView, IViewElementFactoryService viewElementFactoryService, IAsynchronousOperationListenerProvider listenerProvider, IAsyncQuickInfoSession asyncQuickInfoSession, IThreadingContext threadingContext, QuickInfoOnTheFlyDocsElement onTheFlyDocsElement)
{
_textView = textView;
_viewElementFactoryService = viewElementFactoryService;
_asyncListener = listenerProvider.GetListener(FeatureAttribute.OnTheFlyDocs);
_asyncQuickInfoSession = asyncQuickInfoSession;
_threadingContext = threadingContext;
_onTheFlyDocsElement = editorFeaturesOnTheFlyDocsElement.OnTheFlyDocsElement;
_document = editorFeaturesOnTheFlyDocsElement.Document;
_onTheFlyDocsInfo = onTheFlyDocsElement.Info;
_document = onTheFlyDocsElement.Document;

var sparkle = new ImageElement(new VisualStudio.Core.Imaging.ImageId(CopilotConstants.CopilotIconMonikerGuid, CopilotConstants.CopilotIconSparkleId));

Expand Down Expand Up @@ -137,7 +137,7 @@ private async Task SetResultTextAsync(ICopilotCodeAnalysisService copilotService

try
{
var response = await copilotService.GetOnTheFlyDocsAsync(_onTheFlyDocsElement.SymbolSignature, _onTheFlyDocsElement.DeclarationCode, _onTheFlyDocsElement.Language, cancellationToken).ConfigureAwait(false);
var response = await copilotService.GetOnTheFlyDocsAsync(_onTheFlyDocsInfo.SymbolSignature, _onTheFlyDocsInfo.DeclarationCode, _onTheFlyDocsInfo.Language, cancellationToken).ConfigureAwait(false);
var copilotRequestTime = stopwatch.Elapsed;

await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
Expand Down Expand Up @@ -202,12 +202,12 @@ public void RequestResults()
CurrentState = OnTheFlyDocsState.Loading;
Logger.Log(FunctionId.Copilot_On_The_Fly_Docs_Loading_State_Entered, KeyValueLogMessage.Create(m =>
{
m["SymbolHeaderText"] = _onTheFlyDocsElement.SymbolSignature;
m["HasDocumentationComments"] = _onTheFlyDocsElement.HasComments;
m["SymbolHeaderText"] = _onTheFlyDocsInfo.SymbolSignature;
m["HasDocumentationComments"] = _onTheFlyDocsInfo.HasComments;
}, LogLevel.Information));

OnTheFlyDocsLogger.LogOnTheFlyDocsResultsRequested();
if (_onTheFlyDocsElement.HasComments)
if (_onTheFlyDocsInfo.HasComments)
{
OnTheFlyDocsLogger.LogOnTheFlyDocsResultsRequestedWithDocComments();
}
Expand Down
12 changes: 6 additions & 6 deletions src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsViewFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
using System;
using System.ComponentModel.Composition;
using System.Windows;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo;
using Microsoft.CodeAnalysis.Editor.QuickInfo;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.QuickInfo.Presentation;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Text.Adornments;
Expand All @@ -20,7 +20,7 @@ namespace Microsoft.CodeAnalysis.QuickInfo;

[Export(typeof(IViewElementFactory))]
[Name("OnTheFlyDocsElement converter")]
[TypeConversion(from: typeof(EditorFeaturesOnTheFlyDocsElement), to: typeof(UIElement))]
[TypeConversion(from: typeof(QuickInfoOnTheFlyDocsElement), to: typeof(UIElement))]
[Order(Before = "Default")]
internal sealed class OnTheFlyDocsViewFactory : IViewElementFactory
{
Expand All @@ -46,11 +46,11 @@ public OnTheFlyDocsViewFactory(IViewElementFactoryService factoryService, IAsync
throw new InvalidOperationException("TView must be UIElement");
}

var editorFeaturesOnTheFlyDocsElement = (EditorFeaturesOnTheFlyDocsElement)model;
var onTheFlyDocsElement = (QuickInfoOnTheFlyDocsElement)model;

Logger.Log(FunctionId.Copilot_On_The_Fly_Docs_Showed_Link, KeyValueLogMessage.Create(m =>
{
m["SymbolHeaderText"] = editorFeaturesOnTheFlyDocsElement.OnTheFlyDocsElement.SymbolSignature;
m["SymbolHeaderText"] = onTheFlyDocsElement.Info.SymbolSignature;
}, LogLevel.Information));

var quickInfoSession = _asyncQuickInfoBroker.GetSession(textView);
Expand All @@ -62,11 +62,11 @@ public OnTheFlyDocsViewFactory(IViewElementFactoryService factoryService, IAsync

OnTheFlyDocsLogger.LogShowedOnTheFlyDocsLink();

if (editorFeaturesOnTheFlyDocsElement.OnTheFlyDocsElement.HasComments)
if (onTheFlyDocsElement.Info.HasComments)
{
OnTheFlyDocsLogger.LogShowedOnTheFlyDocsLinkWithDocComments();
}

return new OnTheFlyDocsView(textView, _factoryService, _listenerProvider, quickInfoSession, _threadingContext, editorFeaturesOnTheFlyDocsElement) as TView;
return new OnTheFlyDocsView(textView, _factoryService, _listenerProvider, quickInfoSession, _threadingContext, onTheFlyDocsElement) as TView;
}
}
11 changes: 6 additions & 5 deletions src/EditorFeatures/Core/Extensions/LSPExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ public static Roslyn.Text.Adornments.ContainerElement ToLSPElement(this VisualSt
private static object? ToLSPElement(object? value)
=> value switch
{
VisualStudio.Core.Imaging.ImageId imageId => ToLSPImageId(imageId),
VisualStudio.Text.Adornments.ImageElement element => ToLSPImageElement(element),
VisualStudio.Text.Adornments.ContainerElement element => ToLSPElement(element),
VisualStudio.Text.Adornments.ClassifiedTextElement element => ToLSPElement(element),
VisualStudio.Text.Adornments.ClassifiedTextRun run => ToLSPRun(run),
VisualStudio.Core.Imaging.ImageId imageId => imageId.ToLSPImageId(),
VisualStudio.Text.Adornments.ImageElement element => element.ToLSPImageElement(),
VisualStudio.Text.Adornments.ContainerElement element => element.ToLSPElement(),
VisualStudio.Text.Adornments.ClassifiedTextElement element => element.ToLSPElement(),
VisualStudio.Text.Adornments.ClassifiedTextRun run => run.ToLSPRun(),

_ => value,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Completion.Providers;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Options;
Expand Down Expand Up @@ -480,11 +478,9 @@ private static void UpdateSessionData(IAsyncCompletionSession session, Completio
if (description == null)
return null;

var lineFormattingOptions = snapshot.TextBuffer.GetLineFormattingOptions(_editorOptionsService, explicitFormat: false);
var context = new IntellisenseQuickInfoBuilderContext(
document, displayOptions.ClassificationOptions, lineFormattingOptions, _threadingContext, _operationExecutor, _asyncListener, _streamingPresenter);
var navigationActionFactory = new NavigationActionFactory(document, _threadingContext, _operationExecutor, _asyncListener, _streamingPresenter);

var elements = IntelliSense.Helpers.BuildInteractiveTextElements(description.TaggedParts, context).ToArray();
var elements = description.TaggedParts.ToInteractiveVsTextAdornments(navigationActionFactory);
if (elements.Length == 0)
return new ClassifiedTextElement();

Expand Down
Loading

0 comments on commit 7c66b29

Please sign in to comment.