diff options
author | Kirill Osenkov <github@osenkov.com> | 2018-08-15 21:13:16 +0300 |
---|---|---|
committer | Kirill Osenkov <github@osenkov.com> | 2018-08-15 21:13:16 +0300 |
commit | b407d9744a34fd4647cf3e99068a28d728fd65a2 (patch) | |
tree | e6d6cef915f32dec4f2d16b1868045143deb589f /src/Text/Impl/EditorOperations/Commands/NavigateToNextIssueCommandHandler.cs | |
parent | cf974b266fb428d846e2e16a25a4eeb8f635c26f (diff) |
Updating to VS-Platform 15.8.519 (29d85337c7d2af248a692fd65ac37b444551e4cf).
Diffstat (limited to 'src/Text/Impl/EditorOperations/Commands/NavigateToNextIssueCommandHandler.cs')
-rw-r--r-- | src/Text/Impl/EditorOperations/Commands/NavigateToNextIssueCommandHandler.cs | 156 |
1 files changed, 156 insertions, 0 deletions
diff --git a/src/Text/Impl/EditorOperations/Commands/NavigateToNextIssueCommandHandler.cs b/src/Text/Impl/EditorOperations/Commands/NavigateToNextIssueCommandHandler.cs new file mode 100644 index 0000000..25948cf --- /dev/null +++ b/src/Text/Impl/EditorOperations/Commands/NavigateToNextIssueCommandHandler.cs @@ -0,0 +1,156 @@ +namespace Microsoft.VisualStudio.Text.Operations.Implementation +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.Composition; + using System.Diagnostics; + using System.Linq; + using Microsoft.VisualStudio.Commanding; + using Microsoft.VisualStudio.Text.Editor; + using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; + using Microsoft.VisualStudio.Text.Tagging; + using Microsoft.VisualStudio.Utilities; + + [Export(typeof(ICommandHandler))] + [Name("default " + nameof(NavigateToNextIssueCommandHandler))] + [ContentType("any")] + [TextViewRole(PredefinedTextViewRoles.Analyzable)] + internal sealed class NavigateToNextIssueCommandHandler : ICommandHandler<NavigateToNextIssueInDocumentCommandArgs>, ICommandHandler<NavigateToPreviousIssueInDocumentCommandArgs> + { + [Import] + private Lazy<IBufferTagAggregatorFactoryService> tagAggregatorFactoryService; + + public string DisplayName => Strings.NextIssue; + + #region Previous Issue + + public CommandState GetCommandState(NavigateToPreviousIssueInDocumentCommandArgs args) => CommandState.Available; + + public bool ExecuteCommand(NavigateToPreviousIssueInDocumentCommandArgs args, CommandExecutionContext executionContext) + { + var snapshot = args.TextView.TextSnapshot; + var spans = this.GetTagSpansCollection(snapshot, args.ErrorTagTypeNames); + + if (spans.Count == 0) + { + return true; + } + + (int indexOfErrorSpan, bool containsPoint) = IndexOfTagSpanNearPoint(spans, args.TextView.Caret.Position.BufferPosition.Position); + + int nextIndex = indexOfErrorSpan - 1; + if (containsPoint && (spans.Count == 1)) + { + // There is only one error tag and it contains the caret. Ensure it stays put. + return true; + } + + // Wrap if needed. + if (nextIndex < 0) + { + nextIndex = (spans.Count - 1); + } + + args.TextView.Caret.MoveTo(new SnapshotPoint(snapshot, spans[nextIndex].Start)); + args.TextView.Caret.EnsureVisible(); + return true; + } + + #endregion + + #region Next Issue + public CommandState GetCommandState(NavigateToNextIssueInDocumentCommandArgs args) => CommandState.Available; + + public bool ExecuteCommand(NavigateToNextIssueInDocumentCommandArgs args, CommandExecutionContext executionContext) + { + var snapshot = args.TextView.TextSnapshot; + var spans = this.GetTagSpansCollection(snapshot, args.ErrorTagTypeNames); + + if (spans.Count == 0) + { + return true; + } + + (int indexOfErrorSpan, bool containsPoint) = IndexOfTagSpanNearPoint(spans, args.TextView.Caret.Position.BufferPosition.Position); + + int nextIndex = indexOfErrorSpan + 1; + if (containsPoint) + { + if (spans.Count == 1) + { + // There is only one error tag and it contains the caret. Ensure it stays put. + return true; + } + } + else + { + nextIndex = indexOfErrorSpan; + } + + // Wrap if needed. + if ((indexOfErrorSpan == -1) || (nextIndex >= spans.Count)) + { + nextIndex = 0; + } + + args.TextView.Caret.MoveTo(new SnapshotPoint(snapshot, spans[nextIndex].Start)); + args.TextView.Caret.EnsureVisible(); + return true; + } + + #endregion + + private static (int index, bool containsPoint) IndexOfTagSpanNearPoint(NormalizedSpanCollection spans, int point) + { + Debug.Assert(spans.Count > 0); + Span? tagBefore = null; + Span? tagAfter = null; + + for (int i = 0; i < spans.Count; i++) + { + tagBefore = tagAfter; + tagAfter = spans[i]; + + // Case 0: point falls within error tag. We use explicit comparisons instead + // of 'Contains' so that we match a tag even if the caret at the end of it. + if ((point >= tagAfter.Value.Start) && (point <= tagAfter.Value.End)) + { + // Return tag containing the point. + return (i, true); + } + + // Case 1: point falls between two tags. + if ((tagBefore != null) && (tagBefore.Value.End < point) && (tagAfter.Value.Start > point)) + { + // Return tag following the point. + return (i, false); + } + } + + // Case 2: point falls after all tags. + return (-1, false); + } + + private NormalizedSpanCollection GetTagSpansCollection(ITextSnapshot snapshot, IEnumerable<string> errorTagTypeNames) + { + using (var tagger = this.tagAggregatorFactoryService.Value.CreateTagAggregator<IErrorTag>(snapshot.TextBuffer)) + { + var rawTags = tagger.GetTags(new SnapshotSpan(snapshot, 0, snapshot.Length)); + var curatedTags = (errorTagTypeNames?.Any() ?? false) ? + rawTags.Where(tag => errorTagTypeNames.Contains(tag.Tag.ErrorType)) : + rawTags; + + // In this case we only grab the first span that the IMappingTagSpan maps to because we always + // want to place the caret at the start of the error, and so, don't care about possibly disjoint + // subspans after mapping to the view's buffer. NormalizedSpanCollection takes care of sorting + // and joining overlapping spans together for us. It's possible for a tag to map to zero spans + // in projection scenarios in which the tag exists entirely within a region that doesn't map to + // visible space. + return new NormalizedSpanCollection( + curatedTags.Select(tagSpan => tagSpan.Span.GetSpans(snapshot)) + .Where(spanCollection => spanCollection.Count > 0) + .Select(spanCollection => spanCollection[0].Span)); + } + } + } +} |