summaryrefslogtreecommitdiff
path: root/lib/gitlab/diff/inline_diff.rb
blob: 789c14518b003260f302c86ac14928ec69c52cb2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
module Gitlab
  module Diff
    class InlineDiff
      attr_accessor :old_line, :new_line, :offset

      def self.for_lines(lines)
        local_edit_indexes = self.find_local_edits(lines)

        inline_diffs = []

        local_edit_indexes.each do |index|
          old_index = index
          new_index = index + 1
          old_line = lines[old_index]
          new_line = lines[new_index]

          old_diffs, new_diffs = new(old_line, new_line, offset: 1).inline_diffs

          inline_diffs[old_index] = old_diffs
          inline_diffs[new_index] = new_diffs
        end

        inline_diffs
      end

      def initialize(old_line, new_line, offset: 0)
        @old_line = old_line[offset..-1]
        @new_line = new_line[offset..-1]
        @offset = offset
      end

      def inline_diffs
        # Skip inline diff if empty line was replaced with content
        return if old_line == ""

        lcp = longest_common_prefix(old_line, new_line)
        lcs = longest_common_suffix(old_line[lcp..-1], new_line[lcp..-1])

        lcp += offset
        old_length = old_line.length + offset
        new_length = new_line.length + offset

        old_diff_range = lcp..(old_length - lcs - 1)
        new_diff_range = lcp..(new_length - lcs - 1)

        old_diffs = [old_diff_range] if old_diff_range.begin <= old_diff_range.end
        new_diffs = [new_diff_range] if new_diff_range.begin <= new_diff_range.end

        [old_diffs, new_diffs]
      end

      private

      def self.find_local_edits(lines)
        line_prefixes = lines.map { |line| line.match(/\A([+-])/) ? $1 : ' ' }
        joined_line_prefixes = " #{line_prefixes.join} "

        offset = 0
        local_edit_indexes = []
        while index = joined_line_prefixes.index(" -+ ", offset)
          local_edit_indexes << index
          offset = index + 1
        end

        local_edit_indexes
      end

      def longest_common_prefix(a, b)
        max_length = [a.length, b.length].max

        length = 0
        (0..max_length - 1).each do |pos|
          old_char = a[pos]
          new_char = b[pos]

          break if old_char != new_char
          length += 1
        end

        length
      end

      def longest_common_suffix(a, b)
        longest_common_prefix(a.reverse, b.reverse)
      end
    end
  end
end