/// <summary> /// Do a quick line-level diff on both strings, then rediff the parts for /// greater accuracy. This speedup can produce non-minimal Diffs. /// </summary> /// <param name="text1"></param> /// <param name="text2"></param> /// <param name="token"></param> /// <param name="optimizeForSpeed"></param> /// <returns></returns> private static List <Diff> LineDiff(string text1, string text2, CancellationToken token, bool optimizeForSpeed) { // Scan the text on a line-by-line basis first. var compressor = new LineToCharCompressor(); text1 = compressor.Compress(text1, char.MaxValue * 2 / 3); text2 = compressor.Compress(text2, char.MaxValue); var diffs = Compute(text1, text2, false, token, optimizeForSpeed) .Select(diff => diff.Replace(compressor.Decompress(diff.Text))) .ToList(); // Eliminate freak matches (e.g. blank lines) diffs.CleanupSemantic(); // Rediff any replacement blocks, this time character-by-character. // Add a dummy entry at the end. diffs.Add(Diff.Equal(string.Empty)); var pointer = 0; var countDelete = 0; var countInsert = 0; var insertBuilder = new StringBuilder(); var deleteBuilder = new StringBuilder(); while (pointer < diffs.Count) { switch (diffs[pointer].Operation) { case Operation.Insert: countInsert++; insertBuilder.Append(diffs[pointer].Text); break; case Operation.Delete: countDelete++; deleteBuilder.Append(diffs[pointer].Text); break; case Operation.Equal: // Upon reaching an equality, check for prior redundancies. if (countDelete >= 1 && countInsert >= 1) { // Delete the offending records and add the merged ones. var diffsWithinLine = Compute(deleteBuilder.ToString(), insertBuilder.ToString(), false, token, optimizeForSpeed); var count = countDelete + countInsert; var index = pointer - count; diffs.Splice(index, count, diffsWithinLine); pointer = index + diffsWithinLine.Count; } countInsert = 0; countDelete = 0; deleteBuilder.Clear(); insertBuilder.Clear(); break; } pointer++; } diffs.RemoveAt(diffs.Count - 1); // Remove the dummy entry at the end. return(diffs); }
/// <summary> /// Increase the context until it is unique, /// but don't let the pattern expand beyond Match_MaxBits.</summary> /// <param name="text">Source text</param> /// <param name="patchMargin"></param> internal void AddContext(string text, short patchMargin = 4) { if (text.Length == 0) { return; } var pattern = text.Substring(Start2, Length1); var padding = 0; // Look for the first and last matches of pattern in text. If two // different matches are found, increase the pattern length. while (text.IndexOf(pattern, StringComparison.Ordinal) != text.LastIndexOf(pattern, StringComparison.Ordinal) && pattern.Length < Constants.MatchMaxBits - patchMargin - patchMargin) { padding += patchMargin; var begin = Math.Max(0, Start2 - padding); pattern = text.Substring(begin, Math.Min(text.Length, Start2 + Length1 + padding) - begin); } // Add one chunk for good luck. padding += patchMargin; // Add the prefix. var begin1 = Math.Max(0, Start2 - padding); var prefix = text.Substring(begin1, Start2 - begin1); if (prefix.Length != 0) { Diffs.Insert(0, Diff.Equal(prefix)); } // Add the suffix. var begin2 = Start2 + Length1; var length = Math.Min(text.Length, Start2 + Length1 + padding) - begin2; var suffix = text.Substring(begin2, length); if (suffix.Length != 0) { Diffs.Add(Diff.Equal(suffix)); } // Roll back the start points. Start1 -= prefix.Length; Start2 -= prefix.Length; // Extend the lengths. Length1 += prefix.Length + suffix.Length; Length2 += prefix.Length + suffix.Length; }
/// <summary> /// Find the differences between two texts. Simplifies the problem by /// stripping any common prefix or suffix off the texts before diffing. /// </summary> /// <param name="text1">Old string to be diffed.</param> /// <param name="text2">New string to be diffed.</param> /// <param name="checklines">Speedup flag. If false, then don't run a line-level diff first to identify the changed areas. If true, then run a faster slightly less optimal diff.</param> /// <param name="token">Cancellation token for cooperative cancellation</param> /// <param name="optimizeForSpeed">Should optimizations be enabled?</param> /// <returns></returns> public static List <Diff> Compute(string text1, string text2, bool checklines, CancellationToken token, bool optimizeForSpeed) { if (text1.Length == text2.Length && text1.Length == 0) { return(new List <Diff>()); } var commonlength = TextUtil.CommonPrefix(text1, text2); if (commonlength == text1.Length && commonlength == text2.Length) { // equal return(new List <Diff>() { Diff.Equal(text1) }); } // Trim off common prefix (speedup). var commonprefix = text1.Substring(0, commonlength); text1 = text1.Substring(commonlength); text2 = text2.Substring(commonlength); // Trim off common suffix (speedup). commonlength = TextUtil.CommonSuffix(text1, text2); var commonsuffix = text1.Substring(text1.Length - commonlength); text1 = text1.Substring(0, text1.Length - commonlength); text2 = text2.Substring(0, text2.Length - commonlength); // Compute the diff on the middle block. var diffs = ComputeImpl(text1, text2, checklines, token, optimizeForSpeed); // Restore the prefix and suffix. if (commonprefix.Length != 0) { diffs.Insert(0, Diff.Equal(commonprefix)); } if (commonsuffix.Length != 0) { diffs.Add(Diff.Equal(commonsuffix)); } diffs.CleanupMerge(); return(diffs); }
public void AddPaddingAfterLastDiff(string nullPadding) { if (Diffs.Count == 0 || Diffs.Last().Operation != Operation.Equal) { // Add nullPadding equality. Diffs.Add(Diff.Equal(nullPadding)); Length1 += nullPadding.Length; Length2 += nullPadding.Length; } else if (nullPadding.Length > Diffs[Diffs.Count - 1].Text.Length) { // Grow last equality. var lastDiff = Diffs[Diffs.Count - 1]; var extraLength = nullPadding.Length - lastDiff.Text.Length; var text = lastDiff.Text + nullPadding.Substring(0, extraLength); Diffs[Diffs.Count - 1] = lastDiff.Replace(text); Length1 += extraLength; Length2 += extraLength; } }
/// <summary> /// Find the differences between two texts. Simplifies the problem by /// stripping any common prefix or suffix off the texts before diffing. /// </summary> /// <param name="text1">Old string to be diffed.</param> /// <param name="text2">New string to be diffed.</param> /// <param name="checklines">Speedup flag. If false, then don't run a line-level diff first to identify the changed areas. If true, then run a faster slightly less optimal diff.</param> /// <param name="optimizeForSpeed">Should optimizations be enabled?</param> /// <param name="token">Cancellation token for cooperative cancellation</param> /// <returns></returns> internal static IEnumerable <Diff> Compute(ReadOnlySpan <char> text1, ReadOnlySpan <char> text2, bool checklines, bool optimizeForSpeed, CancellationToken token) { if (text1.Length == text2.Length && text1.Length == 0) { return(Enumerable.Empty <Diff>()); } var commonlength = TextUtil.CommonPrefix(text1, text2); if (commonlength == text1.Length && commonlength == text2.Length) { // equal return(new List <Diff>() { Diff.Equal(text1) }); } // Trim off common prefix (speedup). var commonprefix = text1.Slice(0, commonlength); text1 = text1[commonlength..];
public void AddPaddingBeforeFirstDiff(string nullPadding) { if (Diffs.Count == 0 || Diffs[0].Operation != Operation.Equal) { // Add nullPadding equality. Diffs.Insert(0, Diff.Equal(nullPadding)); Start1 -= nullPadding.Length; // Should be 0. Start2 -= nullPadding.Length; // Should be 0. Length1 += nullPadding.Length; Length2 += nullPadding.Length; } else if (nullPadding.Length > Diffs[0].Text.Length) { // Grow first equality. var firstDiff = Diffs[0]; var extraLength = nullPadding.Length - firstDiff.Text.Length; Diffs[0] = firstDiff.Replace(nullPadding.Substring(firstDiff.Text.Length) + firstDiff.Text); Start1 -= extraLength; Start2 -= extraLength; Length1 += extraLength; Length2 += extraLength; } }
/// <summary> /// Find the differences between two texts. Assumes that the texts do not /// have any common prefix or suffix. /// </summary> /// <param name="text1">Old string to be diffed.</param> /// <param name="text2">New string to be diffed.</param> /// <param name="checklines">Speedup flag. If false, then don't run a line-level diff first to identify the changed areas. If true, then run a faster slightly less optimal diff.</param> /// <param name="token">Cancellation token for cooperative cancellation</param> /// <param name="optimizeForSpeed">Should optimizations be enabled?</param> /// <returns></returns> private static List <Diff> ComputeImpl( string text1, string text2, bool checklines, CancellationToken token, bool optimizeForSpeed) { var diffs = new List <Diff>(); if (text1.Length == 0) { // Just add some text (speedup). diffs.Add(Diff.Insert(text2)); return(diffs); } if (text2.Length == 0) { // Just delete some text (speedup). diffs.Add(Diff.Delete(text1)); return(diffs); } var longtext = text1.Length > text2.Length ? text1 : text2; var shorttext = text1.Length > text2.Length ? text2 : text1; var i = longtext.IndexOf(shorttext, StringComparison.Ordinal); if (i != -1) { // Shorter text is inside the longer text (speedup). var op = text1.Length > text2.Length ? Operation.Delete : Operation.Insert; diffs.Add(Diff.Create(op, longtext.Substring(0, i))); diffs.Add(Diff.Equal(shorttext)); diffs.Add(Diff.Create(op, longtext.Substring(i + shorttext.Length))); return(diffs); } if (shorttext.Length == 1) { // Single character string. // After the previous speedup, the character can't be an equality. diffs.Add(Diff.Delete(text1)); diffs.Add(Diff.Insert(text2)); return(diffs); } // Don't risk returning a non-optimal diff if we have unlimited time. if (optimizeForSpeed) { // Check to see if the problem can be split in two. var result = TextUtil.HalfMatch(text1, text2); if (!result.IsEmpty) { // A half-match was found, sort out the return data. // Send both pairs off for separate processing. var diffsA = Compute(result.Prefix1, result.Prefix2, checklines, token, optimizeForSpeed); var diffsB = Compute(result.Suffix1, result.Suffix2, checklines, token, optimizeForSpeed); // Merge the results. diffs = diffsA; diffs.Add(Diff.Equal(result.CommonMiddle)); diffs.AddRange(diffsB); return(diffs); } } if (checklines && text1.Length > 100 && text2.Length > 100) { return(LineDiff(text1, text2, token, optimizeForSpeed)); } return(MyersDiffBisect(text1, text2, token, optimizeForSpeed)); }
/// <summary> /// Reduce the number of edits by eliminating semantically trivial equalities. /// </summary> /// <param name="diffs"></param> public static void CleanupSemantic(this List <Diff> diffs) { // Stack of indices where equalities are found. var equalities = new Stack <int>(); // Always equal to equalities[equalitiesLength-1][1] string lastEquality = null; var pointer = 0; // Index of current position. // Number of characters that changed prior to the equality. var lengthInsertions1 = 0; var lengthDeletions1 = 0; // Number of characters that changed after the equality. var lengthInsertions2 = 0; var lengthDeletions2 = 0; while (pointer < diffs.Count) { if (diffs[pointer].Operation == Operation.Equal) { // Equality found. equalities.Push(pointer); lengthInsertions1 = lengthInsertions2; lengthDeletions1 = lengthDeletions2; lengthInsertions2 = 0; lengthDeletions2 = 0; lastEquality = diffs[pointer].Text; } else { // an insertion or deletion if (diffs[pointer].Operation == Operation.Insert) { lengthInsertions2 += diffs[pointer].Text.Length; } else { lengthDeletions2 += diffs[pointer].Text.Length; } // Eliminate an equality that is smaller or equal to the edits on both // sides of it. if (lastEquality != null && (lastEquality.Length <= Math.Max(lengthInsertions1, lengthDeletions1)) && (lastEquality.Length <= Math.Max(lengthInsertions2, lengthDeletions2))) { // Duplicate record. diffs.Splice(equalities.Peek(), 1, Diff.Delete(lastEquality), Diff.Insert(lastEquality)); // Throw away the equality we just deleted. equalities.Pop(); if (equalities.Count > 0) { equalities.Pop(); } pointer = equalities.Count > 0 ? equalities.Peek() : -1; lengthInsertions1 = 0; // Reset the counters. lengthDeletions1 = 0; lengthInsertions2 = 0; lengthDeletions2 = 0; lastEquality = null; } } pointer++; } diffs.CleanupMerge(); diffs.CleanupSemanticLossless(); // Find any overlaps between deletions and insertions. // e.g: <del>abcxxx</del><ins>xxxdef</ins> // -> <del>abc</del>xxx<ins>def</ins> // e.g: <del>xxxabc</del><ins>defxxx</ins> // -> <ins>def</ins>xxx<del>abc</del> // Only extract an overlap if it is as big as the edit ahead or behind it. pointer = 1; while (pointer < diffs.Count) { if (diffs[pointer - 1].Operation == Operation.Delete && diffs[pointer].Operation == Operation.Insert) { var deletion = diffs[pointer - 1].Text; var insertion = diffs[pointer].Text; var overlapLength1 = TextUtil.CommonOverlap(deletion, insertion); var overlapLength2 = TextUtil.CommonOverlap(insertion, deletion); if (overlapLength1 >= overlapLength2) { if (overlapLength1 >= deletion.Length / 2.0 || overlapLength1 >= insertion.Length / 2.0) { // Overlap found. // Insert an equality and trim the surrounding edits. var newDiffs = new[] { Diff.Delete(deletion.Substring(0, deletion.Length - overlapLength1)), Diff.Equal(insertion.Substring(0, overlapLength1)), Diff.Insert(insertion.Substring(overlapLength1)) }; diffs.Splice(pointer - 1, 2, newDiffs); pointer++; } } else { if (overlapLength2 >= deletion.Length / 2.0 || overlapLength2 >= insertion.Length / 2.0) { // Reverse overlap found. // Insert an equality and swap and trim the surrounding edits. diffs.Splice(pointer - 1, 2, Diff.Insert(insertion.Substring(0, insertion.Length - overlapLength2)), Diff.Equal(deletion.Substring(0, overlapLength2)), Diff.Delete(deletion.Substring(overlapLength2) )); pointer++; } } pointer++; } pointer++; } }
/// <summary> /// Look for single edits surrounded on both sides by equalities /// which can be shifted sideways to align the edit to a word boundary. /// e.g: The c<ins>at c</ins>ame. -> The <ins>cat </ins>came. /// </summary> /// <param name="diffs"></param> internal static void CleanupSemanticLossless(this List <Diff> diffs) { var pointer = 1; // Intentionally ignore the first and last element (don't need checking). while (pointer < diffs.Count - 1) { var previous = diffs[pointer - 1]; var current = diffs[pointer]; var next = diffs[pointer + 1]; if (previous.Operation == Operation.Equal && next.Operation == Operation.Equal) { // This is a single edit surrounded by equalities. var equality1 = previous.Text; var edit = current.Text; var equality2 = next.Text; // First, shift the edit as far left as possible. var commonOffset = TextUtil.CommonSuffix(equality1, edit); if (commonOffset > 0) { var commonString = edit.Substring(edit.Length - commonOffset); equality1 = equality1.Substring(0, equality1.Length - commonOffset); edit = commonString + edit.Substring(0, edit.Length - commonOffset); equality2 = commonString + equality2; } // Second, step character by character right, // looking for the best fit. var bestEquality1 = equality1; var bestEdit = edit; var bestEquality2 = equality2; var bestScore = DiffCleanupSemanticScore(equality1, edit) + DiffCleanupSemanticScore(edit, equality2); while (edit.Length != 0 && equality2.Length != 0 && edit[0] == equality2[0]) { equality1 += edit[0]; edit = edit.Substring(1) + equality2[0]; equality2 = equality2.Substring(1); var score = DiffCleanupSemanticScore(equality1, edit) + DiffCleanupSemanticScore(edit, equality2); // The >= encourages trailing rather than leading whitespace on // edits. if (score >= bestScore) { bestScore = score; bestEquality1 = equality1; bestEdit = edit; bestEquality2 = equality2; } } if (previous.Text != bestEquality1) { // We have an improvement, save it back to the diff. var newDiffs = new[] { Diff.Equal(bestEquality1), current.Replace(bestEdit), Diff.Equal(bestEquality2) }.Where(d => !string.IsNullOrEmpty(d.Text)) .ToArray(); diffs.Splice(pointer - 1, 3, newDiffs); pointer = pointer - (3 - newDiffs.Length); } } pointer++; } }
/// <summary> /// Reorder and merge like edit sections. Merge equalities. /// Any edit section can move as long as it doesn't cross an equality. /// </summary> /// <param name="diffs">list of Diffs</param> internal static void CleanupMerge(this List <Diff> diffs) { // Add a dummy entry at the end. diffs.Add(Diff.Equal(string.Empty)); var nofdiffs = 0; var sbDelete = new StringBuilder(); var sbInsert = new StringBuilder(); var pointer = 0; while (pointer < diffs.Count) { switch (diffs[pointer].Operation) { case Operation.Insert: nofdiffs++; sbInsert.Append(diffs[pointer].Text); pointer++; break; case Operation.Delete: nofdiffs++; sbDelete.Append(diffs[pointer].Text); pointer++; break; case Operation.Equal: // Upon reaching an equality, check for prior redundancies. if (nofdiffs > 1) { if (sbDelete.Length > 0 && sbInsert.Length > 0) { // Factor out any common prefixies. var commonlength = TextUtil.CommonPrefix(sbInsert, sbDelete); if (commonlength != 0) { var commonprefix = sbInsert.ToString(0, commonlength); sbInsert.Remove(0, commonlength); sbDelete.Remove(0, commonlength); var index = pointer - nofdiffs - 1; if (index >= 0 && diffs[index].Operation == Operation.Equal) { diffs[index] = diffs[index].Replace(diffs[index].Text + commonprefix); } else { diffs.Insert(0, Diff.Equal(commonprefix)); pointer++; } } // Factor out any common suffixies. commonlength = TextUtil.CommonSuffix(sbInsert, sbDelete); if (commonlength != 0) { var commonsuffix = sbInsert.ToString(sbInsert.Length - commonlength, commonlength); sbInsert.Remove(sbInsert.Length - commonlength, commonlength); sbDelete.Remove(sbDelete.Length - commonlength, commonlength); diffs[pointer] = diffs[pointer].Replace(commonsuffix + diffs[pointer].Text); } } // Delete the offending records and add the merged ones. IEnumerable <Diff> Replacements() { if (sbDelete.Length > 0) { yield return(Diff.Delete(sbDelete.ToString())); } if (sbInsert.Length > 0) { yield return(Diff.Insert(sbInsert.ToString())); } } var replacements = Replacements().ToList(); diffs.Splice(pointer - nofdiffs, nofdiffs, replacements); pointer = pointer - nofdiffs + replacements.Count + 1; } else if (pointer > 0 && diffs[pointer - 1].Operation == Operation.Equal) { // Merge this equality with the previous one. diffs[pointer - 1] = diffs[pointer - 1].Replace(diffs[pointer - 1].Text + diffs[pointer].Text); diffs.RemoveAt(pointer); } else { pointer++; } nofdiffs = 0; sbDelete.Clear(); sbInsert.Clear(); break; } } if (diffs.Last().Text.Length == 0) { diffs.RemoveAt(diffs.Count - 1); // Remove the dummy entry at the end. } // Second pass: look for single edits surrounded on both sides by // equalities which can be shifted sideways to eliminate an equality. // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC var changes = false; // Intentionally ignore the first and last element (don't need checking). for (var i = 1; i < diffs.Count - 1; i++) { var previous = diffs[i - 1]; var current = diffs[i]; var next = diffs[i + 1]; if (previous.Operation == Operation.Equal && next.Operation == Operation.Equal) { // This is a single edit surrounded by equalities. if (current.Text.EndsWith(previous.Text, StringComparison.Ordinal)) { // Shift the edit over the previous equality. var text = previous.Text + current.Text.Substring(0, current.Text.Length - previous.Text.Length); diffs[i] = current.Replace(text); diffs[i + 1] = next.Replace(previous.Text + next.Text); diffs.Splice(i - 1, 1); changes = true; } else if (current.Text.StartsWith(next.Text, StringComparison.Ordinal)) { // Shift the edit over the next equality. diffs[i - 1] = previous.Replace(previous.Text + next.Text); diffs[i] = current.Replace(current.Text.Substring(next.Text.Length) + next.Text); diffs.Splice(i + 1, 1); changes = true; } } } // If shifts were made, the diff needs reordering and another shift sweep. if (changes) { diffs.CleanupMerge(); } }
/// <summary> /// Look through the patches and break up any which are longer than the /// maximum limit of the match algorithm. /// Intended to be called only from within patch_apply. /// </summary> /// <param name="patches"></param> /// <param name="patchMargin"></param> internal static void SplitMax(this List <Patch> patches, short patchMargin = 4) { var patchSize = Constants.MatchMaxBits; for (var x = 0; x < patches.Count; x++) { if (patches[x].Length1 <= patchSize) { continue; } var bigpatch = patches[x]; // Remove the big old patch. patches.Splice(x--, 1); var start1 = bigpatch.Start1; var start2 = bigpatch.Start2; var precontext = string.Empty; var diffs = bigpatch.Diffs; while (diffs.Count != 0) { // Create one of several smaller patches. var patch = new Patch(); var empty = true; patch.Start1 = start1 - precontext.Length; patch.Start2 = start2 - precontext.Length; if (precontext.Length != 0) { patch.Length1 = patch.Length2 = precontext.Length; patch.Diffs.Add(Diff.Equal(precontext)); } while (diffs.Count != 0 && patch.Length1 < patchSize - patchMargin) { var diffType = diffs[0].Operation; var diffText = diffs[0].Text; if (diffType == Operation.Insert) { // Insertions are harmless. patch.Length2 += diffText.Length; start2 += diffText.Length; patch.Diffs.Add(diffs.First()); diffs.RemoveAt(0); empty = false; } else if (diffType == Operation.Delete && patch.Diffs.Count == 1 && patch.Diffs.First().Operation == Operation.Equal && diffText.Length > 2 * patchSize) { // This is a large deletion. Let it pass in one chunk. patch.Length1 += diffText.Length; start1 += diffText.Length; empty = false; patch.Diffs.Add(Diff.Create(diffType, diffText)); diffs.RemoveAt(0); } else { // Deletion or equality. Only take as much as we can stomach. diffText = diffText.Substring(0, Math.Min(diffText.Length, patchSize - patch.Length1 - patchMargin)); patch.Length1 += diffText.Length; start1 += diffText.Length; if (diffType == Operation.Equal) { patch.Length2 += diffText.Length; start2 += diffText.Length; } else { empty = false; } patch.Diffs.Add(Diff.Create(diffType, diffText)); if (diffText == diffs[0].Text) { diffs.RemoveAt(0); } else { diffs[0] = diffs[0].Replace(diffs[0].Text.Substring(diffText.Length)); } } } // Compute the head context for the next patch. precontext = patch.Diffs.Text2(); precontext = precontext.Substring(Math.Max(0, precontext.Length - patchMargin)); // Append the end context for this patch. var text1 = diffs.Text1(); var postcontext = text1.Length > patchMargin?text1.Substring(0, patchMargin) : text1; if (postcontext.Length != 0) { patch.Length1 += postcontext.Length; patch.Length2 += postcontext.Length; if (patch.Diffs.Count != 0 && patch.Diffs[patch.Diffs.Count - 1].Operation == Operation.Equal) { patch.Diffs[patch.Diffs.Count - 1] = patch.Diffs[patch.Diffs.Count - 1].Replace(patch.Diffs[patch.Diffs.Count - 1].Text + postcontext); } else { patch.Diffs.Add(Diff.Equal(postcontext)); } } if (!empty) { patches.Splice(++x, 0, patch); } } } }
/// <summary> /// Add some padding on text start and end so that edges can match something. /// Intended to be called only from within patch_apply. /// </summary> /// <param name="patches"></param> /// <param name="patchMargin"></param> /// <returns>The padding string added to each side.</returns> internal static string AddPadding(this List <Patch> patches, short patchMargin = 4) { var paddingLength = patchMargin; var nullPaddingSb = new StringBuilder(); for (short x = 1; x <= paddingLength; x++) { nullPaddingSb.Append((char)x); } var nullPadding = nullPaddingSb.ToString(); // Bump all the patches forward. foreach (var aPatch in patches) { aPatch.Start1 += paddingLength; aPatch.Start2 += paddingLength; } // Add some padding on start of first diff. var patch = patches.First(); var diffs = patch.Diffs; if (diffs.Count == 0 || diffs[0].Operation != Operation.Equal) { // Add nullPadding equality. diffs.Insert(0, Diff.Equal(nullPadding)); patch.Start1 -= paddingLength; // Should be 0. patch.Start2 -= paddingLength; // Should be 0. patch.Length1 += paddingLength; patch.Length2 += paddingLength; } else if (paddingLength > diffs[0].Text.Length) { // Grow first equality. var firstDiff = diffs[0]; var extraLength = nullPadding.Length - firstDiff.Text.Length; diffs[0] = firstDiff.Replace(nullPadding.Substring(firstDiff.Text.Length) + firstDiff.Text); patch.Start1 -= extraLength; patch.Start2 -= extraLength; patch.Length1 += extraLength; patch.Length2 += extraLength; } // Add some padding on end of last diff. patch = patches.Last(); diffs = patch.Diffs; if (diffs.Count == 0 || diffs.Last().Operation != Operation.Equal) { // Add nullPadding equality. diffs.Add(Diff.Equal(nullPadding)); patch.Length1 += paddingLength; patch.Length2 += paddingLength; } else if (paddingLength > diffs[diffs.Count - 1].Text.Length) { // Grow last equality. var lastDiff = diffs[diffs.Count - 1]; var extraLength = nullPadding.Length - lastDiff.Text.Length; var text = lastDiff.Text + nullPadding.Substring(0, extraLength); diffs[diffs.Count - 1] = lastDiff.Replace(text); patch.Length1 += extraLength; patch.Length2 += extraLength; } return(nullPadding); }