pi-vim 0.3.2 → 0.8.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.
package/README.md CHANGED
@@ -2,10 +2,6 @@
2
2
 
3
3
  Modal vim-like editing for Pi's input prompt. Covers the high-frequency 90% command surface.
4
4
 
5
- ## why
6
-
7
- You love Pi, you love Vim, you'll love pi-vim.
8
-
9
5
  ## install
10
6
 
11
7
  ```bash
@@ -14,9 +10,58 @@ pi install npm:pi-vim
14
10
 
15
11
  Restart Pi after install.
16
12
 
13
+ ## configure
14
+
15
+ pi-vim reads persistent Pi settings from `~/.pi/agent/settings.json` and project `.pi/settings.json`.
16
+
17
+ Clipboard write mirroring is controlled by `piVim.clipboardMirror`:
18
+
19
+ ```json
20
+ {
21
+ "piVim": {
22
+ "clipboardMirror": "all"
23
+ }
24
+ }
25
+ ```
26
+
27
+ | value | behavior |
28
+ |-------|----------|
29
+ | `all` | Mirror every unnamed-register write (default/current behavior) |
30
+ | `yank` | Mirror yanks only; deletes/changes update only pi-vim's internal register |
31
+ | `never` | Never mirror register writes to the OS clipboard |
32
+
33
+ The setting controls write mirroring only. `p` / `P` keep the paste policy documented below.
34
+
35
+ ## wrapping pi-vim
36
+
37
+ Supported: `pi-vim` first, `@jordyvd/pi-image-attachments` second. pi-vim does not call `ctx.ui.getEditorComponent()`; the wrapper does. Inverse order unsupported.
38
+
39
+ Wrappers must decorate in place or forward unintercepted surface: lifecycle (`handleInput`, `render`, `invalidate`), text (`getText`, `setText`, `insertTextAtCursor`, `getExpandedText`), callbacks (`onSubmit`, `onChange`, `onEscape`, `onCtrlD`, `onPasteImage`, `onExtensionShortcut`), `actionHandlers`, flags (`focused`, `disableSubmit`), reads (`getLines`, `getCursor`, `getMode()`).
40
+
41
+ #18/#21 delegation is not adopted: no previous-extension wrapping, insert delegate, or generic composition layer.
42
+
43
+ Manual smoke. If raw `-e` cannot resolve Pi peer packages, run `npm install --ignore-scripts --package-lock=false` in the image checkout.
44
+
45
+ ```bash
46
+ # repo root
47
+ pi -e ./index.ts -e ../pi-image-attachments/index.ts
48
+ # this worktree
49
+ pi -e ./index.ts -e ../../../pi-image-attachments/index.ts
50
+ ```
51
+
52
+ Check: insert text; add/paste image path; see `[Image #1]` widget; submit text+image stripped; switch INSERT/NORMAL modes.
53
+
54
+ ## contributor setup
55
+
56
+ Hooks install with `npm install` after cloning. To wire them explicitly:
57
+
58
+ ```bash
59
+ npm run hooks:install
60
+ ```
61
+
17
62
  ## stats
18
63
 
19
- - **112 commands**: motions, operators, counts, text objects, undo/redo
64
+ - **188 commands**: motions, operators, counts, text objects, undo/redo, ex quit
20
65
  - **sub-µs word motions** via precomputed boundary cache (~4ms startup, ~150KB memory)
21
66
  - **0 dependencies**
22
67
 
@@ -33,22 +78,19 @@ u # undo
33
78
  2} # jump two paragraphs forward
