summaryrefslogtreecommitdiff
path: root/lib/gitlab/diff/char_diff.rb
blob: c8bb39e9f5dedeed7e13e25fceb933ef0e82d973 (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
# frozen_string_literal: true

module Gitlab
  module Diff
    class CharDiff
      include Gitlab::Utils::StrongMemoize

      def initialize(old_string, new_string)
        @old_string = old_string.to_s
        @new_string = new_string.to_s
        @changes = []
      end

      def generate_diff
        @changes = diff_match_patch.diff_main(@old_string, @new_string)
        diff_match_patch.diff_cleanupSemantic(@changes)

        @changes
      end

      def changed_ranges(offset: 0)
        old_diffs = []
        new_diffs = []
        new_pointer = old_pointer = offset

        generate_diff.each do |(action, content)|
          content_size = content.size

          if action == :equal
            new_pointer += content_size
            old_pointer += content_size
          end

          if action == :delete
            old_diffs << (old_pointer..(old_pointer + content_size - 1))
            old_pointer += content_size
          end

          if action == :insert
            new_diffs << (new_pointer..(new_pointer + content_size - 1))
            new_pointer += content_size
          end
        end

        [old_diffs, new_diffs]
      end

      def to_html
        @changes.map do |op, text|
          %{<span class="#{html_class_names(op)}">#{ERB::Util.html_escape(text)}</span>}
        end.join.html_safe
      end

      private

      def diff_match_patch
        strong_memoize(:diff_match_patch) { DiffMatchPatch.new }
      end

      def html_class_names(operation)
        class_names = ['idiff']

        case operation
        when :insert
          class_names << 'addition'
        when :delete
          class_names << 'deletion'
        end

        class_names.join(' ')
      end
    end
  end
end