From b1a10c7ef24e72043042728ccde4fcf774fba5af Mon Sep 17 00:00:00 2001 From: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com> Date: Mon, 13 Apr 2026 19:38:37 -0400 Subject: [PATCH] fix: let Ctrl+D pass through as EOF on Windows/Linux (#599) Co-authored-by: Claude Opus 4.6 (1M context) --- .../src/components/settings/ShortcutsPane.tsx | 10 ++-- .../terminal-pane/TerminalContextMenu.tsx | 8 ++- .../terminal-shortcut-policy.test.ts | 49 +++++++++++++++++++ .../terminal-pane/terminal-shortcut-policy.ts | 32 ++++++++++-- 4 files changed, 91 insertions(+), 8 deletions(-) diff --git a/src/renderer/src/components/settings/ShortcutsPane.tsx b/src/renderer/src/components/settings/ShortcutsPane.tsx index b47e9034cb1..3bc2a36aff6 100644 --- a/src/renderer/src/components/settings/ShortcutsPane.tsx +++ b/src/renderer/src/components/settings/ShortcutsPane.tsx @@ -36,7 +36,7 @@ const SHORTCUT_GROUP_DEFINITIONS: ShortcutGroupDefinition[] = [ { action: 'Switch worktree', searchKeywords: ['shortcut', 'global', 'worktree', 'switch', 'jump'], - keys: ({ mod, shift }) => mod === '⌘' ? [mod, 'J'] : [mod, shift, 'J'] + keys: ({ mod, shift }) => (mod === '⌘' ? [mod, 'J'] : [mod, shift, 'J']) }, { action: 'Create worktree', @@ -131,12 +131,16 @@ const SHORTCUT_GROUP_DEFINITIONS: ShortcutGroupDefinition[] = [ { action: 'Split pane right', searchKeywords: ['shortcut', 'pane', 'split'], - keys: ({ mod }) => [mod, 'D'] + // Why: on Windows/Linux, Ctrl+D must pass through as EOF (#586), + // so split-right requires Shift on non-Mac platforms. + keys: ({ mod, shift }) => (mod === '⌘' ? [mod, 'D'] : [mod, shift, 'D']) }, { action: 'Split pane down', searchKeywords: ['shortcut', 'pane', 'split'], - keys: ({ mod, shift }) => [mod, shift, 'D'] + // Why: on Windows/Linux, Ctrl+Shift+D is taken by split-right (#586), + // so split-down uses Alt+Shift+D following Windows Terminal convention. + keys: ({ mod, shift }) => (mod === '⌘' ? [mod, shift, 'D'] : ['Alt', shift, 'D']) }, { action: 'Close pane (EOF)', diff --git a/src/renderer/src/components/terminal-pane/TerminalContextMenu.tsx b/src/renderer/src/components/terminal-pane/TerminalContextMenu.tsx index d82beff2e29..84748da6b79 100644 --- a/src/renderer/src/components/terminal-pane/TerminalContextMenu.tsx +++ b/src/renderer/src/components/terminal-pane/TerminalContextMenu.tsx @@ -105,12 +105,16 @@ export default function TerminalContextMenu({ Split Right - {mod}D + {/* Why: on Windows/Linux, Ctrl+D must pass through as EOF (#586), + so split-right requires Shift on non-Mac platforms. */} + {isMac ? `${mod}D` : `${mod}${shift}D`} Split Down - {`${mod}${shift}D`} + {/* Why: on Windows/Linux, Alt+Shift+D is used for split-down because + Ctrl+Shift+D is taken by split-right (#586). */} + {isMac ? `${mod}${shift}D` : `Alt+${shift}D`} {canExpandPane && ( diff --git a/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.test.ts b/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.test.ts index b29ea419a4a..9a93f99fa6e 100644 --- a/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.test.ts +++ b/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.test.ts @@ -106,4 +106,53 @@ describe('resolveTerminalShortcutAction', () => { resolveTerminalShortcutAction(event({ key: 'r', code: 'KeyR', ctrlKey: true }), false) ).toBeNull() }) + + it('lets Ctrl+D pass through as EOF on non-Mac, requires Shift for split (#586)', () => { + // Ctrl+D without Shift on Windows/Linux must NOT trigger split — it's EOF + expect( + resolveTerminalShortcutAction(event({ key: 'd', code: 'KeyD', ctrlKey: true }), false) + ).toBeNull() + + // Ctrl+Shift+D on Windows/Linux splits the pane right (vertical) + expect( + resolveTerminalShortcutAction( + event({ key: 'd', code: 'KeyD', ctrlKey: true, shiftKey: true }), + false + ) + ).toEqual({ type: 'splitActivePane', direction: 'vertical' }) + + // Alt+Shift+D on Windows/Linux splits the pane down (horizontal) + expect( + resolveTerminalShortcutAction( + event({ key: 'd', code: 'KeyD', altKey: true, shiftKey: true }), + false + ) + ).toEqual({ type: 'splitActivePane', direction: 'horizontal' }) + + // Alt+Shift+D should NOT trigger split-down on Mac (Mac uses Cmd+Shift+D) + expect( + resolveTerminalShortcutAction( + event({ key: 'd', code: 'KeyD', altKey: true, shiftKey: true }), + true + ) + ).toBeNull() + + // Alt+D (no Shift) on Windows/Linux must pass through for readline forward-word-delete + expect( + resolveTerminalShortcutAction(event({ key: 'd', code: 'KeyD', altKey: true }), false) + ).toBeNull() + }) + + it('keeps Cmd+D and Cmd+Shift+D for split on macOS', () => { + expect( + resolveTerminalShortcutAction(event({ key: 'd', code: 'KeyD', metaKey: true }), true) + ).toEqual({ type: 'splitActivePane', direction: 'vertical' }) + + expect( + resolveTerminalShortcutAction( + event({ key: 'd', code: 'KeyD', metaKey: true, shiftKey: true }), + true + ) + ).toEqual({ type: 'splitActivePane', direction: 'horizontal' }) + }) }) diff --git a/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.ts b/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.ts index 02c0d60433a..230909360e5 100644 --- a/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.ts +++ b/src/renderer/src/components/terminal-pane/terminal-shortcut-policy.ts @@ -58,13 +58,39 @@ export function resolveTerminalShortcutAction( } if (lowerKey === 'd') { - return { - type: 'splitActivePane', - direction: event.shiftKey ? 'horizontal' : 'vertical' + if (isMac) { + return { + type: 'splitActivePane', + direction: event.shiftKey ? 'horizontal' : 'vertical' + } } + // Why: on Windows/Linux, Ctrl+D is the standard EOF signal for terminals. + // Binding Ctrl+D to split-pane would swallow EOF and break shell workflows + // (see #586). Only Ctrl+Shift+D triggers split on non-Mac platforms; + // Ctrl+D (without Shift) falls through to the terminal as normal input. + if (event.shiftKey) { + return { type: 'splitActivePane', direction: 'vertical' } + } + return null } } + // Why: on Windows/Linux, Alt+Shift+D splits the pane down (horizontal). + // This lives outside the mod+!alt block above because it uses Alt instead + // of Ctrl, following the Windows Terminal convention for split shortcuts + // and avoiding the Ctrl+D / EOF conflict (see #586). + if ( + !isMac && + !event.repeat && + !event.metaKey && + !event.ctrlKey && + event.altKey && + event.shiftKey && + event.key.toLowerCase() === 'd' + ) { + return { type: 'splitActivePane', direction: 'horizontal' } + } + if ( !event.metaKey && !event.ctrlKey &&