authorgravatar for 126517689+SAKETH11111@users.noreply.github.comSaketh <126517689+SAKETH11111@users.noreply.github.com> 2026-04-15 01:29:45-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-04-15 06:29:45+00:00
log7968f278ee54202cc3ae63f8e52d224b13e0265f
treee4ce6526725a0ebfaf55a6f42cbbe8d3d78e7e08
parent44afe784a3b17f0468cc73aeb87b0870d0d78d55
signature Signed by PGP key B5690EEEBB952194

Fix terminal Cmd+Backspace on macOS (#2027)

Co-authored-by: Julius Marminge <julius0216@outlook.com>

3 files changed, 65 insertions(+), 1 deletions(-)

apps/web/src/components/ThreadTerminalDrawer.tsx+13-1
...@@ -28,7 +28,11 @@ import {...@@ -28,7 +28,11 @@ import {
28 resolveWrappedTerminalLinkRange,28 resolveWrappedTerminalLinkRange,
29 wrappedTerminalLinkRangeIntersectsBufferLine,29 wrappedTerminalLinkRangeIntersectsBufferLine,
30} from "../terminal-links";30} from "../terminal-links";
31import { isTerminalClearShortcut, terminalNavigationShortcutData } from "../keybindings";31import {
32 isTerminalClearShortcut,
33 terminalDeleteShortcutData,
34 terminalNavigationShortcutData,
35} from "../keybindings";
32import {36import {
33 DEFAULT_THREAD_TERMINAL_HEIGHT,37 DEFAULT_THREAD_TERMINAL_HEIGHT,
34 DEFAULT_THREAD_TERMINAL_ID,38 DEFAULT_THREAD_TERMINAL_ID,
...@@ -407,6 +411,14 @@ export function TerminalViewport({...@@ -407,6 +411,14 @@ export function TerminalViewport({
407 return false;411 return false;
408 }412 }
409413
414 const deleteData = terminalDeleteShortcutData(event);
415 if (deleteData !== null) {
416 event.preventDefault();
417 event.stopPropagation();
418 void sendTerminalInput(deleteData, "Failed to delete terminal input");
419 return false;
420 }
421
410 if (!isTerminalClearShortcut(event)) return true;422 if (!isTerminalClearShortcut(event)) return true;
411 event.preventDefault();423 event.preventDefault();
412 event.stopPropagation();424 event.stopPropagation();
apps/web/src/keybindings.test.ts+29
...@@ -20,6 +20,7 @@ import {...@@ -20,6 +20,7 @@ import {
20 resolveShortcutCommand,20 resolveShortcutCommand,
21 shouldShowThreadJumpHints,21 shouldShowThreadJumpHints,
22 shortcutLabelForCommand,22 shortcutLabelForCommand,
23 terminalDeleteShortcutData,
23 terminalNavigationShortcutData,24 terminalNavigationShortcutData,
24 threadJumpCommandForIndex,25 threadJumpCommandForIndex,
25 threadJumpIndexFromCommand,26 threadJumpIndexFromCommand,
...@@ -560,6 +561,34 @@ describe("isTerminalClearShortcut", () => {...@@ -560,6 +561,34 @@ describe("isTerminalClearShortcut", () => {
560 });561 });
561});562});
562563
564describe("terminalDeleteShortcutData", () => {
565 it("maps Cmd+Backspace on macOS to delete-to-line-start", () => {
566 assert.strictEqual(
567 terminalDeleteShortcutData(event({ key: "Backspace", metaKey: true }), "MacIntel"),
568 "\u0015",
569 );
570 });
571
572 it("ignores non-macOS platforms and modified variants", () => {
573 assert.isNull(terminalDeleteShortcutData(event({ key: "Backspace", metaKey: true }), "Linux"));
574 assert.isNull(
575 terminalDeleteShortcutData(
576 event({ key: "Backspace", metaKey: true, altKey: true }),
577 "MacIntel",
578 ),
579 );
580 });
581
582 it("ignores non-keydown events", () => {
583 assert.isNull(
584 terminalDeleteShortcutData(
585 event({ type: "keyup", key: "Backspace", metaKey: true }),
586 "MacIntel",
587 ),
588 );
589 });
590});
591
563describe("terminalNavigationShortcutData", () => {592describe("terminalNavigationShortcutData", () => {
564 it("maps Option+Arrow on macOS to word movement", () => {593 it("maps Option+Arrow on macOS to word movement", () => {
565 assert.strictEqual(594 assert.strictEqual(
apps/web/src/keybindings.ts+23
...@@ -37,6 +37,7 @@ const TERMINAL_WORD_BACKWARD = "\u001bb";...@@ -37,6 +37,7 @@ const TERMINAL_WORD_BACKWARD = "\u001bb";
37const TERMINAL_WORD_FORWARD = "\u001bf";37const TERMINAL_WORD_FORWARD = "\u001bf";
38const TERMINAL_LINE_START = "\u0001";38const TERMINAL_LINE_START = "\u0001";
39const TERMINAL_LINE_END = "\u0005";39const TERMINAL_LINE_END = "\u0005";
40const TERMINAL_DELETE_TO_LINE_START = "\u0015";
40const EVENT_CODE_KEY_ALIASES: Readonly<Record<string, readonly string[]>> = {41const EVENT_CODE_KEY_ALIASES: Readonly<Record<string, readonly string[]>> = {
41 BracketLeft: ["["],42 BracketLeft: ["["],
42 BracketRight: ["]"],43 BracketRight: ["]"],
...@@ -370,6 +371,28 @@ export function isTerminalClearShortcut(...@@ -370,6 +371,28 @@ export function isTerminalClearShortcut(
370 );371 );
371}372}
372373
374export function terminalDeleteShortcutData(
375 event: ShortcutEventLike,
376 platform = navigator.platform,
377): string | null {
378 if (event.type !== undefined && event.type !== "keydown") {
379 return null;
380 }
381
382 if (!isMacPlatform(platform)) {
383 return null;
384 }
385
386 const key = normalizeEventKey(event.key);
387 if (key !== "backspace") {
388 return null;
389 }
390
391 return event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey
392 ? TERMINAL_DELETE_TO_LINE_START
393 : null;
394}
395
373export function terminalNavigationShortcutData(396export function terminalNavigationShortcutData(
374 event: ShortcutEventLike,397 event: ShortcutEventLike,
375 platform = navigator.platform,398 platform = navigator.platform,