Skip to content

Korean/CJK IME input drops characters in the terminal#30

Merged
simion merged 2 commits into
simion:mainfrom
Leesin0222:fix/cjk-ime-input
Jun 12, 2026
Merged

Korean/CJK IME input drops characters in the terminal#30
simion merged 2 commits into
simion:mainfrom
Leesin0222:fix/cjk-ime-input

Conversation

@Leesin0222

@Leesin0222 Leesin0222 commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Typing Korean (and other IME-composed CJK) into any terminal pane dropped characters: each syllable collapsed to its leading jamo, so 안녕하세요 came out as ㅇㄴㅎ세. ASCII input was unaffected. This bridges the gap WebKit leaves in xterm.js's IME handling so composed input reaches the PTY intact.

fix issue #29

AS-IS

image

TO-BE

image

Root cause

WebKit (WKWebView) does not drive CJK composition through the standard compositionstart / update / end events. Those never fire; on every keystroke isComposing stays false and keyCode is always 229. Instead WebKit composes directly in xterm's helper textarea via input events:

  • inputType: "insertText": a fresh jamo is appended (syllable start)
  • inputType: "insertReplacementText": the in-progress syllable is refined

xterm's _inputEvent forwards only insertText and ignores everything else, so every refinement is dropped and only the leading jamo of each syllable reaches the PTY.

Event trace from the live app (localStorage.imeDebug=1), typing 안녕:

input insertText data="ㅇ" taValue="ㅇ" → forwarded ✓
input insertReplacementText data="아" taValue="아" → DROPPED ✗
input insertReplacementText data="안" taValue="안" → DROPPED ✗
input insertText data="ㄴ" taValue="안ㄴ" → forwarded ✓
input insertReplacementText data="녀" taValue="안녀" → DROPPED ✗
input insertReplacementText data="녕" taValue="안녕" → DROPPED ✗

PTY receives + = ㅇㄴ, matching the symptom.

Fix

New src/lib/ime.ts (setupImeReplacementBridge): for every input event xterm drops (replacement + composition-delete types), diff the textarea value (code-point aware) against its previous value and send backspaces (DEL, 0x7f) for the removed suffix plus the new tail, so the PTY line mirrors the textarea exactly. insertText stays handled by xterm; the bridge only resyncs its diff baseline. Diffing the whole composing value also handles Korean final-consonant migration ( + 아나). A keyCode === 229 keydown guard keeps xterm's keydown path inert during IME, and an opt-in imeDebug logger is left in for future WebKit regressions.

Wired into TerminalPane (agent/right-split tabs) and AuxTerminal (bottom-split shells). English/control keys route through keypress/keydown and never hit the bridge.

Testing

  • src/lib/ime.test.ts (14 cases, happy-dom): delta math (append, replace, final-consonant migration, shrink, surrogate-pair backspaces), input-type gating, the full 안녕 WebKit sequence reconstructing on a model PTY, plus Enter baseline reset, cleanup, and the no-PTY case.
  • Full suite green (139 tests), tsc -b clean.
  • Manually verified in the live app: agent tabs + bottom split, 안녕하세요, final-consonant migration, mid-composition backspace, mixed English, and Enter to submit.

Affected surfaces

  • Agent tabs (main pane)
  • Right split shells
  • Bottom split shells (AuxTerminal)

@simion simion merged commit e21fd9e into simion:main Jun 12, 2026
2 checks passed
@simion

simion commented Jun 12, 2026

Copy link
Copy Markdown
Owner

tested locally,. all good ✔️ merged and tagged v0.11.5

Thanks for your contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants