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

SimpleTagger.cs « Tagging « TextLogic « Def « Text « src - github.com/microsoft/vs-editor-api.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 37d845ba800ca0ba7582ff74e19f59af3e4617a5 (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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
//
//  Copyright (c) Microsoft Corporation. All rights reserved.
//  Licensed under the MIT License. See License.txt in the project root for license information.
//
namespace Microsoft.VisualStudio.Text.Tagging
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;

    using Microsoft.VisualStudio.Text;

    /// <summary>
    /// Provides simple, thread-safe storage of and interaction with tags of the given type.
    /// </summary>
    /// <typeparam name="T">The type, which must be a subtype of <see cref="ITag"/>.</typeparam>
    public class SimpleTagger<T> : ITagger<T> where T : ITag
    {
        #region Private members

        private List<TrackingTagSpan<T>> _trackingTagSpans = new List<TrackingTagSpan<T>>();

        private ITextBuffer buffer;
        private object mutex = new object();
        #endregion

        /// <summary>
        /// Initializes a new instance of <see cref="SimpleTagger&lt;T&gt;"/> for the specified buffer.
        /// </summary>
        /// <param name="buffer">Subject buffer that will be tagged.</param>
        public SimpleTagger(ITextBuffer buffer)
        {
            this.buffer = buffer;
        }

        private int _batchNesting;
        private ITrackingSpan _batchSpan;

        private void StartBatch()
        {
            Interlocked.Increment(ref _batchNesting);
        }

        private void EndBatch()
        {
            if (Interlocked.Decrement(ref _batchNesting) == 0)
            {
                ITrackingSpan batchedRange = Interlocked.Exchange(ref _batchSpan, null);

                if (batchedRange != null)
                {
                    EventHandler<SnapshotSpanEventArgs> handler = this.TagsChanged;
                    if (handler != null)
                    {
                        handler(this, new SnapshotSpanEventArgs(batchedRange.GetSpan(buffer.CurrentSnapshot)));
                    }
                }
            }
        }

        private void UpdateBatchSpan(ITrackingSpan snapshotSpan)
        {
            ITrackingSpan newBatchSpan = snapshotSpan;

            // If there currently is a batch span, update it to include the biggest
            // range of buffer affected so far.
            if (_batchSpan != null)
            {
                ITextSnapshot snapshot = buffer.CurrentSnapshot;

                SnapshotSpan currentBatchSpan = _batchSpan.GetSpan(snapshot);
                SnapshotSpan currentUpdate = snapshotSpan.GetSpan(snapshot);

                SnapshotPoint newStart = currentBatchSpan.Start < currentUpdate.Start ? currentBatchSpan.Start : currentUpdate.Start;
                SnapshotPoint newEnd = currentBatchSpan.End > currentUpdate.End ? currentBatchSpan.End : currentUpdate.End;

                // In the event of multiple updates, we use the tracking mode of the first update's span for predictability
                newBatchSpan = snapshot.CreateTrackingSpan(new SnapshotSpan(newStart, newEnd), _batchSpan.TrackingMode);
            }

            _batchSpan = newBatchSpan;
        }

        #region SimpleTagger<T> Members

        /// <summary>
        /// Adds a tag over the given span.
        /// </summary>
        /// <param name="span">The <see cref="ITrackingSpan"/> that tracks the tag across text versions.</param>
        /// <param name="tag">The tag to associate with the given span.</param>
        /// <returns>The <see cref="TrackingTagSpan&lt;T&gt;"/> that was added, which can be used to remove the tag later on.</returns>
        /// <remarks>This method is safe to use from any thread.</remarks>
        /// <exception cref="ArgumentNullException"><paramref name="span"/> or <paramref name="tag"/> is null.</exception>
        public TrackingTagSpan<T> CreateTagSpan(ITrackingSpan span, T tag)
        {
            if (span == null)
                throw new ArgumentNullException("span");
            if (tag == null)
                throw new ArgumentNullException("tag");

            var tagSpan = new TrackingTagSpan<T>(span, tag);

            StartBatch();
            try
            {
                lock (mutex)
                {
                    _trackingTagSpans.Add(tagSpan);
                    UpdateBatchSpan(tagSpan.Span);
                }
            }
            finally
            {
                EndBatch();
            }

            return tagSpan;
        }

        /// <summary>
        /// Removes a tag span that was created by calling <see cref="CreateTagSpan"/>.
        /// </summary>
        /// <param name="tagSpan">The <see cref="TrackingTagSpan&lt;T&gt;"/> returned from a previous call to <see cref="CreateTagSpan"/>.</param>
        /// <returns><c>true</c> if removed successfully, otherwise <c>false</c>.</returns>
        /// <remarks>This method is safe to use from any thread.</remarks>
        public bool RemoveTagSpan(TrackingTagSpan<T> tagSpan)
        {
            if (tagSpan == null)
                throw new ArgumentNullException("tagSpan");

            bool removed = false;

            StartBatch();
            try
            {
                lock (mutex)
                {
                    // Find the tracking tag span to be removed
                    removed = (_trackingTagSpans.Remove(tagSpan));
                    if (removed)
                    {
                        UpdateBatchSpan(tagSpan.Span);
                    }
                }
            }
            finally
            {
                EndBatch();
            }

            return removed;
        }

        /// <summary>
        /// Removes all tag spans that match the conditions specified by the predicate.
        /// </summary>
        /// <param name="match">The <see cref="Predicate&lt;T&gt;"/> that defines the match.</param>
        /// <returns>The number of tag spans removed.</returns>
        /// <remarks>This method is safe to use from any thread.</remarks>
        /// <exception cref="ArgumentNullException"><paramref name="match"/> is null.</exception>
        public int RemoveTagSpans(Predicate<TrackingTagSpan<T>> match)
        {
            if (match == null)
                throw new ArgumentNullException("match");

            int removedCount = 0;

            StartBatch();
            try
            {
                lock (mutex)
                {
                    removedCount = _trackingTagSpans.RemoveAll(tagSpan =>
                    {
                        // If we have a match, then we'll need to update the batch span to include this span.
                        if (match(tagSpan))
                        {
                            UpdateBatchSpan(tagSpan.Span);
                            return true;
                        }
                        return false;
                    });
                }
            }
            finally
            {
                EndBatch();
            }

            return removedCount;
        }

        /// <summary>
        /// Gets the tagged spans that intersect the given <see cref="SnapshotSpan"/>.
        /// </summary>
        /// <param name="span">The <see cref="SnapshotSpan"/> to use.</param>
        /// <returns>The set of <see cref="TrackingTagSpan&lt;T&gt;"/> objects that intersect the given span, in order.</returns>
        public IEnumerable<TrackingTagSpan<T>> GetTaggedSpans(SnapshotSpan span)
        {
            IList<TrackingTagSpan<T>> tagSpanList = new List<TrackingTagSpan<T>>(_trackingTagSpans);

            lock (mutex)
            {
                tagSpanList = new List<TrackingTagSpan<T>>(_trackingTagSpans);
            }

            return tagSpanList.Where(tagSpan => span.IntersectsWith(tagSpan.Span.GetSpan(span.Snapshot)));
        }

        /// <summary>
        /// Gets an IDisposable object that represents an update batch.
        /// </summary>
        /// <returns>An IDisposable object that represents an update batch.</returns>
        public IDisposable Update()
        {
            return new Batch(this);
        }

        #endregion

        #region ITagger<T> Members

        /// <summary>
        /// Gets all the tags that intersect the spans in the specified snapshot
        /// of the desired type.
        /// </summary>
        /// <param name="spans">The spans to visit.</param>
        /// <returns>A <see cref="ITagSpan&lt;T&gt;"/> for each tag.</returns>
        public IEnumerable<ITagSpan<T>> GetTags(NormalizedSnapshotSpanCollection spans)
        {
            if (spans.Count == 0)
                yield break;

            IList<TrackingTagSpan<T>> tagSpanList;

            lock (mutex)
            {
                tagSpanList = new List<TrackingTagSpan<T>>(_trackingTagSpans);
            }

            foreach (var tagSpan in tagSpanList)
            {
                SnapshotSpan tagSnapshotSpan = tagSpan.Span.GetSpan(spans[0].Snapshot);
                
                foreach (var querySpan in spans)
                {
                    if (tagSnapshotSpan.IntersectsWith(querySpan))
                    {
                        yield return new TagSpan<T>(tagSnapshotSpan, tagSpan.Tag);
                        break;
                    }
                }
            }
        }

        /// <summary>
        /// Occurs when one or more tags have been added or removed.
        /// </summary>
        public event EventHandler<SnapshotSpanEventArgs> TagsChanged;

        #endregion

        private class Batch : IDisposable
        {
            SimpleTagger<T> _tagger;
            internal Batch(SimpleTagger<T> tagger)
            {
                if (tagger == null)
                {
                    throw new ArgumentNullException("tagger");
                }
                _tagger = tagger;
                _tagger.StartBatch();
            }

            #region IDisposable Members

            public void Dispose()
            {
                _tagger.EndBatch();
                _tagger = null;
                GC.SuppressFinalize(this);
            }

            #endregion
        }
    }
}