diff --git a/demo/client.ts b/demo/client.ts index 0f2148c9..55e97ed4 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -548,7 +548,7 @@ function addDecoration() { const marker = term.addMarker(1); const decoration = term.registerDecoration({ marker }); decoration.onRender((e) => { - console.log(e); + console.log('onRender', e); e.style.backgroundColor = 'red'; }); } diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index efb97c16..5b97130e 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -4,19 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import { addDisposableDomListener } from 'browser/Lifecycle'; -import { IDecorationService, IRenderService } from 'browser/services/Services'; +import { IRenderService } from 'browser/services/Services'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; -import { IBufferService } from 'common/services/Services'; +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: IDecoration, decorationOptions: IDecorationOptions): void; + renderDecoration(decoration: IInternalDecoration, decorationOptions: IDecorationOptions): void; } export class BufferDecorationRenderer extends Disposable implements IDecorationRenderer { + private _animationFrame: number | undefined; private _decorationContainer: HTMLElement; private readonly _decorations: BufferDecoration[] = []; private _altBufferIsActive: boolean = false; @@ -30,32 +31,40 @@ export class BufferDecorationRenderer extends Disposable implements IDecorationR this._decorationContainer = document.createElement('div'); this._decorationContainer.classList.add('xterm-decoration-container'); this._screenElement.appendChild(this._decorationContainer); - this.register(this._renderService.onRenderedBufferChange(() => this.refreshDecorations())); - this.register(this._renderService.onDimensionsChange(() => this.refreshDecorations())); - this.register(addDisposableDomListener(window, 'resize', () => this.refreshDecorations())); + 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))); } - public refreshDecorations(shouldRecreate?: boolean): void { - if (!this._renderService) { + + private _queueRefresh(): void { + if (this._animationFrame !== undefined) { return; } + this._animationFrame = window.requestAnimationFrame(() => { + this.refreshDecorations(); + this._animationFrame = undefined; + }); + } + + 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 renderDecoration(decorationOptions: IDecorationOptions): void { - if (decorationOptions.overviewRulerItemColor) { - return; - } - const decoration = new BufferDecoration(this._bufferService, decorationOptions); - (decoration as BufferDecoration).render(this._decorationContainer, this._renderService, true); - if (this._decorationContainer && decoration.element && !this._decorationContainer.contains(decoration.element)) { - this._decorationContainer.append(decoration.element!); + 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 { @@ -68,6 +77,7 @@ export class BufferDecorationRenderer extends Disposable implements IDecorationR super.dispose(); } } + export class BufferDecoration extends Disposable implements IDecoration { private readonly _marker: IMarker; private _element: HTMLElement | undefined; @@ -85,7 +95,6 @@ export class BufferDecoration extends Disposable implements IDecoration { private _onRender = new EventEmitter(); public get onRender(): IEvent { return this._onRender.event; } - public x: number; public anchor: 'left' | 'right'; public width: number; @@ -93,6 +102,7 @@ export class BufferDecoration extends Disposable implements IDecoration { constructor( private readonly _bufferService: IBufferService, + private readonly _internalDecoration: IInternalDecoration, options: IDecorationOptions ) { super(); @@ -107,16 +117,18 @@ export class BufferDecoration extends Disposable implements IDecoration { public render(container: HTMLElement, renderService: IRenderService, shouldRecreate?: boolean): void { this._container = container; if (!this._element || shouldRecreate) { - this._createElement(renderService, shouldRecreate); + const element = this._createElement(renderService, shouldRecreate); + 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!); } } - private _createElement(renderService: IRenderService, shouldRecreate?: boolean): void { + private _createElement(renderService: IRenderService, shouldRecreate?: boolean): HTMLElement { if (shouldRecreate && this._element && this._container && this._container.contains(this._element)) { this._container.removeChild(this._element); } @@ -136,6 +148,8 @@ export class BufferDecoration extends Disposable implements IDecoration { } else { this._element.style.left = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : ''; } + + return this._element; } private _refreshStyle(renderService: IRenderService): void { diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 580a831a..5f5fc397 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -5,10 +5,10 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { IDecorationRenderer } from 'browser/Decorations/BufferDecorationRenderer'; -import { IDecorationService, IRenderService } from 'browser/services/Services'; +import { IRenderService } from 'browser/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; -import { IBufferService, IInstantiationService } from 'common/services/Services'; +import { IBufferService, IDecorationService, IInstantiationService, IInternalDecoration } from 'common/services/Services'; import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; const enum ScrollbarConstants { WIDTH = 7 @@ -45,14 +45,16 @@ export class OverviewRulerRenderer extends Disposable implements IDecorationRend this.register(this._decorationService.onDecorationRegistered(e => this.renderDecoration(e))); this.register(this._decorationService.onDecorationRemoved(d => d.dispose())); } - public renderDecoration(decorationOptions: IDecorationOptions): void { - if (!this._ctx || !decorationOptions.overviewRulerItemColor) { + public renderDecoration(decoration: IInternalDecoration): void { + if (!this._ctx || !decoration.options.overviewRulerItemColor) { return; } - const decoration = this._instantiationService.createInstance(ScrollbarDecoration, { marker: decorationOptions.marker, overviewRulerItemColor: decorationOptions.overviewRulerItemColor }); + + // TODO: Does this do anything anymore? + this._instantiationService.createInstance(ScrollbarDecoration, { marker: decoration.options.marker, overviewRulerItemColor: decoration.options.overviewRulerItemColor }); this._ctx.lineWidth = 1; - this._ctx.strokeStyle = decorationOptions.overviewRulerItemColor; + this._ctx.strokeStyle = decoration.options.overviewRulerItemColor; this._ctx.strokeRect( 0, Math.round(this._canvas.height * (decoration.marker.line / this._bufferService.buffers.active.lines.length)), diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 2c00d79b..d07b9c30 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -83,11 +83,14 @@ export class Terminal extends CoreTerminal implements ITerminal { private _customKeyEventHandler: CustomKeyEventHandler | undefined; + // TODO: Move into CoreTerminal.ts + // common services + private _decorationService: DecorationService; + // browser services private _charSizeService: ICharSizeService | undefined; private _mouseService: IMouseService | undefined; private _renderService: IRenderService | undefined; - private _decorationService: DecorationService | undefined; private _characterJoinerService: ICharacterJoinerService | undefined; private _selectionService: ISelectionService | undefined; private _soundService: ISoundService | undefined; diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index b8e1626e..8860bb41 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -206,11 +206,6 @@ export interface ILinkifier { registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number; deregisterLinkMatcher(matcherId: number): boolean; } -export interface IDecorationService extends IDisposable { - readonly onDecorationRegistered: IEvent; - readonly onDecorationRemoved: IEvent; - registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; -} interface ILinkState { decorations: ILinkDecorations; diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 8587a5c9..1598ef02 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -115,9 +115,3 @@ export interface ICharacterJoinerService { deregister(joinerId: number): boolean; getJoinedCharacters(row: number): [number, number][]; } -export const IDecorationService = createDecorator('DecorationService'); -export interface IDecorationService extends IDisposable { - readonly onDecorationRegistered: IEvent; - readonly onDecorationRemoved: IEvent; - registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; -} diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 1bd67e0c..7c49c7b4 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -6,16 +6,16 @@ import { EventEmitter } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; -import { IDecorationService } from 'common/services/Services'; +import { IDecorationService, IInternalDecoration } from 'common/services/Services'; import { IDecorationOptions, IDecoration, IMarker, IEvent } from 'xterm'; export class DecorationService extends Disposable implements IDecorationService { private _animationFrame: number | undefined; - private _onDecorationRegistered = this.register(new EventEmitter()); - public get onDecorationRegistered(): IEvent { return this._onDecorationRegistered.event; } - private _onDecorationRemoved = this.register(new EventEmitter()); - public get onDecorationRemoved(): IEvent { return this._onDecorationRemoved.event; } - private _decorations: IDecoration[] = []; + private _onDecorationRegistered = this.register(new EventEmitter()); + public get onDecorationRegistered(): IEvent { return this._onDecorationRegistered.event; } + private _onDecorationRemoved = this.register(new EventEmitter()); + public get onDecorationRemoved(): IEvent { return this._onDecorationRemoved.event; } + private _decorations: IInternalDecoration[] = []; constructor() { super(); @@ -33,7 +33,7 @@ export class DecorationService extends Disposable implements IDecorationService } }); this._decorations.push(decoration); - this._onDecorationRegistered.fire(options); + this._onDecorationRegistered.fire(decoration); } return decoration; } @@ -57,19 +57,21 @@ export class DecorationService extends Disposable implements IDecorationService } } -class Decoration implements IDecoration { +class Decoration implements IInternalDecoration { public marker: IMarker; - private _onRender = new EventEmitter(); - public get onRender(): IEvent { return this._onRender.event; } + public readonly onRenderEmitter = new EventEmitter(); + public readonly onRender = this.onRenderEmitter.event; private _onDispose = new EventEmitter(); - public get onDispose(): IEvent { return this._onDispose.event; } + public readonly onDispose = this._onDispose.event; public element: HTMLElement | undefined; public isDisposed: boolean = false; public dispose(): void { throw new Error('Method not implemented.'); } - constructor(decorationOptions: IDecorationOptions) { - this.marker = decorationOptions?.marker; + constructor( + public readonly options: IDecorationOptions + ) { + this.marker = options.marker; this.element = undefined; } } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 8bd272f0..62108ffb 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IEvent } from 'common/EventEmitter'; +import { IEvent, IEventEmitter } from 'common/EventEmitter'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource, IDisposable } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; @@ -300,8 +300,14 @@ export interface IUnicodeVersionProvider { readonly version: string; wcwidth(ucs: number): 0 | 1 | 2; } + +export const IDecorationService = createDecorator('DecorationService'); export interface IDecorationService extends IDisposable { - readonly onDecorationRegistered: IEvent; - readonly onDecorationRemoved: IEvent; + readonly onDecorationRegistered: IEvent; + readonly onDecorationRemoved: IEvent; registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; } +export interface IInternalDecoration extends IDecoration { + readonly options: IDecorationOptions; + readonly onRenderEmitter: IEventEmitter; +}