pi-vim 0.3.2 → 0.9.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 +182 -169
- package/clipboard-policy.ts +73 -0
- package/index.ts +1019 -185
- package/motions.ts +14 -4
- package/package.json +7 -3
- package/text-objects.ts +303 -0
- package/word-boundary-cache.ts +7 -7
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,52 @@ 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 which local register writes cross the OS clipboard boundary. `p` / `P` keep non-mirrored writes local.
|
|
34
|
+
|
|
35
|
+
## wrapping pi-vim
|
|
36
|
+
|
|
37
|
+
Supported: `pi-vim` first, `@jordyvd/pi-image-attachments` second. pi-vim does not wrap previous editors; wrappers decorate in place or forward the CustomEditor surface: lifecycle (`handleInput`, `render`, `invalidate`), text (`getText`, `setText`, `insertTextAtCursor`, `getExpandedText`), callbacks, `actionHandlers`, flags, reads (`getLines`, `getCursor`, `getMode()`). Inverse order, insert delegates, and generic composition are unsupported.
|
|
38
|
+
|
|
39
|
+
Smoke:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pi -e ./index.ts -e ../pi-image-attachments/index.ts
|
|
43
|
+
pi -e ./index.ts -e ../../../pi-image-attachments/index.ts
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Check: insert text; add/paste image path; see `[Image #1]`; submit text+image stripped; switch INSERT/NORMAL.
|
|
47
|
+
|
|
48
|
+
## contributor setup
|
|
49
|
+
|
|
50
|
+
Hooks install with `npm install` after cloning. To wire them explicitly:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm run hooks:install
|
|
54
|
+
```
|
|
55
|
+
|
|
17
56
|
## stats
|
|
18
57
|
|
|
19
|
-
- **
|
|
58
|
+
- **188 commands**: motions, operators, counts, text objects, undo/redo, ex quit
|
|
20
59
|
- **sub-µs word motions** via precomputed boundary cache (~4ms startup, ~150KB memory)
|
|
21
60
|
- **0 dependencies**
|
|
22
61
|
|
|
@@ -33,22 +72,19 @@ u # undo
|
|
|
33
72
|
2} # jump two paragraphs forward
|
|
34
73
|
```
|
|
35
74
|
|
|
36
|
-
Mode indicator (`INSERT` / `NORMAL`) appears
|
|
37
|
-
|
|
38
|
-
|
|
75
|
+
Mode indicator (`INSERT` / `NORMAL` / `EX`) appears bottom-right, theme-colored.
|
|
76
|
+
|
|
77
|
+
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
78
|
|
|
40
79
|
## why pi-vim
|
|
41
80
|
|
|
42
81
|
- Fast modal editing without leaving Pi.
|
|
43
82
|
- Count-aware motions/operators (`2dw`, `3G`, `d2j`, `2}`).
|
|
44
|
-
-
|
|
83
|
+
- REPL-focused defaults; out-of-scope boundaries documented.
|
|
45
84
|
- Clipboard/register behavior is explicit and tested.
|
|
46
85
|
|
|
47
|
-
|
|
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.).
|
|
86
|
+
Use pi-vim for fast Vim muscle-memory in Pi prompts. Skip it if you need
|
|
87
|
+
full Vim parity (visual mode, macros, search, extended ex-commands, …).
|
|
52
88
|
|
|
53
89
|
## common recipes
|
|
54
90
|
|
|
@@ -56,6 +92,11 @@ ex-commands, etc.).
|
|
|
56
92
|
|------|------|
|
|
57
93
|
| Jump to exact line 25 | `25gg` (or `25G`) |
|
|
58
94
|
| Delete two words | `2dw` |
|
|
95
|
+
| Change current whitespace-delimited WORD | `ciW` |
|
|
96
|
+
| Delete WORD plus adjacent whitespace | `daW` |
|
|
97
|
+
| Change inside double quotes | `ci"` |
|
|
98
|
+
| Delete inside parentheses | `di(` |
|
|
99
|
+
| Yank braces with contents | `ya{` |
|
|
59
100
|
| Change to end of line | `C` |
|
|
60
101
|
| Delete current + 2 lines below | `d2j` |
|
|
61
102
|
| Yank 3 lines | `3yy` |
|
|
@@ -73,7 +114,8 @@ ex-commands, etc.).
|
|
|
73
114
|
| key | action |
|
|
74
115
|
|----------|----------------------------------------|
|
|
75
116
|
| `Esc` / `Ctrl+[` | Insert → Normal mode |
|
|
76
|
-
| `Esc` / `Ctrl+[` | Normal mode → pass to Pi (
|
|
117
|
+
| `Esc` / `Ctrl+[` | Normal mode → pass to Pi (aborts the agent under default Pi keybindings) |
|
|
118
|
+
| `:` | Normal → EX mini-mode |
|
|
77
119
|
| `i` | Normal → Insert at cursor |
|
|
78
120
|
| `a` | Normal → Insert after cursor |
|
|
79
121
|
| `I` | Normal → Insert at first non-whitespace |
|
|
@@ -81,6 +123,24 @@ ex-commands, etc.).
|
|
|
81
123
|
| `o` | Normal → open line below + Insert |
|
|
82
124
|
| `O` | Normal → open line above + Insert |
|
|
83
125
|
|
|
126
|
+
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.
|
|
127
|
+
|
|
128
|
+
#### ex mini-mode
|
|
129
|
+
|
|
130
|
+
Quit-only ex flows.
|
|
131
|
+
|
|
132
|
+
| key / command | action |
|
|
133
|
+
|---------------|--------|
|
|
134
|
+
| `:` | Enter EX mini-mode |
|
|
135
|
+
| `Enter` | Execute pending ex command |
|
|
136
|
+
| `Esc` | Cancel EX mini-mode |
|
|
137
|
+
| `Backspace` / `Ctrl+h` | Delete one ex-command character; on bare `:` exits EX mode |
|
|
138
|
+
| `:q` | Quit the current Pi session only when the prompt is empty or whitespace-only; otherwise show a warning |
|
|
139
|
+
| `:q!` | Force quit the current Pi session even when the prompt has text |
|
|
140
|
+
| `:qa` | Same safe quit policy as `:q` |
|
|
141
|
+
| `:qa!` | Same force quit policy as `:q!` |
|
|
142
|
+
| unsupported `:{cmd}` | Show warning notification; no quit |
|
|
143
|
+
|
|
84
144
|
Insert-mode shortcuts (stay in Insert mode):
|
|
85
145
|
|
|
86
146
|
| key | action |
|
|
@@ -94,48 +154,18 @@ Insert-mode shortcuts (stay in Insert mode):
|
|
|
94
154
|
|
|
95
155
|
### navigation (normal mode)
|
|
96
156
|
|
|
97
|
-
A `{count}` prefix can be prepended to
|
|
98
|
-
|
|
99
|
-
| key
|
|
100
|
-
|
|
101
|
-
| `h`
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
104
|
-
| `
|
|
105
|
-
| `
|
|
106
|
-
| `{count}
|
|
107
|
-
|
|
108
|
-
|
|
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.
|
|
157
|
+
A `{count}` prefix can be prepended to navigation keys (max: `9999`).
|
|
158
|
+
|
|
159
|
+
| key | action |
|
|
160
|
+
|-----|--------|
|
|
161
|
+
| `h` / `l` / `j` / `k`; `{count}h/l/j/k` | Move left/right/down/up; line moves clamp to the buffer |
|
|
162
|
+
| `0` / `^` / `_` / `$` | Line start / first non-whitespace / counted first non-whitespace / line end |
|
|
163
|
+
| `gg` / `G`; `{count}gg` / `{count}G` | Buffer start/end or absolute 1-indexed line |
|
|
164
|
+
| `w` / `b` / `e`; `{count}w/b/e` | `word` start/back/end motions |
|
|
165
|
+
| `W` / `B` / `E`; `{count}W/B/E` | whitespace-delimited `WORD` motions |
|
|
166
|
+
| `{` / `}`; `{count}{` / `{count}}` | Previous/next paragraph start |
|
|
167
|
+
|
|
168
|
+
`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
169
|
|
|
140
170
|
---
|
|
141
171
|
|
|
@@ -159,62 +189,60 @@ Char-find motions compose with operators: `df{char}`, `ct{char}`, `d{count}t{cha
|
|
|
159
189
|
|
|
160
190
|
### edit operators (normal mode)
|
|
161
191
|
|
|
162
|
-
|
|
163
|
-
|
|
192
|
+
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).
|
|
193
|
+
|
|
194
|
+
#### text objects
|
|
195
|
+
|
|
196
|
+
Text objects compose as `d`/`c`/`y` + `i`/`a` + object. `i` means inner; `a` means around.
|
|
197
|
+
|
|
198
|
+
| object | keys | range |
|
|
199
|
+
|--------|------|-------|
|
|
200
|
+
| word | `iw` / `aw` | Keyword word; `aw` includes spaces |
|
|
201
|
+
| WORD | `iW` / `aW` | Line-local whitespace-delimited WORD; `aW` includes adjacent whitespace |
|
|
202
|
+
| quotes | `i"` / `a"`, `i'` / `a'`, <code>i`</code> / <code>a`</code> | Smallest containing quote pair on the line |
|
|
203
|
+
| parentheses | `i(` / `a(`; aliases `i)` / `a)`, `ib` / `ab` | Smallest containing pair |
|
|
204
|
+
| brackets | `i[` / `a[`; aliases `i]` / `a]` | Smallest containing pair |
|
|
205
|
+
| braces | `i{` / `a{`; aliases `i}` / `a}`, `iB` / `aB` | Smallest containing pair |
|
|
206
|
+
|
|
207
|
+
Semantics:
|
|
208
|
+
- WORD objects are line-local and whitespace-delimited.
|
|
209
|
+
- Quote objects are line-local; odd-backslash escapes are ignored; `a` includes delimiters only, not surrounding whitespace.
|
|
210
|
+
- Bracket objects are buffer-aware, nested, lexical, and not parser-aware; brackets inside strings/comments still count.
|
|
211
|
+
- Empty inner delimiter objects no-op for delete/yank; change enters Insert at the inner start without writing the register.
|
|
212
|
+
- Delimited counts cancel (`d2i"`, `2ci(`, `y2a{`). Counted word/WORD text objects work for delete/change only; counted yank text objects cancel.
|
|
164
213
|
|
|
165
214
|
#### delete `d{motion}` / `dd`
|
|
166
215
|
|
|
167
|
-
A `{count}` or dual-count prefix (`{pfx}d{op}{motion}`) is supported for
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
| command
|
|
171
|
-
|
|
172
|
-
| `dw`
|
|
173
|
-
| `
|
|
174
|
-
| `
|
|
175
|
-
| `
|
|
176
|
-
| `
|
|
177
|
-
| `
|
|
178
|
-
| `d{count}
|
|
179
|
-
| `
|
|
180
|
-
| `
|
|
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 |
|
|
216
|
+
A `{count}` or dual-count prefix (`{pfx}d{op}{motion}`) is supported for word,
|
|
217
|
+
WORD, char-find, and linewise motions. Maximum total count: `9999`.
|
|
218
|
+
|
|
219
|
+
| command | deletes |
|
|
220
|
+
|---------|---------|
|
|
221
|
+
| `dw` / `de` / `db`; `dW` / `dE` / `dB` | word/WORD motion ranges; `{count}` repeats |
|
|
222
|
+
| `d$` / `d0` / `d^` | To EOL / BOL / first non-whitespace |
|
|
223
|
+
| `d_` / `dd`; `d{count}_` / `{count}dd` | Current or counted whole lines |
|
|
224
|
+
| `d{count}j` / `d{count}k` / `dG` | Linewise down/up/to EOF |
|
|
225
|
+
| `df{c}` / `dt{c}` / `dF{c}` / `dT{c}`; `d{count}f{c}` | Char-find ranges |
|
|
226
|
+
| `diw` / `daw`; `diW` / `daW` | Inner/around word or WORD |
|
|
227
|
+
| `d{count}iw` / `d{count}iW`; `d{count}aw` / `d{count}aW` | Counted word/WORD text objects |
|
|
228
|
+
| `di"` / `da"` (`'`, <code>`</code>) | Inside/around quotes |
|
|
229
|
+
| `di(` / `da(`, `di[` / `da[`, `di{` / `da{` | Inside/around brackets; aliases `)`, `]`, `}`, `b`, `B` |
|
|
198
230
|
|
|
199
231
|
#### change `c{motion}` / `cc`
|
|
200
232
|
|
|
201
233
|
Same motion and count set as `d`. Deletes text then enters Insert mode.
|
|
202
234
|
|
|
203
|
-
| command
|
|
204
|
-
|
|
205
|
-
| `cw`
|
|
206
|
-
| `
|
|
207
|
-
| `
|
|
208
|
-
| `
|
|
209
|
-
| `
|
|
210
|
-
| `
|
|
211
|
-
| `
|
|
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) |
|
|
235
|
+
| command | action |
|
|
236
|
+
|---------|--------|
|
|
237
|
+
| `cw` / `ce` / `cb`; `cW` / `cE` / `cB` | Change word/WORD motion ranges + Insert |
|
|
238
|
+
| `c{count}w/e/b`; `c{count}W/E/B` | Change counted word/WORD motions + Insert |
|
|
239
|
+
| `ciw` / `caw`; `ciW` / `caW` | Change word/WORD text objects + Insert |
|
|
240
|
+
| `c{count}iw` / `c{count}iW`; `c{count}aw` / `c{count}aW` | Change counted word/WORD text objects + Insert |
|
|
241
|
+
| `ci"` / `ca"` (`'`, <code>`</code>) | Change inside/around quotes + Insert |
|
|
242
|
+
| `ci(` / `ca(`, `ci[` / `ca[`, `ci{` / `ca{` | Change inside/around brackets + Insert |
|
|
243
|
+
| `cc` / `c_`; `c{count}_` | Change current or counted whole lines + Insert |
|
|
216
244
|
| `c$` / `c0` / `c^` | Delete to EOL / BOL / first non-whitespace + Insert |
|
|
217
|
-
| …
|
|
245
|
+
| … | All `d` motions apply |
|
|
218
246
|
|
|
219
247
|
#### single-key edits
|
|
220
248
|
|
|
@@ -237,33 +265,20 @@ A `{count}` prefix is supported for `x`, `p`, `P`. Maximum: `9999`.
|
|
|
237
265
|
|
|
238
266
|
Same motion set as `d`. Writes to register, **no text mutation**.
|
|
239
267
|
|
|
240
|
-
| command | yanks
|
|
241
|
-
|
|
242
|
-
| `yy`
|
|
243
|
-
| `
|
|
244
|
-
| `
|
|
245
|
-
| `
|
|
246
|
-
| `
|
|
247
|
-
| `
|
|
248
|
-
| `
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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.
|
|
268
|
+
| command | yanks |
|
|
269
|
+
|---------|-------|
|
|
270
|
+
| `yy` / `Y`; `{count}yy` / `{count}Y` | Whole line(s) + trailing `\n` |
|
|
271
|
+
| `y{count}j` / `y{count}k` / `yG`; `y_` / `y{count}_` | Linewise ranges |
|
|
272
|
+
| `yw` / `ye` / `yb`; `yW` / `yE` / `yB` | word/WORD motion ranges |
|
|
273
|
+
| `y$` / `y0` / `y^`; `yf{c}` | EOL / BOL / first non-whitespace / char-find |
|
|
274
|
+
| `yiw` / `yaw`; `yiW` / `yaW` | Inner/around word or WORD |
|
|
275
|
+
| `yi"` / `ya"` (`'`, <code>`</code>) | Inside/around quotes |
|
|
276
|
+
| `yi(` / `ya(`, `yi[` / `ya[`, `yi{` / `ya{` | Inside/around brackets; aliases `)`, `]`, `}`, `b`, `B` |
|
|
277
|
+
|
|
278
|
+
Counted `word`/`WORD` yank motions and counted yank text objects (`y2w`,
|
|
279
|
+
`2yw`, `y2W`, `2yW`, `y2aw`, `2yaw`, `y2aW`, `y2a{`, …) are intentionally not
|
|
280
|
+
implemented and cancel the pending operator. Linewise counted yank (`{count}yy`,
|
|
281
|
+
`y{count}j/k`) is supported.
|
|
267
282
|
|
|
268
283
|
---
|
|
269
284
|
|
|
@@ -276,8 +291,7 @@ Linewise counted yank (`{count}yy`, `y{count}j/k`) remains supported.
|
|
|
276
291
|
| `{count}p` | Put `{count}` times after cursor |
|
|
277
292
|
| `{count}P` | Put `{count}` times before cursor |
|
|
278
293
|
|
|
279
|
-
Put reads
|
|
280
|
-
Line-wise detection: register content ending in `\n` is treated as line-wise.
|
|
294
|
+
Put reads the OS clipboard first unless the last local register write was not mirrored. Paste text ending in `\n` is line-wise.
|
|
281
295
|
|
|
282
296
|
---
|
|
283
297
|
|
|
@@ -295,54 +309,53 @@ Line-wise detection: register content ending in `\n` is treated as line-wise.
|
|
|
295
309
|
|
|
296
310
|
## register and clipboard policy
|
|
297
311
|
|
|
298
|
-
-
|
|
299
|
-
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
- `p` / `P`
|
|
304
|
-
-
|
|
312
|
+
- `piVim.clipboardMirror = "all"` is the default: every unnamed-register write mirrors to the OS clipboard best-effort.
|
|
313
|
+
- `piVim.clipboardMirror = "yank"` mirrors yanks only; deletes and changes update only pi-vim's internal shadow.
|
|
314
|
+
- `piVim.clipboardMirror = "never"` disables write mirroring while keeping internal register writes synchronous.
|
|
315
|
+
- Rapid mirrored writes coalesce: only the latest pending value is guaranteed to be mirrored.
|
|
316
|
+
- `p` / `P` read the OS clipboard first when no local write was skipped by policy, falling back to the shadow on read failure/timeout.
|
|
317
|
+
- If policy skipped the last local write, `p` / `P` use the shadow so delete/yank → put works without touching the OS clipboard.
|
|
318
|
+
- While a mirror is in flight, `p` / `P` use the shadow so immediate yank/delete → put stays ordered.
|
|
319
|
+
- Pi owns the terminal clipboard backends; on Wayland external state may lag while the shadow stays authoritative for immediate puts.
|
|
305
320
|
|
|
306
321
|
---
|
|
307
322
|
|
|
308
323
|
## known differences from full Vim
|
|
309
324
|
|
|
310
|
-
| area
|
|
311
|
-
|
|
312
|
-
| `$` motion
|
|
313
|
-
| `w` / `e` / `b` + `W` / `E` / `B` | Cross-line for `word`
|
|
314
|
-
| `0` / `$` operators
|
|
315
|
-
| Undo
|
|
316
|
-
|
|
|
317
|
-
|
|
|
318
|
-
|
|
|
319
|
-
|
|
|
320
|
-
|
|
|
321
|
-
|
|
|
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 |
|
|
325
|
+
| area | this extension | full Vim |
|
|
326
|
+
|------|----------------|----------|
|
|
327
|
+
| `$` motion | Moves past the last char (readline `Ctrl+E`) | Moves to the last char |
|
|
328
|
+
| `w` / `e` / `b` + `W` / `E` / `B` | Cross-line for both `word` and `WORD` motions | Cross-line |
|
|
329
|
+
| `0` / `$` operators | Exclusive of the anchor col | `0` is inclusive of col 0 |
|
|
330
|
+
| Undo / redo | Delegates undo to readline; normal-mode `<C-r>` redo is supported | Full per-change undo tree |
|
|
331
|
+
| Visual mode | Not implemented | `v`, `V`, `<C-v>` |
|
|
332
|
+
| Text objects | `iw` / `aw`, `iW` / `aW`, quote objects, and paren/bracket/brace objects; delimited counts cancel | Full text-object set |
|
|
333
|
+
| Count prefix | Operators, motions, navigation, `x`, `r`, `p`, `P`; capped at `MAX_COUNT=9999` | Full support |
|
|
334
|
+
| Registers / macros / search | Not implemented | Supported |
|
|
335
|
+
| Ex commands | Quit-only EX mini-mode (`:q`, `:q!`, `:qa`, `:qa!`) | Full ex command-line surface |
|
|
336
|
+
| 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
337
|
|
|
326
338
|
---
|
|
327
339
|
|
|
328
340
|
## out of scope
|
|
329
341
|
|
|
330
|
-
|
|
342
|
+
Explicitly deferred:
|
|
331
343
|
|
|
332
344
|
- Visual modes (`v`, `V`, block visual)
|
|
333
|
-
-
|
|
334
|
-
-
|
|
335
|
-
-
|
|
336
|
-
-
|
|
337
|
-
-
|
|
338
|
-
-
|
|
339
|
-
-
|
|
340
|
-
-
|
|
341
|
-
-
|
|
342
|
-
-
|
|
343
|
-
-
|
|
344
|
-
-
|
|
345
|
-
-
|
|
345
|
+
- Tag text objects (`it`, `at`)
|
|
346
|
+
- Paragraph/sentence text objects (`ip`, `ap`, `is`, `as`)
|
|
347
|
+
- Angle bracket text objects (`i<`, `a<`)
|
|
348
|
+
- Visual-mode text-object selection
|
|
349
|
+
- Parser-aware delimiter matching
|
|
350
|
+
- Delimited-object counts (`d2i"`, `2ci(`, `y2a{`)
|
|
351
|
+
- Named registers (`"a`, `"b`, …), macros (`q{char}`, `@{char}`)
|
|
352
|
+
- Ex surface beyond quit (`:s`, `:g`, `:w`, `:r`, …)
|
|
353
|
+
- Search (`/`, `?`, `n`, `N`), repeat (`.`)
|
|
354
|
+
- Replace mode (`R`) — only `r{char}` is supported
|
|
355
|
+
- Count prefix beyond currently supported motions
|
|
356
|
+
- No insert-mode `<C-r>` expansion, no cross-session redo persistence
|
|
357
|
+
- No upstream `pi-tui` redo prerequisite
|
|
358
|
+
- Window / tab / buffer management, plugin ecosystem compatibility
|
|
346
359
|
|
|
347
360
|
---
|
|
348
361
|
|
|
@@ -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
|
+
}
|