diff --git a/src/browser/CoreBrowserTerminal.ts b/src/browser/CoreBrowserTerminal.ts index 4557e1652c..d3e860e878 100644 --- a/src/browser/CoreBrowserTerminal.ts +++ b/src/browser/CoreBrowserTerminal.ts @@ -854,6 +854,10 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { const shouldIgnoreComposition = this.browser.isMac && this.options.macOptionIsMeta && event.altKey; if (!shouldIgnoreComposition && !this._compositionHelper!.keydown(event)) { + // Reset _keyDownSeen when composition handles the keydown (e.g. keyCode 229 on + // Android). The keydown did not produce any data, so the subsequent input event + // must be allowed through to _inputEvent() to forward the IME text. + this._keyDownSeen = false; if (this.options.scrollOnUserInput && this.buffer.ybase !== this.buffer.ydisp) { this.scrollToBottom(true); } @@ -1037,6 +1041,9 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { // The key was handled so clear the dead key state, otherwise certain keystrokes like arrow // keys could be ignored this._unprocessedDeadKey = false; + // Cancel any pending composition send when the input event handles the committed text. + // This prevents double-sending when _finalizeComposition's setTimeout fires. + this._compositionHelper?.cancelPendingComposition(); const text = ev.data; this.coreService.triggerDataEvent(text, true); diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 7b34645952..e57a709cb5 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -360,6 +360,9 @@ export class MockCompositionHelper implements ICompositionHelper { public keydown(ev: KeyboardEvent): boolean { return true; } + public cancelPendingComposition(): void { + // no-op + } } export class MockCoreBrowserService implements ICoreBrowserService { diff --git a/src/browser/Types.ts b/src/browser/Types.ts index 497afcf535..b473a2a791 100644 --- a/src/browser/Types.ts +++ b/src/browser/Types.ts @@ -44,6 +44,7 @@ export interface ICompositionHelper { compositionend(): void; updateCompositionElements(dontRecurse?: boolean): void; keydown(ev: KeyboardEvent): boolean; + cancelPendingComposition(): void; } export interface IBrowser { diff --git a/src/browser/input/CompositionHelper.ts b/src/browser/input/CompositionHelper.ts index c9ec396ab6..80be50c1c3 100644 --- a/src/browser/input/CompositionHelper.ts +++ b/src/browser/input/CompositionHelper.ts @@ -203,6 +203,15 @@ export class CompositionHelper { } } + /** + * Cancel any pending composition send. This is called from _inputEvent when the + * IME committed text is handled via the input event, preventing a double-send from + * the _finalizeComposition timeout. + */ + public cancelPendingComposition(): void { + this._isSendingComposition = false; + } + /** * Apply any changes made to the textarea after the current event chain is allowed to complete. * This should be called when not currently composing but a keydown event with the "composition