34
79
  ```
35
80
 
36
- Mode indicator (`INSERT` / `NORMAL`) appears at bottom-right.
37
- Its label is theme-colored: reverse-video `borderMuted` for
38
- INSERT, `borderAccent` for NORMAL.
81
+ Mode indicator (`INSERT` / `NORMAL` / `EX`) appears bottom-right, theme-colored.
82
+
83
+ Requires `@mariozechner/pi-tui >= 0.47.0`. With `pi-tui >= 0.49.3` and DECSCUSR support, cursor shape follows mode; otherwise software cursor remains.
39
84
 
40
85
  ## why pi-vim
41
86
 
42
87
  - Fast modal editing without leaving Pi.
43
88
  - Count-aware motions/operators (`2dw`, `3G`, `d2j`, `2}`).
44
- - Strong REPL-focused defaults; safe out-of-scope boundaries documented.
89
+ - REPL-focused defaults; out-of-scope boundaries documented.
45
90
  - Clipboard/register behavior is explicit and tested.
46
91
 
47
- ## for you / not for you
48
-
49
- Use pi-vim if you want fast Vim muscle-memory in Pi prompts.
50
- Skip it if you need full Vim feature parity (visual mode, macros, search,
51
- ex-commands, etc.).
92
+ Use pi-vim for fast Vim muscle-memory in Pi prompts. Skip it if you need
93
+ full Vim parity (visual mode, macros, search, extended ex-commands, …).
52
94
 
53
95
  ## common recipes
54
96
 
@@ -56,6 +98,11 @@ ex-commands, etc.).
56
98
  |------|------|
57
99
  | Jump to exact line 25 | `25gg` (or `25G`) |
58
100
  | Delete two words | `2dw` |
101
+ | Change current whitespace-delimited WORD | `ciW` |
102
+ | Delete WORD plus adjacent whitespace | `daW` |
103
+ | Change inside double quotes | `ci"` |
104
+ | Delete inside parentheses | `di(` |
105
+ | Yank braces with contents | `ya{` |
59
106
  | Change to end of line | `C` |
60
107
  | Delete current + 2 lines below | `d2j` |
61
108
  | Yank 3 lines | `3yy` |
@@ -73,7 +120,8 @@ ex-commands, etc.).
73
120
  | key | action |
74
121
  |----------|----------------------------------------|
75
122
  | `Esc` / `Ctrl+[` | Insert → Normal mode |
76
- | `Esc` / `Ctrl+[` | Normal mode → pass to Pi (abort agent) |
123
+ | `Esc` / `Ctrl+[` | Normal mode → pass to Pi (aborts the agent under default Pi keybindings) |
124
+ | `:` | Normal → EX mini-mode |
77
125
  | `i` | Normal → Insert at cursor |
78
126
  | `a` | Normal → Insert after cursor |
79
127
  | `I` | Normal → Insert at first non-whitespace |
@@ -81,6 +129,24 @@ ex-commands, etc.).
81
129
  | `o` | Normal → open line below + Insert |
82
130
  | `O` | Normal → open line above + Insert |
83
131
 
132
+ Optional: heavy users may want to move Pi's `app.interrupt` off bare `escape` in `~/.pi/agent/keybindings.json` since it overlaps with Insert→Normal. Pick your own replacement; user config overrides defaults.
133
+
134
+ #### ex mini-mode
135
+
136
+ Quit-only ex flows.
137
+
138
+ | key / command | action |
139
+ |---------------|--------|
140
+ | `:` | Enter EX mini-mode |
141
+ | `Enter` | Execute pending ex command |
142
+ | `Esc` | Cancel EX mini-mode |
143
+ | `Backspace` / `Ctrl+h` | Delete one ex-command character; on bare `:` exits EX mode |
144
+ | `:q` | Quit the current Pi session only when the prompt is empty or whitespace-only; otherwise show a warning |
145
+ | `:q!` | Force quit the current Pi session even when the prompt has text |
146
+ | `:qa` | Same safe quit policy as `:q` |
147
+ | `:qa!` | Same force quit policy as `:q!` |
148
+ | unsupported `:{cmd}` | Show warning notification; no quit |
149
+
84
150
  Insert-mode shortcuts (stay in Insert mode):
85
151
 
86
152
  | key | action |
@@ -94,48 +160,18 @@ Insert-mode shortcuts (stay in Insert mode):
94
160
 
95
161
  ### navigation (normal mode)
96
162
 
