Files
gitlab-ce/lib/gitlab/diff/viewer_hunk.rb
2025-06-07 06:12:13 +00:00

138 lines
3.8 KiB
Ruby

# frozen_string_literal: true
module Gitlab
module Diff
class ViewerHunk
attr_reader :header
attr_accessor :lines
def self.init_from_diff_lines(diff_lines)
return [] if diff_lines.empty?
hunks = []
current_hunk = nil
line_count = diff_lines.size
diff_lines.each_with_index do |line, index|
current_line = line
is_match = current_line.type == 'match'
if is_match || current_hunk.nil?
if is_match
previous_line_pos = hunks.last&.lines&.last&.old_pos
next_line = index < line_count - 1 ? diff_lines[index + 1] : nil
next_line_pos = next_line&.old_pos
current_hunk = new(
lines: [],
header: ViewerHunkHeader.new(current_line, previous_line_pos, next_line_pos)
)
else
current_hunk = new(lines: [line])
end
hunks << current_hunk
else
current_hunk.lines << line
end
end
hunks
end
def self.init_from_expanded_lines(diff_lines, bottom, closest_line_number)
return if diff_lines.empty?
match_lines, non_match_lines = diff_lines.partition { |line| line.type == 'match' }
return if non_match_lines.empty?
# this is different from init_from_diff_lines which uses old_pos to determine lines in between
# frontend always sends closest_line_number as new_pos since that is what is used by UnfoldPresenter#diff_lines
first_pos = non_match_lines.first.new_pos
last_pos = non_match_lines.last.new_pos
closest_line_number = 0 unless valid_closest_line_number?(bottom, closest_line_number, first_pos, last_pos)
header = if match_lines.first
start_line, end_line = bottom ? [last_pos, closest_line_number] : [closest_line_number, first_pos]
ViewerHunkHeader.new(match_lines.first, start_line, end_line)
end
create_viewer_hunks(non_match_lines, header, bottom)
end
def self.create_viewer_hunks(non_match_lines, header, bottom)
if bottom
[new(lines: non_match_lines), new(lines: nil, header: header)]
else
[new(lines: non_match_lines, header: header)]
end
end
private_class_method :create_viewer_hunks
def self.valid_closest_line_number?(bottom, closest_line_number, first_pos, last_pos)
return false if closest_line_number.nil? || closest_line_number < 0
closest_line_number == 0 || (
if bottom
last_pos < closest_line_number
else
closest_line_number < first_pos
end
)
end
private_class_method :valid_closest_line_number?
def initialize(lines: [], header: nil)
@lines = lines
@header = header
end
def parallel_lines
::Gitlab::Diff::ParallelDiff.parallelize(lines)
end
end
class ViewerHunkHeader
attr_reader :line
MAX_EXPANDABLE_LINES = 20
def initialize(line, previous_line_pos, next_line_pos)
@line = line
@previous_line_pos = previous_line_pos
@next_line_pos = next_line_pos
end
def text
@line.text
end
def expand_directions
return [:both] if (1..MAX_EXPANDABLE_LINES).cover?(line_count_between)
directions = []
directions << :up if @next_line_pos&.positive?
directions << :down if @previous_line_pos&.positive?
directions
end
private
def line_count_between
return 0 if invalid_pos?(@previous_line_pos) || invalid_pos?(@next_line_pos)
# 1..3 -> lines in between: 1
@next_line_pos - @previous_line_pos - 1
end
def invalid_pos?(pos)
pos.nil? || pos == 0
end
end
end
end