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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +4 -0
  3. data/AGENTS.md +96 -0
  4. data/CLAUDE.md +1 -0
  5. data/README.md +15 -1
  6. data/docs/binding.md +39 -0
  7. data/docs/command.md +163 -4
  8. data/docs/config.md +12 -4
  9. data/docs/done.md +21 -0
  10. data/docs/spec.md +214 -18
  11. data/docs/todo.md +1 -5
  12. data/docs/tutorial.md +24 -0
  13. data/docs/vim_diff.md +105 -173
  14. data/lib/ruvim/app.rb +1165 -70
  15. data/lib/ruvim/buffer.rb +47 -1
  16. data/lib/ruvim/cli.rb +18 -3
  17. data/lib/ruvim/clipboard.rb +2 -0
  18. data/lib/ruvim/command_invocation.rb +3 -1
  19. data/lib/ruvim/command_line.rb +2 -0
  20. data/lib/ruvim/command_registry.rb +2 -0
  21. data/lib/ruvim/config_dsl.rb +2 -0
  22. data/lib/ruvim/config_loader.rb +2 -0
  23. data/lib/ruvim/context.rb +2 -0
  24. data/lib/ruvim/dispatcher.rb +143 -13
  25. data/lib/ruvim/display_width.rb +3 -0
  26. data/lib/ruvim/editor.rb +466 -71
  27. data/lib/ruvim/ex_command_registry.rb +2 -0
  28. data/lib/ruvim/file_watcher.rb +243 -0
  29. data/lib/ruvim/git/blame.rb +245 -0
  30. data/lib/ruvim/git/branch.rb +97 -0
  31. data/lib/ruvim/git/commit.rb +102 -0
  32. data/lib/ruvim/git/diff.rb +129 -0
  33. data/lib/ruvim/git/handler.rb +84 -0
  34. data/lib/ruvim/git/log.rb +41 -0
  35. data/lib/ruvim/git/status.rb +103 -0
  36. data/lib/ruvim/global_commands.rb +1066 -105
  37. data/lib/ruvim/highlighter.rb +19 -22
  38. data/lib/ruvim/input.rb +40 -28
  39. data/lib/ruvim/keymap_manager.rb +83 -0
  40. data/lib/ruvim/keyword_chars.rb +2 -0
  41. data/lib/ruvim/lang/base.rb +25 -0
  42. data/lib/ruvim/lang/csv.rb +18 -0
  43. data/lib/ruvim/lang/diff.rb +41 -0
  44. data/lib/ruvim/lang/json.rb +52 -0
  45. data/lib/ruvim/lang/markdown.rb +170 -0
  46. data/lib/ruvim/lang/ruby.rb +236 -0
  47. data/lib/ruvim/lang/scheme.rb +44 -0
  48. data/lib/ruvim/lang/tsv.rb +19 -0
  49. data/lib/ruvim/rich_view/json_renderer.rb +131 -0
  50. data/lib/ruvim/rich_view/jsonl_renderer.rb +57 -0
  51. data/lib/ruvim/rich_view/markdown_renderer.rb +248 -0
  52. data/lib/ruvim/rich_view/table_renderer.rb +176 -0
  53. data/lib/ruvim/rich_view.rb +109 -0
  54. data/lib/ruvim/screen.rb +503 -109
  55. data/lib/ruvim/terminal.rb +18 -1
  56. data/lib/ruvim/text_metrics.rb +2 -0
  57. data/lib/ruvim/version.rb +1 -1
  58. data/lib/ruvim/window.rb +2 -0
  59. data/lib/ruvim.rb +24 -0
  60. data/test/app_completion_test.rb +98 -0
  61. data/test/app_dot_repeat_test.rb +13 -0
  62. data/test/app_motion_test.rb +13 -0
  63. data/test/app_scenario_test.rb +898 -1
  64. data/test/app_startup_test.rb +187 -0
  65. data/test/arglist_test.rb +113 -0
  66. data/test/buffer_test.rb +49 -30
  67. data/test/cli_test.rb +14 -0
  68. data/test/clipboard_test.rb +67 -0
  69. data/test/command_line_test.rb +118 -0
  70. data/test/config_dsl_test.rb +87 -0
  71. data/test/dispatcher_test.rb +322 -0
  72. data/test/display_width_test.rb +41 -0
  73. data/test/editor_register_test.rb +23 -0
  74. data/test/file_watcher_test.rb +197 -0
  75. data/test/follow_test.rb +199 -0
  76. data/test/git_blame_test.rb +713 -0
  77. data/test/highlighter_test.rb +165 -0
  78. data/test/indent_test.rb +287 -0
  79. data/test/input_screen_integration_test.rb +40 -2
  80. data/test/markdown_renderer_test.rb +279 -0
  81. data/test/on_save_hook_test.rb +150 -0
  82. data/test/rich_view_test.rb +734 -0
  83. data/test/screen_test.rb +304 -0
  84. data/test/search_option_test.rb +19 -0
  85. data/test/test_helper.rb +9 -0
  86. metadata +49 -2
