// 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); }
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); }
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); }
public GoToMatchingBraceCommandHandler(IBraceMatchingService braceMatchingService) { _braceMatchingService = braceMatchingService ?? throw new ArgumentNullException(nameof(braceMatchingService)); }