private ImmutableDictionary <ITextBuffer, TagSpanIntervalTree <TTag> > ComputeNewTagTrees(
                ImmutableDictionary <ITextBuffer, TagSpanIntervalTree <TTag> > oldTagTrees,
                TaggerContext <TTag> context)
            {
                // Ignore any tag spans reported for any buffers we weren't interested in.

                var spansToTag      = context.SpansToTag;
                var buffersToTag    = spansToTag.Select(dss => dss.SnapshotSpan.Snapshot.TextBuffer).ToSet();
                var newTagsByBuffer =
                    context.tagSpans.Where(ts => buffersToTag.Contains(ts.Span.Snapshot.TextBuffer))
                    .ToLookup(t => t.Span.Snapshot.TextBuffer);
                var spansTagged = context._spansTagged;

                var spansToInvalidateByBuffer = spansTagged.ToLookup(
                    keySelector: span => span.Snapshot.TextBuffer,
                    elementSelector: span => span);

                // Walk through each relevant buffer and decide what the interval tree should be
                // for that buffer.  In general this will work by keeping around old tags that
                // weren't in the range that was re-tagged, and merging them with the new tags
                // produced for the range that was re-tagged.
                var newTagTrees = ImmutableDictionary <ITextBuffer, TagSpanIntervalTree <TTag> > .Empty;

                foreach (var buffer in buffersToTag)
                {
                    var newTagTree = ComputeNewTagTree(oldTagTrees, buffer, newTagsByBuffer[buffer], spansToInvalidateByBuffer[buffer]);
                    if (newTagTree != null)
                    {
                        newTagTrees = newTagTrees.Add(buffer, newTagTree);
                    }
                }

                return(newTagTrees);
            }
 private Task ProduceTagsAsync(TaggerContext <TTag> context, CancellationToken cancellationToken)
 {
     // If the feature is disabled, then just produce no tags.
     return(ShouldSkipTagProduction()
         ? Task.CompletedTask
         : _dataSource.ProduceTagsAsync(context, cancellationToken));
 }
Exemplo n.º 3
0
 /// <summary>
 /// Produce tags for the given context.
 /// </summary>
 protected virtual async Task ProduceTagsAsync(TaggerContext <TTag> context)
 {
     foreach (var spanToTag in context.SpansToTag)
     {
         context.CancellationToken.ThrowIfCancellationRequested();
         await ProduceTagsAsync(context, spanToTag, GetCaretPosition(context.CaretPosition, spanToTag.SnapshotSpan)).ConfigureAwait(false);
     }
 }
Exemplo n.º 4
0
            private void ProduceTagsSynchronously(TaggerContext <TTag> context)
            {
                if (ShouldSkipTagProduction())
                {
                    return;
                }

                _dataSource.ProduceTagsSynchronously(context);
            }
Exemplo n.º 5
0
 /// <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(
             context, spanToTag,
             GetCaretPosition(context.CaretPosition, spanToTag.SnapshotSpan));
     }
 }
Exemplo n.º 6
0
            private Task ProduceTagsAsync(TaggerContext <TTag> context)
            {
                if (ShouldSkipTagProduction())
                {
                    // If the feature is disabled, then just produce no tags.
                    return(Task.CompletedTask);
                }

                return(_dataSource.ProduceTagsAsync(context));
            }
Exemplo n.º 7
0
            private Task ProduceTagsAsync(TaggerContext <TTag> context)
            {
                var options            = _dataSource.Options ?? SpecializedCollections.EmptyEnumerable <Option <bool> >();
                var perLanguageOptions = _dataSource.PerLanguageOptions ?? SpecializedCollections.EmptyEnumerable <PerLanguageOption <bool> >();

                if (options.Any(option => !_subjectBuffer.GetOption(option)) ||
                    perLanguageOptions.Any(option => !_subjectBuffer.GetOption(option)))
                {
                    // If the feature is disabled, then just produce no tags.
                    return(SpecializedTasks.EmptyTask);
                }

                return(_dataSource.ProduceTagsAsync(context));
            }
Exemplo n.º 8
0
 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);
 }
