From 4b615f48ff07627a031b0175b5039f8a03b9280e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 14 Mar 2022 15:48:09 -0700 Subject: [PATCH] Reduce state in buffer decorations --- .../Decorations/BufferDecorationRenderer.ts | 168 +++++------------- .../Decorations/OverviewRulerRenderer.ts | 8 +- src/common/services/DecorationService.ts | 2 + src/common/services/Services.ts | 1 + src/tsconfig-library-base.json | 3 +- 5 files changed, 55 insertions(+), 127 deletions(-) diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index 5b97130e..9e3b2d72 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -5,39 +5,41 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { IRenderService } from 'browser/services/Services'; -import { IEvent, EventEmitter } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IBufferService, IDecorationService, IInternalDecoration } from 'common/services/Services'; -import { IMarker } from 'common/Types'; -import { IDecoration, IDecorationOptions } from 'xterm'; -export interface IDecorationRenderer { - refreshDecorations(shouldRecreate?: boolean): void; - renderDecoration(decoration: IInternalDecoration, decorationOptions: IDecorationOptions): void; -} +export class BufferDecorationRenderer extends Disposable { + private readonly _container: HTMLElement; + private readonly _decorationElements: Map = new Map(); -export class BufferDecorationRenderer extends Disposable implements IDecorationRenderer { private _animationFrame: number | undefined; - private _decorationContainer: HTMLElement; - private readonly _decorations: BufferDecoration[] = []; private _altBufferIsActive: boolean = false; constructor( @IBufferService private readonly _bufferService: IBufferService, @IRenderService private readonly _renderService: IRenderService, private readonly _decorationService: IDecorationService, - private readonly _screenElement: HTMLElement) { + private readonly _screenElement: HTMLElement + ) { super(); - this._decorationContainer = document.createElement('div'); - this._decorationContainer.classList.add('xterm-decoration-container'); - this._screenElement.appendChild(this._decorationContainer); + + this._container = document.createElement('div'); + this._container.classList.add('xterm-decoration-container'); + this._screenElement.appendChild(this._container); + this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh())); this.register(this._renderService.onDimensionsChange(() => this._queueRefresh())); this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh())); this.register(this._bufferService.buffers.onBufferActivate(() => { this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt; })); - this.register(this._decorationService.onDecorationRegistered(options => this.renderDecoration(options))); + this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh())); + } + + public override dispose(): void { + this._container.remove(); + this._decorationElements.clear(); + super.dispose(); } private _queueRefresh(): void { @@ -50,131 +52,53 @@ export class BufferDecorationRenderer extends Disposable implements IDecorationR }); } - public refreshDecorations(shouldRecreate?: boolean): void { - console.log('refresh decorations', this._decorations.length); - for (const decoration of this._decorations) { - decoration.render(this._decorationContainer, this._renderService, shouldRecreate); + public refreshDecorations(): void { + for (const decoration of this._decorationService.decorations) { + this._renderDecoration(decoration); } } - public renderDecoration(decoration: IInternalDecoration): void { - const bufferDecoration = new BufferDecoration(this._bufferService, decoration, decoration.options); - this._decorations.push(bufferDecoration); - // bufferDecoration.render(this._decorationContainer, this._renderService, true); - if (this._decorationContainer && bufferDecoration.element && !this._decorationContainer.contains(bufferDecoration.element)) { - this._decorationContainer.append(bufferDecoration.element!); - } - this._queueRefresh(); - } - - public override dispose(): void { - if (this._screenElement && this._decorationContainer && this._screenElement.contains(this._decorationContainer)) { - this._screenElement.removeChild(this._decorationContainer); - } - for (const bufferDecoration of this._decorations) { - bufferDecoration.dispose(); - } - super.dispose(); - } -} - -export class BufferDecoration extends Disposable implements IDecoration { - private readonly _marker: IMarker; - private _element: HTMLElement | undefined; - private _container: HTMLElement | undefined; - private _altBufferIsActive: boolean = false; - - public isDisposed: boolean = false; - - public get element(): HTMLElement | undefined { return this._element; } - public get marker(): IMarker { return this._marker; } - - private _onDispose = new EventEmitter(); - public get onDispose(): IEvent { return this._onDispose.event; } - - private _onRender = new EventEmitter(); - public get onRender(): IEvent { return this._onRender.event; } - - public x: number; - public anchor: 'left' | 'right'; - public width: number; - public height: number; - - constructor( - private readonly _bufferService: IBufferService, - private readonly _internalDecoration: IInternalDecoration, - options: IDecorationOptions - ) { - super(); - this.x = options.x ?? 0; - this._marker = options.marker; - this._marker.onDispose(() => this.dispose()); - this.anchor = options.anchor || 'left'; - this.width = options.width || 1; - this.height = options.height || 1; - } - - public render(container: HTMLElement, renderService: IRenderService, shouldRecreate?: boolean): void { - this._container = container; - if (!this._element || shouldRecreate) { - const element = this._createElement(renderService, shouldRecreate); + private _renderDecoration(decoration: IInternalDecoration): void { + let element = this._decorationElements.get(decoration); + if (!element) { + element = this._createElement(decoration); + this._decorationElements.set(decoration, element); this._container.appendChild(element); } - this._refreshStyle(renderService); - if (this._element) { - console.log('firing on render'); - this._onRender.fire(this._element); - this._internalDecoration.onRenderEmitter.fire(this._element!); - } + this._refreshStyle(decoration, element); + decoration.onRenderEmitter.fire(element); } - private _createElement(renderService: IRenderService, shouldRecreate?: boolean): HTMLElement { - if (shouldRecreate && this._element && this._container && this._container.contains(this._element)) { - this._container.removeChild(this._element); - } - this._element = document.createElement('div'); - this._element.classList.add('xterm-decoration'); - this._element.style.width = `${this.width * renderService.dimensions.actualCellWidth}px`; - this._element.style.height = `${this.height * renderService.dimensions.actualCellHeight}px`; - this._element.style.top = `${(this.marker.line - this._bufferService.buffers.active.ydisp) * renderService.dimensions.actualCellHeight}px`; - this._element.style.lineHeight = `${renderService.dimensions.actualCellHeight}px`; + private _createElement(decoration: IInternalDecoration): HTMLElement { + const element = document.createElement('div'); + element.classList.add('xterm-decoration'); + element.style.width = `${(decoration.options.width || 1) * this._renderService.dimensions.actualCellWidth}px`; + element.style.height = `${(decoration.options.height || 1) * this._renderService.dimensions.actualCellHeight}px`; + element.style.top = `${(decoration.marker.line - this._bufferService.buffers.active.ydisp) * this._renderService.dimensions.actualCellHeight}px`; + element.style.lineHeight = `${this._renderService.dimensions.actualCellHeight}px`; - if (this.x && this.x > this._bufferService.cols) { + const x = decoration.options.x ?? 0; + if (x && x > this._bufferService.cols) { // exceeded the container width, so hide - this._element.style.display = 'none'; + element.style.display = 'none'; } - if (this.anchor === 'right') { - this._element.style.right = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : ''; + if ((decoration.options.anchor || 'left') === 'right') { + element.style.right = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : ''; } else { - this._element.style.left = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : ''; + element.style.left = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : ''; } - return this._element; + return element; } - private _refreshStyle(renderService: IRenderService): void { - if (!this._element) { - return; - } - const line = this.marker.line - this._bufferService.buffers.active.ydisp; + private _refreshStyle(decoration: IInternalDecoration, element: HTMLElement): void { + const line = decoration.marker.line - this._bufferService.buffers.active.ydisp; if (line < 0 || line > this._bufferService.rows) { // outside of viewport - this._element.style.display = 'none'; + element.style.display = 'none'; } else { - this._element.style.top = `${line * renderService.dimensions.actualCellHeight}px`; - this._element.style.display = this._altBufferIsActive ? 'none' : 'block'; + element.style.top = `${line * this._renderService.dimensions.actualCellHeight}px`; + element.style.display = this._altBufferIsActive ? 'none' : 'block'; } } - - public override dispose(): void { - if (this.isDisposed || !this._container) { - return; - } - if (this._element && this._container.contains(this._element)) { - this._container.removeChild(this._element); - } - this.isDisposed = true; - this._onDispose.fire(); - } } - diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 5f5fc397..2ee0970c 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -4,17 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { addDisposableDomListener } from 'browser/Lifecycle'; -import { IDecorationRenderer } from 'browser/Decorations/BufferDecorationRenderer'; import { IRenderService } from 'browser/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IBufferService, IDecorationService, IInstantiationService, IInternalDecoration } from 'common/services/Services'; import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; + const enum ScrollbarConstants { WIDTH = 7 } -export class OverviewRulerRenderer extends Disposable implements IDecorationRenderer { +export class OverviewRulerRenderer extends Disposable { private _canvas: HTMLCanvasElement; private _ctx: CanvasRenderingContext2D | null; private _decorations: ScrollbarDecoration[] = []; @@ -42,10 +42,10 @@ export class OverviewRulerRenderer extends Disposable implements IDecorationRend this.register(this._renderService.onRenderedBufferChange(() => this.refreshDecorations())); this.register(this._renderService.onDimensionsChange(() => this.refreshDecorations())); this.register(addDisposableDomListener(window, 'resize', () => this.refreshDecorations())); - this.register(this._decorationService.onDecorationRegistered(e => this.renderDecoration(e))); + this.register(this._decorationService.onDecorationRegistered(e => this.registerDecoration(e))); this.register(this._decorationService.onDecorationRemoved(d => d.dispose())); } - public renderDecoration(decoration: IInternalDecoration): void { + public registerDecoration(decoration: IInternalDecoration): void { if (!this._ctx || !decoration.options.overviewRulerItemColor) { return; } diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 7c49c7b4..af1d30b0 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -17,6 +17,8 @@ export class DecorationService extends Disposable implements IDecorationService public get onDecorationRemoved(): IEvent { return this._onDecorationRemoved.event; } private _decorations: IInternalDecoration[] = []; + public get decorations(): IterableIterator { return this._decorations.values(); } + constructor() { super(); } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 62108ffb..67bbb5ee 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -303,6 +303,7 @@ export interface IUnicodeVersionProvider { export const IDecorationService = createDecorator('DecorationService'); export interface IDecorationService extends IDisposable { + readonly decorations: IterableIterator; readonly onDecorationRegistered: IEvent; readonly onDecorationRemoved: IEvent; registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; diff --git a/src/tsconfig-library-base.json b/src/tsconfig-library-base.json index e08695d7..00cecc06 100644 --- a/src/tsconfig-library-base.json +++ b/src/tsconfig-library-base.json @@ -4,6 +4,7 @@ "composite": true, "strict": true, "declarationMap": true, - "experimentalDecorators": true + "experimentalDecorators": true, + "downlevelIteration": true } }