ruvim 0.2.0 → 0.3.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 +84 -0
- data/CLAUDE.md +1 -0
- data/docs/binding.md +23 -0
- data/docs/command.md +85 -0
- data/docs/config.md +2 -2
- data/docs/done.md +21 -0
- data/docs/spec.md +157 -12
- data/docs/todo.md +1 -5
- data/docs/vim_diff.md +94 -172
- data/lib/ruvim/app.rb +882 -69
- data/lib/ruvim/buffer.rb +35 -1
- data/lib/ruvim/cli.rb +12 -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 +455 -71
- data/lib/ruvim/ex_command_registry.rb +2 -0
- data/lib/ruvim/global_commands.rb +890 -63
- data/lib/ruvim/highlighter.rb +16 -21
- data/lib/ruvim/input.rb +39 -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/json.rb +18 -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/markdown_renderer.rb +248 -0
- data/lib/ruvim/rich_view/table_renderer.rb +176 -0
- data/lib/ruvim/rich_view.rb +93 -0
- data/lib/ruvim/screen.rb +503 -106
- 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 +14 -0
- data/test/app_completion_test.rb +73 -0
- data/test/app_dot_repeat_test.rb +13 -0
- data/test/app_motion_test.rb +13 -0
- data/test/app_scenario_test.rb +729 -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/dispatcher_test.rb +322 -0
- data/test/editor_register_test.rb +23 -0
- data/test/highlighter_test.rb +121 -0
- data/test/indent_test.rb +201 -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 +478 -0
- data/test/screen_test.rb +304 -0
- metadata +33 -2
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "tempfile"
|
|
2
4
|
require "open3"
|
|
3
5
|
|
|
@@ -5,7 +7,7 @@ module RuVim
|
|
|
5
7
|
class GlobalCommands
|
|
6
8
|
include Singleton
|
|
7
9
|
|
|
8
|
-
def call(spec_call, ctx, argv: [], kwargs: {}, bang: false, count:
|
|
10
|
+
def call(spec_call, ctx, argv: [], kwargs: {}, bang: false, count: nil)
|
|
9
11
|
case spec_call
|
|
10
12
|
when Symbol, String
|
|
11
13
|
public_send(spec_call.to_sym, ctx, argv: argv, kwargs: kwargs, bang: bang, count: count)
|
|
@@ -23,11 +25,11 @@ module RuVim
|
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
def cursor_up(ctx, count:, **)
|
|
26
|
-
ctx.window.move_up(ctx.buffer, count)
|
|
28
|
+
ctx.window.move_up(ctx.buffer, normalized_count(count))
|
|
27
29
|
end
|
|
28
30
|
|
|
29
31
|
def cursor_down(ctx, count:, **)
|
|
30
|
-
ctx.window.move_down(ctx.buffer, count)
|
|
32
|
+
ctx.window.move_down(ctx.buffer, normalized_count(count))
|
|
31
33
|
end
|
|
32
34
|
|
|
33
35
|
def cursor_page_up(ctx, kwargs:, count:, **)
|
|
@@ -72,6 +74,18 @@ module RuVim
|
|
|
72
74
|
call(:window_scroll_down, ctx, count:, bang:, kwargs: { lines: 1, view_height: current_view_height(ctx) + 1 })
|
|
73
75
|
end
|
|
74
76
|
|
|
77
|
+
def window_cursor_line_top(ctx, count:, **)
|
|
78
|
+
place_cursor_line_in_window(ctx, where: :top, count:)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def window_cursor_line_center(ctx, count:, **)
|
|
82
|
+
place_cursor_line_in_window(ctx, where: :center, count:)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def window_cursor_line_bottom(ctx, count:, **)
|
|
86
|
+
place_cursor_line_in_window(ctx, where: :bottom, count:)
|
|
87
|
+
end
|
|
88
|
+
|
|
75
89
|
def cursor_line_start(ctx, **)
|
|
76
90
|
ctx.window.cursor_x = 0
|
|
77
91
|
ctx.window.clamp_to_buffer(ctx.buffer)
|
|
@@ -91,7 +105,7 @@ module RuVim
|
|
|
91
105
|
|
|
92
106
|
def cursor_buffer_start(ctx, count:, **)
|
|
93
107
|
record_jump(ctx)
|
|
94
|
-
target_row = [count.to_i - 1, 0].max
|
|
108
|
+
target_row = [normalized_count(count).to_i - 1, 0].max
|
|
95
109
|
target_row = [target_row, ctx.buffer.line_count - 1].min
|
|
96
110
|
ctx.window.cursor_y = target_row
|
|
97
111
|
cursor_first_nonblank(ctx)
|
|
@@ -99,11 +113,7 @@ module RuVim
|
|
|
99
113
|
|
|
100
114
|
def cursor_buffer_end(ctx, count:, **)
|
|
101
115
|
record_jump(ctx)
|
|
102
|
-
|
|
103
|
-
target_row = [count - 1, ctx.buffer.line_count - 1].min
|
|
104
|
-
else
|
|
105
|
-
target_row = ctx.buffer.line_count - 1
|
|
106
|
-
end
|
|
116
|
+
target_row = count.nil? ? (ctx.buffer.line_count - 1) : [normalized_count(count) - 1, ctx.buffer.line_count - 1].min
|
|
107
117
|
ctx.window.cursor_y = target_row
|
|
108
118
|
cursor_first_nonblank(ctx)
|
|
109
119
|
end
|
|
@@ -156,6 +166,7 @@ module RuVim
|
|
|
156
166
|
|
|
157
167
|
def enter_insert_mode(ctx, **)
|
|
158
168
|
materialize_intro_buffer_if_needed(ctx)
|
|
169
|
+
ensure_modifiable_for_insert!(ctx)
|
|
159
170
|
ctx.buffer.begin_change_group
|
|
160
171
|
ctx.editor.enter_insert_mode
|
|
161
172
|
ctx.editor.echo("-- INSERT --")
|
|
@@ -180,6 +191,7 @@ module RuVim
|
|
|
180
191
|
|
|
181
192
|
def open_line_below(ctx, **)
|
|
182
193
|
materialize_intro_buffer_if_needed(ctx)
|
|
194
|
+
ensure_modifiable_for_insert!(ctx)
|
|
183
195
|
y = ctx.window.cursor_y
|
|
184
196
|
x = ctx.buffer.line_length(y)
|
|
185
197
|
ctx.buffer.begin_change_group
|
|
@@ -193,6 +205,7 @@ module RuVim
|
|
|
193
205
|
|
|
194
206
|
def open_line_above(ctx, **)
|
|
195
207
|
materialize_intro_buffer_if_needed(ctx)
|
|
208
|
+
ensure_modifiable_for_insert!(ctx)
|
|
196
209
|
y = ctx.window.cursor_y
|
|
197
210
|
ctx.buffer.begin_change_group
|
|
198
211
|
_new_y, new_x = ctx.buffer.insert_newline(y, 0)
|
|
@@ -250,6 +263,42 @@ module RuVim
|
|
|
250
263
|
ctx.editor.focus_window_direction(:down)
|
|
251
264
|
end
|
|
252
265
|
|
|
266
|
+
def window_focus_or_split_left(ctx, **)
|
|
267
|
+
ed = ctx.editor
|
|
268
|
+
if ed.has_split_ancestor_on_axis?(:left)
|
|
269
|
+
ed.focus_window_direction(:left)
|
|
270
|
+
else
|
|
271
|
+
ed.split_current_window(layout: :vertical, place: :before)
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def window_focus_or_split_right(ctx, **)
|
|
276
|
+
ed = ctx.editor
|
|
277
|
+
if ed.has_split_ancestor_on_axis?(:right)
|
|
278
|
+
ed.focus_window_direction(:right)
|
|
279
|
+
else
|
|
280
|
+
ed.split_current_window(layout: :vertical, place: :after)
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def window_focus_or_split_up(ctx, **)
|
|
285
|
+
ed = ctx.editor
|
|
286
|
+
if ed.has_split_ancestor_on_axis?(:up)
|
|
287
|
+
ed.focus_window_direction(:up)
|
|
288
|
+
else
|
|
289
|
+
ed.split_current_window(layout: :horizontal, place: :before)
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def window_focus_or_split_down(ctx, **)
|
|
294
|
+
ed = ctx.editor
|
|
295
|
+
if ed.has_split_ancestor_on_axis?(:down)
|
|
296
|
+
ed.focus_window_direction(:down)
|
|
297
|
+
else
|
|
298
|
+
ed.split_current_window(layout: :horizontal, place: :after)
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
253
302
|
def tab_new(ctx, argv:, **)
|
|
254
303
|
path = argv[0]
|
|
255
304
|
if ctx.buffer.modified? && !ctx.editor.effective_option("hidden", window: ctx.window, buffer: ctx.buffer)
|
|
@@ -258,24 +307,39 @@ module RuVim
|
|
|
258
307
|
return
|
|
259
308
|
end
|
|
260
309
|
end
|
|
261
|
-
|
|
262
|
-
if path && !path.empty?
|
|
263
|
-
b = ctx.editor.current_buffer
|
|
264
|
-
ctx.editor.echo("tab #{ctx.editor.current_tabpage_number}/#{ctx.editor.tabpage_count}: #{b.path || '[No Name]'}")
|
|
265
|
-
else
|
|
266
|
-
ctx.editor.echo("tab #{ctx.editor.current_tabpage_number}/#{ctx.editor.tabpage_count}")
|
|
267
|
-
end
|
|
268
|
-
tab
|
|
310
|
+
ctx.editor.tabnew(path: path)
|
|
269
311
|
end
|
|
270
312
|
|
|
271
313
|
def tab_next(ctx, count:, **)
|
|
314
|
+
count = normalized_count(count)
|
|
272
315
|
ctx.editor.tabnext(count)
|
|
273
|
-
ctx.editor.echo("tab #{ctx.editor.current_tabpage_number}/#{ctx.editor.tabpage_count}")
|
|
274
316
|
end
|
|
275
317
|
|
|
276
318
|
def tab_prev(ctx, count:, **)
|
|
319
|
+
count = normalized_count(count)
|
|
277
320
|
ctx.editor.tabprev(count)
|
|
278
|
-
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def tab_list(ctx, **)
|
|
324
|
+
editor = ctx.editor
|
|
325
|
+
items = []
|
|
326
|
+
# For current tab, use live window_order; for others, use saved snapshot
|
|
327
|
+
editor.tabpages.each_with_index do |tab, i|
|
|
328
|
+
is_current = (i == editor.current_tabpage_index)
|
|
329
|
+
current_marker = is_current ? ">" : " "
|
|
330
|
+
items << "#{current_marker}Tab page #{i + 1}"
|
|
331
|
+
win_ids = is_current ? editor.window_order : editor.tabpage_windows(tab)
|
|
332
|
+
win_ids.each do |wid|
|
|
333
|
+
win = editor.windows[wid]
|
|
334
|
+
next unless win
|
|
335
|
+
buf = editor.buffers[win.buffer_id]
|
|
336
|
+
next unless buf
|
|
337
|
+
active = (is_current && wid == editor.current_window_id) ? ">" : " "
|
|
338
|
+
name = buf.display_name
|
|
339
|
+
items << " #{active} #{name}"
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
editor.echo_multiline(items)
|
|
279
343
|
end
|
|
280
344
|
|
|
281
345
|
def enter_command_line_mode(ctx, **)
|
|
@@ -295,6 +359,7 @@ module RuVim
|
|
|
295
359
|
|
|
296
360
|
def delete_char(ctx, count:, **)
|
|
297
361
|
materialize_intro_buffer_if_needed(ctx)
|
|
362
|
+
count = normalized_count(count)
|
|
298
363
|
ctx.buffer.begin_change_group
|
|
299
364
|
deleted = +""
|
|
300
365
|
count.times do
|
|
@@ -308,8 +373,80 @@ module RuVim
|
|
|
308
373
|
ctx.window.clamp_to_buffer(ctx.buffer)
|
|
309
374
|
end
|
|
310
375
|
|
|
376
|
+
def substitute_char(ctx, count:, bang:, **)
|
|
377
|
+
call(:change_motion, ctx, count:, bang:, kwargs: { motion: "l" })
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def swapcase_char(ctx, count:, **)
|
|
381
|
+
materialize_intro_buffer_if_needed(ctx)
|
|
382
|
+
count = normalized_count(count)
|
|
383
|
+
|
|
384
|
+
y = ctx.window.cursor_y
|
|
385
|
+
x = ctx.window.cursor_x
|
|
386
|
+
processed = false
|
|
387
|
+
|
|
388
|
+
ctx.buffer.begin_change_group
|
|
389
|
+
count.times do
|
|
390
|
+
line = ctx.buffer.line_at(y)
|
|
391
|
+
break if x >= line.length
|
|
392
|
+
|
|
393
|
+
ch = line[x]
|
|
394
|
+
swapped = ch.to_s.swapcase
|
|
395
|
+
if !swapped.empty? && swapped != ch
|
|
396
|
+
ctx.buffer.delete_span(y, x, y, x + 1)
|
|
397
|
+
ctx.buffer.insert_char(y, x, swapped[0])
|
|
398
|
+
end
|
|
399
|
+
processed = true
|
|
400
|
+
x += 1
|
|
401
|
+
end
|
|
402
|
+
ctx.buffer.end_change_group
|
|
403
|
+
|
|
404
|
+
ctx.window.cursor_y = y
|
|
405
|
+
ctx.window.cursor_x = processed ? x : ctx.window.cursor_x
|
|
406
|
+
ctx.window.clamp_to_buffer(ctx.buffer)
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def join_lines(ctx, count:, **)
|
|
410
|
+
materialize_intro_buffer_if_needed(ctx)
|
|
411
|
+
joins = [normalized_count(count) - 1, 1].max
|
|
412
|
+
y = ctx.window.cursor_y
|
|
413
|
+
x = ctx.window.cursor_x
|
|
414
|
+
changed = false
|
|
415
|
+
|
|
416
|
+
ctx.buffer.begin_change_group
|
|
417
|
+
joins.times do
|
|
418
|
+
break if y >= ctx.buffer.line_count - 1
|
|
419
|
+
|
|
420
|
+
left = ctx.buffer.line_at(y)
|
|
421
|
+
right = ctx.buffer.line_at(y + 1)
|
|
422
|
+
join_col = left.length
|
|
423
|
+
|
|
424
|
+
# Join raw lines first.
|
|
425
|
+
break unless ctx.buffer.delete_char(y, join_col)
|
|
426
|
+
|
|
427
|
+
right_trimmed = right.sub(/\A\s+/, "")
|
|
428
|
+
trimmed_count = right.length - right_trimmed.length
|
|
429
|
+
if trimmed_count.positive?
|
|
430
|
+
ctx.buffer.delete_span(y, join_col, y, join_col + trimmed_count)
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
need_space = !left.empty? && !left.match?(/\s\z/) && !right_trimmed.empty? && !right_trimmed.match?(/\A\s/)
|
|
434
|
+
ctx.buffer.insert_char(y, join_col, " ") if need_space
|
|
435
|
+
|
|
436
|
+
x = join_col
|
|
437
|
+
changed = true
|
|
438
|
+
end
|
|
439
|
+
ctx.buffer.end_change_group
|
|
440
|
+
|
|
441
|
+
ctx.window.cursor_y = y
|
|
442
|
+
ctx.window.cursor_x = x
|
|
443
|
+
ctx.window.clamp_to_buffer(ctx.buffer)
|
|
444
|
+
ctx.editor.echo("joined") if changed
|
|
445
|
+
end
|
|
446
|
+
|
|
311
447
|
def delete_line(ctx, count:, **)
|
|
312
448
|
materialize_intro_buffer_if_needed(ctx)
|
|
449
|
+
count = normalized_count(count)
|
|
313
450
|
ctx.buffer.begin_change_group
|
|
314
451
|
deleted_lines = []
|
|
315
452
|
count.times { deleted_lines << ctx.buffer.delete_line(ctx.window.cursor_y) }
|
|
@@ -321,14 +458,15 @@ module RuVim
|
|
|
321
458
|
def delete_motion(ctx, count:, kwargs:, **)
|
|
322
459
|
materialize_intro_buffer_if_needed(ctx)
|
|
323
460
|
motion = (kwargs[:motion] || kwargs["motion"]).to_s
|
|
461
|
+
ncount = normalized_count(count)
|
|
324
462
|
handled =
|
|
325
463
|
case motion
|
|
326
|
-
when "h" then delete_chars_left(ctx,
|
|
327
|
-
when "l" then delete_chars_right(ctx,
|
|
328
|
-
when "j" then delete_lines_down(ctx,
|
|
329
|
-
when "k" then delete_lines_up(ctx,
|
|
464
|
+
when "h" then delete_chars_left(ctx, ncount)
|
|
465
|
+
when "l" then delete_chars_right(ctx, ncount)
|
|
466
|
+
when "j" then delete_lines_down(ctx, ncount)
|
|
467
|
+
when "k" then delete_lines_up(ctx, ncount)
|
|
330
468
|
when "$" then delete_to_end_of_line(ctx)
|
|
331
|
-
when "w" then delete_word_forward(ctx,
|
|
469
|
+
when "w" then delete_word_forward(ctx, ncount)
|
|
332
470
|
when "iw" then delete_text_object_word(ctx, around: false)
|
|
333
471
|
when "aw" then delete_text_object_word(ctx, around: true)
|
|
334
472
|
else
|
|
@@ -433,6 +571,7 @@ module RuVim
|
|
|
433
571
|
|
|
434
572
|
def replace_char(ctx, argv:, count:, **)
|
|
435
573
|
materialize_intro_buffer_if_needed(ctx)
|
|
574
|
+
count = normalized_count(count)
|
|
436
575
|
ch = argv[0].to_s
|
|
437
576
|
raise RuVim::CommandError, "replace requires a character" if ch.empty?
|
|
438
577
|
|
|
@@ -453,6 +592,7 @@ module RuVim
|
|
|
453
592
|
end
|
|
454
593
|
|
|
455
594
|
def yank_line(ctx, count:, **)
|
|
595
|
+
count = normalized_count(count)
|
|
456
596
|
start = ctx.window.cursor_y
|
|
457
597
|
text = ctx.buffer.line_block_text(start, count)
|
|
458
598
|
store_yank_register(ctx, text:, type: :linewise)
|
|
@@ -557,6 +697,45 @@ module RuVim
|
|
|
557
697
|
ctx.editor.enter_normal_mode
|
|
558
698
|
end
|
|
559
699
|
|
|
700
|
+
def indent_lines(ctx, count:, **)
|
|
701
|
+
count = normalized_count(count)
|
|
702
|
+
start_row = ctx.window.cursor_y
|
|
703
|
+
end_row = [start_row + count - 1, ctx.buffer.line_count - 1].min
|
|
704
|
+
reindent_range(ctx, start_row, end_row)
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
def indent_motion(ctx, count:, kwargs:, **)
|
|
708
|
+
motion = (kwargs[:motion] || kwargs["motion"]).to_s
|
|
709
|
+
ncount = normalized_count(count)
|
|
710
|
+
start_row = ctx.window.cursor_y
|
|
711
|
+
case motion
|
|
712
|
+
when "j"
|
|
713
|
+
end_row = [start_row + ncount, ctx.buffer.line_count - 1].min
|
|
714
|
+
when "k"
|
|
715
|
+
end_row = start_row
|
|
716
|
+
start_row = [start_row - ncount, 0].max
|
|
717
|
+
when "G"
|
|
718
|
+
end_row = ctx.buffer.line_count - 1
|
|
719
|
+
when "gg"
|
|
720
|
+
end_row = start_row
|
|
721
|
+
start_row = 0
|
|
722
|
+
else
|
|
723
|
+
ctx.editor.echo("Unsupported motion for =: #{motion}")
|
|
724
|
+
return
|
|
725
|
+
end
|
|
726
|
+
reindent_range(ctx, start_row, end_row)
|
|
727
|
+
end
|
|
728
|
+
|
|
729
|
+
def visual_indent(ctx, **)
|
|
730
|
+
sel = ctx.editor.visual_selection
|
|
731
|
+
return unless sel
|
|
732
|
+
|
|
733
|
+
start_row = sel[:start_row]
|
|
734
|
+
end_row = sel[:end_row]
|
|
735
|
+
reindent_range(ctx, start_row, end_row)
|
|
736
|
+
ctx.editor.enter_normal_mode
|
|
737
|
+
end
|
|
738
|
+
|
|
560
739
|
def visual_select_text_object(ctx, kwargs:, **)
|
|
561
740
|
motion = (kwargs[:motion] || kwargs["motion"]).to_s
|
|
562
741
|
span = text_object_span(ctx.buffer, ctx.window, motion)
|
|
@@ -582,6 +761,9 @@ module RuVim
|
|
|
582
761
|
size = File.exist?(target) ? File.size(target) : 0
|
|
583
762
|
suffix = bang ? " (force accepted)" : ""
|
|
584
763
|
ctx.editor.echo("\"#{target}\" #{ctx.buffer.line_count}L, #{size}B written#{suffix}")
|
|
764
|
+
if ctx.editor.get_option("onsavehook")
|
|
765
|
+
ctx.buffer.lang_module.on_save(ctx, target)
|
|
766
|
+
end
|
|
585
767
|
end
|
|
586
768
|
|
|
587
769
|
def app_quit(ctx, bang:, **)
|
|
@@ -605,6 +787,25 @@ module RuVim
|
|
|
605
787
|
ctx.editor.request_quit!
|
|
606
788
|
end
|
|
607
789
|
|
|
790
|
+
def app_quit_all(ctx, bang:, **)
|
|
791
|
+
unless bang
|
|
792
|
+
modified = ctx.editor.buffers.values.select { |b| b.file_buffer? && b.modified? }
|
|
793
|
+
unless modified.empty?
|
|
794
|
+
ctx.editor.echo_error("#{modified.size} buffer(s) have unsaved changes (add ! to override)")
|
|
795
|
+
return
|
|
796
|
+
end
|
|
797
|
+
end
|
|
798
|
+
ctx.editor.request_quit!
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
def file_write_quit_all(ctx, bang:, **)
|
|
802
|
+
ctx.editor.buffers.each_value do |buf|
|
|
803
|
+
next unless buf.file_buffer? && buf.modified? && buf.path
|
|
804
|
+
buf.write_to(buf.path)
|
|
805
|
+
end
|
|
806
|
+
app_quit_all(ctx, bang: true)
|
|
807
|
+
end
|
|
808
|
+
|
|
608
809
|
def file_write_quit(ctx, argv:, bang:, **)
|
|
609
810
|
file_write(ctx, argv:, bang:)
|
|
610
811
|
return unless ctx.editor.running?
|
|
@@ -639,9 +840,7 @@ module RuVim
|
|
|
639
840
|
end
|
|
640
841
|
end
|
|
641
842
|
|
|
642
|
-
|
|
643
|
-
ctx.editor.switch_to_buffer(new_buffer.id)
|
|
644
|
-
ctx.editor.echo(File.exist?(path) ? "\"#{path}\" #{new_buffer.line_count}L" : "\"#{path}\" [New File]")
|
|
843
|
+
ctx.editor.open_path(path)
|
|
645
844
|
end
|
|
646
845
|
|
|
647
846
|
def file_goto_under_cursor(ctx, **)
|
|
@@ -651,9 +850,10 @@ module RuVim
|
|
|
651
850
|
return
|
|
652
851
|
end
|
|
653
852
|
|
|
654
|
-
|
|
853
|
+
target = parse_gf_target(token)
|
|
854
|
+
path = resolve_gf_path(ctx, target[:path])
|
|
655
855
|
unless path
|
|
656
|
-
ctx.editor.echo_error("File not found: #{
|
|
856
|
+
ctx.editor.echo_error("File not found: #{target[:path]}")
|
|
657
857
|
return
|
|
658
858
|
end
|
|
659
859
|
|
|
@@ -665,6 +865,7 @@ module RuVim
|
|
|
665
865
|
end
|
|
666
866
|
|
|
667
867
|
ctx.editor.open_path(path)
|
|
868
|
+
move_cursor_to_gf_line(ctx, target[:line]) if target[:line]
|
|
668
869
|
end
|
|
669
870
|
|
|
670
871
|
def buffer_list(ctx, **)
|
|
@@ -672,23 +873,28 @@ module RuVim
|
|
|
672
873
|
alt_id = ctx.editor.alternate_buffer_id
|
|
673
874
|
items = ctx.editor.buffer_ids.map do |id|
|
|
674
875
|
b = ctx.editor.buffers.fetch(id)
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
876
|
+
indicator = id == current_id ? "%a" : " "
|
|
877
|
+
indicator = "# " if id == alt_id && id != current_id
|
|
878
|
+
mod = b.modified? ? "+" : " "
|
|
879
|
+
name = b.path ? "\"#{b.path}\"" : "[No Name]"
|
|
880
|
+
line_info = "line #{b.respond_to?(:cursor_line) ? b.cursor_line : 0}"
|
|
881
|
+
# Find the window showing this buffer to get cursor line
|
|
882
|
+
win = ctx.editor.windows.values.find { |w| w.buffer_id == id }
|
|
883
|
+
line_info = "line #{win ? win.cursor_y + 1 : 0}"
|
|
884
|
+
"%3d %s %s %-30s %s" % [id, indicator, mod, name, line_info]
|
|
681
885
|
end
|
|
682
|
-
ctx.editor.
|
|
886
|
+
ctx.editor.echo_multiline(items)
|
|
683
887
|
end
|
|
684
888
|
|
|
685
889
|
def buffer_next(ctx, count:, bang:, **)
|
|
890
|
+
count = normalized_count(count)
|
|
686
891
|
target = ctx.editor.current_buffer.id
|
|
687
892
|
count.times { target = ctx.editor.next_buffer_id_from(target, 1) }
|
|
688
893
|
switch_buffer_id(ctx, target, bang:)
|
|
689
894
|
end
|
|
690
895
|
|
|
691
896
|
def buffer_prev(ctx, count:, bang:, **)
|
|
897
|
+
count = normalized_count(count)
|
|
692
898
|
target = ctx.editor.current_buffer.id
|
|
693
899
|
count.times { target = ctx.editor.next_buffer_id_from(target, -1) }
|
|
694
900
|
switch_buffer_id(ctx, target, bang:)
|
|
@@ -768,7 +974,7 @@ module RuVim
|
|
|
768
974
|
when "config"
|
|
769
975
|
"Config: XDG Ruby DSL at ~/.config/ruvim/init.rb and ftplugin/<filetype>.rb"
|
|
770
976
|
when "bindings", "keys", "keymap"
|
|
771
|
-
"Bindings:
|
|
977
|
+
"Bindings: use :bindings (current effective key bindings by layer). Docs: docs/binding.md"
|
|
772
978
|
when "number", "relativenumber", "ignorecase", "smartcase", "hlsearch", "tabstop", "filetype"
|
|
773
979
|
option_help_line(key)
|
|
774
980
|
else
|
|
@@ -784,8 +990,17 @@ module RuVim
|
|
|
784
990
|
def ex_define_command(ctx, argv:, bang:, **)
|
|
785
991
|
registry = RuVim::ExCommandRegistry.instance
|
|
786
992
|
if argv.empty?
|
|
787
|
-
|
|
788
|
-
|
|
993
|
+
user_cmds = registry.all.select { |spec| spec.source == :user }
|
|
994
|
+
if user_cmds.empty?
|
|
995
|
+
ctx.editor.echo("No user commands")
|
|
996
|
+
else
|
|
997
|
+
header = " Name Definition"
|
|
998
|
+
items = [header] + user_cmds.map { |spec|
|
|
999
|
+
body = spec.respond_to?(:body) ? spec.body.to_s : spec.name.to_s
|
|
1000
|
+
" %-12s%s" % [spec.name, body]
|
|
1001
|
+
}
|
|
1002
|
+
ctx.editor.echo_multiline(items)
|
|
1003
|
+
end
|
|
789
1004
|
return
|
|
790
1005
|
end
|
|
791
1006
|
|
|
@@ -912,14 +1127,33 @@ module RuVim
|
|
|
912
1127
|
end
|
|
913
1128
|
|
|
914
1129
|
def ex_commands(ctx, **)
|
|
915
|
-
|
|
1130
|
+
rows = RuVim::ExCommandRegistry.instance.all.map do |spec|
|
|
916
1131
|
alias_text = spec.aliases.empty? ? "" : " (#{spec.aliases.join(', ')})"
|
|
917
1132
|
source = spec.source == :user ? " [user]" : ""
|
|
918
|
-
"#{spec.name}#{alias_text}#{source}"
|
|
1133
|
+
name = "#{spec.name}#{alias_text}#{source}"
|
|
1134
|
+
desc = spec.desc.to_s
|
|
1135
|
+
keys = ex_command_binding_labels(ctx.editor, spec)
|
|
1136
|
+
[name, desc, keys]
|
|
1137
|
+
end
|
|
1138
|
+
name_width = rows.map { |name, _desc, _keys| name.length }.max || 0
|
|
1139
|
+
items = rows.map do |name, desc, keys|
|
|
1140
|
+
line = "#{name.ljust(name_width)} #{desc}"
|
|
1141
|
+
line += " keys: #{keys.join(', ')}" unless keys.empty?
|
|
1142
|
+
line
|
|
919
1143
|
end
|
|
920
1144
|
ctx.editor.show_help_buffer!(title: "[Commands]", lines: ["Ex commands", "", *items])
|
|
921
1145
|
end
|
|
922
1146
|
|
|
1147
|
+
def ex_bindings(ctx, argv: [], **)
|
|
1148
|
+
keymaps = ctx.editor.keymap_manager
|
|
1149
|
+
raise RuVim::CommandError, "Keymap manager is unavailable" unless keymaps
|
|
1150
|
+
|
|
1151
|
+
mode_filter, sort = parse_bindings_args(argv)
|
|
1152
|
+
entries = keymaps.binding_entries_for_context(ctx.editor, mode: mode_filter)
|
|
1153
|
+
lines = bindings_buffer_lines(ctx.editor, entries, mode_filter:, sort:)
|
|
1154
|
+
ctx.editor.show_help_buffer!(title: "[Bindings]", lines:)
|
|
1155
|
+
end
|
|
1156
|
+
|
|
923
1157
|
def ex_set(ctx, argv:, **)
|
|
924
1158
|
ex_set_common(ctx, argv, scope: :auto)
|
|
925
1159
|
end
|
|
@@ -942,6 +1176,7 @@ module RuVim
|
|
|
942
1176
|
end
|
|
943
1177
|
|
|
944
1178
|
ctx.editor.set_quickfix_list(items)
|
|
1179
|
+
ctx.editor.select_quickfix(0)
|
|
945
1180
|
ctx.editor.jump_to_location(ctx.editor.current_quickfix_item)
|
|
946
1181
|
ctx.editor.echo("quickfix: #{items.length} item(s)")
|
|
947
1182
|
end
|
|
@@ -956,6 +1191,7 @@ module RuVim
|
|
|
956
1191
|
end
|
|
957
1192
|
|
|
958
1193
|
ctx.editor.set_location_list(items, window_id: ctx.window.id)
|
|
1194
|
+
ctx.editor.select_location_list(0, window_id: ctx.window.id)
|
|
959
1195
|
ctx.editor.jump_to_location(ctx.editor.current_location_list_item(ctx.window.id))
|
|
960
1196
|
ctx.editor.echo("location list: #{items.length} item(s)")
|
|
961
1197
|
end
|
|
@@ -975,6 +1211,7 @@ module RuVim
|
|
|
975
1211
|
return
|
|
976
1212
|
end
|
|
977
1213
|
ctx.editor.jump_to_location(item)
|
|
1214
|
+
refresh_list_window(ctx.editor, :quickfix)
|
|
978
1215
|
ctx.editor.echo(quickfix_item_echo(ctx.editor))
|
|
979
1216
|
end
|
|
980
1217
|
|
|
@@ -985,6 +1222,7 @@ module RuVim
|
|
|
985
1222
|
return
|
|
986
1223
|
end
|
|
987
1224
|
ctx.editor.jump_to_location(item)
|
|
1225
|
+
refresh_list_window(ctx.editor, :quickfix)
|
|
988
1226
|
ctx.editor.echo(quickfix_item_echo(ctx.editor))
|
|
989
1227
|
end
|
|
990
1228
|
|
|
@@ -1004,6 +1242,7 @@ module RuVim
|
|
|
1004
1242
|
return
|
|
1005
1243
|
end
|
|
1006
1244
|
ctx.editor.jump_to_location(item)
|
|
1245
|
+
refresh_list_window(ctx.editor, :location_list)
|
|
1007
1246
|
ctx.editor.echo(location_item_echo(ctx.editor, ctx.window.id))
|
|
1008
1247
|
end
|
|
1009
1248
|
|
|
@@ -1014,37 +1253,89 @@ module RuVim
|
|
|
1014
1253
|
return
|
|
1015
1254
|
end
|
|
1016
1255
|
ctx.editor.jump_to_location(item)
|
|
1256
|
+
refresh_list_window(ctx.editor, :location_list)
|
|
1017
1257
|
ctx.editor.echo(location_item_echo(ctx.editor, ctx.window.id))
|
|
1018
1258
|
end
|
|
1019
1259
|
|
|
1020
|
-
def ex_substitute(ctx, pattern:, replacement:, global: false, **)
|
|
1260
|
+
def ex_substitute(ctx, pattern:, replacement:, flags_str: nil, range_start: nil, range_end: nil, global: false, **)
|
|
1021
1261
|
materialize_intro_buffer_if_needed(ctx)
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
end
|
|
1035
|
-
end
|
|
1262
|
+
flags = parse_substitute_flags(flags_str, default_global: global)
|
|
1263
|
+
raise RuVim::CommandError, "Confirm flag (:s///c) is not yet supported" if flags[:confirm]
|
|
1264
|
+
|
|
1265
|
+
regex = build_substitute_regex(pattern, flags, ctx)
|
|
1266
|
+
|
|
1267
|
+
r_start = range_start || 0
|
|
1268
|
+
r_end = range_end || (ctx.buffer.line_count - 1)
|
|
1269
|
+
|
|
1270
|
+
if flags[:count_only]
|
|
1271
|
+
total = count_matches_in_range(ctx.buffer, regex, r_start, r_end, flags[:global])
|
|
1272
|
+
ctx.editor.echo("#{total} match(es)")
|
|
1273
|
+
return
|
|
1036
1274
|
end
|
|
1037
1275
|
|
|
1276
|
+
changed = substitute_range(ctx, regex, replacement, r_start, r_end, flags)
|
|
1277
|
+
|
|
1038
1278
|
if changed.positive?
|
|
1039
|
-
ctx.buffer.begin_change_group
|
|
1040
|
-
ctx.buffer.replace_all_lines!(new_lines)
|
|
1041
|
-
ctx.buffer.end_change_group
|
|
1042
1279
|
ctx.editor.echo("#{changed} substitution(s)")
|
|
1280
|
+
elsif flags[:no_error]
|
|
1281
|
+
ctx.editor.echo("Pattern not found: #{pattern}")
|
|
1043
1282
|
else
|
|
1044
1283
|
ctx.editor.echo("Pattern not found: #{pattern}")
|
|
1045
1284
|
end
|
|
1046
1285
|
end
|
|
1047
1286
|
|
|
1287
|
+
def ex_grep(ctx, argv:, kwargs: {}, **)
|
|
1288
|
+
run_external_grep(ctx, argv:, target: :quickfix)
|
|
1289
|
+
end
|
|
1290
|
+
|
|
1291
|
+
def ex_lgrep(ctx, argv:, kwargs: {}, **)
|
|
1292
|
+
run_external_grep(ctx, argv:, target: :location_list)
|
|
1293
|
+
end
|
|
1294
|
+
|
|
1295
|
+
def ex_delete_lines(ctx, kwargs: {}, **)
|
|
1296
|
+
materialize_intro_buffer_if_needed(ctx)
|
|
1297
|
+
r_start = kwargs[:range_start]
|
|
1298
|
+
r_end = kwargs[:range_end]
|
|
1299
|
+
unless r_start && r_end
|
|
1300
|
+
# Default to current line
|
|
1301
|
+
r_start = r_end = ctx.window.cursor_y
|
|
1302
|
+
end
|
|
1303
|
+
|
|
1304
|
+
count = r_end - r_start + 1
|
|
1305
|
+
deleted_text = ctx.buffer.line_block_text(r_start, count)
|
|
1306
|
+
ctx.buffer.begin_change_group
|
|
1307
|
+
count.times { ctx.buffer.delete_line(r_start) }
|
|
1308
|
+
ctx.buffer.end_change_group
|
|
1309
|
+
store_delete_register(ctx, text: deleted_text, type: :linewise)
|
|
1310
|
+
ctx.window.cursor_y = [r_start, ctx.buffer.line_count - 1].min
|
|
1311
|
+
ctx.window.cursor_x = 0
|
|
1312
|
+
ctx.window.clamp_to_buffer(ctx.buffer)
|
|
1313
|
+
ctx.editor.echo("#{count} line(s) deleted")
|
|
1314
|
+
end
|
|
1315
|
+
|
|
1316
|
+
def ex_yank_lines(ctx, kwargs: {}, **)
|
|
1317
|
+
materialize_intro_buffer_if_needed(ctx)
|
|
1318
|
+
r_start = kwargs[:range_start]
|
|
1319
|
+
r_end = kwargs[:range_end]
|
|
1320
|
+
unless r_start && r_end
|
|
1321
|
+
r_start = r_end = ctx.window.cursor_y
|
|
1322
|
+
end
|
|
1323
|
+
|
|
1324
|
+
count = r_end - r_start + 1
|
|
1325
|
+
text = ctx.buffer.line_block_text(r_start, count)
|
|
1326
|
+
store_yank_register(ctx, text:, type: :linewise)
|
|
1327
|
+
ctx.editor.echo("#{count} line(s) yanked")
|
|
1328
|
+
end
|
|
1329
|
+
|
|
1330
|
+
def ex_rich(ctx, argv: [], **)
|
|
1331
|
+
format = argv.first
|
|
1332
|
+
RuVim::RichView.toggle!(ctx.editor, format: format)
|
|
1333
|
+
end
|
|
1334
|
+
|
|
1335
|
+
def rich_toggle(ctx, **)
|
|
1336
|
+
RuVim::RichView.toggle!(ctx.editor)
|
|
1337
|
+
end
|
|
1338
|
+
|
|
1048
1339
|
def submit_search(ctx, pattern:, direction:)
|
|
1049
1340
|
text = pattern.to_s
|
|
1050
1341
|
if text.empty?
|
|
@@ -1060,6 +1351,35 @@ module RuVim
|
|
|
1060
1351
|
|
|
1061
1352
|
private
|
|
1062
1353
|
|
|
1354
|
+
def reindent_range(ctx, start_row, end_row)
|
|
1355
|
+
buf = ctx.buffer
|
|
1356
|
+
lang_mod = buf.lang_module
|
|
1357
|
+
|
|
1358
|
+
sw = ctx.editor.effective_option("shiftwidth", buffer: buf).to_i
|
|
1359
|
+
sw = 2 if sw <= 0
|
|
1360
|
+
|
|
1361
|
+
buf.begin_change_group
|
|
1362
|
+
(start_row..end_row).each do |row|
|
|
1363
|
+
target_indent = lang_mod.calculate_indent(buf.lines, row, sw)
|
|
1364
|
+
next unless target_indent
|
|
1365
|
+
|
|
1366
|
+
line = buf.line_at(row)
|
|
1367
|
+
current_indent = line[/\A */].to_s.length
|
|
1368
|
+
next if current_indent == target_indent
|
|
1369
|
+
|
|
1370
|
+
buf.delete_span(row, 0, row, current_indent) if current_indent > 0
|
|
1371
|
+
buf.insert_text(row, 0, " " * target_indent) if target_indent > 0
|
|
1372
|
+
end
|
|
1373
|
+
buf.end_change_group
|
|
1374
|
+
|
|
1375
|
+
ctx.window.cursor_y = start_row
|
|
1376
|
+
line = buf.line_at(start_row)
|
|
1377
|
+
ctx.window.cursor_x = (line[/\A */]&.length || 0)
|
|
1378
|
+
ctx.window.clamp_to_buffer(buf)
|
|
1379
|
+
count = end_row - start_row + 1
|
|
1380
|
+
ctx.editor.echo("#{count} line#{"s" if count > 1} indented")
|
|
1381
|
+
end
|
|
1382
|
+
|
|
1063
1383
|
def parse_vimgrep_pattern(argv)
|
|
1064
1384
|
raw = Array(argv).join(" ").strip
|
|
1065
1385
|
raise RuVim::CommandError, "Usage: :vimgrep /pattern/" if raw.empty?
|
|
@@ -1071,6 +1391,70 @@ module RuVim
|
|
|
1071
1391
|
end
|
|
1072
1392
|
end
|
|
1073
1393
|
|
|
1394
|
+
def run_external_grep(ctx, argv:, target:)
|
|
1395
|
+
args = Array(argv).join(" ").strip
|
|
1396
|
+
raise RuVim::CommandError, "Usage: :grep pattern [files...]" if args.empty?
|
|
1397
|
+
|
|
1398
|
+
grepprg = ctx.editor.effective_option("grepprg", window: ctx.window, buffer: ctx.buffer) || "grep -n"
|
|
1399
|
+
cmd = "#{grepprg} #{args}"
|
|
1400
|
+
|
|
1401
|
+
stdout, stderr, status = Open3.capture3(cmd)
|
|
1402
|
+
if stdout.strip.empty? && !status.success?
|
|
1403
|
+
msg = stderr.strip.empty? ? "No matches found" : stderr.strip
|
|
1404
|
+
ctx.editor.echo_error(msg)
|
|
1405
|
+
return
|
|
1406
|
+
end
|
|
1407
|
+
|
|
1408
|
+
items = parse_grep_output(ctx, stdout)
|
|
1409
|
+
if items.empty?
|
|
1410
|
+
ctx.editor.echo_error("No matches found")
|
|
1411
|
+
return
|
|
1412
|
+
end
|
|
1413
|
+
|
|
1414
|
+
case target
|
|
1415
|
+
when :quickfix
|
|
1416
|
+
ctx.editor.set_quickfix_list(items)
|
|
1417
|
+
ctx.editor.select_quickfix(0)
|
|
1418
|
+
ctx.editor.jump_to_location(ctx.editor.current_quickfix_item)
|
|
1419
|
+
ctx.editor.echo("quickfix: #{items.length} item(s)")
|
|
1420
|
+
when :location_list
|
|
1421
|
+
ctx.editor.set_location_list(items, window_id: ctx.window.id)
|
|
1422
|
+
ctx.editor.select_location_list(0, window_id: ctx.window.id)
|
|
1423
|
+
ctx.editor.jump_to_location(ctx.editor.current_location_list_item(ctx.window.id))
|
|
1424
|
+
ctx.editor.echo("location list: #{items.length} item(s)")
|
|
1425
|
+
end
|
|
1426
|
+
end
|
|
1427
|
+
|
|
1428
|
+
def parse_grep_output(ctx, output)
|
|
1429
|
+
items = []
|
|
1430
|
+
output.each_line do |line|
|
|
1431
|
+
line = line.chomp
|
|
1432
|
+
# Parse filename:lineno:text format
|
|
1433
|
+
if (m = line.match(/\A(.+?):(\d+):(.*)?\z/))
|
|
1434
|
+
filepath = m[1]
|
|
1435
|
+
lineno = m[2].to_i - 1 # 0-based
|
|
1436
|
+
text = m[3].to_s
|
|
1437
|
+
buf = ensure_buffer_for_grep_file(ctx, filepath)
|
|
1438
|
+
items << { buffer_id: buf.id, row: lineno, col: 0, text: text }
|
|
1439
|
+
end
|
|
1440
|
+
end
|
|
1441
|
+
items
|
|
1442
|
+
end
|
|
1443
|
+
|
|
1444
|
+
def ensure_buffer_for_grep_file(ctx, filepath)
|
|
1445
|
+
abspath = File.expand_path(filepath)
|
|
1446
|
+
# Check if buffer already exists for this file
|
|
1447
|
+
existing = ctx.editor.buffers.values.find { |b| b.path && File.expand_path(b.path) == abspath }
|
|
1448
|
+
return existing if existing
|
|
1449
|
+
|
|
1450
|
+
# Create buffer for the file
|
|
1451
|
+
if File.exist?(abspath)
|
|
1452
|
+
ctx.editor.add_buffer_from_file(abspath)
|
|
1453
|
+
else
|
|
1454
|
+
ctx.editor.add_empty_buffer(path: abspath)
|
|
1455
|
+
end
|
|
1456
|
+
end
|
|
1457
|
+
|
|
1074
1458
|
def grep_items_for_buffers(buffers, regex)
|
|
1075
1459
|
Array(buffers).flat_map do |buffer|
|
|
1076
1460
|
buffer.lines.each_with_index.flat_map do |line, row|
|
|
@@ -1136,6 +1520,22 @@ module RuVim
|
|
|
1136
1520
|
editor.echo("#{kind} closed")
|
|
1137
1521
|
end
|
|
1138
1522
|
|
|
1523
|
+
def refresh_list_window(editor, kind)
|
|
1524
|
+
wids = editor.find_window_ids_by_buffer_kind(kind)
|
|
1525
|
+
return if wids.empty?
|
|
1526
|
+
|
|
1527
|
+
lines = case kind
|
|
1528
|
+
when :quickfix then quickfix_buffer_lines(editor)
|
|
1529
|
+
when :location_list then location_list_buffer_lines(editor, editor.current_window_id)
|
|
1530
|
+
end
|
|
1531
|
+
wids.each do |wid|
|
|
1532
|
+
buf = editor.buffers[editor.windows[wid].buffer_id]
|
|
1533
|
+
next unless buf
|
|
1534
|
+
# Bypass modifiable check — this is an internal refresh of a readonly list buffer
|
|
1535
|
+
buf.instance_variable_set(:@lines, Array(lines).map(&:dup))
|
|
1536
|
+
end
|
|
1537
|
+
end
|
|
1538
|
+
|
|
1139
1539
|
def quickfix_item_echo(editor)
|
|
1140
1540
|
item = editor.current_quickfix_item
|
|
1141
1541
|
list_item_echo(editor, item, editor.quickfix_index, editor.quickfix_items.length, label: "qf")
|
|
@@ -1408,6 +1808,7 @@ module RuVim
|
|
|
1408
1808
|
flat = cursor_to_offset(buffer, row, col)
|
|
1409
1809
|
idx = flat
|
|
1410
1810
|
keyword_rx = keyword_char_regex(editor, buffer, window)
|
|
1811
|
+
count = normalized_count(count)
|
|
1411
1812
|
count.times do
|
|
1412
1813
|
idx = next_word_start_offset(text, idx, keyword_rx)
|
|
1413
1814
|
return nil unless idx
|
|
@@ -1419,7 +1820,7 @@ module RuVim
|
|
|
1419
1820
|
buffer = ctx.buffer
|
|
1420
1821
|
row = ctx.window.cursor_y
|
|
1421
1822
|
col = ctx.window.cursor_x
|
|
1422
|
-
count =
|
|
1823
|
+
count = normalized_count(count)
|
|
1423
1824
|
target = { row:, col: }
|
|
1424
1825
|
count.times do
|
|
1425
1826
|
target =
|
|
@@ -1460,6 +1861,14 @@ module RuVim
|
|
|
1460
1861
|
idx = cursor_to_offset(buffer, row, col)
|
|
1461
1862
|
n = text.length
|
|
1462
1863
|
keyword_rx = keyword_char_regex(editor, buffer, window)
|
|
1864
|
+
|
|
1865
|
+
# Vim-like `e`: if already on the end of a word, move to the next word's end.
|
|
1866
|
+
if idx < n
|
|
1867
|
+
cur_cls = char_class(text[idx], keyword_rx)
|
|
1868
|
+
next_cls = (idx + 1 < n) ? char_class(text[idx + 1], keyword_rx) : nil
|
|
1869
|
+
idx += 1 if cur_cls != :space && next_cls != cur_cls
|
|
1870
|
+
end
|
|
1871
|
+
|
|
1463
1872
|
while idx < n && char_class(text[idx], keyword_rx) == :space
|
|
1464
1873
|
idx += 1
|
|
1465
1874
|
end
|
|
@@ -1846,6 +2255,30 @@ module RuVim
|
|
|
1846
2255
|
ctx.window.clamp_to_buffer(ctx.buffer)
|
|
1847
2256
|
end
|
|
1848
2257
|
|
|
2258
|
+
def place_cursor_line_in_window(ctx, where:, count:)
|
|
2259
|
+
if count
|
|
2260
|
+
target_row = [[normalized_count(count) - 1, 0].max, ctx.buffer.line_count - 1].min
|
|
2261
|
+
ctx.window.cursor_y = target_row
|
|
2262
|
+
ctx.window.clamp_to_buffer(ctx.buffer)
|
|
2263
|
+
end
|
|
2264
|
+
|
|
2265
|
+
height = current_view_height(ctx)
|
|
2266
|
+
max_row_offset = [ctx.buffer.line_count - height, 0].max
|
|
2267
|
+
desired =
|
|
2268
|
+
case where
|
|
2269
|
+
when :top
|
|
2270
|
+
ctx.window.cursor_y
|
|
2271
|
+
when :center
|
|
2272
|
+
ctx.window.cursor_y - (height / 2)
|
|
2273
|
+
when :bottom
|
|
2274
|
+
ctx.window.cursor_y - height + 1
|
|
2275
|
+
else
|
|
2276
|
+
ctx.window.row_offset
|
|
2277
|
+
end
|
|
2278
|
+
ctx.window.row_offset = [[desired, 0].max, max_row_offset].min
|
|
2279
|
+
ctx.window.clamp_to_buffer(ctx.buffer)
|
|
2280
|
+
end
|
|
2281
|
+
|
|
1849
2282
|
def cursor_to_offset(buffer, row, col)
|
|
1850
2283
|
offset = 0
|
|
1851
2284
|
row.times { |r| offset += buffer.line_length(r) + 1 }
|
|
@@ -1881,6 +2314,7 @@ module RuVim
|
|
|
1881
2314
|
lines = text.sub(/\n\z/, "").split("\n", -1)
|
|
1882
2315
|
return if lines.empty?
|
|
1883
2316
|
|
|
2317
|
+
count = normalized_count(count)
|
|
1884
2318
|
insert_at = before ? ctx.window.cursor_y : (ctx.window.cursor_y + 1)
|
|
1885
2319
|
ctx.buffer.begin_change_group
|
|
1886
2320
|
count.times { ctx.buffer.insert_lines_at(insert_at, lines) }
|
|
@@ -1956,6 +2390,16 @@ module RuVim
|
|
|
1956
2390
|
nil
|
|
1957
2391
|
end
|
|
1958
2392
|
|
|
2393
|
+
def normalized_count(count, default: 1)
|
|
2394
|
+
n = count.nil? ? default : count.to_i
|
|
2395
|
+
n = default if n <= 0
|
|
2396
|
+
n
|
|
2397
|
+
end
|
|
2398
|
+
|
|
2399
|
+
def ensure_modifiable_for_insert!(ctx)
|
|
2400
|
+
raise RuVim::CommandError, "Buffer is not modifiable" unless ctx.buffer.modifiable?
|
|
2401
|
+
end
|
|
2402
|
+
|
|
1959
2403
|
def maybe_autowrite_before_switch(ctx)
|
|
1960
2404
|
return false unless ctx.editor.effective_option("autowrite", window: ctx.window, buffer: ctx.buffer)
|
|
1961
2405
|
return false unless ctx.buffer.file_buffer?
|
|
@@ -1972,7 +2416,7 @@ module RuVim
|
|
|
1972
2416
|
return nil if line.empty?
|
|
1973
2417
|
|
|
1974
2418
|
x = [[window.cursor_x, 0].max, [line.length - 1, 0].max].min
|
|
1975
|
-
file_char = /[[:alnum:]_
|
|
2419
|
+
file_char = /[[:alnum:]_\.\/~:-]/
|
|
1976
2420
|
if line[x] !~ file_char
|
|
1977
2421
|
left = x - 1
|
|
1978
2422
|
right = x + 1
|
|
@@ -1992,6 +2436,25 @@ module RuVim
|
|
|
1992
2436
|
line[s...e]
|
|
1993
2437
|
end
|
|
1994
2438
|
|
|
2439
|
+
def parse_gf_target(token)
|
|
2440
|
+
raw = token.to_s
|
|
2441
|
+
if (m = /\A(.+):(\d+)\z/.match(raw))
|
|
2442
|
+
return { path: m[1], line: m[2].to_i } unless m[1].end_with?(":")
|
|
2443
|
+
end
|
|
2444
|
+
{ path: raw, line: nil }
|
|
2445
|
+
end
|
|
2446
|
+
|
|
2447
|
+
def move_cursor_to_gf_line(ctx, line_no)
|
|
2448
|
+
line = line_no.to_i
|
|
2449
|
+
return if line <= 0
|
|
2450
|
+
|
|
2451
|
+
w = ctx.editor.current_window
|
|
2452
|
+
b = ctx.editor.current_buffer
|
|
2453
|
+
w.cursor_y = [line - 1, b.line_count - 1].min
|
|
2454
|
+
w.cursor_x = 0
|
|
2455
|
+
w.clamp_to_buffer(b)
|
|
2456
|
+
end
|
|
2457
|
+
|
|
1995
2458
|
def resolve_gf_path(ctx, token)
|
|
1996
2459
|
candidates = gf_candidate_paths(ctx, token.to_s)
|
|
1997
2460
|
candidates.find { |p| File.file?(p) || File.directory?(p) }
|
|
@@ -2063,7 +2526,7 @@ module RuVim
|
|
|
2063
2526
|
items = editor.option_snapshot(window: ctx.window, buffer: ctx.buffer).map do |opt|
|
|
2064
2527
|
format_option_value(opt[:name], opt[:effective])
|
|
2065
2528
|
end
|
|
2066
|
-
ctx.editor.
|
|
2529
|
+
ctx.editor.echo_multiline(items)
|
|
2067
2530
|
return
|
|
2068
2531
|
end
|
|
2069
2532
|
|
|
@@ -2211,7 +2674,235 @@ module RuVim
|
|
|
2211
2674
|
]
|
|
2212
2675
|
end
|
|
2213
2676
|
|
|
2677
|
+
def parse_bindings_args(argv)
|
|
2678
|
+
mode_filter = nil
|
|
2679
|
+
sort = "key"
|
|
2680
|
+
|
|
2681
|
+
Array(argv).each do |raw|
|
|
2682
|
+
token = raw.to_s.strip
|
|
2683
|
+
next if token.empty?
|
|
2684
|
+
|
|
2685
|
+
if token.include?("=")
|
|
2686
|
+
key, value = token.split("=", 2).map(&:strip)
|
|
2687
|
+
case key.downcase
|
|
2688
|
+
when "sort"
|
|
2689
|
+
sort = parse_bindings_sort(value)
|
|
2690
|
+
else
|
|
2691
|
+
raise RuVim::CommandError, "Unknown option for :bindings: #{key}"
|
|
2692
|
+
end
|
|
2693
|
+
next
|
|
2694
|
+
end
|
|
2695
|
+
|
|
2696
|
+
raise RuVim::CommandError, "Too many positional args for :bindings" if mode_filter
|
|
2697
|
+
|
|
2698
|
+
mode_filter = parse_bindings_mode_filter(token)
|
|
2699
|
+
end
|
|
2700
|
+
|
|
2701
|
+
[mode_filter, sort]
|
|
2702
|
+
end
|
|
2703
|
+
|
|
2704
|
+
def parse_bindings_sort(raw)
|
|
2705
|
+
token = raw.to_s.strip.downcase
|
|
2706
|
+
case token
|
|
2707
|
+
when "", "key", "keys" then "key"
|
|
2708
|
+
when "command", "cmd" then "command"
|
|
2709
|
+
else
|
|
2710
|
+
raise RuVim::CommandError, "Unknown sort for :bindings: #{raw}"
|
|
2711
|
+
end
|
|
2712
|
+
end
|
|
2713
|
+
|
|
2714
|
+
def parse_bindings_mode_filter(raw)
|
|
2715
|
+
return nil if raw.nil? || raw.to_s.strip.empty?
|
|
2716
|
+
|
|
2717
|
+
token = raw.to_s.strip.downcase
|
|
2718
|
+
case token
|
|
2719
|
+
when "n", "normal" then :normal
|
|
2720
|
+
when "i", "insert" then :insert
|
|
2721
|
+
when "v", "visual", "visual_char" then :visual_char
|
|
2722
|
+
when "vl", "visual_line" then :visual_line
|
|
2723
|
+
when "vb", "visual_block", "x" then :visual_block
|
|
2724
|
+
when "o", "operator", "operator_pending" then :operator_pending
|
|
2725
|
+
when "c", "cmdline", "command", "command_line" then :command_line
|
|
2726
|
+
else
|
|
2727
|
+
raise RuVim::CommandError, "Unknown mode for :bindings: #{raw}"
|
|
2728
|
+
end
|
|
2729
|
+
end
|
|
2730
|
+
|
|
2731
|
+
def bindings_buffer_lines(editor, entries, mode_filter:, sort:)
|
|
2732
|
+
buffer = editor.current_buffer
|
|
2733
|
+
filetype = buffer.options["filetype"].to_s
|
|
2734
|
+
filetype = nil if filetype.empty?
|
|
2735
|
+
|
|
2736
|
+
lines = [
|
|
2737
|
+
"Bindings",
|
|
2738
|
+
"",
|
|
2739
|
+
"Buffer: #{buffer.display_name}",
|
|
2740
|
+
"Filetype: #{filetype || '-'}",
|
|
2741
|
+
"Mode filter: #{mode_filter || 'all'}",
|
|
2742
|
+
"Sort: #{sort}",
|
|
2743
|
+
""
|
|
2744
|
+
]
|
|
2745
|
+
|
|
2746
|
+
any = false
|
|
2747
|
+
%i[buffer filetype app].each do |layer|
|
|
2748
|
+
layer_entries = entries.select { |e| e.layer == layer }
|
|
2749
|
+
next if layer_entries.empty?
|
|
2750
|
+
|
|
2751
|
+
any = true
|
|
2752
|
+
lines << "Layer: #{layer}"
|
|
2753
|
+
append_binding_entries_grouped!(lines, layer_entries, layer:, sort:)
|
|
2754
|
+
lines << ""
|
|
2755
|
+
end
|
|
2756
|
+
|
|
2757
|
+
lines << "(no bindings)" unless any
|
|
2758
|
+
lines
|
|
2759
|
+
end
|
|
2760
|
+
|
|
2761
|
+
def append_binding_entries_grouped!(lines, entries, layer:, sort:)
|
|
2762
|
+
groups = entries.group_by do |e|
|
|
2763
|
+
if layer == :app && e.scope == :global
|
|
2764
|
+
[:global, nil]
|
|
2765
|
+
elsif e.mode
|
|
2766
|
+
[:mode, e.mode]
|
|
2767
|
+
else
|
|
2768
|
+
[:plain, nil]
|
|
2769
|
+
end
|
|
2770
|
+
end
|
|
2771
|
+
|
|
2772
|
+
groups.keys.sort_by { |kind, mode| binding_group_sort_key(kind, mode) }.each do |kind, mode|
|
|
2773
|
+
group_entries = groups[[kind, mode]]
|
|
2774
|
+
next if group_entries.nil? || group_entries.empty?
|
|
2775
|
+
|
|
2776
|
+
if kind == :global
|
|
2777
|
+
lines << " [global]"
|
|
2778
|
+
elsif mode
|
|
2779
|
+
lines << " [#{mode}]"
|
|
2780
|
+
end
|
|
2781
|
+
|
|
2782
|
+
group_entries = sort_binding_entries(group_entries, sort:)
|
|
2783
|
+
parts = group_entries.map { |entry| binding_entry_display_parts(entry) }
|
|
2784
|
+
rhs_width = parts.map { |_, rhs, _| rhs.length }.max || 0
|
|
2785
|
+
parts.each do |lhs, rhs, desc|
|
|
2786
|
+
lines << format_binding_entry_line(lhs, rhs, desc, rhs_width:)
|
|
2787
|
+
end
|
|
2788
|
+
end
|
|
2789
|
+
end
|
|
2790
|
+
|
|
2791
|
+
def binding_group_sort_key(kind, mode)
|
|
2792
|
+
rank =
|
|
2793
|
+
case kind
|
|
2794
|
+
when :plain then 0
|
|
2795
|
+
when :mode then 1
|
|
2796
|
+
when :global then 2
|
|
2797
|
+
else 9
|
|
2798
|
+
end
|
|
2799
|
+
[rank, binding_mode_order_index(mode), mode.to_s]
|
|
2800
|
+
end
|
|
2801
|
+
|
|
2802
|
+
def binding_mode_order_index(mode)
|
|
2803
|
+
return -1 if mode.nil?
|
|
2804
|
+
|
|
2805
|
+
order = {
|
|
2806
|
+
normal: 0,
|
|
2807
|
+
insert: 1,
|
|
2808
|
+
visual_char: 2,
|
|
2809
|
+
visual_line: 3,
|
|
2810
|
+
visual_block: 4,
|
|
2811
|
+
operator_pending: 5,
|
|
2812
|
+
command_line: 6
|
|
2813
|
+
}
|
|
2814
|
+
order.fetch(mode.to_sym, 99)
|
|
2815
|
+
end
|
|
2816
|
+
|
|
2817
|
+
def sort_binding_entries(entries, sort:)
|
|
2818
|
+
case sort.to_s
|
|
2819
|
+
when "command"
|
|
2820
|
+
entries.sort_by do |e|
|
|
2821
|
+
[e.id.to_s, format_binding_tokens(e.tokens), e.bang ? 1 : 0, e.argv.inspect, e.kwargs.inspect]
|
|
2822
|
+
end
|
|
2823
|
+
else
|
|
2824
|
+
entries
|
|
2825
|
+
end
|
|
2826
|
+
end
|
|
2827
|
+
|
|
2828
|
+
def binding_entry_display_parts(entry)
|
|
2829
|
+
lhs = format_binding_tokens(entry.tokens)
|
|
2830
|
+
rhs = entry.id.to_s
|
|
2831
|
+
rhs += "!" if entry.bang
|
|
2832
|
+
rhs += " argv=#{entry.argv.inspect}" unless entry.argv.nil? || entry.argv.empty?
|
|
2833
|
+
rhs += " kwargs=#{entry.kwargs.inspect}" unless entry.kwargs.nil? || entry.kwargs.empty?
|
|
2834
|
+
desc = binding_command_desc(entry.id)
|
|
2835
|
+
[lhs, rhs, desc.to_s]
|
|
2836
|
+
end
|
|
2837
|
+
|
|
2838
|
+
def format_binding_entry_line(lhs, rhs, desc, rhs_width:)
|
|
2839
|
+
line = " #{lhs.ljust(18)} #{rhs.ljust(rhs_width)}"
|
|
2840
|
+
line += " #{desc}" unless desc.to_s.empty?
|
|
2841
|
+
line
|
|
2842
|
+
end
|
|
2843
|
+
|
|
2844
|
+
def format_binding_tokens(tokens)
|
|
2845
|
+
Array(tokens).map { |t| format_binding_token(t) }.join
|
|
2846
|
+
end
|
|
2847
|
+
|
|
2848
|
+
def format_binding_token(token)
|
|
2849
|
+
case token.to_s
|
|
2850
|
+
when "\e" then "<Esc>"
|
|
2851
|
+
when "\t" then "<Tab>"
|
|
2852
|
+
when "\r" then "<CR>"
|
|
2853
|
+
else token.to_s
|
|
2854
|
+
end
|
|
2855
|
+
end
|
|
2856
|
+
|
|
2857
|
+
def binding_command_desc(command_id)
|
|
2858
|
+
RuVim::CommandRegistry.instance.fetch(command_id).desc.to_s
|
|
2859
|
+
rescue StandardError
|
|
2860
|
+
""
|
|
2861
|
+
end
|
|
2862
|
+
|
|
2863
|
+
def ex_command_binding_labels(editor, ex_spec)
|
|
2864
|
+
keymaps = editor.keymap_manager
|
|
2865
|
+
return [] unless keymaps
|
|
2866
|
+
|
|
2867
|
+
command_ids = command_ids_for_ex_callable(ex_spec.call)
|
|
2868
|
+
return [] if command_ids.empty?
|
|
2869
|
+
|
|
2870
|
+
entries = keymaps.binding_entries_for_context(editor).select do |entry|
|
|
2871
|
+
entry.layer == :app && command_ids.include?(entry.id.to_s)
|
|
2872
|
+
end
|
|
2873
|
+
entries.sort_by do |entry|
|
|
2874
|
+
[binding_mode_order_index(entry.mode), entry.scope == :global ? 1 : 0, format_binding_tokens(entry.tokens)]
|
|
2875
|
+
end.map do |entry|
|
|
2876
|
+
format_ex_command_binding_label(entry)
|
|
2877
|
+
end.uniq
|
|
2878
|
+
end
|
|
2879
|
+
|
|
2880
|
+
def command_ids_for_ex_callable(callable)
|
|
2881
|
+
RuVim::CommandRegistry.instance.all.filter_map do |spec|
|
|
2882
|
+
spec.id if same_command_callable?(spec.call, callable)
|
|
2883
|
+
end
|
|
2884
|
+
end
|
|
2885
|
+
|
|
2886
|
+
def same_command_callable?(a, b)
|
|
2887
|
+
if (a.is_a?(Symbol) || a.is_a?(String)) && (b.is_a?(Symbol) || b.is_a?(String))
|
|
2888
|
+
return a.to_sym == b.to_sym
|
|
2889
|
+
end
|
|
2890
|
+
a.equal?(b)
|
|
2891
|
+
end
|
|
2892
|
+
|
|
2893
|
+
def format_ex_command_binding_label(entry)
|
|
2894
|
+
lhs = format_binding_tokens(entry.tokens)
|
|
2895
|
+
if entry.scope == :global
|
|
2896
|
+
"global:#{lhs}"
|
|
2897
|
+
elsif entry.mode && entry.mode.to_sym != :normal
|
|
2898
|
+
"#{entry.mode}:#{lhs}"
|
|
2899
|
+
else
|
|
2900
|
+
lhs
|
|
2901
|
+
end
|
|
2902
|
+
end
|
|
2903
|
+
|
|
2214
2904
|
def paste_charwise(ctx, text, before:, count:)
|
|
2905
|
+
count = normalized_count(count)
|
|
2215
2906
|
y = ctx.window.cursor_y
|
|
2216
2907
|
x = ctx.window.cursor_x
|
|
2217
2908
|
insert_col = before ? x : [x + 1, ctx.buffer.line_length(y)].min
|
|
@@ -2242,7 +2933,7 @@ module RuVim
|
|
|
2242
2933
|
end
|
|
2243
2934
|
|
|
2244
2935
|
def move_to_search(ctx, pattern:, direction:, count:)
|
|
2245
|
-
count =
|
|
2936
|
+
count = normalized_count(count)
|
|
2246
2937
|
regex = compile_search_regex(pattern, editor: ctx.editor, window: ctx.window, buffer: ctx.buffer)
|
|
2247
2938
|
count.times do
|
|
2248
2939
|
match = find_next_match(ctx.buffer, ctx.window, regex, direction: direction)
|
|
@@ -2330,5 +3021,141 @@ module RuVim
|
|
|
2330
3021
|
rescue StandardError
|
|
2331
3022
|
0
|
|
2332
3023
|
end
|
|
3024
|
+
|
|
3025
|
+
def parse_substitute_flags(flags_str, default_global: false)
|
|
3026
|
+
flags = { global: default_global, ignore_case: false, match_case: false, count_only: false, no_error: false, confirm: false }
|
|
3027
|
+
return flags if flags_str.nil? || flags_str.empty?
|
|
3028
|
+
|
|
3029
|
+
flags_str.each_char do |ch|
|
|
3030
|
+
case ch
|
|
3031
|
+
when "g" then flags[:global] = true
|
|
3032
|
+
when "i" then flags[:ignore_case] = true
|
|
3033
|
+
when "I" then flags[:match_case] = true
|
|
3034
|
+
when "n" then flags[:count_only] = true
|
|
3035
|
+
when "e" then flags[:no_error] = true
|
|
3036
|
+
when "c" then flags[:confirm] = true
|
|
3037
|
+
end
|
|
3038
|
+
end
|
|
3039
|
+
flags
|
|
3040
|
+
end
|
|
3041
|
+
|
|
3042
|
+
def build_substitute_regex(pattern, flags, ctx)
|
|
3043
|
+
if flags[:match_case]
|
|
3044
|
+
# I flag: force case-sensitive
|
|
3045
|
+
Regexp.new(pattern.to_s)
|
|
3046
|
+
elsif flags[:ignore_case]
|
|
3047
|
+
# i flag: force case-insensitive
|
|
3048
|
+
Regexp.new(pattern.to_s, Regexp::IGNORECASE)
|
|
3049
|
+
else
|
|
3050
|
+
compile_search_regex(pattern, editor: ctx.editor, window: ctx.window, buffer: ctx.buffer)
|
|
3051
|
+
end
|
|
3052
|
+
rescue RegexpError => e
|
|
3053
|
+
raise RuVim::CommandError, "Invalid regex: #{e.message}"
|
|
3054
|
+
end
|
|
3055
|
+
|
|
3056
|
+
def substitute_range(ctx, regex, replacement, r_start, r_end, flags)
|
|
3057
|
+
changed = 0
|
|
3058
|
+
new_lines = ctx.buffer.lines.each_with_index.map do |line, idx|
|
|
3059
|
+
if idx >= r_start && idx <= r_end
|
|
3060
|
+
if flags[:global]
|
|
3061
|
+
line.scan(regex) { changed += 1 }
|
|
3062
|
+
line.gsub(regex, replacement)
|
|
3063
|
+
else
|
|
3064
|
+
if line.match?(regex)
|
|
3065
|
+
changed += 1
|
|
3066
|
+
line.sub(regex, replacement)
|
|
3067
|
+
else
|
|
3068
|
+
line
|
|
3069
|
+
end
|
|
3070
|
+
end
|
|
3071
|
+
else
|
|
3072
|
+
line
|
|
3073
|
+
end
|
|
3074
|
+
end
|
|
3075
|
+
|
|
3076
|
+
if changed.positive?
|
|
3077
|
+
ctx.buffer.begin_change_group
|
|
3078
|
+
ctx.buffer.replace_all_lines!(new_lines)
|
|
3079
|
+
ctx.buffer.end_change_group
|
|
3080
|
+
end
|
|
3081
|
+
changed
|
|
3082
|
+
end
|
|
3083
|
+
|
|
3084
|
+
def count_matches_in_range(buffer, regex, r_start, r_end, global)
|
|
3085
|
+
total = 0
|
|
3086
|
+
(r_start..r_end).each do |idx|
|
|
3087
|
+
line = buffer.line_at(idx)
|
|
3088
|
+
if global
|
|
3089
|
+
line.scan(regex) { total += 1 }
|
|
3090
|
+
else
|
|
3091
|
+
total += 1 if line.match?(regex)
|
|
3092
|
+
end
|
|
3093
|
+
end
|
|
3094
|
+
total
|
|
3095
|
+
end
|
|
3096
|
+
|
|
3097
|
+
public
|
|
3098
|
+
|
|
3099
|
+
def arglist_show(ctx, **)
|
|
3100
|
+
arglist = ctx.editor.arglist
|
|
3101
|
+
if arglist.empty?
|
|
3102
|
+
ctx.editor.echo("No arguments")
|
|
3103
|
+
return
|
|
3104
|
+
end
|
|
3105
|
+
|
|
3106
|
+
current_index = ctx.editor.arglist_index
|
|
3107
|
+
items = arglist.map.with_index do |path, i|
|
|
3108
|
+
if i == current_index
|
|
3109
|
+
"[#{path}]"
|
|
3110
|
+
else
|
|
3111
|
+
" #{path}"
|
|
3112
|
+
end
|
|
3113
|
+
end
|
|
3114
|
+
ctx.editor.echo_multiline(items)
|
|
3115
|
+
end
|
|
3116
|
+
|
|
3117
|
+
def arglist_next(ctx, count:, **)
|
|
3118
|
+
count = normalized_count(count)
|
|
3119
|
+
path = ctx.editor.arglist_next(count)
|
|
3120
|
+
switch_to_file(ctx, path)
|
|
3121
|
+
ctx.editor.echo("Argument #{ctx.editor.arglist_index + 1} of #{ctx.editor.arglist.length}: #{path}")
|
|
3122
|
+
end
|
|
3123
|
+
|
|
3124
|
+
def arglist_prev(ctx, count:, **)
|
|
3125
|
+
count = normalized_count(count)
|
|
3126
|
+
path = ctx.editor.arglist_prev(count)
|
|
3127
|
+
switch_to_file(ctx, path)
|
|
3128
|
+
ctx.editor.echo("Argument #{ctx.editor.arglist_index + 1} of #{ctx.editor.arglist.length}: #{path}")
|
|
3129
|
+
end
|
|
3130
|
+
|
|
3131
|
+
def arglist_first(ctx, **)
|
|
3132
|
+
path = ctx.editor.arglist_first
|
|
3133
|
+
return ctx.editor.error("No arguments") unless path
|
|
3134
|
+
switch_to_file(ctx, path)
|
|
3135
|
+
ctx.editor.echo("Argument 1 of #{ctx.editor.arglist.length}: #{path}")
|
|
3136
|
+
end
|
|
3137
|
+
|
|
3138
|
+
def arglist_last(ctx, **)
|
|
3139
|
+
path = ctx.editor.arglist_last
|
|
3140
|
+
return ctx.editor.error("No arguments") unless path
|
|
3141
|
+
switch_to_file(ctx, path)
|
|
3142
|
+
ctx.editor.echo("Argument #{ctx.editor.arglist.length} of #{ctx.editor.arglist.length}: #{path}")
|
|
3143
|
+
end
|
|
3144
|
+
|
|
3145
|
+
private
|
|
3146
|
+
|
|
3147
|
+
def switch_to_file(ctx, path)
|
|
3148
|
+
existing_buffer = ctx.editor.buffers.values.find { |buf| buf.path == path }
|
|
3149
|
+
if existing_buffer
|
|
3150
|
+
ctx.editor.set_alternate_buffer_id(ctx.editor.current_buffer.id)
|
|
3151
|
+
ctx.editor.activate_buffer(existing_buffer.id)
|
|
3152
|
+
existing_buffer.id
|
|
3153
|
+
else
|
|
3154
|
+
ctx.editor.set_alternate_buffer_id(ctx.editor.current_buffer.id)
|
|
3155
|
+
buffer = ctx.editor.add_buffer_from_file(path)
|
|
3156
|
+
ctx.editor.current_window.buffer_id = buffer.id
|
|
3157
|
+
buffer.id
|
|
3158
|
+
end
|
|
3159
|
+
end
|
|
2333
3160
|
end
|
|
2334
3161
|
end
|