feat: more native keyboard navigation in multiline editor (#655)
Signed-off-by: Thibault Sottiaux <tibo@openai.com>
This commit is contained in:
committed by
GitHub
parent
69ce06d2f8
commit
d401283a41
@@ -419,6 +419,58 @@ export default class TextBuffer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete everything from the caret to the *end* of the current line. The
|
||||||
|
* caret itself stays in place (column remains unchanged). Mirrors the
|
||||||
|
* common Ctrl+K shortcut in many shells and editors.
|
||||||
|
*/
|
||||||
|
deleteToLineEnd(): void {
|
||||||
|
dbg("deleteToLineEnd", { beforeCursor: this.getCursor() });
|
||||||
|
|
||||||
|
const line = this.line(this.cursorRow);
|
||||||
|
if (this.cursorCol >= this.lineLen(this.cursorRow)) {
|
||||||
|
// Nothing to delete – caret already at EOL.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pushUndo();
|
||||||
|
|
||||||
|
// Keep the prefix before the caret, discard the remainder.
|
||||||
|
this.lines[this.cursorRow] = cpSlice(line, 0, this.cursorCol);
|
||||||
|
this.version++;
|
||||||
|
|
||||||
|
dbg("deleteToLineEnd:after", {
|
||||||
|
cursor: this.getCursor(),
|
||||||
|
line: this.line(this.cursorRow),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete everything from the *start* of the current line up to (but not
|
||||||
|
* including) the caret. The caret is moved to column-0, mirroring the
|
||||||
|
* behaviour of the familiar Ctrl+U binding.
|
||||||
|
*/
|
||||||
|
deleteToLineStart(): void {
|
||||||
|
dbg("deleteToLineStart", { beforeCursor: this.getCursor() });
|
||||||
|
|
||||||
|
if (this.cursorCol === 0) {
|
||||||
|
// Nothing to delete – caret already at SOL.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pushUndo();
|
||||||
|
|
||||||
|
const line = this.line(this.cursorRow);
|
||||||
|
this.lines[this.cursorRow] = cpSlice(line, this.cursorCol);
|
||||||
|
this.cursorCol = 0;
|
||||||
|
this.version++;
|
||||||
|
|
||||||
|
dbg("deleteToLineStart:after", {
|
||||||
|
cursor: this.getCursor(),
|
||||||
|
line: this.line(this.cursorRow),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------
|
/* ------------------------------------------------------------------
|
||||||
* Word‑wise deletion helpers – exposed publicly so tests (and future
|
* Word‑wise deletion helpers – exposed publicly so tests (and future
|
||||||
* key‑bindings) can invoke them directly.
|
* key‑bindings) can invoke them directly.
|
||||||
@@ -791,7 +843,6 @@ export default class TextBuffer {
|
|||||||
!key["ctrl"] &&
|
!key["ctrl"] &&
|
||||||
!key["alt"]
|
!key["alt"]
|
||||||
) {
|
) {
|
||||||
/* navigation */
|
|
||||||
this.move("left");
|
this.move("left");
|
||||||
} else if (
|
} else if (
|
||||||
key["rightArrow"] &&
|
key["rightArrow"] &&
|
||||||
@@ -816,7 +867,9 @@ export default class TextBuffer {
|
|||||||
} else if (key["end"]) {
|
} else if (key["end"]) {
|
||||||
this.move("end");
|
this.move("end");
|
||||||
}
|
}
|
||||||
/* delete */
|
|
||||||
|
// Deletions
|
||||||
|
//
|
||||||
// In raw terminal mode many frameworks (Ink included) surface a physical
|
// In raw terminal mode many frameworks (Ink included) surface a physical
|
||||||
// Backspace key‑press as the single DEL (0x7f) byte placed in `input` with
|
// Backspace key‑press as the single DEL (0x7f) byte placed in `input` with
|
||||||
// no `key.backspace` flag set. Treat that byte exactly like an ordinary
|
// no `key.backspace` flag set. Treat that byte exactly like an ordinary
|
||||||
@@ -839,22 +892,47 @@ export default class TextBuffer {
|
|||||||
// forward deletion so we don't lose that capability on keyboards that
|
// forward deletion so we don't lose that capability on keyboards that
|
||||||
// expose both behaviours.
|
// expose both behaviours.
|
||||||
this.backspace();
|
this.backspace();
|
||||||
}
|
} else if (key["delete"]) {
|
||||||
// Forward deletion (Fn+Delete on macOS, or Delete key with Shift held after
|
// Forward deletion (Fn+Delete on macOS, or Delete key with Shift held after
|
||||||
// the branch above) – remove the character *under / to the right* of the
|
// the branch above) – remove the character *under / to the right* of the
|
||||||
// caret, merging lines when at EOL similar to many editors.
|
// caret, merging lines when at EOL similar to many editors.
|
||||||
else if (key["delete"]) {
|
|
||||||
this.del();
|
this.del();
|
||||||
} else if (input && !key["ctrl"] && !key["meta"]) {
|
}
|
||||||
|
// Normal input
|
||||||
|
else if (input && !key["ctrl"] && !key["meta"]) {
|
||||||
this.insert(input);
|
this.insert(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* printable */
|
// Emacs/readline-style shortcuts
|
||||||
|
else if (key["ctrl"] && (input === "a" || input === "\x01")) {
|
||||||
|
// Ctrl+A or ⌥← → start of line
|
||||||
|
this.move("home");
|
||||||
|
} else if (key["ctrl"] && (input === "e" || input === "\x05")) {
|
||||||
|
// Ctrl+E or ⌥→ → end of line
|
||||||
|
this.move("end");
|
||||||
|
} else if (key["ctrl"] && (input === "b" || input === "\x02")) {
|
||||||
|
// Ctrl+B → char left
|
||||||
|
this.move("left");
|
||||||
|
} else if (key["ctrl"] && (input === "f" || input === "\x06")) {
|
||||||
|
// Ctrl+F → char right
|
||||||
|
this.move("right");
|
||||||
|
} else if (key["ctrl"] && (input === "d" || input === "\x04")) {
|
||||||
|
// Ctrl+D → forward delete
|
||||||
|
this.del();
|
||||||
|
} else if (key["ctrl"] && (input === "k" || input === "\x0b")) {
|
||||||
|
// Ctrl+K → kill to EOL
|
||||||
|
this.deleteToLineEnd();
|
||||||
|
} else if (key["ctrl"] && (input === "u" || input === "\x15")) {
|
||||||
|
// Ctrl+U → kill to SOL
|
||||||
|
this.deleteToLineStart();
|
||||||
|
} else if (key["ctrl"] && (input === "w" || input === "\x17")) {
|
||||||
|
// Ctrl+W → delete word left
|
||||||
|
this.deleteWordLeft();
|
||||||
|
}
|
||||||
|
|
||||||
/* clamp + scroll */
|
/* printable, clamp + scroll */
|
||||||
this.ensureCursorInRange();
|
this.ensureCursorInRange();
|
||||||
this.ensureCursorVisible(vp);
|
this.ensureCursorVisible(vp);
|
||||||
|
|
||||||
const cursorMoved =
|
const cursorMoved =
|
||||||
this.cursorRow !== beforeRow || this.cursorCol !== beforeCol;
|
this.cursorRow !== beforeRow || this.cursorCol !== beforeCol;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user