Beispiel #1
0
 private DiffResult ComputeDifference(
     ITextSnapshot snapshot,
     TagSpanIntervalTree <TTag> latestSpans,
     TagSpanIntervalTree <TTag> previousSpans)
 {
     return(Difference(latestSpans.GetSpans(snapshot), previousSpans.GetSpans(snapshot), _dataSource.TagComparer));
 }
Beispiel #2
0
            private TagSpanIntervalTree <TTag> GetTagTree(ITextSnapshot snapshot, ImmutableDictionary <ITextBuffer, TagSpanIntervalTree <TTag> > tagTrees)
            {
                TagSpanIntervalTree <TTag> tagTree = null;

                return(tagTrees.TryGetValue(snapshot.TextBuffer, out tagTree)
                    ? tagTree
                    : new TagSpanIntervalTree <TTag>(snapshot.TextBuffer, _dataSource.SpanTrackingMode));
            }
Beispiel #3
0
 private void GetCachedInfo(out SnapshotSpan?cachedTaggedSpan, out TagSpanIntervalTree <IClassificationTag>?cachedTags)
 {
     lock (_gate)
     {
         cachedTaggedSpan = _cachedTaggedSpan;
         cachedTags       = _cachedTags;
     }
 }
Beispiel #4
0
 private NormalizedSnapshotSpanCollection ComputeDifference(
     ITextSnapshot snapshot,
     TagSpanIntervalTree <TTag> latestSpans,
     TagSpanIntervalTree <TTag> previousSpans)
 {
     return(new NormalizedSnapshotSpanCollection(
                Difference(latestSpans.GetSpans(snapshot), previousSpans.GetSpans(snapshot), _dataSource.TagComparer)));
 }
Beispiel #5
0
            public IEnumerable <ITagSpan <IClassificationTag> > GetAllTags(NormalizedSnapshotSpanCollection spans, CancellationToken cancellationToken)
            {
                this.AssertIsForeground();
                if (spans.Count == 0)
                {
                    return(Array.Empty <ITagSpan <IClassificationTag> >());
                }

                var firstSpan = spans.First();
                var snapshot  = firstSpan.Snapshot;

                Debug.Assert(snapshot.TextBuffer == _subjectBuffer);

                // We want to classify from the start of the first requested span to the end of the
                // last requested span.
                var spanToTag = new SnapshotSpan(snapshot,
                                                 Span.FromBounds(spans.First().Start, spans.Last().End));

                // We don't need to actually classify if what we're being asked for is a subspan
                // of the last classification we performed.
                var cachedTaggedSpan = this.CachedTaggedSpan;
                var canReuseCache    =
                    cachedTaggedSpan?.Snapshot == snapshot &&
                    cachedTaggedSpan.Value.Contains(spanToTag);

                if (!canReuseCache)
                {
                    // Our cache is not there, or is out of date.  We need to compute the up to date
                    // results.

                    var document = snapshot.GetOpenDocumentInCurrentContextWithChanges();
                    if (document == null)
                    {
                        return(Array.Empty <ITagSpan <IClassificationTag> >());
                    }

                    _classificationService = _classificationService ?? document.Project.LanguageServices.GetService <IEditorClassificationService>();

                    var context = new TaggerContext <IClassificationTag>(document, snapshot, cancellationToken: cancellationToken);

                    var task = SemanticClassificationUtilities.ProduceTagsAsync(
                        context, new DocumentSnapshotSpan(document, spanToTag),
                        _classificationService, _owner._typeMap);
                    task.Wait(cancellationToken);

                    CachedTaggedSpan = spanToTag;
                    CachedTags       = new TagSpanIntervalTree <IClassificationTag>(snapshot.TextBuffer, SpanTrackingMode.EdgeExclusive, context.tagSpans);
                }

                if (this.CachedTags == null)
                {
                    return(Array.Empty <ITagSpan <IClassificationTag> >());
                }

                return(this.CachedTags.GetIntersectingTagSpans(spans));
            }
