File: Implementation\AbstractVsTextViewFilter.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_4volmoxl_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;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.BraceMatching;
using Microsoft.CodeAnalysis.Debugging;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.LanguageServices.Implementation.Extensions;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Utilities;
using Roslyn.Utilities;
using TextSpan = Microsoft.VisualStudio.TextManager.Interop.TextSpan;
 
namespace Microsoft.VisualStudio.LanguageServices.Implementation;
 
internal abstract class AbstractVsTextViewFilter : AbstractOleCommandTarget, IVsTextViewFilter
{
    public AbstractVsTextViewFilter(
        IWpfTextView wpfTextView,
        IComponentModel componentModel)
        : base(wpfTextView, componentModel)
    {
    }
 
    int IVsTextViewFilter.GetDataTipText(TextSpan[] pSpan, out string pbstrText)
    {
        try
        {
            if (pSpan == null || pSpan.Length != 1)
            {
                pbstrText = null;
                return VSConstants.E_INVALIDARG;
            }
 
            return GetDataTipTextImpl(pSpan, out pbstrText);
        }
        catch (Exception e) when (FatalError.ReportAndCatch(e) && false)
        {
            throw ExceptionUtilities.Unreachable();
        }
    }
 
    protected virtual int GetDataTipTextImpl(TextSpan[] pSpan, out string pbstrText)
    {
        var subjectBuffer = WpfTextView.GetBufferContainingCaret();
        if (subjectBuffer == null)
        {
            pbstrText = null;
            return VSConstants.E_FAIL;
        }
 
        return GetDataTipTextImpl(subjectBuffer, pSpan, out pbstrText);
    }
 
    protected int GetDataTipTextImpl(ITextBuffer subjectBuffer, TextSpan[] pSpan, out string pbstrText)
    {
        pbstrText = null;
 
        var vsBuffer = EditorAdaptersFactory.GetBufferAdapter(subjectBuffer);
 
        // TODO: broken in REPL
        if (vsBuffer == null)
        {
            return VSConstants.E_FAIL;
        }
 
        using (Logger.LogBlock(FunctionId.Debugging_VsLanguageDebugInfo_GetDataTipText, CancellationToken.None))
        {
            pbstrText = null;
            if (pSpan == null || pSpan.Length != 1)
            {
                return VSConstants.E_INVALIDARG;
            }
 
            var result = VSConstants.E_FAIL;
            string pbstrTextInternal = null;
 
            var uiThreadOperationExecutor = ComponentModel.GetService<IUIThreadOperationExecutor>();
            uiThreadOperationExecutor.Execute(
                title: ServicesVSResources.Debugger,
                defaultDescription: ServicesVSResources.Getting_DataTip_text,
                allowCancellation: true,
                showProgress: false,
                action: context =>
            {
                IServiceProvider serviceProvider = ComponentModel.GetService<SVsServiceProvider>();
                var debugger = (IVsDebugger)serviceProvider.GetService(typeof(SVsShellDebugger));
                var debugMode = new DBGMODE[1];
 
                var cancellationToken = context.UserCancellationToken;
                if (ErrorHandler.Succeeded(debugger.GetMode(debugMode)) && debugMode[0] != DBGMODE.DBGMODE_Design)
                {
                    var textSpan = pSpan[0];
 
                    var textSnapshot = subjectBuffer.CurrentSnapshot;
                    var document = textSnapshot.GetOpenDocumentInCurrentContextWithChanges();
 
                    if (document != null)
                    {
                        var languageDebugInfo = document.Project.Services.GetService<ILanguageDebugInfoService>();
                        if (languageDebugInfo != null)
                        {
                            var spanOpt = textSnapshot.TryGetSpan(textSpan);
                            if (spanOpt.HasValue)
                            {
                                var dataTipInfo = languageDebugInfo.GetDataTipInfoAsync(document, spanOpt.Value.Start, cancellationToken).WaitAndGetResult(cancellationToken);
                                if (!dataTipInfo.IsDefault)
                                {
                                    var resultSpan = dataTipInfo.Span.ToSnapshotSpan(textSnapshot);
                                    var textOpt = dataTipInfo.Text;
 
                                    pSpan[0] = resultSpan.ToVsTextSpan();
                                    result = debugger.GetDataTipValue((IVsTextLines)vsBuffer, pSpan, textOpt, out pbstrTextInternal);
                                }
                            }
                        }
                    }
                }
            });
 
            pbstrText = pbstrTextInternal;
            return result;
        }
    }
 
    int IVsTextViewFilter.GetPairExtents(int iLine, int iIndex, TextSpan[] pSpan)
    {
        try
        {
            var result = VSConstants.S_OK;
            ComponentModel.GetService<IUIThreadOperationExecutor>().Execute(
                "Intellisense",
                defaultDescription: "",
                allowCancellation: true,
                showProgress: false,
                action: c => result = GetPairExtentsWorker(iLine, iIndex, pSpan, c.UserCancellationToken));
 
            return result;
        }
        catch (Exception e) when (FatalError.ReportAndCatch(e) && false)
        {
            throw ExceptionUtilities.Unreachable();
        }
    }
 
