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); }
public static TextChangeRange?Accumulate(this TextChangeRange?accumulatedTextChangeSoFar, IEnumerable <TextChangeRange> changesInNextVersion) { if (!changesInNextVersion.Any()) { return(accumulatedTextChangeSoFar); } // get encompassing text change and accumulate it once. // we could apply each one individually like we do in SyntaxDiff::ComputeSpansInNew by calculating delta // between each change in changesInNextVersion which is already sorted in its textual position ascending order. // but end result will be same as just applying it once with encompassed text change range. var newChange = TextChangeRange.Collapse(changesInNextVersion); // no previous accumulated change, return the new value. if (accumulatedTextChangeSoFar == null) { return(newChange); } // set initial value from the old one. var currentStart = accumulatedTextChangeSoFar.Value.Span.Start; var currentOldEnd = accumulatedTextChangeSoFar.Value.Span.End; var currentNewEnd = accumulatedTextChangeSoFar.Value.Span.Start + accumulatedTextChangeSoFar.Value.NewLength; // this is a port from // csharp\rad\Text\SourceText.cpp - CSourceText::OnChangeLineText // which accumulate text changes to one big text change that would encompass all changes // Merge incoming edit data with old edit data here. // RULES: // 1) position values are always associated with a buffer version. // 2) Comparison between position values is only allowed if their // buffer version is the same. // 3) newChange.Span.End and newChange.Span.Start + newChange.NewLength (both stored and incoming) // refer to the same position, but have different buffer versions. // 4) The incoming end position is associated with buffer versions // n-1 (old) and n(new). // 5) The stored end position BEFORE THIS EDIT is associated with // buffer versions 0 (old) and n-1 (new). // 6) The stored end position AFTER THIS EDIT should be associated // with buffer versions 0 (old) and n(new). // 7) To transform a position P from buffer version of x to y, apply // the delta between any position C(x) and C(y), ASSUMING that // both positions P and C are affected by all edits between // buffer versions x and y. // 8) The start position is relative to all buffer versions, because // it precedes all edits(by definition) // First, the start position. This one is easy, because it is not // complicated by buffer versioning -- it is always the "earliest" // of all incoming values. if (newChange.Span.Start < currentStart) { currentStart = newChange.Span.Start; } // Okay, now the end position. We must make a choice between the // stored end position and the incoming end position. Per rule #2, // we must use the stored NEW end and the incoming OLD end, both of // which are relative to buffer version n-1. if (currentNewEnd > newChange.Span.End) { // We have chosen to keep the stored end because it occurs past // the incoming edit. So, we need currentOldEnd and // currentNewEnd. Since currentOldEnd is already relative // to buffer 0, it is unmodified. Since currentNewEnd is // relative to buffer n-1 (and we need n), apply to it the delta // between the incoming end position values, which are n-1 and n. currentNewEnd = currentNewEnd + newChange.NewLength - newChange.Span.Length; } else { // We have chosen to use the incoming end because it occurs past // the stored edit. So, we need newChange.Span.End and (newChange.Span.Start + newChange.NewLength). // Since (newChange.Span.Start + newChange.NewLength) is already relative to buffer n, it is copied // unmodified. Since newChange.Span.End is relative to buffer n-1 (and // we need 0), apply to it the delta between the stored end // position values, which are relative to 0 and n-1. currentOldEnd = currentOldEnd + newChange.Span.End - currentNewEnd; currentNewEnd = newChange.Span.Start + newChange.NewLength; } return(new TextChangeRange(TextSpan.FromBounds(currentStart, currentOldEnd), currentNewEnd - currentStart)); }
private 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()); }