Beispiel #1
0
        void HandleTextReplacing(object sender, Core.Text.TextChangeEventArgs e)
        {
            var handler = TextChanged;

            if (handler != null)
            {
                lock (replaceLock) {
                    var oldText      = CurrentText;
                    var changes      = new Microsoft.CodeAnalysis.Text.TextChange[e.TextChanges.Count];
                    var changeRanges = new TextChangeRange[e.TextChanges.Count];
                    for (int i = 0; i < e.TextChanges.Count; ++i)
                    {
                        var c    = e.TextChanges[i];
                        var span = new TextSpan(c.Offset, c.RemovalLength);
                        changes[i]      = new Microsoft.CodeAnalysis.Text.TextChange(span, c.InsertedText.Text);
                        changeRanges[i] = new TextChangeRange(span, c.InsertionLength);
                    }
                    var newText = oldText.WithChanges(changes);
                    currentText = newText;
                    try {
                        handler(this, new Microsoft.CodeAnalysis.Text.TextChangeEventArgs(oldText, newText, changeRanges));
                    } catch (ArgumentException ae) {
                        LoggingService.LogWarning(ae.Message + " re opening " + editor.FileName + " as roslyn source text.");
                        workspace.InformDocumentClose(Id, editor.FileName);
                        Dispose();                          // 100% ensure that this object is disposed
                        if (workspace.GetDocument(Id) != null)
                        {
                            TypeSystemService.InformDocumentOpen(Id, editor);
                        }
                    } catch (Exception ex) {
                        LoggingService.LogError("Error while text replacing", ex);
                    }
                }
            }
        }
        void HandleTextReplacing(object sender, Core.Text.TextChangeEventArgs e)
        {
            var handler = TextChanged;

            if (handler != null)
            {
                lock (replaceLock) {
                    var oldText      = CurrentText;
                    var changes      = new Microsoft.CodeAnalysis.Text.TextChange[e.TextChanges.Count];
                    var changeRanges = new TextChangeRange[e.TextChanges.Count];
                    for (int i = 0; i < e.TextChanges.Count; ++i)
                    {
                        var c    = e.TextChanges[i];
                        var span = new TextSpan(c.Offset, c.RemovalLength);
                        changes[i]      = new Microsoft.CodeAnalysis.Text.TextChange(span, c.InsertedText.Text);
                        changeRanges[i] = new TextChangeRange(span, c.InsertionLength);
                    }
                    var newText = oldText.WithChanges(changes);
                    currentText = newText;
                    try {
                        handler(this, new Microsoft.CodeAnalysis.Text.TextChangeEventArgs(oldText, newText, changeRanges));
                    } catch (Exception ex) {
                        LoggingService.LogError("Error while text replacing", ex);
                    }
                }
            }
        }
Beispiel #3
0
        public void IncrementalParsingIsSameAsFullParsing_LeadingTriviaEdgeCase()
        {
            /* The formatting here is important,
             * we are trying to force a situation
             * where the spacing trivia for the leading
             * indentation is stored on the "ios" prefix
             * instead of the attribute name node we are
             * incrementally parsing
             */
            const string Full            = @"<Node ios:otherAttr=""foobar""
        android:attribute
        ios:alpha=""1"" />";
            const string IncrementalBase = @"<Node ios:otherAttr=""foobar""
        a
        ios:alpha=""1"" />";

            var full           = Parser.ParseText(Full);
            var incremental    = Parser.ParseText(IncrementalBase);
            var additionalText = "ndroid:attribute";

            // Complete the attribute one character at a time incrementally
            for (int i = 1; i <= additionalText.Length; i++)
            {
                var insertionIndex     = IncrementalBase.IndexOf(" a");
                var newIncrementalText = IncrementalBase.Replace(" a", " a" + additionalText.Substring(0, i));
                var change             = new TextChangeRange(new TextSpan(insertionIndex + i + 1, 0), 1);
                incremental = Parser.ParseIncremental(new StringBuffer(newIncrementalText), new[] { change }, incremental);
            }

            Assert.Equal(full.ToFullString(), incremental.ToFullString());
            AssertSameNodes(full, incremental);
        }
Beispiel #4
0
        private void RecordChange(TextChangeRange textChangeRange, SyntaxNodeOrToken removedNode, SyntaxNodeOrToken insertedNode)
        {
            if (_changes.Count > 0)
            {
                var last = _changes[_changes.Count - 1];
                if (last.Range.Span.End == textChangeRange.Span.Start)
                {
                    // merge changes...
                    last.OldNodes?.Enqueue(removedNode);
                    last.NewNodes?.Enqueue(insertedNode);
                    _changes[_changes.Count - 1] = new ChangeRecord(
                        new TextChangeRange(new TextSpan(last.Range.Span.Start, last.Range.Span.Length + textChangeRange.Span.Length), last.Range.NewLength + textChangeRange.NewLength),
                        last.OldNodes ?? CreateQueue(removedNode),
                        last.NewNodes ?? CreateQueue(insertedNode));
                    return;
                }

                Debug.Assert(textChangeRange.Span.Start >= last.Range.Span.End);
            }

            _changes.Add(new ChangeRecord(textChangeRange, CreateQueue(removedNode), CreateQueue(insertedNode)));

            // Local Functions
            Queue <SyntaxNodeOrToken> CreateQueue(SyntaxNodeOrToken nodeOrToken)
            {
                var queue = new Queue <SyntaxNodeOrToken>();

                queue.Enqueue(nodeOrToken);
                return(queue);
            }
        }
Beispiel #5
0
            private void AccumulateTextChanges(TextContentChangedEventArgs contentChanged)
            {
                _workQueue.AssertIsForeground();
                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);
                    this.AccumulatedTextChanges = this.AccumulatedTextChanges == null
                                ? textChangeRange
                                : this.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);
                    }

                    this.AccumulatedTextChanges = this.AccumulatedTextChanges.Accumulate(textChangeRanges);
                    break;
                }
            }
