diff --git a/css/xterm.css b/css/xterm.css index b3d8d4f2..7ddcc2d0 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -59,10 +59,10 @@ } .xterm .xterm-helper-textarea { - /* - * HACK: to fix IE's blinking cursor - * Move textarea out of the screen to the far left, so that the cursor is not visible. - */ + padding: 0; + border: 0; + margin: 0; + /* Move textarea out of the screen to the far left, so that the cursor is not visible */ position: absolute; opacity: 0; left: -9999em; diff --git a/src/browser/Clipboard.ts b/src/browser/Clipboard.ts index 5fe1e8fc..b845c421 100644 --- a/src/browser/Clipboard.ts +++ b/src/browser/Clipboard.ts @@ -78,17 +78,6 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA textarea.style.zIndex = '1000'; textarea.focus(); - - // Reset the terminal textarea's styling - // Timeout needs to be long enough for click event to be handled. - setTimeout(() => { - textarea.style.position = ''; - textarea.style.width = ''; - textarea.style.height = ''; - textarea.style.left = ''; - textarea.style.top = ''; - textarea.style.zIndex = ''; - }, 200); } /** diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 42b3fcca..8f7a470c 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -281,6 +281,26 @@ export class Terminal extends CoreTerminal implements ITerminal { this._onBlur.fire(); } + private _syncTextArea(): void { + if (!this.buffer.isCursorInViewport || this._compositionHelper!.isComposing) { + return; + } + + const cellHeight = Math.ceil(this._charSizeService!.height * this.optionsService.options.lineHeight); + const cursorTop = this._bufferService.buffer.y * cellHeight; + const cursorLeft = this._bufferService.buffer.x * this._charSizeService!.width; + + // Sync the textarea to the exact position of the composition view so the IME knows where the + // text is. + this.textarea!.style.position = 'absolute'; + this.textarea!.style.left = cursorLeft + 'px'; + this.textarea!.style.top = cursorTop + 'px'; + this.textarea!.style.width = this._charSizeService!.width + 'px'; + this.textarea!.style.height = cellHeight + 'px'; + this.textarea!.style.lineHeight = cellHeight + 'px'; + this.textarea!.style.zIndex = '-5'; + } + /** * Initialize default behavior */ @@ -436,7 +456,11 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._inputHandler.onRequestSyncScrollBar(() => this.viewport!.syncScrollArea())); this.register(this.viewport); - this.register(this.onCursorMove(() => this._renderService!.onCursorMove())); + this.register(this.onCursorMove(() => { + this._renderService!.onCursorMove(); + this._syncTextArea(); + + })); this.register(this.onResize(() => this._renderService!.onResize(this.cols, this.rows))); this.register(this.onBlur(() => this._renderService!.onBlur())); this.register(this.onFocus(() => this._renderService!.onFocus())); diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 2d6f6cbb..6a4b4a0d 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -309,6 +309,9 @@ export class MockViewport implements IViewport { } export class MockCompositionHelper implements ICompositionHelper { + public get isComposing(): boolean { + return false; + } public compositionstart(): void { throw new Error('Method not implemented.'); } diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 1245ab04..2227e39f 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -87,6 +87,7 @@ export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; export type LineData = CharData[]; export interface ICompositionHelper { + readonly isComposing: boolean; compositionstart(): void; compositionupdate(ev: CompositionEvent): void; compositionend(): void; diff --git a/src/browser/input/CompositionHelper.ts b/src/browser/input/CompositionHelper.ts index c8101b40..b56bbfe2 100644 --- a/src/browser/input/CompositionHelper.ts +++ b/src/browser/input/CompositionHelper.ts @@ -22,6 +22,7 @@ export class CompositionHelper { * IME. This variable determines whether the compositionText should be displayed on the UI. */ private _isComposing: boolean; + public get isComposing(): boolean { return this._isComposing; } /** * The position within the input textarea's value of the current composition. @@ -118,7 +119,6 @@ export class CompositionHelper { private _finalizeComposition(waitForPropagation: boolean): void { this._compositionView.classList.remove('active'); this._isComposing = false; - this._clearTextareaPosition(); if (!waitForPropagation) { // Cancel any delayed composition send requests and send the input immediately. @@ -207,6 +207,7 @@ export class CompositionHelper { // Sync the textarea to the exact position of the composition view so the IME knows where the // text is. const compositionViewBounds = this._compositionView.getBoundingClientRect(); + this._textarea.style.position = 'absolute'; this._textarea.style.left = cursorLeft + 'px'; this._textarea.style.top = cursorTop + 'px'; this._textarea.style.width = compositionViewBounds.width + 'px'; @@ -218,13 +219,4 @@ export class CompositionHelper { setTimeout(() => this.updateCompositionElements(true), 0); } } - - /** - * Clears the textarea's position so that the cursor does not blink on IE. - * @private - */ - private _clearTextareaPosition(): void { - this._textarea.style.left = ''; - this._textarea.style.top = ''; - } }