Exemplo n.º 9
0
            /// <summary>
            /// Called on the foreground thread.  Passed a boolean to say if we're computing the
            /// initial set of tags or not.  If we're computing the initial set of tags, we lower
            /// all our delays so that we can get results to the screen as quickly as possible.
            ///
            /// This gives a good experience when a document is opened as the document appears
            /// complete almost immediately.  Once open though, our normal delays come into play
            /// so as to not cause a flashy experience.
            /// </summary>
            private async Task RecomputeTagsForegroundAsync(bool initialTags, CancellationToken cancellationToken)
            {
                this.AssertIsForeground();
                if (cancellationToken.IsCancellationRequested)
                {
                    return;
                }

                using (Logger.LogBlock(FunctionId.Tagger_TagSource_RecomputeTags, cancellationToken))
                {
                    // Make a copy of all the data we need while we're on the foreground.  Then switch to a threadpool
                    // thread to do the computation. Finally, once new tags have been computed, then we update our state
                    // again on the foreground.
                    var spansToTag    = GetSpansAndDocumentsToTag();
                    var caretPosition = _dataSource.GetCaretPoint(_textViewOpt, _subjectBuffer);
                    var oldTagTrees   = this.CachedTagTrees;
                    var oldState      = this.State;

                    var textChangeRange = this.AccumulatedTextChanges;
                    this.AccumulatedTextChanges = null;

                    await TaskScheduler.Default;

                    cancellationToken.ThrowIfCancellationRequested();

                    // Create a context to store pass the information along and collect the results.
                    var context = new TaggerContext <TTag>(
                        oldState, spansToTag, caretPosition, textChangeRange, oldTagTrees);
                    await ProduceTagsAsync(context, cancellationToken).ConfigureAwait(false);

                    cancellationToken.ThrowIfCancellationRequested();

                    // Process the result to determine what changed.
                    var newTagTrees     = ComputeNewTagTrees(oldTagTrees, context);
                    var bufferToChanges = ProcessNewTagTrees(spansToTag, oldTagTrees, newTagTrees, cancellationToken);

                    // Then switch back to the UI thread to update our state and kick off the work to notify the editor.
                    await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

                    // Once we assign our state, we're uncancellable.  We must report the changed information
                    // to the editor.  The only case where it's ok not to is if the tagger itself is disposed.
                    cancellationToken = CancellationToken.None;

                    this.CachedTagTrees = newTagTrees;
                    this.State          = context.State;

                    OnTagsChangedForBuffer(bufferToChanges, initialTags);
                }
            }
            private void ProcessContext(
                List <DocumentSnapshotSpan> spansToTag,
                ImmutableDictionary <ITextBuffer, TagSpanIntervalTree <TTag> > oldTagTrees,
                TaggerContext <TTag> context)
            {
                var buffersToTag = spansToTag.Select(dss => dss.SnapshotSpan.Snapshot.TextBuffer).ToSet();

                // Ignore any tag spans reported for any buffers we weren't interested in.
                var newTagsByBuffer = context.tagSpans.Where(ts => buffersToTag.Contains(ts.Span.Snapshot.TextBuffer))
                                      .ToLookup(t => t.Span.Snapshot.TextBuffer);

                var newTagTrees = ConvertToTagTrees(oldTagTrees, newTagsByBuffer, context._spansTagged);

                ProcessNewTagTrees(spansToTag, oldTagTrees, newTagTrees, context.State, context.CancellationToken);
            }
Exemplo n.º 11
0
            private async Task RecomputeTagsAsync(
                object oldState,
                SnapshotPoint?caretPosition,
                TextChangeRange?textChangeRange,
                List <DocumentSnapshotSpan> spansToTag,
                ImmutableDictionary <ITextBuffer, TagSpanIntervalTree <TTag> > oldTagTrees,
                CancellationToken cancellationToken)
            {
                cancellationToken.ThrowIfCancellationRequested();

                var context = new TaggerContext <TTag>(
                    oldState, spansToTag, caretPosition, textChangeRange, oldTagTrees, cancellationToken);

                await ProduceTagsAsync(context).ConfigureAwait(false);

                ProcessContext(spansToTag, oldTagTrees, context);
            }
            public TagSpanIntervalTree <TTag> GetAccurateTagIntervalTreeForBuffer(
                ITextBuffer buffer,
                CancellationToken cancellationToken
                )
            {
                _workQueue.AssertIsForeground();

                if (!this.UpToDate)
                {
                    // We're not up to date.  That means we have an outstanding update that we're
                    // currently processing.  Unfortunately we have no way to track the progress of
                    // that update (i.e. a Task).  Also, even if we did, we'd have the problem that
                    // we have delays coded into the normal tagging process.  So waiting on that Task
                    // could take a long time.
                    //
                    // So, instead, we just cancel whatever work we're currently doing, and we just
                    // compute the results synchronously in this call.

                    // We can cancel any background computations currently happening
                    _workQueue.CancelCurrentWork();

                    var spansToTag = GetSpansAndDocumentsToTag();

                    // Safe to access _cachedTagTrees here.  We're on the UI thread.
                    var oldTagTrees = this.CachedTagTrees;
                    var caretPoint  = _dataSource.GetCaretPoint(_textViewOpt, _subjectBuffer);

                    var context = new TaggerContext <TTag>(
                        this.State,
                        spansToTag,
                        caretPoint,
                        this.AccumulatedTextChanges,
                        oldTagTrees,
                        cancellationToken
                        );

                    ProduceTagsSynchronously(context);

                    ProcessContext(oldTagTrees, context, initialTags: false);
                }

                Debug.Assert(this.UpToDate);
                this.CachedTagTrees.TryGetValue(buffer, out var tags);
                return(tags);
            }