97
- A `{count}` prefix can be prepended to any navigation key (max: `9999`).
98
-
99
- | key | action |
100
- |---------------|-------------------------------|
101
- | `h` | Left |
102
- | `l` | Right |
103
- | `j` | Down |
104
- | `k` | Up |
105
- | `{count}h/l` | Move left/right `{count}` cols |
106
- | `{count}j/k` | Move down/up `{count}` lines (clamped to buffer size) |
107
- | `0` | Line start |
108
- | `^` | First non-whitespace char of line |
109
- | `_` | First non-whitespace char; with `{count}`, move down `count - 1` lines first |
110
- | `$` | Line end |
111
- | `gg` | Buffer start (line 1) |
112
- | `{count}gg` | Go to line `{count}` (1-indexed, clamped) |
113
- | `G` | Buffer end (last line) |
114
- | `{count}G` | Go to line `{count}` (1-indexed, clamped) |
115
- | `w` | Next `word` start (keyword/punctuation aware) |
116
- | `b` | Previous `word` start |
117
- | `e` | `word` end (inclusive) |
118
- | `W` | Next `WORD` start (whitespace-delimited token) |
119
- | `B` | Previous `WORD` start |
120
- | `E` | `WORD` end (inclusive) |
121
- | `{count}w/b/e`| Move `{count}` `word` motions |
122
- | `{count}W/B/E`| Move `{count}` `WORD` motions |
123
- | `}` | Move to next paragraph start (line start col `0`) |
124
- | `{` | Move to previous paragraph start (line start col `0`) |
125
- | `{count}}` | Repeat `}` `{count}` times |
126
- | `{count}{` | Repeat `{` `{count}` times |
127
-
128
- `word` (`w/b/e`) splits punctuation from keyword chars. `WORD` (`W/B/E`)
129
- treats any non-whitespace run as one token (`foo-bar`, `path/to`, `x.y`).
130
-
131
- Paragraph boundary definition (this extension wave):
132
- - blank line: matches `^\s*$`
133
- - paragraph start: non-blank line at BOF, or non-blank line immediately after a blank line
134
-
135
- Standalone `{` / `}` motions are navigation-only (no text/register mutation).
136
- Counted forms (`{count}{`, `{count}}`) step paragraph-by-paragraph.
137
- If no further paragraph boundary exists, motions clamp at BOF/EOF.
138
- Operator forms with braces (`d{`, `d}`, `c{`, `c}`, `y{`, `y}`) are out of scope for this wave.
163
+ A `{count}` prefix can be prepended to navigation keys (max: `9999`).
164
+
165
+ | key | action |
166
+ |-----|--------|
167
+ | `h` / `l` / `j` / `k`; `{count}h/l/j/k` | Move left/right/down/up; line moves clamp to the buffer |
168
+ | `0` / `^` / `_` / `$` | Line start / first non-whitespace / counted first non-whitespace / line end |
169
+ | `gg` / `G`; `{count}gg` / `{count}G` | Buffer start/end or absolute 1-indexed line |
170
+ | `w` / `b` / `e`; `{count}w/b/e` | `word` start/back/end motions |
171
+ | `W` / `B` / `E`; `{count}W/B/E` | whitespace-delimited `WORD` motions |
172
+ | `{` / `}`; `{count}{` / `{count}}` | Previous/next paragraph start |
173
+
174
+ `word` splits punctuation from keyword chars; `WORD` treats any non-whitespace run as one token (`foo-bar`, `path/to`). Paragraph starts are non-blank lines at BOF or after blank lines (`^\s*$`). `{` / `}` are navigation-only; brace operator forms (`d{`, `c}`, `y{`, …) are out of scope.
139
175
 
140
176
  ---
141
177
 
