pi-vim 0.1.9 → 0.2.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 +13 -9
- package/index.ts +254 -44
- package/package.json +1 -1
- package/types.ts +1 -0
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@ Esc # NORMAL mode
|
|
|
41
41
|
3gg # jump to absolute line 3
|
|
42
42
|
2dw # delete two words
|
|
43
43
|
u # undo
|
|
44
|
+
<C-r> # redo last undone edit (safe no-op when empty)
|
|
44
45
|
2} # jump two paragraphs forward
|
|
45
46
|
```
|
|
46
47
|
|
|
@@ -71,6 +72,7 @@ ex-commands, etc.).
|
|
|
71
72
|
| Join 3 lines with spacing | `3J` |
|
|
72
73
|
| Jump 2 paragraphs forward | `2}` |
|
|
73
74
|
| Undo last edit | `u` |
|
|
75
|
+
| Redo last undone edit | `<C-r>` |
|
|
74
76
|
|
|
75
77
|
---
|
|
76
78
|
|
|
@@ -278,13 +280,14 @@ Line-wise detection: register content ending in `\n` is treated as line-wise.
|
|
|
278
280
|
|
|
279
281
|
---
|
|
280
282
|
|
|
281
|
-
### Undo
|
|
283
|
+
### Undo / Redo
|
|
282
284
|
|
|
283
|
-
| Key | Action
|
|
284
|
-
|
|
285
|
-
| `u` | Undo
|
|
286
|
-
|
|
287
|
-
|
|
285
|
+
| Key | Action |
|
|
286
|
+
|-----|--------|
|
|
287
|
+
| `u` | Undo in normal mode |
|
|
288
|
+
| `Ctrl+_` | Undo in normal mode (alias for `u`) |
|
|
289
|
+
| `<C-r>` | Redo one undone change in normal mode; safe no-op when redo history is empty |
|
|
290
|
+
| `{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) |
|
|
288
291
|
|
|
289
292
|
---
|
|
290
293
|
|
|
@@ -308,7 +311,7 @@ Redo (`<C-r>`) is **not implemented** — see [Out of scope](#out-of-scope).
|
|
|
308
311
|
| `w` / `e` / `b` + `W` / `E` / `B` | Cross-line for `word` + `WORD` motions | Cross-line |
|
|
309
312
|
| `0` / `$` operators | Exclusive of anchor col | `0` inclusive of col 0 |
|
|
310
313
|
| Undo depth | Delegates to underlying readline undo | Full per-change undo tree |
|
|
311
|
-
| Redo |
|
|
314
|
+
| 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>` |
|
|
312
315
|
| Visual mode | Not implemented | `v`, `V`, `<C-v>` |
|
|
313
316
|
| Text objects | Supports `iw`/`aw` only | Full text-object set |
|
|
314
317
|
| Count prefix | Supported for operators, word/char motions, navigation, and edits (`x`, `p`/`P`); capped at `MAX_COUNT=9999` to prevent abuse | Full support |
|
|
@@ -332,8 +335,9 @@ These are **explicitly deferred** and not planned for this feature:
|
|
|
332
335
|
- Search mode (`/`, `?`, `n`, `N`)
|
|
333
336
|
- Repeat (`.`)
|
|
334
337
|
- Extended count prefix beyond currently supported motions (e.g. `:`, global operator counts)
|
|
335
|
-
-
|
|
336
|
-
|
|
338
|
+
- No insert-mode `<C-r>` feature expansion beyond current underlying-editor behavior.
|
|
339
|
+
- No cross-session redo persistence.
|
|
340
|
+
- No upstream `pi-tui` redo prerequisite in this wave.
|
|
337
341
|
- Window / tab / buffer management
|
|
338
342
|
- Plugin / runtime ecosystem compatibility
|
|
339
343
|
|
package/index.ts
CHANGED
|
@@ -77,6 +77,7 @@ import {
|
|
|
77
77
|
CTRL_A,
|
|
78
78
|
CTRL_E,
|
|
79
79
|
CTRL_K,
|
|
80
|
+
CTRL_R,
|
|
80
81
|
CTRL_UNDERSCORE,
|
|
81
82
|
NEWLINE,
|
|
82
83
|
ESC_DOWN,
|
|
@@ -99,6 +100,24 @@ const BRACKETED_PASTE_END = "\x1b[201~";
|
|
|
99
100
|
const BRACKETED_PASTE_END_TAIL = BRACKETED_PASTE_END.slice(1);
|
|
100
101
|
const MAX_COUNT = 9999;
|
|
101
102
|
|
|
103
|
+
type EditorSnapshot = {
|
|
104
|
+
text: string;
|
|
105
|
+
cursor: { line: number; col: number };
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
type TransitionState = "none" | "undo" | "redo";
|
|
109
|
+
|
|
110
|
+
type ModalEditorInternals = {
|
|
111
|
+
state?: { lines?: string[]; cursorLine?: number; cursorCol?: number };
|
|
112
|
+
preferredVisualCol?: number | null;
|
|
113
|
+
lastAction?: string | null;
|
|
114
|
+
historyIndex?: number;
|
|
115
|
+
onChange?: (text: string) => void;
|
|
116
|
+
tui?: { requestRender?: () => void };
|
|
117
|
+
pushUndoSnapshot?: () => void;
|
|
118
|
+
setCursorCol?: (col: number) => void;
|
|
119
|
+
};
|
|
120
|
+
|
|
102
121
|
export class ModalEditor extends CustomEditor {
|
|
103
122
|
private mode: Mode = "insert";
|
|
104
123
|
private pendingMotion: PendingMotion = null;
|
|
@@ -111,7 +130,10 @@ export class ModalEditor extends CustomEditor {
|
|
|
111
130
|
private lastCharMotion: LastCharMotion | null = null;
|
|
112
131
|
private discardingBracketedPasteInNormalMode: boolean = false;
|
|
113
132
|
private pendingEscWhileDiscardingBracketedPasteInNormalMode: boolean = false;
|
|
114
|
-
private
|
|
133
|
+
private wordBoundaryCache = new WordBoundaryCache();
|
|
134
|
+
private readonly redoStack: EditorSnapshot[] = [];
|
|
135
|
+
private currentTransition: TransitionState = "none";
|
|
136
|
+
private onChangeHooked: boolean = false;
|
|
115
137
|
|
|
116
138
|
// Unnamed register
|
|
117
139
|
private unnamedRegister: string = "";
|
|
@@ -126,6 +148,183 @@ export class ModalEditor extends CustomEditor {
|
|
|
126
148
|
getMode(): Mode { return this.mode; }
|
|
127
149
|
getText(): string { return this.getLines().join("\n"); }
|
|
128
150
|
|
|
151
|
+
override setText(text: string): void {
|
|
152
|
+
this.clearRedoStack();
|
|
153
|
+
super.setText(text);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private captureSnapshot(): EditorSnapshot {
|
|
157
|
+
const cursor = this.getCursor();
|
|
158
|
+
return {
|
|
159
|
+
text: this.getText(),
|
|
160
|
+
cursor: { line: cursor.line, col: cursor.col },
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private requireRedoRestoreState(
|
|
165
|
+
editor: ModalEditorInternals,
|
|
166
|
+
): { lines: string[]; cursorLine?: number; cursorCol?: number } {
|
|
167
|
+
const state = editor.state;
|
|
168
|
+
if (!state || !Array.isArray(state.lines)) {
|
|
169
|
+
throw new Error("Redo restore prerequisite: editor state unavailable");
|
|
170
|
+
}
|
|
171
|
+
return state;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private restoreSnapshot(snapshot: EditorSnapshot): void {
|
|
175
|
+
const editor = this as unknown as ModalEditorInternals;
|
|
176
|
+
const state = this.requireRedoRestoreState(editor);
|
|
177
|
+
|
|
178
|
+
const lines = snapshot.text.split("\n");
|
|
179
|
+
state.lines = lines.length > 0 ? lines : [""];
|
|
180
|
+
|
|
181
|
+
const maxLine = Math.max(0, state.lines.length - 1);
|
|
182
|
+
const cursorLine = Math.max(0, Math.min(snapshot.cursor.line, maxLine));
|
|
183
|
+
const line = state.lines[cursorLine] ?? "";
|
|
184
|
+
const cursorCol = Math.max(0, Math.min(snapshot.cursor.col, line.length));
|
|
185
|
+
|
|
186
|
+
state.cursorLine = cursorLine;
|
|
187
|
+
if (typeof editor.setCursorCol === "function") {
|
|
188
|
+
editor.setCursorCol(cursorCol);
|
|
189
|
+
} else {
|
|
190
|
+
state.cursorCol = cursorCol;
|
|
191
|
+
editor.preferredVisualCol = null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
this.invalidateWordBoundaryCache();
|
|
195
|
+
|
|
196
|
+
editor.historyIndex = -1;
|
|
197
|
+
editor.lastAction = null;
|
|
198
|
+
editor.onChange?.(this.getText());
|
|
199
|
+
editor.tui?.requestRender?.();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private snapshotChanged(a: EditorSnapshot, b: EditorSnapshot): boolean {
|
|
203
|
+
return a.text !== b.text
|
|
204
|
+
|| a.cursor.line !== b.cursor.line
|
|
205
|
+
|| a.cursor.col !== b.cursor.col;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private withTransition<T>(
|
|
209
|
+
transition: Exclude<TransitionState, "none">,
|
|
210
|
+
action: () => T,
|
|
211
|
+
): T {
|
|
212
|
+
const previousTransition = this.currentTransition;
|
|
213
|
+
this.currentTransition = transition;
|
|
214
|
+
try {
|
|
215
|
+
return action();
|
|
216
|
+
} finally {
|
|
217
|
+
this.currentTransition = previousTransition;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private performUndo(): void {
|
|
222
|
+
this.withTransition("undo", () => {
|
|
223
|
+
const beforeUndo = this.captureSnapshot();
|
|
224
|
+
super.handleInput(CTRL_UNDERSCORE);
|
|
225
|
+
const afterUndo = this.captureSnapshot();
|
|
226
|
+
|
|
227
|
+
if (this.snapshotChanged(beforeUndo, afterUndo)) {
|
|
228
|
+
this.redoStack.push(beforeUndo);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private performRedo(count: number = this.takeTotalCount(1)): void {
|
|
234
|
+
const maxSteps = Math.max(1, Math.min(MAX_COUNT, count));
|
|
235
|
+
const editor = this as unknown as ModalEditorInternals;
|
|
236
|
+
|
|
237
|
+
for (let i = 0; i < maxSteps; i++) {
|
|
238
|
+
const snapshot = this.redoStack[this.redoStack.length - 1];
|
|
239
|
+
if (!snapshot) break;
|
|
240
|
+
|
|
241
|
+
this.withTransition("redo", () => {
|
|
242
|
+
this.requireRedoRestoreState(editor);
|
|
243
|
+
if (typeof editor.pushUndoSnapshot !== "function") {
|
|
244
|
+
throw new Error(
|
|
245
|
+
"Redo restore prerequisite: pushUndoSnapshot unavailable",
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
editor.pushUndoSnapshot();
|
|
249
|
+
this.restoreSnapshot(snapshot);
|
|
250
|
+
this.redoStack.pop();
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private clearRedoStack(): void {
|
|
256
|
+
this.redoStack.length = 0;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private invalidateWordBoundaryCache(): void {
|
|
260
|
+
this.wordBoundaryCache = new WordBoundaryCache();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private ensureOnChangeHook(): void {
|
|
264
|
+
if (this.onChangeHooked) return;
|
|
265
|
+
|
|
266
|
+
const editor = this as unknown as ModalEditorInternals;
|
|
267
|
+
const originalOnChange = editor.onChange;
|
|
268
|
+
|
|
269
|
+
editor.onChange = (text: string) => {
|
|
270
|
+
originalOnChange?.(text);
|
|
271
|
+
this.centralInvalidationCheck();
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
this.onChangeHooked = true;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private centralInvalidationCheck(): void {
|
|
278
|
+
if (this.redoStack.length === 0) return;
|
|
279
|
+
if (this.currentTransition !== "none") return;
|
|
280
|
+
this.clearRedoStack();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private applySyntheticEdit(mutation: () => void): void {
|
|
284
|
+
const editor = this as unknown as ModalEditorInternals;
|
|
285
|
+
if (!editor.state || !Array.isArray(editor.state.lines)) {
|
|
286
|
+
throw new Error(
|
|
287
|
+
"Synthetic edit prerequisite: editor state unavailable",
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (typeof editor.pushUndoSnapshot !== "function") {
|
|
292
|
+
throw new Error(
|
|
293
|
+
"Synthetic edit prerequisite: pushUndoSnapshot unavailable",
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const textBefore = this.getText();
|
|
298
|
+
const preCursorLine = editor.state.cursorLine;
|
|
299
|
+
const preCursorCol = editor.state.cursorCol;
|
|
300
|
+
|
|
301
|
+
mutation();
|
|
302
|
+
|
|
303
|
+
if (this.getText() === textBefore) return;
|
|
304
|
+
|
|
305
|
+
// Text changed — push undo boundary for pre-mutation state.
|
|
306
|
+
// Briefly swap pre-mutation state in for the snapshot, then
|
|
307
|
+
// restore the post-mutation result.
|
|
308
|
+
const postLines = editor.state.lines.slice();
|
|
309
|
+
const postCursorLine = editor.state.cursorLine;
|
|
310
|
+
const postCursorCol = editor.state.cursorCol;
|
|
311
|
+
const postPreferredCol = editor.preferredVisualCol;
|
|
312
|
+
|
|
313
|
+
const preLines = textBefore.split("\n");
|
|
314
|
+
editor.state.lines = preLines.length > 0 ? preLines : [""];
|
|
315
|
+
editor.state.cursorLine = preCursorLine;
|
|
316
|
+
editor.state.cursorCol = preCursorCol;
|
|
317
|
+
editor.pushUndoSnapshot();
|
|
318
|
+
|
|
319
|
+
editor.state.lines = postLines;
|
|
320
|
+
editor.state.cursorLine = postCursorLine;
|
|
321
|
+
editor.state.cursorCol = postCursorCol;
|
|
322
|
+
editor.preferredVisualCol = postPreferredCol;
|
|
323
|
+
|
|
324
|
+
editor.onChange?.(this.getText());
|
|
325
|
+
editor.tui?.requestRender?.();
|
|
326
|
+
}
|
|
327
|
+
|
|
129
328
|
private clearPendingState(): void {
|
|
130
329
|
this.pendingMotion = null;
|
|
131
330
|
this.pendingTextObject = null;
|
|
@@ -176,6 +375,8 @@ export class ModalEditor extends CustomEditor {
|
|
|
176
375
|
}
|
|
177
376
|
|
|
178
377
|
handleInput(data: string): void {
|
|
378
|
+
this.ensureOnChangeHook();
|
|
379
|
+
|
|
179
380
|
if (this.mode !== "insert") {
|
|
180
381
|
if (this.discardingBracketedPasteInNormalMode) {
|
|
181
382
|
if (this.isEscapeLikeInput(data)) {
|
|
@@ -227,19 +428,17 @@ export class ModalEditor extends CustomEditor {
|
|
|
227
428
|
}
|
|
228
429
|
// Alt+o: open new line below (stay in insert mode)
|
|
229
430
|
if (matchesKey(data, Key.alt("o")) || data === "\x1bo") {
|
|
230
|
-
|
|
231
|
-
super.handleInput(NEWLINE);
|
|
431
|
+
this.openLineBelow();
|
|
232
432
|
return;
|
|
233
433
|
}
|
|
234
434
|
// Alt+Shift+o: open new line above (stay in insert mode)
|
|
235
435
|
// \x1bO is the legacy sequence for Alt+Shift+O (VT100 SS3 prefix in non-Kitty terminals)
|
|
236
436
|
if (matchesKey(data, Key.shiftAlt("o")) || data === "\x1bO") {
|
|
237
|
-
|
|
238
|
-
super.handleInput(NEWLINE);
|
|
239
|
-
super.handleInput(ESC_UP);
|
|
437
|
+
this.openLineAbove();
|
|
240
438
|
return;
|
|
241
439
|
}
|
|
242
|
-
|
|
440
|
+
super.handleInput(data);
|
|
441
|
+
return;
|
|
243
442
|
}
|
|
244
443
|
|
|
245
444
|
if (this.pendingTextObject) {
|
|
@@ -631,6 +830,8 @@ export class ModalEditor extends CustomEditor {
|
|
|
631
830
|
|| data === "p"
|
|
632
831
|
|| data === "P"
|
|
633
832
|
|| data === "J"
|
|
833
|
+
|| data === CTRL_R
|
|
834
|
+
|| matchesKey(data, "ctrl+r")
|
|
634
835
|
);
|
|
635
836
|
const supportsCountedCharMotion = (
|
|
636
837
|
CHAR_MOTION_KEYS.has(data)
|
|
@@ -754,8 +955,13 @@ export class ModalEditor extends CustomEditor {
|
|
|
754
955
|
return;
|
|
755
956
|
}
|
|
756
957
|
|
|
757
|
-
if (data === "u") {
|
|
758
|
-
|
|
958
|
+
if (data === "u" || data === CTRL_UNDERSCORE || matchesKey(data, "ctrl+_")) {
|
|
959
|
+
this.performUndo();
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
if (data === CTRL_R || matchesKey(data, "ctrl+r")) {
|
|
964
|
+
this.performRedo();
|
|
759
965
|
return;
|
|
760
966
|
}
|
|
761
967
|
|
|
@@ -788,6 +994,17 @@ export class ModalEditor extends CustomEditor {
|
|
|
788
994
|
super.handleInput(data);
|
|
789
995
|
}
|
|
790
996
|
|
|
997
|
+
private openLineBelow(): void {
|
|
998
|
+
super.handleInput(CTRL_E);
|
|
999
|
+
super.handleInput(NEWLINE);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
private openLineAbove(): void {
|
|
1003
|
+
super.handleInput(CTRL_A);
|
|
1004
|
+
super.handleInput(NEWLINE);
|
|
1005
|
+
super.handleInput(ESC_UP);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
791
1008
|
private handleMappedKey(key: string): void {
|
|
792
1009
|
const seq = NORMAL_KEYS[key];
|
|
793
1010
|
switch (key) {
|
|
@@ -809,14 +1026,11 @@ export class ModalEditor extends CustomEditor {
|
|
|
809
1026
|
super.handleInput(CTRL_A);
|
|
810
1027
|
break;
|
|
811
1028
|
case "o":
|
|
812
|
-
|
|
813
|
-
super.handleInput(NEWLINE);
|
|
1029
|
+
this.openLineBelow();
|
|
814
1030
|
this.mode = "insert";
|
|
815
1031
|
break;
|
|
816
1032
|
case "O":
|
|
817
|
-
|
|
818
|
-
super.handleInput(NEWLINE);
|
|
819
|
-
super.handleInput(ESC_UP);
|
|
1033
|
+
this.openLineAbove();
|
|
820
1034
|
this.mode = "insert";
|
|
821
1035
|
break;
|
|
822
1036
|
case "D":
|
|
@@ -951,43 +1165,39 @@ export class ModalEditor extends CustomEditor {
|
|
|
951
1165
|
const steps = Math.max(0, count - 1);
|
|
952
1166
|
if (steps === 0) return;
|
|
953
1167
|
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
};
|
|
1168
|
+
this.applySyntheticEdit(() => {
|
|
1169
|
+
const editor = this as unknown as ModalEditorInternals;
|
|
1170
|
+
const state = editor.state;
|
|
1171
|
+
if (!state || !Array.isArray(state.lines)) return;
|
|
959
1172
|
|
|
960
|
-
|
|
961
|
-
|
|
1173
|
+
const currentLine = state.cursorLine ?? 0;
|
|
1174
|
+
let joinPoint = state.cursorCol ?? 0;
|
|
962
1175
|
|
|
963
|
-
|
|
964
|
-
|
|
1176
|
+
for (let i = 0; i < steps; i++) {
|
|
1177
|
+
if (currentLine >= state.lines.length - 1) break;
|
|
965
1178
|
|
|
966
|
-
|
|
967
|
-
|
|
1179
|
+
const left = state.lines[currentLine]!;
|
|
1180
|
+
const right = state.lines[currentLine + 1]!;
|
|
1181
|
+
let joined: string;
|
|
968
1182
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1183
|
+
if (normalize) {
|
|
1184
|
+
const trimmedRight = right.trimStart();
|
|
1185
|
+
const leftEndsWithSpace = left.length > 0 && /\s/.test(left[left.length - 1]!);
|
|
1186
|
+
const needsSeparator = !leftEndsWithSpace && trimmedRight.length > 0;
|
|
1187
|
+
joined = needsSeparator ? `${left} ${trimmedRight}` : left + trimmedRight;
|
|
1188
|
+
joinPoint = left.length;
|
|
1189
|
+
} else {
|
|
1190
|
+
joined = left + right;
|
|
1191
|
+
joinPoint = left.length;
|
|
1192
|
+
}
|
|
972
1193
|
|
|
973
|
-
|
|
974
|
-
const trimmedRight = right.trimStart();
|
|
975
|
-
const leftEndsWithSpace = left.length > 0 && /\s/.test(left[left.length - 1]!);
|
|
976
|
-
const needsSeparator = !leftEndsWithSpace && trimmedRight.length > 0;
|
|
977
|
-
joined = needsSeparator ? `${left} ${trimmedRight}` : left + trimmedRight;
|
|
978
|
-
joinPoint = left.length;
|
|
979
|
-
} else {
|
|
980
|
-
joined = left + right;
|
|
981
|
-
joinPoint = left.length;
|
|
1194
|
+
state.lines.splice(currentLine, 2, joined);
|
|
982
1195
|
}
|
|
983
1196
|
|
|
984
|
-
state.
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
state.cursorCol = joinPoint;
|
|
989
|
-
editor.preferredVisualCol = joinPoint;
|
|
990
|
-
editor.tui?.requestRender?.();
|
|
1197
|
+
state.cursorLine = currentLine;
|
|
1198
|
+
state.cursorCol = joinPoint;
|
|
1199
|
+
editor.preferredVisualCol = joinPoint;
|
|
1200
|
+
});
|
|
991
1201
|
}
|
|
992
1202
|
|
|
993
1203
|
private isWordChar(ch: string): boolean {
|
package/package.json
CHANGED
package/types.ts
CHANGED
|
@@ -43,6 +43,7 @@ export const ESC_DELETE = "\x1b[3~";
|
|
|
43
43
|
export const CTRL_A = "\x01"; // line start
|
|
44
44
|
export const CTRL_E = "\x05"; // line end
|
|
45
45
|
export const CTRL_K = "\x0b"; // kill to end of line
|
|
46
|
+
export const CTRL_R = "\x12"; // ctrl+r — readline redo trigger in vim layer
|
|
46
47
|
export const CTRL_UNDERSCORE = "\x1f"; // ctrl+_ — readline undo
|
|
47
48
|
export const NEWLINE = "\n"; // newline character
|
|
48
49
|
export const ESC_UP = "\x1b[A"; // cursor up
|