Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 57 additions & 20 deletions patches/@xterm__xterm@6.1.0-beta.287.patch

Large diffs are not rendered by default.

30 changes: 15 additions & 15 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,60 @@ describe('terminal keyboard controller', () => {
fixture.dispose()
}
})

it('leaves IME-processed (keyCode 229) punctuation keydowns to xterm', () => {
// macOS Chromium reports every keydown with keyCode 229 while an IME is
// active — including its ASCII/English mode — and xterm then batches the
// inserted text behind CompositionHelper's 0ms textarea diff. Claiming
// these keydowns made the forwarder clear the shared textarea mid-window,
// which dropped fast-typed letters and could emit a DEL that swallowed
// the just-forwarded character (the "cannot type ?" symptom).
const fixture = setup({
platform: 'darwin',
inputSourceFeatures: {
forwardAsciiPunctuation: true,
forwardShortTextReplacements: false
}
})
try {
expect(
fixture.controller.handle(
keyEvent('keydown', '?', { code: 'Slash', keyCode: 229, shiftKey: true })
)
).toBe(true)
expect(
fixture.controller.handle(keyEvent('keydown', '?', { code: 'Slash', keyCode: 229 }))
).toBe(true)
fixture.textarea.value = 'why?'
fixture.textarea.dispatchEvent(new InputEvent('input', {
bubbles: true,
inputType: 'insertText',
data: '?'
}))
// Unclaimed input events must pass through untouched: nothing forwarded
// and the textarea (xterm's diff baseline) must not be cleared.
expect(fixture.sent).toEqual([])
expect(fixture.textarea.value).toBe('why?')
} finally {
fixture.dispose()
}
})

it('leaves IME-processed letter keydowns to xterm even when replacements are on', () => {
const fixture = setup({
platform: 'darwin',
inputSourceFeatures: {
forwardAsciiPunctuation: true,
forwardShortTextReplacements: true
}
})
try {
expect(
fixture.controller.handle(keyEvent('keydown', 'a', { code: 'KeyA', keyCode: 229 }))
).toBe(true)
expect(fixture.sent).toEqual([])
} finally {
fixture.dispose()
}
})
})
14 changes: 14 additions & 0 deletions ui/src/components/workspace/terminal-ime-native-text-candidates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ function isSyntheticUnicodeTextKey(event: ImeNativeTextKeyEvent): boolean {
return isSinglePrintableTextKey(event.key)
}

function isImeProcessedKeydown(event: ImeNativeTextKeyEvent): boolean {
return (event.keyCode ?? event.which) === 229
}

export function isImeNativeTextKeydownCandidate(
event: ImeNativeTextKeyEvent,
compositionActive: boolean,
Expand All @@ -88,6 +92,16 @@ export function isImeNativeTextKeydownCandidate(
if (event.type !== 'keydown') return false
if (event.ctrlKey || event.altKey || event.metaKey) return false
if (event.isComposing === true || compositionActive) return false
// keyCode 229 marks a keydown the IME owns (macOS Chromium keeps the real
// character in `key` while an IME is active, even in its ASCII mode).
// xterm routes every 229 keydown through CompositionHelper's textarea-diff
// path, which batches pending inserts behind one 0ms timer over the shared
// helper textarea. Claiming such keys here makes the forwarder clear that
// textarea mid-window, so the pending diff loses fast-typed letters and can
// even emit a spurious DEL that swallows the just-forwarded character.
// The 229 path already forwards IME-produced text (including full-width
// punctuation) byte-faithfully, so the claim is only for non-IME keydowns.
if (isImeProcessedKeydown(event)) return false
if (isSyntheticUnicodeTextKey(event)) return true
if (isCjkDirectPunctuationKey(event.key)) return true
if (inputSourceFeatures.forwardAsciiPunctuation && isAsciiPunctuationKey(event.key)) return true
Expand Down