Beispiel #6
0
            private bool TryStealTagsFromRelatedTagSource(TextContentChangedEventArgs e)
            {
                // see bug 778731
#if INTERACTIVE
                // If we don't have a way to find the related buffer, we're done immediately
                if (bufferToRelatedTagSource == null)
                {
                    return(false);
                }

                // We can only steal tags if we know where the edit came from, so do we?
                var editTag = e.EditTag as RestoreHistoryEditTag;

                if (editTag == null)
                {
                    return(false);
                }

                var originalSpan = editTag.OriginalSpan;

                var relatedTagSource = bufferToRelatedTagSource(originalSpan.Snapshot.TextBuffer);
                if (relatedTagSource == null)
                {
                    return(false);
                }

                // Reading the other tag source's cached tags is safe, since this field is allowed to be
                // accessed from multiple threads and is immutable. We still need to have a local copy
                // though to play it safe and be a good citizen (well, as good as a citizen that's about
                // to steal something can be...)
                var relatedCachedTags = relatedTagSource.cachedTags;
                TagSpanIntervalTree <TTag> relatedIntervalTree;

                if (!relatedCachedTags.TryGetValue(originalSpan.Snapshot.TextBuffer, out relatedIntervalTree))
                {
                    return(false);
                }

                // Excellent! Let's build a new interval tree with these tags mapped to our buffer
                // instead
                var tagsForThisBuffer = from tagSpan in relatedIntervalTree.GetSpans(originalSpan.Snapshot)
                                        where tagSpan.Span.IntersectsWith(originalSpan)
                                        let snapshotSpan = new SnapshotSpan(e.After, tagSpan.SpanStart - originalSpan.Start, tagSpan.Span.Length)
                                                           select new TagSpan <TTag>(snapshotSpan, tagSpan.Tag);

                var intervalTreeForThisBuffer = new TagSpanIntervalTree <TTag>(e.After.TextBuffer, relatedIntervalTree.SpanTrackingMode, tagsForThisBuffer);

                // Update our cached tags
                UpdateCachedTagsForBuffer(e.After, intervalTreeForThisBuffer);
                return(true);
#else
                return(false);
#endif
            }
            public IEnumerable <ITagSpan <IClassificationTag> > GetAllTags(NormalizedSnapshotSpanCollection spans, CancellationToken cancellationToken)
            {
                this.AssertIsForeground();
                if (spans.Count == 0)
                {
                    return(Array.Empty <ITagSpan <IClassificationTag> >());
                }

                var firstSpan = spans.First();
                var snapshot  = firstSpan.Snapshot;

                Debug.Assert(snapshot.TextBuffer == _subjectBuffer);

                var document = snapshot.GetOpenDocumentInCurrentContextWithChanges();

                if (document == null)
                {
                    return(Array.Empty <ITagSpan <IClassificationTag> >());
                }

                // We want to classify from the start of the first requested span to the end of the
                // last requested span.
                var spanToTag = new SnapshotSpan(snapshot, Span.FromBounds(spans.First().Start, spans.Last().End));

                GetCachedInfo(out var cachedTaggedSpan, out var cachedTags);

                // We don't need to actually classify if what we're being asked for is a subspan
                // of the last classification we performed.
                var canReuseCache =
                    cachedTaggedSpan?.Snapshot == snapshot &&
                    cachedTaggedSpan.Value.Contains(spanToTag);

                if (!canReuseCache)
                {
                    // Our cache is not there, or is out of date.  We need to compute the up to date results.
                    var context = new TaggerContext <IClassificationTag>(document, snapshot);
                    this.ThreadingContext.JoinableTaskFactory.Run(
                        () => ProduceTagsAsync(context, new DocumentSnapshotSpan(document, spanToTag), _owner._typeMap, cancellationToken));

                    cachedTaggedSpan = spanToTag;
                    cachedTags       = new TagSpanIntervalTree <IClassificationTag>(snapshot.TextBuffer, SpanTrackingMode.EdgeExclusive, context.tagSpans);

                    lock (_gate)
                    {
                        _cachedTaggedSpan = cachedTaggedSpan;
                        _cachedTags       = cachedTags;
                    }
                }

                return(cachedTags == null
                    ? Array.Empty <ITagSpan <IClassificationTag> >()
                    : cachedTags.GetIntersectingTagSpans(spans));
            }
            private IEnumerable <ITagSpan <TTag> > GetNonIntersectingTagSpans(
                IEnumerable <SnapshotSpan> spansToInvalidate,
                TagSpanIntervalTree <TTag> oldTagTree
                )
            {
                var snapshot = spansToInvalidate.First().Snapshot;

                var tagSpansToInvalidate = new List <ITagSpan <TTag> >(
                    spansToInvalidate.SelectMany(ss => oldTagTree.GetIntersectingSpans(ss))
                    );

                return(oldTagTree.GetSpans(snapshot).Except(tagSpansToInvalidate, _tagSpanComparer));
            }
