Example #1
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);
 }
        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));
        }
        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));
        }
Example #4
0
 internal ChangeRecord(TextChangeRange range, List<SyntaxNodeOrToken> newNodes)
 {
     this.Range = range;
     this.NewNodes = newNodes;
 }
Example #5
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);
        }
Example #6
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;
        }
Example #7
0
 public ChangeRangeWithText(TextChangeRange range, string newText)
 {
     this.Range = range;
     this.NewText = newText;
 }
Example #8
0
 internal ChangeRecord(TextChangeRange range, Stack<SyntaxNodeOrToken> oldNodes, Stack<SyntaxNodeOrToken> newNodes)
 {
     this.Range = range;
     this.OldNodes = oldNodes;
     this.NewNodes = newNodes;
 }
Example #9
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);
        }
Example #10
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();
        }
        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 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;
        }
Example #13
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;
		}
Example #14
0
 public void Ctor1()
 {
     Assert.Throws(
         typeof(ArgumentOutOfRangeException),
         () => { var notUsed = new TextChangeRange(new TextSpan(), -1); });
 }