ruvim 0.1.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.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/test.yml +15 -0
  3. data/README.md +135 -0
  4. data/Rakefile +36 -0
  5. data/docs/binding.md +125 -0
  6. data/docs/command.md +306 -0
  7. data/docs/config.md +155 -0
  8. data/docs/done.md +112 -0
  9. data/docs/plugin.md +559 -0
  10. data/docs/spec.md +655 -0
  11. data/docs/todo.md +63 -0
  12. data/docs/tutorial.md +490 -0
  13. data/docs/vim_diff.md +179 -0
  14. data/exe/ruvim +6 -0
  15. data/lib/ruvim/app.rb +1600 -0
  16. data/lib/ruvim/buffer.rb +421 -0
  17. data/lib/ruvim/cli.rb +264 -0
  18. data/lib/ruvim/clipboard.rb +73 -0
  19. data/lib/ruvim/command_invocation.rb +14 -0
  20. data/lib/ruvim/command_line.rb +63 -0
  21. data/lib/ruvim/command_registry.rb +38 -0
  22. data/lib/ruvim/config_dsl.rb +134 -0
  23. data/lib/ruvim/config_loader.rb +68 -0
  24. data/lib/ruvim/context.rb +26 -0
  25. data/lib/ruvim/dispatcher.rb +120 -0
  26. data/lib/ruvim/display_width.rb +110 -0
  27. data/lib/ruvim/editor.rb +1025 -0
  28. data/lib/ruvim/ex_command_registry.rb +80 -0
  29. data/lib/ruvim/global_commands.rb +1889 -0
  30. data/lib/ruvim/highlighter.rb +52 -0
  31. data/lib/ruvim/input.rb +66 -0
  32. data/lib/ruvim/keymap_manager.rb +96 -0
  33. data/lib/ruvim/screen.rb +452 -0
  34. data/lib/ruvim/terminal.rb +30 -0
  35. data/lib/ruvim/text_metrics.rb +96 -0
  36. data/lib/ruvim/version.rb +5 -0
  37. data/lib/ruvim/window.rb +71 -0
  38. data/lib/ruvim.rb +30 -0
  39. data/sig/ruvim.rbs +4 -0
  40. data/test/app_completion_test.rb +39 -0
  41. data/test/app_dot_repeat_test.rb +54 -0
  42. data/test/app_motion_test.rb +73 -0
  43. data/test/app_register_test.rb +47 -0
  44. data/test/app_scenario_test.rb +77 -0
  45. data/test/app_startup_test.rb +199 -0
  46. data/test/app_text_object_test.rb +54 -0
  47. data/test/app_unicode_behavior_test.rb +66 -0
  48. data/test/buffer_test.rb +72 -0
  49. data/test/cli_test.rb +165 -0
  50. data/test/config_dsl_test.rb +78 -0
  51. data/test/dispatcher_test.rb +124 -0
  52. data/test/editor_mark_test.rb +69 -0
  53. data/test/editor_register_test.rb +64 -0
  54. data/test/fixtures/render_basic_snapshot.txt +8 -0
  55. data/test/fixtures/render_basic_snapshot_nonumber.txt +8 -0
  56. data/test/fixtures/render_unicode_scrolled_snapshot.txt +7 -0
  57. data/test/highlighter_test.rb +16 -0
  58. data/test/input_screen_integration_test.rb +69 -0
  59. data/test/keymap_manager_test.rb +48 -0
  60. data/test/render_snapshot_test.rb +70 -0
  61. data/test/screen_test.rb +123 -0
  62. data/test/search_option_test.rb +39 -0
  63. data/test/test_helper.rb +15 -0
  64. data/test/text_metrics_test.rb +42 -0
  65. data/test/window_test.rb +21 -0
  66. metadata +106 -0
