ruvim 0.2.0 → 0.4.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/.github/workflows/test.yml +4 -0
- data/AGENTS.md +96 -0
- data/CLAUDE.md +1 -0
- data/README.md +15 -1
- data/docs/binding.md +39 -0
- data/docs/command.md +163 -4
- data/docs/config.md +12 -4
- data/docs/done.md +21 -0
- data/docs/spec.md +214 -18
- data/docs/todo.md +1 -5
- data/docs/tutorial.md +24 -0
- data/docs/vim_diff.md +105 -173
- data/lib/ruvim/app.rb +1165 -70
- data/lib/ruvim/buffer.rb +47 -1
- data/lib/ruvim/cli.rb +18 -3
- data/lib/ruvim/clipboard.rb +2 -0
- data/lib/ruvim/command_invocation.rb +3 -1
- data/lib/ruvim/command_line.rb +2 -0
- data/lib/ruvim/command_registry.rb +2 -0
- data/lib/ruvim/config_dsl.rb +2 -0
- data/lib/ruvim/config_loader.rb +2 -0
- data/lib/ruvim/context.rb +2 -0
- data/lib/ruvim/dispatcher.rb +143 -13
- data/lib/ruvim/display_width.rb +3 -0
- data/lib/ruvim/editor.rb +466 -71
- data/lib/ruvim/ex_command_registry.rb +2 -0
- data/lib/ruvim/file_watcher.rb +243 -0
- data/lib/ruvim/git/blame.rb +245 -0
- data/lib/ruvim/git/branch.rb +97 -0
- data/lib/ruvim/git/commit.rb +102 -0
- data/lib/ruvim/git/diff.rb +129 -0
- data/lib/ruvim/git/handler.rb +84 -0
- data/lib/ruvim/git/log.rb +41 -0
- data/lib/ruvim/git/status.rb +103 -0
- data/lib/ruvim/global_commands.rb +1066 -105
- data/lib/ruvim/highlighter.rb +19 -22
- data/lib/ruvim/input.rb +40 -28
- data/lib/ruvim/keymap_manager.rb +83 -0
- data/lib/ruvim/keyword_chars.rb +2 -0
- data/lib/ruvim/lang/base.rb +25 -0
- data/lib/ruvim/lang/csv.rb +18 -0
- data/lib/ruvim/lang/diff.rb +41 -0
- data/lib/ruvim/lang/json.rb +52 -0
- data/lib/ruvim/lang/markdown.rb +170 -0
- data/lib/ruvim/lang/ruby.rb +236 -0
- data/lib/ruvim/lang/scheme.rb +44 -0
- data/lib/ruvim/lang/tsv.rb +19 -0
- data/lib/ruvim/rich_view/json_renderer.rb +131 -0
- data/lib/ruvim/rich_view/jsonl_renderer.rb +57 -0
- data/lib/ruvim/rich_view/markdown_renderer.rb +248 -0
- data/lib/ruvim/rich_view/table_renderer.rb +176 -0
- data/lib/ruvim/rich_view.rb +109 -0
- data/lib/ruvim/screen.rb +503 -109
- data/lib/ruvim/terminal.rb +18 -1
- data/lib/ruvim/text_metrics.rb +2 -0
- data/lib/ruvim/version.rb +1 -1
- data/lib/ruvim/window.rb +2 -0
- data/lib/ruvim.rb +24 -0
- data/test/app_completion_test.rb +98 -0
- data/test/app_dot_repeat_test.rb +13 -0
- data/test/app_motion_test.rb +13 -0
- data/test/app_scenario_test.rb +898 -1
- data/test/app_startup_test.rb +187 -0
- data/test/arglist_test.rb +113 -0
- data/test/buffer_test.rb +49 -30
- data/test/cli_test.rb +14 -0
- data/test/clipboard_test.rb +67 -0
- data/test/command_line_test.rb +118 -0
- data/test/config_dsl_test.rb +87 -0
- data/test/dispatcher_test.rb +322 -0
- data/test/display_width_test.rb +41 -0
- data/test/editor_register_test.rb +23 -0
- data/test/file_watcher_test.rb +197 -0
- data/test/follow_test.rb +199 -0
- data/test/git_blame_test.rb +713 -0
- data/test/highlighter_test.rb +165 -0
- data/test/indent_test.rb +287 -0
- data/test/input_screen_integration_test.rb +40 -2
- data/test/markdown_renderer_test.rb +279 -0
- data/test/on_save_hook_test.rb +150 -0
- data/test/rich_view_test.rb +734 -0
- data/test/screen_test.rb +304 -0
- data/test/search_option_test.rb +19 -0
- data/test/test_helper.rb +9 -0
- metadata +49 -2
data/lib/ruvim/screen.rb
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module RuVim
|
|
2
4
|
class Screen
|
|
3
5
|
DEFAULT_TABSTOP = 2
|
|
4
6
|
SYNTAX_CACHE_LIMIT = 2048
|
|
7
|
+
WRAP_SEGMENTS_CACHE_LIMIT = 1024
|
|
5
8
|
def initialize(terminal:)
|
|
6
9
|
@terminal = terminal
|
|
7
10
|
@last_frame = nil
|
|
8
11
|
@syntax_color_cache = {}
|
|
12
|
+
@wrapped_segments_cache = {}
|
|
9
13
|
end
|
|
10
14
|
|
|
11
15
|
def invalidate_cache!
|
|
@@ -13,6 +17,8 @@ module RuVim
|
|
|
13
17
|
end
|
|
14
18
|
|
|
15
19
|
def render(editor)
|
|
20
|
+
@rich_render_info = nil
|
|
21
|
+
|
|
16
22
|
rows, cols = @terminal.winsize
|
|
17
23
|
text_rows, text_cols = editor.text_viewport_size(rows:, cols:)
|
|
18
24
|
text_rows = [text_rows, 1].max
|
|
@@ -28,15 +34,31 @@ module RuVim
|
|
|
28
34
|
rect = rects[win_id]
|
|
29
35
|
next unless rect
|
|
30
36
|
content_width = [rect[:width] - number_column_width(editor, win, buf), 1].max
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
if RuVim::RichView.active?(editor)
|
|
38
|
+
# Vertical scrolling only — keep raw col_offset untouched
|
|
39
|
+
win.ensure_visible(
|
|
40
|
+
buf,
|
|
41
|
+
height: [rect[:height], 1].max,
|
|
42
|
+
width: content_width,
|
|
43
|
+
tabstop: tabstop_for(editor, win, buf),
|
|
44
|
+
scrolloff: editor.effective_option("scrolloff", window: win, buffer: buf),
|
|
45
|
+
sidescrolloff: editor.effective_option("sidescrolloff", window: win, buffer: buf)
|
|
46
|
+
)
|
|
47
|
+
ensure_visible_rich(editor, win, buf, rect, content_width)
|
|
48
|
+
else
|
|
49
|
+
win.col_offset = 0 if wrap_enabled?(editor, win, buf)
|
|
50
|
+
win.ensure_visible(
|
|
51
|
+
buf,
|
|
52
|
+
height: [rect[:height], 1].max,
|
|
53
|
+
width: content_width,
|
|
54
|
+
tabstop: tabstop_for(editor, win, buf),
|
|
55
|
+
scrolloff: editor.effective_option("scrolloff", window: win, buffer: buf),
|
|
56
|
+
sidescrolloff: editor.effective_option("sidescrolloff", window: win, buffer: buf)
|
|
57
|
+
)
|
|
58
|
+
if wrap_enabled?(editor, win, buf)
|
|
59
|
+
ensure_visible_under_wrap(editor, win, buf, height: [rect[:height], 1].max, content_w: content_width)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
40
62
|
end
|
|
41
63
|
|
|
42
64
|
frame = build_frame(editor, rows:, cols:, text_rows:, text_cols:, rects:)
|
|
@@ -71,15 +93,19 @@ module RuVim
|
|
|
71
93
|
lines = {}
|
|
72
94
|
render_window_area(editor, lines, rects, text_rows:, text_cols:)
|
|
73
95
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
96
|
+
if editor.hit_enter_active? && editor.hit_enter_lines
|
|
97
|
+
render_hit_enter_overlay(editor, lines, text_rows:, cols:)
|
|
98
|
+
else
|
|
99
|
+
status_row = text_rows + 1
|
|
100
|
+
lines[status_row] = "\e[7m#{truncate(status_line(editor, cols), cols)}\e[m"
|
|
101
|
+
lines[status_row + 1] = ""
|
|
102
|
+
|
|
103
|
+
if editor.command_line_active?
|
|
104
|
+
cmd = editor.command_line
|
|
105
|
+
lines[status_row + 1] = truncate("#{cmd.prefix}#{cmd.text}", cols)
|
|
106
|
+
elsif editor.message_error?
|
|
107
|
+
lines[status_row + 1] = error_message_line(editor.message.to_s, cols)
|
|
108
|
+
end
|
|
83
109
|
end
|
|
84
110
|
|
|
85
111
|
{
|
|
@@ -90,66 +116,129 @@ module RuVim
|
|
|
90
116
|
}
|
|
91
117
|
end
|
|
92
118
|
|
|
93
|
-
def
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
119
|
+
def render_hit_enter_overlay(editor, lines, text_rows:, cols:)
|
|
120
|
+
msg_lines = editor.hit_enter_lines
|
|
121
|
+
prompt = "Press ENTER or type command to continue"
|
|
122
|
+
# Total rows available: text_rows (text area) + 1 (status) + 1 (command) = text_rows + 2
|
|
123
|
+
total_rows = text_rows + 2
|
|
124
|
+
# We need msg_lines.length rows for messages + 1 row for the prompt
|
|
125
|
+
overlay_count = msg_lines.length + 1
|
|
126
|
+
start_row = [total_rows - overlay_count + 1, 1].max
|
|
127
|
+
msg_lines.each_with_index do |line, i|
|
|
128
|
+
row_no = start_row + i
|
|
129
|
+
break if row_no > total_rows
|
|
130
|
+
lines[row_no] = truncate(line.to_s, cols)
|
|
98
131
|
end
|
|
132
|
+
prompt_row = start_row + msg_lines.length
|
|
133
|
+
prompt_row = [prompt_row, total_rows].min
|
|
134
|
+
lines[prompt_row] = "\e[7m#{truncate(prompt, cols)}\e[m"
|
|
99
135
|
end
|
|
100
136
|
|
|
101
|
-
def
|
|
102
|
-
|
|
137
|
+
def render_window_area(editor, lines, rects, text_rows:, text_cols:)
|
|
138
|
+
render_tree_windows(editor, lines, rects, text_rows:, text_cols:)
|
|
139
|
+
end
|
|
103
140
|
|
|
141
|
+
def render_tree_windows(editor, lines, rects, text_rows:, text_cols:)
|
|
142
|
+
# Pre-render each window's rows
|
|
143
|
+
window_rows_cache = {}
|
|
104
144
|
editor.window_order.each do |win_id|
|
|
105
145
|
rect = rects[win_id]
|
|
106
146
|
next unless rect
|
|
107
|
-
|
|
108
147
|
window = editor.windows.fetch(win_id)
|
|
109
148
|
buffer = editor.buffers.fetch(window.buffer_id)
|
|
110
149
|
gutter_w = number_column_width(editor, window, buffer)
|
|
111
150
|
content_w = [rect[:width] - gutter_w, 1].max
|
|
112
|
-
|
|
113
|
-
rect[:height].times do |dy|
|
|
114
|
-
row_no = rect[:top] + dy
|
|
115
|
-
lines[row_no] = rows[dy] || (" " * rect[:width])
|
|
116
|
-
end
|
|
151
|
+
window_rows_cache[win_id] = window_render_rows(editor, window, buffer, height: rect[:height], gutter_w:, content_w:)
|
|
117
152
|
end
|
|
118
153
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
lines[rect[:sep_row]] = ("-" * text_cols)
|
|
122
|
-
end
|
|
123
|
-
end
|
|
154
|
+
# Build a row-plan: for each screen row, collect the pieces to concatenate
|
|
155
|
+
row_plans = build_row_plans(editor.layout_tree, rects, text_rows, text_cols)
|
|
124
156
|
|
|
125
|
-
def render_vertical_windows(editor, lines, rects, text_rows:, text_cols:)
|
|
126
157
|
1.upto(text_rows) do |row_no|
|
|
158
|
+
plan = row_plans[row_no]
|
|
159
|
+
unless plan
|
|
160
|
+
lines[row_no] = " " * text_cols
|
|
161
|
+
next
|
|
162
|
+
end
|
|
163
|
+
|
|
127
164
|
pieces = +""
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
@__window_rows_cache[key][dy] || (" " * rect[:width])
|
|
144
|
-
else
|
|
145
|
-
" " * rect[:width]
|
|
146
|
-
end
|
|
147
|
-
pieces << text
|
|
148
|
-
pieces << "|" if idx < editor.window_order.length - 1
|
|
165
|
+
plan.each do |piece|
|
|
166
|
+
case piece[:type]
|
|
167
|
+
when :window
|
|
168
|
+
rect = rects[piece[:id]]
|
|
169
|
+
dy = row_no - rect[:top]
|
|
170
|
+
rows = window_rows_cache[piece[:id]]
|
|
171
|
+
text = (rows && dy >= 0 && dy < rect[:height]) ? (rows[dy] || " " * rect[:width]) : " " * rect[:width]
|
|
172
|
+
pieces << text
|
|
173
|
+
when :vsep
|
|
174
|
+
pieces << "|"
|
|
175
|
+
when :hsep
|
|
176
|
+
pieces << "-" * piece[:width]
|
|
177
|
+
when :blank
|
|
178
|
+
pieces << " " * piece[:width]
|
|
179
|
+
end
|
|
149
180
|
end
|
|
150
181
|
lines[row_no] = pieces
|
|
151
182
|
end
|
|
152
|
-
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Build a row-by-row plan for compositing. Each row's plan is an array of
|
|
186
|
+
# pieces to concatenate left-to-right.
|
|
187
|
+
def build_row_plans(node, rects, text_rows, text_cols)
|
|
188
|
+
plans = {}
|
|
189
|
+
fill_row_plans(node, rects, plans, 1, text_rows)
|
|
190
|
+
plans
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def fill_row_plans(node, rects, plans, row_start, row_end)
|
|
194
|
+
return unless node
|
|
195
|
+
|
|
196
|
+
if node[:type] == :window
|
|
197
|
+
rect = rects[node[:id]]
|
|
198
|
+
return unless rect
|
|
199
|
+
rect[:height].times do |dy|
|
|
200
|
+
row_no = rect[:top] + dy
|
|
201
|
+
next if row_no < row_start || row_no > row_end
|
|
202
|
+
plans[row_no] ||= []
|
|
203
|
+
plans[row_no] << { type: :window, id: node[:id] }
|
|
204
|
+
end
|
|
205
|
+
return
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
children = node[:children]
|
|
209
|
+
if node[:type] == :vsplit
|
|
210
|
+
children.each_with_index do |child, i|
|
|
211
|
+
fill_row_plans(child, rects, plans, row_start, row_end)
|
|
212
|
+
if i < children.length - 1
|
|
213
|
+
# Insert vsep marker for the rows spanned by these children
|
|
214
|
+
child_leaves = tree_leaves_for_rects(child)
|
|
215
|
+
child_rects_list = child_leaves.filter_map { |id| rects[id] }
|
|
216
|
+
next if child_rects_list.empty?
|
|
217
|
+
top = child_rects_list.map { |r| r[:top] }.min
|
|
218
|
+
bottom = child_rects_list.map { |r| r[:top] + r[:height] - 1 }.max
|
|
219
|
+
top.upto(bottom) do |row_no|
|
|
220
|
+
next if row_no < row_start || row_no > row_end
|
|
221
|
+
plans[row_no] ||= []
|
|
222
|
+
plans[row_no] << { type: :vsep }
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
elsif node[:type] == :hsplit
|
|
227
|
+
children.each_with_index do |child, i|
|
|
228
|
+
fill_row_plans(child, rects, plans, row_start, row_end)
|
|
229
|
+
if i < children.length - 1
|
|
230
|
+
child_leaves = tree_leaves_for_rects(child)
|
|
231
|
+
child_rects_list = child_leaves.filter_map { |id| rects[id] }
|
|
232
|
+
next if child_rects_list.empty?
|
|
233
|
+
sep_row = child_rects_list.map { |r| r[:top] + r[:height] }.max
|
|
234
|
+
left = child_rects_list.map { |r| r[:left] }.min
|
|
235
|
+
right = child_rects_list.map { |r| r[:left] + r[:width] - 1 }.max
|
|
236
|
+
next unless sep_row >= row_start && sep_row <= row_end
|
|
237
|
+
plans[sep_row] ||= []
|
|
238
|
+
plans[sep_row] << { type: :hsep, width: right - left + 1 }
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
153
242
|
end
|
|
154
243
|
|
|
155
244
|
def can_diff_render?(frame)
|
|
@@ -301,22 +390,144 @@ module RuVim
|
|
|
301
390
|
end
|
|
302
391
|
|
|
303
392
|
def plain_window_render_rows(editor, window, buffer, height:, gutter_w:, content_w:)
|
|
393
|
+
if RuVim::RichView.active?(editor)
|
|
394
|
+
return rich_view_render_rows(editor, window, buffer, height:, gutter_w:, content_w:)
|
|
395
|
+
end
|
|
396
|
+
|
|
304
397
|
Array.new(height) do |dy|
|
|
305
398
|
buffer_row = window.row_offset + dy
|
|
306
399
|
if buffer_row < buffer.line_count
|
|
307
400
|
render_window_row(editor, window, buffer, buffer_row, gutter_w:, content_w:)
|
|
308
401
|
else
|
|
309
|
-
|
|
402
|
+
render_gutter_prefix(editor, window, buffer, nil, gutter_w) + pad_plain_display("~", content_w)
|
|
310
403
|
end
|
|
311
404
|
end
|
|
312
405
|
end
|
|
313
406
|
|
|
407
|
+
def rich_view_render_rows(editor, window, buffer, height:, gutter_w:, content_w:)
|
|
408
|
+
raw_lines = []
|
|
409
|
+
height.times do |dy|
|
|
410
|
+
row = window.row_offset + dy
|
|
411
|
+
raw_lines << (row < buffer.line_count ? buffer.line_at(row) : nil)
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
non_nil = raw_lines.compact
|
|
415
|
+
context = rich_view_context(editor, window, buffer)
|
|
416
|
+
formatted = RuVim::RichView.render_visible_lines(editor, non_nil, context: context)
|
|
417
|
+
fmt_idx = 0
|
|
418
|
+
col_offset_sc = @rich_render_info ? @rich_render_info[:col_offset_sc] : 0
|
|
419
|
+
|
|
420
|
+
Array.new(height) do |dy|
|
|
421
|
+
buffer_row = window.row_offset + dy
|
|
422
|
+
prefix = render_gutter_prefix(editor, window, buffer, buffer_row < buffer.line_count ? buffer_row : nil, gutter_w)
|
|
423
|
+
if buffer_row < buffer.line_count
|
|
424
|
+
line = formatted[fmt_idx] || ""
|
|
425
|
+
fmt_idx += 1
|
|
426
|
+
body = render_rich_view_line_sc(line, width: content_w, skip_sc: col_offset_sc)
|
|
427
|
+
prefix + body
|
|
428
|
+
else
|
|
429
|
+
prefix + pad_plain_display("~", content_w)
|
|
430
|
+
end
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def rich_view_context(editor, window, buffer)
|
|
435
|
+
state = editor.rich_state
|
|
436
|
+
return {} unless state
|
|
437
|
+
|
|
438
|
+
format = state[:format]
|
|
439
|
+
renderer = RuVim::RichView.renderer_for(format)
|
|
440
|
+
return {} unless renderer && renderer.respond_to?(:needs_pre_context?) && renderer.needs_pre_context?
|
|
441
|
+
|
|
442
|
+
# Collect lines before the visible area for state tracking (e.g., code fences)
|
|
443
|
+
pre_lines = []
|
|
444
|
+
(0...window.row_offset).each do |row|
|
|
445
|
+
pre_lines << buffer.line_at(row) if row < buffer.line_count
|
|
446
|
+
end
|
|
447
|
+
{ pre_context_lines: pre_lines }
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
# Render a formatted rich-view line by skipping `skip_sc` display columns
|
|
451
|
+
# then showing the next `width` display columns. Using screen columns
|
|
452
|
+
# instead of character indices keeps alignment correct when lines mix
|
|
453
|
+
# CJK and ASCII characters with different char-to-display-width ratios.
|
|
454
|
+
# ANSI escape sequences (\e[...m) are treated as zero-width and passed
|
|
455
|
+
# through to the output unchanged.
|
|
456
|
+
def render_rich_view_line_sc(text, width:, skip_sc:)
|
|
457
|
+
# Phase 1: skip `skip_sc` display columns
|
|
458
|
+
# Collect ANSI sequences encountered during skip so active styles carry over.
|
|
459
|
+
chars = text.to_s
|
|
460
|
+
pos = 0
|
|
461
|
+
skipped = 0
|
|
462
|
+
len = chars.length
|
|
463
|
+
pending_ansi = +""
|
|
464
|
+
while pos < len
|
|
465
|
+
if chars[pos] == "\e"
|
|
466
|
+
end_pos = find_ansi_end(chars, pos)
|
|
467
|
+
pending_ansi << chars[pos...end_pos]
|
|
468
|
+
pos = end_pos
|
|
469
|
+
next
|
|
470
|
+
end
|
|
471
|
+
ch = chars[pos]
|
|
472
|
+
cw = RuVim::DisplayWidth.cell_width(ch, col: skipped, tabstop: 8)
|
|
473
|
+
break if skipped + cw > skip_sc
|
|
474
|
+
skipped += cw
|
|
475
|
+
pos += 1
|
|
476
|
+
end
|
|
477
|
+
# If a wide char straddles the skip boundary, pad with a space
|
|
478
|
+
leading_pad = skip_sc - skipped
|
|
479
|
+
|
|
480
|
+
# Phase 2: collect `width` display columns
|
|
481
|
+
out = +""
|
|
482
|
+
out << pending_ansi unless pending_ansi.empty?
|
|
483
|
+
col = 0
|
|
484
|
+
if leading_pad > 0
|
|
485
|
+
out << " " * leading_pad
|
|
486
|
+
col += leading_pad
|
|
487
|
+
pos += 1 if pos < len && chars[pos] != "\e"
|
|
488
|
+
end
|
|
489
|
+
while pos < len
|
|
490
|
+
if chars[pos] == "\e"
|
|
491
|
+
end_pos = find_ansi_end(chars, pos)
|
|
492
|
+
out << chars[pos...end_pos]
|
|
493
|
+
pos = end_pos
|
|
494
|
+
next
|
|
495
|
+
end
|
|
496
|
+
ch = chars[pos]
|
|
497
|
+
cw = RuVim::DisplayWidth.cell_width(ch, col: skipped + col, tabstop: 8)
|
|
498
|
+
break if col + cw > width
|
|
499
|
+
out << ch
|
|
500
|
+
col += cw
|
|
501
|
+
pos += 1
|
|
502
|
+
end
|
|
503
|
+
out << " " * [width - col, 0].max
|
|
504
|
+
out << "\e[m"
|
|
505
|
+
out
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
# Find the end position of an ANSI escape sequence starting at `pos`.
|
|
509
|
+
# Handles CSI sequences (\e[...X) where X is a letter.
|
|
510
|
+
def find_ansi_end(str, pos)
|
|
511
|
+
i = pos + 1 # skip \e
|
|
512
|
+
return i if i >= str.length
|
|
513
|
+
if str[i] == "["
|
|
514
|
+
i += 1
|
|
515
|
+
# Skip parameter bytes and intermediate bytes
|
|
516
|
+
i += 1 while i < str.length && str[i].ord >= 0x20 && str[i].ord <= 0x3F
|
|
517
|
+
# Final byte
|
|
518
|
+
i += 1 if i < str.length && str[i].ord >= 0x40 && str[i].ord <= 0x7E
|
|
519
|
+
else
|
|
520
|
+
i += 1
|
|
521
|
+
end
|
|
522
|
+
i
|
|
523
|
+
end
|
|
524
|
+
|
|
314
525
|
def wrapped_window_render_rows(editor, window, buffer, height:, gutter_w:, content_w:)
|
|
315
526
|
rows = []
|
|
316
527
|
row_idx = window.row_offset
|
|
317
528
|
while rows.length < height
|
|
318
529
|
if row_idx >= buffer.line_count
|
|
319
|
-
rows << (
|
|
530
|
+
rows << (render_gutter_prefix(editor, window, buffer, nil, gutter_w) + pad_plain_display("~", content_w))
|
|
320
531
|
next
|
|
321
532
|
end
|
|
322
533
|
|
|
@@ -325,7 +536,7 @@ module RuVim
|
|
|
325
536
|
segments.each_with_index do |seg, seg_i|
|
|
326
537
|
break if rows.length >= height
|
|
327
538
|
|
|
328
|
-
gutter =
|
|
539
|
+
gutter = render_gutter_prefix(editor, window, buffer, seg_i.zero? ? row_idx : nil, gutter_w)
|
|
329
540
|
rows << gutter + render_text_segment(line, editor, buffer_row: row_idx, window:, buffer:, width: content_w,
|
|
330
541
|
source_col_start: seg[:source_col_start], display_prefix: seg[:display_prefix])
|
|
331
542
|
end
|
|
@@ -334,7 +545,92 @@ module RuVim
|
|
|
334
545
|
rows
|
|
335
546
|
end
|
|
336
547
|
|
|
548
|
+
def ensure_visible_under_wrap(editor, window, buffer, height:, content_w:)
|
|
549
|
+
return if height.to_i <= 0 || buffer.line_count <= 0
|
|
550
|
+
|
|
551
|
+
window.row_offset = [[window.row_offset.to_i, 0].max, buffer.line_count - 1].min
|
|
552
|
+
return if window.cursor_y < window.row_offset
|
|
553
|
+
|
|
554
|
+
cursor_line = buffer.line_at(window.cursor_y)
|
|
555
|
+
cursor_segs = wrapped_segments_for_line(editor, window, buffer, cursor_line, width: content_w)
|
|
556
|
+
cursor_seg_index = wrapped_segment_index(cursor_segs, window.cursor_x)
|
|
557
|
+
|
|
558
|
+
visual_rows_before = 0
|
|
559
|
+
row = window.row_offset
|
|
560
|
+
while row < window.cursor_y
|
|
561
|
+
visual_rows_before += wrapped_segments_for_line(editor, window, buffer, buffer.line_at(row), width: content_w).length
|
|
562
|
+
row += 1
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
cursor_visual_row = visual_rows_before + cursor_seg_index
|
|
566
|
+
while cursor_visual_row >= height && window.row_offset < window.cursor_y
|
|
567
|
+
dropped = wrapped_segments_for_line(editor, window, buffer, buffer.line_at(window.row_offset), width: content_w).length
|
|
568
|
+
window.row_offset += 1
|
|
569
|
+
cursor_visual_row -= dropped
|
|
570
|
+
end
|
|
571
|
+
rescue StandardError
|
|
572
|
+
nil
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
# Compute a screen-column-based horizontal scroll offset for rich mode.
|
|
576
|
+
# Unlike normal mode (which stores a char index in window.col_offset),
|
|
577
|
+
# rich mode must scroll by display columns because CJK-padded formatted
|
|
578
|
+
# lines have different character counts for the same display width.
|
|
579
|
+
def ensure_visible_rich(editor, win, buf, rect, content_w)
|
|
580
|
+
state = editor.rich_state
|
|
581
|
+
unless state
|
|
582
|
+
@rich_render_info = nil
|
|
583
|
+
@rich_col_offset_sc = 0
|
|
584
|
+
return
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
format = state[:format]
|
|
588
|
+
delimiter = state[:delimiter]
|
|
589
|
+
renderer = RuVim::RichView.renderer_for(format)
|
|
590
|
+
height = [rect[:height], 1].max
|
|
591
|
+
|
|
592
|
+
raw_lines = height.times.map { |dy|
|
|
593
|
+
row = win.row_offset + dy
|
|
594
|
+
row < buf.line_count ? buf.line_at(row) : nil
|
|
595
|
+
}.compact
|
|
596
|
+
|
|
597
|
+
cursor_raw_line = buf.line_at(win.cursor_y)
|
|
598
|
+
|
|
599
|
+
if renderer && renderer.respond_to?(:cursor_display_col)
|
|
600
|
+
cursor_sc = renderer.cursor_display_col(
|
|
601
|
+
cursor_raw_line, win.cursor_x, visible_lines: raw_lines, delimiter: delimiter
|
|
602
|
+
)
|
|
603
|
+
else
|
|
604
|
+
cursor_sc = RuVim::TextMetrics.screen_col_for_char_index(cursor_raw_line, win.cursor_x)
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
# Use persisted screen-column offset from previous frame
|
|
608
|
+
offset_sc = @rich_col_offset_sc || 0
|
|
609
|
+
|
|
610
|
+
sso = editor.effective_option("sidescrolloff", window: win, buffer: buf).to_i
|
|
611
|
+
sso = [[sso, 0].max, [content_w - 1, 0].max].min
|
|
612
|
+
|
|
613
|
+
if cursor_sc < offset_sc + sso
|
|
614
|
+
offset_sc = [cursor_sc - sso, 0].max
|
|
615
|
+
elsif cursor_sc >= offset_sc + content_w - sso
|
|
616
|
+
offset_sc = cursor_sc - content_w + sso + 1
|
|
617
|
+
end
|
|
618
|
+
offset_sc = [offset_sc, 0].max
|
|
619
|
+
|
|
620
|
+
@rich_col_offset_sc = offset_sc
|
|
621
|
+
|
|
622
|
+
if win == editor.current_window
|
|
623
|
+
@rich_render_info = {
|
|
624
|
+
col_offset_sc: offset_sc,
|
|
625
|
+
cursor_sc: cursor_sc,
|
|
626
|
+
delimiter: delimiter
|
|
627
|
+
}
|
|
628
|
+
end
|
|
629
|
+
end
|
|
630
|
+
|
|
337
631
|
def wrap_enabled?(editor, window, buffer)
|
|
632
|
+
return false if editor.rich_state
|
|
633
|
+
|
|
338
634
|
!!editor.effective_option("wrap", window:, buffer:)
|
|
339
635
|
end
|
|
340
636
|
|
|
@@ -345,15 +641,25 @@ module RuVim
|
|
|
345
641
|
linebreak = !!editor.effective_option("linebreak", window:, buffer:)
|
|
346
642
|
showbreak = editor.effective_option("showbreak", window:, buffer:).to_s
|
|
347
643
|
breakindent = !!editor.effective_option("breakindent", window:, buffer:)
|
|
644
|
+
line = line.to_s
|
|
645
|
+
return [{ source_col_start: 0, display_prefix: "" }] if line.empty?
|
|
646
|
+
|
|
647
|
+
cache_key = [line.object_id, line.length, line.hash, width, tabstop, linebreak, showbreak, breakindent]
|
|
648
|
+
if (cached = @wrapped_segments_cache[cache_key])
|
|
649
|
+
return cached
|
|
650
|
+
end
|
|
651
|
+
|
|
348
652
|
indent_prefix = breakindent ? wrapped_indent_prefix(line, tabstop:, max_width: [width - RuVim::DisplayWidth.display_width(showbreak, tabstop:), 0].max) : ""
|
|
653
|
+
segs = compute_wrapped_segments(line, width:, tabstop:, linebreak:, showbreak:, indent_prefix:)
|
|
654
|
+
@wrapped_segments_cache[cache_key] = segs
|
|
655
|
+
@wrapped_segments_cache.shift while @wrapped_segments_cache.length > WRAP_SEGMENTS_CACHE_LIMIT
|
|
656
|
+
segs
|
|
657
|
+
end
|
|
349
658
|
|
|
659
|
+
def compute_wrapped_segments(line, width:, tabstop:, linebreak:, showbreak:, indent_prefix:)
|
|
350
660
|
segs = []
|
|
351
661
|
start_col = 0
|
|
352
662
|
first = true
|
|
353
|
-
line = line.to_s
|
|
354
|
-
if line.empty?
|
|
355
|
-
return [{ source_col_start: 0, display_prefix: "" }]
|
|
356
|
-
end
|
|
357
663
|
|
|
358
664
|
while start_col < line.length
|
|
359
665
|
display_prefix = first ? "" : "#{showbreak}#{indent_prefix}"
|
|
@@ -361,7 +667,7 @@ module RuVim
|
|
|
361
667
|
avail = [width - prefix_w, 1].max
|
|
362
668
|
cells, = RuVim::TextMetrics.clip_cells_for_width(line[start_col..].to_s, avail, source_col_start: start_col, tabstop:)
|
|
363
669
|
if cells.empty?
|
|
364
|
-
segs << { source_col_start: start_col, display_prefix: display_prefix }
|
|
670
|
+
segs << { source_col_start: start_col, display_prefix: display_prefix }.freeze
|
|
365
671
|
break
|
|
366
672
|
end
|
|
367
673
|
|
|
@@ -372,7 +678,7 @@ module RuVim
|
|
|
372
678
|
end
|
|
373
679
|
end
|
|
374
680
|
|
|
375
|
-
segs << { source_col_start: start_col, display_prefix: display_prefix }
|
|
681
|
+
segs << { source_col_start: start_col, display_prefix: display_prefix }.freeze
|
|
376
682
|
next_start = cells.last.source_col.to_i + 1
|
|
377
683
|
if linebreak
|
|
378
684
|
next_start += 1 while next_start < line.length && line[next_start] == " "
|
|
@@ -383,7 +689,20 @@ module RuVim
|
|
|
383
689
|
first = false
|
|
384
690
|
end
|
|
385
691
|
|
|
386
|
-
segs
|
|
692
|
+
segs.freeze
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
def wrapped_segment_index(segs, cursor_x)
|
|
696
|
+
x = cursor_x.to_i
|
|
697
|
+
seg_index = 0
|
|
698
|
+
segs.each_with_index do |seg, i|
|
|
699
|
+
nxt = segs[i + 1]
|
|
700
|
+
if nxt.nil? || x < nxt[:source_col_start]
|
|
701
|
+
seg_index = i
|
|
702
|
+
break
|
|
703
|
+
end
|
|
704
|
+
end
|
|
705
|
+
seg_index
|
|
387
706
|
end
|
|
388
707
|
|
|
389
708
|
def linebreak_break_index(cells, line)
|
|
@@ -453,7 +772,7 @@ module RuVim
|
|
|
453
772
|
def render_window_row(editor, window, buffer, buffer_row, gutter_w:, content_w:)
|
|
454
773
|
line = buffer.line_at(buffer_row)
|
|
455
774
|
line = line[window.col_offset..] || ""
|
|
456
|
-
prefix =
|
|
775
|
+
prefix = render_gutter_prefix(editor, window, buffer, buffer_row, gutter_w)
|
|
457
776
|
body = render_text_line(line, editor, buffer_row:, window:, buffer:, width: content_w)
|
|
458
777
|
prefix + body
|
|
459
778
|
end
|
|
@@ -497,6 +816,15 @@ module RuVim
|
|
|
497
816
|
sign + num.rjust([num_width - 1, 0].max) + (num_width.positive? ? " " : "")
|
|
498
817
|
end
|
|
499
818
|
|
|
819
|
+
def render_gutter_prefix(editor, window, buffer, buffer_row, width)
|
|
820
|
+
prefix = line_number_prefix(editor, window, buffer, buffer_row, width)
|
|
821
|
+
return prefix if prefix.empty?
|
|
822
|
+
return prefix if buffer_row.nil?
|
|
823
|
+
|
|
824
|
+
current_line = (buffer_row == window.cursor_y)
|
|
825
|
+
"#{line_number_fg_seq(editor, current_line: current_line)}#{prefix}\e[m"
|
|
826
|
+
end
|
|
827
|
+
|
|
500
828
|
def sign_column_width(editor, window, buffer)
|
|
501
829
|
raw = editor.effective_option("signcolumn", window:, buffer:).to_s
|
|
502
830
|
case raw
|
|
@@ -543,15 +871,27 @@ module RuVim
|
|
|
543
871
|
end
|
|
544
872
|
|
|
545
873
|
def search_bg_seq(editor)
|
|
546
|
-
|
|
874
|
+
term_color(editor, "\e[48;2;255;215;0m", "\e[43m")
|
|
547
875
|
end
|
|
548
876
|
|
|
549
877
|
def colorcolumn_bg_seq(editor)
|
|
550
|
-
|
|
878
|
+
term_color(editor, "\e[48;2;72;72;72m", "\e[48;5;238m")
|
|
551
879
|
end
|
|
552
880
|
|
|
553
881
|
def cursorline_bg_seq(editor)
|
|
554
|
-
|
|
882
|
+
term_color(editor, "\e[48;2;58;58;58m", "\e[48;5;236m")
|
|
883
|
+
end
|
|
884
|
+
|
|
885
|
+
def term_color(editor, truecolor_seq, fallback_seq)
|
|
886
|
+
truecolor_enabled?(editor) ? truecolor_seq : fallback_seq
|
|
887
|
+
end
|
|
888
|
+
|
|
889
|
+
def line_number_fg_seq(editor, current_line: false)
|
|
890
|
+
if truecolor_enabled?(editor)
|
|
891
|
+
current_line ? "\e[38;2;190;190;190m" : "\e[38;2;120;120;120m"
|
|
892
|
+
else
|
|
893
|
+
current_line ? "\e[37m" : "\e[90m"
|
|
894
|
+
end
|
|
555
895
|
end
|
|
556
896
|
|
|
557
897
|
def truecolor_enabled?(editor)
|
|
@@ -569,18 +909,39 @@ module RuVim
|
|
|
569
909
|
when :visual_char then "-- VISUAL --"
|
|
570
910
|
when :visual_line then "-- VISUAL LINE --"
|
|
571
911
|
when :visual_block then "-- VISUAL BLOCK --"
|
|
912
|
+
when :rich then "-- RICH --"
|
|
572
913
|
else "-- NORMAL --"
|
|
573
914
|
end
|
|
574
915
|
|
|
575
916
|
path = buffer.display_name
|
|
576
917
|
mod = buffer.modified? ? " [+]" : ""
|
|
918
|
+
stream = buffer.stream_status ? " [#{buffer.stream_status}]" : ""
|
|
919
|
+
loading = file_loading_status_token(buffer)
|
|
920
|
+
tab = tab_status_token(editor)
|
|
577
921
|
msg = editor.message_error? ? "" : editor.message.to_s
|
|
578
|
-
left = "#{mode} #{path}#{mod}"
|
|
579
|
-
right = " #{window.cursor_y + 1}:#{window.cursor_x + 1} "
|
|
922
|
+
left = "#{mode} #{path}#{mod}#{stream}#{loading}"
|
|
923
|
+
right = " #{window.cursor_y + 1}:#{window.cursor_x + 1}#{tab} "
|
|
580
924
|
body_width = [width - right.length, 0].max
|
|
581
925
|
"#{compose_status_body(left, msg, body_width)}#{right}"
|
|
582
926
|
end
|
|
583
927
|
|
|
928
|
+
def file_loading_status_token(buffer)
|
|
929
|
+
return "" unless buffer.respond_to?(:loading_state)
|
|
930
|
+
return "" unless buffer.file_buffer?
|
|
931
|
+
|
|
932
|
+
state = buffer.loading_state
|
|
933
|
+
return "" unless state
|
|
934
|
+
return "" if state.to_sym == :closed
|
|
935
|
+
|
|
936
|
+
" [load/#{state}]"
|
|
937
|
+
end
|
|
938
|
+
|
|
939
|
+
def tab_status_token(editor)
|
|
940
|
+
return "" if editor.tabpage_count <= 1
|
|
941
|
+
|
|
942
|
+
" tab:#{editor.current_tabpage_number}/#{editor.tabpage_count}"
|
|
943
|
+
end
|
|
944
|
+
|
|
584
945
|
def compose_status_body(left, msg, width)
|
|
585
946
|
w = [width.to_i, 0].max
|
|
586
947
|
return "" if w.zero?
|
|
@@ -596,7 +957,8 @@ module RuVim
|
|
|
596
957
|
end
|
|
597
958
|
|
|
598
959
|
def truncate(str, width)
|
|
599
|
-
RuVim::TextMetrics.terminal_safe_text(str)
|
|
960
|
+
safe = RuVim::TextMetrics.terminal_safe_text(str)
|
|
961
|
+
RuVim::TextMetrics.pad_plain_to_screen_width(safe, width)
|
|
600
962
|
end
|
|
601
963
|
|
|
602
964
|
def error_message_line(msg, cols)
|
|
@@ -614,6 +976,16 @@ module RuVim
|
|
|
614
976
|
def cursor_screen_position(editor, text_rows, rects)
|
|
615
977
|
window = editor.current_window
|
|
616
978
|
|
|
979
|
+
if editor.hit_enter_active? && editor.hit_enter_lines
|
|
980
|
+
total_rows = text_rows + 2
|
|
981
|
+
msg_count = editor.hit_enter_lines.length
|
|
982
|
+
prompt_row = [total_rows - msg_count, 1].max + msg_count
|
|
983
|
+
prompt_row = [prompt_row, total_rows].min
|
|
984
|
+
prompt_text = "Press ENTER or type command to continue"
|
|
985
|
+
col = [prompt_text.length + 1, total_rows].min
|
|
986
|
+
return [prompt_row, col]
|
|
987
|
+
end
|
|
988
|
+
|
|
617
989
|
if editor.command_line_active?
|
|
618
990
|
row = text_rows + 2
|
|
619
991
|
col = 1 + editor.command_line.prefix.length + editor.command_line.cursor
|
|
@@ -634,14 +1006,7 @@ module RuVim
|
|
|
634
1006
|
row += 1
|
|
635
1007
|
end
|
|
636
1008
|
segs = wrapped_segments_for_line(editor, window, buffer, line, width: content_w)
|
|
637
|
-
seg_index =
|
|
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
|
|
1009
|
+
seg_index = wrapped_segment_index(segs, window.cursor_x)
|
|
645
1010
|
seg = segs[seg_index] || { source_col_start: 0, display_prefix: "" }
|
|
646
1011
|
row = rect[:top] + visual_rows_before + seg_index
|
|
647
1012
|
seg_prefix_w = RuVim::DisplayWidth.display_width(seg[:display_prefix].to_s, tabstop:)
|
|
@@ -649,6 +1014,9 @@ module RuVim
|
|
|
649
1014
|
cursor_sc = RuVim::TextMetrics.screen_col_for_char_index(line, window.cursor_x, tabstop:) + extra_virtual
|
|
650
1015
|
seg_sc = RuVim::TextMetrics.screen_col_for_char_index(line, seg[:source_col_start], tabstop:)
|
|
651
1016
|
col = rect[:left] + gutter_w + seg_prefix_w + [cursor_sc - seg_sc, 0].max
|
|
1017
|
+
elsif @rich_render_info
|
|
1018
|
+
row = rect[:top] + (window.cursor_y - window.row_offset)
|
|
1019
|
+
col = rect[:left] + gutter_w + [@rich_render_info[:cursor_sc] - @rich_render_info[:col_offset_sc], 0].max
|
|
652
1020
|
else
|
|
653
1021
|
row = rect[:top] + (window.cursor_y - window.row_offset)
|
|
654
1022
|
extra_virtual = [window.cursor_x - line.length, 0].max
|
|
@@ -666,39 +1034,64 @@ module RuVim
|
|
|
666
1034
|
end
|
|
667
1035
|
|
|
668
1036
|
def window_rects(editor, text_rows:, text_cols:)
|
|
1037
|
+
tree = editor.layout_tree
|
|
1038
|
+
return {} if tree.nil?
|
|
669
1039
|
ids = editor.window_order
|
|
670
1040
|
return {} if ids.empty?
|
|
671
|
-
return { ids.first => { top: 1, left: 1, height: text_rows, width: text_cols } } if ids.length == 1
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
1041
|
+
return { ids.first => { top: 1, left: 1, height: text_rows, width: text_cols } } if ids.length == 1
|
|
1042
|
+
|
|
1043
|
+
compute_tree_rects(tree, top: 1, left: 1, height: text_rows, width: text_cols)
|
|
1044
|
+
end
|
|
1045
|
+
|
|
1046
|
+
def compute_tree_rects(node, top:, left:, height:, width:)
|
|
1047
|
+
if node[:type] == :window
|
|
1048
|
+
return { node[:id] => { top: top, left: left, height: height, width: width } }
|
|
1049
|
+
end
|
|
1050
|
+
|
|
1051
|
+
children = node[:children]
|
|
1052
|
+
n = children.length
|
|
1053
|
+
rects = {}
|
|
1054
|
+
|
|
1055
|
+
case node[:type]
|
|
1056
|
+
when :vsplit
|
|
1057
|
+
sep_count = n - 1
|
|
1058
|
+
usable = [width - sep_count, n].max
|
|
1059
|
+
widths = split_sizes(usable, n)
|
|
1060
|
+
cur_left = left
|
|
1061
|
+
children.each_with_index do |child, i|
|
|
680
1062
|
w = widths[i]
|
|
681
|
-
|
|
682
|
-
|
|
1063
|
+
child_rects = compute_tree_rects(child, top: top, left: cur_left, height: height, width: w)
|
|
1064
|
+
child_rects.each_value { |r| r[:separator] = :vertical }
|
|
1065
|
+
rects.merge!(child_rects)
|
|
1066
|
+
cur_left += w + 1
|
|
683
1067
|
end
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
rects = {}
|
|
691
|
-
ids.each_with_index do |id, i|
|
|
1068
|
+
when :hsplit
|
|
1069
|
+
sep_count = n - 1
|
|
1070
|
+
usable = [height - sep_count, n].max
|
|
1071
|
+
heights = split_sizes(usable, n)
|
|
1072
|
+
cur_top = top
|
|
1073
|
+
children.each_with_index do |child, i|
|
|
692
1074
|
h = heights[i]
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
1075
|
+
child_rects = compute_tree_rects(child, top: cur_top, left: left, height: h, width: width)
|
|
1076
|
+
child_rects.each_value { |r| r[:separator] = :horizontal }
|
|
1077
|
+
rects.merge!(child_rects)
|
|
1078
|
+
if i < n - 1
|
|
1079
|
+
# Mark separator row for the last window in this child
|
|
1080
|
+
child_leaves = tree_leaves_for_rects(child)
|
|
1081
|
+
last_leaf = child_leaves.last
|
|
1082
|
+
rects[last_leaf][:sep_row] = cur_top + h if last_leaf && rects[last_leaf]
|
|
698
1083
|
end
|
|
1084
|
+
cur_top += h + 1
|
|
699
1085
|
end
|
|
700
|
-
rects
|
|
701
1086
|
end
|
|
1087
|
+
|
|
1088
|
+
rects
|
|
1089
|
+
end
|
|
1090
|
+
|
|
1091
|
+
def tree_leaves_for_rects(node)
|
|
1092
|
+
return [node[:id]] if node[:type] == :window
|
|
1093
|
+
|
|
1094
|
+
node[:children].flat_map { |c| tree_leaves_for_rects(c) }
|
|
702
1095
|
end
|
|
703
1096
|
|
|
704
1097
|
def split_sizes(total, n)
|
|
@@ -733,6 +1126,7 @@ module RuVim
|
|
|
733
1126
|
search = editor.last_search
|
|
734
1127
|
return {} unless search && search[:pattern]
|
|
735
1128
|
return {} unless editor.effective_option("hlsearch")
|
|
1129
|
+
return {} if editor.hlsearch_suppressed?
|
|
736
1130
|
|
|
737
1131
|
regex = build_screen_search_regex(editor, search[:pattern])
|
|
738
1132
|
cols = {}
|