fix(terminal): scope IME redispatch ownership

This commit is contained in:
OrcaWin
2026-08-06 19:48:57 -07:00
parent f0306d9ffc
commit ef08bfb0c7
3 changed files with 29 additions and 4 deletions
@@ -284,6 +284,7 @@ export function useTerminalKeyboardShortcuts({
// location from its own keydown event and clear it on keyup.
let optionKeyLocation = 0
const heldImeEnterModifiers = new Set<'shift' | 'ctrl'>()
const terminalImeEnterModifierKeydowns = new Set<'shift' | 'ctrl'>()
const nativeOnlyShortcutTracker = createTerminalNativeOnlyShortcutTracker()
const deferredNewlineSender = createTerminalImeDeferredNewlineSender()
const modifiedEnterChordOwner = createTerminalImeModifiedEnterChordOwner()
@@ -337,7 +338,9 @@ export function useTerminalKeyboardShortcuts({
(!keyboardScope || keyboardEventBelongsToScope(e, keyboardScope)) &&
!isEditableTarget(e.target)
) {
heldImeEnterModifiers.add(e.key === 'Shift' ? 'shift' : 'ctrl')
const kind = e.key === 'Shift' ? 'shift' : 'ctrl'
heldImeEnterModifiers.add(kind)
terminalImeEnterModifierKeydowns.add(kind)
}
}
}
@@ -613,7 +616,13 @@ export function useTerminalKeyboardShortcuts({
if ((e.isComposing || hasPendingImeComposition) && (e.key === 'Enter' || imeProcessEnter)) {
if (isWindows) {
const chord = getModifiedEnterChord(e)
if (chord && !modifiedEnterChordOwner.claim(chord)) {
const claimedChord = chord
? {
...chord,
terminalModifierKeyDownObserved: terminalImeEnterModifierKeydowns.has(chord.kind)
}
: null
if (claimedChord && !modifiedEnterChordOwner.claim(claimedChord)) {
return
}
}
@@ -825,6 +834,7 @@ export function useTerminalKeyboardShortcuts({
if (releasedImeEnterModifier) {
const kind = releasedImeEnterModifier
heldImeEnterModifiers.delete(kind)
terminalImeEnterModifierKeydowns.delete(kind)
modifiedEnterChordOwner.release({ kind, code: e.code, timeStamp: e.timeStamp })
}
if (e.key !== 'Enter') {
@@ -845,7 +855,13 @@ export function useTerminalKeyboardShortcuts({
if (originatingChord) {
e.preventDefault()
e.stopImmediatePropagation()
deferredNewlineSender.releaseRedispatchedEnter(e, originatingChord)
const modifierStillMatches = getImeEnterModifier(e) === originatingChord.kind
deferredNewlineSender.releaseRedispatchedEnter(
e,
modifierStillMatches || originatingChord.terminalModifierKeyDownObserved
? originatingChord
: undefined
)
return
}
@@ -920,6 +936,7 @@ export function useTerminalKeyboardShortcuts({
const onNativeOnlyBlur = (): void => {
nativeOnlyShortcutTracker.clear()
heldImeEnterModifiers.clear()
terminalImeEnterModifierKeydowns.clear()
modifiedEnterChordOwner.clear()
deferredNewlineSender.clearRedispatchedEnters()
observedEnterKeydownTimeStamps.clear()
@@ -300,6 +300,13 @@ describe('createTerminalImeModifiedEnterChordOwner', () => {
expect(defer).toHaveBeenCalledTimes(1)
expect(owner.absorb(chord('shift', 5037.9, 'Enter'))).toBe(true)
expect(owner.ownsRedispatchedEnter()).toBe(false)
})
it('owns a modifier-lost redispatch only after a terminal modifier keydown', () => {
const owner = createTerminalImeModifiedEnterChordOwner()
expect(owner.claim({ ...chord('shift', 10), terminalModifierKeyDownObserved: true })).toBe(true)
expect(owner.ownsRedispatchedEnter()).toBe(true)
})
@@ -65,6 +65,7 @@ export type TerminalImeModifiedEnterKind = 'shift' | 'ctrl'
export type TerminalImeModifiedEnterChord = TerminalImeEnterIdentity & {
kind: TerminalImeModifiedEnterKind
terminalModifierKeyDownObserved?: boolean
}
export type TerminalImeModifiedEnterChordOwner = {
@@ -88,7 +89,7 @@ export function createTerminalImeModifiedEnterChordOwner(): TerminalImeModifiedE
return true
},
absorb: ({ kind }) => activeChord?.kind === kind,
ownsRedispatchedEnter: () => activeChord !== null,
ownsRedispatchedEnter: () => activeChord?.terminalModifierKeyDownObserved === true,
release: ({ kind }) => {
if (activeChord?.kind === kind) {
const releasedChord = activeChord