pi-vim 0.2.1 → 0.3.1
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 +7 -2
- package/index.ts +51 -15
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -34,6 +34,8 @@ u # undo
|
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
Mode indicator (`INSERT` / `NORMAL`) appears at bottom-right.
|
|
37
|
+
Its label is theme-colored: reverse-video `borderMuted` for
|
|
38
|
+
INSERT, `borderAccent` for NORMAL.
|
|
37
39
|
|
|
38
40
|
## why pi-vim
|
|
39
41
|
|
|
@@ -74,7 +76,7 @@ ex-commands, etc.).
|
|
|
74
76
|
| `Esc` / `Ctrl+[` | Normal mode → pass to Pi (abort agent) |
|
|
75
77
|
| `i` | Normal → Insert at cursor |
|
|
76
78
|
| `a` | Normal → Insert after cursor |
|
|
77
|
-
| `I` | Normal → Insert at
|
|
79
|
+
| `I` | Normal → Insert at first non-whitespace |
|
|
78
80
|
| `A` | Normal → Insert at line end |
|
|
79
81
|
| `o` | Normal → open line below + Insert |
|
|
80
82
|
| `O` | Normal → open line above + Insert |
|
|
@@ -238,7 +240,9 @@ Same motion set as `d`. Writes to register, **no text mutation**.
|
|
|
238
240
|
| command | yanks |
|
|
239
241
|
|---------|---------------------------------|
|
|
240
242
|
| `yy` | Whole line + trailing `\n` |
|
|
243
|
+
| `Y` | Whole line + trailing `\n` (same as `yy`) |
|
|
241
244
|
| `{count}yy` | `{count}` whole lines + trailing `\n` |
|
|
245
|
+
| `{count}Y` | `{count}` whole lines + trailing `\n` (same as `{count}yy`) |
|
|
242
246
|
| `y{count}j` | Current line + `{count}` lines below (linewise) |
|
|
243
247
|
| `y{count}k` | Current line + `{count}` lines above (linewise) |
|
|
244
248
|
| `yG` | Current line to end of buffer (linewise) |
|
|
@@ -281,7 +285,8 @@ Line-wise detection: register content ending in `\n` is treated as line-wise.
|
|
|
281
285
|
|
|
282
286
|
| key | action |
|
|
283
287
|
|-----|--------|
|
|
284
|
-
| `u` | Undo in normal mode |
|
|
288
|
+
| `u` | Undo one change in normal mode |
|
|
289
|
+
| `{count}u` | Undo up to `{count}` changes in normal mode; clamps at available history |
|
|
285
290
|
| `Ctrl+_` | Undo in normal mode (alias for `u`) |
|
|
286
291
|
| `<C-r>` | Redo one undone change in normal mode; safe no-op when redo history is empty |
|
|
287
292
|
| `{count}<C-r>` | Redo up to `{count}` undone changes in order; clamps at available history and consumes count state (no leak to the next command) |
|
package/index.ts
CHANGED
|
@@ -136,6 +136,7 @@ export class ModalEditor extends CustomEditor {
|
|
|
136
136
|
private readonly redoStack: EditorSnapshot[] = [];
|
|
137
137
|
private currentTransition: TransitionState = "none";
|
|
138
138
|
private onChangeHooked: boolean = false;
|
|
139
|
+
private readonly labelColorizers: { insert: (s: string) => string; normal: (s: string) => string } | null;
|
|
139
140
|
|
|
140
141
|
// Unnamed register
|
|
141
142
|
private unnamedRegister: string = "";
|
|
@@ -143,6 +144,16 @@ export class ModalEditor extends CustomEditor {
|
|
|
143
144
|
try { copyToClipboard(text); } catch { /* best effort */ }
|
|
144
145
|
};
|
|
145
146
|
|
|
147
|
+
constructor(
|
|
148
|
+
tui: any,
|
|
149
|
+
theme: any,
|
|
150
|
+
kb: any,
|
|
151
|
+
labelColorizers?: { insert: (s: string) => string; normal: (s: string) => string } | null,
|
|
152
|
+
) {
|
|
153
|
+
super(tui, theme, kb);
|
|
154
|
+
this.labelColorizers = labelColorizers ?? null;
|
|
155
|
+
}
|
|
156
|
+
|
|
146
157
|
// Test seams
|
|
147
158
|
setClipboardFn(fn: (text: string) => void): void { this.clipboardFn = fn; }
|
|
148
159
|
getRegister(): string { return this.unnamedRegister; }
|
|
@@ -220,16 +231,22 @@ export class ModalEditor extends CustomEditor {
|
|
|
220
231
|
}
|
|
221
232
|
}
|
|
222
233
|
|
|
223
|
-
private performUndo(): void {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
this.
|
|
231
|
-
|
|
232
|
-
|
|
234
|
+
private performUndo(count: number = this.takeTotalCount(1)): void {
|
|
235
|
+
const maxSteps = Math.max(1, Math.min(MAX_COUNT, count));
|
|
236
|
+
for (let i = 0; i < maxSteps; i++) {
|
|
237
|
+
let changed = false;
|
|
238
|
+
this.withTransition("undo", () => {
|
|
239
|
+
const beforeUndo = this.captureSnapshot();
|
|
240
|
+
super.handleInput(CTRL_UNDERSCORE);
|
|
241
|
+
const afterUndo = this.captureSnapshot();
|
|
242
|
+
|
|
243
|
+
if (this.snapshotChanged(beforeUndo, afterUndo)) {
|
|
244
|
+
this.redoStack.push(beforeUndo);
|
|
245
|
+
changed = true;
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
if (!changed) break;
|
|
249
|
+
}
|
|
233
250
|
}
|
|
234
251
|
|
|
235
252
|
private performRedo(count: number = this.takeTotalCount(1)): void {
|
|
@@ -889,7 +906,11 @@ export class ModalEditor extends CustomEditor {
|
|
|
889
906
|
|| data === "C"
|
|
890
907
|
|| data === "p"
|
|
891
908
|
|| data === "P"
|
|
909
|
+
|| data === "Y"
|
|
892
910
|
|| data === "J"
|
|
911
|
+
|| data === "u"
|
|
912
|
+
|| data === CTRL_UNDERSCORE
|
|
913
|
+
|| matchesKey(data, "ctrl+_")
|
|
893
914
|
|| data === CTRL_R
|
|
894
915
|
|| matchesKey(data, "ctrl+r")
|
|
895
916
|
);
|
|
@@ -996,6 +1017,12 @@ export class ModalEditor extends CustomEditor {
|
|
|
996
1017
|
return;
|
|
997
1018
|
}
|
|
998
1019
|
|
|
1020
|
+
if (data === "Y") {
|
|
1021
|
+
const count = this.takeTotalCount(1);
|
|
1022
|
+
this.yankLinewiseByDelta(count - 1);
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
999
1026
|
if (CHAR_MOTION_KEYS.has(data)) {
|
|
1000
1027
|
this.pendingMotion = data as PendingMotion;
|
|
1001
1028
|
return;
|
|
@@ -1091,7 +1118,7 @@ export class ModalEditor extends CustomEditor {
|
|
|
1091
1118
|
break;
|
|
1092
1119
|
case "I":
|
|
1093
1120
|
this.mode = "insert";
|
|
1094
|
-
|
|
1121
|
+
this.moveCursorToFirstNonWhitespace();
|
|
1095
1122
|
break;
|
|
1096
1123
|
case "o":
|
|
1097
1124
|
this.openLineBelow();
|
|
@@ -2187,10 +2214,14 @@ export class ModalEditor extends CustomEditor {
|
|
|
2187
2214
|
const lines = super.render(width);
|
|
2188
2215
|
if (lines.length === 0) return lines;
|
|
2189
2216
|
|
|
2190
|
-
const
|
|
2217
|
+
const rawLabel = this.getModeLabel();
|
|
2218
|
+
const colorize = this.labelColorizers
|
|
2219
|
+
? (this.mode === "insert" ? this.labelColorizers.insert : this.labelColorizers.normal)
|
|
2220
|
+
: null;
|
|
2221
|
+
const label = colorize ? colorize(rawLabel) : rawLabel;
|
|
2191
2222
|
const last = lines.length - 1;
|
|
2192
|
-
if (visibleWidth(lines[last]!) >=
|
|
2193
|
-
lines[last] = truncateToWidth(lines[last]!, width -
|
|
2223
|
+
if (visibleWidth(lines[last]!) >= visibleWidth(rawLabel)) {
|
|
2224
|
+
lines[last] = truncateToWidth(lines[last]!, width - visibleWidth(rawLabel), "") + label;
|
|
2194
2225
|
}
|
|
2195
2226
|
return lines;
|
|
2196
2227
|
}
|
|
@@ -2225,6 +2256,11 @@ export class ModalEditor extends CustomEditor {
|
|
|
2225
2256
|
|
|
2226
2257
|
export default function (pi: ExtensionAPI) {
|
|
2227
2258
|
pi.on("session_start", (_event, ctx) => {
|
|
2228
|
-
ctx.ui.
|
|
2259
|
+
const t = ctx.ui.theme;
|
|
2260
|
+
const colorizers = t ? {
|
|
2261
|
+
insert: (s: string) => t.fg("borderMuted", `\x1b[7m${s}\x1b[27m`),
|
|
2262
|
+
normal: (s: string) => t.fg("borderAccent", `\x1b[7m${s}\x1b[27m`),
|
|
2263
|
+
} : null;
|
|
2264
|
+
ctx.ui.setEditorComponent((tui, theme, kb) => new ModalEditor(tui, theme, kb, colorizers));
|
|
2229
2265
|
});
|
|
2230
2266
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-vim",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Vim-style modal editing for Pi's TUI editor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"license": "MIT",
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
15
|
-
"url": "
|
|
15
|
+
"url": "https://github.com/lajarre/pi-vim.git"
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
18
|
"*.ts",
|