Beispiel #1
0
        private static void ComputeDiffImpl(ReadOnlySpan <char> left, ReadOnlySpan <char> right, ImmutableArray <Diff> .Builder builder)
        {
            // Only text was added
            if (left.Length == 0)
            {
                builder.AddIfNotEmpty(new Diff(Operation.INSERT, right));
                return;
            }

            // Only text was deleted
            if (right.Length == 0)
            {
                builder.AddIfNotEmpty(new Diff(Operation.DELETE, left));
                return;
            }

            var longText  = left.Length > right.Length ? left : right;
            var shortText = left.Length > right.Length ? right : left;
            int index     = longText.IndexOf(shortText, StringComparison.Ordinal);

            // Shorter text is inside the longer text
            if (index != -1)
            {
                Operation op = (left.Length > right.Length) ? Operation.DELETE : Operation.INSERT;
                builder.AddIfNotEmpty(new Diff(op, longText.Slice(0, index)));
                builder.AddIfNotEmpty(new Diff(Operation.EQUAL, shortText));
                builder.AddIfNotEmpty(new Diff(op, longText.Slice(index + shortText.Length)));
                return;
            }

            // Single character string.
            // After the previous speedup, the character can't be an equality.
            if (shortText.Length == 1)
            {
                builder.AddIfNotEmpty(new Diff(Operation.DELETE, left));
                builder.AddIfNotEmpty(new Diff(Operation.INSERT, right));
                return;
            }

            // Check to see if the problem can be split in two.
            var success = TryComputeHalfMatch(left, right, out var leftA, out var leftB, out var rightA, out var rightB, out var common);

            if (success)
            {
                ComputeDiff(leftA, rightA, builder);
                builder.AddIfNotEmpty(new Diff(Operation.EQUAL, common));
                ComputeDiff(leftB, rightB, builder);
                return;
            }

            if (left.Length > 100 && right.Length > 100)
            {
                ComputeLineModeDiff(left, right, builder);
                return;
            }

            Bisect(left, right, builder);
        }
Beispiel #2
0
        private static void ComputeDiff(ReadOnlySpan <char> left, ReadOnlySpan <char> right, ImmutableArray <Diff> .Builder builder)
        {
            // Check for equality
            if (left.SequenceEqual(right))
            {
                builder.AddIfNotEmpty(new Diff(Operation.EQUAL, left));
                return;
            }

            // Trim off common prefix
            int commonPrefixLength = ComputeCommonPrefix(left, right);

            var commonPrefix = left.Slice(0, commonPrefixLength);

            left  = left.Slice(commonPrefixLength);
            right = right.Slice(commonPrefixLength);

            // Trim off common suffix
            int commonSuffixLength = ComputeCommonSuffix(left, right);
            var commonSuffix       = left.Slice(left.Length - commonSuffixLength);

            left  = left.Slice(0, left.Length - commonSuffixLength);
            right = right.Slice(0, right.Length - commonSuffixLength);

            // Compute the diff on the middle block.
            ComputeDiffImpl(left, right, builder);

            // Restore the prefix and suffix.
            if (commonPrefix.Length > 0)
            {
                builder.AddIfNotEmpty(new Diff(Operation.EQUAL, commonPrefix));
            }
            if (commonSuffix.Length > 0)
            {
                builder.AddIfNotEmpty(new Diff(Operation.EQUAL, commonSuffix));
            }

            CleanUpMerge(builder);
        }