Exemplo n.º 13
0
 protected virtual Task ProduceTagsAsync(TaggerContext <TTag> context, DocumentSnapshotSpan spanToTag, int?caretPosition)
 {
     return(Task.CompletedTask);
 }
Exemplo n.º 14
0
 internal Task ProduceTagsAsync_ForTestingPurposesOnly(TaggerContext <TTag> context)
 {
     return(ProduceTagsAsync(context));
 }
 protected virtual Task ProduceTagsAsync(TaggerContext <TTag> context, DocumentSnapshotSpan spanToTag, int?caretPosition)
 {
     return(SpecializedTasks.EmptyTask);
 }
            /// <summary>
            /// Called on the foreground thread.  Passed a boolean to say if we're computing the
            /// initial set of tags or not.  If we're computing the initial set of tags, we lower
            /// all our delays so that we can get results to the screen as quickly as possible.
            /// <para/> This gives a good experience when a document is opened as the document appears complete almost
            /// immediately.  Once open though, our normal delays come into play so as to not cause a flashy experience.
            /// </summary>
            /// <param name="highPriority">
            /// If this tagging request should be processed as quickly as possible with no extra delays added for it.
            /// </param>
            private async Task RecomputeTagsAsync(bool highPriority, CancellationToken cancellationToken)
            {
                // if we're tagging documents that are not visible, then introduce a long delay so that we avoid
                // consuming machine resources on work the user isn't likely to see.  ConfigureAwait(true) so that if
                // we're on the UI thread that we stay on it.
                //
                // Don't do this for explicit high priority requests as the caller wants the UI updated as quickly as
                // possible.
                if (!highPriority)
                {
                    await _visibilityTracker.DelayWhileNonVisibleAsync(
                        _dataSource.ThreadingContext, _subjectBuffer, DelayTimeSpan.NonFocus, cancellationToken).ConfigureAwait(true);
                }

                await _dataSource.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

                _dataSource.ThreadingContext.ThrowIfNotOnUIThread();
                if (cancellationToken.IsCancellationRequested)
                {
                    return;
                }

                using (Logger.LogBlock(FunctionId.Tagger_TagSource_RecomputeTags, cancellationToken))
                {
                    // Make a copy of all the data we need while we're on the foreground.  Then switch to a threadpool
                    // thread to do the computation. Finally, once new tags have been computed, then we update our state
                    // again on the foreground.
                    var spansToTag    = GetSpansAndDocumentsToTag();
                    var caretPosition = _dataSource.GetCaretPoint(_textView, _subjectBuffer);
                    var oldTagTrees   = this.CachedTagTrees;
                    var oldState      = this.State;

                    var textChangeRange = this.AccumulatedTextChanges;
                    this.AccumulatedTextChanges = null;

                    // Technically not necessary since we ConfigureAwait(false) right above this.  But we want to ensure
                    // we're always moving to the threadpool here in case the above code ever changes.
                    await TaskScheduler.Default;

                    cancellationToken.ThrowIfCancellationRequested();

                    // Create a context to store pass the information along and collect the results.
                    var context = new TaggerContext <TTag>(
                        oldState, spansToTag, caretPosition, textChangeRange, oldTagTrees);
                    await ProduceTagsAsync(context, cancellationToken).ConfigureAwait(false);

                    cancellationToken.ThrowIfCancellationRequested();

                    // Process the result to determine what changed.
                    var newTagTrees     = ComputeNewTagTrees(oldTagTrees, context);
                    var bufferToChanges = ProcessNewTagTrees(spansToTag, oldTagTrees, newTagTrees, cancellationToken);

                    // Then switch back to the UI thread to update our state and kick off the work to notify the editor.
                    await _dataSource.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

                    // Once we assign our state, we're uncancellable.  We must report the changed information
                    // to the editor.  The only case where it's ok not to is if the tagger itself is disposed.
                    cancellationToken = CancellationToken.None;

                    this.CachedTagTrees = newTagTrees;
                    this.State          = context.State;

                    OnTagsChangedForBuffer(bufferToChanges, highPriority);

                    // Once we've computed tags, pause ourselves if we're no longer visible.  That way we don't consume any
                    // machine resources that the user won't even notice.
                    PauseIfNotVisible();
                }
            }
 internal Task ProduceTagsAsync(TaggerContext <TTag> context)
 => _provider.ProduceTagsAsync(context);