// Internal for testing purposes
        internal static int GetPairExtentsWorker(ITextView textView, Workspace workspace, IBraceMatchingService braceMatcher, int iLine, int iIndex, TextSpan[] pSpan, 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 matchingSpan = braceMatcher.FindMatchingSpanAsync(document, position, 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();

                                if (matchingSpan.Value.Start < position)
                                {
                                    pSpan[0].iStartLine = vsTextSpan.iStartLine;
                                    pSpan[0].iStartIndex = vsTextSpan.iStartIndex;
                                }
                                else if (matchingSpan.Value.End > position)
                                {
                                    pSpan[0].iEndLine = vsTextSpan.iEndLine;
                                    pSpan[0].iEndIndex = vsTextSpan.iEndIndex;
                                }
                            }
                        }
                    }
                }
            }

            return VSConstants.S_OK;
        }
        // Internal for testing purposes
        internal static int GetPairExtentsWorker(ITextView textView, Workspace workspace, IBraceMatchingService braceMatcher, 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 matchingSpan = braceMatcher.FindMatchingSpanAsync(document, position, 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, cancellationToken).WaitAndGetResult(cancellationToken);
                                        var vsClosingSpans = textView.GetSpanInView(closingSpans.Value.ToSnapshotSpan(subjectBuffer.CurrentSnapshot)).ToList().First().ToVsTextSpan();
                                        pSpan[0].iEndIndex = vsClosingSpans.iStartIndex;
                                    }
                                }
                                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, cancellationToken).WaitAndGetResult(cancellationToken);
                                        var vsOpeningSpans = textView.GetSpanInView(openingSpans.Value.ToSnapshotSpan(subjectBuffer.CurrentSnapshot)).ToList().First().ToVsTextSpan();
                                        pSpan[0].iStartIndex = vsOpeningSpans.iStartIndex;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            return VSConstants.S_OK;
        }
        // Internal for testing purposes
        internal static int GetPairExtentsWorker(ITextView textView, IBraceMatchingService braceMatcher, 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 matchingSpan = braceMatcher.FindMatchingSpanAsync(document, position, 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, cancellationToken).WaitAndGetResult(cancellationToken);
                                        var vsClosingSpans = textView.GetSpanInView(closingSpans.Value.ToSnapshotSpan(subjectBuffer.CurrentSnapshot)).ToList().First().ToVsTextSpan();
                                        pSpan[0].iEndIndex = vsClosingSpans.iStartIndex;
                                    }
                                }
                                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, cancellationToken).WaitAndGetResult(cancellationToken);
                                        var vsOpeningSpans = textView.GetSpanInView(openingSpans.Value.ToSnapshotSpan(subjectBuffer.CurrentSnapshot)).ToList().First().ToVsTextSpan();
                                        pSpan[0].iStartIndex = vsOpeningSpans.iStartIndex;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            return(VSConstants.S_OK);
        }
Exemple #4
0
        public static async Task <TextSpan?> FindMatchingSpanAsync(
            this IBraceMatchingService service,
            Document document,
            int position,
            CancellationToken cancellationToken
            )
        {
            // These are the matching spans when checking the token to the right of the position.
            var braces1 = await service
                          .GetMatchingBracesAsync(document, position, cancellationToken)
                          .ConfigureAwait(false);

            // These are the matching spans when checking the token to the left of the position.
            BraceMatchingResult?braces2 = null;

            // Ensure caret is valid at left of position.
            if (position > 0)
            {
                braces2 = await service
                          .GetMatchingBracesAsync(document, position - 1, cancellationToken)
                          .ConfigureAwait(false);
            }

            // Favor matches where the position is on the outside boundary of the braces. i.e. if we
            // have:  {^()}
            //
            // then this would return the  ()  not the  {}
            if (
                braces1.HasValue &&
                position >= braces1.Value.LeftSpan.Start &&
                position < braces1.Value.LeftSpan.End
                )
            {
                // ^{ } -- return right span
                return(braces1.Value.RightSpan);
            }
            else if (
                braces2.HasValue &&
                position > braces2.Value.RightSpan.Start &&
                position <= braces2.Value.RightSpan.End
                )
            {
                // { }^ -- return left span
                return(braces2.Value.LeftSpan);
            }
            else if (
                braces2.HasValue &&
                position > braces2.Value.LeftSpan.Start &&
                position <= braces2.Value.LeftSpan.End
                )
            {
                // {^ } -- return right span
                return(braces2.Value.RightSpan);
            }
            else if (
                braces1.HasValue &&
                position >= braces1.Value.RightSpan.Start &&
                position < braces1.Value.RightSpan.End
                )
            {
                // { ^} - return left span
                return(braces1.Value.LeftSpan);
            }

            return(null);
        }
Exemple #5
0
 public GoToMatchingBraceCommandHandler(IBraceMatchingService braceMatchingService, IGlobalOptionService globalOptions)
 {
     _braceMatchingService = braceMatchingService;
     _globalOptions        = globalOptions;
 }
        private static async Task <(BraceMatchingResult?leftOfPosition, BraceMatchingResult?rightOfPosition)> GetAllMatchingBracesAsync(
            IBraceMatchingService service,
            Document document,
            int position,
            CancellationToken cancellationToken
            )
        {
            // These are the matching spans when checking the token to the right of the position.
            var rightOfPosition = await service
                                  .GetMatchingBracesAsync(document, position, cancellationToken)
                                  .ConfigureAwait(false);

            // The braces to the right of the position should only be added if the position is
            // actually within the span of the start brace.  Note that this is what we want for
            // single character braces as well as multi char braces.  i.e. if the user has:
            //
            //      ^{ }    // then { and } are matching braces.
            //      {^ }    // then { and } are not matching braces.
            //
            //      ^<@ @>  // then <@ and @> are matching braces.
            //      <^@ @>  // then <@ and @> are matching braces.
            //      <@^ @>  // then <@ and @> are not matching braces.
            if (rightOfPosition.HasValue && !rightOfPosition.Value.LeftSpan.Contains(position))
            {
                // Not a valid match.
                rightOfPosition = null;
            }

            if (position == 0)
            {
                // We're at the start of the document, can't find braces to the left of the position.
                return(leftOfPosition : null, rightOfPosition);
            }

            // See if we're touching the end of some construct.  i.e.:
            //
            //      { }^
            //      <@ @>^
            //      <@ @^>
            //
            // But not
            //
            //      { ^}
            //      <@ ^@>

            var leftOfPosition = await service
                                 .GetMatchingBracesAsync(document, position - 1, cancellationToken)
                                 .ConfigureAwait(false);

            if (
                leftOfPosition.HasValue &&
                position <= leftOfPosition.Value.RightSpan.End &&
                position > leftOfPosition.Value.RightSpan.Start
                )
            {
                // Found a valid pair on the left of us.
                return(leftOfPosition, rightOfPosition);
            }

            // No valid pair of braces on the left of us.
            return(leftOfPosition : null, rightOfPosition);
        }
Exemple #7
0
 public GoToMatchingBraceCommandHandler(IBraceMatchingService braceMatchingService)
 {
     _braceMatchingService = braceMatchingService ??
                             throw new ArgumentNullException(nameof(braceMatchingService));
 }