Add idle timeout which pauses cursor blink

Fixes #4628
This commit is contained in:
Daniel Imms
2025-12-28 13:30:58 -08:00
parent fb10cd62c9
commit f8c1f5a32f
3 changed files with 116 additions and 1 deletions
+53 -1
View File
@@ -15,6 +15,7 @@ import { color } from 'common/Color';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { IBufferService, ICoreService, IInstantiationService, IOptionsService } from 'common/services/Services';
import { Emitter } from 'vs/base/common/event';
import { addDisposableListener } from 'vs/base/browser/dom';
const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';
@@ -23,6 +24,12 @@ const FG_CLASS_PREFIX = 'xterm-fg-';
const BG_CLASS_PREFIX = 'xterm-bg-';
const FOCUS_CLASS = 'xterm-focus';
const SELECTION_CLASS = 'xterm-selection';
const CURSOR_BLINK_IDLE_CLASS = 'xterm-cursor-blink-idle';
/**
* The idle time after which cursor blinking stops (2 minutes).
*/
const IDLE_TIMEOUT = 5000; // 2 * 60 * 1000;
let nextTerminalId = 1;
@@ -42,6 +49,7 @@ export class DomRenderer extends Disposable implements IRenderer {
private _selectionContainer: HTMLElement;
private _widthCache: WidthCache;
private _selectionRenderModel: ISelectionRenderModel = createSelectionRenderModel();
private _idleTimeout: number | undefined;
public dimensions: IRenderDimensions;
@@ -89,6 +97,8 @@ export class DomRenderer extends Disposable implements IRenderer {
this._register(this._linkifier2.onShowLinkUnderline(e => this._handleLinkHover(e)));
this._register(this._linkifier2.onHideLinkUnderline(e => this._handleLinkLeave(e)));
this._register(addDisposableListener(this._document, 'mousedown', () => this._resetIdleTimer()));
this._register(toDisposable(() => {
this._element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass);
@@ -99,6 +109,7 @@ export class DomRenderer extends Disposable implements IRenderer {
this._widthCache.dispose();
this._themeStyleElement.remove();
this._dimensionsStyleElement.remove();
this._clearIdleTimer();
}));
this._widthCache = new WidthCache(this._document, this._helperContainer);
@@ -225,6 +236,10 @@ export class DomRenderer extends Disposable implements IRenderer {
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` +
` animation: ${blinkAnimationBlockId} 1s step-end infinite;` +
`}` +
// Disable cursor blinking when idle
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${CURSOR_BLINK_IDLE_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS} {` +
` animation: none !important;` +
`}` +
// !important helps fix an issue where the cursor will not render on top of the selection,
// however it's very hard to fix this issue and retain the blink animation without the use of
// !important. So this edge case fails when cursor blink is on.
@@ -328,11 +343,14 @@ export class DomRenderer extends Disposable implements IRenderer {
public handleBlur(): void {
this._rowContainer.classList.remove(FOCUS_CLASS);
this._clearIdleTimer();
this.renderRows(0, this._bufferService.rows - 1);
}
public handleFocus(): void {
this._rowContainer.classList.add(FOCUS_CLASS);
this._rowContainer.classList.remove(CURSOR_BLINK_IDLE_CLASS);
this._resetIdleTimer();
this.renderRows(this._bufferService.buffer.y, this._bufferService.buffer.y);
}
@@ -406,7 +424,8 @@ export class DomRenderer extends Disposable implements IRenderer {
}
public handleCursorMove(): void {
// No-op, the cursor is drawn when rows are drawn
// Reset idle timer on cursor movement (which happens on input)
this._resetIdleTimer();
}
private _handleOptionsChanged(): void {
@@ -539,4 +558,37 @@ export class DomRenderer extends Disposable implements IRenderer {
);
}
}
/**
* Resets the idle timer. If the terminal is idle for IDLE_TIMEOUT, cursor
* blinking will stop.
*/
private _resetIdleTimer(): void {
if (!this._coreBrowserService.isFocused) {
return;
}
this._clearIdleTimer();
this._rowContainer.classList.remove(CURSOR_BLINK_IDLE_CLASS);
this._idleTimeout = this._coreBrowserService.window.setTimeout(() => {
this._stopBlinkingDueToIdle();
}, IDLE_TIMEOUT);
}
/**
* Clears the idle timer.
*/
private _clearIdleTimer(): void {
if (this._idleTimeout) {
this._coreBrowserService.window.clearTimeout(this._idleTimeout);
this._idleTimeout = undefined;
}
}
/**
* Stops cursor blinking due to idle timeout.
*/
private _stopBlinkingDueToIdle(): void {
this._rowContainer.classList.add(CURSOR_BLINK_IDLE_CLASS);
this._idleTimeout = undefined;
}
}