Welcome to mirror list, hosted at ThFree Co, Russian Federation.

NavigateToNextIssueCommandHandler.cs « Commands « EditorOperations « Impl « Text « src - github.com/microsoft/vs-editor-api.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 25948cf3177452dcc85201d7064c957121812cbf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
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));
            }
        }
    }
}