private DiffResult ComputeDifference( ITextSnapshot snapshot, TagSpanIntervalTree <TTag> latestSpans, TagSpanIntervalTree <TTag> previousSpans) { return(Difference(latestSpans.GetSpans(snapshot), previousSpans.GetSpans(snapshot), _dataSource.TagComparer)); }
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)); }
private void GetCachedInfo(out SnapshotSpan?cachedTaggedSpan, out TagSpanIntervalTree <IClassificationTag>?cachedTags) { lock (_gate) { cachedTaggedSpan = _cachedTaggedSpan; cachedTags = _cachedTags; } }
private NormalizedSnapshotSpanCollection ComputeDifference( ITextSnapshot snapshot, TagSpanIntervalTree <TTag> latestSpans, TagSpanIntervalTree <TTag> previousSpans) { return(new NormalizedSnapshotSpanCollection( Difference(latestSpans.GetSpans(snapshot), previousSpans.GetSpans(snapshot), _dataSource.TagComparer))); }
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)); }
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)); }
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); }
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); }
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)));
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(); } }
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)); }
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));
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)); }