| author | |
| committer | |
| log | 7968f278ee54202cc3ae63f8e52d224b13e0265f |
| tree | e4ce6526725a0ebfaf55a6f42cbbe8d3d78e7e08 |
| parent | 44afe784a3b17f0468cc73aeb87b0870d0d78d55 |
| signature | Signed by PGP key B5690EEEBB952194 |
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"; |
| 31 | import { isTerminalClearShortcut, terminalNavigationShortcutData } from "../keybindings"; | 31 | import { |
| 32 | isTerminalClearShortcut, | ||
| 33 | terminalDeleteShortcutData, | ||
| 34 | terminalNavigationShortcutData, | ||
| 35 | } from "../keybindings"; | ||
| 32 | import { | 36 | import { |
| 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 | } |
| 409 | 413 | ||
| 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 | }); |
| 562 | 563 | ||
| 564 | describe("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 | |||
| 563 | describe("terminalNavigationShortcutData", () => { | 592 | describe("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"; |
| 37 | const TERMINAL_WORD_FORWARD = "\u001bf"; | 37 | const TERMINAL_WORD_FORWARD = "\u001bf"; |
| 38 | const TERMINAL_LINE_START = "\u0001"; | 38 | const TERMINAL_LINE_START = "\u0001"; |
| 39 | const TERMINAL_LINE_END = "\u0005"; | 39 | const TERMINAL_LINE_END = "\u0005"; |
| 40 | const TERMINAL_DELETE_TO_LINE_START = "\u0015"; | ||
| 40 | const EVENT_CODE_KEY_ALIASES: Readonly<Record<string, readonly string[]>> = { | 41 | const 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 | } |
| 372 | 373 | ||
| 374 | export 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 | |||
| 373 | export function terminalNavigationShortcutData( | 396 | export function terminalNavigationShortcutData( |
| 374 | event: ShortcutEventLike, | 397 | event: ShortcutEventLike, |
| 375 | platform = navigator.platform, | 398 | platform = navigator.platform, |