/// <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);
        }
        /// <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..];
示例#3
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();
            }
        }