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)); }
/// <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); } }
private void ProduceTagsSynchronously(TaggerContext <TTag> context) { if (ShouldSkipTagProduction()) { return; } _dataSource.ProduceTagsSynchronously(context); }
/// <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)); } }
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)); }
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)); }
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); }
/// <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); }
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); }
protected virtual Task ProduceTagsAsync(TaggerContext <TTag> context, DocumentSnapshotSpan spanToTag, int?caretPosition) { return(Task.CompletedTask); }
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);