@@ -159,62 +195,60 @@ Char-find motions compose with operators: `df{char}`, `ct{char}`, `d{count}t{cha
159
195
 
160
196
  ### edit operators (normal mode)
161
197
 
162
- All operators write to the unnamed register and mirror to the system clipboard
163
- (best-effort; clipboard failure never breaks editing).
198
+ Register-writing edits write to the unnamed register. With the default clipboard mirror policy, they also mirror to the system clipboard best-effort (clipboard failure never breaks editing).
199
+
200
+ #### text objects
201
+
202
+ Text objects compose as `d`/`c`/`y` + `i`/`a` + object. `i` means inner; `a` means around.
203
+
204
+ | object | keys | range |
205
+ |--------|------|-------|
206
+ | word | `iw` / `aw` | Keyword word; `aw` includes spaces |
207
+ | WORD | `iW` / `aW` | Line-local whitespace-delimited WORD; `aW` includes adjacent whitespace |
208
+ | quotes | `i"` / `a"`, `i'` / `a'`, <code>i`</code> / <code>a`</code> | Smallest containing quote pair on the line |
209
+ | parentheses | `i(` / `a(`; aliases `i)` / `a)`, `ib` / `ab` | Smallest containing pair |
210
+ | brackets | `i[` / `a[`; aliases `i]` / `a]` | Smallest containing pair |
211
+ | braces | `i{` / `a{`; aliases `i}` / `a}`, `iB` / `aB` | Smallest containing pair |
212
+
213
+ Semantics:
214
+ - WORD objects are line-local and whitespace-delimited.
215
+ - Quote objects are line-local; odd-backslash escapes are ignored; `a` includes delimiters only, not surrounding whitespace.
216
+ - Bracket objects are buffer-aware, nested, lexical, and not parser-aware; brackets inside strings/comments still count.
217
+ - Empty inner delimiter objects no-op for delete/yank; change enters Insert at the inner start without writing the register.
218
+ - Delimited counts cancel (`d2i"`, `2ci(`, `y2a{`). Counted word/WORD text objects work for delete/change only; counted yank text objects cancel.
164
219
 
165
220
  #### delete `d{motion}` / `dd`
166
221
 
