Skip to content

Commit 84bf18d

Browse files
committed
fix(ui): make Backspace work across all terminals
Ink's key.backspace flag depends on what the terminal sends — Linux consoles, tmux, and many SSH configs deliver Backspace as raw 0x7f or 0x08 with no flag set, or surface it as key.delete. Catch the raw bytes too, and route Delete-key presses at end-of-line to backspace semantics (matches readline). Filter control bytes out of the printable branch so a stray Backspace can never end up inserted as a glyph.
1 parent d97dc84 commit 84bf18d

1 file changed

Lines changed: 22 additions & 5 deletions

File tree

src/ui/Input.tsx

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -254,12 +254,26 @@ export function Input({ disabled, onSubmit, onAbort, commands, history, cwd }: I
254254
if (key.ctrl && input === "a") return setState(moveStart(state));
255255
if (key.ctrl && input === "e") return setState(moveEnd(state));
256256

257-
// Edits
258-
if (key.backspace) {
257+
// Edits. Some terminals (Linux console, tmux, certain SSH configs)
258+
// deliver Backspace as raw 0x7f/0x08 without setting key.backspace,
259+
// or even surface it as key.delete. We catch every shape so the
260+
// floor — "Backspace deletes a char" — always works.
261+
const isBackspaceByte = input === "\x7f" || input === "\b";
262+
if (key.backspace || isBackspaceByte) {
259263
setSuggestionIdx(0);
260264
return setState(backspace(state));
261265
}
262-
if (key.delete) return setState(deleteForward(state));
266+
// `key.delete` historically also fires for Backspace on some Ink
267+
// builds, so prefer the backspace semantics when the buffer is
268+
// non-empty and the cursor isn't at end-of-line. Forward-delete
269+
// stays accessible via Ctrl-D.
270+
if (key.delete) {
271+
if (state.cursor > 0 && state.cursor === state.buffer.length) {
272+
setSuggestionIdx(0);
273+
return setState(backspace(state));
274+
}
275+
return setState(deleteForward(state));
276+
}
263277
// Ctrl-D matches readline: on an empty buffer it's EOF (i.e. quit),
264278
// on a non-empty buffer it deletes forward like Delete.
265279
if (key.ctrl && input === "d") {
@@ -279,8 +293,11 @@ export function Input({ disabled, onSubmit, onAbort, commands, history, cwd }: I
279293
// Undo
280294
if (key.ctrl && input === "z") return setState(undo(state));
281295

282-
// Printable text — Ink's useInput delivers individual chars (or pasted runs)
283-
if (input && !key.ctrl && !key.meta) {
296+
// Printable text — Ink's useInput delivers individual chars (or pasted runs).
297+
// Strip control bytes (0x00–0x1f, 0x7f) so a stray Backspace or escape
298+
// fragment doesn't end up inserted as a glyph in the buffer.
299+
const isPrintable = input && !key.ctrl && !key.meta && !/[\x00-\x1f\x7f]/.test(input);
300+
if (isPrintable) {
284301
setSuggestionIdx(0);
285302
// Once the user starts editing on top of a recalled history
286303
// entry, snap out of history mode — the entry is now their

0 commit comments

Comments
 (0)