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; }
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)); }
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); } }