mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
fix(terminal): keep IME-consumed Process chords out of Orca shortcuts
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -280,7 +280,10 @@ describe('Windows IME keyboard ownership', () => {
|
||||
it.each([
|
||||
// Why: an active IME reports every consumed key as Process/229, e.g. Shift+ㅃ or an MS-IME Ctrl chord.
|
||||
{ label: 'Shift+KeyQ', code: 'KeyQ', modifier: { shiftKey: true } },
|
||||
{ label: 'Ctrl+KeyI', code: 'KeyI', modifier: { ctrlKey: true } }
|
||||
{ label: 'Ctrl+KeyI', code: 'KeyI', modifier: { ctrlKey: true } },
|
||||
// Ctrl+K / Ctrl+W are bound on Windows, so a leaked Process chord would clear or close the pane.
|
||||
{ label: 'Ctrl+KeyK', code: 'KeyK', modifier: { ctrlKey: true } },
|
||||
{ label: 'Ctrl+KeyW', code: 'KeyW', modifier: { ctrlKey: true } }
|
||||
])('does not treat an IME-consumed $label as a modified Enter', ({ code, modifier }) => {
|
||||
const harness = createHarness()
|
||||
const hook = renderHook(() => useTerminalKeyboardShortcuts(harness.deps))
|
||||
@@ -299,6 +302,36 @@ describe('Windows IME keyboard ownership', () => {
|
||||
|
||||
expect(consumed.defaultPrevented).toBe(false)
|
||||
expect(harness.sendInput).not.toHaveBeenCalled()
|
||||
expect(harness.deps.onClearPaneScrollback).not.toHaveBeenCalled()
|
||||
expect(harness.deps.onRequestClosePane).not.toHaveBeenCalled()
|
||||
hook.unmount()
|
||||
harness.dispose()
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ label: 'Ctrl+KeyK', code: 'KeyK' },
|
||||
{ label: 'Ctrl+KeyW', code: 'KeyW' }
|
||||
])('stops an IME-consumed $label from reaching window-level shortcuts', ({ code }) => {
|
||||
const harness = createHarness()
|
||||
const hook = renderHook(() => useTerminalKeyboardShortcuts(harness.deps))
|
||||
const windowHandler = vi.fn()
|
||||
window.addEventListener('keydown', windowHandler)
|
||||
harness.startComposition()
|
||||
|
||||
harness.terminalInput.dispatchEvent(
|
||||
keyboardEvent('keydown', {
|
||||
key: 'Process',
|
||||
code,
|
||||
keyCode: 229,
|
||||
timeStamp: 10,
|
||||
isComposing: true,
|
||||
ctrlKey: true
|
||||
})
|
||||
)
|
||||
vi.runAllTimers()
|
||||
|
||||
expect(windowHandler).not.toHaveBeenCalled()
|
||||
window.removeEventListener('keydown', windowHandler)
|
||||
hook.unmount()
|
||||
harness.dispose()
|
||||
})
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
createTerminalImeDeferredNewlineSender,
|
||||
createTerminalImeModifiedEnterChordOwner,
|
||||
getTerminalImeModifiedEnterKind,
|
||||
isTerminalImeConsumedKey,
|
||||
isTerminalImeEnterKeyUp,
|
||||
isTerminalImeProcessEnter
|
||||
} from './terminal-ime-deferred-newline'
|
||||
@@ -500,6 +501,18 @@ export function useTerminalKeyboardShortcuts({
|
||||
terminalPaneForImeShortcut?.terminal.element
|
||||
)
|
||||
const imeProcessEnter = isWindows && hasPendingImeComposition && isTerminalImeProcessEnter(e)
|
||||
if (
|
||||
isWindows &&
|
||||
hasPendingImeComposition &&
|
||||
!imeProcessEnter &&
|
||||
isTerminalImeConsumedKey(e)
|
||||
) {
|
||||
// Why: Process has no logical key, so shortcut matching would fall back to the physical code and
|
||||
// fire Ctrl+K/Ctrl+W here and in window-level handlers mid-composition. xterm already ignores
|
||||
// keyCode 229 while composing, so swallowing the chord loses no input.
|
||||
e.stopImmediatePropagation()
|
||||
return
|
||||
}
|
||||
const shortcutEvent = imeProcessEnter
|
||||
? {
|
||||
key: 'Enter',
|
||||
|
||||
@@ -106,6 +106,11 @@ export function getTerminalImeModifiedEnterKind(
|
||||
return null
|
||||
}
|
||||
|
||||
// Why: an active Windows IME reports every key it consumes as Process/229, whatever the physical key was.
|
||||
export function isTerminalImeConsumedKey(event: Pick<KeyboardEvent, 'key' | 'keyCode'>): boolean {
|
||||
return event.key === 'Process' && event.keyCode === 229
|
||||
}
|
||||
|
||||
export function isTerminalImeProcessEnter(
|
||||
event: Pick<
|
||||
KeyboardEvent,
|
||||
@@ -113,9 +118,9 @@ export function isTerminalImeProcessEnter(
|
||||
>
|
||||
): boolean {
|
||||
return (
|
||||
event.key === 'Process' &&
|
||||
event.keyCode === 229 &&
|
||||
// Why: an active Windows IME reports every consumed key as Process/229, so only the physical code distinguishes Enter.
|
||||
isTerminalImeConsumedKey(event) &&
|
||||
// Only the physical code distinguishes Enter. Keep this strict: a key event without a scan code
|
||||
// reports code '' and falls back to the Enter keyup path rather than guessing.
|
||||
(event.code === 'Enter' || event.code === 'NumpadEnter') &&
|
||||
getTerminalImeModifiedEnterKind(event) !== null
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user