data/docs/plugin.md ADDED
@@ -0,0 +1,559 @@
1
+ # RuVim 拡張 / Plugin メモ(現状)
2
+
3
+ この文書は、現状の RuVim で「拡張っぽいもの」をどう書くかをまとめたものです。
4
+
5
+ 現時点では正式な plugin manager / plugin API はありません。
6
+ 実用上は、`init.rb` / `ftplugin/*.rb` に Ruby DSL で拡張を書きます。
7
+
8
+ 重要:
9
+ - ここに書いている DSL / `ctx` API は、現状まだ未確定です
10
+ - 将来の整理で互換性なく変更される可能性があります
11
+ - 当面は「実験的な拡張 API」として扱ってください
12
+
13
+ ## どこに書くか
14
+
15
+ - 全体設定 / 拡張:
16
+ - `$XDG_CONFIG_HOME/ruvim/init.rb`
17
+ - `~/.config/ruvim/init.rb`
18
+ - filetype ごとの拡張:
19
+ - `$XDG_CONFIG_HOME/ruvim/ftplugin/<filetype>.rb`
20
+ - `~/.config/ruvim/ftplugin/<filetype>.rb`
21
+
22
+ ## 何ができるか(DSL)
23
+
24
+ `ConfigDSL` で主に次を定義できます。
25
+
26
+ - `nmap(seq, command_id=nil, ..., &block)`
27
+ - `imap(seq, command_id=nil, ..., &block)`
28
+ - `map_global(seq, command_id=nil, mode: ..., ..., &block)`
29
+ - `command(id, &block)`(内部コマンド)
30
+ - `ex_command(name, &block)`(Ex コマンド)
31
+ - `ex_command_call(name, command_id, ...)`(Ex -> 内部コマンドの中継)
32
+ - `set`, `setlocal`, `setglobal`
33
+
34
+ ## DSL メソッドリファレンス(現状)
35
+
36
+ ### `nmap(seq, command_id=nil, desc: ..., **opts, &block)`
37
+
38
+ - 用途:
39
+ - Normal mode のキーバインドを定義
40
+ - 書き方:
41
+ - `command_id` 指定版
42
+ - block 版(推奨: 小さい拡張向け)
43
+ - 登録先:
44
+ - `init.rb` では `mode map (:normal)`
45
+ - `ftplugin/*.rb` では `filetype-local normal map`
46
+ - 主な引数:
47
+ - `seq`: キー列(例: `"H"`, `"gh"`)
48
+ - `command_id`: 内部コマンドID(例: `"user.hello"`)
49
+ - `desc`: block 版で生成される内部コマンドの説明
50
+ - `opts`: `argv:`, `kwargs:`, `bang:` を指定可能
51
+ - 例:
52
+
53
+ ```ruby
54
+ nmap "H", "user.hello"
55
+ nmap "gH", "user.echo", kwargs: { text: "hi" }
56
+
57
+ nmap "K", desc: "Show buffer name" do |ctx, **|
58
+ ctx.editor.echo(ctx.buffer.display_name)
59
+ end
60
+ ```
61
+
62
+ block 版の内部動作:
63
+ - 匿名の内部コマンドIDを自動生成して `CommandRegistry` に登録
64
+ - その command を keymap に bind
65
+
66
+ ### `imap(seq, command_id=nil, desc: ..., **opts, &block)`
67
+
68
+ - 用途:
69
+ - Insert mode のキーバインドを定義
70
+ - 登録先:
71
+ - `init.rb` では `mode map (:insert)`
72
+ - `ftplugin/*.rb` では `filetype-local insert map`
73
+ - 例:
74
+
75
+ ```ruby
76
+ imap "jk", "ui.clear_message"
77
+ ```
78
+
79
+ 注記:
80
+ - 現状の Insert mode は通常文字入力が先に処理される経路もあるため、複合キーの期待通り動作には制限が出る場合があります。
81
+
82
+ ### `map_global(seq, command_id=nil, mode: :normal, desc: ..., **opts, &block)`
83
+
84
+ - 用途:
85
+ - 汎用のキーバインド定義
86
+ - 登録先:
87
+ - `mode:` を指定した場合: その mode の map
88
+ - `mode: nil` の場合: `global map`(最下位フォールバック)
89
+ - 例:
90
+
91
+ ```ruby
92
+ map_global "Q", "app.quit", mode: :normal
93
+ map_global ["<C-w>", "x"], "window.focus_next", mode: nil
94
+
95
+ map_global "?", mode: :normal, desc: "Show file name" do |ctx, **|
96
+ ctx.editor.echo(ctx.buffer.display_name)
97
+ end
98
+ ```
99
+
100
+ ### `command(id, desc: ..., &block)`
101
+
102
+ - 用途:
103
+ - 内部コマンド(command ID)を登録
104
+ - 登録先:
105
+ - `RuVim::CommandRegistry`(source=`:user`)
106
+ - 使いどころ:
107
+ - keymap から呼ぶ処理
108
+ - `ex_command_call` の呼び先
109
+ - 例:
110
+
111
+ ```ruby
112
+ command "user.show_path", desc: "Show current path" do |ctx, **|
113
+ ctx.editor.echo(ctx.buffer.path || "[No Name]")
114
+ end
115
+ ```
116
+
117
+ ### `ex_command(name, desc: ..., aliases: [], nargs: :any, bang: false, &block)`
118
+
119
+ - 用途:
120
+ - Ex コマンド(`:Name`)を登録
121
+ - 登録先:
122
+ - `RuVim::ExCommandRegistry`(source=`:user`)
123
+ - 特徴:
124
+ - 同名が既に存在する場合は置き換える(DSL 内で unregister -> register)
125
+ - `aliases`, `nargs`, `bang` を指定できる
126
+ - `nargs`:
127
+ - `0`, `1`, `:maybe_one`, `:any`
128
+ - 例:
129
+
130
+ ```ruby
131
+ ex_command "BufName", desc: "Show current buffer name", nargs: 0 do |ctx, argv:, kwargs:, bang:, count:|
132
+ ctx.editor.echo(ctx.buffer.display_name)
133
+ end
134
+ ```
135
+
136
+ ### `ex_command_call(name, command_id, ...)`
137
+
138
+ - 用途:
139
+ - 既存の内部コマンドを Ex コマンドとして公開する
140
+ - 中身:
141
+ - `ex_command` を作り、`CommandRegistry` の `command_id` を呼ぶ薄い中継
142
+ - 例:
143
+
144
+ ```ruby
145
+ command "user.hello" do |ctx, **|
146
+ ctx.editor.echo("hello")
147
+ end
148
+
149
+ ex_command_call "Hello", "user.hello"
150
+ ```
151
+
152
+ ### `set(option_expr)`, `setlocal(option_expr)`, `setglobal(option_expr)`
153
+
154
+ - 用途:
155
+ - option を Ruby DSL から設定
156
+ - `option_expr` の形式(現状):
157
+ - `"number"`(ON)
158
+ - `"nonumber"`(OFF)
159
+ - `"tabstop=4"`(値設定)
160
+ - スコープ:
161
+ - `set`: option 定義に応じて自動(global / buffer / window)
162
+ - `setlocal`: local 側(buffer または window)
163
+ - `setglobal`: global 側
164
+ - 例:
165
+
166
+ ```ruby
167
+ set "number"
168
+ set "relativenumber"
169
+ setlocal "tabstop=2"
170
+ setglobal "tabstop=8"
171
+ ```
172
+
173
+ ## 最小例
174
+
175
+ ```ruby
176
+ # ~/.config/ruvim/init.rb
177
+
178
+ command "user.hello", desc: "Say hello" do |ctx, argv:, kwargs:, bang:, count:|
179
+ ctx.editor.echo("hello x#{count}")
180
+ end
181
+
182
+ nmap "H", "user.hello"
183
+ ex_command_call "Hello", "user.hello", desc: "Run hello"
184
+ ```
185
+
186
+ これで:
187
+
188
+ - Normal mode で `H` -> `user.hello`
189
+ - `:Hello` -> `user.hello`
190
+
191
+ ## `nmap` はどこに登録される?
192
+
193
+ `nmap` は「global map」ではなく、`Normal mode 用のマップ` に登録されます。
194
+
195
+ - `init.rb` での `nmap`
196
+ - セッション全体の Normal map(app-wide)
197
+ - `ftplugin/*.rb` での `nmap`
198
+ - `filetype-local` Normal map
199
+
200
+ ### キーマップ解決順(優先順)
201
+
202
+ 1. `filetype-local`
203
+ 2. `buffer-local`(内部 API はある。DSL は未整備)
204
+ 3. `mode map`(`nmap`, `imap` など)
205
+ 4. `global map`(`map_global(..., mode: nil)`)
206
+
207
+ 同じキーが複数定義されている場合、上にあるものが優先されます。
208
+
209
+ ## `command` と `ex_command` の違い
210
+
211
+ ### `command`
212
+
213
+ 内部コマンド(command ID)を定義します。
214
+ 主に keymap から呼ぶ対象です。
215
+
216
+ ```ruby
217
+ command "user.bufname" do |ctx, **|
218
+ ctx.editor.echo(ctx.buffer.display_name)
219
+ end
220
+ ```
221
+
222
+ ### `ex_command`
223
+
224
+ `:` で呼べる Ex コマンドを定義します。
225
+
226
+ ```ruby
227
+ ex_command "BufName", desc: "Show current buffer name", nargs: 0 do |ctx, argv:, kwargs:, bang:, count:|
228
+ ctx.editor.echo(ctx.buffer.display_name)
229
+ end
230
+ ```
231
+
232
+ ### `ex_command_call`(おすすめ)
233
+
234
+ 既存の内部コマンドを Ex から呼びたい時の薄い中継です。
235
+
236
+ ```ruby
237
+ ex_command_call "Hello", "user.hello", desc: "Run hello"
238
+ ```
239
+
240
+ ## filetype ごとの拡張例
241
+
242
+ ```ruby
243
+ # ~/.config/ruvim/ftplugin/ruby.rb
244
+
245
+ setlocal "tabstop=2"
246
+
247
+ command "ruby.say_ft" do |ctx, **|
248
+ ctx.editor.echo("ruby ftplugin")
249
+ end
250
+
251
+ nmap "K", "ruby.say_ft"
252
+ ```
253
+
254
+ この `nmap "K", ...` は Ruby バッファでのみ有効です。
255
+
256
+ ## 実行ブロックの引数
257
+
258
+ `command` / `ex_command` の block は、基本的に次の形で受けると扱いやすいです。
259
+
260
+ ```ruby
261
+ do |ctx, argv:, kwargs:, bang:, count:|
262
+ # ...
263
+ end
264
+ ```
265
+
266
+ - `ctx.editor`
267
+ - `ctx.buffer`
268
+ - `ctx.window`
269
+ - `argv`(Ex 引数)
270
+ - `kwargs`(キーマップや内部呼び出しの named args)
271
+ - `bang`(Ex の `!`)
272
+ - `count`(Normal mode の count)
273
+
274
+ ## `ctx` API リファレンス(現状)
275
+
276
+ block に渡される `ctx` は `RuVim::Context` です。
277
+
278
+ 注意:
279
+ - この `ctx` API リファレンスは「現時点で使えるもの」の記録です
280
+ - 安定 API の宣言ではありません(名前・挙動・公開範囲が変わる可能性があります)
281
+
282
+ ### `ctx.editor`
283
+
284
+ - 型: `RuVim::Editor`
285
+ - 用途:
286
+ - 全体状態へのアクセス
287
+ - message 表示(`echo`, `echo_error`)
288
+ - mode / jump / buffer / window 操作
289
+
290
+ 推奨:
291
+ - plugin からはまず `ctx.editor`, `ctx.buffer`, `ctx.window` を使う
292
+ - `Editor` の public メソッドは多いが、すべてが高水準 API とは限らない
293
+ - 下の「よく使う API」から使い始めるのが安全
294
+
295
+ #### `ctx.editor` よく使う API(推奨)
296
+
297
+ 状態参照:
298
+ - `current_buffer` -> `RuVim::Buffer`
299
+ - `current_window` -> `RuVim::Window`
300
+ - `mode` / `mode=`
301
+ - `running?`
302
+ - `message`, `message_error?`
303
+
304
+ 表示 / 通知:
305
+ - `echo(text)` : 通常メッセージ表示
306
+ - `echo_error(text)` : エラーメッセージ表示(下段・強調)
307
+ - `clear_message`
308
+
309
+ mode 操作:
310
+ - `enter_normal_mode`
311
+ - `enter_insert_mode`
312
+ - `enter_visual(mode)` (`:visual_char`, `:visual_line`, `:visual_block`)
313
+ - `enter_command_line_mode(prefix)` (`":"`, `"/"`, `"?"`)
314
+ - `cancel_command_line`
315
+
316
+ window / tab 操作:
317
+ - `split_current_window(layout: :horizontal|:vertical)`
318
+ - `close_current_window`
319
+ - `focus_next_window`, `focus_prev_window`
320
+ - `focus_window_direction(:left|:right|:up|:down)`
321
+ - `tabnew(path: nil)`, `tabnext(step=1)`, `tabprev(step=1)`
322
+
323
+ buffer 操作:
324
+ - `switch_to_buffer(buffer_id)`
325
+ - `open_path(path)`
326
+ - `buffers`(`{id => Buffer}`)
327
+ - `buffer_ids`
328
+ - `alternate_buffer_id`
329
+
330
+ option:
331
+ - `effective_option(name, window: ..., buffer: ...)`
332
+ - `set_option(name, value, scope: :auto|:global|:buffer|:window)`
333
+ - `get_option(name, window: ..., buffer: ...)`
334
+ - `global_options`
335
+
336
+ register:
337
+ - `get_register(name="\"")`
338
+ - `set_register(name="\"", text:, type: :charwise|:linewise)`
339
+ - `store_operator_register(name="\"", text:, type:, kind: :yank|:delete|:change)`
340
+ - `set_active_register(name)`, `active_register_name`, `consume_active_register`
341
+
342
+ jump / mark:
343
+ - `set_mark(name)`
344
+ - `mark_location(name, ...)`
345
+ - `push_jump_location(location=nil)`
346
+ - `jump_to_mark(name, linewise: false)`
347
+ - `jump_to_location(loc, linewise: false)`
348
+ - `jump_older(linewise: false)`, `jump_newer(linewise: false)`
349
+
350
+ search / find 状態:
351
+ - `last_search`, `set_last_search(pattern:, direction:)`
352
+ - `last_find`, `set_last_find(char:, direction:, till:)`
353
+
354
+ quickfix / location list:
355
+ - `set_quickfix_list(items)`, `quickfix_items`, `quickfix_index`, `current_quickfix_item`, `move_quickfix(step)`
356
+ - `set_location_list(items, window_id: ...)`, `location_items(window_id)`, `current_location_list_item(window_id)`, `move_location_list(step, window_id: ...)`
357
+
358
+ 終了:
359
+ - `request_quit!`
360
+
361
+ #### `ctx.editor` の高度 / 内部寄り API(使うときは注意)
362
+
363
+ - `add_empty_buffer`, `add_buffer_from_file`, `add_virtual_buffer`
364
+ - `add_window`, `close_window(id)`, `focus_window(id)`
365
+ - `show_help_buffer!`, `show_intro_buffer_if_applicable!`, `materialize_intro_buffer!`
366
+ - `text_viewport_size`, `window_order`, `windows`, `tabpages`
367
+ - `option_def`, `option_default_scope`, `option_snapshot`
368
+
369
+ これらは便利ですが、UI 内部の都合と結びついているものもあります。
370
+
371
+ よく使う例:
372
+
373
+ ```ruby
374
+ ctx.editor.echo("hello")
375
+ ctx.editor.echo_error("something wrong")
376
+ ```
377
+
378
+ ### `ctx.buffer`
379
+
380
+ - 型: `RuVim::Buffer`
381
+ - 意味:
382
+ - current window に表示中の current buffer
383
+ - よく使う例:
384
+
385
+ ```ruby
386
+ ctx.buffer.display_name
387
+ ctx.buffer.path
388
+ ctx.buffer.lines
389
+ ctx.buffer.modified?
390
+ ```
391
+
392
+ 注意:
393
+ - `ctx.buffer` を直接編集する場合は `readonly` / `modifiable` 制約に注意
394
+ - 低レベル API を呼ぶと undo 粒度の制御が必要になることがあります
395
+
396
+ #### `ctx.buffer` API リファレンス(用途別)
397
+
398
+ 状態参照:
399
+ - `id`
400
+ - `path`, `path=`
401
+ - `display_name`
402
+ - `kind`(`:file`, `:help`, `:intro`, `:quickfix`, `:location_list` など)
403
+ - `name`
404
+ - `options`(buffer-local option ストレージ)
405
+ - `modified?`, `modified=`
406
+ - `readonly?`, `readonly=`
407
+ - `modifiable?`, `modifiable=`
408
+ - `file_buffer?`, `virtual_buffer?`, `intro_buffer?`
409
+
410
+ 行アクセス:
411
+ - `lines`(配列そのもの)
412
+ - `line_count`
413
+ - `line_at(row)`
414
+ - `line_length(row)`
415
+
416
+ 編集(低レベル):
417
+ - `insert_char(row, col, char)`
418
+ - `insert_text(row, col, text)` -> `[row, col]`
419
+ - `insert_newline(row, col)` -> `[row, col]`
420
+ - `backspace(row, col)` -> `[row, col]`
421
+ - `delete_char(row, col)` -> `true/false`
422
+ - `delete_line(row)` -> `deleted_line`
423
+ - `delete_span(start_row, start_col, end_row, end_col)` -> `true/false`
424
+ - `insert_lines_at(index, new_lines)`
425
+ - `replace_all_lines!(new_lines)`
426
+
427
+ 範囲の読み取り:
428
+ - `span_text(start_row, start_col, end_row, end_col)` : charwise 範囲文字列
429
+ - `line_block_text(start_row, count=1)` : linewise 範囲文字列(末尾 `\n` 付き)
430
+
431
+ undo / redo:
432
+ - `begin_change_group`
433
+ - `end_change_group`
434
+ - `can_undo?`, `can_redo?`
435
+ - `undo!`, `redo!`
436
+
437
+ ファイル I/O:
438
+ - `write_to(path=nil)` : 保存
439
+ - `reload_from_file!(path=nil)` : 再読込(undo/redo クリア)
440
+
441
+ 特殊バッファ:
442
+ - `configure_special!(kind:, name: nil, readonly: true, modifiable: false)`
443
+ - `become_normal_empty_buffer!`
444
+
445
+ plugin で編集する時の基本パターン(推奨):
446
+
447
+ ```ruby
448
+ buf = ctx.buffer
449
+ win = ctx.window
450
+
451
+ buf.begin_change_group
452
+ begin
453
+ buf.insert_text(win.cursor_y, win.cursor_x, "hello")
454
+ win.cursor_x += 5
455
+ ensure
456
+ buf.end_change_group
457
+ end
458
+ ```
459
+
460
+ ### `ctx.window`
461
+
462
+ - 型: `RuVim::Window`
463
+ - 意味:
464
+ - current window
465
+ - よく使う例:
466
+
467
+ ```ruby
468
+ row = ctx.window.cursor_y
469
+ col = ctx.window.cursor_x
470
+ ```
471
+
472
+ #### `ctx.window` API リファレンス(用途別)
473
+
474
+ 状態(attr):
475
+ - `id`
476
+ - `buffer_id`, `buffer_id=`
477
+ - `cursor_x`, `cursor_x=`
478
+ - `cursor_y`, `cursor_y=`
479
+ - `row_offset`, `row_offset=`
480
+ - `col_offset`, `col_offset=`
481
+ - `options`(window-local option ストレージ)
482
+
483
+ 移動 / 位置補正:
484
+ - `clamp_to_buffer(buffer)` : カーソルをバッファ範囲に収める
485
+ - `move_left(buffer, count=1)` : grapheme 境界を考慮して左移動
486
+ - `move_right(buffer, count=1)` : grapheme 境界を考慮して右移動
487
+ - `move_up(buffer, count=1)`
488
+ - `move_down(buffer, count=1)`
489
+ - `ensure_visible(buffer, height:, width:, tabstop: 2)` : スクロール位置を調整
490
+
491
+ plugin では通常:
492
+ - `cursor_x`, `cursor_y` を更新
493
+ - 最後に `clamp_to_buffer(ctx.buffer)` を呼ぶ
494
+
495
+ 例:
496
+
497
+ ```ruby
498
+ ctx.window.cursor_y += 10
499
+ ctx.window.clamp_to_buffer(ctx.buffer)
500
+ ```
501
+
502
+ ### `ctx.invocation`
503
+
504
+ - 型: `RuVim::CommandInvocation` または `nil`
505
+ - 意味:
506
+ - 現在実行中のコマンド呼び出し情報
507
+ - 直接使わなくても、通常は `ctx.count` / `ctx.bang?` で足ります
508
+
509
+ ### `ctx.count`
510
+
511
+ - 型: `Integer`
512
+ - 意味:
513
+ - Normal mode の count(なければ `1`)
514
+
515
+ ### `ctx.bang?`
516
+
517
+ - 型: `true` / `false`
518
+ - 意味:
519
+ - Ex コマンドが `!` 付きで呼ばれたかどうか
520
+
521
+ ## block で使う引数(`argv`, `kwargs`, `bang`, `count`)
522
+
523
+ block は `ctx` 以外にも keyword 引数を受け取れます。
524
+
525
+ ```ruby
526
+ command "user.demo" do |ctx, argv:, kwargs:, bang:, count:|
527
+ ctx.editor.echo("argv=#{argv.inspect} kwargs=#{kwargs.inspect} bang=#{bang} count=#{count}")
528
+ end
529
+ ```
530
+
531
+ - `argv`
532
+ - Ex コマンド引数(配列)
533
+ - `kwargs`
534
+ - keymap / 内部呼び出しの named args(Hash)
535
+ - `bang`
536
+ - `ctx.bang?` と同じ意味
537
+ - `count`
538
+ - `ctx.count` と同じ意味
539
+
540
+ ## どこまで plugin と呼べる?
541
+
542
+ 現状は「設定ファイルに書く Ruby 拡張」です。
543
+
544
+ - plugin manager: なし
545
+ - plugin の load order 制御: 最小
546
+ - 公開 hook API: 最小
547
+
548
+ ただし、`init.rb` から `require` して自分のファイル群に分割すれば、実用的なローカル plugin 構成は作れます。
549
+
550
+ ```ruby
551
+ # ~/.config/ruvim/init.rb
552
+ require File.expand_path("plugins/my_tools", __dir__)
553
+ ```
554
+
555
+ ## 注意点
556
+
557
+ - 設定ファイルは Ruby として評価されます(任意コード実行)
558
+ - 信頼できるコードだけ読み込む前提です
559
+ - 将来 API が変わる可能性があります(現状は「育てる段階」)