Skip to content
Open
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
31 changes: 19 additions & 12 deletions frontend/app/view/term/termwrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -616,31 +616,32 @@ export class TermWrap {
return;
}

// IME Composition Handling
// Block all data during composition - only send the final text after compositionend
// This prevents xterm.js from sending intermediate composition data (e.g., during compositionupdate)
if (this.isComposing) {
dlog("Blocked data during composition:", data);
return;
}

if (this.pasteActive) {
if (this.multiInputCallback) {
this.multiInputCallback(data);
}
}

// IME Deduplication (for Capslock input method switching)
// When switching input methods with Capslock during composition, some systems send the
// composed text twice. We allow the first send and block subsequent duplicates.
// IME handling: Check if this is confirmed text from a recent compositionend
// This must be checked BEFORE isComposing, because when implicitly confirming IME
// (e.g., typing next char), the event order is:
// compositionend → compositionstart (new) → xterm onData
// By the time onData fires, a new composition has started (isComposing=true),
// but we still need to send the confirmed text from the previous composition.
//
// We also handle deduplication here: when switching input methods with Capslock
// during composition, some systems send the composed text twice. We allow the
// first send and block subsequent duplicates within the dedup window.
const IMEDedupWindowMs = 50;
const now = Date.now();
const timeSinceCompositionEnd = now - this.lastCompositionEnd;
if (timeSinceCompositionEnd < IMEDedupWindowMs && data === this.lastComposedText && this.lastComposedText) {
if (!this.firstDataAfterCompositionSent) {
// First send after composition - allow it but mark as sent
// First send after composition - allow it even if isComposing is true
this.firstDataAfterCompositionSent = true;
dlog("First data after composition, allowing:", data);
this.sendDataHandler?.(data);
return;
} else {
// Second send of the same data - this is a duplicate from Capslock switching, block it
dlog("Blocked duplicate IME data:", data);
Expand All @@ -650,6 +651,12 @@ export class TermWrap {
}
}

// Block intermediate composition data (e.g., during compositionupdate)
if (this.isComposing) {
dlog("Blocked data during composition:", data);
return;
}

this.sendDataHandler?.(data);
}

Expand Down