fix: let Ctrl+D pass through as EOF on Windows/Linux (#599)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jinwoo Hong
2026-04-13 19:38:37 -04:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 7cdc866f96
commit b1a10c7ef2
4 changed files with 91 additions and 8 deletions
@@ -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)',
@@ -105,12 +105,16 @@ export default function TerminalContextMenu({
<DropdownMenuItem onSelect={onSplitRight}>
<PanelRightOpen />
Split Right
<DropdownMenuShortcut>{mod}D</DropdownMenuShortcut>
{/* Why: on Windows/Linux, Ctrl+D must pass through as EOF (#586),
so split-right requires Shift on non-Mac platforms. */}
<DropdownMenuShortcut>{isMac ? `${mod}D` : `${mod}${shift}D`}</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem onSelect={onSplitDown}>
<PanelBottomOpen />
Split Down
<DropdownMenuShortcut>{`${mod}${shift}D`}</DropdownMenuShortcut>
{/* Why: on Windows/Linux, Alt+Shift+D is used for split-down because
Ctrl+Shift+D is taken by split-right (#586). */}
<DropdownMenuShortcut>{isMac ? `${mod}${shift}D` : `Alt+${shift}D`}</DropdownMenuShortcut>
</DropdownMenuItem>
{canExpandPane && (
<DropdownMenuItem onSelect={onToggleExpand}>
@@ -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' })
})
})
@@ -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 &&