Beispiel #3
0
        private static void Bisect(ReadOnlySpan <char> text1, ReadOnlySpan <char> text2, ImmutableArray <Diff> .Builder builder)
        {
            // Cache the text lengths to prevent multiple calls.
            int text1_length = text1.Length;
            int text2_length = text2.Length;
            int max_d        = (text1_length + text2_length + 1) / 2;
            int v_offset     = max_d;
            int v_length     = 2 * max_d;

            int[] v1 = new int[v_length];
            int[] v2 = new int[v_length];
            for (int x = 0; x < v_length; x++)
            {
                v1[x] = -1;
                v2[x] = -1;
            }
            v1[v_offset + 1] = 0;
            v2[v_offset + 1] = 0;
            int delta = text1_length - text2_length;
            // If the total number of characters is odd, then the front path will
            // collide with the reverse path.
            bool front = (delta % 2 != 0);
            // Offsets for start and end of k loop.
            // Prevents mapping of space beyond the grid.
            int k1start = 0;
            int k1end   = 0;
            int k2start = 0;
            int k2end   = 0;

            for (int d = 0; d < max_d; d++)
            {
                // Walk the front path one step.
                for (int k1 = -d + k1start; k1 <= d - k1end; k1 += 2)
                {
                    int k1_offset = v_offset + k1;
                    int x1;
                    if (k1 == -d || k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1])
                    {
                        x1 = v1[k1_offset + 1];
                    }
                    else
                    {
                        x1 = v1[k1_offset - 1] + 1;
                    }
                    int y1 = x1 - k1;
                    while (x1 < text1_length && y1 < text2_length &&
                           text1[x1] == text2[y1])
                    {
                        x1++;
                        y1++;
                    }
                    v1[k1_offset] = x1;
                    if (x1 > text1_length)
                    {
                        // Ran off the right of the graph.
                        k1end += 2;
                    }
                    else if (y1 > text2_length)
                    {
                        // Ran off the bottom of the graph.
                        k1start += 2;
                    }
                    else if (front)
                    {
                        int k2_offset = v_offset + delta - k1;
                        if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1)
                        {
                            // Mirror x2 onto top-left coordinate system.
                            int x2 = text1_length - v2[k2_offset];
                            if (x1 >= x2)
                            {
                                // Overlap detected.
                                BisectSplit(text1, text2, x1, y1, builder);
                                return;
                            }
                        }
                    }
                }

                // Walk the reverse path one step.
                for (int k2 = -d + k2start; k2 <= d - k2end; k2 += 2)
                {
                    int k2_offset = v_offset + k2;
                    int x2;
                    if (k2 == -d || k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1])
                    {
                        x2 = v2[k2_offset + 1];
                    }
                    else
                    {
                        x2 = v2[k2_offset - 1] + 1;
                    }
                    int y2 = x2 - k2;
                    while (x2 < text1_length && y2 < text2_length &&
                           text1[text1_length - x2 - 1]
                           == text2[text2_length - y2 - 1])
                    {
                        x2++;
                        y2++;
                    }
                    v2[k2_offset] = x2;
                    if (x2 > text1_length)
                    {
                        // Ran off the left of the graph.
                        k2end += 2;
                    }
                    else if (y2 > text2_length)
                    {
                        // Ran off the top of the graph.
                        k2start += 2;
                    }
                    else if (!front)
                    {
                        int k1_offset = v_offset + delta - k2;
                        if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1)
                        {
                            int x1 = v1[k1_offset];
                            int y1 = v_offset + x1 - k1_offset;
                            // Mirror x2 onto top-left coordinate system.
                            x2 = text1_length - v2[k2_offset];
                            if (x1 >= x2)
                            {
                                // Overlap detected.
                                BisectSplit(text1, text2, x1, y1, builder);
                                return;
                            }
                        }
                    }
                }
            }

            // number of diffs equals number of characters, no commonality at all.
            builder.AddIfNotEmpty(new Diff(Operation.DELETE, text1));
            builder.AddIfNotEmpty(new Diff(Operation.INSERT, text2));
        }
Beispiel #4
0
        private static void ComputeLineModeDiff(ReadOnlySpan <char> left, ReadOnlySpan <char> right, ImmutableArray <Diff> .Builder builder)
        {
            ComputeDiffLines(left, right, out var newLeft, out var newRight, out var linesArray);
            ComputeDiff(newRight, newLeft, builder);

            // Convert the diff back to original text.
            ConvertCharsToLines(builder, linesArray);
            // Eliminate freak matches (e.g. blank lines)
            CleanUpSemantic(builder);

            // Rediff any replacement blocks, this time character-by-character.
            // Add a dummy entry at the end.
            builder.AddIfNotEmpty(new Diff(Operation.EQUAL, string.Empty));
            int pointer     = 0;
            int deleteCount = 0;
            int insertCount = 0;
            ReadOnlySpan <char> deletedText  = default;
            ReadOnlySpan <char> insertedText = default;

            while (pointer < builder.Count)
            {
                switch (builder[pointer].Operation)
                {
                case Operation.INSERT:
                    insertCount++;
                    if (insertedText == default)
                    {
                        insertedText = builder[pointer].Text.Span;
                    }
                    break;

                case Operation.DELETE:
                    deleteCount++;
                    if (deletedText == default)
                    {
                        deletedText = builder[pointer].Text.Span;
                    }
                    break;

                case Operation.EQUAL:
                    // Upon reaching an equality, check for prior redundancies.
                    if (deleteCount >= 1 && insertCount >= 1)
                    {
                        // Delete the offending records and add the merged ones.
                        for (int i = pointer - deleteCount - insertCount; i < deleteCount + insertCount; i++)
                        {
                            builder.RemoveAt(i);
                        }

                        pointer = pointer - deleteCount - insertCount;
                        var newBuilder = ImmutableArray.CreateBuilder <Diff>();
                        ComputeDiff(deletedText, insertedText, newBuilder);
                        for (int i = 0; i < newBuilder.Count; i++)
                        {
                            builder.Insert(i + pointer, newBuilder[i]);
                        }
                        pointer = pointer + newBuilder.Count;
                    }
                    insertCount  = 0;
                    deleteCount  = 0;
                    deletedText  = default;
                    insertedText = default;
                    break;

                default:
                    break;
                }
                pointer++;
            }
        }