Beispiel #6
0
 public void Ctor2()
 {
     var span = new TextSpan(2, 50);
     var range = new TextChangeRange(span, 42);
     Assert.Equal(span, range.Span);
     Assert.Equal(42, range.NewLength);
 }
Beispiel #7
0
        public void Ctor2()
        {
            var span  = new TextSpan(2, 50);
            var range = new TextChangeRange(span, 42);

            Assert.Equal(span, range.Span);
            Assert.Equal(42, range.NewLength);
        }
Beispiel #8
0
 public void Ctor1()
 {
     Assert.Throws <ArgumentOutOfRangeException>(
         () =>
     {
         var notUsed = new TextChangeRange(new TextSpan(), -1);
     }
         );
 }
        private void DocumentOnChanged(object sender, DocumentChangeEventArgs e)
        {
            SetCurrent();
            var textChangeRange = new TextChangeRange(
                new TextSpan(e.Offset, e.RemovalLength),
                e.RemovalLength == 0 ? e.InsertionLength : e.RemovalLength);

            OnTextChanged(new TextChangeEventArgs(_oldText, CurrentText, textChangeRange));
        }
        private void DocumentOnChanged(object sender, DocumentChangeEventArgs e)
        {
            SetCurrent();

            var textChangeRange = new TextChangeRange(
                new TextSpan(e.Offset, e.RemovalLength),
                e.RemovalLength == 0 ? e.InsertionLength : e.RemovalLength);
            OnTextChanged(new TextChangeEventArgs(_before, CurrentText, textChangeRange));
        }
Beispiel #11
0
        internal static TextChangeRange[] ToTextChangeRange(this INormalizedTextChangeCollection changes)
        {
            var res = new TextChangeRange[changes.Count];

            for (int i = 0; i < res.Length; i++)
            {
                res[i] = changes[i].ToTextChangeRange();
            }
            return(res);
        }
Beispiel #12
0
        private List <ChangeRangeWithText> ReduceChanges(List <ChangeRecord> changeRecords)
        {
            var textChanges = new List <ChangeRangeWithText>(changeRecords.Count);

            var oldText = new StringBuilder();
            var newText = new StringBuilder();

            foreach (var cr in changeRecords)
            {
                // try to reduce change range by finding common characters
                if (cr.Range.Span.Length > 0 && cr.Range.NewLength > 0)
                {
                    var range = cr.Range;

                    CopyText(cr.OldNodes, oldText);
                    CopyText(cr.NewNodes, newText);

                    int commonLeadingCount;
                    int commonTrailingCount;
                    GetCommonEdgeLengths(oldText, newText, out commonLeadingCount, out commonTrailingCount);

                    // did we have any common leading or trailing characters between the strings?
                    if (commonLeadingCount > 0 || commonTrailingCount > 0)
                    {
                        range = new TextChangeRange(
                            new TextSpan(range.Span.Start + commonLeadingCount, range.Span.Length - (commonLeadingCount + commonTrailingCount)),
                            range.NewLength - (commonLeadingCount + commonTrailingCount));

                        if (commonTrailingCount > 0)
                        {
                            newText.Remove(newText.Length - commonTrailingCount, commonTrailingCount);
                        }

                        if (commonLeadingCount > 0)
                        {
                            newText.Remove(0, commonLeadingCount);
                        }
                    }

                    // only include adjusted change if there is still a change
                    if (range.Span.Length > 0 || range.NewLength > 0)
                    {
                        textChanges.Add(new ChangeRangeWithText(range, _computeNewText ? newText.ToString() : null));
                    }
                }
                else
                {
                    // pure inserts and deletes
                    textChanges.Add(new ChangeRangeWithText(cr.Range, _computeNewText ? GetText(cr.NewNodes) : null));
                }
            }

            return(textChanges);
        }
Beispiel #13
0
        private static SyntaxNode?GetBestGuessChangedMember(
            ISyntaxFactsService syntaxFactsService,
            SyntaxNode oldRoot,
            SyntaxNode newRoot,
            TextChangeRange range
            )
        {
            // if either old or new tree contains skipped text, re-analyze whole document
            if (oldRoot.ContainsSkippedText || newRoot.ContainsSkippedText)
            {
                return(null);
            }

            // there was top level changes, so we can't use equivalent to see whether two members are same.
            // so, we use some simple text based heuristic to find a member that has changed.
            //
            // if we have a differ that do diff on member level or a way to track member between incremental parsing, then
            // that would be preferable. but currently we don't have such thing.

            // get top level elements at the position where change has happened
            var oldMember = syntaxFactsService.GetContainingMemberDeclaration(
                oldRoot,
                range.Span.Start
                );
            var newMember = syntaxFactsService.GetContainingMemberDeclaration(
                newRoot,
                range.Span.Start
                );

            // reached the top (compilation unit)
            if (oldMember == null || newMember == null)
            {
                return(null);
            }

            // if old member was empty, just use new member
            if (oldMember.Span.IsEmpty)
            {
                return(newMember);
            }

            // looks like change doesn't belong to existing member
            if (!oldMember.Span.Contains(range.Span))
            {
                return(null);
            }

            // change happened inside of the old member, check whether new member seems just delta of that change
            var lengthDelta = range.NewLength - range.Span.Length;

            return((oldMember.Span.Length + lengthDelta) == newMember.Span.Length
              ? newMember
              : null);
        }
        private void DocumentOnChanged(object sender, DocumentChangeEventArgs e)
        {
            var oldSourceText = this.currentSourceText;

            var textSpan        = new TextSpan(e.Offset, e.RemovalLength);
            var textChangeRange = new TextChangeRange(textSpan, e.InsertionLength);

            this.currentSourceText = this.currentSourceText.WithChanges(new TextChange(textSpan, e.InsertedText?.Text ?? string.Empty));

            this.TextChanged?.Invoke(this, new TextChangeEventArgs(oldSourceText, this.currentSourceText, textChangeRange));
        }