data/docs/spec.md CHANGED
@@ -89,10 +89,10 @@ RuVim は Vim と同様に、用途ごとに座標系を分けています。
89
89
  エディタ全体の実行状態です。
90
90
 
91
91
  - buffers / windows 管理
92
- - window order / split layout(`single` / `horizontal` / `vertical`)
93
- - tabpages 管理(タブごとに window set / current window / layout を保持)
92
+ - layout tree(ネストしたウィンドウ分割をツリーで管理)
93
+ - tabpages 管理(タブごとに layout tree / current window を保持)
94
94
  - current window の管理
95
- - mode 管理(`:normal`, `:insert`, `:command_line`)
95
+ - mode 管理(`:normal`, `:insert`, `:command_line`, `:rich`)
96
96
  - ステータスメッセージ
97
97
  - コマンドライン状態
98
98
 
@@ -166,7 +166,8 @@ RuVim::ExCommandRegistry.instance.register(
166
166
  2. `RuVim::App` が mode ごとに処理を分岐
167
167
  3. Normal mode のキーは `RuVim::KeymapManager` で解決
168
168
  4. `RuVim::Dispatcher` が内部コマンド or Ex コマンドを実行
169
- 5. `RuVim::Screen` が再描画
169
+ 5. Insert mode でキー処理後、stdin に未読データが残っていればレンダリングをスキップして追加のキーを読み取り・処理する(ペースト高速化)。このバッチ処理中は autoindent を抑制し、貼り付けテキストが余分にインデントされるのを防ぐ
170
+ 6. `RuVim::Screen` が再描画
170
171
 
171
172
  ## 起動オプション(CLI, 現状)
172
173
 
@@ -211,6 +212,17 @@ RuVim::ExCommandRegistry.instance.register(
211
212
  - `+{cmd}`, `+{line}`, `+`
212
213
  - 起動後の Ex 実行 / 行ジャンプ / 最終行ジャンプ
213
214
 
215
+ 補足(現状実装):
216
+
217
+ - `stdin` が non-TTY で、起動引数ファイルがない場合は `stdin` を follow stream として開く
218
+ - バッファ名は `[stdin]`
219
+ - status line に `[stdin/live]`, `[stdin/closed]`, `[stdin/error]` を表示
220
+ - Normal mode の `Ctrl-c` はデフォルトバインドで `stdin` stream stop(上流 PID へ直接 signal は送らない)
221
+ - `Ctrl-z` は全モード共通で suspend
222
+ - suspend 前に terminal を cooked + main screen に戻す
223
+ - `SIGTSTP` を自身に送って停止
224
+ - `fg` 復帰後に raw + alt screen を再有効化して再描画
225
+
214
226
  起動時コマンド(`-c`, `+...`)は、初期 buffer / file open / intro screen 構築の後に実行します。
215
227
  `--cmd` はそれより前で、user config 読み込み前に実行します。
216
228
 
@@ -253,6 +265,7 @@ RuVim::ExCommandRegistry.instance.register(
253
265
  - `p`, `P`: paste
254
266
  - `r<char>`: 1文字置換
255
267
  - `c` + motion / `cc`: change(削除して Insert mode)
268
+ - `=` + motion / `==`: auto-indent(Ruby / JSON filetype でインデント自動調整。`=j`, `=G` 等)
256
269
  - `v`: Visual (characterwise)
257
270
  - `V`: Visual (linewise)
258
271
  - `Ctrl-v`: Visual (blockwise, 最小)
@@ -263,6 +276,7 @@ RuVim::ExCommandRegistry.instance.register(
263
276
  - `N`: 直前検索を逆方向に繰り返し
264
277
  - `1..9` + 動作: count(例: `3j`, `2x`)
265
278
  - 矢印キー: 移動
279
+ - `Ctrl-z`: shell へ suspend(`fg` で復帰)
266
280
 
267
281
  ### Insert mode
268
282
 
@@ -273,6 +287,7 @@ RuVim::ExCommandRegistry.instance.register(
273
287
  - `Esc`: Normal mode に戻る
274
288
  - `Ctrl-c`: Normal mode に戻る(終了しない)
275
289
  - 矢印キー: 移動
290
+ - `Ctrl-z`: shell へ suspend(`fg` で復帰)
276
291
 
277
292
  ### Visual mode(現状)
278
293
 
@@ -284,8 +299,20 @@ RuVim::ExCommandRegistry.instance.register(
284
299
  - `d`: 選択範囲を delete
285
300
  - `i`/`a` + object: text object を選択(`iw`, `aw`, `ip`, `ap`, `i"`, `a"`, ``i` ``, ``a` ``, `i)`, `a)`, `i]`, `a]`, `i}`, `a}`)
286
301
  - `Esc` / `Ctrl-c`: Normal mode に戻る
302
+ - `Ctrl-z`: shell へ suspend(`fg` で復帰)
287
303
  - blockwise の text object 選択 / paste の Vim 互換挙動は未対応
288
304
 
305
+ ### Rich mode
306
+
307
+ - `:rich [format]` / `gr` で入る(トグル)
308
+ - 同一バッファ上で動作(仮想バッファを作らない)
309
+ - Normal mode とほぼ同じキーバインドが使える(移動・検索・yank 等)
310
+ - バッファを変更する操作(insert/delete/change/paste/replace)はブロック
311
+ - `Esc` / `Ctrl-C` で Normal mode に戻る
312
+ - ステータスラインに `-- RICH --` を表示
313
+ - 描画時に表示行をテーブル整形(`TableRenderer` を利用)
314
+ - wrap は強制 OFF
315
+
289
316
  ### Command-line mode
290
317
 
291
318
  - `:` で入る
@@ -297,16 +324,36 @@ RuVim::ExCommandRegistry.instance.register(
297
324
  - `Tab` (`Ctrl-i`) で Ex 補完
298
325
  - コマンド名
299
326
  - 一部引数(path / buffer / option)
327
+ - `Ctrl-z` で shell へ suspend(`fg` で復帰)
328
+
329
+ ### Hit-enter prompt(複数行メッセージ表示)
330
+
331
+ `:ls` や `:set`(引数なし)など、複数行にわたる出力を行うコマンドの結果を表示するモード。
332
+
333
+ - 画面下部にメッセージ行をオーバーレイ描画
334
+ - 最下行に「Press ENTER or type command to continue」を反転表示
335
+ - ステータスラインは非表示(Vim と同様)
336
+ - 対象コマンド: `:ls` / `:buffers`, `:args`, `:set`(引数なし), `:command`(引数なし)
337
+ - 1行以下の出力時は通常の `echo` を使用
338
+
339
+ #### キー操作
340
+
341
+ - `Enter` / `Space` / `Escape` / `Ctrl-C` / その他のキー → dismiss(通常モードに戻る)
342
+ - `:` → dismiss して Command-line mode に入る
343
+ - `/` / `?` → dismiss して検索 Command-line mode に入る
300
344
 
301
345
  ## Ex コマンド仕様(現状 builtin)
302
346
 
303
347
  - `:w [path]` / `:write [path]`
304
348
  - `:q[!]` / `:quit[!]`
349
+ - `:qa[!]` / `:qall[!]`
305
350
  - `:wq[!] [path]`
351
+ - `:wqa[!]` / `:wqall[!]` / `:xa[!]` / `:xall[!]`
306
352
  - `:e <path>` / `:edit <path>`
307
353
  - `:e[!] [path]` / `:edit[!] [path]`
308
354
  - `:help [topic]`
309
355
  - `:commands`
356
+ - `:bindings [mode]`
310
357
  - `:command[!] <Name> <ex-body>`
311
358
  - `:ruby <code>` / `:rb <code>`
312
359
  - `:!<command>`
@@ -323,6 +370,13 @@ RuVim::ExCommandRegistry.instance.register(
323
370
  - `:vimgrep`, `:lvimgrep`
324
371
  - `:copen`, `:cclose`, `:cnext` / `:cn`, `:cprev` / `:cp`
325
372
  - `:lopen`, `:lclose`, `:lnext` / `:ln`, `:lprev` / `:lp`
373
+ - `:grep /pattern/ [path...]`, `:lgrep /pattern/ [path...]`
374
+ - `:filter [/pattern/]` : 検索マッチ行のみのフィルタバッファを作成(`g/` キーバインド)
375
+ - `:rich [format]`
376
+ - `:d [count]` / `:delete`
377
+ - `:y [count]` / `:yank`
378
+ - `:tabs`
379
+ - `:args`, `:next`, `:prev`, `:first`, `:last`
326
380
 
327
381
  ## 検索仕様(現状)
328
382
 
@@ -332,7 +386,7 @@ RuVim::ExCommandRegistry.instance.register(
332
386
  - `N` : 直前検索を逆方向に繰り返し
333
387
  - `*`, `#` : カーソル下の単語を前/後方検索(単語境界つき)
334
388
  - `g*`, `g#` : カーソル下の単語を前/後方検索(部分一致)
335
- - `:%s/pat/repl/g` : バッファ全体 substitute(最小実装)
389
+ - `:{range}s/pat/repl/[flags]` : substitute(フラグ: `g`, `i`, `I`, `n`, `e` 対応。`c` は未実装)
336
390
 
337
391
  ### 仕様メモ
338
392
 
@@ -347,10 +401,20 @@ RuVim::ExCommandRegistry.instance.register(
347
401
  - `:q` は未保存変更があると拒否
348
402
  - `:q` は Vim 寄りに、複数 window 時は current window を閉じる(window が1つで tab が複数なら current tab を閉じる)
349
403
  - `:q!` は強制的に window / tab / app を閉じる
404
+ - `:qa` は全ウィンドウ/タブを無視して一括終了(未保存バッファがあると拒否、`:qa!` で強制)
405
+ - `:wqa` は全バッファを保存して一括終了
350
406
  - `:e` は未保存変更があると拒否(`!` で破棄可)
351
407
  - `:e!`(引数なし)は現在ファイルの再読込(undo/redo クリア)
352
408
  - `:buffer!`, `:bnext!`, `:bprev!` は未保存変更があっても切替
353
409
  - `:w!` は現状 `:w` と同等に受理(権限昇格などは未実装)
410
+ - `:bindings` は current buffer 文脈の有効 key binding を layer 別(`buffer`, `filetype`, `app`)に一覧表示
411
+ - 任意で mode filter を受ける(例 `:bindings normal`)
412
+ - 大きいファイルを開くときは、閾値以上で段階読み込みになる場合がある
413
+ - status line に `[load/live]`(失敗時 `[load/error]`)
414
+ - デフォルト実装は先頭 `8MB` を先に表示し、残りをバックグラウンド読み込み後に反映
415
+ - 環境変数:
416
+ - `RUVIM_ASYNC_FILE_THRESHOLD_BYTES`
417
+ - `RUVIM_ASYNC_FILE_PREFIX_BYTES`
354
418
 
355
419
  ### `:command`(現状仕様)
356
420
 
@@ -395,6 +459,16 @@ RuVim::ExCommandRegistry.instance.register(
395
459
  - バッファ切替時に直前バッファを更新
396
460
  - `:buffer #` で切替可能
397
461
 
462
+ ### arglist(引数リスト)
463
+
464
+ - `Editor#arglist` と `Editor#arglist_index` を保持
465
+ - 起動時に複数ファイル引数があるとarglistが初期化される
466
+ - 複数ファイル起動時、レイアウトオプション未指定でも全ファイルをバッファとして読み込む(`:ls` に表示される)
467
+ - `:args` : arglistを表示(現在の引数は `[filename]` で表示)
468
+ - `:next` / `:prev` : arglist内を移動
469
+ - `:first` / `:last` : arglistの最初/最後に移動
470
+ - arglist移動時にalternate bufferも更新される
471
+
398
472
  ## 画面描画仕様(現状)
399
473
 
400
474
  ANSI エスケープシーケンスによる再描画です。
@@ -409,19 +483,24 @@ ANSI エスケープシーケンスによる再描画です。
409
483
  - ファイル未指定起動時は Vim 風 intro screen を表示(RuVim では intro 用の read-only 特殊バッファ)
410
484
  - カーソル位置の文字を反転表示(見やすさ向上)
411
485
 
412
- ### split UI(現状)
486
+ ### split UI
413
487
 
414
488
  - `:split` / `:vsplit` で複数 window を作成
415
- - レイアウトは簡易タイル(等分割)
416
- - `horizontal`: 上下分割
417
- - `vertical`: 左右分割
418
- - nested split / Vim の厳密な window tree は未実装
489
+ - レイアウトはツリー構造(ネストした分割に対応)
490
+ - `hsplit`: 上下分割
491
+ - `vsplit`: 左右分割
492
+ - 例: vsplit 後に右ウィンドウを split すると、右カラムだけが上下分割される
493
+ - 同方向の分割は親ノードにマージ(hsplit の中で hsplit しても 1 レベルに統合)
494
+ - `close_window` でツリーを簡略化(子が 1 個になった分割ノードは子に置き換え)
495
+ - `focus_window_direction` は正規化座標空間で最近接ウィンドウを選択
419
496
  - 各 window は cursor / scroll を独立して保持
420
497
 
421
498
  ### Tabpage(現状)
422
499
 
423
500
  - `:tabnew [path]` で新しいタブを作成
424
501
  - `:tabnext`, `:tabprev` で移動
502
+ - `:tabs` で全タブ一覧を表示(各タブのウィンドウとバッファ名)
503
+ - ステータスラインに `tab:n/m` を表示(タブが2つ以上のとき)
425
504
  - 各タブは以下を独立に保持
426
505
  - window list(表示中 window 群)
427
506
  - current window
@@ -434,6 +513,12 @@ ANSI エスケープシーケンスによる再描画です。
434
513
  - 入力待機は `stdin + resize通知` を `IO.select` で待つ
435
514
  - 描画ごとに `winsize` を再取得して viewport を再計算
436
515
 
516
+ ### suspend / resume(現状)
517
+
518
+ - `Ctrl-z` 入力は app レベルで処理し、モードに関係なく suspend する
519
+ - suspend 時は terminal を cooked + main screen に戻してから `SIGTSTP` を送る
520
+ - `fg` 復帰時は alt screen を再有効化し、`Screen` キャッシュを破棄して全面再描画する
521
+
437
522
  ### Command-line 改善(現状)
438
523
 
439
524
  - prefix ごとの履歴保持(`:` `/` `?`)
@@ -518,12 +603,15 @@ Vim 完全互換ではなく、まずは扱いやすい粒度を優先した仕
518
603
  - `diw`, `daw` : text object word(簡易)
519
604
  - `di"`, `da"`, `di)`, `da)` : delimiter text object(簡易・同一行中心)
520
605
 
521
- 設計上は operator-pending 状態機械を導入しており、`d/y/c` を同じ流れで扱います。
606
+ 設計上は operator-pending 状態機械を導入しており、`d/y/c/=` を同じ流れで扱います。
522
607
 
523
608
  補足:
524
609
 
525
610
  - 現状 `y` operator も実装済み(`yy`, `yw`)
526
611
  - 現状 `c` operator も実装済み(`cw`, `cc`, `c$`, `ciw`, `caw` など)
612
+ - 現状 `=` operator も実装済み(`==`, `=j`, `=k`, `=G`, `=gg`、Visual `=`)
613
+ - Ruby filetype の場合にネスト構造に基づく自動インデントを適用
614
+ - 他の filetype はフォールバック(現在のインデント維持)
527
615
 
528
616
  ### text object(現状)
529
617
 
@@ -594,7 +682,7 @@ Vim 完全互換ではなく、まずは扱いやすい粒度を優先した仕
594
682
  - 代表例:
595
683
  - window-local: `number`, `relativenumber`, `wrap`, `linebreak`, `breakindent`, `cursorline`, `scrolloff`, `sidescrolloff`
596
684
  - global: `ignorecase`, `smartcase`, `hlsearch`, `incsearch`, `splitbelow`, `splitright`, `hidden`, `clipboard`, `timeoutlen`
597
- - buffer-local: `tabstop`, `expandtab`, `shiftwidth`, `softtabstop`, `autoindent`, `smartindent`, `filetype`
685
+ - buffer-local: `tabstop`, `expandtab`, `shiftwidth`, `softtabstop`, `autoindent`, `smartindent`, `filetype`, `onsavehook`
598
686
  - 詳細な一覧・実装状況は `docs/config.md` を参照
599
687
 
600
688
  ## Filetype / ftplugin(現状の基礎)
@@ -608,19 +696,85 @@ Vim 完全互換ではなく、まずは扱いやすい粒度を優先した仕
608
696
  - `setlocal` / `setglobal` / `set`(DSL)で option 変更可
609
697
  - 同一 buffer の同一 filetype ftplugin は一度だけ読み込む
610
698
 
699
+ ## Lang モジュール on_save フック
700
+
701
+ - Lang モジュールに `on_save(ctx, path)` ライフサイクルフックを定義
702
+ - `:w` でファイル保存後、`onsavehook` オプションが有効(デフォルト `true`)なら `buffer.lang_module.on_save(ctx, target)` を呼び出す
703
+ - `Lang::Base.on_save` はデフォルトで何もしない(no-op)
704
+ - `Lang::Ruby.on_save` は `ruby -wc` で構文チェックを実行し、エラー/警告を quickfix list に展開する
705
+ - エラー出力を `filename:line:` 形式でパースし quickfix items に変換
706
+ - 複数エラー時はヒント `(]q to see next, N total)` を表示
707
+ - 正常時は quickfix list を空にクリアする
708
+ - `:set noonsavehook` で無効化可能
709
+
611
710
  ## シンタックスハイライト(最小)
612
711
 
613
712
  - 描画時に filetype ごとの regex ベース highlighter を適用
614
713
  - 現状の対応 filetype:
615
714
  - `ruby`
616
- - `json`
715
+ - `json` / `jsonl`
716
+ - `markdown`(見出し・フェンス・HR・ブロック引用・インライン装飾)
617
717
  - 優先度(高 -> 低):
618
718
  - cursor / visual
619
719
  - search highlight
620
720
  - syntax highlight
621
- - 実装は `lib/ruvim/highlighter.rb`
721
+ - 実装は `lib/ruvim/highlighter.rb`(ディスパッチャ)+ `lib/ruvim/lang/markdown.rb`(言語固有ロジック)
622
722
  - Vim の syntax / treesitter 相当の互換性はない(最小実装)
623
723
 
724
+ ## Rich mode(構造化データ表示)
725
+
726
+ 構造化データ(TSV/CSV)や Markdown を見やすく整形して表示するモードです。
727
+ Visual mode と同様に Normal mode の上に乗るモードとして設計されています。
728
+
729
+ - **アーキテクチャ**: filetype ごとにレンダラーを登録できる汎用フレームワーク
730
+ - `RuVim::RichView` モジュール(`lib/ruvim/rich_view.rb`)
731
+ - レンダラー登録: `RichView.register(filetype, renderer)`
732
+ - レンダラー: `TableRenderer`(TSV/CSV)、`MarkdownRenderer`(Markdown)、`JsonRenderer`(JSON)、`JsonlRenderer`(JSONL)
733
+ - `JsonRenderer` は仮想バッファ方式: ミニファイ JSON を `JSON.pretty_generate` で整形し、読み取り専用バッファに表示
734
+ - `JsonlRenderer` は仮想バッファ方式: 各行を個別にパース・整形し、`---` セパレータで区切って読み取り専用バッファに表示。パースエラー行はエラーマーカー付きで表示
735
+ - 仮想バッファ方式のレンダラーでは `Esc` / `C-c` でバッファを閉じて元に戻れる
736
+ - **起動方法**:
737
+ - `:rich [format]` Ex コマンド(トグル)
738
+ - `gr` Normal mode キーバインド(トグル)
739
+ - **モード仕様**:
740
+ - 同一バッファ上で動作(仮想バッファを作成しない)
741
+ - `Editor#rich_state` に format/delimiter を保持
742
+ - Normal mode とほぼ同じキーバインドが使える(移動・検索・yank 等)
743
+ - バッファを変更する操作(insert/delete/change/paste/replace)はブロック
744
+ - `Esc` / `Ctrl-C` で Normal mode に戻る
745
+ - ステータスラインに `-- RICH --` を表示
746
+ - wrap は強制 OFF
747
+ - **レンダリング**:
748
+ - `Screen` の `plain_window_render_rows` で `editor.rich_mode?` を判定
749
+ - Rich mode の場合、表示行だけを `RichView.render_visible_lines` で整形
750
+ - バッファ内容は変更せず、描画パイプラインでの変換のみ
751
+ - `render_rich_view_line_sc` は ANSI エスケープシーケンスを幅ゼロとして扱い、横スクロール時もスタイルを正しく維持
752
+ - **横スクロール**:
753
+ - 表示カラム(display column)ベースでスクロール量を管理(`@rich_col_offset_sc`)
754
+ - `renderer.cursor_display_col` で raw バッファの `cursor_x` を整形後の表示カラムに変換し、スクロールオフセットを決定
755
+ - `render_rich_view_line_sc` で各行を同じ表示カラム数だけスキップして描画(CJK/ASCII 混在でも列揃えが保たれる)
756
+ - ワイド文字がビューポート左端をまたぐ場合はスペースで置換(部分表示は不可)
757
+ - カーソルの画面位置も整形後の表示カラム座標で計算
758
+ - **テーブルレンダラー仕様**:
759
+ - 区切り表示: ` | `(スペース+パイプ+スペース)
760
+ - 列幅は画面に見えている行だけから計算(大規模ファイルでも高速)
761
+ - CJK 文字の表示幅を `DisplayWidth.display_width` で正確に計算
762
+ - CSV は最小限の quoted field パース対応
763
+ - 列が1つしかない場合は元の行をそのまま返す
764
+ - **Markdown レンダラー仕様**:
765
+ - インラインマーカー(`#`, `**`, `*`, `` ` `` 等)は残し、ANSI スタイルを重ねる
766
+ - 見出し H1-H6: レベル別 bold + 色
767
+ - インライン装飾: `**bold**`, `*italic*`, `` `code` ``, `[text](url)`, チェックボックス
768
+ - コードブロック: `` ``` ``/`~~~` フェンスで状態追跡、内容を暖色表示
769
+ - コードフェンス状態は `pre_context_lines` で表示領域前の行から引き継ぎ
770
+ - テーブル: `|...|` パターンを検出し、列幅揃え + box-drawing 罫線(`│`, `─`, `┼`, `├`, `┤`)
771
+ - HR: `---`/`***`/`___` を `─` 線に置換
772
+ - ブロック引用: `>` で始まる行を cyan 表示
773
+ - テーブル行のカーソルマッピングはパディング後の位置を計算
774
+ - **filetype 検出**:
775
+ - `.tsv` → `tsv`, `.csv` → `csv`, `.md` → `markdown`
776
+ - `:rich`(引数なし)は filetype から判定、不明なら内容を見て自動推測
777
+
624
778
  ## シングルトン方針
625
779
 
626
780
  グローバルに共有して良い「定義系」だけシングルトンにしています。
@@ -641,6 +795,9 @@ Vim 完全互換ではなく、まずは扱いやすい粒度を優先した仕
641
795
  - `nmap`
642
796
  - `imap`
643
797
  - `map_global`
798
+ - `set`
799
+ - `setlocal`
800
+ - `setglobal`
644
801
  - `command`
645
802
  - `ex_command`
646
803
  - `ex_command_call`
@@ -650,6 +807,47 @@ Vim 完全互換ではなく、まずは扱いやすい粒度を優先した仕
650
807
  - XDG 設定ファイル(`init.rb`)は Ruby として評価されるため、信頼できる内容のみ使用する
651
808
  - 直接内部状態に触る代わりに、まずは DSL API を通す設計
652
809
 
810
+ ## Git 連携
811
+
812
+ ### Git Blame
813
+
814
+ `<C-g>` で `:git ` プリセットのコマンドラインモードに入る。`:git <subcommand>` で実行。
815
+
816
+ ### GitStatus
817
+
818
+ `:git status` で `git status` の結果を `kind: :git_status` の読み取り専用バッファで表示。
819
+
820
+ ### GitDiff
821
+
822
+ `:git diff` で `git diff` の結果を `kind: :git_diff` の読み取り専用バッファで表示(filetype: diff)。追加引数をそのまま渡せる(例: `:git diff --cached`)。差分がない場合はメッセージ表示のみ。Enter で差分行に対応するファイルの該当行にジャンプ。`:git log -p` バッファでも同様に動作する。
823
+
824
+ ### GitLog
825
+
826
+ `:git log` で `git log` の結果を `kind: :git_log` の読み取り専用バッファで表示。追加引数をそのまま渡せる(例: `:git log -p`)。`-p` 指定時は filetype: diff で syntax highlight が効く。出力はストリーミングで逐次表示(カーソルは先頭行に固定)。バッファを閉じるとプロセスも停止する。
827
+
828
+ ### GitBranch
829
+
830
+ `:git branch` で `git branch -a` の結果を `kind: :git_branch` の読み取り専用バッファで表示。コミット日時の新しい順にソート。各行にブランチ名、日付、最新コミットのサブジェクトを表示。Enter でカーソル行のブランチをチェックアウト。
831
+
832
+ ### GitCommit
833
+
834
+ `:git commit` でコミットメッセージ編集バッファ(`kind: :git_commit`)を開く。`#` で始まる行はコメント(git status 情報を表示)。insert モードで開始。`:w` または `:wq` でコミット実行。`:q!` でキャンセル。メッセージが空の場合はコミットを中止。
835
+
836
+ ### GitBlame
837
+
838
+ - **Blame バッファ**: `kind: :blame`、readonly、modifiable=false
839
+ - 表示形式: `短縮ハッシュ 著者名 日付 コード行`
840
+ - 内部で `git blame --porcelain` を使用して構造化パース
841
+ - ソースファイルのカーソル行位置を引き継ぐ
842
+
843
+ Blame バッファ内のバッファローカルバインディング:
844
+
845
+ - `p` (GitBlamePrev): カーソル行のコミット C に対し `git blame C^` の結果に更新。履歴スタックに現在の状態を push
846
+ - `P` (GitBlameBack): 履歴スタックから pop して前の blame 状態に復元
847
+ - `c` (GitBlameCommit): カーソル行のコミットの `git show` 結果を `kind: :git_show` の読み取り専用バッファで表示(filetype: diff)
848
+
849
+ 実装: `lib/ruvim/git/blame.rb`(blame パース・実行)、`lib/ruvim/git/commands.rb`(status/diff/log 実行)、`lib/ruvim/global_commands.rb`(コマンドハンドラ)
850
+
653
851
  ## テスト(現状)
654
852
 
655
853
  - `Minitest` を利用
@@ -662,10 +860,8 @@ Vim 完全互換ではなく、まずは扱いやすい粒度を優先した仕
662
860
 
663
861
  - 永続 undo(`undofile` / `undodir` 相当)
664
862
  - session 保存/復元(`-S` / `:mksession` 相当の実体)
665
- - `:grep` / `:make` / `:cfile` / `:lgrep` / `:lfile` など quickfix 入口
666
- - Ex range/address(`:1,10d`, `:.,$s/.../.../` など)
667
- - `:substitute` フラグ拡張(`c`, `i`, `I`, `n`, `e` など)
668
- - arglist(複数ファイル起動 + `:args`, `:next`, `:prev` 等)
863
+ - `:make` / `:cfile` / `:lfile` など quickfix 入口(`:grep` / `:lgrep` は実装済み)
864
+ - `:substitute` の `c`(confirm)フラグ(`g`, `i`, `I`, `n`, `e` は実装済み)
669
865
  - `Ctrl-w` resize / close-others / equalize など window 操作拡張
670
866
  - `:set` 高度構文(`+=`, `-=`, `:set all`, 短縮名)
671
867
  - tag jump / folds / `:global` / `:normal`
data/docs/todo.md CHANGED
@@ -85,7 +85,7 @@
85
85
  - session file(`-S [session]` placeholder 実体化)
86
86
  - `wrap` / `linebreak` / `breakindent` / `showbreak` の整合性
87
87
  - `wildmenu` / `completeopt` / `pumheight` の UI コンポーネント寄せ
88
- - arglist(複数ファイル通常起動 + `:args/:next/:prev`)
88
+ - arglist 残件(`:args/:next/:prev` の挙動精度向上)
89
89
 
90
90
  ### この順番の理由(依存)
91
91
 
@@ -120,10 +120,6 @@
120
120
 
121
121
  ### P1: Vim 運用でよく触る基盤(あると詰まりにくい)
122
122
 
123
- - arglist(複数ファイル通常起動 + 操作)
124
- - 複数ファイル引数(通常起動)
125
- - `:args`, `:next`, `:prev`, `:first`, `:last`
126
- - `alternate buffer (#)` との整合
127
123
  - `Ctrl-w` window 操作の拡張
128
124
  - `Ctrl-w c`(window close)
129
125
  - `Ctrl-w o`(only / 他 window を閉じる)
data/docs/tutorial.md CHANGED
@@ -28,6 +28,7 @@ ruvim path/to/file.txt
28
28
  - `-R`(readonly で開く。現在バッファの `:w` を拒否)
29
29
  - `-M`(modifiable off 相当。編集操作を拒否し、あわせて readonly)
30
30
  - `-Z`(restricted mode。config/ftplugin を読まず、`:ruby` と `:!` を無効化)
31
+ - `-f`(follow mode: `tail -f` 相当。ファイルの追記をリアルタイムに追従)
31
32
  - `-n`(現状 no-op。将来の swap/永続機能向け互換フラグ)
32
33
  - `-o[N]` / `-O[N]` / `-p[N]`(複数ファイルを split / vsplit / tab で開く)
33
34
  - `-V[N]` / `--verbose[=N]`(起動/設定/Ex のログを stderr に出す)
@@ -140,11 +141,33 @@ ruvim -p a.rb b.rb
140
141
  - `:tabnew [path]`
141
142
  - `:tabnext` / `:tabn`
142
143
  - `:tabprev` / `:tabp`
144
+ - `:follow`(`tail -f` 相当の追従モードをトグル)
143
145
  - `:vimgrep /foo/`
144
146
  - `:copen`, `:cnext`, `:cprev`, `:cclose`
145
147
  - `:lvimgrep /foo/`
146
148
  - `:lopen`, `:lnext`, `:lprev`, `:lclose`
147
149
 
150
+ ## Follow mode(`tail -f` 相当)
151
+
152
+ ファイルへの追記をリアルタイムにバッファへ反映する追従モードです。
153
+
154
+ ```bash
155
+ ruvim -f /var/log/syslog # 起動時から follow mode
156
+ ruvim -f log1.txt log2.txt # 複数ファイルすべて follow
157
+ ```
158
+
159
+ 起動後に切り替えることもできます:
160
+
161
+ ```vim
162
+ :follow " follow 開始(トグル)
163
+ :follow " もう一度で停止
164
+ ```
165
+
166
+ - `Ctrl-C`(ノーマルモード)でも停止できます
167
+ - カーソルが最終行(`G`)にいると末尾を自動追従、途中にいればスクロール位置を維持
168
+ - follow 中はバッファ変更不可(停止後に編集可能)
169
+ - ファイルが truncate/削除された場合はメッセージを表示し、監視を継続(削除時は再作成を待機)
170
+
148
171
  ## Undo / Redo
149
172
 
150
173
  - `u`: 直前の変更を取り消す
@@ -238,6 +261,7 @@ register prefix を付けると register を指定できます。
238
261
  - `:set ignorecase` / `:set noignorecase` : 検索の大文字小文字を無視(global)
239
262
  - `:set smartcase` / `:set nosmartcase` : `ignorecase` 有効時に大文字を含む検索を大文字小文字区別にする(global)
240
263
  - `:set hlsearch` / `:set nohlsearch` : 検索ハイライトの ON/OFF(global)
264
+ - `:nohlsearch` (`:noh`) : 検索ハイライトを一時的にクリア(次の検索で自動復帰)
241
265
  - `:set tabstop=4` : tab 幅設定(既定スコープは buffer-local)
242
266
  - `:setlocal number` : 現在 window のみ変更
243
267
  - `:setglobal tabstop=8` : global 値を変更