Merge pull request #5672 from Tyriar/blink_opt
Prevent blink interval from running unless needed
This commit is contained in:
@@ -39,8 +39,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
private _observerDisposable = this._register(new MutableDisposable());
|
private _observerDisposable = this._register(new MutableDisposable());
|
||||||
|
|
||||||
private _model: RenderModel = new RenderModel();
|
private _model: RenderModel = new RenderModel();
|
||||||
|
private _rowHasBlinkingCells: boolean[] = [];
|
||||||
|
private _rowHasBlinkingCellsCount: number = 0;
|
||||||
private _workCell: ICellData = new CellData();
|
private _workCell: ICellData = new CellData();
|
||||||
private _workCell2: ICellData = new CellData();
|
|
||||||
private _cellColorResolver: CellColorResolver;
|
private _cellColorResolver: CellColorResolver;
|
||||||
|
|
||||||
private _canvas: HTMLCanvasElement;
|
private _canvas: HTMLCanvasElement;
|
||||||
@@ -112,6 +113,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
this._coreBrowserService,
|
this._coreBrowserService,
|
||||||
this._optionsService
|
this._optionsService
|
||||||
));
|
));
|
||||||
|
this._resetBlinkingRowState();
|
||||||
|
|
||||||
this._deviceMaxTextureSize = this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE);
|
this._deviceMaxTextureSize = this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE);
|
||||||
|
|
||||||
@@ -185,6 +187,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
this._updateDimensions();
|
this._updateDimensions();
|
||||||
|
|
||||||
this._model.resize(this._terminal.cols, this._terminal.rows);
|
this._model.resize(this._terminal.cols, this._terminal.rows);
|
||||||
|
this._resetBlinkingRowState();
|
||||||
|
|
||||||
// Resize all render layers
|
// Resize all render layers
|
||||||
for (const l of this._renderLayers) {
|
for (const l of this._renderLayers) {
|
||||||
@@ -238,6 +241,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
this._requestRedrawViewport();
|
this._requestRedrawViewport();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public handleViewportVisibilityChange(isVisible: boolean): void {
|
||||||
|
this._textBlinkStateManager.setViewportVisible(isVisible);
|
||||||
|
}
|
||||||
|
|
||||||
public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
|
public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
|
||||||
for (const l of this._renderLayers) {
|
for (const l of this._renderLayers) {
|
||||||
l.handleSelectionChanged(this._terminal, start, end, columnSelectMode);
|
l.handleSelectionChanged(this._terminal, start, end, columnSelectMode);
|
||||||
@@ -330,6 +337,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
l.reset(this._terminal);
|
l.reset(this._terminal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._resetBlinkingRowState();
|
||||||
|
this._textBlinkStateManager.setNeedsBlinkInViewport(false);
|
||||||
|
|
||||||
this._cursorBlinkStateManager.value?.restartBlinkAnimation();
|
this._cursorBlinkStateManager.value?.restartBlinkAnimation();
|
||||||
this._updateCursorBlink();
|
this._updateCursorBlink();
|
||||||
}
|
}
|
||||||
@@ -427,6 +437,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
for (y = start; y <= end; y++) {
|
for (y = start; y <= end; y++) {
|
||||||
row = y + terminal.buffer.ydisp;
|
row = y + terminal.buffer.ydisp;
|
||||||
line = terminal.buffer.lines.get(row)!;
|
line = terminal.buffer.lines.get(row)!;
|
||||||
|
let rowHasBlinkingCells = false;
|
||||||
this._model.lineLengths[y] = 0;
|
this._model.lineLengths[y] = 0;
|
||||||
isCursorRow = cursorY === row;
|
isCursorRow = cursorY === row;
|
||||||
skipJoinedCheckUntilX = 0;
|
skipJoinedCheckUntilX = 0;
|
||||||
@@ -484,6 +495,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
code = cell.getCode();
|
code = cell.getCode();
|
||||||
i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
|
i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
|
||||||
|
|
||||||
|
if (!rowHasBlinkingCells && cell.isBlink()) {
|
||||||
|
rowHasBlinkingCells = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Load colors/resolve overrides into work colors
|
// Load colors/resolve overrides into work colors
|
||||||
this._cellColorResolver.resolve(cell, x, row, this.dimensions.device.cell.width);
|
this._cellColorResolver.resolve(cell, x, row, this.dimensions.device.cell.width);
|
||||||
|
|
||||||
@@ -563,11 +578,31 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
x--; // Go back to the previous update cell for next iteration
|
x--; // Go back to the previous update cell for next iteration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this._setRowBlinkState(y, rowHasBlinkingCells);
|
||||||
}
|
}
|
||||||
if (modelUpdated) {
|
if (modelUpdated) {
|
||||||
this._rectangleRenderer.value!.updateBackgrounds(this._model);
|
this._rectangleRenderer.value!.updateBackgrounds(this._model);
|
||||||
}
|
}
|
||||||
this._rectangleRenderer.value!.updateCursor(this._model);
|
this._rectangleRenderer.value!.updateCursor(this._model);
|
||||||
|
this._updateTextBlinkState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _resetBlinkingRowState(): void {
|
||||||
|
this._rowHasBlinkingCells = new Array(this._terminal.rows).fill(false);
|
||||||
|
this._rowHasBlinkingCellsCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setRowBlinkState(row: number, hasBlinkingCells: boolean): void {
|
||||||
|
const previous = this._rowHasBlinkingCells[row];
|
||||||
|
if (previous === hasBlinkingCells) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._rowHasBlinkingCells[row] = hasBlinkingCells;
|
||||||
|
this._rowHasBlinkingCellsCount += hasBlinkingCells ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateTextBlinkState(): void {
|
||||||
|
this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ export class DomRenderer extends Disposable implements IRenderer {
|
|||||||
private _selectionRenderModel: ISelectionRenderModel = createSelectionRenderModel();
|
private _selectionRenderModel: ISelectionRenderModel = createSelectionRenderModel();
|
||||||
private _cursorBlinkStateManager: CursorBlinkStateManager;
|
private _cursorBlinkStateManager: CursorBlinkStateManager;
|
||||||
private _textBlinkStateManager: TextBlinkStateManager;
|
private _textBlinkStateManager: TextBlinkStateManager;
|
||||||
|
private _rowHasBlinkingCells: boolean[] = [];
|
||||||
|
private _rowHasBlinkingCellsCount: number = 0;
|
||||||
|
|
||||||
public dimensions: IRenderDimensions;
|
public dimensions: IRenderDimensions;
|
||||||
|
|
||||||
@@ -329,10 +331,14 @@ export class DomRenderer extends Disposable implements IRenderer {
|
|||||||
const row = this._document.createElement('div');
|
const row = this._document.createElement('div');
|
||||||
this._rowContainer.appendChild(row);
|
this._rowContainer.appendChild(row);
|
||||||
this._rowElements.push(row);
|
this._rowElements.push(row);
|
||||||
|
this._rowHasBlinkingCells.push(false);
|
||||||
}
|
}
|
||||||
// Remove excess elements
|
// Remove excess elements
|
||||||
while (this._rowElements.length > rows) {
|
while (this._rowElements.length > rows) {
|
||||||
this._rowContainer.removeChild(this._rowElements.pop()!);
|
this._rowContainer.removeChild(this._rowElements.pop()!);
|
||||||
|
if (this._rowHasBlinkingCells.pop()) {
|
||||||
|
this._rowHasBlinkingCellsCount--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,6 +366,10 @@ export class DomRenderer extends Disposable implements IRenderer {
|
|||||||
this.renderRows(this._bufferService.buffer.y, this._bufferService.buffer.y);
|
this.renderRows(this._bufferService.buffer.y, this._bufferService.buffer.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public handleViewportVisibilityChange(isVisible: boolean): void {
|
||||||
|
this._textBlinkStateManager.setViewportVisible(isVisible);
|
||||||
|
}
|
||||||
|
|
||||||
public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
|
public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
|
||||||
// Remove all selections
|
// Remove all selections
|
||||||
this._selectionContainer.replaceChildren();
|
this._selectionContainer.replaceChildren();
|
||||||
@@ -461,6 +471,11 @@ export class DomRenderer extends Disposable implements IRenderer {
|
|||||||
*/
|
*/
|
||||||
e.replaceChildren();
|
e.replaceChildren();
|
||||||
}
|
}
|
||||||
|
if (this._rowHasBlinkingCellsCount > 0) {
|
||||||
|
this._rowHasBlinkingCells.fill(false);
|
||||||
|
this._rowHasBlinkingCellsCount = 0;
|
||||||
|
this._textBlinkStateManager.setNeedsBlinkInViewport(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderRows(start: number, end: number): void {
|
public renderRows(start: number, end: number): void {
|
||||||
@@ -470,6 +485,7 @@ export class DomRenderer extends Disposable implements IRenderer {
|
|||||||
const cursorBlink = this._coreService.decPrivateModes.cursorBlink ?? this._optionsService.rawOptions.cursorBlink;
|
const cursorBlink = this._coreService.decPrivateModes.cursorBlink ?? this._optionsService.rawOptions.cursorBlink;
|
||||||
const cursorStyle = this._coreService.decPrivateModes.cursorStyle ?? this._optionsService.rawOptions.cursorStyle;
|
const cursorStyle = this._coreService.decPrivateModes.cursorStyle ?? this._optionsService.rawOptions.cursorStyle;
|
||||||
const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle;
|
const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle;
|
||||||
|
const rowInfo = { hasBlinkingCells: false };
|
||||||
|
|
||||||
for (let y = start; y <= end; y++) {
|
for (let y = start; y <= end; y++) {
|
||||||
const row = y + buffer.ydisp;
|
const row = y + buffer.ydisp;
|
||||||
@@ -491,10 +507,13 @@ export class DomRenderer extends Disposable implements IRenderer {
|
|||||||
this.dimensions.css.cell.width,
|
this.dimensions.css.cell.width,
|
||||||
this._widthCache,
|
this._widthCache,
|
||||||
-1,
|
-1,
|
||||||
-1
|
-1,
|
||||||
|
rowInfo
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
this._setRowBlinkState(y, rowInfo.hasBlinkingCells);
|
||||||
}
|
}
|
||||||
|
this._updateTextBlinkState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _terminalSelector(): string {
|
private get _terminalSelector(): string {
|
||||||
@@ -539,6 +558,7 @@ export class DomRenderer extends Disposable implements IRenderer {
|
|||||||
const cursorBlink = this._optionsService.rawOptions.cursorBlink;
|
const cursorBlink = this._optionsService.rawOptions.cursorBlink;
|
||||||
const cursorStyle = this._optionsService.rawOptions.cursorStyle;
|
const cursorStyle = this._optionsService.rawOptions.cursorStyle;
|
||||||
const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle;
|
const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle;
|
||||||
|
const rowInfo = { hasBlinkingCells: false };
|
||||||
|
|
||||||
// refresh rows within link range
|
// refresh rows within link range
|
||||||
for (let i = y; i <= y2; ++i) {
|
for (let i = y; i <= y2; ++i) {
|
||||||
@@ -561,10 +581,26 @@ export class DomRenderer extends Disposable implements IRenderer {
|
|||||||
this.dimensions.css.cell.width,
|
this.dimensions.css.cell.width,
|
||||||
this._widthCache,
|
this._widthCache,
|
||||||
enabled ? (i === y ? x : 0) : -1,
|
enabled ? (i === y ? x : 0) : -1,
|
||||||
enabled ? ((i === y2 ? x2 : cols) - 1) : -1
|
enabled ? ((i === y2 ? x2 : cols) - 1) : -1,
|
||||||
|
rowInfo
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
this._setRowBlinkState(i, rowInfo.hasBlinkingCells);
|
||||||
}
|
}
|
||||||
|
this._updateTextBlinkState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setRowBlinkState(row: number, hasBlinkingCells: boolean): void {
|
||||||
|
const previous = this._rowHasBlinkingCells[row];
|
||||||
|
if (previous === hasBlinkingCells) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._rowHasBlinkingCells[row] = hasBlinkingCells;
|
||||||
|
this._rowHasBlinkingCellsCount += hasBlinkingCells ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateTextBlinkState(): void {
|
||||||
|
this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount > 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,10 +71,14 @@ export class DomRendererRowFactory {
|
|||||||
cellWidth: number,
|
cellWidth: number,
|
||||||
widthCache: WidthCache,
|
widthCache: WidthCache,
|
||||||
linkStart: number,
|
linkStart: number,
|
||||||
linkEnd: number
|
linkEnd: number,
|
||||||
|
rowInfo?: { hasBlinkingCells: boolean }
|
||||||
): HTMLSpanElement[] {
|
): HTMLSpanElement[] {
|
||||||
|
|
||||||
const elements: HTMLSpanElement[] = [];
|
const elements: HTMLSpanElement[] = [];
|
||||||
|
if (rowInfo) {
|
||||||
|
rowInfo.hasBlinkingCells = false;
|
||||||
|
}
|
||||||
const joinedRanges = this._characterJoinerService.getJoinedCharacters(row);
|
const joinedRanges = this._characterJoinerService.getJoinedCharacters(row);
|
||||||
const colors = this._themeService.colors;
|
const colors = this._themeService.colors;
|
||||||
|
|
||||||
@@ -155,6 +159,9 @@ export class DomRendererRowFactory {
|
|||||||
const isInSelection = this._isCellInSelection(x, row);
|
const isInSelection = this._isCellInSelection(x, row);
|
||||||
const isCursorCell = isCursorRow && x === cursorX;
|
const isCursorCell = isCursorRow && x === cursorX;
|
||||||
const isLinkHover = hasHover && x >= linkStart && x <= linkEnd;
|
const isLinkHover = hasHover && x >= linkStart && x <= linkEnd;
|
||||||
|
if (rowInfo && cell.isBlink()) {
|
||||||
|
rowInfo.hasBlinkingCells = true;
|
||||||
|
}
|
||||||
const isBlinkHidden = !blinkOn && cell.isBlink();
|
const isBlinkHidden = !blinkOn && cell.isBlink();
|
||||||
if (isBlinkHidden) {
|
if (isBlinkHidden) {
|
||||||
classes.push(RowCss.BLINK_HIDDEN_CLASS);
|
classes.push(RowCss.BLINK_HIDDEN_CLASS);
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { assert } from 'chai';
|
||||||
|
import { TextBlinkStateManager } from 'browser/renderer/shared/TextBlinkStateManager';
|
||||||
|
import { MockOptionsService } from 'common/TestUtils.test';
|
||||||
|
import type { ICoreBrowserService } from 'browser/services/Services';
|
||||||
|
import { Emitter } from 'common/Event';
|
||||||
|
|
||||||
|
class FakeWindow {
|
||||||
|
public nextId = 1;
|
||||||
|
public intervals = new Map<number, () => void>();
|
||||||
|
|
||||||
|
public setInterval(callback: () => void, _duration: number): number {
|
||||||
|
const id = this.nextId++;
|
||||||
|
this.intervals.set(id, callback);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public clearInterval(id: number): void {
|
||||||
|
this.intervals.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createManager(duration: number): {
|
||||||
|
manager: TextBlinkStateManager;
|
||||||
|
window: FakeWindow;
|
||||||
|
getRenderCount: () => number;
|
||||||
|
} {
|
||||||
|
const fakeWindow = new FakeWindow();
|
||||||
|
let renderCount = 0;
|
||||||
|
const coreBrowserService: ICoreBrowserService = {
|
||||||
|
serviceBrand: undefined,
|
||||||
|
isFocused: true,
|
||||||
|
dpr: 1,
|
||||||
|
onDprChange: new Emitter<number>().event,
|
||||||
|
onWindowChange: new Emitter<Window & typeof globalThis>().event,
|
||||||
|
window: fakeWindow as any,
|
||||||
|
mainDocument: {} as any
|
||||||
|
};
|
||||||
|
const optionsService = new MockOptionsService({ blinkIntervalDuration: duration });
|
||||||
|
const manager = new TextBlinkStateManager(() => {
|
||||||
|
renderCount++;
|
||||||
|
}, coreBrowserService, optionsService);
|
||||||
|
return {
|
||||||
|
manager,
|
||||||
|
window: fakeWindow,
|
||||||
|
getRenderCount: () => renderCount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOnlyIntervalCallback(window: FakeWindow): () => void {
|
||||||
|
const iterator = window.intervals.values();
|
||||||
|
const first = iterator.next();
|
||||||
|
assert.ok(!first.done);
|
||||||
|
assert.ok(iterator.next().done);
|
||||||
|
return first.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('TextBlinkStateManager', () => {
|
||||||
|
it('starts interval only when needed', () => {
|
||||||
|
const { manager, window } = createManager(100);
|
||||||
|
assert.equal(window.intervals.size, 0);
|
||||||
|
manager.setNeedsBlinkInViewport(true);
|
||||||
|
assert.equal(window.intervals.size, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stops interval and restores blink visibility when no longer needed', () => {
|
||||||
|
const { manager, window, getRenderCount } = createManager(100);
|
||||||
|
manager.setNeedsBlinkInViewport(true);
|
||||||
|
const tick = getOnlyIntervalCallback(window);
|
||||||
|
tick();
|
||||||
|
const rendersAfterTick = getRenderCount();
|
||||||
|
assert.equal(manager.isBlinkOn, false);
|
||||||
|
manager.setNeedsBlinkInViewport(false);
|
||||||
|
assert.equal(window.intervals.size, 0);
|
||||||
|
assert.equal(manager.isBlinkOn, true);
|
||||||
|
assert.equal(getRenderCount(), rendersAfterTick + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('pauses while viewport is hidden and resumes when visible', () => {
|
||||||
|
const { manager, window } = createManager(100);
|
||||||
|
manager.setNeedsBlinkInViewport(true);
|
||||||
|
assert.equal(window.intervals.size, 1);
|
||||||
|
manager.setViewportVisible(false);
|
||||||
|
assert.equal(window.intervals.size, 0);
|
||||||
|
manager.setViewportVisible(true);
|
||||||
|
assert.equal(window.intervals.size, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not start interval when duration is zero', () => {
|
||||||
|
const { manager, window } = createManager(0);
|
||||||
|
manager.setNeedsBlinkInViewport(true);
|
||||||
|
assert.equal(window.intervals.size, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -11,6 +11,8 @@ export class TextBlinkStateManager extends Disposable {
|
|||||||
private _intervalDuration: number = 0;
|
private _intervalDuration: number = 0;
|
||||||
private _interval: number | undefined;
|
private _interval: number | undefined;
|
||||||
private _blinkOn: boolean = true;
|
private _blinkOn: boolean = true;
|
||||||
|
private _needsBlinkInViewport: boolean = false;
|
||||||
|
private _isViewportVisible: boolean = true;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _renderCallback: () => void,
|
private readonly _renderCallback: () => void,
|
||||||
@@ -33,6 +35,24 @@ export class TextBlinkStateManager extends Disposable {
|
|||||||
return this._intervalDuration > 0;
|
return this._intervalDuration > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setNeedsBlinkInViewport(needsBlinkInViewport: boolean): void {
|
||||||
|
if (this._needsBlinkInViewport === needsBlinkInViewport) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._needsBlinkInViewport = needsBlinkInViewport;
|
||||||
|
this._updateIntervalState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public setViewportVisible(isVisible: boolean): void {
|
||||||
|
if (this._isViewportVisible === isVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._isViewportVisible = isVisible;
|
||||||
|
this._updateIntervalState();
|
||||||
|
}
|
||||||
|
|
||||||
public setIntervalDuration(duration: number): void {
|
public setIntervalDuration(duration: number): void {
|
||||||
if (duration === this._intervalDuration) {
|
if (duration === this._intervalDuration) {
|
||||||
return;
|
return;
|
||||||
@@ -40,22 +60,31 @@ export class TextBlinkStateManager extends Disposable {
|
|||||||
|
|
||||||
this._intervalDuration = duration;
|
this._intervalDuration = duration;
|
||||||
this._clearInterval();
|
this._clearInterval();
|
||||||
|
this._updateIntervalState();
|
||||||
|
}
|
||||||
|
|
||||||
const wasBlinkOn = this._blinkOn;
|
private _updateIntervalState(): void {
|
||||||
if (duration > 0) {
|
const shouldBlink = this._intervalDuration > 0 && this._needsBlinkInViewport && this._isViewportVisible;
|
||||||
|
if (shouldBlink) {
|
||||||
|
if (this._interval !== undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const wasBlinkOn = this._blinkOn;
|
||||||
this._blinkOn = true;
|
this._blinkOn = true;
|
||||||
this._interval = this._coreBrowserService.window.setInterval(() => {
|
this._interval = this._coreBrowserService.window.setInterval(() => {
|
||||||
this._blinkOn = !this._blinkOn;
|
this._blinkOn = !this._blinkOn;
|
||||||
this._renderCallback();
|
this._renderCallback();
|
||||||
}, duration);
|
}, this._intervalDuration);
|
||||||
if (!wasBlinkOn) {
|
if (!wasBlinkOn) {
|
||||||
this._renderCallback();
|
this._renderCallback();
|
||||||
}
|
}
|
||||||
} else {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._clearInterval();
|
||||||
|
if (!this._blinkOn) {
|
||||||
this._blinkOn = true;
|
this._blinkOn = true;
|
||||||
if (!wasBlinkOn) {
|
this._renderCallback();
|
||||||
this._renderCallback();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ export interface IRenderer extends IDisposable {
|
|||||||
handleCharSizeChanged(): void;
|
handleCharSizeChanged(): void;
|
||||||
handleBlur(): void;
|
handleBlur(): void;
|
||||||
handleFocus(): void;
|
handleFocus(): void;
|
||||||
|
handleViewportVisibilityChange?(isVisible: boolean): void;
|
||||||
handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;
|
handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;
|
||||||
handleCursorMove(): void;
|
handleCursorMove(): void;
|
||||||
clear(): void;
|
clear(): void;
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ export class RenderService extends Disposable implements IRenderService {
|
|||||||
|
|
||||||
private _handleIntersectionChange(entry: IntersectionObserverEntry): void {
|
private _handleIntersectionChange(entry: IntersectionObserverEntry): void {
|
||||||
this._isPaused = entry.isIntersecting === undefined ? (entry.intersectionRatio === 0) : !entry.isIntersecting;
|
this._isPaused = entry.isIntersecting === undefined ? (entry.intersectionRatio === 0) : !entry.isIntersecting;
|
||||||
|
this._renderer.value?.handleViewportVisibilityChange?.(!this._isPaused);
|
||||||
|
|
||||||
// Terminal was hidden on open
|
// Terminal was hidden on open
|
||||||
if (!this._isPaused && !this._charSizeService.hasValidSize) {
|
if (!this._isPaused && !this._charSizeService.hasValidSize) {
|
||||||
|
|||||||
Reference in New Issue
Block a user