Beispiel #9
0
            private static IEnumerable <ITagSpan <TTag> > GetTagsForSmallNumberOfSpans(
                NormalizedSnapshotSpanCollection requestedSpans,
                TagSpanIntervalTree <TTag> tags)
            {
                var result = new List <ITagSpan <TTag> >();

                foreach (var s in requestedSpans)
                {
                    result.AddRange(tags.GetIntersectingSpans(s));
                }

                return(result);
            }
            private void RemoveTagsThatIntersectEdit(TextContentChangedEventArgs e)
            {
                if (!e.Changes.Any())
                {
                    return;
                }

                // We might be able to steal the cached tags from another tag source
                if (TryStealTagsFromRelatedTagSource(e))
                {
                    return;
                }

                var buffer = e.After.TextBuffer;

                if (!this.CachedTagTrees.TryGetValue(buffer, out var treeForBuffer))
                {
                    return;
                }

                var tagsToRemove = e.Changes.SelectMany(
                    c => treeForBuffer.GetIntersectingSpans(new SnapshotSpan(e.After, c.NewSpan))
                    );

                if (!tagsToRemove.Any())
                {
                    return;
                }

                var allTags    = treeForBuffer.GetSpans(e.After).ToList();
                var newTagTree = new TagSpanIntervalTree <TTag>(
                    buffer,
                    treeForBuffer.SpanTrackingMode,
                    allTags.Except(tagsToRemove, _tagSpanComparer)
                    );

                var snapshot = e.After;

                this.CachedTagTrees = this.CachedTagTrees.SetItem(snapshot.TextBuffer, newTagTree);

                // Not sure why we are diffing when we already have tagsToRemove. is it due to _tagSpanComparer might return
                // different result than GetIntersectingSpans?
                //
                // treeForBuffer basically points to oldTagTrees. case where oldTagTrees not exist is already taken cared by
                // CachedTagTrees.TryGetValue.
                var difference = ComputeDifference(snapshot, newTagTree, treeForBuffer);

                RaiseTagsChanged(snapshot.TextBuffer, difference);
            }
Beispiel #11
0
            private void RemoveTagsThatIntersectEdit(TextContentChangedEventArgs e)
            {
                if (!e.Changes.Any())
                {
                    return;
                }

                // We might be able to steal the cached tags from another tag source
                if (TryStealTagsFromRelatedTagSource(e))
                {
                    return;
                }

                var buffer = e.After.TextBuffer;
                TagSpanIntervalTree <TTag> treeForBuffer;

                if (!this.CachedTagTrees.TryGetValue(buffer, out treeForBuffer))
                {
                    return;
                }

                var tagsToRemove = e.Changes.SelectMany(c => treeForBuffer.GetIntersectingSpans(new SnapshotSpan(e.After, c.NewSpan)));

                if (!tagsToRemove.Any())
                {
                    return;
                }

                var allTags    = treeForBuffer.GetSpans(e.After).ToList();
                var newTagTree = new TagSpanIntervalTree <TTag>(
                    buffer,
                    treeForBuffer.SpanTrackingMode,
                    allTags.Except(tagsToRemove, _tagSpanComparer));

                var snapshot = e.After;

                var oldTagTrees = this.CachedTagTrees;

                this.CachedTagTrees = oldTagTrees.SetItem(snapshot.TextBuffer, newTagTree);

                // Grab our old tags. We might not have any, so in this case we'll just pretend it's
                // empty
                var oldTagTree = GetTagTree(snapshot, oldTagTrees);

                var difference = ComputeDifference(snapshot, newTagTree, oldTagTree);

                RaiseTagsChanged(snapshot.TextBuffer, difference);
            }
