Exemplo n.º 1
0
        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);
            }
Exemplo n.º 3
0
        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();
        }
Exemplo n.º 4
0
 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)
 {
 }
Exemplo n.º 5
0
 // 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)
 {
 }
Exemplo n.º 6
0
 // 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)
 {
 }
Exemplo n.º 7
0
        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);
            }
        }
Exemplo n.º 8
0
            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);
            }
Exemplo n.º 10
0
        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;
        }
Exemplo n.º 11
0
        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);
        }
Exemplo n.º 12
0
        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;
            }
        }
Exemplo n.º 13
0
        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();
        }
Exemplo n.º 14
0
        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));
        }
Exemplo n.º 15
0
        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);
        }
Exemplo n.º 16
0
        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;
            }
        }
Exemplo n.º 17
0
        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);
                        }
                    }
                }
            }
        }