File: InProcess\EditorInProcess_Hover.cs
Web Access
Project: src\src\Razor\src\Razor\test\Microsoft.VisualStudio.Razor.IntegrationTests\Microsoft.VisualStudio.Razor.IntegrationTests.csproj (Microsoft.VisualStudio.Razor.IntegrationTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Adornments;
 
namespace Microsoft.VisualStudio.Extensibility.Testing;
 
internal partial class EditorInProcess
{
    public async Task<IEnumerable<object>> HoverAsync(int position, CancellationToken cancellationToken)
    {
        var hoverService = await GetComponentModelServiceAsync<IAsyncQuickInfoBroker>(cancellationToken);
 
        var view = await GetActiveTextViewAsync(cancellationToken);
 
        var trackingPoint = view.TextSnapshot.CreateTrackingPoint(position, PointTrackingMode.Positive);
        var quickInfoSession = await hoverService.TriggerQuickInfoAsync(view, trackingPoint, QuickInfoSessionOptions.None, cancellationToken);
 
        using var semaphore = new SemaphoreSlim(1);
        await semaphore.WaitAsync(cancellationToken);
 
        if (quickInfoSession is null)
        {
            throw new InvalidOperationException("Quick Info Session failed to launch.");
        }
 
        quickInfoSession.StateChanged += QuickInfoSession_StateChanged;
 
        if (QuickInfoResolved(quickInfoSession))
        {
            semaphore.Release();
            quickInfoSession.StateChanged -= QuickInfoSession_StateChanged;
        }
 
        try
        {
            await semaphore.WaitAsync(cancellationToken);
        }
        finally
        {
            quickInfoSession.StateChanged -= QuickInfoSession_StateChanged;
        }
 
        return quickInfoSession.Content;
 
        static bool QuickInfoResolved(IAsyncQuickInfoSession quickInfoSession)
        {
            return quickInfoSession.State == QuickInfoSessionState.Visible;
        }
 
        void QuickInfoSession_StateChanged(object sender, QuickInfoSessionStateChangedEventArgs e)
        {
            if (e.NewState == QuickInfoSessionState.Visible)
            {
                semaphore.Release();
            }
        }
    }
 
    public async Task<string> GetHoverStringAsync(int position, CancellationToken cancellationToken)
    {
        var hoverContent = await HoverAsync(position, cancellationToken);
 
        using var _ = StringBuilderPool.GetPooledObject(out var sb);
 
        TraverseContent(hoverContent, sb);
        return sb.ToString();
 
        static void TraverseContent(IEnumerable<object> objects, StringBuilder stringBuilder)
        {
            foreach (var obj in objects)
            {
                switch (obj)
                {
                    case ImageElement imageElement:
                        break;
                    case ClassifiedTextElement textElement:
                        foreach (var run in textElement.Runs)
                        {
                            stringBuilder.Append(run.Text);
                        }
 
                        break;
                    case ContainerElement containerElement:
                        TraverseContent(containerElement.Elements, stringBuilder);
                        break;
                    default:
                        throw new NotImplementedException("Unknown QuickInfo element encountered");
                }
            }
        }
    }
}