Beispiel #15
0
        private static TextChangeRange ExtendToAffectedRange(
            CSharp.CSharpSyntaxNode oldTree,
            TextChangeRange changeRange
            )
        {
            // we will increase affected range of the change by the number of lookahead tokens
            // original code in Blender seem to imply the lookahead at the end of a node is 1 token
            // max. TODO: 1 token lookahead seems a bit too optimistic. Increase if needed.
            const int maxLookahead = 1;

            // check if change is not after the end. TODO: there should be an assert somewhere about
            // changes starting at least at the End of old tree
            var lastCharIndex = oldTree.FullWidth - 1;

            // Move the start of the change range so that it is contained within oldTree.
            var start = Math.Max(Math.Min(changeRange.Span.Start, lastCharIndex), 0);

            // the first iteration aligns us with the change start. subsequent iteration move us to
            // the left by maxLookahead tokens.  We only need to do this as long as we're not at the
            // start of the tree.  Also, the tokens we get back may be zero width.  In that case we
            // need to keep on looking backward.
            for (var i = 0; start > 0 && i <= maxLookahead;)
            {
                var token = oldTree.FindToken(start, findInsideTrivia: false);
                Debug.Assert(
                    token.Kind() != SyntaxKind.None,
                    "how could we not get a real token back?"
                    );

                start = Math.Max(0, token.Position - 1);

                // Only increment i if we got a non-zero width token.  Otherwise, we want to just do
                // this again having moved back one space.
                if (token.FullWidth > 0)
                {
                    i++;
                }
            }

            if (IsInsideInterpolation(oldTree, start))
            {
                // If the changed range starts inside an interpolated string, we
                // move the start of the change range to the beginning of the line so that any
                // interpolated string literal in the changed range will be scanned in its entirety.
                var column =
                    oldTree.SyntaxTree.GetLineSpan(new TextSpan(start, 0)).Span.Start.Character;
                start = Math.Max(start - column, 0);
            }

            var finalSpan   = TextSpan.FromBounds(start, changeRange.Span.End);
            var finalLength = changeRange.NewLength + (changeRange.Span.Start - start);

            return(new TextChangeRange(finalSpan, finalLength));
        }
        private void DocumentOnChanged(object sender, DocumentChangeEventArgs e)
        {
            if (_updatding) return;

            var oldText = _currentText;

            var textSpan = new TextSpan(e.Offset, e.RemovalLength);
            var textChangeRange = new TextChangeRange(textSpan, e.InsertionLength);
            _currentText = _currentText.WithChanges(new TextChange(textSpan, e.InsertedText?.Text ?? string.Empty));

            TextChanged?.Invoke(this, new TextChangeEventArgs(oldText, _currentText, textChangeRange));
        }
Beispiel #17
0
        public Blender(
            Lexer lexer,
            CSharp.CSharpSyntaxNode oldTree,
            IEnumerable <TextChangeRange> changes
            )
        {
            Debug.Assert(lexer != null);
            _lexer   = lexer;
            _changes = ImmutableStack.Create <TextChangeRange>();

            if (changes != null)
            {
                // TODO: Consider implementing NormalizedChangeCollection for TextSpan. the real
                // reason why we are collapsing is because we want to extend change ranges and
                // cannot allow them to overlap. This does not seem to be a big deal since multiple
                // changes are infrequent and typically close to each other. However if we have
                // NormalizedChangeCollection for TextSpan we can have both - we can extend ranges
                // and not require collapsing them. NormalizedChangeCollection would also ensure
                // that changes are always normalized.

                // TODO: this is a temporary measure to prevent individual change spans from
                // overlapping after they are widened to effective width (+1 token at the start).
                // once we have normalized collection for TextSpan we will not need to collapse all
                // the change spans.

                var collapsed = TextChangeRange.Collapse(changes);

                // extend the change to its affected range. This will make it easier
                // to filter out affected nodes since we will be able simply check
                // if node intersects with a change.
                var affectedRange = ExtendToAffectedRange(oldTree, collapsed);
                _changes = _changes.Push(affectedRange);
            }

            if (oldTree == null)
            {
                // start at lexer current position if no nodes specified
                _oldTreeCursor = new Cursor();
                _newPosition   = lexer.TextWindow.Position;
            }
            else
            {
                _oldTreeCursor = Cursor.FromRoot(oldTree).MoveToFirstChild();
                _newPosition   = 0;
            }

            _changeDelta        = 0;
            _newDirectives      = default(DirectiveStack);
            _oldDirectives      = default(DirectiveStack);
            _newLexerDrivenMode = 0;
        }
Beispiel #18
0
        private void EhAvalonEditsDocumentChanged(object sender, DocumentChangeEventArgs e)
        {
            if (_updatingCount > 0)
            {
                return;
            }

            var oldText = _currentSourceText_Roslyn;

            var textSpan        = new TextSpan(e.Offset, e.RemovalLength);
            var textChangeRange = new TextChangeRange(textSpan, e.InsertionLength);

            _currentSourceText_Roslyn = _currentSourceText_Roslyn.WithChanges(new TextChange(textSpan, e.InsertedText?.Text ?? string.Empty));

            TextChanged?.Invoke(this, new TextChangeEventArgs(oldText, _currentSourceText_Roslyn, textChangeRange));
        }
Beispiel #19
0
        private void DocumentOnChanged(object sender, DocumentChangeEventArgs e)
        {
            if (_updatding)
            {
                return;
            }

            var oldText = _currentText;

            var textSpan        = new TextSpan(e.Offset, e.RemovalLength);
            var textChangeRange = new TextChangeRange(textSpan, e.InsertionLength);

            _currentText = _currentText.WithChanges(new TextChange(textSpan, e.InsertedText?.Text ?? string.Empty));

            TextChanged?.Invoke(this, new TextChangeEventArgs(oldText, _currentText, textChangeRange));
        }
