/// <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);
        }
Ejemplo n.º 2
0
        /// <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);
        }
Ejemplo n.º 4
0
 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..];
Ejemplo n.º 6
0
 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));
        }
Ejemplo n.º 8
0
        /// <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++;
            }
        }
Ejemplo n.º 9
0
        /// <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++;
            }
        }
Ejemplo n.º 10
0
        /// <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);
        }