167
- A `{count}` or dual-count prefix (`{pfx}d{op}{motion}`) is supported for
168
- word, char-find, and linewise motions. Maximum total count: `9999`.
169
-
170
- | command | deletes |
171
- |-------------------|-----------------------------------------------------------|
172
- | `dw` | Forward to next `word` start (exclusive, can cross lines) |
173
- | `de` | Forward to `word` end (inclusive, can cross lines) |
174
- | `db` | Backward to `word` start (exclusive, can cross lines) |
175
- | `dW` | Forward to next `WORD` start (exclusive, can cross lines) |
176
- | `dE` | Forward to `WORD` end (inclusive, can cross lines) |
177
- | `dB` | Backward to `WORD` start (exclusive, can cross lines) |
178
- | `d{count}w/e/b` | Forward/backward `{count}` `word` motions |
179
- | `d{count}W/E/B` | Forward/backward `{count}` `WORD` motions |
180
- | `d$` | To end of line |
181
- | `d0` | To start of line |
182
- | `d^` | To first non-whitespace char of line |
183
- | `d_` | Current line (linewise, same as `dd`) |
184
- | `d{count}_` | `{count}` lines (linewise, same as `{count}dd`) |
185
- | `dd` | Current line (linewise) |
186
- | `{count}dd` | `{count}` lines (linewise) |
187
- | `d{count}j` | Current line + `{count}` lines below (linewise) |
188
- | `d{count}k` | Current line + `{count}` lines above (linewise) |
189
- | `dG` | Current line to end of buffer (linewise) |
190
- | `df{char}` | To and including `char` |
191
- | `d{count}f{char}` | To and including Nth `char` |
192
- | `dt{char}` | Up to (not including) `char` |
193
- | `dF{char}` | Backward to and including `char` |
194
- | `dT{char}` | Backward to one after `char` |
195
- | `diw` | Inner word |
196
- | `daw` | Around word (includes surrounding spaces) |
197
- | `d{count}aw` | Around `{count}` words |
222
+ A `{count}` or dual-count prefix (`{pfx}d{op}{motion}`) is supported for word,
223
+ WORD, char-find, and linewise motions. Maximum total count: `9999`.
224
+
225
+ | command | deletes |
226
+ |---------|---------|
227
+ | `dw` / `de` / `db`; `dW` / `dE` / `dB` | word/WORD motion ranges; `{count}` repeats |
228
+ | `d$` / `d0` / `d^` | To EOL / BOL / first non-whitespace |
229
+ | `d_` / `dd`; `d{count}_` / `{count}dd` | Current or counted whole lines |
230
+ | `d{count}j` / `d{count}k` / `dG` | Linewise down/up/to EOF |
231
+ | `df{c}` / `dt{c}` / `dF{c}` / `dT{c}`; `d{count}f{c}` | Char-find ranges |
232
+ | `diw` / `daw`; `diW` / `daW` | Inner/around word or WORD |
233
+ | `d{count}iw` / `d{count}iW`; `d{count}aw` / `d{count}aW` | Counted word/WORD text objects |
234
+ | `di"` / `da"` (`'`, <code>`</code>) | Inside/around quotes |
235
+ | `di(` / `da(`, `di[` / `da[`, `di{` / `da{` | Inside/around brackets; aliases `)`, `]`, `}`, `b`, `B` |
198
236
 
199
237
  #### change `c{motion}` / `cc`
200
238
 
201
239
  Same motion and count set as `d`. Deletes text then enters Insert mode.
202
240
 
203
- | command | action |
204
- |-----------------|------------------------------------|
205
- | `cw` | Change `word` + Insert |
206
- | `ce` / `cb` | Change to `word` end / previous `word` start |
207
- | `cW` | Change `WORD` + Insert (`cW` on non-space behaves like `cE`) |
208
- | `cE` / `cB` | Change to `WORD` end / previous `WORD` start |
209
- | `c{count}w/e/b` | Change `{count}` `word` motions + Insert |
210
- | `c{count}W/E/B` | Change `{count}` `WORD` motions + Insert |
211
- | `ciw` | Change inner word |
212
- | `caw` | Change around word |
213
- | `cc` | Delete line content + Insert |
214
- | `c_` | Change line (linewise, same as `cc`) |
215
- | `c{count}_` | Change `{count}` lines (linewise) |
241
+ | command | action |
242
+ |---------|--------|
243
+ | `cw` / `ce` / `cb`; `cW` / `cE` / `cB` | Change word/WORD motion ranges + Insert |
244
+ | `c{count}w/e/b`; `c{count}W/E/B` | Change counted word/WORD motions + Insert |
245
+ | `ciw` / `caw`; `ciW` / `caW` | Change word/WORD text objects + Insert |
246
+ | `c{count}iw` / `c{count}iW`; `c{count}aw` / `c{count}aW` | Change counted word/WORD text objects + Insert |
247
+ | `ci"` / `ca"` (`'`, <code>`</code>) | Change inside/around quotes + Insert |
248
+ | `ci(` / `ca(`, `ci[` / `ca[`, `ci{` / `ca{` | Change inside/around brackets + Insert |
249
+ | `cc` / `c_`; `c{count}_` | Change current or counted whole lines + Insert |
216
250
  | `c$` / `c0` / `c^` | Delete to EOL / BOL / first non-whitespace + Insert |
217
- | … | All `d` motions apply |
251
+ | … | All `d` motions apply |
218
252
 
219
253
  #### single-key edits
220
254
 
@@ -237,33 +271,20 @@ A `{count}` prefix is supported for `x`, `p`, `P`. Maximum: `9999`.
237
271
 
238
272
  Same motion set as `d`. Writes to register, **no text mutation**.
239
273
 
240
- | command | yanks |
241
- |---------|---------------------------------|
242
- | `yy` | Whole line + trailing `\n` |
243
- | `Y` | Whole line + trailing `\n` (same as `yy`) |
244
- | `{count}yy` | `{count}` whole lines + trailing `\n` |
245
- | `{count}Y` | `{count}` whole lines + trailing `\n` (same as `{count}yy`) |
246
- | `y{count}j` | Current line + `{count}` lines below (linewise) |
247
- | `y{count}k` | Current line + `{count}` lines above (linewise) |
248
- | `yG` | Current line to end of buffer (linewise) |
249
- | `yw` | Forward to next `word` start |
250
- | `ye` | To `word` end (inclusive) |
251
- | `yb` | Backward to `word` start |
252
- | `yW` | Forward to next `WORD` start |
253
- | `yE` | To `WORD` end (inclusive) |
254
- | `yB` | Backward to `WORD` start |
255
- | `y$` | To end of line |
256
- | `y0` | To start of line |
257
- | `y^` | To first non-whitespace char of line |
258
- | `y_` | Whole line (linewise, same as `yy`) |
259
- | `y{count}_` | `{count}` whole lines (linewise) |
260
- | `yf{c}` | To and including `char` |
261
- | `yiw` | Inner word |
262
- | `yaw` | Around word (includes spaces) |
263
-
264
- Counted yank caveat: counted `word`/`WORD` yank motions are intentionally not
265
- implemented (`y2w`, `2yw`, `y2W`, `2yW`, etc. cancel the pending operator).
266
- Linewise counted yank (`{count}yy`, `y{count}j/k`) remains supported.
274
+ | command | yanks |
275
+ |---------|-------|
276
+ | `yy` / `Y`; `{count}yy` / `{count}Y` | Whole line(s) + trailing `\n` |
277
+ | `y{count}j` / `y{count}k` / `yG`; `y_` / `y{count}_` | Linewise ranges |
278
+ | `yw` / `ye` / `yb`; `yW` / `yE` / `yB` | word/WORD motion ranges |
279
+ | `y$` / `y0` / `y^`; `yf{c}` | EOL / BOL / first non-whitespace / char-find |
280
+ | `yiw` / `yaw`; `yiW` / `yaW` | Inner/around word or WORD |
281
+ | `yi"` / `ya"` (`'`, <code>`</code>) | Inside/around quotes |
282
+ | `yi(` / `ya(`, `yi[` / `ya[`, `yi{` / `ya{` | Inside/around brackets; aliases `)`, `]`, `}`, `b`, `B` |
283
+
284
+ Counted `word`/`WORD` yank motions and counted yank text objects (`y2w`,
285
+ `2yw`, `y2W`, `2yW`, `y2aw`, `2yaw`, `y2aW`, `y2a{`, …) are intentionally not
286
+ implemented and cancel the pending operator. Linewise counted yank (`{count}yy`,
287
+ `y{count}j/k`) is supported.
267
288
 
268
289
  ---
269
290
 
@@ -276,8 +297,8 @@ Linewise counted yank (`{count}yy`, `y{count}j/k`) remains supported.
276
297
  | `{count}p` | Put `{count}` times after cursor |
277
298
  | `{count}P` | Put `{count}` times before cursor |
278
299
 
279
- Put reads from the **unnamed register** (not OS clipboard).
280
- Line-wise detection: register content ending in `\n` is treated as line-wise.
300
+ Put reads the OS clipboard first, falling back to the internal unnamed-register shadow on slow read.
301
+ Paste text ending in `\n` is treated as line-wise.
281
302
 
282
303
  ---
283
304
 
@@ -295,54 +316,52 @@ Line-wise detection: register content ending in `\n` is treated as line-wise.
295
316
 
296
317
  ## register and clipboard policy
297
318
 
298
- - One unnamed register (like Vim's `""` register).
299
- - Every `d`, `c`, `x`, `s`, `S`, `D`, `C`, `y` operator form
300
- (including `dd`/`d_`, `{count}dd`, `d{count}j/k`, `dG`, `yy`/`y_`, `{count}yy`,
301
- `y{count}j/k`, `yG`) writes to the register and mirrors to the OS clipboard
302
- (via `copyToClipboard`, best-effort).
303
- - `p` / `P` read from the unnamed register only (not the OS clipboard).
304
- - This gives stable behaviour across local terminals and SSH / OSC52 setups.
319
+ - `piVim.clipboardMirror = "all"` is the default: every unnamed-register write mirrors to the OS clipboard best-effort.
320
+ - `piVim.clipboardMirror = "yank"` mirrors yanks only; deletes and changes update only pi-vim's internal shadow.
321
+ - `piVim.clipboardMirror = "never"` disables write mirroring while keeping internal register writes synchronous.
322
+ - Rapid mirrored writes coalesce: only the latest pending value is guaranteed to be mirrored.
323
+ - `p` / `P` read the OS clipboard first, falling back to the shadow on read failure/timeout.
324
+ - While a mirror is in flight, `p` / `P` use the shadow so immediate yank/delete put stays ordered.
325
+ - Pi owns the terminal clipboard backends; on Wayland external state may lag while the shadow stays authoritative for immediate puts.
305
326
 
306
327
  ---
307
328
 
308
329
  ## known differences from full Vim
309
330
 
310
- | area | this extension | full Vim |
311
- |-----------------------|----------------------------------------|-------------------------------|
312
- | `$` motion | Moves past last char (readline CTRL+E) | Moves to last char |
313
- | `w` / `e` / `b` + `W` / `E` / `B` | Cross-line for `word` + `WORD` motions | Cross-line |
314
- | `0` / `$` operators | Exclusive of anchor col | `0` inclusive of col 0 |
315
- | Undo depth | Delegates to underlying readline undo | Full per-change undo tree |
316
- | Redo | Normal-mode `<C-r>` supported (safe no-op when empty; counted redo is stepwise, clamps to available history, and preserves single-step undo granularity) | `<C-r>` |
317
- | Visual mode | Not implemented | `v`, `V`, `<C-v>` |
318
- | Text objects | Supports `iw`/`aw` only | Full text-object set |
319
- | Count prefix | Supported for operators, word/char motions, navigation, and edits (`x`, `r`, `p`/`P`); capped at `MAX_COUNT=9999` to prevent abuse | Full support |
320
- | Named registers | Not implemented (`"a`, etc.) | Supported |
321
- | Macros | Not implemented (`q`, `@`) | Supported |
322
- | Search | Not implemented (`/`, `?`, `n`, `N`) | Supported |
323
- | Ex commands | Not implemented (`:s`, `:g`, etc.) | Supported |
324
- | Multi-line operators | Supports `d/c/y` with `w/e/b` and `W/E/B`, plus `j/k` counts and `G`; not full Vim motion matrix | Rich cross-line semantics |
331
+ | area | this extension | full Vim |
332
+ |------|----------------|----------|
333
+ | `$` motion | Moves past the last char (readline `Ctrl+E`) | Moves to the last char |
334
+ | `w` / `e` / `b` + `W` / `E` / `B` | Cross-line for both `word` and `WORD` motions | Cross-line |
335
+ | `0` / `$` operators | Exclusive of the anchor col | `0` is inclusive of col 0 |
336
+ | Undo / redo | Delegates undo to readline; normal-mode `<C-r>` redo is supported | Full per-change undo tree |
337
+ | Visual mode | Not implemented | `v`, `V`, `<C-v>` |
338
+ | Text objects | `iw` / `aw`, `iW` / `aW`, quote objects, and paren/bracket/brace objects; delimited counts cancel | Full text-object set |
339
+ | Count prefix | Operators, motions, navigation, `x`, `r`, `p`, `P`; capped at `MAX_COUNT=9999` | Full support |
340
+ | Registers / macros / search | Not implemented | Supported |
341
+ | Ex commands | Quit-only EX mini-mode (`:q`, `:q!`, `:qa`, `:qa!`) | Full ex command-line surface |
342
+ | Multi-line operators | `d/c/y` with `w/e/b`, `W/E/B`, `j/k`, and `G`; not the full Vim motion matrix | Rich cross-line semantics |
325
343
 
326
344
  ---
327
345
 
328
346
  ## out of scope
329
347
 
330
- These are **explicitly deferred** and not planned for this feature:
348
+ Explicitly deferred:
331
349
 
332
350
  - Visual modes (`v`, `V`, block visual)
333
- - Extended text objects beyond word (`ip`, `i"`, `i(`, etc.)
334
- - Named registers (`"a`, `"b`, )
335
- - Macros (`q{char}`, `@{char}`)
336
- - Ex command surface (`:s`, `:g`, `:r`, …)
337
- - Search mode (`/`, `?`, `n`, `N`)
338
- - Repeat (`.`)
339
- - Replace mode (`R`) only single-char `r{char}` is supported
340
- - Extended count prefix beyond currently supported motions (e.g. `:`, global operator counts)
341
- - No insert-mode `<C-r>` feature expansion beyond current underlying-editor behavior.
342
- - No cross-session redo persistence.
343
- - No upstream `pi-tui` redo prerequisite in this wave.
344
- - Window / tab / buffer management
345
- - Plugin / runtime ecosystem compatibility
351
+ - Tag text objects (`it`, `at`)
352
+ - Paragraph/sentence text objects (`ip`, `ap`, `is`, `as`)
353
+ - Angle bracket text objects (`i<`, `a<`)
354
+ - Visual-mode text-object selection
355
+ - Parser-aware delimiter matching
356
+ - Delimited-object counts (`d2i"`, `2ci(`, `y2a{`)
357
+ - Named registers (`"a`, `"b`, …), macros (`q{char}`, `@{char}`)
358
+ - Ex surface beyond quit (`:s`, `:g`, `:w`, `:r`, )
359
+ - Search (`/`, `?`, `n`, `N`), repeat (`.`)
360
+ - Replace mode (`R`) — only `r{char}` is supported
361
+ - Count prefix beyond currently supported motions
362
+ - No insert-mode `<C-r>` expansion, no cross-session redo persistence
363
+ - No upstream `pi-tui` redo prerequisite
364
+ - Window / tab / buffer management, plugin ecosystem compatibility
346
365
 
347
366
  ---
348
367
 
@@ -0,0 +1,73 @@
1
+ import { SettingsManager } from "@mariozechner/pi-coding-agent";
2
+
3
+ export type ClipboardMirrorPolicy = "all" | "yank" | "never";
4
+ export type RegisterWriteSource = "mutation" | "yank";
5
+
6
+ export const DEFAULT_CLIPBOARD_MIRROR_POLICY: ClipboardMirrorPolicy = "all";
7
+
8
+ export type PiVimSettings = { clipboardMirror?: unknown };
9
+
10
+ type UnknownRecord = Record<string, unknown>;
11
+
12
+ const missing = Symbol();
13
+
14
+ function formatInvalid(value: unknown) {
15
+ const type = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
16
+ try {
17
+ return `${JSON.stringify(value) ?? type} (type ${type})`;
18
+ } catch {
19
+ return `(type ${type})`;
20
+ }
21
+ }
22
+
23
+ function readSetting(settings: unknown): unknown {
24
+ if (typeof settings !== "object" || settings === null || !Object.hasOwn(settings, "piVim")) return missing;
25
+ const piVim = (settings as UnknownRecord).piVim;
26
+ if (typeof piVim !== "object" || piVim === null || Array.isArray(piVim)) return piVim;
27
+ return Object.hasOwn(piVim, "clipboardMirror") ? (piVim as UnknownRecord).clipboardMirror : missing;
28
+ }
29
+
30
+ export function resolveClipboardMirrorPolicy(value: unknown) {
31
+ if (value === undefined) return { policy: DEFAULT_CLIPBOARD_MIRROR_POLICY };
32
+
33
+ if (typeof value === "string") {
34
+ const policy = value.trim().toLowerCase();
35
+ if (policy === "all" || policy === "yank" || policy === "never") {
36
+ return { policy: policy as ClipboardMirrorPolicy };
37
+ }
38
+ }
39
+
40
+ return {
41
+ policy: DEFAULT_CLIPBOARD_MIRROR_POLICY,
42
+ warning: `Invalid piVim.clipboardMirror ${formatInvalid(value)}; expected all, yank, never. Using all.`,
43
+ };
44
+ }
45
+
46
+ export function readPiVimClipboardMirrorSetting(globalSettings: unknown, projectSettings: unknown): unknown | undefined {
47
+ const project = readSetting(projectSettings);
48
+ if (project !== missing) return project;
49
+ const global = readSetting(globalSettings);
50
+ return global === missing ? undefined : global;
51
+ }
52
+
53
+ function readPiVimSettingsFromDisk(cwd: string): PiVimSettings {
54
+ const settings = SettingsManager.create(cwd);
55
+ return {
56
+ clipboardMirror: readPiVimClipboardMirrorSetting(settings.getGlobalSettings(), settings.getProjectSettings()),
57
+ };
58
+ }
59
+
60
+ let piVimSettingsReader = readPiVimSettingsFromDisk;
61
+
62
+ export function readPiVimSettings(cwd: string) {
63
+ return piVimSettingsReader(cwd);
64
+ }
65
+
66
+ export function setPiVimSettingsReaderForTests(reader: typeof readPiVimSettingsFromDisk) {
67
+ const prev = piVimSettingsReader;
68
+ piVimSettingsReader = reader;
69
+
70
+ return () => {
71
+ piVimSettingsReader = prev;
72
+ };
73
+ }