Beispiel #20
0
        void T(string originalText, string replacementText)
        {
            var offset    = Xml.IndexOf(originalText);
            var length    = originalText.Length;
            var newLength = replacementText.Length;
            var newXml    = Xml.Replace(originalText, replacementText);
            var changes   = new TextChangeRange[] { new TextChangeRange(new TextSpan(offset, length), newLength) };

            var root    = Parser.ParseText(Xml);
            var newRoot = Parser.ParseIncremental(newXml, changes, root);

            Assert.NotSame(root, newRoot);
            Assert.Equal(newXml, newRoot.ToFullString());

            AssertShareElementGreenNodesWithName("Y", 2, root.Body, newRoot.Body);
            AssertShareAttributeGreenNodesWithPrefix("reused", 2, root.Body, newRoot.Body);
        }
Beispiel #21
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;
            }
        }
        private static IEnumerable <ClassifiedSpan> GetChangedClassifiedSpans(TextChangeRange textChange, IEnumerable <ClassifiedSpan> oldClassifiedSpans, IEnumerable <ClassifiedSpan> newClassifiedSpans)
        {
            int offset = textChange.NewLength - textChange.Span.Length;

            foreach (ClassifiedSpan span in newClassifiedSpans)
            {
                ClassifiedSpan offsettedSpan = span;

                if (span.TextSpan.Start > textChange.Span.End && span.TextSpan.Start > offset)
                {
                    offsettedSpan = new ClassifiedSpan(span.ClassificationType, new TextSpan(span.TextSpan.Start - offset, span.TextSpan.Length));
                }

                if (!oldClassifiedSpans.Contains(offsettedSpan))
                {
                    yield return(span);
                }
            }
        }
Beispiel #23
0
        private static SyntaxNode?GetChangedMember(
            ISyntaxFactsService syntaxFactsService,
            SyntaxNode oldRoot,
            SyntaxNode newRoot,
            TextChangeRange range
            )
        {
            // if either old or new tree contains skipped text, re-analyze whole document
            if (oldRoot.ContainsSkippedText || newRoot.ContainsSkippedText)
            {
                return(null);
            }

            var oldMember = syntaxFactsService.GetContainingMemberDeclaration(
                oldRoot,
                range.Span.Start
                );
            var newMember = syntaxFactsService.GetContainingMemberDeclaration(
                newRoot,
                range.Span.Start
                );

            // reached the top (compilation unit)
            if (oldMember == null || newMember == null)
            {
                return(null);
            }

            // member doesn't contain the change
            if (!syntaxFactsService.ContainsInMemberBody(oldMember, range.Span))
            {
                return(null);
            }

            // member signature has changed
            if (!oldMember.IsEquivalentTo(newMember, topLevel: true))
            {
                return(null);
            }

            // looks like inside of the body has changed
            return(newMember);
        }
            private void AccumulateTextChanges(TextContentChangedEventArgs contentChanged)
            {
                this.AssertIsForeground();
                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);
                    this.AccumulatedTextChanges = this.AccumulatedTextChanges == null
                                ? textChangeRange
                                : this.AccumulatedTextChanges.Accumulate(SpecializedCollections.SingletonEnumerable(textChangeRange));
                }

                break;

                default:
                {
                    using var _ = ArrayBuilder <TextChangeRange> .GetInstance(count, out var textChangeRanges);

                    foreach (var c in contentChanges)
                    {
                        textChangeRanges.Add(new TextChangeRange(new TextSpan(c.OldSpan.Start, c.OldSpan.Length), c.NewLength));
                    }

                    this.AccumulatedTextChanges = this.AccumulatedTextChanges.Accumulate(textChangeRanges);
                    break;
                }
                }
            }
Beispiel #25
0
 public void Ctor1()
 {
     Assert.Throws(
         typeof(ArgumentOutOfRangeException),
         () => { var notUsed = new TextChangeRange(new TextSpan(), -1); });
 }
