mirror of
https://github.com/stablyai/orca
synced 2026-04-21 14:17:16 +00:00
Merge 6aa874ea6a into 8c2554ab28
This commit is contained in:
commit
41ff0304aa
3 changed files with 107 additions and 8 deletions
|
|
@ -0,0 +1,79 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
resolveTerminalShortcutAction,
|
||||
type TerminalShortcutEvent
|
||||
} from './terminal-shortcut-policy'
|
||||
|
||||
function event(overrides: Partial<TerminalShortcutEvent>): TerminalShortcutEvent {
|
||||
return {
|
||||
key: '',
|
||||
code: '',
|
||||
metaKey: false,
|
||||
ctrlKey: false,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
repeat: false,
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
describe('non-mac Ctrl+Left/Right word-nav', () => {
|
||||
// Windows Terminal, GNOME Terminal, and Konsole all bind Ctrl+←/→ to
|
||||
// word-nav. xterm.js emits \e[1;5D / \e[1;5C which default readline doesn't
|
||||
// map, so translate to \eb / \ef (same bytes as our Alt+Arrow rule).
|
||||
it('translates Ctrl+←/→ on Windows/Linux to readline \\eb / \\ef', () => {
|
||||
expect(
|
||||
resolveTerminalShortcutAction(
|
||||
event({ key: 'ArrowLeft', code: 'ArrowLeft', ctrlKey: true }),
|
||||
false
|
||||
)
|
||||
).toEqual({ type: 'sendInput', data: '\x1bb' })
|
||||
expect(
|
||||
resolveTerminalShortcutAction(
|
||||
event({ key: 'ArrowRight', code: 'ArrowRight', ctrlKey: true }),
|
||||
false
|
||||
)
|
||||
).toEqual({ type: 'sendInput', data: '\x1bf' })
|
||||
})
|
||||
|
||||
it('does not translate Ctrl+Arrow on macOS (reserved by OS)', () => {
|
||||
// Mac uses Cmd+Arrow for line-nav and Option+Arrow for word-nav.
|
||||
// Ctrl+Arrow is the macOS Mission Control / Spaces chord.
|
||||
expect(
|
||||
resolveTerminalShortcutAction(
|
||||
event({ key: 'ArrowLeft', code: 'ArrowLeft', ctrlKey: true }),
|
||||
true
|
||||
)
|
||||
).toBeNull()
|
||||
expect(
|
||||
resolveTerminalShortcutAction(
|
||||
event({ key: 'ArrowRight', code: 'ArrowRight', ctrlKey: true }),
|
||||
true
|
||||
)
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('does not intercept Ctrl+Shift+Arrow (selection passthrough)', () => {
|
||||
expect(
|
||||
resolveTerminalShortcutAction(
|
||||
event({ key: 'ArrowLeft', code: 'ArrowLeft', ctrlKey: true, shiftKey: true }),
|
||||
false
|
||||
)
|
||||
).toBeNull()
|
||||
expect(
|
||||
resolveTerminalShortcutAction(
|
||||
event({ key: 'ArrowRight', code: 'ArrowRight', ctrlKey: true, shiftKey: true }),
|
||||
false
|
||||
)
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('does not intercept Ctrl+Alt+Arrow (different chord)', () => {
|
||||
expect(
|
||||
resolveTerminalShortcutAction(
|
||||
event({ key: 'ArrowLeft', code: 'ArrowLeft', ctrlKey: true, altKey: true }),
|
||||
false
|
||||
)
|
||||
).toBeNull()
|
||||
})
|
||||
})
|
||||
|
|
@ -110,14 +110,6 @@ describe('resolveTerminalShortcutAction', () => {
|
|||
true
|
||||
)
|
||||
).toBeNull()
|
||||
|
||||
// Non-Mac Ctrl+Arrow must pass through unchanged (readline's word-nav there).
|
||||
expect(
|
||||
resolveTerminalShortcutAction(
|
||||
event({ key: 'ArrowLeft', code: 'ArrowLeft', ctrlKey: true }),
|
||||
false
|
||||
)
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('uses ctrl as the non-mac pane modifier but still requires shift for tab-safe chords', () => {
|
||||
|
|
@ -217,6 +209,14 @@ describe('resolveTerminalShortcutAction', () => {
|
|||
)
|
||||
).toBeNull()
|
||||
|
||||
// Ctrl+Alt+Arrow (Linux workspace switching on some desktops) must pass through on non-Mac.
|
||||
expect(
|
||||
resolveTerminalShortcutAction(
|
||||
event({ key: 'ArrowLeft', code: 'ArrowLeft', ctrlKey: true, altKey: true }),
|
||||
false
|
||||
)
|
||||
).toBeNull()
|
||||
|
||||
// Regression guard: plain ArrowLeft must still pass through untouched.
|
||||
expect(
|
||||
resolveTerminalShortcutAction(event({ key: 'ArrowLeft', code: 'ArrowLeft' }), true)
|
||||
|
|
|
|||
|
|
@ -176,6 +176,26 @@ export function resolveTerminalShortcutAction(
|
|||
return { type: 'sendInput', data: event.key === 'ArrowLeft' ? '\x1bb' : '\x1bf' }
|
||||
}
|
||||
|
||||
if (
|
||||
!isMac &&
|
||||
!event.metaKey &&
|
||||
event.ctrlKey &&
|
||||
!event.altKey &&
|
||||
!event.shiftKey &&
|
||||
(event.key === 'ArrowLeft' || event.key === 'ArrowRight')
|
||||
) {
|
||||
// Why: Windows Terminal, GNOME Terminal, and Konsole all bind Ctrl+←/→ for
|
||||
// word navigation on Linux/Windows — but xterm.js emits \e[1;5D / \e[1;5C,
|
||||
// which default readline (bash, zsh) does not bind to backward-word /
|
||||
// forward-word. Translate to \eb / \ef (same bytes as our Alt+Arrow rule)
|
||||
// so Ctrl+←/→ works for word-nav matching user expectations on those
|
||||
// platforms without requiring a custom inputrc.
|
||||
//
|
||||
// Mac-gated: Ctrl+Arrow on macOS is reserved for Mission Control / Spaces
|
||||
// navigation at the OS level and should never reach the app.
|
||||
return { type: 'sendInput', data: event.key === 'ArrowLeft' ? '\x1bb' : '\x1bf' }
|
||||
}
|
||||
|
||||
// Why: with macOptionIsMeta disabled (to let non-US keyboard layouts compose
|
||||
// characters like @ and €), xterm.js no longer translates Option+letter into
|
||||
// Esc+letter automatically. We match on event.code (physical key) rather than
|
||||
|
|
|
|||
Loading…
Reference in a new issue