From c68e5d831c8c939817e3614a2c7579011eeb3529 Mon Sep 17 00:00:00 2001 From: Minseo Lee Date: Tue, 11 Aug 2026 16:49:58 +0900 Subject: [PATCH] fix(extension): drop manual IME compositionend send that doubled CJK input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xterm.js already handles IME composition correctly: partial jamo are suppressed while _isComposing, and _finalizeComposition emits the final composed string through onData exactly once, asynchronously via setTimeout(0). The local workaround for #1272 sent e.data manually on compositionend — one macrotask BEFORE xterm's own emission of the same string — so every composed Korean syllable was sent to the PTY twice (안녕 → 안녕안녕). ASCII input was unaffected because it never goes through composition. Remove the custom compositionstart/compositionend listeners and let xterm deliver the final string through the normal onData path. Co-Authored-By: Claude --- extension/sidepanel-terminal.js | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/extension/sidepanel-terminal.js b/extension/sidepanel-terminal.js index e6287abca..7dae41c59 100644 --- a/extension/sidepanel-terminal.js +++ b/extension/sidepanel-terminal.js @@ -433,25 +433,15 @@ }); ro.observe(els.mount); - // IME composition handling for Korean/CJK input (issue #1272). - // Suppress partial jamo during composition; only send the final - // composed string on compositionend. Without this, Korean IME - // sends fragmented input or doubles characters. - let composing = false; - const ta = term.textarea; - if (ta) { - ta.addEventListener('compositionstart', () => { composing = true; }); - ta.addEventListener('compositionend', (e) => { - composing = false; - if (e.data && ws && ws.readyState === WebSocket.OPEN) { - ws.send(new TextEncoder().encode(e.data)); - } - }); - } - + // IME composition (Korean/CJK, issue #1272) is handled by xterm.js + // itself: partial jamo are suppressed while _isComposing, and the final + // composed string is emitted through onData once, asynchronously + // (setTimeout in _finalizeComposition). A previous local workaround + // sent e.data manually on compositionend — but xterm emits the same + // string one macrotask later, so every composed syllable went out + // TWICE. Do not re-add a manual compositionend send. term.onData((data) => { - if (composing) return; // suppress partial input events during IME composition if (ws && ws.readyState === WebSocket.OPEN) { ws.send(new TextEncoder().encode(data)); }