protected override async Task RecomputeTagsAsync( SnapshotPoint?caret, TextChangeRange?range, IEnumerable <DocumentSnapshotSpan> spansToCompute, CancellationToken cancellationToken) { this.WorkQueue.AssertIsBackground(); // we should have only one var tuple = spansToCompute.Single(); // split data var span = tuple.SnapshotSpan; var document = tuple.Document; if (document == null) { // given span is not part of our workspace, let base tag source handle this case. await base.RecomputeTagsAsync(caret, range, spansToCompute, cancellationToken).ConfigureAwait(false); return; } var newVersion = await document.Project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false); await RecomputeTagsAsync(range, spansToCompute, document, span.Snapshot, _lastSemanticVersion, newVersion, cancellationToken).ConfigureAwait(false); // this is only place where the version is updated _lastSemanticVersion = newVersion; }
private async Task RecomputeTagsAsync( object oldState, SnapshotPoint?caretPosition, TextChangeRange?textChangeRange, ImmutableArray <DocumentSnapshotSpan> spansToTag, ImmutableDictionary <ITextBuffer, TagSpanIntervalTree <TTag> > oldTagTrees, bool initialTags, CancellationToken cancellationToken ) { cancellationToken.ThrowIfCancellationRequested(); var context = new TaggerContext <TTag>( oldState, spansToTag, caretPosition, textChangeRange, oldTagTrees, cancellationToken ); await ProduceTagsAsync(context).ConfigureAwait(false); ProcessContext(oldTagTrees, context, initialTags); }
protected ProducerPopulatedTagSource( ITextBuffer subjectBuffer, ITagProducer <TTag> tagProducer, ITaggerEventSource eventSource, IAsynchronousOperationListener asyncListener, IForegroundNotificationService notificationService, bool removeTagsThatIntersectEdits, SpanTrackingMode spanTrackingMode, Func <ITextBuffer, ProducerPopulatedTagSource <TTag> > bufferToRelatedTagSource = null) : base(subjectBuffer, notificationService, asyncListener) { if (spanTrackingMode == SpanTrackingMode.Custom) { throw new ArgumentException("SpanTrackingMode.Custom not allowed.", "spanTrackingMode"); } _tagProducer = tagProducer; _removeTagsThatIntersectEdits = removeTagsThatIntersectEdits; _spanTrackingMode = spanTrackingMode; _cachedTags = ImmutableDictionary.Create <ITextBuffer, TagSpanIntervalTree <TTag> >(); _eventSource = eventSource; _bufferToRelatedTagSource = bufferToRelatedTagSource; _accumulatedTextChanges = null; AttachEventHandlersAndStart(); }
internal TaggerContext( Document document, ITextSnapshot snapshot, SnapshotPoint?caretPosition = null, TextChangeRange?textChangeRange = null) : this(state : null, ImmutableArray.Create(new DocumentSnapshotSpan(document, snapshot.GetFullSpan())), caretPosition, textChangeRange, existingTags : null) { }
// For testing only. internal TaggerContext( Document document, ITextSnapshot snapshot, SnapshotPoint?caretPosition = null, TextChangeRange?textChangeRange = null, CancellationToken cancellationToken = default(CancellationToken)) : this(null, new[] { new DocumentSnapshotSpan(document, new SnapshotSpan(snapshot, 0, snapshot.Length)) }, caretPosition, textChangeRange, null, cancellationToken) { }
// For testing only. internal TaggerContext( Document document, ITextSnapshot snapshot, SnapshotPoint?caretPosition = null, TextChangeRange?textChangeRange = null, CancellationToken cancellationToken = default) : this(null, ImmutableArray.Create(new DocumentSnapshotSpan(document, snapshot.GetFullSpan())), caretPosition, textChangeRange, null, cancellationToken) { }
private void AccumulateTextChanges(TextContentChangedEventArgs contentChanged) { var textChangeRanges = contentChanged.Changes.Select(c => new TextChangeRange(new TextSpan(c.OldSpan.Start, c.OldSpan.Length), c.NewLength)).ToList(); lock (_cachedTagsGate) { _accumulatedTextChanges = _accumulatedTextChanges.Accumulate(textChangeRanges); } }
private static TextChangeRange GetChangeRanges(ITextImageVersion oldVersion, ITextImageVersion newVersion, bool forward) { TextChangeRange?range = null; var iterator = GetMultipleVersionTextChanges(oldVersion, newVersion, forward); foreach (var changes in forward ? iterator : iterator.Reverse()) { range = range.Accumulate(changes); } RoslynDebug.Assert(range.HasValue); return(range.Value); }
private TextChangeRange GetChangeRanges(ITextVersion oldVersion, ITextVersion newVersion, bool forward) { TextChangeRange?range = null; var iterator = GetMultipleVersionTextChanges(oldVersion, newVersion, forward); foreach (var changes in forward ? iterator : iterator.Reverse()) { range = range.Accumulate(changes); } Contract.Requires(range.HasValue); return(range.Value); }
internal TaggerContext( object state, ImmutableArray <DocumentSnapshotSpan> spansToTag, SnapshotPoint?caretPosition, TextChangeRange?textChangeRange, ImmutableDictionary <ITextBuffer, TagSpanIntervalTree <TTag> > existingTags) { this.State = state; this.SpansToTag = spansToTag; this.CaretPosition = caretPosition; this.TextChangeRange = textChangeRange; _spansTagged = spansToTag; _existingTags = existingTags; }
protected virtual async Task RecomputeTagsAsync( SnapshotPoint?caretPosition, TextChangeRange?textChangeRange, IEnumerable <DocumentSnapshotSpan> spansToCompute, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var tagSpans = spansToCompute.IsEmpty() ? SpecializedCollections.EmptyEnumerable <ITagSpan <TTag> >() : await _tagProducer.ProduceTagsAsync(spansToCompute, caretPosition, cancellationToken).ConfigureAwait(false); var map = ConvertToTagTree(tagSpans, spansToCompute); ProcessNewTags(spansToCompute, textChangeRange, map); }
private void AccumulateTextChanges(TextContentChangedEventArgs contentChanged) { var contentChanges = contentChanged.Changes; var count = contentChanges.Count; switch (count) { case 0: return; case 1: // PERF: Optimize for the simple case of typing on a line. { var c = contentChanges[0]; var textChangeRange = new TextChangeRange(new TextSpan(c.OldSpan.Start, c.OldSpan.Length), c.NewLength); lock (_cachedTagsGate) { if (_accumulatedTextChanges == null) { _accumulatedTextChanges = textChangeRange; } else { _accumulatedTextChanges = _accumulatedTextChanges.Accumulate(SpecializedCollections.SingletonEnumerable(textChangeRange)); } } } break; default: var textChangeRanges = new TextChangeRange[count]; for (int i = 0; i < count; i++) { var c = contentChanges[i]; textChangeRanges[i] = new TextChangeRange(new TextSpan(c.OldSpan.Start, c.OldSpan.Length), c.NewLength); } lock (_cachedTagsGate) { _accumulatedTextChanges = _accumulatedTextChanges.Accumulate(textChangeRanges); } break; } }
protected ProducerPopulatedTagSource( ITextBuffer subjectBuffer, ITagProducer <TTag> tagProducer, ITaggerEventSource eventSource, IAsynchronousOperationListener asyncListener, IForegroundNotificationService notificationService, bool removeTagsThatIntersectEdits, Func <ITextBuffer, ProducerPopulatedTagSource <TTag> > bufferToRelatedTagSource = null) : base(subjectBuffer, notificationService, asyncListener) { _tagProducer = tagProducer; _removeTagsThatIntersectEdits = removeTagsThatIntersectEdits; _cachedTags = ImmutableDictionary.Create <ITextBuffer, TagSpanIntervalTree <TTag> >(); _eventSource = eventSource; _bufferToRelatedTagSource = bufferToRelatedTagSource; _accumulatedTextChanges = null; AttachEventHandlersAndStart(); }
public static TextChangeRange?Accumulate(this TextChangeRange?accumulatedTextChangeSoFar, IEnumerable <TextChangeRange> changesInNextVersion) { if (!changesInNextVersion.Any()) { return(accumulatedTextChangeSoFar); } // get encompassing text change and accumulate it once. // we could apply each one individually like we do in SyntaxDiff::ComputeSpansInNew by calculating delta // between each change in changesInNextVersion which is already sorted in its textual position ascending order. // but end result will be same as just applying it once with encompassed text change range. var newChange = TextChangeRange.Collapse(changesInNextVersion); // no previous accumulated change, return the new value. if (accumulatedTextChangeSoFar == null) { return(newChange); } // set initial value from the old one. var currentStart = accumulatedTextChangeSoFar.Value.Span.Start; var currentOldEnd = accumulatedTextChangeSoFar.Value.Span.End; var currentNewEnd = accumulatedTextChangeSoFar.Value.Span.Start + accumulatedTextChangeSoFar.Value.NewLength; // this is a port from // csharp\rad\Text\SourceText.cpp - CSourceText::OnChangeLineText // which accumulate text changes to one big text change that would encompass all changes // Merge incoming edit data with old edit data here. // RULES: // 1) position values are always associated with a buffer version. // 2) Comparison between position values is only allowed if their // buffer version is the same. // 3) newChange.Span.End and newChange.Span.Start + newChange.NewLength (both stored and incoming) // refer to the same position, but have different buffer versions. // 4) The incoming end position is associated with buffer versions // n-1 (old) and n(new). // 5) The stored end position BEFORE THIS EDIT is associated with // buffer versions 0 (old) and n-1 (new). // 6) The stored end position AFTER THIS EDIT should be associated // with buffer versions 0 (old) and n(new). // 7) To transform a position P from buffer version of x to y, apply // the delta between any position C(x) and C(y), ASSUMING that // both positions P and C are affected by all edits between // buffer versions x and y. // 8) The start position is relative to all buffer versions, because // it precedes all edits(by definition) // First, the start position. This one is easy, because it is not // complicated by buffer versioning -- it is always the "earliest" // of all incoming values. if (newChange.Span.Start < currentStart) { currentStart = newChange.Span.Start; } // Okay, now the end position. We must make a choice between the // stored end position and the incoming end position. Per rule #2, // we must use the stored NEW end and the incoming OLD end, both of // which are relative to buffer version n-1. if (currentNewEnd > newChange.Span.End) { // We have chosen to keep the stored end because it occurs past // the incoming edit. So, we need currentOldEnd and // currentNewEnd. Since currentOldEnd is already relative // to buffer 0, it is unmodified. Since currentNewEnd is // relative to buffer n-1 (and we need n), apply to it the delta // between the incoming end position values, which are n-1 and n. currentNewEnd = currentNewEnd + newChange.NewLength - newChange.Span.Length; } else { // We have chosen to use the incoming end because it occurs past // the stored edit. So, we need newChange.Span.End and (newChange.Span.Start + newChange.NewLength). // Since (newChange.Span.Start + newChange.NewLength) is already relative to buffer n, it is copied // unmodified. Since newChange.Span.End is relative to buffer n-1 (and // we need 0), apply to it the delta between the stored end // position values, which are relative to 0 and n-1. currentOldEnd = currentOldEnd + newChange.Span.End - currentNewEnd; currentNewEnd = newChange.Span.Start + newChange.NewLength; } return(new TextChangeRange(TextSpan.FromBounds(currentStart, currentOldEnd), currentNewEnd - currentStart)); }
private async Task RecomputeTagsAsync( TextChangeRange?range, IEnumerable <DocumentSnapshotSpan> spansToCompute, Document document, ITextSnapshot snapshot, VersionStamp oldVersion, VersionStamp newVersion, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); // there is no accumulate data, check whether we already reported this if (range == null) { // active file can be called twice for the same top level edit (the very last top level edit). // one from text edit event source and one from semantic change event source. // this make sure that when we are called to recompute due to semantic change event source, // we haven't already recompute it by text edits event source. // for opened files that are not active, it should be called by semantic change event source and recompute tags for whole file. if (newVersion != oldVersion) { // we didn't report this yet await base.RecomputeTagsAsync(null, range, spansToCompute, cancellationToken).ConfigureAwait(false); } return; } // there was top level edit, check whether that edit updated top level element var service = document.Project.LanguageServices.GetService <ISyntaxFactsService>(); if (service == null || newVersion != oldVersion) { // we have newer version, refresh whole buffer await base.RecomputeTagsAsync(null, range, spansToCompute, cancellationToken).ConfigureAwait(false); return; } // no top level edits, find out member that has changed var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var changedSpan = new TextSpan(range.Value.Span.Start, range.Value.NewLength); var member = service.GetContainingMemberDeclaration(root, range.Value.Span.Start); if (member == null || !member.FullSpan.Contains(changedSpan)) { // no top level edit, but out side a member has changed. for now, just re-colorize whole file await base.RecomputeTagsAsync(null, range, spansToCompute, cancellationToken).ConfigureAwait(false); return; } // perf optimization. // semantic classifier has two levels of perf optimization. // first, it will check whether all edits since the last update has happened locally. if it did, it will find the member // that contains the changes and only refresh that member // second, when it can do the first, it will check whether it can even get more perf improvement by using speculative semantic model. // if it can, it will re-adjust span so that it can use speculative binding. var refreshSpan = service.GetMemberBodySpanForSpeculativeBinding(member); var rangeToRecompute = new SnapshotSpan(snapshot, refreshSpan.Contains(changedSpan) ? refreshSpan.ToSpan() : member.FullSpan.ToSpan()); // re-colorize only a member await base.RecomputeTagsAsync( null, range, SpecializedCollections.SingletonEnumerable(new DocumentSnapshotSpan(document, rangeToRecompute)), cancellationToken).ConfigureAwait(false); }
protected override void ProcessNewTags(IEnumerable <DocumentSnapshotSpan> spansToCompute, TextChangeRange?oldTextChangeRange, ImmutableDictionary <ITextBuffer, TagSpanIntervalTree <AbstractNavigatableReferenceHighlightingTag> > newTags) { base.ProcessNewTags(spansToCompute, oldTextChangeRange, newTags); // remember last solution version we updated the tags var document = spansToCompute.First().Document; if (document != null) { _lastUpdateTagsSolutionVersion = document.Project.Solution.WorkspaceVersion; } }
protected virtual void ProcessNewTags( IEnumerable <DocumentSnapshotSpan> spansToCompute, TextChangeRange?oldTextChangeRange, ImmutableDictionary <ITextBuffer, TagSpanIntervalTree <TTag> > newTags) { using (Logger.LogBlock(FunctionId.Tagger_TagSource_ProcessNewTags, CancellationToken.None)) { var oldTags = _cachedTags; lock (_cachedTagsGate) { _cachedTags = newTags; // This can have a race, but it is not easy to remove due to our async and cancellation nature. // so here what we do is this, first we see whether the range we used to determine span to recompute is still same, if it is // then we reset the range so that we can start from scratch. if not (if there was another edit while us recomputing but // we didn't get cancelled), then we keep the existing accumulated text changes so that we at least don't miss recomputing // but at the same time, blindly recompute whole file. _accumulatedTextChanges = Nullable.Equals(_accumulatedTextChanges, oldTextChangeRange) ? null : _accumulatedTextChanges; } // Now diff the two resultant sets and fire off the notifications if (!HasTagsChangedListener) { return; } foreach (var latestBuffer in _cachedTags.Keys) { var snapshot = spansToCompute.First(s => s.SnapshotSpan.Snapshot.TextBuffer == latestBuffer).SnapshotSpan.Snapshot; if (oldTags.ContainsKey(latestBuffer)) { var difference = ComputeDifference(snapshot, _cachedTags[latestBuffer], oldTags[latestBuffer]); if (difference.Count > 0) { RaiseTagsChanged(latestBuffer, difference); } } else { // It's a new buffer, so report all spans are changed var allSpans = new NormalizedSnapshotSpanCollection(_cachedTags[latestBuffer].GetSpans(snapshot).Select(t => t.Span)); if (allSpans.Count > 0) { RaiseTagsChanged(latestBuffer, allSpans); } } } foreach (var oldBuffer in oldTags.Keys) { if (!_cachedTags.ContainsKey(oldBuffer)) { // This buffer disappeared, so let's notify that the old tags are gone var allSpans = new NormalizedSnapshotSpanCollection(oldTags[oldBuffer].GetSpans(oldBuffer.CurrentSnapshot).Select(t => t.Span)); if (allSpans.Count > 0) { RaiseTagsChanged(oldBuffer, allSpans); } } } } }