public static TextDiff Consume_Diff(DataStream <char> Old, DataStream <char> New) { /* Scan both streams until we either find a spot ahead in one that matches the current spot in the other OR we hit the end of the stream */ var CancelToken = new CancellationTokenSource(); var uOld = Find_Next_Unique(Old, Old.Position); var uNew = Find_Next_Unique(New, New.Position); TextDiff addDiff = null; TextDiff rmvDiff = null; Parallel.Invoke(new ParallelOptions() { CancellationToken = CancelToken.Token }, () => /* Scan for addition */ { /* Find the next unique in stream B that matches A's current */ if (uOld == null) { addDiff = new TextDiff(Old.Length, New.Length, EDiffType.Insertion); /* Insertion at end */ return; } if (uNew == null) { return; } var index = New.Position + (uNew.Length - 1); while (index < New.Length) { var unique = Find_Next_Unique(New, index); if (unique == null) { break; } else if (uOld.AsMemory().Equals(unique.AsMemory())) { addDiff = new TextDiff(New.Position, index, EDiffType.Insertion); CancelToken.Cancel(); } else { index += unique.Length - 1; } } }, () => /* Scan for removal */ { /* Find next unique in stream A matching B's current */ if (uNew == null) { rmvDiff = new TextDiff(New.Length, Old.Length, EDiffType.Removal); /* Removal at end */ return; } if (uOld == null) { return; } var index = Old.Position + (uOld.Length - 1); while (index < Old.Length) { var unique = Find_Next_Unique(Old, index); if (unique == null) { break; } else if (uNew.AsMemory().Equals(unique.AsMemory())) { rmvDiff = new TextDiff(Old.Position, index, EDiffType.Insertion); CancelToken.Cancel(); } else { index += unique.Length - 1; } } } ); /* If nothing was found then the change must be a pure modification so we lockstep ahead in both streams to find the next spot that they both match */ if (addDiff == null && rmvDiff == null) { int pos = New.Position; while ((pos + 1) < Old.Length && (pos + 1) < New.Length && Old.Peek(pos) != New.Peek(pos)) { pos++; } return(new TextDiff(New.Position, pos, EDiffType.Mutation)); } else if (addDiff != null && rmvDiff != null)/* Else find the shorter of the two diffs and return that one */ { if (addDiff.End < rmvDiff.End) { return(addDiff); } else { return(rmvDiff); } } else if (addDiff != null) { return(addDiff); } else if (rmvDiff != null) { return(rmvDiff); } return(null); }
/// <summary> /// Compiles a list of differences between the current text and some given text /// </summary> /// <param name="NewText"></param> /// <returns>List of start/end ranges</returns> public static LinkedList <TextDiff> Difference(StringPtr OldText, StringPtr NewText) { if ((OldText == null || OldText.Length <= 0) && (NewText == null || NewText.Length <= 0)) { return(new LinkedList <TextDiff>()); } else if ((OldText == null || OldText.Length <= 0) && NewText.Length > 0) { return(new LinkedList <TextDiff>(new TextDiff[] { new TextDiff(0, NewText.Length, EDiffType.Insertion) })); } else if ((NewText == null || NewText.Length <= 0) && OldText.Length > 0) { return(new LinkedList <TextDiff>(new TextDiff[] { new TextDiff(0, OldText.Length, EDiffType.Insertion) })); } var A = new DataStream <char>(OldText.AsMemory(), '\0'); var B = new DataStream <char>(NewText.AsMemory(), '\0'); var Chunks = new LinkedList <TextDiff>(); while (true) { if (A.Next != B.Next)/* Just entered a spot where the texts stopped matching */ { var diff = TextDiff.Consume_Diff(A, B); if (diff == null || diff.Length <= 0) {/* No more diffs available */ return(Chunks); } /* Progress the spplicable stream by the difference ammount */ switch (diff.Type) { case EDiffType.Insertion: { B.Consume(diff.Length); } break; case EDiffType.Removal: { A.Consume(diff.Length); } break; case EDiffType.Mutation: { A.Consume(diff.Length); B.Consume(diff.Length); } break; default: throw new NotImplementedException($"Handling for {nameof(EDiffType)} \"{diff.Type}\" has not been implemented!"); } Chunks.AddLast(diff); } if (A.atEnd && B.atEnd) { break; } } /* Check for text diff past the end of stream */ return(Chunks); }