BigW Consortium Gitlab

inline_diff.rb 2.27 KB
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