diff --git a/packages/app/src/terminal/runtime/terminal-emulator-runtime.ts b/packages/app/src/terminal/runtime/terminal-emulator-runtime.ts index f4a6df21c..3e33fc6ee 100644 --- a/packages/app/src/terminal/runtime/terminal-emulator-runtime.ts +++ b/packages/app/src/terminal/runtime/terminal-emulator-runtime.ts @@ -317,7 +317,9 @@ export class TerminalEmulatorRuntime { !shouldInterceptDomTerminalKey({ key: normalizedKey, ctrlKey: event.ctrlKey, + shiftKey: event.shiftKey, altKey: event.altKey, + metaKey: event.metaKey, pendingModifiers: this.pendingModifiers, }) ) { diff --git a/packages/app/src/utils/terminal-keys.test.ts b/packages/app/src/utils/terminal-keys.test.ts index 2a627d587..96ac8340e 100644 --- a/packages/app/src/utils/terminal-keys.test.ts +++ b/packages/app/src/utils/terminal-keys.test.ts @@ -57,7 +57,9 @@ describe("terminal key helpers", () => { shouldInterceptDomTerminalKey({ key: "Escape", ctrlKey: false, + shiftKey: false, altKey: false, + metaKey: false, pendingModifiers: { ctrl: false, shift: false, alt: false }, }), ).toBe(false); @@ -65,7 +67,9 @@ describe("terminal key helpers", () => { shouldInterceptDomTerminalKey({ key: "c", ctrlKey: true, + shiftKey: false, altKey: false, + metaKey: false, pendingModifiers: { ctrl: false, shift: false, alt: false }, }), ).toBe(false); @@ -73,7 +77,9 @@ describe("terminal key helpers", () => { shouldInterceptDomTerminalKey({ key: "c", ctrlKey: false, + shiftKey: false, altKey: false, + metaKey: false, pendingModifiers: { ctrl: true, shift: false, alt: false }, }), ).toBe(true); @@ -81,12 +87,73 @@ describe("terminal key helpers", () => { shouldInterceptDomTerminalKey({ key: "Escape", ctrlKey: false, + shiftKey: false, altKey: false, + metaKey: false, pendingModifiers: { ctrl: false, shift: false, alt: true }, }), ).toBe(true); }); + it("intercepts Enter with DOM shift modifier for CSI u encoding", () => { + expect( + shouldInterceptDomTerminalKey({ + key: "Enter", + ctrlKey: false, + shiftKey: true, + altKey: false, + metaKey: false, + pendingModifiers: { ctrl: false, shift: false, alt: false }, + }), + ).toBe(true); + }); + + it("intercepts Enter with any DOM modifier for CSI u encoding", () => { + expect( + shouldInterceptDomTerminalKey({ + key: "Enter", + ctrlKey: true, + shiftKey: false, + altKey: false, + metaKey: false, + pendingModifiers: { ctrl: false, shift: false, alt: false }, + }), + ).toBe(true); + expect( + shouldInterceptDomTerminalKey({ + key: "Enter", + ctrlKey: false, + shiftKey: false, + altKey: true, + metaKey: false, + pendingModifiers: { ctrl: false, shift: false, alt: false }, + }), + ).toBe(true); + expect( + shouldInterceptDomTerminalKey({ + key: "Enter", + ctrlKey: false, + shiftKey: false, + altKey: false, + metaKey: true, + pendingModifiers: { ctrl: false, shift: false, alt: false }, + }), + ).toBe(true); + }); + + it("does not intercept plain Enter without modifiers", () => { + expect( + shouldInterceptDomTerminalKey({ + key: "Enter", + ctrlKey: false, + shiftKey: false, + altKey: false, + metaKey: false, + pendingModifiers: { ctrl: false, shift: false, alt: false }, + }), + ).toBe(false); + }); + it("detects pending modifier state", () => { expect(hasPendingTerminalModifiers({ ctrl: false, shift: false, alt: false })).toBe(false); expect(hasPendingTerminalModifiers({ ctrl: true, shift: false, alt: false })).toBe(true); diff --git a/packages/app/src/utils/terminal-keys.ts b/packages/app/src/utils/terminal-keys.ts index 55c7c523a..bafd5d2e8 100644 --- a/packages/app/src/utils/terminal-keys.ts +++ b/packages/app/src/utils/terminal-keys.ts @@ -85,10 +85,21 @@ export function hasPendingTerminalModifiers(modifiers: PendingTerminalModifiers) export function shouldInterceptDomTerminalKey(args: { key: string; ctrlKey: boolean; + shiftKey: boolean; altKey: boolean; + metaKey: boolean; pendingModifiers: PendingTerminalModifiers; }): boolean { - return hasPendingTerminalModifiers(args.pendingModifiers); + if (hasPendingTerminalModifiers(args.pendingModifiers)) { + return true; + } + // xterm.js sends plain \r for Enter regardless of modifiers. + // Intercept modified Enter so it gets CSI u encoding (Kitty keyboard protocol), + // which Claude Code and other TUI apps use for Shift+Enter newlines. + if (args.key === "Enter" && (args.shiftKey || args.ctrlKey || args.altKey || args.metaKey)) { + return true; + } + return false; } export function mergeTerminalModifiers(args: {