inline_diff.rb 3.1 KB
Newer Older
V
Valeriy Sizov 已提交
1 2 3 4 5 6
module Gitlab
  class InlineDiff
    class << self

      START  = "#!idiff-start!#"
      FINISH = "#!idiff-finish!#"
A
Andrey Kumanyaev 已提交
7

8
      def processing(diff_arr)
V
Valeriy Sizov 已提交
9 10 11 12 13 14
        indexes = _indexes_of_changed_lines diff_arr

        indexes.each do |index|
          first_line = diff_arr[index+1]
          second_line = diff_arr[index+2]

D
Dmitriy Zaporozhets 已提交
15 16 17
          # Skip inline diff if empty line was replaced with content
          next if first_line == "-\n"

18
          first_token = find_first_token(first_line, second_line)
19
          apply_first_token(diff_arr, index, first_token)
D
Dmitriy Zaporozhets 已提交
20

21
          last_token = find_last_token(first_line, second_line, first_token)
22
          apply_last_token(diff_arr, index, last_token)
V
Valeriy Sizov 已提交
23
        end
24

V
Valeriy Sizov 已提交
25 26 27
        diff_arr
      end

28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
      def apply_first_token(diff_arr, index, first_token)
        start = first_token + START

        if first_token.empty?
          # In case if we remove string of spaces in commit
          diff_arr[index+1].sub!("-", "-" => "-#{START}")
          diff_arr[index+2].sub!("+", "+" => "+#{START}")
        else
          diff_arr[index+1].sub!(first_token, first_token => start)
          diff_arr[index+2].sub!(first_token, first_token => start)
        end
      end

      def apply_last_token(diff_arr, index, last_token)
        # This is tricky: escape backslashes so that `sub` doesn't interpret them
        # as backreferences. Regexp.escape does NOT do the right thing.
        replace_token = FINISH + last_token.gsub(/\\/, '\&\&')
        diff_arr[index+1].sub!(/#{Regexp.escape(last_token)}$/, replace_token)
        diff_arr[index+2].sub!(/#{Regexp.escape(last_token)}$/, replace_token)
      end

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
      def find_first_token(first_line, second_line)
        max_length = [first_line.size, second_line.size].max
        first_the_same_symbols = 0

        (0..max_length + 1).each do |i|
          first_the_same_symbols = i - 1

          if first_line[i] != second_line[i] && i > 0
            break
          end
        end

        first_line[0..first_the_same_symbols][1..-1]
      end

      def find_last_token(first_line, second_line, first_token)
        max_length = [first_line.size, second_line.size].max
        last_the_same_symbols = 0

        (1..max_length + 1).each do |i|
          last_the_same_symbols = -i
          shortest_line = second_line.size > first_line.size ? first_line : second_line

          if (first_line[-i] != second_line[-i]) || "#{first_token}#{START}".size == shortest_line[1..-i].size
            break
          end
        end

        last_the_same_symbols += 1
        first_line[last_the_same_symbols..-1]
      end

81
      def _indexes_of_changed_lines(diff_arr)
V
Valeriy Sizov 已提交
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
        chain_of_first_symbols = ""
        diff_arr.each_with_index do |line, i|
          chain_of_first_symbols += line[0]
        end
        chain_of_first_symbols.gsub!(/[^\-\+]/, "#")

        offset = 0
        indexes = []
        while index = chain_of_first_symbols.index("#-+#", offset)
          indexes << index
          offset = index + 1
        end
        indexes
      end

97
      def replace_markers(line)
V
Valeriy Sizov 已提交
98 99 100 101 102 103 104
        line.gsub!(START, "<span class='idiff'>")
        line.gsub!(FINISH, "</span>")
        line
      end
    end
  end
end