# 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