Skip to content

Commit 95d01de

Browse files
committed
Sync textarea with cursor. Fixes #2598
1 parent dc541c2 commit 95d01de

File tree

6 files changed

+35
-26
lines changed

6 files changed

+35
-26
lines changed

css/xterm.css

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,10 @@
5959
}
6060

6161
.xterm .xterm-helper-textarea {
62-
/*
63-
* HACK: to fix IE's blinking cursor
64-
* Move textarea out of the screen to the far left, so that the cursor is not visible.
65-
*/
62+
padding: 0;
63+
border: 0;
64+
margin: 0;
65+
/* Move textarea out of the screen to the far left, so that the cursor is not visible */
6666
position: absolute;
6767
opacity: 0;
6868
left: -9999em;

src/browser/Clipboard.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,17 +78,6 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA
7878
textarea.style.zIndex = '1000';
7979

8080
textarea.focus();
81-
82-
// Reset the terminal textarea's styling
83-
// Timeout needs to be long enough for click event to be handled.
84-
setTimeout(() => {
85-
textarea.style.position = '';
86-
textarea.style.width = '';
87-
textarea.style.height = '';
88-
textarea.style.left = '';
89-
textarea.style.top = '';
90-
textarea.style.zIndex = '';
91-
}, 200);
9281
}
9382

9483
/**

src/browser/Terminal.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,26 @@ export class Terminal extends CoreTerminal implements ITerminal {
281281
this._onBlur.fire();
282282
}
283283

284+
private _syncTextArea(): void {
285+
if (!this.buffer.isCursorInViewport || this._compositionHelper!.isComposing) {
286+
return;
287+
}
288+
289+
const cellHeight = Math.ceil(this._charSizeService!.height * this.optionsService.options.lineHeight);
290+
const cursorTop = this._bufferService.buffer.y * cellHeight;
291+
const cursorLeft = this._bufferService.buffer.x * this._charSizeService!.width;
292+
293+
// Sync the textarea to the exact position of the composition view so the IME knows where the
294+
// text is.
295+
this.textarea!.style.position = 'absolute';
296+
this.textarea!.style.left = cursorLeft + 'px';
297+
this.textarea!.style.top = cursorTop + 'px';
298+
this.textarea!.style.width = this._charSizeService!.width + 'px';
299+
this.textarea!.style.height = cellHeight + 'px';
300+
this.textarea!.style.lineHeight = cellHeight + 'px';
301+
this.textarea!.style.zIndex = '-5';
302+
}
303+
284304
/**
285305
* Initialize default behavior
286306
*/
@@ -436,7 +456,11 @@ export class Terminal extends CoreTerminal implements ITerminal {
436456
this.register(this._inputHandler.onRequestSyncScrollBar(() => this.viewport!.syncScrollArea()));
437457
this.register(this.viewport);
438458

439-
this.register(this.onCursorMove(() => this._renderService!.onCursorMove()));
459+
this.register(this.onCursorMove(() => {
460+
this._renderService!.onCursorMove();
461+
this._syncTextArea();
462+
463+
}));
440464
this.register(this.onResize(() => this._renderService!.onResize(this.cols, this.rows)));
441465
this.register(this.onBlur(() => this._renderService!.onBlur()));
442466
this.register(this.onFocus(() => this._renderService!.onFocus()));

src/browser/TestUtils.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,9 @@ export class MockViewport implements IViewport {
309309
}
310310

311311
export class MockCompositionHelper implements ICompositionHelper {
312+
public get isComposing(): boolean {
313+
return false;
314+
}
312315
public compositionstart(): void {
313316
throw new Error('Method not implemented.');
314317
}

src/browser/Types.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;
8787
export type LineData = CharData[];
8888

8989
export interface ICompositionHelper {
90+
readonly isComposing: boolean;
9091
compositionstart(): void;
9192
compositionupdate(ev: CompositionEvent): void;
9293
compositionend(): void;

src/browser/input/CompositionHelper.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export class CompositionHelper {
2222
* IME. This variable determines whether the compositionText should be displayed on the UI.
2323
*/
2424
private _isComposing: boolean;
25+
public get isComposing(): boolean { return this._isComposing; }
2526

2627
/**
2728
* The position within the input textarea's value of the current composition.
@@ -118,7 +119,6 @@ export class CompositionHelper {
118119
private _finalizeComposition(waitForPropagation: boolean): void {
119120
this._compositionView.classList.remove('active');
120121
this._isComposing = false;
121-
this._clearTextareaPosition();
122122

123123
if (!waitForPropagation) {
124124
// Cancel any delayed composition send requests and send the input immediately.
@@ -207,6 +207,7 @@ export class CompositionHelper {
207207
// Sync the textarea to the exact position of the composition view so the IME knows where the
208208
// text is.
209209
const compositionViewBounds = this._compositionView.getBoundingClientRect();
210+
this._textarea.style.position = 'absolute';
210211
this._textarea.style.left = cursorLeft + 'px';
211212
this._textarea.style.top = cursorTop + 'px';
212213
this._textarea.style.width = compositionViewBounds.width + 'px';
@@ -218,13 +219,4 @@ export class CompositionHelper {
218219
setTimeout(() => this.updateCompositionElements(true), 0);
219220
}
220221
}
221-
222-
/**
223-
* Clears the textarea's position so that the cursor does not blink on IE.
224-
* @private
225-
*/
226-
private _clearTextareaPosition(): void {
227-
this._textarea.style.left = '';
228-
this._textarea.style.top = '';
229-
}
230222
}

0 commit comments

Comments
 (0)