AbstractAsynchronousTaggerProvider.cs 12.9 KB
Newer Older
J
Jonathon Marolf 已提交
1 2 3
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
4

5 6
#nullable enable

7
using System.Collections.Generic;
8 9 10
#if DEBUG
using System.Diagnostics;
#endif
11 12 13
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Options;
14
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
15 16
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.TestHooks;
17
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
18 19 20 21 22 23 24 25 26 27
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Tagging;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Editor.Tagging
{
    /// <summary>
    /// Base type of all asynchronous tagger providers (<see cref="ITaggerProvider"/> and <see cref="IViewTaggerProvider"/>). 
    /// </summary>
28
    internal abstract partial class AbstractAsynchronousTaggerProvider<TTag> : ForegroundThreadAffinitizedObject where TTag : ITag
29
    {
30
        private readonly object _uniqueKey = new();
31
        private readonly IForegroundNotificationService _notificationService;
32

C
Finish  
Cyrus Najmabadi 已提交
33 34
        protected readonly IAsynchronousOperationListener AsyncListener;

35 36 37 38 39 40 41 42 43 44 45 46 47
        /// <summary>
        /// The behavior the tagger engine will have when text changes happen to the subject buffer
        /// it is attached to.  Most taggers can simply use <see cref="TaggerTextChangeBehavior.None"/>.
        /// However, advanced taggers that want to perform specialized behavior depending on what has
        /// actually changed in the file can specify <see cref="TaggerTextChangeBehavior.TrackTextChanges"/>.
        /// 
        /// If this is specified the tagger engine will track text changes and pass them along as
        /// <see cref="TaggerContext{TTag}.TextChangeRange"/> when calling 
        /// <see cref="ProduceTagsAsync(TaggerContext{TTag})"/>.
        /// </summary>
        protected virtual TaggerTextChangeBehavior TextChangeBehavior => TaggerTextChangeBehavior.None;

        /// <summary>
48
        /// The behavior the tagger will have when changes happen to the caret.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
        /// </summary>
        protected virtual TaggerCaretChangeBehavior CaretChangeBehavior => TaggerCaretChangeBehavior.None;

        /// <summary>
        /// The behavior of tags that are created by the async tagger.  This will matter for tags
        /// created for a previous version of a document that are mapped forward by the async
        /// tagging architecture.  This value cannot be <see cref="SpanTrackingMode.Custom"/>.
        /// </summary>
        protected virtual SpanTrackingMode SpanTrackingMode => SpanTrackingMode.EdgeExclusive;

        /// <summary>
        /// Comparer used to determine if two <see cref="ITag"/>s are the same.  This is used by
        /// the <see cref="AbstractAsynchronousTaggerProvider{TTag}"/> to determine if a previous set of
        /// computed tags and a current set of computed tags should be considered the same or not.
        /// If they are the same, then the UI will not be updated.  If they are different then
        /// the UI will be updated for sets of tags that have been removed or added.
        /// </summary>
66
        protected virtual IEqualityComparer<TTag> TagComparer => EqualityComparer<TTag>.Default;
67 68 69 70 71 72 73 74

        /// <summary>
        /// Options controlling this tagger.  The tagger infrastructure will check this option
        /// against the buffer it is associated with to see if it should tag or not.
        /// 
        /// An empty enumerable, or null, can be returned to indicate that this tagger should 
        /// run unconditionally.
        /// </summary>
75 76
        protected virtual IEnumerable<Option2<bool>> Options => SpecializedCollections.EmptyEnumerable<Option2<bool>>();
        protected virtual IEnumerable<PerLanguageOption2<bool>> PerLanguageOptions => SpecializedCollections.EmptyEnumerable<PerLanguageOption2<bool>>();
77

78 79 80
        /// <summary>
        /// This controls what delay tagger will use to let editor know about newly inserted tags
        /// </summary>
H
Heejae Chang 已提交
81
        protected virtual TaggerDelay AddedTagNotificationDelay => TaggerDelay.NearImmediate;
82 83 84 85

        /// <summary>
        /// This controls what delay tagger will use to let editor know about just deleted tags.
        /// </summary>
H
Heejae Chang 已提交
86
        protected virtual TaggerDelay RemovedTagNotificationDelay => TaggerDelay.NearImmediate;
87

88 89 90 91
#if DEBUG
        public readonly string StackTrace;
#endif

92
        protected AbstractAsynchronousTaggerProvider(
93
            IThreadingContext threadingContext,
94 95
            IAsynchronousOperationListener asyncListener,
            IForegroundNotificationService notificationService)
96
            : base(threadingContext)
97
        {
C
Finish  
Cyrus Najmabadi 已提交
98
            AsyncListener = asyncListener;
99 100 101 102 103
            _notificationService = notificationService;

#if DEBUG
            StackTrace = new StackTrace().ToString();
#endif
104 105
        }

106
        internal IAccurateTagger<T>? CreateTaggerWorker<T>(ITextView textViewOpt, ITextBuffer subjectBuffer) where T : ITag
107
        {
J
Jason Malinowski 已提交
108
            if (!subjectBuffer.GetFeatureOnOffOption(EditorComponentOnOffOptions.Tagger))
109 110 111 112 113
            {
                return null;
            }

            var tagSource = GetOrCreateTagSource(textViewOpt, subjectBuffer);
C
Finish  
Cyrus Najmabadi 已提交
114
            return new Tagger(ThreadingContext, AsyncListener, _notificationService, tagSource, subjectBuffer) as IAccurateTagger<T>;
115 116 117 118
        }

        private TagSource GetOrCreateTagSource(ITextView textViewOpt, ITextBuffer subjectBuffer)
        {
C
CyrusNajmabadi 已提交
119
            if (!this.TryRetrieveTagSource(textViewOpt, subjectBuffer, out var tagSource))
120
            {
C
Finish  
Cyrus Najmabadi 已提交
121
                tagSource = new TagSource(textViewOpt, subjectBuffer, this, AsyncListener, _notificationService);
122 123 124 125 126 127 128 129 130 131 132

                this.StoreTagSource(textViewOpt, subjectBuffer, tagSource);
                tagSource.Disposed += (s, e) => this.RemoveTagSource(textViewOpt, subjectBuffer);
            }

            return tagSource;
        }

        private bool TryRetrieveTagSource(ITextView textViewOpt, ITextBuffer subjectBuffer, out TagSource tagSource)
        {
            return textViewOpt != null
133 134
                ? textViewOpt.TryGetPerSubjectBufferProperty(subjectBuffer, _uniqueKey, out tagSource)
                : subjectBuffer.Properties.TryGetProperty(_uniqueKey, out tagSource);
135 136 137 138 139 140
        }

        private void RemoveTagSource(ITextView textViewOpt, ITextBuffer subjectBuffer)
        {
            if (textViewOpt != null)
            {
141
                textViewOpt.RemovePerSubjectBufferProperty<TagSource, ITextView>(subjectBuffer, _uniqueKey);
142 143 144
            }
            else
            {
145
                subjectBuffer.Properties.RemoveProperty(_uniqueKey);
146 147 148 149 150 151 152
            }
        }

        private void StoreTagSource(ITextView textViewOpt, ITextBuffer subjectBuffer, TagSource tagSource)
        {
            if (textViewOpt != null)
            {
153
                textViewOpt.AddPerSubjectBufferProperty(subjectBuffer, _uniqueKey, tagSource);
154 155 156
            }
            else
            {
157
                subjectBuffer.Properties.AddProperty(_uniqueKey, tagSource);
158 159 160
            }
        }

161 162 163 164 165 166 167
        /// <summary>
        /// Called by the <see cref="AbstractAsynchronousTaggerProvider{TTag}"/> infrastructure to 
        /// determine the caret position.  This value will be passed in as the value to 
        /// <see cref="TaggerContext{TTag}.CaretPosition"/> in the call to
        /// <see cref="ProduceTagsAsync(TaggerContext{TTag})"/>.
        /// </summary>
        protected virtual SnapshotPoint? GetCaretPoint(ITextView textViewOpt, ITextBuffer subjectBuffer)
168
            => textViewOpt?.GetCaretPoint(subjectBuffer);
169

170 171 172 173 174 175
        /// <summary>
        /// Called by the <see cref="AbstractAsynchronousTaggerProvider{TTag}"/> infrastructure to determine
        /// the set of spans that it should asynchronously tag.  This will be called in response to
        /// notifications from the <see cref="ITaggerEventSource"/> that something has changed, and
        /// will only be called from the UI thread.  The tagger infrastructure will then determine
        /// the <see cref="DocumentSnapshotSpan"/>s associated with these <see cref="SnapshotSpan"/>s
176
        /// and will asynchronously call into <see cref="ProduceTagsAsync(TaggerContext{TTag})"/> at some point in
177 178 179
        /// the future to produce tags for these spans.
        /// </summary>
        protected virtual IEnumerable<SnapshotSpan> GetSpansToTag(ITextView textViewOpt, ITextBuffer subjectBuffer)
180
        {
181
            // For a standard tagger, the spans to tag is the span of the entire snapshot.
182
            return SpecializedCollections.SingletonEnumerable(subjectBuffer.CurrentSnapshot.GetFullSpan());
183 184
        }

185 186 187 188 189 190 191 192
        /// <summary>
        /// Creates the <see cref="ITaggerEventSource"/> that notifies the <see cref="AbstractAsynchronousTaggerProvider{TTag}"/>
        /// that it should recompute tags for the text buffer after an appropriate <see cref="TaggerDelay"/>.
        /// </summary>
        protected abstract ITaggerEventSource CreateEventSource(ITextView textViewOpt, ITextBuffer subjectBuffer);

        /// <summary>
        /// Produce tags for the given context.
193
        /// Keep in sync with <see cref="ProduceTagsSynchronously(TaggerContext{TTag})"/>
194
        /// </summary>
C
Cyrus Najmabadi 已提交
195
        protected virtual async Task ProduceTagsAsync(TaggerContext<TTag> context)
196 197 198 199
        {
            foreach (var spanToTag in context.SpansToTag)
            {
                context.CancellationToken.ThrowIfCancellationRequested();
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
                await ProduceTagsAsync(
                    context, spanToTag,
                    GetCaretPosition(context.CaretPosition, spanToTag.SnapshotSpan)).ConfigureAwait(false);
            }
        }

        /// <summary>
        /// Produce tags for the given context.
        /// Keep in sync with <see cref="ProduceTagsAsync(TaggerContext{TTag})"/>
        /// </summary>
        protected void ProduceTagsSynchronously(TaggerContext<TTag> context)
        {
            foreach (var spanToTag in context.SpansToTag)
            {
                context.CancellationToken.ThrowIfCancellationRequested();
                ProduceTagsSynchronously(
D
dotnet-bot 已提交
216
                    context, spanToTag,
217
                    GetCaretPosition(context.CaretPosition, spanToTag.SnapshotSpan));
218 219 220 221 222 223 224 225 226
            }
        }

        private static int? GetCaretPosition(SnapshotPoint? caretPosition, SnapshotSpan snapshotSpan)
        {
            return caretPosition.HasValue && caretPosition.Value.Snapshot == snapshotSpan.Snapshot
                ? caretPosition.Value.Position : (int?)null;
        }

227
        protected virtual Task ProduceTagsAsync(TaggerContext<TTag> context, DocumentSnapshotSpan spanToTag, int? caretPosition)
228
            => Task.CompletedTask;
229

230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
        protected virtual void ProduceTagsSynchronously(TaggerContext<TTag> context, DocumentSnapshotSpan spanToTag, int? caretPosition)
        {
            // By default we implement the sync version of this by blocking on the async version.
            //
            // The benefit of this is that all taggers can implicitly be used as IAccurateTaggers
            // without any code changes.
            // 
            // However, the drawback is that it means the UI thread might be blocked waiting for 
            // tasks to be scheduled and run on the threadpool. 
            //
            // Taggers that need to be called accurately should override this method to produce
            // results quickly if possible.
            ProduceTagsAsync(context, spanToTag, caretPosition).Wait(context.CancellationToken);
        }

245
        internal TestAccessor GetTestAccessor()
246
            => new(this);
247

248 249 250 251 252
        private struct DiffResult
        {
            public NormalizedSnapshotSpanCollection Added { get; }
            public NormalizedSnapshotSpanCollection Removed { get; }

253
            public DiffResult(List<SnapshotSpan> added, List<SnapshotSpan> removed)
254
                : this(added?.Count == 0 ? null : (IEnumerable<SnapshotSpan>?)added, removed?.Count == 0 ? null : (IEnumerable<SnapshotSpan>?)removed)
H
Heejae Chang 已提交
255 256 257
            {
            }

258
            public DiffResult(IEnumerable<SnapshotSpan>? added, IEnumerable<SnapshotSpan>? removed)
259 260 261 262 263 264 265
            {
                Added = added != null ? new NormalizedSnapshotSpanCollection(added) : NormalizedSnapshotSpanCollection.Empty;
                Removed = removed != null ? new NormalizedSnapshotSpanCollection(removed) : NormalizedSnapshotSpanCollection.Empty;
            }

            public int Count => Added.Count + Removed.Count;
        }
266 267 268 269 270 271

        internal readonly struct TestAccessor
        {
            private readonly AbstractAsynchronousTaggerProvider<TTag> _provider;

            public TestAccessor(AbstractAsynchronousTaggerProvider<TTag> provider)
272
                => _provider = provider;
273 274 275 276

            internal Task ProduceTagsAsync(TaggerContext<TTag> context)
                => _provider.ProduceTagsAsync(context);
        }
277
    }
S
Sam Harwell 已提交
278
}