diff --git a/apps/web/src/components/ThreadTerminalDrawer.tsx b/apps/web/src/components/ThreadTerminalDrawer.tsx index 91f4dbc1b36..14f4f640501 100644 --- a/apps/web/src/components/ThreadTerminalDrawer.tsx +++ b/apps/web/src/components/ThreadTerminalDrawer.tsx @@ -28,7 +28,11 @@ import { resolveWrappedTerminalLinkRange, wrappedTerminalLinkRangeIntersectsBufferLine, } from "../terminal-links"; -import { isTerminalClearShortcut, terminalNavigationShortcutData } from "../keybindings"; +import { + isTerminalClearShortcut, + terminalDeleteShortcutData, + terminalNavigationShortcutData, +} from "../keybindings"; import { DEFAULT_THREAD_TERMINAL_HEIGHT, DEFAULT_THREAD_TERMINAL_ID, @@ -407,6 +411,14 @@ export function TerminalViewport({ return false; } + const deleteData = terminalDeleteShortcutData(event); + if (deleteData !== null) { + event.preventDefault(); + event.stopPropagation(); + void sendTerminalInput(deleteData, "Failed to delete terminal input"); + return false; + } + if (!isTerminalClearShortcut(event)) return true; event.preventDefault(); event.stopPropagation(); diff --git a/apps/web/src/keybindings.test.ts b/apps/web/src/keybindings.test.ts index 613f67d5487..2c968e6bc32 100644 --- a/apps/web/src/keybindings.test.ts +++ b/apps/web/src/keybindings.test.ts @@ -20,6 +20,7 @@ import { resolveShortcutCommand, shouldShowThreadJumpHints, shortcutLabelForCommand, + terminalDeleteShortcutData, terminalNavigationShortcutData, threadJumpCommandForIndex, threadJumpIndexFromCommand, @@ -560,6 +561,34 @@ describe("isTerminalClearShortcut", () => { }); }); +describe("terminalDeleteShortcutData", () => { + it("maps Cmd+Backspace on macOS to delete-to-line-start", () => { + assert.strictEqual( + terminalDeleteShortcutData(event({ key: "Backspace", metaKey: true }), "MacIntel"), + "\u0015", + ); + }); + + it("ignores non-macOS platforms and modified variants", () => { + assert.isNull(terminalDeleteShortcutData(event({ key: "Backspace", metaKey: true }), "Linux")); + assert.isNull( + terminalDeleteShortcutData( + event({ key: "Backspace", metaKey: true, altKey: true }), + "MacIntel", + ), + ); + }); + + it("ignores non-keydown events", () => { + assert.isNull( + terminalDeleteShortcutData( + event({ type: "keyup", key: "Backspace", metaKey: true }), + "MacIntel", + ), + ); + }); +}); + describe("terminalNavigationShortcutData", () => { it("maps Option+Arrow on macOS to word movement", () => { assert.strictEqual( diff --git a/apps/web/src/keybindings.ts b/apps/web/src/keybindings.ts index 286454dc05e..977c1319b9e 100644 --- a/apps/web/src/keybindings.ts +++ b/apps/web/src/keybindings.ts @@ -37,6 +37,7 @@ const TERMINAL_WORD_BACKWARD = "\u001bb"; const TERMINAL_WORD_FORWARD = "\u001bf"; const TERMINAL_LINE_START = "\u0001"; const TERMINAL_LINE_END = "\u0005"; +const TERMINAL_DELETE_TO_LINE_START = "\u0015"; const EVENT_CODE_KEY_ALIASES: Readonly> = { BracketLeft: ["["], BracketRight: ["]"], @@ -370,6 +371,28 @@ export function isTerminalClearShortcut( ); } +export function terminalDeleteShortcutData( + event: ShortcutEventLike, + platform = navigator.platform, +): string | null { + if (event.type !== undefined && event.type !== "keydown") { + return null; + } + + if (!isMacPlatform(platform)) { + return null; + } + + const key = normalizeEventKey(event.key); + if (key !== "backspace") { + return null; + } + + return event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey + ? TERMINAL_DELETE_TO_LINE_START + : null; +} + export function terminalNavigationShortcutData( event: ShortcutEventLike, platform = navigator.platform,