diff --git a/src/renderer/src/components/terminal-pane/keyboard-handlers.ts b/src/renderer/src/components/terminal-pane/keyboard-handlers.ts index b208368a40d..6580bbb81b8 100644 --- a/src/renderer/src/components/terminal-pane/keyboard-handlers.ts +++ b/src/renderer/src/components/terminal-pane/keyboard-handlers.ts @@ -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() diff --git a/src/renderer/src/components/terminal-pane/terminal-ime-deferred-newline.test.ts b/src/renderer/src/components/terminal-pane/terminal-ime-deferred-newline.test.ts index 0af8344c8f2..d1ef2b882eb 100644 --- a/src/renderer/src/components/terminal-pane/terminal-ime-deferred-newline.test.ts +++ b/src/renderer/src/components/terminal-pane/terminal-ime-deferred-newline.test.ts @@ -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) }) diff --git a/src/renderer/src/components/terminal-pane/terminal-ime-deferred-newline.ts b/src/renderer/src/components/terminal-pane/terminal-ime-deferred-newline.ts index dfc4391d781..2fd141fe3ce 100644 --- a/src/renderer/src/components/terminal-pane/terminal-ime-deferred-newline.ts +++ b/src/renderer/src/components/terminal-pane/terminal-ime-deferred-newline.ts @@ -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