mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 07:04:45 +00:00
feat(tui): add character jump navigation (Ctrl+], Ctrl+Alt+]) (#1074)
Add Bash/Readline-style character search: - Ctrl+] enters forward jump mode, awaits next character, jumps to it - Ctrl+Alt+] does the same but searches backward - Multi-line search - Case-sensitive matching - Pressing the hotkey again cancels; control chars cancel and fall through
This commit is contained in:
parent
e20583aac8
commit
c5d16fe456
6 changed files with 304 additions and 1 deletions
|
|
@ -27,6 +27,8 @@ Modifier combinations: `ctrl+shift+x`, `alt+ctrl+x`, `ctrl+shift+alt+x`, etc.
|
||||||
| `cursorWordRight` | `alt+right`, `ctrl+right`, `alt+f` | Move cursor word right |
|
| `cursorWordRight` | `alt+right`, `ctrl+right`, `alt+f` | Move cursor word right |
|
||||||
| `cursorLineStart` | `home`, `ctrl+a` | Move to line start |
|
| `cursorLineStart` | `home`, `ctrl+a` | Move to line start |
|
||||||
| `cursorLineEnd` | `end`, `ctrl+e` | Move to line end |
|
| `cursorLineEnd` | `end`, `ctrl+e` | Move to line end |
|
||||||
|
| `jumpForward` | `ctrl+]` | Jump forward to character |
|
||||||
|
| `jumpBackward` | `ctrl+alt+]` | Jump backward to character |
|
||||||
| `pageUp` | `pageUp` | Scroll up by page |
|
| `pageUp` | `pageUp` | Scroll up by page |
|
||||||
| `pageDown` | `pageDown` | Scroll down by page |
|
| `pageDown` | `pageDown` | Scroll down by page |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3951,6 +3951,8 @@ export class InteractiveMode {
|
||||||
const cursorWordRight = this.getEditorKeyDisplay("cursorWordRight");
|
const cursorWordRight = this.getEditorKeyDisplay("cursorWordRight");
|
||||||
const cursorLineStart = this.getEditorKeyDisplay("cursorLineStart");
|
const cursorLineStart = this.getEditorKeyDisplay("cursorLineStart");
|
||||||
const cursorLineEnd = this.getEditorKeyDisplay("cursorLineEnd");
|
const cursorLineEnd = this.getEditorKeyDisplay("cursorLineEnd");
|
||||||
|
const jumpForward = this.getEditorKeyDisplay("jumpForward");
|
||||||
|
const jumpBackward = this.getEditorKeyDisplay("jumpBackward");
|
||||||
const pageUp = this.getEditorKeyDisplay("pageUp");
|
const pageUp = this.getEditorKeyDisplay("pageUp");
|
||||||
const pageDown = this.getEditorKeyDisplay("pageDown");
|
const pageDown = this.getEditorKeyDisplay("pageDown");
|
||||||
|
|
||||||
|
|
@ -3988,6 +3990,8 @@ export class InteractiveMode {
|
||||||
| \`${cursorWordLeft}\` / \`${cursorWordRight}\` | Move by word |
|
| \`${cursorWordLeft}\` / \`${cursorWordRight}\` | Move by word |
|
||||||
| \`${cursorLineStart}\` | Start of line |
|
| \`${cursorLineStart}\` | Start of line |
|
||||||
| \`${cursorLineEnd}\` | End of line |
|
| \`${cursorLineEnd}\` | End of line |
|
||||||
|
| \`${jumpForward}\` | Jump forward to character |
|
||||||
|
| \`${jumpBackward}\` | Jump backward to character |
|
||||||
| \`${pageUp}\` / \`${pageDown}\` | Scroll by page |
|
| \`${pageUp}\` / \`${pageDown}\` | Scroll by page |
|
||||||
|
|
||||||
**Editing**
|
**Editing**
|
||||||
|
|
|
||||||
|
|
@ -298,8 +298,13 @@ editor.getPaddingX(); // Get current padding
|
||||||
- `Enter` - Submit
|
- `Enter` - Submit
|
||||||
- `Shift+Enter`, `Ctrl+Enter`, or `Alt+Enter` - New line (terminal-dependent, Alt+Enter most reliable)
|
- `Shift+Enter`, `Ctrl+Enter`, or `Alt+Enter` - New line (terminal-dependent, Alt+Enter most reliable)
|
||||||
- `Tab` - Autocomplete
|
- `Tab` - Autocomplete
|
||||||
- `Ctrl+K` - Delete line
|
- `Ctrl+K` - Delete to end of line
|
||||||
|
- `Ctrl+U` - Delete to start of line
|
||||||
|
- `Ctrl+W` or `Alt+Backspace` - Delete word backwards
|
||||||
|
- `Alt+D` or `Alt+Delete` - Delete word forwards
|
||||||
- `Ctrl+A` / `Ctrl+E` - Line start/end
|
- `Ctrl+A` / `Ctrl+E` - Line start/end
|
||||||
|
- `Ctrl+]` - Jump forward to character (awaits next keypress, then moves cursor to first occurrence)
|
||||||
|
- `Ctrl+Alt+]` - Jump backward to character
|
||||||
- Arrow keys, Backspace, Delete work as expected
|
- Arrow keys, Backspace, Delete work as expected
|
||||||
|
|
||||||
### Markdown
|
### Markdown
|
||||||
|
|
|
||||||
|
|
@ -196,6 +196,9 @@ export class Editor implements Component, Focusable {
|
||||||
private killRing: string[] = [];
|
private killRing: string[] = [];
|
||||||
private lastAction: "kill" | "yank" | "type-word" | null = null;
|
private lastAction: "kill" | "yank" | "type-word" | null = null;
|
||||||
|
|
||||||
|
// Character jump mode
|
||||||
|
private jumpMode: "forward" | "backward" | null = null;
|
||||||
|
|
||||||
// Undo support
|
// Undo support
|
||||||
private undoStack: EditorState[] = [];
|
private undoStack: EditorState[] = [];
|
||||||
|
|
||||||
|
|
@ -437,6 +440,26 @@ export class Editor implements Component, Focusable {
|
||||||
handleInput(data: string): void {
|
handleInput(data: string): void {
|
||||||
const kb = getEditorKeybindings();
|
const kb = getEditorKeybindings();
|
||||||
|
|
||||||
|
// Handle character jump mode (awaiting next character to jump to)
|
||||||
|
if (this.jumpMode !== null) {
|
||||||
|
// Cancel if the hotkey is pressed again
|
||||||
|
if (kb.matches(data, "jumpForward") || kb.matches(data, "jumpBackward")) {
|
||||||
|
this.jumpMode = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.charCodeAt(0) >= 32) {
|
||||||
|
// Printable character - perform the jump
|
||||||
|
const direction = this.jumpMode;
|
||||||
|
this.jumpMode = null;
|
||||||
|
this.jumpToChar(data, direction);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Control character - cancel and fall through to normal handling
|
||||||
|
this.jumpMode = null;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle bracketed paste mode
|
// Handle bracketed paste mode
|
||||||
if (data.includes("\x1b[200~")) {
|
if (data.includes("\x1b[200~")) {
|
||||||
this.isInPaste = true;
|
this.isInPaste = true;
|
||||||
|
|
@ -678,6 +701,16 @@ export class Editor implements Component, Focusable {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Character jump mode triggers
|
||||||
|
if (kb.matches(data, "jumpForward")) {
|
||||||
|
this.jumpMode = "forward";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (kb.matches(data, "jumpBackward")) {
|
||||||
|
this.jumpMode = "backward";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Shift+Space - insert regular space
|
// Shift+Space - insert regular space
|
||||||
if (matchesKey(data, "shift+space")) {
|
if (matchesKey(data, "shift+space")) {
|
||||||
this.insertCharacter(" ");
|
this.insertCharacter(" ");
|
||||||
|
|
@ -1664,6 +1697,40 @@ export class Editor implements Component, Focusable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jump to the first occurrence of a character in the specified direction.
|
||||||
|
* Multi-line search. Case-sensitive. Skips the current cursor position.
|
||||||
|
*/
|
||||||
|
private jumpToChar(char: string, direction: "forward" | "backward"): void {
|
||||||
|
this.lastAction = null;
|
||||||
|
const isForward = direction === "forward";
|
||||||
|
const lines = this.state.lines;
|
||||||
|
|
||||||
|
const end = isForward ? lines.length : -1;
|
||||||
|
const step = isForward ? 1 : -1;
|
||||||
|
|
||||||
|
for (let lineIdx = this.state.cursorLine; lineIdx !== end; lineIdx += step) {
|
||||||
|
const line = lines[lineIdx] || "";
|
||||||
|
const isCurrentLine = lineIdx === this.state.cursorLine;
|
||||||
|
|
||||||
|
// Current line: start after/before cursor; other lines: search full line
|
||||||
|
const searchFrom = isCurrentLine
|
||||||
|
? isForward
|
||||||
|
? this.state.cursorCol + 1
|
||||||
|
: this.state.cursorCol - 1
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const idx = isForward ? line.indexOf(char, searchFrom) : line.lastIndexOf(char, searchFrom);
|
||||||
|
|
||||||
|
if (idx !== -1) {
|
||||||
|
this.state.cursorLine = lineIdx;
|
||||||
|
this.state.cursorCol = idx;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No match found - cursor stays in place
|
||||||
|
}
|
||||||
|
|
||||||
private moveWordForwards(): void {
|
private moveWordForwards(): void {
|
||||||
this.lastAction = null;
|
this.lastAction = null;
|
||||||
const currentLine = this.state.lines[this.state.cursorLine] || "";
|
const currentLine = this.state.lines[this.state.cursorLine] || "";
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ export type EditorAction =
|
||||||
| "cursorWordRight"
|
| "cursorWordRight"
|
||||||
| "cursorLineStart"
|
| "cursorLineStart"
|
||||||
| "cursorLineEnd"
|
| "cursorLineEnd"
|
||||||
|
| "jumpForward"
|
||||||
|
| "jumpBackward"
|
||||||
| "pageUp"
|
| "pageUp"
|
||||||
| "pageDown"
|
| "pageDown"
|
||||||
// Deletion
|
// Deletion
|
||||||
|
|
@ -72,6 +74,8 @@ export const DEFAULT_EDITOR_KEYBINDINGS: Required<EditorKeybindingsConfig> = {
|
||||||
cursorWordRight: ["alt+right", "ctrl+right", "alt+f"],
|
cursorWordRight: ["alt+right", "ctrl+right", "alt+f"],
|
||||||
cursorLineStart: ["home", "ctrl+a"],
|
cursorLineStart: ["home", "ctrl+a"],
|
||||||
cursorLineEnd: ["end", "ctrl+e"],
|
cursorLineEnd: ["end", "ctrl+e"],
|
||||||
|
jumpForward: "ctrl+]",
|
||||||
|
jumpBackward: "ctrl+alt+]",
|
||||||
pageUp: "pageUp",
|
pageUp: "pageUp",
|
||||||
pageDown: "pageDown",
|
pageDown: "pageDown",
|
||||||
// Deletion
|
// Deletion
|
||||||
|
|
|
||||||
|
|
@ -1965,4 +1965,225 @@ describe("Editor component", () => {
|
||||||
assert.strictEqual(editor.isShowingAutocomplete(), false);
|
assert.strictEqual(editor.isShowingAutocomplete(), false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Character jump (Ctrl+])", () => {
|
||||||
|
it("jumps forward to first occurrence of character on same line", () => {
|
||||||
|
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||||
|
|
||||||
|
editor.setText("hello world");
|
||||||
|
editor.handleInput("\x01"); // Ctrl+A - go to start
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 0 });
|
||||||
|
|
||||||
|
editor.handleInput("\x1d"); // Ctrl+] (legacy sequence for ctrl+])
|
||||||
|
editor.handleInput("o"); // Jump to first 'o'
|
||||||
|
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 4 }); // 'o' in "hello"
|
||||||
|
});
|
||||||
|
|
||||||
|
it("jumps forward to next occurrence after cursor", () => {
|
||||||
|
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||||
|
|
||||||
|
editor.setText("hello world");
|
||||||
|
editor.handleInput("\x01"); // Ctrl+A - go to start
|
||||||
|
// Move cursor to the 'o' in "hello" (col 4)
|
||||||
|
for (let i = 0; i < 4; i++) editor.handleInput("\x1b[C");
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 4 });
|
||||||
|
|
||||||
|
editor.handleInput("\x1d"); // Ctrl+]
|
||||||
|
editor.handleInput("o"); // Jump to next 'o' (in "world")
|
||||||
|
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 7 }); // 'o' in "world"
|
||||||
|
});
|
||||||
|
|
||||||
|
it("jumps forward across multiple lines", () => {
|
||||||
|
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||||
|
|
||||||
|
editor.setText("abc\ndef\nghi");
|
||||||
|
// Cursor is at end (line 2, col 3). Move to line 0 via up arrows, then Ctrl+A
|
||||||
|
editor.handleInput("\x1b[A"); // Up
|
||||||
|
editor.handleInput("\x1b[A"); // Up - now on line 0
|
||||||
|
editor.handleInput("\x01"); // Ctrl+A - go to start of line
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 0 });
|
||||||
|
|
||||||
|
editor.handleInput("\x1d"); // Ctrl+]
|
||||||
|
editor.handleInput("g"); // Jump to 'g' on line 3
|
||||||
|
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 2, col: 0 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("jumps backward to first occurrence before cursor on same line", () => {
|
||||||
|
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||||
|
|
||||||
|
editor.setText("hello world");
|
||||||
|
// Cursor at end (col 11)
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 11 });
|
||||||
|
|
||||||
|
editor.handleInput("\x1b\x1d"); // Ctrl+Alt+] (ESC followed by Ctrl+])
|
||||||
|
editor.handleInput("o"); // Jump to last 'o' before cursor
|
||||||
|
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 7 }); // 'o' in "world"
|
||||||
|
});
|
||||||
|
|
||||||
|
it("jumps backward across multiple lines", () => {
|
||||||
|
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||||
|
|
||||||
|
editor.setText("abc\ndef\nghi");
|
||||||
|
// Cursor at end of line 3
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 2, col: 3 });
|
||||||
|
|
||||||
|
editor.handleInput("\x1b\x1d"); // Ctrl+Alt+]
|
||||||
|
editor.handleInput("a"); // Jump to 'a' on line 1
|
||||||
|
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 0 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does nothing when character is not found (forward)", () => {
|
||||||
|
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||||
|
|
||||||
|
editor.setText("hello world");
|
||||||
|
editor.handleInput("\x01"); // Ctrl+A - go to start
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 0 });
|
||||||
|
|
||||||
|
editor.handleInput("\x1d"); // Ctrl+]
|
||||||
|
editor.handleInput("z"); // 'z' doesn't exist
|
||||||
|
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 0 }); // Cursor unchanged
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does nothing when character is not found (backward)", () => {
|
||||||
|
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||||
|
|
||||||
|
editor.setText("hello world");
|
||||||
|
// Cursor at end
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 11 });
|
||||||
|
|
||||||
|
editor.handleInput("\x1b\x1d"); // Ctrl+Alt+]
|
||||||
|
editor.handleInput("z"); // 'z' doesn't exist
|
||||||
|
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 11 }); // Cursor unchanged
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is case-sensitive", () => {
|
||||||
|
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||||
|
|
||||||
|
editor.setText("Hello World");
|
||||||
|
editor.handleInput("\x01"); // Ctrl+A - go to start
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 0 });
|
||||||
|
|
||||||
|
// Search for lowercase 'h' - should not find it (only 'H' exists)
|
||||||
|
editor.handleInput("\x1d"); // Ctrl+]
|
||||||
|
editor.handleInput("h");
|
||||||
|
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 0 }); // Cursor unchanged
|
||||||
|
|
||||||
|
// Search for uppercase 'W' - should find it
|
||||||
|
editor.handleInput("\x1d"); // Ctrl+]
|
||||||
|
editor.handleInput("W");
|
||||||
|
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 6 }); // 'W' in "World"
|
||||||
|
});
|
||||||
|
|
||||||
|
it("cancels jump mode when Ctrl+] is pressed again", () => {
|
||||||
|
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||||
|
|
||||||
|
editor.setText("hello world");
|
||||||
|
editor.handleInput("\x01"); // Ctrl+A - go to start
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 0 });
|
||||||
|
|
||||||
|
editor.handleInput("\x1d"); // Ctrl+] - enter jump mode
|
||||||
|
editor.handleInput("\x1d"); // Ctrl+] again - cancel
|
||||||
|
|
||||||
|
// Type 'o' normally - should insert, not jump
|
||||||
|
editor.handleInput("o");
|
||||||
|
assert.strictEqual(editor.getText(), "ohello world");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("cancels jump mode on Escape and processes the Escape", () => {
|
||||||
|
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||||
|
|
||||||
|
editor.setText("hello world");
|
||||||
|
editor.handleInput("\x01"); // Ctrl+A - go to start
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 0 });
|
||||||
|
|
||||||
|
editor.handleInput("\x1d"); // Ctrl+] - enter jump mode
|
||||||
|
editor.handleInput("\x1b"); // Escape - cancel jump mode
|
||||||
|
|
||||||
|
// Cursor should be unchanged (Escape itself doesn't move cursor in editor)
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 0 });
|
||||||
|
|
||||||
|
// Type 'o' normally - should insert, not jump
|
||||||
|
editor.handleInput("o");
|
||||||
|
assert.strictEqual(editor.getText(), "ohello world");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("cancels backward jump mode when Ctrl+Alt+] is pressed again", () => {
|
||||||
|
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||||
|
|
||||||
|
editor.setText("hello world");
|
||||||
|
// Cursor at end
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 11 });
|
||||||
|
|
||||||
|
editor.handleInput("\x1b\x1d"); // Ctrl+Alt+] - enter backward jump mode
|
||||||
|
editor.handleInput("\x1b\x1d"); // Ctrl+Alt+] again - cancel
|
||||||
|
|
||||||
|
// Type 'o' normally - should insert, not jump
|
||||||
|
editor.handleInput("o");
|
||||||
|
assert.strictEqual(editor.getText(), "hello worldo");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("searches for special characters", () => {
|
||||||
|
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||||
|
|
||||||
|
editor.setText("foo(bar) = baz;");
|
||||||
|
editor.handleInput("\x01"); // Ctrl+A - go to start
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 0 });
|
||||||
|
|
||||||
|
// Jump to '('
|
||||||
|
editor.handleInput("\x1d"); // Ctrl+]
|
||||||
|
editor.handleInput("(");
|
||||||
|
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 3 });
|
||||||
|
|
||||||
|
// Jump to '='
|
||||||
|
editor.handleInput("\x1d"); // Ctrl+]
|
||||||
|
editor.handleInput("=");
|
||||||
|
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 9 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles empty text gracefully", () => {
|
||||||
|
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||||
|
|
||||||
|
editor.setText("");
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 0 });
|
||||||
|
|
||||||
|
editor.handleInput("\x1d"); // Ctrl+]
|
||||||
|
editor.handleInput("x");
|
||||||
|
|
||||||
|
assert.deepStrictEqual(editor.getCursor(), { line: 0, col: 0 }); // Cursor unchanged
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resets lastAction when jumping", () => {
|
||||||
|
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||||
|
|
||||||
|
editor.setText("hello world");
|
||||||
|
editor.handleInput("\x01"); // Ctrl+A - go to start
|
||||||
|
|
||||||
|
// Type to set lastAction to "type-word"
|
||||||
|
editor.handleInput("x");
|
||||||
|
assert.strictEqual(editor.getText(), "xhello world");
|
||||||
|
|
||||||
|
// Jump forward
|
||||||
|
editor.handleInput("\x1d"); // Ctrl+]
|
||||||
|
editor.handleInput("o");
|
||||||
|
|
||||||
|
// Type more - should start a new undo unit (lastAction was reset)
|
||||||
|
editor.handleInput("Y");
|
||||||
|
assert.strictEqual(editor.getText(), "xhellYo world");
|
||||||
|
|
||||||
|
// Undo should only undo "Y", not "x" as well
|
||||||
|
editor.handleInput("\x1b[45;5u"); // Ctrl+- (undo)
|
||||||
|
assert.strictEqual(editor.getText(), "xhello world");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue