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.
- checksums.yaml +7 -0
- data/.github/workflows/test.yml +15 -0
- data/README.md +135 -0
- data/Rakefile +36 -0
- data/docs/binding.md +125 -0
- data/docs/command.md +306 -0
- data/docs/config.md +155 -0
- data/docs/done.md +112 -0
- data/docs/plugin.md +559 -0
- data/docs/spec.md +655 -0
- data/docs/todo.md +63 -0
- data/docs/tutorial.md +490 -0
- data/docs/vim_diff.md +179 -0
- data/exe/ruvim +6 -0
- data/lib/ruvim/app.rb +1600 -0
- data/lib/ruvim/buffer.rb +421 -0
- data/lib/ruvim/cli.rb +264 -0
- data/lib/ruvim/clipboard.rb +73 -0
- data/lib/ruvim/command_invocation.rb +14 -0
- data/lib/ruvim/command_line.rb +63 -0
- data/lib/ruvim/command_registry.rb +38 -0
- data/lib/ruvim/config_dsl.rb +134 -0
- data/lib/ruvim/config_loader.rb +68 -0
- data/lib/ruvim/context.rb +26 -0
- data/lib/ruvim/dispatcher.rb +120 -0
- data/lib/ruvim/display_width.rb +110 -0
- data/lib/ruvim/editor.rb +1025 -0
- data/lib/ruvim/ex_command_registry.rb +80 -0
- data/lib/ruvim/global_commands.rb +1889 -0
- data/lib/ruvim/highlighter.rb +52 -0
- data/lib/ruvim/input.rb +66 -0
- data/lib/ruvim/keymap_manager.rb +96 -0
- data/lib/ruvim/screen.rb +452 -0
- data/lib/ruvim/terminal.rb +30 -0
- data/lib/ruvim/text_metrics.rb +96 -0
- data/lib/ruvim/version.rb +5 -0
- data/lib/ruvim/window.rb +71 -0
- data/lib/ruvim.rb +30 -0
- data/sig/ruvim.rbs +4 -0
- data/test/app_completion_test.rb +39 -0
- data/test/app_dot_repeat_test.rb +54 -0
- data/test/app_motion_test.rb +73 -0
- data/test/app_register_test.rb +47 -0
- data/test/app_scenario_test.rb +77 -0
- data/test/app_startup_test.rb +199 -0
- data/test/app_text_object_test.rb +54 -0
- data/test/app_unicode_behavior_test.rb +66 -0
- data/test/buffer_test.rb +72 -0
- data/test/cli_test.rb +165 -0
- data/test/config_dsl_test.rb +78 -0
- data/test/dispatcher_test.rb +124 -0
- data/test/editor_mark_test.rb +69 -0
- data/test/editor_register_test.rb +64 -0
- data/test/fixtures/render_basic_snapshot.txt +8 -0
- data/test/fixtures/render_basic_snapshot_nonumber.txt +8 -0
- data/test/fixtures/render_unicode_scrolled_snapshot.txt +7 -0
- data/test/highlighter_test.rb +16 -0
- data/test/input_screen_integration_test.rb +69 -0
- data/test/keymap_manager_test.rb +48 -0
- data/test/render_snapshot_test.rb +70 -0
- data/test/screen_test.rb +123 -0
- data/test/search_option_test.rb +39 -0
- data/test/test_helper.rb +15 -0
- data/test/text_metrics_test.rb +42 -0
- data/test/window_test.rb +21 -0
- metadata +106 -0
data/docs/spec.md
ADDED
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
# RuVim 仕様(現状実装 + 設計方針)
|
|
2
|
+
|
|
3
|
+
## 目的
|
|
4
|
+
|
|
5
|
+
RuVim は Ruby で実装する Vim ライクなターミナルエディタです。
|
|
6
|
+
|
|
7
|
+
- 生ターミナル入力(raw mode)
|
|
8
|
+
- モード(Normal / Insert / Command-line)
|
|
9
|
+
- `:` Ex 風コマンド
|
|
10
|
+
- キーバインドからコマンド実行
|
|
11
|
+
- コマンド定義と UI 入力経路の分離
|
|
12
|
+
|
|
13
|
+
この文書は「現状の実装」と「今後の拡張前提の設計」をまとめた仕様です。
|
|
14
|
+
|
|
15
|
+
## 基本概念
|
|
16
|
+
|
|
17
|
+
### 1. Buffer
|
|
18
|
+
|
|
19
|
+
テキスト本体を保持する単位です。
|
|
20
|
+
|
|
21
|
+
- 行配列 `lines`
|
|
22
|
+
- ファイルパス `path`
|
|
23
|
+
- 変更フラグ `modified?`
|
|
24
|
+
|
|
25
|
+
`Buffer` は表示状態(カーソル位置・スクロール)を持ちません。
|
|
26
|
+
|
|
27
|
+
#### Buffer 構造メモ(性能検討)
|
|
28
|
+
|
|
29
|
+
現状の `RuVim::Buffer` は「行配列 + Ruby String 直接編集」です。
|
|
30
|
+
|
|
31
|
+
- 利点:
|
|
32
|
+
- 実装が単純でデバッグしやすい
|
|
33
|
+
- undo snapshot 実装と相性がよい
|
|
34
|
+
- 欠点:
|
|
35
|
+
- 大きいテキストで中央付近の挿入/削除が増えるとコピーコストが目立つ
|
|
36
|
+
- 永続 undo / 差分保存を入れると snapshot 方式のメモリ負荷が上がる
|
|
37
|
+
|
|
38
|
+
将来の候補(検討済み):
|
|
39
|
+
|
|
40
|
+
- `piece table`
|
|
41
|
+
- undo/redo と相性がよく、エディタ実装で定番
|
|
42
|
+
- 行インデックスを別途持つ必要がある
|
|
43
|
+
- `rope`
|
|
44
|
+
- 大規模テキストの編集に強い
|
|
45
|
+
- 実装複雑度が上がる(Ruby での実装コスト高め)
|
|
46
|
+
|
|
47
|
+
当面の方針:
|
|
48
|
+
|
|
49
|
+
- まずは現在構造のまま描画/ハイライト/検索側を最適化
|
|
50
|
+
- `永続 undo` を実装する段階で `piece table` 移行を再評価する
|
|
51
|
+
|
|
52
|
+
### 2. Window
|
|
53
|
+
|
|
54
|
+
`Buffer` をどの位置で表示するかを持つビューです。
|
|
55
|
+
|
|
56
|
+
- `buffer_id`
|
|
57
|
+
- `cursor_x`, `cursor_y`
|
|
58
|
+
- `row_offset`, `col_offset`
|
|
59
|
+
|
|
60
|
+
同一 buffer を複数 window で表示できる前提の設計です(現状 UI は単一 window)。
|
|
61
|
+
同一 buffer を複数 window で表示できる前提の設計です(現状 UI は simple split 対応)。
|
|
62
|
+
|
|
63
|
+
#### 座標系(現状の単位)
|
|
64
|
+
|
|
65
|
+
RuVim は Vim と同様に、用途ごとに座標系を分けています。
|
|
66
|
+
|
|
67
|
+
- `buffer row` (`cursor_y`, `row_offset`)
|
|
68
|
+
- 行番号ベース(0-origin)
|
|
69
|
+
- `char index` (`cursor_x`, `col_offset`)
|
|
70
|
+
- Ruby の UTF-8 `String` に対する文字 index(byte offset ではない)
|
|
71
|
+
- `grapheme boundary`
|
|
72
|
+
- 左右移動時は `RuVim::TextMetrics` で grapheme cluster 境界に揃える
|
|
73
|
+
- `screen column`
|
|
74
|
+
- 描画・横スクロールでは表示幅(全角/結合文字/tab 幅込み)を使う
|
|
75
|
+
|
|
76
|
+
役割分担(現状):
|
|
77
|
+
|
|
78
|
+
- `RuVim::Window`
|
|
79
|
+
- カーソル位置保持(`char index`)
|
|
80
|
+
- `ensure_visible` で `screen column` ベースの横スクロール
|
|
81
|
+
- `RuVim::TextMetrics`
|
|
82
|
+
- `char index <-> screen column` 変換
|
|
83
|
+
- grapheme 境界移動
|
|
84
|
+
- `RuVim::Screen`
|
|
85
|
+
- 表示幅ベースの clipping / padding / cursor 描画位置計算
|
|
86
|
+
|
|
87
|
+
### 3. Editor
|
|
88
|
+
|
|
89
|
+
エディタ全体の実行状態です。
|
|
90
|
+
|
|
91
|
+
- buffers / windows 管理
|
|
92
|
+
- window order / split layout(`single` / `horizontal` / `vertical`)
|
|
93
|
+
- tabpages 管理(タブごとに window set / current window / layout を保持)
|
|
94
|
+
- current window の管理
|
|
95
|
+
- mode 管理(`:normal`, `:insert`, `:command_line`)
|
|
96
|
+
- ステータスメッセージ
|
|
97
|
+
- コマンドライン状態
|
|
98
|
+
|
|
99
|
+
## コマンドモデル
|
|
100
|
+
|
|
101
|
+
### 方針
|
|
102
|
+
|
|
103
|
+
- コマンドは「呼び出し可能な処理」として定義
|
|
104
|
+
- キーバインド入力と Ex 入力は最終的に同じ実行系へ流す
|
|
105
|
+
- builtin は Symbol ベース、拡張は Proc も使える
|
|
106
|
+
|
|
107
|
+
### 内部コマンド (`CommandRegistry`)
|
|
108
|
+
|
|
109
|
+
内部コマンドは ID で管理します(例: `cursor.left`, `buffer.delete_line`)。
|
|
110
|
+
|
|
111
|
+
- 登録先: `RuVim::CommandRegistry.instance`
|
|
112
|
+
- 定義値: `call:` に `Symbol` または `Proc`
|
|
113
|
+
- 実行先: `RuVim::GlobalCommands.instance`
|
|
114
|
+
|
|
115
|
+
例:
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
RuVim::CommandRegistry.instance.register(
|
|
119
|
+
"cursor.left",
|
|
120
|
+
call: :cursor_left,
|
|
121
|
+
desc: "Move cursor left"
|
|
122
|
+
)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Ex コマンド (`ExCommandRegistry`)
|
|
126
|
+
|
|
127
|
+
ユーザーが `:` 行で入力するコマンド名を管理します。
|
|
128
|
+
|
|
129
|
+
- 登録先: `RuVim::ExCommandRegistry.instance`
|
|
130
|
+
- canonical name + `aliases`
|
|
131
|
+
- `nargs`, `bang`, `desc` を保持
|
|
132
|
+
|
|
133
|
+
例:
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
RuVim::ExCommandRegistry.instance.register(
|
|
137
|
+
"w",
|
|
138
|
+
call: :file_write,
|
|
139
|
+
aliases: %w[write],
|
|
140
|
+
nargs: :maybe_one,
|
|
141
|
+
bang: true,
|
|
142
|
+
desc: "Write current buffer"
|
|
143
|
+
)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`alias_for` は持ちません。`aliases` を登録時に展開し、同じ spec を参照させます。
|
|
147
|
+
|
|
148
|
+
### コマンド実装 (`GlobalCommands`)
|
|
149
|
+
|
|
150
|
+
コマンド本体は `RuVim::GlobalCommands.instance` のメソッドで実装します。
|
|
151
|
+
|
|
152
|
+
- `Symbol` 指定時は `public_send`
|
|
153
|
+
- `Proc` 指定時は `call`
|
|
154
|
+
|
|
155
|
+
想定の引数形:
|
|
156
|
+
|
|
157
|
+
- `ctx` (`RuVim::Context`)
|
|
158
|
+
- `argv: []`
|
|
159
|
+
- `kwargs: {}`
|
|
160
|
+
- `bang: false`
|
|
161
|
+
- `count: 1`
|
|
162
|
+
|
|
163
|
+
## 入力と実行の流れ
|
|
164
|
+
|
|
165
|
+
1. `RuVim::Input` が raw mode のキー入力を読む
|
|
166
|
+
2. `RuVim::App` が mode ごとに処理を分岐
|
|
167
|
+
3. Normal mode のキーは `RuVim::KeymapManager` で解決
|
|
168
|
+
4. `RuVim::Dispatcher` が内部コマンド or Ex コマンドを実行
|
|
169
|
+
5. `RuVim::Screen` が再描画
|
|
170
|
+
|
|
171
|
+
## 起動オプション(CLI, 現状)
|
|
172
|
+
|
|
173
|
+
インストール後の `ruvim` コマンド(実体は `exe/ruvim`)は `RuVim::CLI` を通して起動オプションを解釈します(`bin/ruvim` は互換ラッパー)。
|
|
174
|
+
|
|
175
|
+
- `--help`, `--version`
|
|
176
|
+
- `--clean`
|
|
177
|
+
- user config と ftplugin の読み込みを抑止
|
|
178
|
+
- `-R`
|
|
179
|
+
- 起動時に開いた current buffer を readonly にする(現状は保存禁止の意味)
|
|
180
|
+
- `-d`
|
|
181
|
+
- diff mode 互換 placeholder(現状は通常起動 + 未実装メッセージ表示)
|
|
182
|
+
- `-q {errorfile}`
|
|
183
|
+
- `-q {errorfile}` は quickfix 起動互換 placeholder(現状は未実装メッセージ表示)
|
|
184
|
+
- Ex の quickfix/location list は最小実装あり(`:vimgrep`, `:lvimgrep`, `:copen`, `:cnext` など)
|
|
185
|
+
- `-S [session]`
|
|
186
|
+
- session 起動互換 placeholder(現状は未実装メッセージ表示)
|
|
187
|
+
- `-M`
|
|
188
|
+
- 起動時に開いた file buffer を `modifiable=false` + `readonly=true` にする
|
|
189
|
+
- `-Z`
|
|
190
|
+
- restricted mode(現状)
|
|
191
|
+
- user config / ftplugin を読み込まない
|
|
192
|
+
- `:ruby` を禁止する
|
|
193
|
+
- `-n`
|
|
194
|
+
- 現状は no-op(将来の swap / 永続 undo / session 互換の先行予約)
|
|
195
|
+
- `-o[N]`, `-O[N]`, `-p[N]`
|
|
196
|
+
- 複数ファイルを水平 split / 垂直 split / tab で開く
|
|
197
|
+
- `N` は現状受理するがレイアウト数の厳密制御には未使用(将来拡張用)
|
|
198
|
+
- `-V[N]`, `--verbose[=N]`
|
|
199
|
+
- verbose ログを `stderr` に出力(現状は startup / config / startup actions / Ex submit の簡易ログ)
|
|
200
|
+
- `--startuptime FILE`
|
|
201
|
+
- 起動フェーズ時刻の簡易ログをファイルへ出力
|
|
202
|
+
- 現状は `init.start`, `pre_config_actions.done`, `config.loaded`, `signals.installed`, `buffers.opened`, `ftplugin.loaded`, `startup_actions.done`
|
|
203
|
+
- `--cmd {cmd}`
|
|
204
|
+
- user config 読み込み前に Ex コマンドを実行(複数回指定可)
|
|
205
|
+
- `-u {path|NONE}`
|
|
206
|
+
- `path`: 指定ファイルを設定として読み込む
|
|
207
|
+
- `NONE`: user config のみ無効化(ftplugin は有効)
|
|
208
|
+
- `-c {cmd}`
|
|
209
|
+
- 起動後に Ex コマンドを実行(複数回指定可)
|
|
210
|
+
- `+{cmd}`, `+{line}`, `+`
|
|
211
|
+
- 起動後の Ex 実行 / 行ジャンプ / 最終行ジャンプ
|
|
212
|
+
|
|
213
|
+
起動時コマンド(`-c`, `+...`)は、初期 buffer / file open / intro screen 構築の後に実行します。
|
|
214
|
+
`--cmd` はそれより前で、user config 読み込み前に実行します。
|
|
215
|
+
|
|
216
|
+
### Keymap layering(現状)
|
|
217
|
+
|
|
218
|
+
`RuVim::KeymapManager` は以下の優先順で解決します(高 -> 低)。
|
|
219
|
+
|
|
220
|
+
1. filetype-local
|
|
221
|
+
2. buffer-local
|
|
222
|
+
3. mode-local
|
|
223
|
+
4. global
|
|
224
|
+
|
|
225
|
+
現状の標準バインドは mode-local のみですが、内部 API として各レイヤーの登録を持っています。
|
|
226
|
+
|
|
227
|
+
## モード仕様(現状)
|
|
228
|
+
|
|
229
|
+
### Normal mode
|
|
230
|
+
|
|
231
|
+
- `h/j/k/l`: 移動
|
|
232
|
+
- `0`: 行頭へ移動
|
|
233
|
+
- `$`: 行末へ移動
|
|
234
|
+
- `^`: 行頭の最初の非空白へ移動
|
|
235
|
+
- `w/b/e`: 単語移動
|
|
236
|
+
- `f/F/t/T` + 文字: 行内文字移動
|
|
237
|
+
- `;`, `,`: 直前の行内文字移動を繰り返し / 逆方向
|
|
238
|
+
- `%`: 対応括弧ジャンプ(`()[]{}`)
|
|
239
|
+
- `gg`: 先頭へ移動
|
|
240
|
+
- `G`: 末尾へ移動
|
|
241
|
+
- `i`: Insert mode
|
|
242
|
+
- `a`, `A`, `I`: 挿入開始位置を変えて Insert mode
|
|
243
|
+
- `o`, `O`: 下/上に行を開いて Insert mode
|
|
244
|
+
- `:`: Command-line mode
|
|
245
|
+
- `/`: 前方検索の command-line mode
|
|
246
|
+
- `?`: 後方検索の command-line mode
|
|
247
|
+
- `x`: カーソル位置の文字削除
|
|
248
|
+
- `dd`: 現在行削除
|
|
249
|
+
- `d` + motion: operator-pending delete(例: `dw`, `dj`, `dk`, `d$`, `dh`, `dl`)
|
|
250
|
+
- text object(`iw`, `aw`, `ip`, `ap`, `i"`, `a"`, ``i` ``, ``a` ``, `i)`, `a)`, `i]`, `a]`, `i}`, `a}`)を `d/y/c` と Visual で一部利用可
|
|
251
|
+
- `yy`, `yw`: yank
|
|
252
|
+
- `p`, `P`: paste
|
|
253
|
+
- `r<char>`: 1文字置換
|
|
254
|
+
- `c` + motion / `cc`: change(削除して Insert mode)
|
|
255
|
+
- `v`: Visual (characterwise)
|
|
256
|
+
- `V`: Visual (linewise)
|
|
257
|
+
- `Ctrl-v`: Visual (blockwise, 最小)
|
|
258
|
+
- `u`: Undo
|
|
259
|
+
- `Ctrl-r`: Redo
|
|
260
|
+
- `.`: 直前変更の repeat(拡張版。`x`, `dd`, `d{motion}`, `p/P`, `r<char>`, `i/a/A/I/o/O`, `cc`, `c{motion}`)
|
|
261
|
+
- `n`: 直前検索を同方向に繰り返し
|
|
262
|
+
- `N`: 直前検索を逆方向に繰り返し
|
|
263
|
+
- `1..9` + 動作: count(例: `3j`, `2x`)
|
|
264
|
+
- 矢印キー: 移動
|
|
265
|
+
|
|
266
|
+
### Insert mode
|
|
267
|
+
|
|
268
|
+
- 通常文字: 挿入
|
|
269
|
+
- `Enter`: 改行
|
|
270
|
+
- `Backspace`: 1文字削除 / 行結合
|
|
271
|
+
- `Ctrl-n` / `Ctrl-p`: buffer words 補完(次/前)
|
|
272
|
+
- `Esc`: Normal mode に戻る
|
|
273
|
+
- `Ctrl-c`: Normal mode に戻る(終了しない)
|
|
274
|
+
- 矢印キー: 移動
|
|
275
|
+
|
|
276
|
+
### Visual mode(現状)
|
|
277
|
+
|
|
278
|
+
- `v`: characterwise Visual の開始 / 終了
|
|
279
|
+
- `V`: linewise Visual の開始 / 切替
|
|
280
|
+
- `Ctrl-v`: blockwise Visual の開始 / 切替(最小)
|
|
281
|
+
- 移動キー: `h/j/k/l`, `w/b/e`, `0/$/^`, `gg/G`, 矢印キー
|
|
282
|
+
- `y`: 選択範囲を yank
|
|
283
|
+
- `d`: 選択範囲を delete
|
|
284
|
+
- `i`/`a` + object: text object を選択(`iw`, `aw`, `ip`, `ap`, `i"`, `a"`, ``i` ``, ``a` ``, `i)`, `a)`, `i]`, `a]`, `i}`, `a}`)
|
|
285
|
+
- `Esc` / `Ctrl-c`: Normal mode に戻る
|
|
286
|
+
- blockwise の text object 選択 / paste の Vim 互換挙動は未対応
|
|
287
|
+
|
|
288
|
+
### Command-line mode
|
|
289
|
+
|
|
290
|
+
- `:` で入る
|
|
291
|
+
- `/` `?` でも入る(検索用)
|
|
292
|
+
- 文字入力 / `Backspace`
|
|
293
|
+
- `Enter` で Ex 実行
|
|
294
|
+
- `Esc` でキャンセル
|
|
295
|
+
- `Left` / `Right` でカーソル移動
|
|
296
|
+
- `Tab` (`Ctrl-i`) で Ex 補完
|
|
297
|
+
- コマンド名
|
|
298
|
+
- 一部引数(path / buffer / option)
|
|
299
|
+
|
|
300
|
+
## Ex コマンド仕様(現状 builtin)
|
|
301
|
+
|
|
302
|
+
- `:w [path]` / `:write [path]`
|
|
303
|
+
- `:q[!]` / `:quit[!]`
|
|
304
|
+
- `:wq[!] [path]`
|
|
305
|
+
- `:e <path>` / `:edit <path>`
|
|
306
|
+
- `:e[!] [path]` / `:edit[!] [path]`
|
|
307
|
+
- `:help [topic]`
|
|
308
|
+
- `:commands`
|
|
309
|
+
- `:command[!] <Name> <ex-body>`
|
|
310
|
+
- `:ruby <code>` / `:rb <code>`
|
|
311
|
+
- `:ls` / `:buffers`
|
|
312
|
+
- `:bnext` / `:bn`
|
|
313
|
+
- `:bprev` / `:bp`
|
|
314
|
+
- `:buffer <id|name|#>` / `:b <id|name|#>`
|
|
315
|
+
- `:split`
|
|
316
|
+
- `:vsplit`
|
|
317
|
+
- `:tabnew [path]`
|
|
318
|
+
- `:tabnext` / `:tabn`
|
|
319
|
+
- `:tabprev` / `:tabp`
|
|
320
|
+
|
|
321
|
+
## 検索仕様(現状)
|
|
322
|
+
|
|
323
|
+
- `/pattern` : 前方検索
|
|
324
|
+
- `?pattern` : 後方検索
|
|
325
|
+
- `n` : 直前検索を同方向に繰り返し
|
|
326
|
+
- `N` : 直前検索を逆方向に繰り返し
|
|
327
|
+
- `*`, `#` : カーソル下の単語を前/後方検索(単語境界つき)
|
|
328
|
+
- `g*`, `g#` : カーソル下の単語を前/後方検索(部分一致)
|
|
329
|
+
- `:%s/pat/repl/g` : バッファ全体 substitute(最小実装)
|
|
330
|
+
|
|
331
|
+
### 仕様メモ
|
|
332
|
+
|
|
333
|
+
- 検索文字列は Ruby 正規表現として扱う
|
|
334
|
+
- 末尾/先頭でラップ検索する
|
|
335
|
+
- 空の検索文字列は直前検索を再利用(直前がなければエラー)
|
|
336
|
+
- 検索開始位置は現在カーソルの次文字/前文字
|
|
337
|
+
- 可視範囲の検索ハイライトを表示(簡易)
|
|
338
|
+
|
|
339
|
+
補足:
|
|
340
|
+
|
|
341
|
+
- `:q` は未保存変更があると拒否
|
|
342
|
+
- `:q` は Vim 寄りに、複数 window 時は current window を閉じる(window が1つで tab が複数なら current tab を閉じる)
|
|
343
|
+
- `:q!` は強制的に window / tab / app を閉じる
|
|
344
|
+
- `:e` は未保存変更があると拒否(`!` で破棄可)
|
|
345
|
+
- `:e!`(引数なし)は現在ファイルの再読込(undo/redo クリア)
|
|
346
|
+
- `:buffer!`, `:bnext!`, `:bprev!` は未保存変更があっても切替
|
|
347
|
+
- `:w!` は現状 `:w` と同等に受理(権限昇格などは未実装)
|
|
348
|
+
|
|
349
|
+
### `:command`(現状仕様)
|
|
350
|
+
|
|
351
|
+
- `:command Name ex_body` でユーザー定義 Ex コマンドを追加
|
|
352
|
+
- `:command! Name ex_body` で上書き
|
|
353
|
+
- `:command`(引数なし)でユーザー定義コマンド名一覧表示
|
|
354
|
+
- 定義されたコマンドは Ex コマンドとして `:Name` で実行
|
|
355
|
+
- 現状は「Ex コマンド文字列のエイリアス展開」に近い仕様
|
|
356
|
+
|
|
357
|
+
### `:ruby` / `:rb`(現状仕様)
|
|
358
|
+
|
|
359
|
+
- Ex 行から Ruby コードを評価する
|
|
360
|
+
- 評価スコープで利用可能なローカル:
|
|
361
|
+
- `ctx`
|
|
362
|
+
- `editor`
|
|
363
|
+
- `buffer`
|
|
364
|
+
- `window`
|
|
365
|
+
- 返り値はステータスラインに表示(`inspect`)
|
|
366
|
+
|
|
367
|
+
### バッファ管理 Ex コマンド(現状仕様)
|
|
368
|
+
|
|
369
|
+
- `:ls` / `:buffers` : バッファ一覧表示
|
|
370
|
+
- `:bnext` / `:bn` : 次バッファへ
|
|
371
|
+
- `:bprev` / `:bp` : 前バッファへ
|
|
372
|
+
- `:buffer` / `:b` : バッファ切替
|
|
373
|
+
- 数値ID
|
|
374
|
+
- パス名 / basename
|
|
375
|
+
- `#`(alternate buffer)
|
|
376
|
+
|
|
377
|
+
### alternate buffer(`#`)
|
|
378
|
+
|
|
379
|
+
- `Editor#alternate_buffer_id` を保持
|
|
380
|
+
- バッファ切替時に直前バッファを更新
|
|
381
|
+
- `:buffer #` で切替可能
|
|
382
|
+
|
|
383
|
+
## 画面描画仕様(現状)
|
|
384
|
+
|
|
385
|
+
ANSI エスケープシーケンスによる再描画です。
|
|
386
|
+
|
|
387
|
+
- 代替スクリーン (`?1049h / ?1049l`)
|
|
388
|
+
- カーソル非表示/表示 (`?25l / ?25h`)
|
|
389
|
+
- 行キャッシュによる簡易差分描画(同サイズ時)
|
|
390
|
+
- 最下段: status line
|
|
391
|
+
- Command-line mode 時は最下段を command-line、1つ上を status line
|
|
392
|
+
- ファイル未指定起動時は Vim 風 intro screen を表示(RuVim では intro 用の read-only 特殊バッファ)
|
|
393
|
+
- カーソル位置の文字を反転表示(見やすさ向上)
|
|
394
|
+
|
|
395
|
+
### split UI(現状)
|
|
396
|
+
|
|
397
|
+
- `:split` / `:vsplit` で複数 window を作成
|
|
398
|
+
- レイアウトは簡易タイル(等分割)
|
|
399
|
+
- `horizontal`: 上下分割
|
|
400
|
+
- `vertical`: 左右分割
|
|
401
|
+
- nested split / Vim の厳密な window tree は未実装
|
|
402
|
+
- 各 window は cursor / scroll を独立して保持
|
|
403
|
+
|
|
404
|
+
### Tabpage(現状)
|
|
405
|
+
|
|
406
|
+
- `:tabnew [path]` で新しいタブを作成
|
|
407
|
+
- `:tabnext`, `:tabprev` で移動
|
|
408
|
+
- 各タブは以下を独立に保持
|
|
409
|
+
- window list(表示中 window 群)
|
|
410
|
+
- current window
|
|
411
|
+
- split layout(horizontal / vertical / single)
|
|
412
|
+
|
|
413
|
+
### リサイズ対応
|
|
414
|
+
|
|
415
|
+
- `SIGWINCH` を trap して `Screen` キャッシュを無効化
|
|
416
|
+
- self-pipe (`IO.pipe`) で resize 通知を `select` 待ちへ伝播
|
|
417
|
+
- 入力待機は `stdin + resize通知` を `IO.select` で待つ
|
|
418
|
+
- 描画ごとに `winsize` を再取得して viewport を再計算
|
|
419
|
+
|
|
420
|
+
### Command-line 改善(現状)
|
|
421
|
+
|
|
422
|
+
- prefix ごとの履歴保持(`:` `/` `?`)
|
|
423
|
+
- `Up/Down` で履歴移動
|
|
424
|
+
- `Tab` で Ex コマンド名補完(prefix `:` のとき)
|
|
425
|
+
|
|
426
|
+
### 文字幅対応(現状ベースライン)
|
|
427
|
+
|
|
428
|
+
- UTF-8 文字列はそのまま保持・表示
|
|
429
|
+
- タブは描画時にスペース展開(`TABSTOP=2`)
|
|
430
|
+
- カーソル列計算に簡易 display width 計算を使用
|
|
431
|
+
- 一部の全角文字を幅2として扱う近似実装
|
|
432
|
+
- `RUVIM_AMBIGUOUS_WIDTH=2` で曖昧幅文字を幅2として扱える
|
|
433
|
+
- variation selector / ZWJ は幅0扱い
|
|
434
|
+
- 一部 emoji range を幅2として扱う
|
|
435
|
+
|
|
436
|
+
描画の幅処理(現状):
|
|
437
|
+
|
|
438
|
+
- 本文行の clipping/padding は `RuVim::TextMetrics.clip_cells_for_width` を共通利用
|
|
439
|
+
- tab 展開
|
|
440
|
+
- `source_col` 保持(cursor/search/syntax highlight の重ね合わせ用)
|
|
441
|
+
- 横スクロール可視判定と cursor 列計算は `char index <-> screen column` 変換で揃える
|
|
442
|
+
- ステータス/エラー行は「プレーン文字列を先に切り詰めてから SGR を付ける」方針で ANSI 切断を避ける
|
|
443
|
+
|
|
444
|
+
制約:
|
|
445
|
+
|
|
446
|
+
- 左右移動(`h/l`・矢印左右)は grapheme cluster を考慮するが、他の移動は未統一
|
|
447
|
+
- 完全な Unicode grapheme / East Asian Width 互換ではない
|
|
448
|
+
- 横スクロール可視判定は `screen column` ベース(`TextMetrics` 利用)
|
|
449
|
+
|
|
450
|
+
### エンコーディング方針(現状)
|
|
451
|
+
|
|
452
|
+
- 読み込み:
|
|
453
|
+
- `File.binread` で bytes を取得
|
|
454
|
+
- `RuVim::Buffer.decode_text` で UTF-8 へ変換して保持
|
|
455
|
+
- UTF-8 として妥当ならそのまま使用
|
|
456
|
+
- 不正 UTF-8 の場合は `Encoding.default_external` を試し、それでもダメなら `scrub`
|
|
457
|
+
- 内部表現:
|
|
458
|
+
- `Buffer#lines` は UTF-8 `String` 前提
|
|
459
|
+
- 保存:
|
|
460
|
+
- 現状は `File.binwrite` で `lines.join("\n")` をそのまま保存
|
|
461
|
+
- 実質的に UTF-8 保存(元 encoding の保持は未実装)
|
|
462
|
+
- 制約:
|
|
463
|
+
- 元ファイル encoding の保持/再保存は未対応
|
|
464
|
+
- 不正バイト列を完全保持したまま編集するモードは未実装
|
|
465
|
+
|
|
466
|
+
## Undo / Redo 仕様(現状)
|
|
467
|
+
|
|
468
|
+
- 実装単位: `RuVim::Buffer` ごとの undo / redo stack
|
|
469
|
+
- `u`: undo
|
|
470
|
+
- `Ctrl-r`: redo
|
|
471
|
+
- undo 対象:
|
|
472
|
+
- 文字挿入
|
|
473
|
+
- 改行
|
|
474
|
+
- backspace
|
|
475
|
+
- `x`
|
|
476
|
+
- `dd`
|
|
477
|
+
|
|
478
|
+
### undo 粒度(現状)
|
|
479
|
+
|
|
480
|
+
- Normal mode の編集コマンドは原則 1 コマンド = 1 undo 単位
|
|
481
|
+
- 例: `3x`, `3dd` はそれぞれ 1 undo
|
|
482
|
+
- Insert mode は「Insert mode に入ってから抜けるまで」を 1 undo 単位
|
|
483
|
+
- `i` で入り、複数文字入力して `Esc`/`Ctrl-c` で抜けると 1 回の `u` で戻る
|
|
484
|
+
|
|
485
|
+
Vim 完全互換ではなく、まずは扱いやすい粒度を優先した仕様です。
|
|
486
|
+
|
|
487
|
+
## Operator-pending 仕様(現状)
|
|
488
|
+
|
|
489
|
+
- `d` を押すと delete operator pending 状態に入る
|
|
490
|
+
- 次のキーを motion として解釈して削除を実行
|
|
491
|
+
|
|
492
|
+
対応している `d + motion`:
|
|
493
|
+
|
|
494
|
+
- `dd` : 行削除
|
|
495
|
+
- `dh` : 左方向に文字削除
|
|
496
|
+
- `dl` : 右方向に文字削除
|
|
497
|
+
- `dj` : 下方向行を含めて行削除
|
|
498
|
+
- `dk` : 上方向行を含めて行削除
|
|
499
|
+
- `d$` : 行末まで削除
|
|
500
|
+
- `dw` : 次の単語先頭まで削除(簡易 word 定義)
|
|
501
|
+
- `diw`, `daw` : text object word(簡易)
|
|
502
|
+
- `di"`, `da"`, `di)`, `da)` : delimiter text object(簡易・同一行中心)
|
|
503
|
+
|
|
504
|
+
設計上は operator-pending 状態機械を導入しており、`d/y/c` を同じ流れで扱います。
|
|
505
|
+
|
|
506
|
+
補足:
|
|
507
|
+
|
|
508
|
+
- 現状 `y` operator も実装済み(`yy`, `yw`)
|
|
509
|
+
- 現状 `c` operator も実装済み(`cw`, `cc`, `c$`, `ciw`, `caw` など)
|
|
510
|
+
|
|
511
|
+
### text object(現状)
|
|
512
|
+
|
|
513
|
+
- 対応:
|
|
514
|
+
- `iw`, `aw`
|
|
515
|
+
- `i"`, `a"`
|
|
516
|
+
- `i)`, `a)`
|
|
517
|
+
- 利用先:
|
|
518
|
+
- operator-pending `d/y/c`
|
|
519
|
+
- Visual mode(選択更新)
|
|
520
|
+
- 制約:
|
|
521
|
+
- `i"` / `a"` / `i)` / `a)` は簡易実装(主に同一行)
|
|
522
|
+
|
|
523
|
+
## レジスタ仕様(現状の基礎)
|
|
524
|
+
|
|
525
|
+
- unnamed register (`"`) を実装
|
|
526
|
+
- named register(`"a`..`"z`)を実装
|
|
527
|
+
- append register(`"A`..`"Z`)を実装(小文字 register へ追記)
|
|
528
|
+
- black hole register(`"_`)を実装
|
|
529
|
+
- yank register `0` を実装(yank 操作で更新)
|
|
530
|
+
- numbered delete register `1-9` を簡易実装(delete/change 操作で回転)
|
|
531
|
+
- `"+`, `"*` は system clipboard register として扱う(利用可能 backend がある場合)
|
|
532
|
+
- delete / yank 操作で指定 register と unnamed register を更新
|
|
533
|
+
- `p`, `P` は指定 register(なければ unnamed)を paste
|
|
534
|
+
- register type:
|
|
535
|
+
- `:charwise`
|
|
536
|
+
- `:linewise`
|
|
537
|
+
- `yy`, `yw`, Visual `y` などで yank
|
|
538
|
+
- `x`, `dd`, `d{motion}`, Visual `d` などで delete した内容も保存
|
|
539
|
+
|
|
540
|
+
## Mark / Jump List(現状の基礎)
|
|
541
|
+
|
|
542
|
+
- mark 設定:
|
|
543
|
+
- `m{a-z}`: local mark(buffer-local)
|
|
544
|
+
- `m{A-Z}`: global mark
|
|
545
|
+
- mark jump:
|
|
546
|
+
- `'{mark}`: 行単位ジャンプ(行頭の最初の非空白へ)
|
|
547
|
+
- `` `{mark} ``: 位置ジャンプ
|
|
548
|
+
- jump list:
|
|
549
|
+
- `<C-o>`: 古い位置へ
|
|
550
|
+
- `<C-i>`: 新しい位置へ(端末では Tab と同じ入力コード)
|
|
551
|
+
- `''`: 行単位で古い位置へ
|
|
552
|
+
- `` `` ``: 位置ジャンプで古い位置へ
|
|
553
|
+
- 現状の jump list 追加契機(簡易):
|
|
554
|
+
- `gg`, `G`
|
|
555
|
+
- 検索 (`/`, `?`, `n`, `N`, `*`, `#`, `g*`, `g#`)
|
|
556
|
+
- バッファ切替(`:bnext`, `:bprev`, `:buffer`)
|
|
557
|
+
|
|
558
|
+
## Macro(現状の基礎)
|
|
559
|
+
|
|
560
|
+
- `q{reg}`: macro 記録開始(`q` で停止)
|
|
561
|
+
- `@{reg}`: macro 再生
|
|
562
|
+
- `@@`: 直前に再生した macro を再生
|
|
563
|
+
- register は `a-z`, `A-Z`, `0-9` を許可(`A-Z` は既存 macro に追記)
|
|
564
|
+
- 再帰再生は簡易ガードあり(同一 macro 再入 / 深すぎる入れ子を拒否)
|
|
565
|
+
|
|
566
|
+
## Option system(現状の基礎)
|
|
567
|
+
|
|
568
|
+
- option スコープ:
|
|
569
|
+
- global
|
|
570
|
+
- buffer-local
|
|
571
|
+
- window-local
|
|
572
|
+
- Ex:
|
|
573
|
+
- `:set`
|
|
574
|
+
- `:setlocal`
|
|
575
|
+
- `:setglobal`
|
|
576
|
+
- 実装済み option:
|
|
577
|
+
- `number`(window-local, bool)
|
|
578
|
+
- `relativenumber`(window-local, bool)
|
|
579
|
+
- `ignorecase` / `smartcase` / `hlsearch`(global, bool)
|
|
580
|
+
- `tabstop`(buffer-local, int)
|
|
581
|
+
- `Screen` は `number` / `relativenumber` / `tabstop` を描画に反映する
|
|
582
|
+
|
|
583
|
+
## Filetype / ftplugin(現状の基礎)
|
|
584
|
+
|
|
585
|
+
- buffer 作成時に path から `filetype` を簡易検出して buffer-local option に保存
|
|
586
|
+
- ftplugin 読み込みパス(優先順):
|
|
587
|
+
- `$XDG_CONFIG_HOME/ruvim/ftplugin/<filetype>.rb`
|
|
588
|
+
- `~/.config/ruvim/ftplugin/<filetype>.rb`
|
|
589
|
+
- ftplugin では:
|
|
590
|
+
- `nmap` / `imap` -> filetype-local keymap として登録
|
|
591
|
+
- `setlocal` / `setglobal` / `set`(DSL)で option 変更可
|
|
592
|
+
- 同一 buffer の同一 filetype ftplugin は一度だけ読み込む
|
|
593
|
+
|
|
594
|
+
## シンタックスハイライト(最小)
|
|
595
|
+
|
|
596
|
+
- 描画時に filetype ごとの regex ベース highlighter を適用
|
|
597
|
+
- 現状の対応 filetype:
|
|
598
|
+
- `ruby`
|
|
599
|
+
- `json`
|
|
600
|
+
- 優先度(高 -> 低):
|
|
601
|
+
- cursor / visual
|
|
602
|
+
- search highlight
|
|
603
|
+
- syntax highlight
|
|
604
|
+
- 実装は `lib/ruvim/highlighter.rb`
|
|
605
|
+
- Vim の syntax / treesitter 相当の互換性はない(最小実装)
|
|
606
|
+
|
|
607
|
+
## シングルトン方針
|
|
608
|
+
|
|
609
|
+
グローバルに共有して良い「定義系」だけシングルトンにしています。
|
|
610
|
+
|
|
611
|
+
- `RuVim::CommandRegistry.instance`
|
|
612
|
+
- `RuVim::ExCommandRegistry.instance`
|
|
613
|
+
- `RuVim::GlobalCommands.instance`
|
|
614
|
+
|
|
615
|
+
`RuVim::Editor` はシングルトンではありません(実行状態の分離のため)。
|
|
616
|
+
|
|
617
|
+
## 設定ファイル(現状)
|
|
618
|
+
|
|
619
|
+
- 起動時に XDG パスの設定ファイルを読み込む(存在する場合)
|
|
620
|
+
- `$XDG_CONFIG_HOME/ruvim/init.rb`
|
|
621
|
+
- 未設定時: `~/.config/ruvim/init.rb`
|
|
622
|
+
- `RuVim::ConfigLoader` + `RuVim::ConfigDSL` を使用
|
|
623
|
+
- DSL は `BasicObject` ベースで、主に以下を提供:
|
|
624
|
+
- `nmap`
|
|
625
|
+
- `imap`
|
|
626
|
+
- `map_global`
|
|
627
|
+
- `command`
|
|
628
|
+
- `ex_command`
|
|
629
|
+
- `ex_command_call`
|
|
630
|
+
|
|
631
|
+
安全性メモ:
|
|
632
|
+
|
|
633
|
+
- XDG 設定ファイル(`init.rb`)は Ruby として評価されるため、信頼できる内容のみ使用する
|
|
634
|
+
- 直接内部状態に触る代わりに、まずは DSL API を通す設計
|
|
635
|
+
|
|
636
|
+
## テスト(現状)
|
|
637
|
+
|
|
638
|
+
- `Minitest` を利用
|
|
639
|
+
- 追加済み:
|
|
640
|
+
- `test/buffer_test.rb`
|
|
641
|
+
- `test/dispatcher_test.rb`
|
|
642
|
+
- `test/keymap_manager_test.rb`
|
|
643
|
+
|
|
644
|
+
## 既知の未実装 / 今後の仕様候補
|
|
645
|
+
|
|
646
|
+
- Undo / Redo
|
|
647
|
+
- 複数 window split
|
|
648
|
+
- Tabpage
|
|
649
|
+
- レジスタ(yank/delete)
|
|
650
|
+
- 検索 (`/`, `?`)
|
|
651
|
+
- operator-pending(`d` + motion)
|
|
652
|
+
- 差分描画
|
|
653
|
+
- filetype / buffer-local keymap
|
|
654
|
+
- user-defined Ex command DSL(`:command`)
|
|
655
|
+
- `:ruby`(Ruby 式評価)
|