BigW Consortium Gitlab

string_range_marker.rb 3.02 KB
Newer Older
1 2
module Gitlab
  class StringRangeMarker
3
    attr_accessor :raw_line, :rich_line, :html_escaped
4

5 6 7 8 9 10 11 12 13
    def initialize(raw_line, rich_line = nil)
      @raw_line = raw_line.dup
      if rich_line.nil?
        @rich_line = raw_line.dup
        @html_escaped = false
      else
        @rich_line = ERB::Util.html_escape(rich_line)
        @html_escaped = true
      end
14 15 16 17 18
    end

    def mark(marker_ranges)
      return rich_line unless marker_ranges

19 20 21 22 23 24 25 26 27 28
      if html_escaped
        rich_marker_ranges = []
        marker_ranges.each do |range|
          # Map the inline-diff range based on the raw line to character positions in the rich line
          rich_positions = position_mapping[range].flatten
          # Turn the array of character positions into ranges
          rich_marker_ranges.concat(collapse_ranges(rich_positions))
        end
      else
        rich_marker_ranges = marker_ranges
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
      end

      offset = 0
      # Mark each range
      rich_marker_ranges.each_with_index do |range, i|
        offset_range = (range.begin + offset)..(range.end + offset)
        original_text = rich_line[offset_range]

        text = yield(original_text, left: i == 0, right: i == rich_marker_ranges.length - 1)

        rich_line[offset_range] = text

        offset += text.length - original_text.length
      end

44
      @html_escaped ? rich_line.html_safe : rich_line
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 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
    end

    private

    # Mapping of character positions in the raw line, to the rich (highlighted) line
    def position_mapping
      @position_mapping ||= begin
        mapping = []
        rich_pos = 0
        (0..raw_line.length).each do |raw_pos|
          rich_char = rich_line[rich_pos]

          # The raw and rich lines are the same except for HTML tags,
          # so skip over any `<...>` segment
          while rich_char == '<'
            until rich_char == '>'
              rich_pos += 1
              rich_char = rich_line[rich_pos]
            end

            rich_pos += 1
            rich_char = rich_line[rich_pos]
          end

          # multi-char HTML entities in the rich line correspond to a single character in the raw line
          if rich_char == '&'
            multichar_mapping = [rich_pos]
            until rich_char == ';'
              rich_pos += 1
              multichar_mapping << rich_pos
              rich_char = rich_line[rich_pos]
            end

            mapping[raw_pos] = multichar_mapping
          else
            mapping[raw_pos] = rich_pos
          end

          rich_pos += 1
        end

        mapping
      end
    end

    # Takes an array of integers, and returns an array of ranges covering the same integers
    def collapse_ranges(positions)
      return [] if positions.empty?
93

94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
      ranges = []

      start = prev = positions[0]
      range = start..prev
      positions[1..-1].each do |pos|
        if pos == prev + 1
          range = start..pos
          prev = pos
        else
          ranges << range
          start = prev = pos
          range = start..prev
        end
      end
      ranges << range

      ranges
    end
  end
end