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; } Debug.Assert(range.Span.Start > last.Span.End); } list.Add(range); }
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; } if (newChange.Span.Length == 0 && newChange.NewLength == 0) { // new change is a non-change, just ignore it and move on newIndex++; goto nextNewChange; } 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; } 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; } 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; } 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; } 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; } 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; } // 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()); }