File: Venus\VenusCommandFilter.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_pxr0p0dn_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices)
// 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.
 
#nullable disable
 
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.LanguageServices.Implementation.Extensions;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.TextManager.Interop;
using Roslyn.Utilities;
 
namespace Microsoft.VisualStudio.LanguageServices.Implementation.Venus;
 
internal class VenusCommandFilter : AbstractVsTextViewFilter
{
    private readonly ITextBuffer _subjectBuffer;
 
    public VenusCommandFilter(
        IWpfTextView wpfTextView,
        ITextBuffer subjectBuffer,
        IOleCommandTarget nextCommandTarget,
        IComponentModel componentModel)
        : base(wpfTextView, componentModel)
    {
        Contract.ThrowIfNull(wpfTextView);
        Contract.ThrowIfNull(subjectBuffer);
        Contract.ThrowIfNull(nextCommandTarget);
 
        _subjectBuffer = subjectBuffer;
 
        // Chain in editor command handler service. It will execute all our command handlers migrated to the modern editor commanding.
        var vsCommandHandlerServiceAdapterFactory = componentModel.GetService<IVsCommandHandlerServiceAdapterFactory>();
        var vsCommandHandlerServiceAdapter = vsCommandHandlerServiceAdapterFactory.Create(wpfTextView, _subjectBuffer, nextCommandTarget);
        NextCommandTarget = vsCommandHandlerServiceAdapter;
    }
 
    protected override ITextBuffer GetSubjectBufferContainingCaret()
        => _subjectBuffer;
 
    protected override async Task<(string pbstrText, int result)> GetDataTipTextImplAsync(TextSpan[] pSpan)
    {
        var textViewModel = WpfTextView.TextViewModel;
        if (textViewModel == null)
        {
            Debug.Assert(WpfTextView.IsClosed);
            return (null, VSConstants.E_FAIL);
        }
 
        // We need to map the TextSpan from the DataBuffer to our subject buffer.
        var span = textViewModel.DataBuffer.CurrentSnapshot.GetSpan(pSpan[0]);
        var subjectSpans = WpfTextView.BufferGraph.MapDownToBuffer(span, SpanTrackingMode.EdgeInclusive, _subjectBuffer);
 
        // The following loop addresses the case where the position is on a seam and maps to multiple source spans.
        // In these cases, we assume it's okay to return the first span that successfully returns a DataTip.
        // It's most likely that either only one will succeed or both with fail.
        var expectedSpanLength = span.Length;
        foreach (var candidateSpan in subjectSpans)
        {
            // First, we'll only consider spans whose length matches our input span. 
            if (candidateSpan.Length != expectedSpanLength)
            {
                continue;
            }
 
            // Next, we'll check to see if there is actually a DataTip for this candidate.
            // If there is, we'll map this span back to the DataBuffer and return it.
            var subjectBufferSpanData = new TextSpan[] { candidateSpan.ToVsTextSpan() };
            var (pbstrText, hr) = await GetDataTipTextImplAsync(_subjectBuffer, subjectBufferSpanData).ConfigureAwait(true);
            if (ErrorHandler.Succeeded(hr))
            {
                var subjectSpan = _subjectBuffer.CurrentSnapshot.GetSpan(subjectBufferSpanData[0]);
 
                // When mapping back up to the surface buffer, if we get more than one span,
                // take the span that intersects with the input span, since that's probably
                // the one we care about.
                // If there are no such spans, just return.
                var surfaceSpan = WpfTextView.BufferGraph.MapUpToBuffer(subjectSpan, SpanTrackingMode.EdgeInclusive, textViewModel.DataBuffer)
                    .SingleOrDefault(x => x.IntersectsWith(span));
 
                if (surfaceSpan == default)
                    return (null, VSConstants.E_FAIL);
 
                // pSpan is an in/out parameter
                pSpan[0] = surfaceSpan.ToVsTextSpan();
                return (pbstrText, hr);
            }
        }
 
        return (null, VSConstants.E_FAIL);
    }
}