Beispiel #26
0
 internal ChangeRecord(TextChangeRange range, Stack<SyntaxNodeOrToken> oldNodes, Stack<SyntaxNodeOrToken> newNodes)
 {
     this.Range = range;
     this.OldNodes = oldNodes;
     this.NewNodes = newNodes;
 }
        /// <summary>
        /// Merges the new change ranges into the old change ranges, adjusting the new ranges to be with respect to the original text
        /// (with neither old or new changes applied) instead of with respect to the original text after "old changes" are applied.
        ///
        /// This may require splitting, concatenation, etc. of individual change ranges.
        /// </summary>
        /// <remarks>
        /// Both `oldChanges` and `newChanges` must contain non-overlapping spans in ascending order.
        /// </remarks>
        public static ImmutableArray <TextChangeRange> Merge(ImmutableArray <TextChangeRange> oldChanges, ImmutableArray <TextChangeRange> newChanges)
        {
            // Earlier steps are expected to prevent us from ever reaching this point with empty change sets.
            if (oldChanges.IsEmpty)
            {
                throw new ArgumentException(nameof(oldChanges));
            }

            if (newChanges.IsEmpty)
            {
                throw new ArgumentException(nameof(newChanges));
            }

            var builder = ArrayBuilder <TextChangeRange> .GetInstance();

            var oldChange = oldChanges[0];
            var newChange = new UnadjustedNewChange(newChanges[0]);

            var oldIndex = 0;
            var newIndex = 0;

            // The sum of characters inserted by old changes minus characters deleted by old changes.
            // This value must be adjusted whenever characters from an old change are added to `builder`.
            var oldDelta = 0;

            // In this loop we "zip" together potentially overlapping old and new changes.
            // It's important that when overlapping changes are found, we don't consume past the end of the overlapping section until the next iteration.
            // so that we don't miss scenarios where the section after the overlap we found itself overlaps with another change
            // e.g.:
            // [-------oldChange1------]
            // [--newChange1--]   [--newChange2--]
            while (true)
            {
                if (oldChange.Span.Length == 0 && oldChange.NewLength == 0)
                {
                    // old change does not insert or delete any characters, so it can be dropped to no effect.
                    if (tryGetNextOldChange())
                    {
                        continue;
                    }
                    else
                    {
                        break;
                    }
                }
                else if (newChange.SpanLength == 0 && newChange.NewLength == 0)
                {
                    // new change does not insert or delete any characters, so it can be dropped to no effect.
                    if (tryGetNextNewChange())
                    {
                        continue;
                    }
                    else
                    {
                        break;
                    }
                }
                else if (newChange.SpanEnd <= oldChange.Span.Start + oldDelta)
                {
                    // new change is entirely before old change, so just take the new change
                    //                old[--------]
                    // new[--------]
                    adjustAndAddNewChange(builder, oldDelta, newChange);
                    if (tryGetNextNewChange())
                    {
                        continue;
                    }
                    else
                    {
                        break;
                    }
                }
                else if (newChange.SpanStart >= oldChange.NewEnd() + oldDelta)
                {
                    // new change is entirely after old change, so just take the old change
                    // old[--------]
                    //                new[--------]
                    addAndAdjustOldDelta(builder, ref oldDelta, oldChange);
                    if (tryGetNextOldChange())
                    {
                        continue;
                    }
                    else
                    {
                        break;
                    }
                }
                else if (newChange.SpanStart < oldChange.Span.Start + oldDelta)
                {
                    // new change starts before old change, but the new change deletion overlaps with the old change insertion
                    // note: 'd' represents a deleted character, 'a' represents a character inserted by an old change, and 'b' represents a character inserted by a new change.
                    //
                    //    old|dddddd|
                    //       |aaaaaa|
                    // ---------------
                    // new|dddddd|
                    //    |bbbbbb|

                    // align the new change and old change start by consuming the part of the new deletion before the old change
                    // (this only deletes characters of the original text)
                    //
                    // old|dddddd|
                    //    |aaaaaa|
                    // ---------------
                    // new|ddd|
                    //    |bbbbbb|
                    var newChangeLeadingDeletion = oldChange.Span.Start + oldDelta - newChange.SpanStart;
                    adjustAndAddNewChange(builder, oldDelta, new UnadjustedNewChange(newChange.SpanStart, newChangeLeadingDeletion, newLength: 0));
                    newChange = new UnadjustedNewChange(oldChange.Span.Start + oldDelta, newChange.SpanLength - newChangeLeadingDeletion, newChange.NewLength);
                    continue;
                }
                else if (newChange.SpanStart > oldChange.Span.Start + oldDelta)
                {
                    // new change starts after old change, but overlaps
                    //
                    // old|dddddd|
                    //    |aaaaaa|
                    // ---------------
                    //    new|dddddd|
                    //       |bbbbbb|

                    // align the old change to the new change by consuming the part of the old change which is before the new change.
                    //
                    //    old|ddd|
                    //       |aaa|
                    // ---------------
                    //    new|dddddd|
                    //       |bbbbbb|

                    var oldChangeLeadingInsertion = newChange.SpanStart - (oldChange.Span.Start + oldDelta);
                    // we must make sure to delete at most as many characters as the entire oldChange deletes
                    var oldChangeLeadingDeletion = Math.Min(oldChange.Span.Length, oldChangeLeadingInsertion);
                    addAndAdjustOldDelta(builder, ref oldDelta, new TextChangeRange(new TextSpan(oldChange.Span.Start, oldChangeLeadingDeletion), oldChangeLeadingInsertion));
                    oldChange = new TextChangeRange(new TextSpan(newChange.SpanStart - oldDelta, oldChange.Span.Length - oldChangeLeadingDeletion), oldChange.NewLength - oldChangeLeadingInsertion);
                    continue;
                }
                else
                {
                    // old and new change start at same adjusted position
                    Debug.Assert(newChange.SpanStart == oldChange.Span.Start + oldDelta);

                    if (newChange.SpanLength <= oldChange.NewLength)
                    {
                        // new change deletes fewer characters than old change inserted
                        //
                        // old|dddddd|
                        //    |aaaaaa|
                        // ---------------
                        // new|ddd|
                        //    |bbbbbb|

                        // - apply the new change deletion to the old change insertion
                        //
                        //    old|dddddd|
                        //       |aaa|
                        // ---------------
                        // new||
                        //    |bbbbbb|
                        //
                        // - move the new change insertion forward by the same amount as its consumed deletion to remain aligned with the old change.
                        // (because the old change and new change have the same adjusted start position, the new change insertion appears directly before the old change insertion in the final text)
                        //
                        //    old|dddddd|
                        //       |aaa|
                        // ---------------
                        //    new||
                        //       |bbbbbb|

                        oldChange = new TextChangeRange(oldChange.Span, oldChange.NewLength - newChange.SpanLength);

                        // the new change deletion is equal to the subset of the old change insertion that we are consuming this iteration
                        oldDelta = oldDelta + newChange.SpanLength;

                        // since the new change insertion occurs before the old change, consume it now
                        newChange = new UnadjustedNewChange(newChange.SpanEnd, spanLength: 0, newChange.NewLength);
                        adjustAndAddNewChange(builder, oldDelta, newChange);
                        if (tryGetNextNewChange())
                        {
                            continue;
                        }
                        else
                        {
                            break;
                        }
                    }
                    else
                    {
                        // new change deletes more characters than old change inserted
                        //
                        // old|d|
                        //    |aa|
                        // ---------------
                        // new|ddd|
                        //    |bbb|

                        // merge the old change into the new change:
                        // - new change deletion deletes all of the old change insertion. reduce the new change deletion accordingly
                        //
                        //   old|d|
                        //      ||
                        // ---------------
                        // new|d|
                        //    |bbb|
                        //
                        // - old change deletion is simply added to the new change deletion.
                        //
                        //  old||
                        //     ||
                        // ---------------
                        // new|dd|
                        //    |bbb|
                        //
                        // - new change is moved to put its adjusted position equal to the old change we just merged in
                        //
                        //  old||
                        //     ||
                        // ---------------
                        //  new|dd|
                        //     |bbb|

                        // adjust the oldDelta to reflect that the old change has been consumed
                        oldDelta = oldDelta - oldChange.Span.Length + oldChange.NewLength;

                        var newDeletion = newChange.SpanLength + oldChange.Span.Length - oldChange.NewLength;
                        newChange = new UnadjustedNewChange(oldChange.Span.Start + oldDelta, newDeletion, newChange.NewLength);
                        if (tryGetNextOldChange())
                        {
                            continue;
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }

            // there may be remaining old changes or remaining new changes (not both, and not neither)
            switch (oldIndex == oldChanges.Length, newIndex == newChanges.Length)
            {
Beispiel #28
0
        internal static void VerifySource(this SyntaxTree tree, IEnumerable <TextChangeRange> changes = null)
        {
            var        root     = tree.GetRoot();
            var        text     = tree.GetText();
            var        fullSpan = new TextSpan(0, text.Length);
            SyntaxNode node     = null;

            // If only a subset of the document has changed,
            // just check that subset to reduce verification cost.
            if (changes != null)
            {
                var change = TextChangeRange.Collapse(changes).Span;
                if (change != fullSpan)
                {
                    // Find the lowest node in the tree that contains the changed region.
                    node = root.DescendantNodes(n => n.FullSpan.Contains(change)).LastOrDefault();
                }
            }

            if (node == null)
            {
                node = root;
            }

            var span        = node.FullSpan;
            var textSpanOpt = span.Intersection(fullSpan);
            int index;

            if (textSpanOpt == null)
            {
                index = 0;
            }
            else
            {
                var fromText = text.ToString(textSpanOpt.Value);
                var fromNode = node.ToFullString();
                index = FindFirstDifference(fromText, fromNode);
            }

            if (index >= 0)
            {
                index += span.Start;
                string message;
                if (index < text.Length)
                {
                    var position = text.Lines.GetLinePosition(index);
                    var line     = text.Lines[position.Line];
                    var allText  = text.ToString(); // Entire document as string to allow inspecting the text in the debugger.
                    message = string.Format("Unexpected difference at offset {0}: Line {1}, Column {2} \"{3}\"",
                                            index,
                                            position.Line + 1,
                                            position.Character + 1,
                                            line.ToString());
                }
                else
                {
                    message = "Unexpected difference past end of the file";
                }
                Debug.Assert(false, message);
            }
        }
Beispiel #29
0
        private List<ChangeRangeWithText> ReduceChanges(List<ChangeRecord> changeRecords)
        {
            var textChanges = new List<ChangeRangeWithText>(changeRecords.Count);

            var oldText = new StringBuilder();
            var newText = new StringBuilder();

            foreach (var cr in changeRecords)
            {
                // try to reduce change range by finding common characters
                if (cr.Range.Span.Length > 0 && cr.Range.NewLength > 0)
                {
                    var range = cr.Range;

                    CopyText(cr.OldNodes, oldText);
                    CopyText(cr.NewNodes, newText);

                    int commonLeadingCount;
                    int commonTrailingCount;
                    GetCommonEdgeLengths(oldText, newText, out commonLeadingCount, out commonTrailingCount);

                    // did we have any common leading or trailing characters between the strings?
                    if (commonLeadingCount > 0 || commonTrailingCount > 0)
                    {
                        range = new TextChangeRange(
                            new TextSpan(range.Span.Start + commonLeadingCount, range.Span.Length - (commonLeadingCount + commonTrailingCount)),
                            range.NewLength - (commonLeadingCount + commonTrailingCount));

                        if (commonTrailingCount > 0)
                        {
                            newText.Remove(newText.Length - commonTrailingCount, commonTrailingCount);
                        }

                        if (commonLeadingCount > 0)
                        {
                            newText.Remove(0, commonLeadingCount);
                        }
                    }

                    // only include adjusted change if there is still a change 
                    if (range.Span.Length > 0 || range.NewLength > 0)
                    {
                        textChanges.Add(new ChangeRangeWithText(range, _computeNewText ? newText.ToString() : null));
                    }
                }
                else
                {
                    // pure inserts and deletes
                    textChanges.Add(new ChangeRangeWithText(cr.Range, _computeNewText ? GetText(cr.NewNodes) : null));
                }
            }

            return textChanges;
        }
Beispiel #30
0
        private static void AddRange(List<TextChangeRange> list, TextChangeRange range)
        {
            if (list.Count > 0)
            {
                var last = list[list.Count - 1];
                if (last.Span.End == range.Span.Start)
                {
                    // merge changes together if they are adjacent
                    list[list.Count - 1] = new TextChangeRange(new TextSpan(last.Span.Start, last.Span.Length + range.Span.Length), last.NewLength + range.NewLength);
                    return;
                }
                else
                {
                    Debug.Assert(range.Span.Start > last.Span.End);
                }
            }

            list.Add(range);
        }
Beispiel #31
0
 internal ChangeRecord(TextChangeRange range, List <SyntaxNodeOrToken> newNodes)
 {
     this.Range    = range;
     this.NewNodes = newNodes;
 }
        private static SyntaxNode GetChangedMember(
            ISyntaxFactsService syntaxFactsService, SyntaxNode oldRoot, SyntaxNode newRoot, TextChangeRange range)
        {
            // if either old or new tree contains skipped text, re-analyze whole document
            if (oldRoot.ContainsSkippedText || newRoot.ContainsSkippedText)
            {
                return null;
            }

            var oldMember = syntaxFactsService.GetContainingMemberDeclaration(oldRoot, range.Span.Start);
            var newMember = syntaxFactsService.GetContainingMemberDeclaration(newRoot, range.Span.Start);

            // reached the top (compilation unit)
            if (oldMember == null || newMember == null)
            {
                return null;
            }

            // member doesn't contain the change
            if (!syntaxFactsService.ContainsInMemberBody(oldMember, range.Span))
            {
                return null;
            }

            // member signature has changed
            if (!oldMember.IsEquivalentTo(newMember, topLevel: true))
            {
                return null;
            }

            // looks like inside of the body has changed
            return newMember;
        }
        private static SyntaxNode GetBestGuessChangedMember(
            ISyntaxFactsService syntaxFactsService, SyntaxNode oldRoot, SyntaxNode newRoot, TextChangeRange range)
        {
            // if either old or new tree contains skipped text, re-analyze whole document
            if (oldRoot.ContainsSkippedText || newRoot.ContainsSkippedText)
            {
                return null;
            }

            // there was top level changes, so we can't use equivalent to see whether two members are same.
            // so, we use some simple text based heuristic to find a member that has changed.
            //
            // if we have a differ that do diff on member level or a way to track member between incremental parsing, then
            // that would be preferable. but currently we don't have such thing.

            // get top level elements at the position where change has happened
            var oldMember = syntaxFactsService.GetContainingMemberDeclaration(oldRoot, range.Span.Start);
            var newMember = syntaxFactsService.GetContainingMemberDeclaration(newRoot, range.Span.Start);

            // reached the top (compilation unit)
            if (oldMember == null || newMember == null)
            {
                return null;
            }

            // if old member was empty, just use new member
            if (oldMember.Span.IsEmpty)
            {
                return newMember;
            }

            // looks like change doesn't belong to existing member
            if (!oldMember.Span.Contains(range.Span))
            {
                return null;
            }

            // change happened inside of the old member, check whether new member seems just delta of that change
            var lengthDelta = range.NewLength - range.Span.Length;

            return (oldMember.Span.Length + lengthDelta) == newMember.Span.Length ? newMember : null;
        }
Beispiel #34
0
 public ChangeRangeWithText(TextChangeRange range, string newText)
 {
     this.Range   = range;
     this.NewText = newText;
 }
Beispiel #35
0
        private static ImmutableArray<TextChangeRange> Merge(ImmutableArray<TextChangeRange> oldChanges, ImmutableArray<TextChangeRange> newChanges)
        {
            var list = new List<TextChangeRange>(oldChanges.Length + newChanges.Length);

            int oldIndex = 0;
            int newIndex = 0;
            int oldDelta = 0;

        nextNewChange:
            if (newIndex < newChanges.Length)
            {
                var newChange = newChanges[newIndex];

            nextOldChange:
                if (oldIndex < oldChanges.Length)
                {
                    var oldChange = oldChanges[oldIndex];

                tryAgain:
                    if (oldChange.Span.Length == 0 && oldChange.NewLength == 0)
                    {
                        // old change is a non-change, just ignore it and move on
                        oldIndex++;
                        goto nextOldChange;
                    }
                    else if (newChange.Span.Length == 0 && newChange.NewLength == 0)
                    {
                        // new change is a non-change, just ignore it and move on
                        newIndex++;
                        goto nextNewChange;
                    }
                    else if (newChange.Span.End < (oldChange.Span.Start + oldDelta))
                    {
                        // new change occurs entirely before old change
                        var adjustedNewChange = new TextChangeRange(new TextSpan(newChange.Span.Start - oldDelta, newChange.Span.Length), newChange.NewLength);
                        AddRange(list, adjustedNewChange);
                        newIndex++;
                        goto nextNewChange;
                    }
                    else if (newChange.Span.Start > oldChange.Span.Start + oldDelta + oldChange.NewLength)
                    {
                        // new change occurs entirely after old change
                        AddRange(list, oldChange);
                        oldDelta = oldDelta - oldChange.Span.Length + oldChange.NewLength;
                        oldIndex++;
                        goto nextOldChange;
                    }
                    else if (newChange.Span.Start < oldChange.Span.Start + oldDelta)
                    {
                        // new change starts before old change, but overlaps
                        // add as much of new change deletion as possible and try again
                        var newChangeLeadingDeletion = (oldChange.Span.Start + oldDelta) - newChange.Span.Start;
                        AddRange(list, new TextChangeRange(new TextSpan(newChange.Span.Start - oldDelta, newChangeLeadingDeletion), 0));
                        newChange = new TextChangeRange(new TextSpan(oldChange.Span.Start + oldDelta, newChange.Span.Length - newChangeLeadingDeletion), newChange.NewLength);
                        goto tryAgain;
                    }
                    else if (newChange.Span.Start > oldChange.Span.Start + oldDelta)
                    {
                        // new change starts after old change, but overlaps
                        // add as much of the old change as possible and try again
                        var oldChangeLeadingInsertion = newChange.Span.Start - (oldChange.Span.Start + oldDelta);
                        AddRange(list, new TextChangeRange(oldChange.Span, oldChangeLeadingInsertion));
                        oldDelta = oldDelta - oldChange.Span.Length + oldChangeLeadingInsertion;
                        oldChange = new TextChangeRange(new TextSpan(oldChange.Span.Start, 0), oldChange.NewLength - oldChangeLeadingInsertion);
                        newChange = new TextChangeRange(new TextSpan(oldChange.Span.Start + oldDelta, newChange.Span.Length), newChange.NewLength);
                        goto tryAgain;
                    }
                    else if (newChange.Span.Start == oldChange.Span.Start + oldDelta)
                    {
                        // new change and old change start at same position
                        if (oldChange.NewLength == 0)
                        {
                            // old change is just a deletion, go ahead and old change now and deal with new change separately
                            AddRange(list, oldChange);
                            oldDelta = oldDelta - oldChange.Span.Length + oldChange.NewLength;
                            oldIndex++;
                            goto nextOldChange;
                        }
                        else if (newChange.Span.Length == 0)
                        {
                            // new change is just an insertion, go ahead and tack it on with old change
                            AddRange(list, new TextChangeRange(oldChange.Span, oldChange.NewLength + newChange.NewLength));
                            oldDelta = oldDelta - oldChange.Span.Length + oldChange.NewLength;
                            oldIndex++;
                            newIndex++;
                            goto nextNewChange;
                        }
                        else
                        {
                            // delete as much from old change as new change can
                            // a new change deletion is a reduction in the old change insertion
                            var oldChangeReduction = Math.Min(oldChange.NewLength, newChange.Span.Length);
                            AddRange(list, new TextChangeRange(oldChange.Span, oldChange.NewLength - oldChangeReduction));
                            oldDelta = oldDelta - oldChange.Span.Length + (oldChange.NewLength - oldChangeReduction);
                            oldIndex++;

                            // deduct the amount removed from oldChange from newChange's deletion span (since its already been applied)
                            newChange = new TextChangeRange(new TextSpan(oldChange.Span.Start + oldDelta, newChange.Span.Length - oldChangeReduction), newChange.NewLength);
                            goto nextOldChange;
                        }
                    }
                }
                else
                {
                    // no more old changes, just add adjusted new change
                    var adjustedNewChange = new TextChangeRange(new TextSpan(newChange.Span.Start - oldDelta, newChange.Span.Length), newChange.NewLength);
                    AddRange(list, adjustedNewChange);
                    newIndex++;
                    goto nextNewChange;
                }
            }
            else
            {
                // no more new changes, just add remaining old changes
                while (oldIndex < oldChanges.Length)
                {
                    AddRange(list, oldChanges[oldIndex]);
                    oldIndex++;
                }
            }

            return list.ToImmutableArray();
        }
Beispiel #36
0
 internal ChangeRecord(TextChangeRange range, List<SyntaxNodeOrToken> newNodes)
 {
     this.Range = range;
     this.NewNodes = newNodes;
 }
Beispiel #37
0
 internal ChangeRecord(TextChangeRange range, Queue <SyntaxNodeOrToken>?oldNodes, Queue <SyntaxNodeOrToken>?newNodes)
 {
     Range    = range;
     OldNodes = oldNodes;
     NewNodes = newNodes;
 }
Beispiel #38
0
 public ChangeRangeWithText(TextChangeRange range, string?newText)
 {
     Range   = range;
     NewText = newText;
 }
Beispiel #39
0
        /// <summary>
        /// Affected range of a change is the range within which nodes can be affected by a change
        /// and cannot be reused. Because of lookahead effective range of a change is larger than
        /// the change itself.
        /// </summary>
        private static TextChangeRange ExtendToAffectedRange(
            CSharp.CSharpSyntaxNode oldTree,
            TextChangeRange changeRange)
        {
            // we will increase affected range of the change by the number of lookahead tokens
            // original code in Blender seem to imply the lookahead at the end of a node is 1 token
            // max. TODO: 1 token lookahead seems a bit too optimistic. Increase if needed. 
            const int maxLookahead = 1;

            // check if change is not after the end. TODO: there should be an assert somwhere about
            // changes starting at least at the End of old tree
            var lastCharIndex = oldTree.FullWidth - 1;

            // Move the start of the change range so that it is contained within oldTree.
            var start = Math.Max(Math.Min(changeRange.Span.Start, lastCharIndex), 0);

            // the first iteration aligns us with the change start. subsequent iteration move us to
            // the left by maxLookahead tokens.  We only need to do this as long as we're not at the
            // start of the tree.  Also, the tokens we get back may be zero width.  In that case we
            // need to keep on looking backward.
            for (var i = 0; start > 0 && i <= maxLookahead;)
            {
                var token = oldTree.FindToken(start, findInsideTrivia: false);
                Debug.Assert(token.Kind() != SyntaxKind.None, "how could we not get a real token back?");

                start = Math.Max(0, token.Position - 1);

                // Only increment i if we got a non-zero width token.  Otherwise, we want to just do
                // this again having moved back one space.
                if (token.FullWidth > 0)
                {
                    i++;
                }
            }

            if (IsInsideInterpolation(oldTree, start))
            {
                // If the changed range starts inside an interpolated string, we
                // move the start of the change range to the beginning of the line so that any
                // interpolated string literal in the changed range will be scanned in its entirety.
                var column = oldTree.SyntaxTree.GetLineSpan(new TextSpan(start, 0)).Span.Start.Character;
                start = Math.Max(start - column, 0);
            }

            var finalSpan = TextSpan.FromBounds(start, changeRange.Span.End);
            var finalLength = changeRange.NewLength + (changeRange.Span.Start - start);
            return new TextChangeRange(finalSpan, finalLength);
        }
Beispiel #40
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));
        }
Beispiel #41
0
		internal static TextChangeRange[] ToTextChangeRange(this INormalizedTextChangeCollection changes) {
			var res = new TextChangeRange[changes.Count];
			for (int i = 0; i < res.Length; i++)
				res[i] = changes[i].ToTextChangeRange();
			return res;
		}
Beispiel #42
0
 internal ChangeRecord(TextChangeRange range, Queue <SyntaxNodeOrToken> oldNodes, Queue <SyntaxNodeOrToken> newNodes)
 {
     this.Range    = range;
     this.OldNodes = oldNodes;
     this.NewNodes = newNodes;
 }
Beispiel #43
0
 public ChangeRangeWithText(TextChangeRange range, string newText)
 {
     this.Range = range;
     this.NewText = newText;
 }