Module: RuVim::RichView::TableRenderer

Defined in:
lib/ruvim/rich_view/table_renderer.rb

Constant Summary collapse

SEPARATOR =
" | "
SEPARATOR_WIDTH =
3

Class Method Summary collapse

Class Method Details

.compute_col_widths(lines, delimiter:) ⇒ Object

Compute max display width per column from visible lines. Returns an Array of column widths, or nil if single-column / empty.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/ruvim/rich_view/table_renderer.rb', line 21

def compute_col_widths(lines, delimiter:)
  return nil if lines.nil? || lines.empty?

  rows = lines.map { |line| split_fields(line, delimiter) }
  max_cols = rows.map(&:length).max
  return nil if max_cols.nil? || max_cols <= 1

  col_widths = Array.new(max_cols, 0)
  rows.each do |fields|
    fields.each_with_index do |field, i|
      w = RuVim::DisplayWidth.display_width(field)
      col_widths[i] = w if w > col_widths[i]
    end
  end
  col_widths
end

.cursor_display_col(raw_line, cursor_x, visible_lines:, delimiter:) ⇒ Object

Compute the display column for the cursor position in a formatted line.



93
94
95
96
97
98
# File 'lib/ruvim/rich_view/table_renderer.rb', line 93

def cursor_display_col(raw_line, cursor_x, visible_lines:, delimiter:)
  col_widths = compute_col_widths(visible_lines, delimiter: delimiter)
  return RuVim::TextMetrics.screen_col_for_char_index(raw_line, cursor_x) unless col_widths

  raw_to_formatted_display_col(raw_line, cursor_x, delimiter: delimiter, col_widths: col_widths)
end

.delimiter_for(format) ⇒ Object



11
12
13
14
15
16
17
# File 'lib/ruvim/rich_view/table_renderer.rb', line 11

def delimiter_for(format)
  case format
  when :csv then ","
  when :tsv then "\t"
  else "\t"
  end
end

.format_line(raw_line, delimiter:, col_widths:) ⇒ Object

Format a single raw line using pre-computed column widths.



39
40
41
42
43
44
45
46
47
48
# File 'lib/ruvim/rich_view/table_renderer.rb', line 39

def format_line(raw_line, delimiter:, col_widths:)
  fields = split_fields(raw_line, delimiter)
  padded = fields.each_with_index.map do |field, i|
    pad_field(field, col_widths[i] || 0)
  end
  (fields.length...col_widths.length).each do |i|
    padded << " " * col_widths[i]
  end
  padded.join(SEPARATOR)
end

.pad_field(field, target_width) ⇒ Object

Pad a field to a target display width using spaces.



169
170
171
172
173
# File 'lib/ruvim/rich_view/table_renderer.rb', line 169

def pad_field(field, target_width)
  current = RuVim::DisplayWidth.display_width(field)
  gap = target_width - current
  gap > 0 ? "#{field}#{' ' * gap}" : field
end

.parse_csv_fields(line) ⇒ Object

Minimal CSV field parser: handles double-quoted fields with embedded commas and escaped quotes ("").



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/ruvim/rich_view/table_renderer.rb', line 123

def parse_csv_fields(line)
  fields = []
  s = line.to_s
  pos = 0

  while pos <= s.length
    if pos < s.length && s[pos] == '"'
      # Quoted field
      pos += 1
      field = +""
      while pos < s.length
        if s[pos] == '"'
          if pos + 1 < s.length && s[pos + 1] == '"'
            field << '"'
            pos += 2
          else
            pos += 1
            break
          end
        else
          field << s[pos]
          pos += 1
        end
      end
      fields << field
      # Skip comma after quoted field
      pos += 1 if pos < s.length && s[pos] == ','
    else
      # Unquoted field
      comma_idx = s.index(',', pos)
      if comma_idx
        fields << s[pos...comma_idx]
        pos = comma_idx + 1
        # Trailing comma means empty last field
        fields << "" if pos == s.length
      else
        fields << s[pos..]
        break
      end
    end
  end

  fields
end

.raw_to_formatted_char_index(raw_line, raw_char_index, delimiter:, col_widths:) ⇒ Object

Map a character index in the raw line to the corresponding index in the formatted line. Needed for correct horizontal scrolling and cursor placement in rich mode.



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/ruvim/rich_view/table_renderer.rb', line 53

def raw_to_formatted_char_index(raw_line, raw_char_index, delimiter:, col_widths:)
  fields = split_fields(raw_line, delimiter)
  pos = 0
  formatted_pos = 0
  fields.each_with_index do |field, i|
    field_end = pos + field.length
    if raw_char_index <= field_end
      offset = [raw_char_index - pos, field.length].min
      return formatted_pos + offset
    end
    pos = field_end + 1 # skip delimiter
    field_dw = RuVim::DisplayWidth.display_width(field)
    pad_chars = [(col_widths[i] || 0) - field_dw, 0].max
    formatted_pos += field.length + pad_chars + SEPARATOR.length
  end
  formatted_pos
end

.raw_to_formatted_display_col(raw_line, raw_char_index, delimiter:, col_widths:) ⇒ Object

Map a character index in the raw line to the display column in the formatted line. Unlike raw_to_formatted_char_index (which returns a character index), this returns the screen column directly, which is needed for display-column-based horizontal scrolling.



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/ruvim/rich_view/table_renderer.rb', line 75

def raw_to_formatted_display_col(raw_line, raw_char_index, delimiter:, col_widths:)
  fields = split_fields(raw_line, delimiter)
  pos = 0
  display_col = 0
  fields.each_with_index do |field, i|
    field_end = pos + field.length
    if raw_char_index <= field_end
      offset_chars = [raw_char_index - pos, field.length].min
      offset_dw = RuVim::DisplayWidth.display_width(field[0...offset_chars])
      return display_col + offset_dw
    end
    display_col += (col_widths[i] || 0) + SEPARATOR_WIDTH
    pos = field_end + 1
  end
  display_col
end

.render_visible(lines, delimiter:) ⇒ Object

Render visible lines: split by delimiter, compute column widths, pad and join. Returns an array of formatted strings.



102
103
104
105
106
107
108
109
# File 'lib/ruvim/rich_view/table_renderer.rb', line 102

def render_visible(lines, delimiter:)
  return lines if lines.nil? || lines.empty?

  col_widths = compute_col_widths(lines, delimiter:)
  return lines unless col_widths

  lines.map { |line| format_line(line, delimiter:, col_widths:) }
end

.split_fields(line, delimiter) ⇒ Object

Split a line into fields respecting the delimiter. For CSV, handles quoted fields minimally.



113
114
115
116
117
118
119
# File 'lib/ruvim/rich_view/table_renderer.rb', line 113

def split_fields(line, delimiter)
  if delimiter == ","
    parse_csv_fields(line)
  else
    line.to_s.split(delimiter, -1)
  end
end