ruvim 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/docs/binding.md +6 -0
- data/docs/command.md +16 -0
- data/docs/config.md +203 -84
- data/docs/lib_cleanup_report.md +79 -0
- data/docs/plugin.md +13 -15
- data/docs/spec.md +39 -22
- data/docs/todo.md +187 -10
- data/docs/tutorial.md +1 -1
- data/docs/vim_diff.md +2 -1
- data/lib/ruvim/app.rb +681 -123
- data/lib/ruvim/config_loader.rb +19 -5
- data/lib/ruvim/context.rb +0 -7
- data/lib/ruvim/dispatcher.rb +10 -0
- data/lib/ruvim/display_width.rb +25 -2
- data/lib/ruvim/editor.rb +173 -4
- data/lib/ruvim/global_commands.rb +500 -55
- data/lib/ruvim/input.rb +22 -10
- data/lib/ruvim/keyword_chars.rb +46 -0
- data/lib/ruvim/screen.rb +388 -53
- data/lib/ruvim/text_metrics.rb +26 -0
- data/lib/ruvim/version.rb +2 -2
- data/lib/ruvim/window.rb +35 -10
- data/lib/ruvim.rb +1 -0
- data/test/app_completion_test.rb +101 -0
- data/test/app_motion_test.rb +97 -2
- data/test/app_scenario_test.rb +270 -0
- data/test/app_startup_test.rb +10 -0
- data/test/config_loader_test.rb +37 -0
- data/test/dispatcher_test.rb +116 -0
- data/test/display_width_test.rb +18 -0
- data/test/fixtures/render_basic_snapshot.txt +7 -8
- data/test/fixtures/render_basic_snapshot_nonumber.txt +1 -2
- data/test/fixtures/render_unicode_scrolled_snapshot.txt +6 -7
- data/test/input_screen_integration_test.rb +26 -13
- data/test/screen_test.rb +166 -0
- data/test/window_test.rb +26 -0
- metadata +5 -1
data/lib/ruvim/screen.rb
CHANGED
|
@@ -19,17 +19,23 @@ module RuVim
|
|
|
19
19
|
text_cols = [text_cols, 1].max
|
|
20
20
|
|
|
21
21
|
rects = window_rects(editor, text_rows:, text_cols:)
|
|
22
|
+
if (current_rect = rects[editor.current_window_id])
|
|
23
|
+
editor.current_window_view_height_hint = [current_rect[:height].to_i, 1].max
|
|
24
|
+
end
|
|
22
25
|
editor.window_order.each do |win_id|
|
|
23
26
|
win = editor.windows.fetch(win_id)
|
|
24
27
|
buf = editor.buffers.fetch(win.buffer_id)
|
|
25
28
|
rect = rects[win_id]
|
|
26
29
|
next unless rect
|
|
27
30
|
content_width = [rect[:width] - number_column_width(editor, win, buf), 1].max
|
|
31
|
+
win.col_offset = 0 if wrap_enabled?(editor, win, buf)
|
|
28
32
|
win.ensure_visible(
|
|
29
33
|
buf,
|
|
30
34
|
height: [rect[:height], 1].max,
|
|
31
35
|
width: content_width,
|
|
32
|
-
tabstop: tabstop_for(editor, win, buf)
|
|
36
|
+
tabstop: tabstop_for(editor, win, buf),
|
|
37
|
+
scrolloff: editor.effective_option("scrolloff", window: win, buffer: buf),
|
|
38
|
+
sidescrolloff: editor.effective_option("sidescrolloff", window: win, buffer: buf)
|
|
33
39
|
)
|
|
34
40
|
end
|
|
35
41
|
|
|
@@ -52,7 +58,9 @@ module RuVim
|
|
|
52
58
|
text_rows = [text_rows, 1].max
|
|
53
59
|
text_cols = [text_cols, 1].max
|
|
54
60
|
rect = window_rects(editor, text_rows:, text_cols:)[editor.current_window_id]
|
|
55
|
-
[rect ? rect[:height].to_i : text_rows, 1].max
|
|
61
|
+
height = [rect ? rect[:height].to_i : text_rows, 1].max
|
|
62
|
+
editor.current_window_view_height_hint = height if editor.respond_to?(:current_window_view_height_hint=)
|
|
63
|
+
height
|
|
56
64
|
rescue StandardError
|
|
57
65
|
1
|
|
58
66
|
end
|
|
@@ -65,6 +73,7 @@ module RuVim
|
|
|
65
73
|
|
|
66
74
|
status_row = text_rows + 1
|
|
67
75
|
lines[status_row] = "\e[7m#{truncate(status_line(editor, cols), cols)}\e[m"
|
|
76
|
+
lines[status_row + 1] = ""
|
|
68
77
|
|
|
69
78
|
if editor.command_line_active?
|
|
70
79
|
cmd = editor.command_line
|
|
@@ -96,21 +105,15 @@ module RuVim
|
|
|
96
105
|
rect = rects[win_id]
|
|
97
106
|
next unless rect
|
|
98
107
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
render_window_row(editor, window, buffer, buffer_row, gutter_w:, content_w:)
|
|
109
|
-
else
|
|
110
|
-
line_number_prefix(editor, window, buffer, nil, gutter_w) + pad_plain_display("~", content_w)
|
|
111
|
-
end
|
|
112
|
-
lines[row_no] = text
|
|
113
|
-
end
|
|
108
|
+
window = editor.windows.fetch(win_id)
|
|
109
|
+
buffer = editor.buffers.fetch(window.buffer_id)
|
|
110
|
+
gutter_w = number_column_width(editor, window, buffer)
|
|
111
|
+
content_w = [rect[:width] - gutter_w, 1].max
|
|
112
|
+
rows = window_render_rows(editor, window, buffer, height: rect[:height], gutter_w:, content_w:)
|
|
113
|
+
rect[:height].times do |dy|
|
|
114
|
+
row_no = rect[:top] + dy
|
|
115
|
+
lines[row_no] = rows[dy] || (" " * rect[:width])
|
|
116
|
+
end
|
|
114
117
|
end
|
|
115
118
|
|
|
116
119
|
rects.each_value do |rect|
|
|
@@ -132,12 +135,12 @@ module RuVim
|
|
|
132
135
|
dy = row_no - rect[:top]
|
|
133
136
|
text =
|
|
134
137
|
if dy >= 0 && dy < rect[:height]
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
@__window_rows_cache ||= {}
|
|
139
|
+
key = [window.id, rect[:height], gutter_w, content_w, window.row_offset, window.col_offset, window.cursor_y, window.cursor_x,
|
|
140
|
+
editor.effective_option("wrap", window:, buffer:), editor.effective_option("linebreak", window:, buffer:),
|
|
141
|
+
editor.effective_option("breakindent", window:, buffer:), editor.effective_option("showbreak", window:, buffer:)]
|
|
142
|
+
@__window_rows_cache[key] ||= window_render_rows(editor, window, buffer, height: rect[:height], gutter_w:, content_w:)
|
|
143
|
+
@__window_rows_cache[key][dy] || (" " * rect[:width])
|
|
141
144
|
else
|
|
142
145
|
" " * rect[:width]
|
|
143
146
|
end
|
|
@@ -146,6 +149,7 @@ module RuVim
|
|
|
146
149
|
end
|
|
147
150
|
lines[row_no] = pieces
|
|
148
151
|
end
|
|
152
|
+
@__window_rows_cache = nil
|
|
149
153
|
end
|
|
150
154
|
|
|
151
155
|
def can_diff_render?(frame)
|
|
@@ -184,39 +188,268 @@ module RuVim
|
|
|
184
188
|
def render_text_line(text, editor, buffer_row:, window:, buffer:, width:)
|
|
185
189
|
tabstop = tabstop_for(editor, window, buffer)
|
|
186
190
|
cells, display_col = RuVim::TextMetrics.clip_cells_for_width(text, width, source_col_start: window.col_offset, tabstop:)
|
|
191
|
+
render_cells(cells, display_col, editor, buffer_row:, window:, buffer:, width:, source_line: buffer.line_at(buffer_row),
|
|
192
|
+
source_col_offset: window.col_offset, leading_display_prefix: "")
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def render_text_segment(source_line, editor, buffer_row:, window:, buffer:, width:, source_col_start:, display_prefix: "")
|
|
196
|
+
tabstop = tabstop_for(editor, window, buffer)
|
|
197
|
+
prefix = display_prefix.to_s
|
|
198
|
+
prefix_w = RuVim::DisplayWidth.display_width(prefix, tabstop:)
|
|
199
|
+
avail = [width - prefix_w, 0].max
|
|
200
|
+
cells, display_col = RuVim::TextMetrics.clip_cells_for_width(source_line[source_col_start..].to_s, avail, source_col_start:, tabstop:)
|
|
201
|
+
body = render_cells(cells, display_col, editor, buffer_row:, window:, buffer:, width: avail, source_line: source_line,
|
|
202
|
+
source_col_offset: source_col_start, leading_display_prefix: prefix)
|
|
203
|
+
if width <= 0
|
|
204
|
+
""
|
|
205
|
+
elsif prefix_w <= 0
|
|
206
|
+
body
|
|
207
|
+
else
|
|
208
|
+
prefix_render = RuVim::TextMetrics.pad_plain_to_screen_width(prefix, [width, 0].max, tabstop:)[0...prefix.length].to_s
|
|
209
|
+
# body already includes padding for avail; prepend the visible prefix and trim to width.
|
|
210
|
+
out = prefix_render + body
|
|
211
|
+
out
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def render_cells(cells, display_col, editor, buffer_row:, window:, buffer:, width:, source_line:, source_col_offset:, leading_display_prefix:)
|
|
187
216
|
highlighted = +""
|
|
217
|
+
tabstop = tabstop_for(editor, window, buffer)
|
|
188
218
|
visual = (editor.current_window_id == window.id && editor.visual_active?) ? editor.visual_selection(window) : nil
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
219
|
+
text_for_highlight = source_line[source_col_offset..].to_s
|
|
220
|
+
search_cols = search_highlight_source_cols(editor, text_for_highlight, source_col_offset: source_col_offset)
|
|
221
|
+
syntax_cols = syntax_highlight_source_cols(editor, window, buffer, text_for_highlight, source_col_offset: source_col_offset)
|
|
222
|
+
list_enabled = !!editor.effective_option("list", window:, buffer:)
|
|
223
|
+
listchars = parse_listchars(editor.effective_option("listchars", window:, buffer:))
|
|
224
|
+
tab_seen = {}
|
|
225
|
+
trail_from = source_line.rstrip.length
|
|
226
|
+
cursorline = !!editor.effective_option("cursorline", window:, buffer:)
|
|
227
|
+
current_line = (editor.current_window_id == window.id && window.cursor_y == buffer_row)
|
|
228
|
+
cursorline_enabled = cursorline && current_line
|
|
229
|
+
colorcolumns = colorcolumn_display_cols(editor, window, buffer)
|
|
230
|
+
leading_prefix_width = RuVim::DisplayWidth.display_width(leading_display_prefix.to_s, tabstop:)
|
|
231
|
+
display_pos = leading_prefix_width
|
|
232
|
+
|
|
233
|
+
cells.each do |cell|
|
|
234
|
+
ch = display_glyph_for_cell(cell, source_line, list_enabled:, listchars:, tab_seen:, trail_from:)
|
|
194
235
|
buffer_col = cell.source_col
|
|
195
236
|
selected = selected_in_visual?(visual, buffer_row, buffer_col)
|
|
196
237
|
cursor_here = (editor.current_window_id == window.id && window.cursor_y == buffer_row && window.cursor_x == buffer_col)
|
|
197
|
-
|
|
238
|
+
colorcolumn_here = colorcolumns[display_pos]
|
|
239
|
+
if cursor_here
|
|
240
|
+
highlighted << cursor_cell_render(editor, ch)
|
|
241
|
+
elsif selected
|
|
198
242
|
highlighted << "\e[7m#{ch}\e[m"
|
|
199
243
|
elsif search_cols[buffer_col]
|
|
200
|
-
highlighted << "
|
|
244
|
+
highlighted << "#{search_bg_seq(editor)}#{ch}\e[m"
|
|
245
|
+
elsif colorcolumn_here
|
|
246
|
+
highlighted << "#{colorcolumn_bg_seq(editor)}#{ch}\e[m"
|
|
247
|
+
elsif cursorline_enabled
|
|
248
|
+
highlighted << "#{cursorline_bg_seq(editor)}#{ch}\e[m"
|
|
201
249
|
elsif (syntax_color = syntax_cols[buffer_col])
|
|
202
250
|
highlighted << "#{syntax_color}#{ch}\e[m"
|
|
203
251
|
else
|
|
204
252
|
highlighted << ch
|
|
205
253
|
end
|
|
254
|
+
display_pos += [cell.display_width.to_i, 1].max
|
|
206
255
|
end
|
|
207
256
|
|
|
208
257
|
if editor.current_window_id == window.id && window.cursor_y == buffer_row
|
|
209
|
-
|
|
210
|
-
if
|
|
211
|
-
|
|
258
|
+
cursor_target = virtual_cursor_display_pos(source_line, window.cursor_x, source_col_offset:, tabstop:, leading_prefix_width:)
|
|
259
|
+
if cursor_target && cursor_target >= display_pos && cursor_target < width
|
|
260
|
+
gap = cursor_target - display_pos
|
|
261
|
+
if gap.positive?
|
|
262
|
+
highlighted << (" " * gap)
|
|
263
|
+
display_col += gap
|
|
264
|
+
display_pos += gap
|
|
265
|
+
end
|
|
266
|
+
highlighted << cursor_cell_render(editor, " ")
|
|
212
267
|
display_col += 1
|
|
268
|
+
display_pos += 1
|
|
213
269
|
end
|
|
214
270
|
end
|
|
215
271
|
|
|
216
|
-
|
|
272
|
+
trailing = [width - display_col, 0].max
|
|
273
|
+
if trailing.positive? && cursorline_enabled
|
|
274
|
+
trailing.times do
|
|
275
|
+
if colorcolumns[display_pos]
|
|
276
|
+
highlighted << "#{colorcolumn_bg_seq(editor)} \e[m"
|
|
277
|
+
else
|
|
278
|
+
highlighted << "#{cursorline_bg_seq(editor)} \e[m"
|
|
279
|
+
end
|
|
280
|
+
display_pos += 1
|
|
281
|
+
end
|
|
282
|
+
else
|
|
283
|
+
highlighted << (" " * trailing)
|
|
284
|
+
end
|
|
217
285
|
highlighted
|
|
218
286
|
end
|
|
219
287
|
|
|
288
|
+
def virtual_cursor_display_pos(source_line, cursor_x, source_col_offset:, tabstop:, leading_prefix_width:)
|
|
289
|
+
return nil if cursor_x < source_col_offset
|
|
290
|
+
|
|
291
|
+
base = RuVim::TextMetrics.screen_col_for_char_index(source_line, cursor_x, tabstop:) -
|
|
292
|
+
RuVim::TextMetrics.screen_col_for_char_index(source_line, source_col_offset, tabstop:)
|
|
293
|
+
extra = [cursor_x.to_i - source_line.to_s.length, 0].max
|
|
294
|
+
leading_prefix_width + [base, 0].max + extra
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def window_render_rows(editor, window, buffer, height:, gutter_w:, content_w:)
|
|
298
|
+
return plain_window_render_rows(editor, window, buffer, height:, gutter_w:, content_w:) unless wrap_enabled?(editor, window, buffer)
|
|
299
|
+
|
|
300
|
+
wrapped_window_render_rows(editor, window, buffer, height:, gutter_w:, content_w:)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def plain_window_render_rows(editor, window, buffer, height:, gutter_w:, content_w:)
|
|
304
|
+
Array.new(height) do |dy|
|
|
305
|
+
buffer_row = window.row_offset + dy
|
|
306
|
+
if buffer_row < buffer.line_count
|
|
307
|
+
render_window_row(editor, window, buffer, buffer_row, gutter_w:, content_w:)
|
|
308
|
+
else
|
|
309
|
+
line_number_prefix(editor, window, buffer, nil, gutter_w) + pad_plain_display("~", content_w)
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def wrapped_window_render_rows(editor, window, buffer, height:, gutter_w:, content_w:)
|
|
315
|
+
rows = []
|
|
316
|
+
row_idx = window.row_offset
|
|
317
|
+
while rows.length < height
|
|
318
|
+
if row_idx >= buffer.line_count
|
|
319
|
+
rows << (line_number_prefix(editor, window, buffer, nil, gutter_w) + pad_plain_display("~", content_w))
|
|
320
|
+
next
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
line = buffer.line_at(row_idx)
|
|
324
|
+
segments = wrapped_segments_for_line(editor, window, buffer, line, width: content_w)
|
|
325
|
+
segments.each_with_index do |seg, seg_i|
|
|
326
|
+
break if rows.length >= height
|
|
327
|
+
|
|
328
|
+
gutter = line_number_prefix(editor, window, buffer, seg_i.zero? ? row_idx : nil, gutter_w)
|
|
329
|
+
rows << gutter + render_text_segment(line, editor, buffer_row: row_idx, window:, buffer:, width: content_w,
|
|
330
|
+
source_col_start: seg[:source_col_start], display_prefix: seg[:display_prefix])
|
|
331
|
+
end
|
|
332
|
+
row_idx += 1
|
|
333
|
+
end
|
|
334
|
+
rows
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def wrap_enabled?(editor, window, buffer)
|
|
338
|
+
!!editor.effective_option("wrap", window:, buffer:)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def wrapped_segments_for_line(editor, window, buffer, line, width:)
|
|
342
|
+
return [{ source_col_start: 0, display_prefix: "" }] if width <= 0
|
|
343
|
+
|
|
344
|
+
tabstop = tabstop_for(editor, window, buffer)
|
|
345
|
+
linebreak = !!editor.effective_option("linebreak", window:, buffer:)
|
|
346
|
+
showbreak = editor.effective_option("showbreak", window:, buffer:).to_s
|
|
347
|
+
breakindent = !!editor.effective_option("breakindent", window:, buffer:)
|
|
348
|
+
indent_prefix = breakindent ? wrapped_indent_prefix(line, tabstop:, max_width: [width - RuVim::DisplayWidth.display_width(showbreak, tabstop:), 0].max) : ""
|
|
349
|
+
|
|
350
|
+
segs = []
|
|
351
|
+
start_col = 0
|
|
352
|
+
first = true
|
|
353
|
+
line = line.to_s
|
|
354
|
+
if line.empty?
|
|
355
|
+
return [{ source_col_start: 0, display_prefix: "" }]
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
while start_col < line.length
|
|
359
|
+
display_prefix = first ? "" : "#{showbreak}#{indent_prefix}"
|
|
360
|
+
prefix_w = RuVim::DisplayWidth.display_width(display_prefix, tabstop:)
|
|
361
|
+
avail = [width - prefix_w, 1].max
|
|
362
|
+
cells, = RuVim::TextMetrics.clip_cells_for_width(line[start_col..].to_s, avail, source_col_start: start_col, tabstop:)
|
|
363
|
+
if cells.empty?
|
|
364
|
+
segs << { source_col_start: start_col, display_prefix: display_prefix }
|
|
365
|
+
break
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
if linebreak && cells.length > 1
|
|
369
|
+
break_idx = linebreak_break_index(cells, line)
|
|
370
|
+
if break_idx && break_idx < cells.length - 1
|
|
371
|
+
cells = cells[0..break_idx]
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
segs << { source_col_start: start_col, display_prefix: display_prefix }
|
|
376
|
+
next_start = cells.last.source_col.to_i + 1
|
|
377
|
+
if linebreak
|
|
378
|
+
next_start += 1 while next_start < line.length && line[next_start] == " "
|
|
379
|
+
end
|
|
380
|
+
break if next_start <= start_col
|
|
381
|
+
|
|
382
|
+
start_col = next_start
|
|
383
|
+
first = false
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
segs
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def linebreak_break_index(cells, line)
|
|
390
|
+
idx = nil
|
|
391
|
+
cells.each_with_index do |cell, i|
|
|
392
|
+
ch = line[cell.source_col]
|
|
393
|
+
idx = i if ch =~ /\s/
|
|
394
|
+
end
|
|
395
|
+
idx
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def wrapped_indent_prefix(line, tabstop:, max_width:)
|
|
399
|
+
indent = line.to_s[/\A[ \t]*/].to_s
|
|
400
|
+
return "" if indent.empty? || max_width <= 0
|
|
401
|
+
|
|
402
|
+
RuVim::TextMetrics.pad_plain_to_screen_width(indent, max_width, tabstop:)[0...indent.length].to_s
|
|
403
|
+
rescue StandardError
|
|
404
|
+
""
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def display_glyph_for_cell(cell, source_line, list_enabled:, listchars:, tab_seen:, trail_from:)
|
|
408
|
+
return cell.glyph unless list_enabled
|
|
409
|
+
|
|
410
|
+
src = source_line[cell.source_col]
|
|
411
|
+
case src
|
|
412
|
+
when "\t"
|
|
413
|
+
first = !tab_seen[cell.source_col]
|
|
414
|
+
tab_seen[cell.source_col] = true
|
|
415
|
+
first ? listchars[:tab_head] : listchars[:tab_fill]
|
|
416
|
+
when " "
|
|
417
|
+
cell.source_col >= trail_from ? listchars[:trail] : cell.glyph
|
|
418
|
+
when "\u00A0"
|
|
419
|
+
listchars[:nbsp]
|
|
420
|
+
else
|
|
421
|
+
cell.glyph
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def parse_listchars(raw)
|
|
426
|
+
raw_key = raw.to_s
|
|
427
|
+
@listchars_cache ||= {}
|
|
428
|
+
return @listchars_cache[raw_key] if @listchars_cache.key?(raw_key)
|
|
429
|
+
|
|
430
|
+
cfg = { tab_head: ">", tab_fill: "-", trail: "-", nbsp: "+" }
|
|
431
|
+
raw_key.split(",").each do |entry|
|
|
432
|
+
entry_key, val = entry.split(":", 2)
|
|
433
|
+
next unless entry_key && val
|
|
434
|
+
|
|
435
|
+
case entry_key.strip
|
|
436
|
+
when "tab"
|
|
437
|
+
chars = val.to_s.each_char.to_a
|
|
438
|
+
cfg[:tab_head] = chars[0] if chars[0]
|
|
439
|
+
cfg[:tab_fill] = chars[1] if chars[1]
|
|
440
|
+
when "trail"
|
|
441
|
+
ch = val.to_s.each_char.first
|
|
442
|
+
cfg[:trail] = ch if ch
|
|
443
|
+
when "nbsp"
|
|
444
|
+
ch = val.to_s.each_char.first
|
|
445
|
+
cfg[:nbsp] = ch if ch
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
@listchars_cache[raw_key] = cfg.freeze
|
|
449
|
+
rescue StandardError
|
|
450
|
+
{ tab_head: ">", tab_fill: "-", trail: "-", nbsp: "+" }
|
|
451
|
+
end
|
|
452
|
+
|
|
220
453
|
def render_window_row(editor, window, buffer, buffer_row, gutter_w:, content_w:)
|
|
221
454
|
line = buffer.line_at(buffer_row)
|
|
222
455
|
line = line[window.col_offset..] || ""
|
|
@@ -234,18 +467,24 @@ module RuVim
|
|
|
234
467
|
end
|
|
235
468
|
|
|
236
469
|
def number_column_width(editor, window, buffer)
|
|
470
|
+
sign_w = sign_column_width(editor, window, buffer)
|
|
237
471
|
enabled = editor.effective_option("number", window:, buffer:) || editor.effective_option("relativenumber", window:, buffer:)
|
|
238
|
-
return
|
|
472
|
+
return sign_w unless enabled
|
|
239
473
|
|
|
240
|
-
[buffer.line_count.to_s.length, 1].max
|
|
474
|
+
base = [buffer.line_count.to_s.length, 1].max
|
|
475
|
+
minw = editor.effective_option("numberwidth", window:, buffer:).to_i
|
|
476
|
+
sign_w + ([[base, minw].max, 1].max + 1)
|
|
241
477
|
end
|
|
242
478
|
|
|
243
479
|
def line_number_prefix(editor, window, buffer, buffer_row, width)
|
|
244
480
|
return "" if width <= 0
|
|
481
|
+
sign_w = sign_column_width(editor, window, buffer)
|
|
482
|
+
sign = " " * sign_w
|
|
483
|
+
num_width = [width - sign_w, 0].max
|
|
245
484
|
show_abs = editor.effective_option("number", window:, buffer:)
|
|
246
485
|
show_rel = editor.effective_option("relativenumber", window:, buffer:)
|
|
247
|
-
return " " *
|
|
248
|
-
return " " *
|
|
486
|
+
return sign + (" " * num_width) unless show_abs || show_rel
|
|
487
|
+
return sign + (" " * num_width) if buffer_row.nil?
|
|
249
488
|
|
|
250
489
|
num =
|
|
251
490
|
if show_rel && buffer_row != window.cursor_y
|
|
@@ -255,13 +494,72 @@ module RuVim
|
|
|
255
494
|
else
|
|
256
495
|
"0"
|
|
257
496
|
end
|
|
258
|
-
num.rjust(
|
|
497
|
+
sign + num.rjust([num_width - 1, 0].max) + (num_width.positive? ? " " : "")
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def sign_column_width(editor, window, buffer)
|
|
501
|
+
raw = editor.effective_option("signcolumn", window:, buffer:).to_s
|
|
502
|
+
case raw
|
|
503
|
+
when "", "auto", "number"
|
|
504
|
+
0
|
|
505
|
+
when "no"
|
|
506
|
+
0
|
|
507
|
+
else
|
|
508
|
+
if (m = /\Ayes(?::(\d+))?\z/.match(raw))
|
|
509
|
+
n = m[1].to_i
|
|
510
|
+
n = 1 if n <= 0
|
|
511
|
+
n
|
|
512
|
+
else
|
|
513
|
+
1
|
|
514
|
+
end
|
|
515
|
+
end
|
|
516
|
+
rescue StandardError
|
|
517
|
+
0
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
def colorcolumn_display_cols(editor, window, buffer)
|
|
521
|
+
raw = editor.effective_option("colorcolumn", window:, buffer:).to_s
|
|
522
|
+
return {} if raw.empty?
|
|
523
|
+
|
|
524
|
+
@colorcolumn_cache ||= {}
|
|
525
|
+
return @colorcolumn_cache[raw] if @colorcolumn_cache.key?(raw)
|
|
526
|
+
|
|
527
|
+
cols = {}
|
|
528
|
+
raw.split(",").each do |tok|
|
|
529
|
+
t = tok.strip
|
|
530
|
+
next if t.empty?
|
|
531
|
+
next unless t.match?(/\A\d+\z/)
|
|
532
|
+
n = t.to_i
|
|
533
|
+
next if n <= 0
|
|
534
|
+
cols[n - 1] = true
|
|
535
|
+
end
|
|
536
|
+
@colorcolumn_cache[raw] = cols.freeze
|
|
537
|
+
rescue StandardError
|
|
538
|
+
{}
|
|
259
539
|
end
|
|
260
540
|
|
|
261
541
|
def pad_plain_display(text, width)
|
|
262
542
|
RuVim::TextMetrics.pad_plain_to_screen_width(text, width, tabstop: DEFAULT_TABSTOP)
|
|
263
543
|
end
|
|
264
544
|
|
|
545
|
+
def search_bg_seq(editor)
|
|
546
|
+
truecolor_enabled?(editor) ? "\e[48;2;255;215;0m" : "\e[43m"
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
def colorcolumn_bg_seq(editor)
|
|
550
|
+
truecolor_enabled?(editor) ? "\e[48;2;72;72;72m" : "\e[48;5;238m"
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
def cursorline_bg_seq(editor)
|
|
554
|
+
truecolor_enabled?(editor) ? "\e[48;2;58;58;58m" : "\e[48;5;236m"
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
def truecolor_enabled?(editor)
|
|
558
|
+
!!editor.effective_option("termguicolors")
|
|
559
|
+
rescue StandardError
|
|
560
|
+
false
|
|
561
|
+
end
|
|
562
|
+
|
|
265
563
|
def status_line(editor, width)
|
|
266
564
|
buffer = editor.current_buffer
|
|
267
565
|
window = editor.current_window
|
|
@@ -275,14 +573,9 @@ module RuVim
|
|
|
275
573
|
end
|
|
276
574
|
|
|
277
575
|
path = buffer.display_name
|
|
278
|
-
ft = editor.effective_option("filetype", buffer:, window:) || File.extname(buffer.path.to_s).delete_prefix(".")
|
|
279
|
-
ft = "-" if ft.empty?
|
|
280
576
|
mod = buffer.modified? ? " [+]" : ""
|
|
281
577
|
msg = editor.message_error? ? "" : editor.message.to_s
|
|
282
|
-
|
|
283
|
-
win_total = editor.window_order.length
|
|
284
|
-
tab_info = "t#{editor.current_tabpage_number}/#{editor.tabpage_count}"
|
|
285
|
-
left = "#{mode} #{tab_info} w#{win_idx}/#{win_total} b#{buffer.id} #{path} [ft=#{ft}]#{mod}"
|
|
578
|
+
left = "#{mode} #{path}#{mod}"
|
|
286
579
|
right = " #{window.cursor_y + 1}:#{window.cursor_x + 1} "
|
|
287
580
|
body_width = [width - right.length, 0].max
|
|
288
581
|
"#{compose_status_body(left, msg, body_width)}#{right}"
|
|
@@ -303,13 +596,21 @@ module RuVim
|
|
|
303
596
|
end
|
|
304
597
|
|
|
305
598
|
def truncate(str, width)
|
|
306
|
-
str.
|
|
599
|
+
RuVim::TextMetrics.terminal_safe_text(str).ljust(width)[0, width]
|
|
307
600
|
end
|
|
308
601
|
|
|
309
602
|
def error_message_line(msg, cols)
|
|
310
603
|
"\e[97;41m#{truncate(msg, cols)}\e[m"
|
|
311
604
|
end
|
|
312
605
|
|
|
606
|
+
def cursor_cell_render(editor, ch)
|
|
607
|
+
"#{cursor_cell_seq(editor)}#{ch}\e[m"
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
def cursor_cell_seq(editor)
|
|
611
|
+
"\e[7m"
|
|
612
|
+
end
|
|
613
|
+
|
|
313
614
|
def cursor_screen_position(editor, text_rows, rects)
|
|
314
615
|
window = editor.current_window
|
|
315
616
|
|
|
@@ -320,13 +621,47 @@ module RuVim
|
|
|
320
621
|
end
|
|
321
622
|
|
|
322
623
|
rect = rects[window.id] || { top: 1, left: 1 }
|
|
323
|
-
|
|
324
|
-
line =
|
|
325
|
-
gutter_w = number_column_width(editor, window,
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
624
|
+
buffer = editor.current_buffer
|
|
625
|
+
line = buffer.line_at(window.cursor_y)
|
|
626
|
+
gutter_w = number_column_width(editor, window, buffer)
|
|
627
|
+
content_w = [rect[:width] - gutter_w, 1].max
|
|
628
|
+
tabstop = tabstop_for(editor, window, buffer)
|
|
629
|
+
if wrap_enabled?(editor, window, buffer)
|
|
630
|
+
visual_rows_before = 0
|
|
631
|
+
row = window.row_offset
|
|
632
|
+
while row < window.cursor_y
|
|
633
|
+
visual_rows_before += wrapped_segments_for_line(editor, window, buffer, buffer.line_at(row), width: content_w).length
|
|
634
|
+
row += 1
|
|
635
|
+
end
|
|
636
|
+
segs = wrapped_segments_for_line(editor, window, buffer, line, width: content_w)
|
|
637
|
+
seg_index = 0
|
|
638
|
+
segs.each_with_index do |seg, i|
|
|
639
|
+
nxt = segs[i + 1]
|
|
640
|
+
if nxt.nil? || window.cursor_x < nxt[:source_col_start]
|
|
641
|
+
seg_index = i
|
|
642
|
+
break
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
seg = segs[seg_index] || { source_col_start: 0, display_prefix: "" }
|
|
646
|
+
row = rect[:top] + visual_rows_before + seg_index
|
|
647
|
+
seg_prefix_w = RuVim::DisplayWidth.display_width(seg[:display_prefix].to_s, tabstop:)
|
|
648
|
+
extra_virtual = [window.cursor_x - line.length, 0].max
|
|
649
|
+
cursor_sc = RuVim::TextMetrics.screen_col_for_char_index(line, window.cursor_x, tabstop:) + extra_virtual
|
|
650
|
+
seg_sc = RuVim::TextMetrics.screen_col_for_char_index(line, seg[:source_col_start], tabstop:)
|
|
651
|
+
col = rect[:left] + gutter_w + seg_prefix_w + [cursor_sc - seg_sc, 0].max
|
|
652
|
+
else
|
|
653
|
+
row = rect[:top] + (window.cursor_y - window.row_offset)
|
|
654
|
+
extra_virtual = [window.cursor_x - line.length, 0].max
|
|
655
|
+
prefix_screen_col = RuVim::TextMetrics.screen_col_for_char_index(line, window.cursor_x, tabstop:) -
|
|
656
|
+
RuVim::TextMetrics.screen_col_for_char_index(line, window.col_offset, tabstop:)
|
|
657
|
+
col = rect[:left] + gutter_w + [prefix_screen_col, 0].max + extra_virtual
|
|
658
|
+
end
|
|
659
|
+
min_row = [rect[:top].to_i, 1].max
|
|
660
|
+
max_row = [rect[:top].to_i + [rect[:height].to_i, 1].max - 1, min_row].max
|
|
661
|
+
min_col = [rect[:left].to_i, 1].max
|
|
662
|
+
max_col = [rect[:left].to_i + [rect[:width].to_i, 1].max - 1, min_col].max
|
|
663
|
+
row = [[row.to_i, min_row].max, max_row].min
|
|
664
|
+
col = [[col.to_i, min_col].max, max_col].min
|
|
330
665
|
[row, col]
|
|
331
666
|
end
|
|
332
667
|
|
data/lib/ruvim/text_metrics.rb
CHANGED
|
@@ -76,6 +76,15 @@ module RuVim
|
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
w = RuVim::DisplayWidth.cell_width(ch, col: display_col, tabstop:)
|
|
79
|
+
if terminal_unsafe_control_char?(ch)
|
|
80
|
+
w = [w, 1].max
|
|
81
|
+
break if display_col + w > max_width
|
|
82
|
+
|
|
83
|
+
cells << Cell.new(glyph: terminal_safe_placeholder(ch), source_col:, display_width: w)
|
|
84
|
+
display_col += w
|
|
85
|
+
source_col += 1
|
|
86
|
+
next
|
|
87
|
+
end
|
|
79
88
|
break if display_col + w > max_width
|
|
80
89
|
|
|
81
90
|
cells << Cell.new(glyph: ch, source_col:, display_width: w)
|
|
@@ -92,5 +101,22 @@ module RuVim
|
|
|
92
101
|
out << (" " * [width.to_i - used, 0].max)
|
|
93
102
|
out
|
|
94
103
|
end
|
|
104
|
+
|
|
105
|
+
def terminal_safe_text(text)
|
|
106
|
+
text.to_s.each_char.map { |ch| terminal_unsafe_control_char?(ch) ? terminal_safe_placeholder(ch) : ch }.join
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def terminal_unsafe_control_char?(ch)
|
|
110
|
+
return false if ch.nil? || ch.empty? || ch == "\t"
|
|
111
|
+
|
|
112
|
+
code = ch.ord
|
|
113
|
+
(code >= 0x00 && code < 0x20) || code == 0x7F || (0x80..0x9F).cover?(code)
|
|
114
|
+
rescue StandardError
|
|
115
|
+
false
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def terminal_safe_placeholder(_ch)
|
|
119
|
+
"?"
|
|
120
|
+
end
|
|
95
121
|
end
|
|
96
122
|
end
|
data/lib/ruvim/version.rb
CHANGED