gitlab-org--gitlab-foss/lib/gitlab/diff/pair_selector.rb

59 lines
1.8 KiB
Ruby

# frozen_string_literal: true
module Gitlab
module Diff
class PairSelector
include Enumerable
# Regex to find a run of deleted lines followed by the same number of added lines
# rubocop: disable Lint/MixedRegexpCaptureTypes
LINE_PAIRS_PATTERN = %r{
# Runs start at the beginning of the string (the first line) or after a space (for an unchanged line)
(?:\A|\s)
# This matches a number of `-`s followed by the same number of `+`s through recursion
(?<del_ins>
-
\g<del_ins>?
\+
)
# Runs end at the end of the string (the last line) or before a space (for an unchanged line)
(?=\s|\z)
}x.freeze
# rubocop: enable Lint/MixedRegexpCaptureTypes
def initialize(lines)
@lines = lines
end
# Finds pairs of old/new line pairs that represent the same line that changed
# rubocop: disable CodeReuse/ActiveRecord
def each
# Prefixes of all diff lines, indicating their types
# For example: `" - + -+ ---+++ --+ -++"`
line_prefixes = lines.each_with_object(+"") { |line, s| s << (line[0] || ' ') }.gsub(/[^ +-]/, ' ')
line_prefixes.scan(LINE_PAIRS_PATTERN) do
# For `"---+++"`, `begin_index == 0`, `end_index == 6`
begin_index, end_index = Regexp.last_match.offset(:del_ins)
# For `"---+++"`, `changed_line_count == 3`
changed_line_count = (end_index - begin_index) / 2
halfway_index = begin_index + changed_line_count
(begin_index...halfway_index).each do |i|
# For `"---+++"`, index 1 maps to 1 + 3 = 4
yield [i, i + changed_line_count]
end
end
end
# rubocop: enable CodeReuse/ActiveRecord
private
attr_reader :lines
end
end
end