    private int GetPairExtentsWorker(int iLine, int iIndex, TextSpan[] pSpan, CancellationToken cancellationToken)
    {
        var braceMatcher = ComponentModel.GetService<IBraceMatchingService>();
        var globalOptions = ComponentModel.GetService<IGlobalOptionService>();
        return GetPairExtentsWorker(
            WpfTextView,
            braceMatcher,
            globalOptions,
            iLine,
            iIndex,
            pSpan,
            (VSConstants.VSStd2KCmdID)this.CurrentlyExecutingCommand == VSConstants.VSStd2KCmdID.GOTOBRACE_EXT,
            cancellationToken);
    }
 
    // Internal for testing purposes
    internal static int GetPairExtentsWorker(ITextView textView, IBraceMatchingService braceMatcher, IGlobalOptionService globalOptions, int iLine, int iIndex, TextSpan[] pSpan, bool extendSelection, CancellationToken cancellationToken)
    {
        pSpan[0].iStartLine = pSpan[0].iEndLine = iLine;
        pSpan[0].iStartIndex = pSpan[0].iEndIndex = iIndex;
 
        var pointInViewBuffer = textView.TextSnapshot.GetLineFromLineNumber(iLine).Start + iIndex;
 
        var subjectBuffer = textView.GetBufferContainingCaret();
        if (subjectBuffer != null)
        {
            // PointTrackingMode and PositionAffinity chosen arbitrarily.
            var positionInSubjectBuffer = textView.BufferGraph.MapDownToBuffer(pointInViewBuffer, PointTrackingMode.Positive, subjectBuffer, PositionAffinity.Successor);
            if (!positionInSubjectBuffer.HasValue)
            {
                positionInSubjectBuffer = textView.BufferGraph.MapDownToBuffer(pointInViewBuffer, PointTrackingMode.Positive, subjectBuffer, PositionAffinity.Predecessor);
            }
 
            if (positionInSubjectBuffer.HasValue)
            {
                var position = positionInSubjectBuffer.Value;
                var document = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
                if (document != null)
                {
                    var options = globalOptions.GetBraceMatchingOptions(document.Project.Language);
                    var matchingSpan = braceMatcher.FindMatchingSpanAsync(document, position, options, cancellationToken).WaitAndGetResult(cancellationToken);
 
                    if (matchingSpan.HasValue)
                    {
                        var resultsInView = textView.GetSpanInView(matchingSpan.Value.ToSnapshotSpan(subjectBuffer.CurrentSnapshot)).ToList();
                        if (resultsInView.Count == 1)
                        {
                            var vsTextSpan = resultsInView[0].ToVsTextSpan();
 
                            // caret is at close parenthesis
                            if (matchingSpan.Value.Start < position)
                            {
                                pSpan[0].iStartLine = vsTextSpan.iStartLine;
                                pSpan[0].iStartIndex = vsTextSpan.iStartIndex;
 
                                // For text selection using goto matching brace, tweak spans to suit the VS editor's behavior.
                                // The vs editor sets selection for GotoBraceExt (Ctrl + Shift + ]) like so :
                                // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                                // if (fExtendSelection)
                                // {
                                //      textSpan.iEndIndex++;
                                //      this.SetSelection(textSpan.iStartLine, textSpan.iStartIndex, textSpan.iEndLine, textSpan.iEndIndex);
                                // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                                // Notice a couple of things: it arbitrarily increments EndIndex by 1 and does nothing similar for StartIndex.
                                // So, if we're extending selection: 
                                //    case a: set EndIndex to left of closing parenthesis -- ^}
                                //            this adjustment is for any of the four cases where caret could be. left or right of open or close parenthesis -- ^{^ ^}^
                                //    case b: set StartIndex to left of opening parenthesis -- ^{
                                //            this adjustment is for cases where caret was originally to the right of the open parenthesis -- {^ }
 
                                // if selecting, adjust end position by using the matching opening span that we just computed.
                                if (extendSelection)
                                {
                                    // case a.
                                    var closingSpans = braceMatcher.FindMatchingSpanAsync(document, matchingSpan.Value.Start, options, cancellationToken).WaitAndGetResult(cancellationToken);
                                    var vsClosingSpans = textView.GetSpanInView(closingSpans.Value.ToSnapshotSpan(subjectBuffer.CurrentSnapshot)).First().ToVsTextSpan();
                                    pSpan[0].iEndIndex = vsClosingSpans.iStartIndex;
                                }
 
                                return VSConstants.S_OK;
                            }
                            else if (matchingSpan.Value.End > position) // caret is at open parenthesis
                            {
                                pSpan[0].iEndLine = vsTextSpan.iEndLine;
                                pSpan[0].iEndIndex = vsTextSpan.iEndIndex;
 
                                // if selecting, adjust start position by using the matching closing span that we computed
                                if (extendSelection)
                                {
                                    // case a.
                                    pSpan[0].iEndIndex = vsTextSpan.iStartIndex;
 
                                    // case b.
                                    var openingSpans = braceMatcher.FindMatchingSpanAsync(document, matchingSpan.Value.End, options, cancellationToken).WaitAndGetResult(cancellationToken);
                                    var vsOpeningSpans = textView.GetSpanInView(openingSpans.Value.ToSnapshotSpan(subjectBuffer.CurrentSnapshot)).First().ToVsTextSpan();
                                    pSpan[0].iStartIndex = vsOpeningSpans.iStartIndex;
                                }
 
                                return VSConstants.S_OK;
                            }
                        }
                    }
                }
            }
        }
 
        return VSConstants.S_FALSE;
    }
 
    int IVsTextViewFilter.GetWordExtent(int iLine, int iIndex, uint dwFlags, TextSpan[] pSpan)
        => VSConstants.E_NOTIMPL;
}