Beispiel #12
0
            private void OnEventSourceChanged(object?sender, TaggerEventArgs _)
            {
                lock (_gate)
                {
                    _cachedTags       = null;
                    _cachedTaggedSpan = null;
                }

                // Note: we explicitly do *not* call into TagsChanged here.  This type exists only for the copy/paste
                // scenario, and in the case the editor always calls into us for the span in question, ignoring
                // TagsChanged, as per DPugh:
                //
                //    For rich text copy, we always call the buffer classifier to get the classifications of the copied
                //    text. It ignores any tags changed events.
                //
                // It's important that we do not call TagsChanged here as the only thing we could do is notify that the
                // entire doc is changed, and that incurs a heavy cost for the editor reacting to that notification.
            }
            public IEnumerable <ITagSpan <IClassificationTag> > GetAllTags(NormalizedSnapshotSpanCollection spans, CancellationToken cancellationToken)
            {
                this.AssertIsForeground();
                if (spans.Count == 0)
                {
                    return(Array.Empty <ITagSpan <IClassificationTag> >());
                }

                var firstSpan = spans.First();
                var snapshot  = firstSpan.Snapshot;

                Debug.Assert(snapshot.TextBuffer == _subjectBuffer);

                var cachedSnapshot = this.CachedSnapshot;

                if (snapshot != cachedSnapshot)
                {
                    // Our cache is not there, or is out of date.  We need to compute the up to date
                    // results.

                    var document = snapshot.GetOpenDocumentInCurrentContextWithChanges();
                    if (document == null)
                    {
                        return(Array.Empty <ITagSpan <IClassificationTag> >());
                    }

                    _classificationService = _classificationService ?? document.Project.LanguageServices.GetService <IEditorClassificationService>();

                    var context   = new TaggerContext <IClassificationTag>(document, snapshot, cancellationToken: cancellationToken);
                    var spanToTag = new DocumentSnapshotSpan(document, snapshot.GetFullSpan());
                    var task      = SemanticClassificationUtilities.ProduceTagsAsync(context, spanToTag, _classificationService, _owner._typeMap);
                    task.Wait(cancellationToken);

                    CachedSnapshot = snapshot;
                    CachedTags     = new TagSpanIntervalTree <IClassificationTag>(snapshot.TextBuffer, SpanTrackingMode.EdgeExclusive, context.tagSpans);
                }

                if (this.CachedTags == null)
                {
                    return(Array.Empty <ITagSpan <IClassificationTag> >());
                }

                return(this.CachedTags.GetIntersectingTagSpans(spans));
            }
            private static DiffResult ComputeDifference(
                ITextSnapshot snapshot,
                TagSpanIntervalTree <TTag> latestTree,
                TagSpanIntervalTree <TTag> previousTree)
            {
                var latestSpans   = latestTree.GetSpans(snapshot);
                var previousSpans = previousTree.GetSpans(snapshot);

                using var _1 = ArrayBuilder <SnapshotSpan> .GetInstance(out var added);

                using var _2 = ArrayBuilder <SnapshotSpan> .GetInstance(out var removed);

                using var latestEnumerator   = latestSpans.GetEnumerator();
                using var previousEnumerator = previousSpans.GetEnumerator();

                var latest   = NextOrNull(latestEnumerator);
                var previous = NextOrNull(previousEnumerator);

                while (latest != null && previous != null)
                {
                    var latestSpan   = latest.Span;
                    var previousSpan = previous.Span;

                    if (latestSpan.Start < previousSpan.Start)
                    {
                        added.Add(latestSpan);
                        latest = NextOrNull(latestEnumerator);
                    }
                    else if (previousSpan.Start < latestSpan.Start)
                    {
                        removed.Add(previousSpan);
                        previous = NextOrNull(previousEnumerator);
                    }
                    else
                    {
                        // If the starts are the same, but the ends are different, report the larger
                        // region to be conservative.
                        if (previousSpan.End > latestSpan.End)
                        {
                            removed.Add(previousSpan);
                            latest = NextOrNull(latestEnumerator);
                        }
                        else if (latestSpan.End > previousSpan.End)
                        {
                            added.Add(latestSpan);
                            previous = NextOrNull(previousEnumerator);
                        }
                        else
                        {
                            if (!EqualityComparer <TTag> .Default.Equals(latest.Tag, previous.Tag))
                            {
                                added.Add(latestSpan);
                            }

                            latest   = NextOrNull(latestEnumerator);
                            previous = NextOrNull(previousEnumerator);
                        }
                    }
                }

                while (latest != null)
                {
                    added.Add(latest.Span);
                    latest = NextOrNull(latestEnumerator);
                }

                while (previous != null)
                {
                    removed.Add(previous.Span);
                    previous = NextOrNull(previousEnumerator);
                }

                return(new DiffResult(new(added), new(removed)));
Beispiel #15
0
            private static IEnumerable <ITagSpan <TTag> > GetTagsForLargeNumberOfSpans(
                NormalizedSnapshotSpanCollection requestedSpans,
                TagSpanIntervalTree <TTag> tags)
            {
                // we are asked with bunch of spans. rather than asking same question again and again, ask once with big span
                // which will return superset of what we want. and then filter them out in O(m+n) cost.
                // m == number of requested spans, n = number of returned spans
                var mergedSpan = new SnapshotSpan(requestedSpans[0].Start, requestedSpans[requestedSpans.Count - 1].End);
                var result     = tags.GetIntersectingSpans(mergedSpan);

                int requestIndex = 0;

                var enumerator = result.GetEnumerator();

                try
                {
                    if (!enumerator.MoveNext())
                    {
                        return(SpecializedCollections.EmptyEnumerable <ITagSpan <TTag> >());
                    }

                    var hashSet = new HashSet <ITagSpan <TTag> >();
                    while (true)
                    {
                        var currentTag = enumerator.Current;

                        var currentRequestSpan = requestedSpans[requestIndex];
                        var currentTagSpan     = currentTag.Span;

                        if (currentRequestSpan.Start > currentTagSpan.End)
                        {
                            if (!enumerator.MoveNext())
                            {
                                break;
                            }
                        }
                        else if (currentTagSpan.Start > currentRequestSpan.End)
                        {
                            requestIndex++;

                            if (requestIndex >= requestedSpans.Count)
                            {
                                break;
                            }
                        }
                        else
                        {
                            if (currentTagSpan.Length > 0)
                            {
                                hashSet.Add(currentTag);
                            }

                            if (!enumerator.MoveNext())
                            {
                                break;
                            }
                        }
                    }

                    return(hashSet);
                }
                finally
                {
                    enumerator.Dispose();
                }
            }
Beispiel #16
0
            public IEnumerable <ITagSpan <IClassificationTag> > GetAllTags(NormalizedSnapshotSpanCollection spans, CancellationToken cancellationToken)
            {
                this.AssertIsForeground();
                if (spans.Count == 0)
                {
                    return(Array.Empty <ITagSpan <IClassificationTag> >());
                }

                var firstSpan = spans.First();
                var snapshot  = firstSpan.Snapshot;

                Debug.Assert(snapshot.TextBuffer == _subjectBuffer);

                var document = snapshot.GetOpenDocumentInCurrentContextWithChanges();

                if (document == null)
                {
                    return(Array.Empty <ITagSpan <IClassificationTag> >());
                }

                var classificationService = document.GetLanguageService <IClassificationService>();

                if (classificationService == null)
                {
                    return(Array.Empty <ITagSpan <IClassificationTag> >());
                }

                // We want to classify from the start of the first requested span to the end of the
                // last requested span.
                var spanToTag = new SnapshotSpan(snapshot, Span.FromBounds(spans.First().Start, spans.Last().End));

                GetCachedInfo(out var cachedTaggedSpan, out var cachedTags);

                // We don't need to actually classify if what we're being asked for is a subspan
                // of the last classification we performed.
                var canReuseCache =
                    cachedTaggedSpan?.Snapshot == snapshot &&
                    cachedTaggedSpan.Value.Contains(spanToTag);

                if (!canReuseCache)
                {
                    // Our cache is not there, or is out of date.  We need to compute the up to date results.
                    var context = new TaggerContext <IClassificationTag>(document, snapshot);
                    var options = _globalOptions.GetClassificationOptions(document.Project.Language);

                    ThreadingContext.JoinableTaskFactory.Run(async() =>
                    {
                        var snapshotSpan = new DocumentSnapshotSpan(document, spanToTag);

                        // When copying/pasting, ensure we have classifications fully computed for the requested spans
                        // for both semantic classifications and embedded lang classifications.
                        await ProduceTagsAsync(context, snapshotSpan, classificationService, options, ClassificationType.Semantic, cancellationToken).ConfigureAwait(false);
                        await ProduceTagsAsync(context, snapshotSpan, classificationService, options, ClassificationType.EmbeddedLanguage, cancellationToken).ConfigureAwait(false);
                    });

                    cachedTaggedSpan = spanToTag;
                    cachedTags       = new TagSpanIntervalTree <IClassificationTag>(snapshot.TextBuffer, SpanTrackingMode.EdgeExclusive, context.tagSpans);

                    lock (_gate)
                    {
                        _cachedTaggedSpan = cachedTaggedSpan;
                        _cachedTags       = cachedTags;
                    }
                }

                return(cachedTags == null
                    ? Array.Empty <ITagSpan <IClassificationTag> >()
                    : cachedTags.GetIntersectingTagSpans(spans));
            }
Beispiel #17
0
            private static DiffResult ComputeDifference(
                ITextSnapshot snapshot,
                TagSpanIntervalTree <TTag> latestTree,
                TagSpanIntervalTree <TTag> previousTree)
            {
                var latestSpans   = latestTree.GetSpans(snapshot);
                var previousSpans = previousTree.GetSpans(snapshot);

                using var addedPool          = SharedPools.Default <List <SnapshotSpan> >().GetPooledObject();
                using var removedPool        = SharedPools.Default <List <SnapshotSpan> >().GetPooledObject();
                using var latestEnumerator   = latestSpans.GetEnumerator();
                using var previousEnumerator = previousSpans.GetEnumerator();

                var added   = addedPool.Object;
                var removed = removedPool.Object;

                var latest   = NextOrDefault(latestEnumerator);
                var previous = NextOrDefault(previousEnumerator);

                while (latest != null && previous != null)
                {
                    var latestSpan   = latest.Span;
                    var previousSpan = previous.Span;

                    if (latestSpan.Start < previousSpan.Start)
                    {
                        added.Add(latestSpan);
                        latest = NextOrDefault(latestEnumerator);
                    }
                    else if (previousSpan.Start < latestSpan.Start)
                    {
                        removed.Add(previousSpan);
                        previous = NextOrDefault(previousEnumerator);
                    }
                    else
                    {
                        // If the starts are the same, but the ends are different, report the larger
                        // region to be conservative.
                        if (previousSpan.End > latestSpan.End)
                        {
                            removed.Add(previousSpan);
                            latest = NextOrDefault(latestEnumerator);
                        }
                        else if (latestSpan.End > previousSpan.End)
                        {
                            added.Add(latestSpan);
                            previous = NextOrDefault(previousEnumerator);
                        }
                        else
                        {
                            if (!EqualityComparer <TTag> .Default.Equals(latest.Tag, previous.Tag))
                            {
                                added.Add(latestSpan);
                            }

                            latest   = NextOrDefault(latestEnumerator);
                            previous = NextOrDefault(previousEnumerator);
                        }
                    }
                }

                while (latest != null)
                {
                    added.Add(latest.Span);
                    latest = NextOrDefault(latestEnumerator);
                }

                while (previous != null)
                {
                    removed.Add(previous.Span);
                    previous = NextOrDefault(previousEnumerator);
                }

                return(new DiffResult(added, removed));
Beispiel #18
0
 private static IEnumerable <ITagSpan <TTag> > GetIntersectingTagSpans(NormalizedSnapshotSpanCollection requestedSpans, TagSpanIntervalTree <TTag> tags)
 {
     // Special case the case where there is only one requested span.  In that case, we don't
     // need to allocate any intermediate collections
     return(requestedSpans.Count == 1
         ? tags.GetIntersectingSpans(requestedSpans[0])
         : requestedSpans.Count < MaxNumberOfRequestedSpans
             ? GetTagsForSmallNumberOfSpans(requestedSpans, tags)
             : GetTagsForLargeNumberOfSpans(requestedSpans, tags));
 }