From a5a9c3b3684797725da03b402c946262b35bced1 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Mar 2022 14:45:14 -0600 Subject: [PATCH 01/65] add scroll decorations --- css/xterm.css | 14 ++++++ demo/client.ts | 9 ++++ demo/index.html | 1 + src/browser/services/DecorationService.ts | 53 ++++++++++++++++++++++- typings/xterm.d.ts | 6 +++ 5 files changed, 82 insertions(+), 1 deletion(-) diff --git a/css/xterm.css b/css/xterm.css index ab3965b4..3ae486c4 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -178,3 +178,17 @@ z-index: 6; position: absolute; } + +.xterm-decoration-scrollbar { + z-index: 7; + position: fixed; + top: 0px; + right: 0px; + width: 50px; +} + +.xterm-decoration-scrollbar.demo-scrollbar { + height: 436px; + left: 872px; + top: 83px; +} diff --git a/demo/client.ts b/demo/client.ts index aee23401..ec7e95ee 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -151,6 +151,7 @@ if (document.location.pathname === '/test') { document.getElementById('custom-glyph').addEventListener('click', writeCustomGlyphHandler); document.getElementById('load-test').addEventListener('click', loadTest); document.getElementById('add-decoration').addEventListener('click', addDecoration); + document.getElementById('add-scrollbar-decoration').addEventListener('click', addScrollbarDecoration); } function createTerminal(): void { @@ -550,3 +551,11 @@ function addDecoration() { decoration.element.style.backgroundColor = 'red'; }); } + +function addScrollbarDecoration() { + term.registerDecoration({marker: term.addMarker(1), scrollbarDecorationColor: 'red'}); + term.registerDecoration({marker: term.addMarker(3), scrollbarDecorationColor: 'green'}); + term.registerDecoration({marker: term.addMarker(5), scrollbarDecorationColor: 'blue'}); + document.querySelector('.xterm-decoration-scrollbar').classList.add('demo-scrollbar'); +} + diff --git a/demo/index.html b/demo/index.html index e024222c..dab52eec 100644 --- a/demo/index.html +++ b/demo/index.html @@ -69,6 +69,7 @@ + diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index ed3c7224..dfbcd918 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -3,6 +3,7 @@ * @license MIT */ +import { addDisposableDomListener } from 'browser/Lifecycle'; import { IDecorationService, IRenderService } from 'browser/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; @@ -17,7 +18,11 @@ export class DecorationService extends Disposable implements IDecorationService private _renderService: IRenderService | undefined; private _animationFrame: number | undefined; - constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService) { super(); } + private _scrollbarDecorationCanvas: CanvasRenderingContext2D | null = null; + private _scrollbarDecorationNode: HTMLCanvasElement | undefined; + private _scrollbarDecorations: { marker: IMarker, color?: string}[] = []; + + constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) { super(); } public attachToDom(screenElement: HTMLElement, renderService: IRenderService): void { this._renderService = renderService; @@ -27,12 +32,22 @@ export class DecorationService extends Disposable implements IDecorationService screenElement.appendChild(this._container); this.register(this._renderService.onRenderedBufferChange(() => this.refresh())); this.register(this._renderService.onDimensionsChange(() => this.refresh(true))); + this.register(addDisposableDomListener(window, 'resize', () => this._refreshScollbarDecorations())); } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { if (decorationOptions.marker.isDisposed || !this._container) { return undefined; } + if (decorationOptions.scrollbarDecorationColor) { + if (!this._scrollbarDecorationCanvas) { + this._scrollbarDecorationNode = document.createElement('canvas'); + this._scrollbarDecorationNode.classList.add('xterm-decoration-scrollbar'); + this._screenElement?.parentElement?.appendChild(this._scrollbarDecorationNode); + this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); + } + this._registerScrollbarDecoration(decorationOptions.marker, decorationOptions.scrollbarDecorationColor); + } const decoration = this._instantiationService.createInstance(Decoration, decorationOptions, this._container); this._decorations.push(decoration); decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1)); @@ -57,6 +72,7 @@ export class DecorationService extends Disposable implements IDecorationService for (const decoration of this._decorations) { decoration.render(this._renderService, shouldRecreate); } + this._refreshScollbarDecorations(); } public dispose(): void { @@ -67,6 +83,41 @@ export class DecorationService extends Disposable implements IDecorationService this._screenElement.removeChild(this._container); } } + + private _registerScrollbarDecoration(marker: IMarker, color?: string): HTMLCanvasElement | undefined { + this._scrollbarDecorations.push({ marker, color }); + if (!this._scrollbarDecorationCanvas) { + return; + } + this._addScrollbarDecoration(marker, color); + } + + private _refreshScollbarDecorations(): void { + if (!this._scrollbarDecorationCanvas) { + return; + } + if (this._scrollbarDecorationNode) { + this._scrollbarDecorationNode.style.width = '7px'; + this._scrollbarDecorationNode.style.height = `${this._screenElement?.parentElement!.clientHeight}px`; + this._scrollbarDecorationNode.width = Math.floor(7*window.devicePixelRatio); + this._scrollbarDecorationNode.height = Math.floor(436*window.devicePixelRatio); + } + this._scrollbarDecorationCanvas.clearRect(0, 0, this._scrollbarDecorationCanvas.canvas.width, this._scrollbarDecorationCanvas.canvas.height); + for (const scrollbarDecoration of this._scrollbarDecorations) { + this._addScrollbarDecoration(scrollbarDecoration.marker, scrollbarDecoration.color); + } + } + + private _addScrollbarDecoration(marker: IMarker, color?: string): void { + if (!this._scrollbarDecorationCanvas || !this._screenElement?.parentElement?.clientHeight) { + return; + } + this._scrollbarDecorationCanvas.lineWidth = 1; + if (color) { + this._scrollbarDecorationCanvas.strokeStyle = color; + } + this._scrollbarDecorationCanvas.strokeRect(0, (marker.line / (this._bufferService.buffers.active.lines.length) * Math.floor(436*window.devicePixelRatio)), Math.floor(7*window.devicePixelRatio), window.devicePixelRatio); + } } export class Decoration extends Disposable implements IDecoration { private readonly _marker: IMarker; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 2cd4daa6..eb31c4a0 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -470,6 +470,12 @@ declare module 'xterm' { * cell height */ height?: number; + + /** + * When provided, renders the decoration in the scrollbar + * with the given color + */ + scrollbarDecorationColor?: string; } /** From 4350c7ffcd5e52cddae85ee46cd5e9a3677e14e0 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Mar 2022 16:23:51 -0600 Subject: [PATCH 02/65] get it to work --- src/browser/Terminal.ts | 9 +++- src/browser/services/DecorationService.ts | 60 +++++++++++++++++------ src/browser/services/Services.ts | 2 +- 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 08963933..a8263cb5 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -70,6 +70,7 @@ export class Terminal extends CoreTerminal implements ITerminal { private _viewportElement: HTMLElement | undefined; private _helperContainer: HTMLElement | undefined; private _compositionView: HTMLElement | undefined; + private _scrollbarDecorationNode: HTMLCanvasElement | undefined; // private _visualBellTimer: number; @@ -471,6 +472,12 @@ export class Terminal extends CoreTerminal implements ITerminal { this._viewportElement = document.createElement('div'); this._viewportElement.classList.add('xterm-viewport'); fragment.appendChild(this._viewportElement); + + //TODO: make this opt in, must be done before the scroll area in order to show up + this._scrollbarDecorationNode = document.createElement('canvas'); + this._scrollbarDecorationNode.classList.add('xterm-decoration-scrollbar'); + this._viewportElement?.appendChild(this._scrollbarDecorationNode); + this._viewportScrollArea = document.createElement('div'); this._viewportScrollArea.classList.add('xterm-scroll-area'); this._viewportElement.appendChild(this._viewportScrollArea); @@ -577,7 +584,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.linkifier.attachToDom(this.element, this._mouseZoneManager); this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService); - this.decorationService.attachToDom(this.screenElement, this._renderService, this._bufferService); + this.decorationService.attachToDom(this._scrollbarDecorationNode, this.screenElement, this._viewportElement, this._renderService); // This event listener must be registered aftre MouseZoneManager is created this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e))); diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index dfbcd918..d5b9bbc8 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -15,6 +15,7 @@ export class DecorationService extends Disposable implements IDecorationService private readonly _decorations: Decoration[] = []; private _container: HTMLElement | undefined; private _screenElement: HTMLElement | undefined; + private _viewportElement: HTMLElement | undefined; private _renderService: IRenderService | undefined; private _animationFrame: number | undefined; @@ -24,9 +25,11 @@ export class DecorationService extends Disposable implements IDecorationService constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) { super(); } - public attachToDom(screenElement: HTMLElement, renderService: IRenderService): void { + public attachToDom(scrollbarDecorationNode: HTMLCanvasElement, screenElement: HTMLElement, viewportElement: HTMLElement, renderService: IRenderService): void { this._renderService = renderService; this._screenElement = screenElement; + this._viewportElement = viewportElement; + this._scrollbarDecorationNode = scrollbarDecorationNode; this._container = document.createElement('div'); this._container.classList.add('xterm-decoration-container'); screenElement.appendChild(this._container); @@ -36,17 +39,14 @@ export class DecorationService extends Disposable implements IDecorationService } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - if (decorationOptions.marker.isDisposed || !this._container) { + if (decorationOptions.marker.isDisposed || !this._container || !this._scrollbarDecorationNode) { return undefined; } if (decorationOptions.scrollbarDecorationColor) { if (!this._scrollbarDecorationCanvas) { - this._scrollbarDecorationNode = document.createElement('canvas'); - this._scrollbarDecorationNode.classList.add('xterm-decoration-scrollbar'); - this._screenElement?.parentElement?.appendChild(this._scrollbarDecorationNode); this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); } - this._registerScrollbarDecoration(decorationOptions.marker, decorationOptions.scrollbarDecorationColor); + return this._registerScrollbarDecoration(decorationOptions.marker, decorationOptions.scrollbarDecorationColor); } const decoration = this._instantiationService.createInstance(Decoration, decorationOptions, this._container); this._decorations.push(decoration); @@ -82,23 +82,23 @@ export class DecorationService extends Disposable implements IDecorationService if (this._screenElement && this._container && this._screenElement.contains(this._container)) { this._screenElement.removeChild(this._container); } + this._scrollbarDecorations = []; + this._scrollbarDecorationNode?.remove(); } - private _registerScrollbarDecoration(marker: IMarker, color?: string): HTMLCanvasElement | undefined { + private _registerScrollbarDecoration(marker: IMarker, color?: string): IDecoration | undefined { this._scrollbarDecorations.push({ marker, color }); - if (!this._scrollbarDecorationCanvas) { - return; - } - this._addScrollbarDecoration(marker, color); + //TODO: marker on dispose + return this._addScrollbarDecoration(marker, color); } private _refreshScollbarDecorations(): void { - if (!this._scrollbarDecorationCanvas) { + if (!this._scrollbarDecorationCanvas || !this._viewportElement) { return; } if (this._scrollbarDecorationNode) { this._scrollbarDecorationNode.style.width = '7px'; - this._scrollbarDecorationNode.style.height = `${this._screenElement?.parentElement!.clientHeight}px`; + this._scrollbarDecorationNode.style.height = `${this._viewportElement.clientHeight}px`; this._scrollbarDecorationNode.width = Math.floor(7*window.devicePixelRatio); this._scrollbarDecorationNode.height = Math.floor(436*window.devicePixelRatio); } @@ -108,8 +108,8 @@ export class DecorationService extends Disposable implements IDecorationService } } - private _addScrollbarDecoration(marker: IMarker, color?: string): void { - if (!this._scrollbarDecorationCanvas || !this._screenElement?.parentElement?.clientHeight) { + private _addScrollbarDecoration(marker: IMarker, color?: string): IDecoration | undefined { + if (!this._scrollbarDecorationCanvas || !this._viewportElement?.clientHeight) { return; } this._scrollbarDecorationCanvas.lineWidth = 1; @@ -117,8 +117,38 @@ export class DecorationService extends Disposable implements IDecorationService this._scrollbarDecorationCanvas.strokeStyle = color; } this._scrollbarDecorationCanvas.strokeRect(0, (marker.line / (this._bufferService.buffers.active.lines.length) * Math.floor(436*window.devicePixelRatio)), Math.floor(7*window.devicePixelRatio), window.devicePixelRatio); + if (this._scrollbarDecorationNode) { + return new ScrollbarDecoration({marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode); + } + return undefined; } } +export class ScrollbarDecoration extends Disposable implements IDecoration { + private readonly _marker: IMarker; + private _element: HTMLElement | undefined; + + 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; } + + constructor( + options: IDecorationOptions, + element: HTMLCanvasElement + ) { + super(); + this._marker = options.marker; + this._element = element; + this._marker.onDispose(() => this.dispose()); + } +} + export class Decoration extends Disposable implements IDecoration { private readonly _marker: IMarker; private _element: HTMLElement | undefined; diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 7faf3f0f..caf6ec5a 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -121,5 +121,5 @@ export const IDecorationService = createDecorator('Decoratio export interface IDecorationService extends IDisposable { registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; refresh(): void; - attachToDom(screenElement: HTMLElement, renderService: IRenderService, bufferService: IBufferService): void; + attachToDom(scrollbarDecorationNode: HTMLCanvasElement, screenElement: HTMLElement, viewportElement: HTMLElement, renderService: IRenderService): void; } From 3a499fc35dae6163d16230e4b806e8be202adb7d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Mar 2022 20:31:40 -0600 Subject: [PATCH 03/65] clear on dispose --- demo/client.ts | 2 +- src/browser/services/DecorationService.ts | 57 +++++++++++++---------- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index ec7e95ee..7eb9247a 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -553,9 +553,9 @@ function addDecoration() { } function addScrollbarDecoration() { + document.querySelector('.xterm-decoration-scrollbar').classList.add('demo-scrollbar'); term.registerDecoration({marker: term.addMarker(1), scrollbarDecorationColor: 'red'}); term.registerDecoration({marker: term.addMarker(3), scrollbarDecorationColor: 'green'}); term.registerDecoration({marker: term.addMarker(5), scrollbarDecorationColor: 'blue'}); - document.querySelector('.xterm-decoration-scrollbar').classList.add('demo-scrollbar'); } diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index d5b9bbc8..f21be5df 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -12,13 +12,14 @@ import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; export class DecorationService extends Disposable implements IDecorationService { - private readonly _decorations: Decoration[] = []; private _container: HTMLElement | undefined; private _screenElement: HTMLElement | undefined; private _viewportElement: HTMLElement | undefined; private _renderService: IRenderService | undefined; private _animationFrame: number | undefined; + private readonly _bufferDecorations: BufferDecoration[] = []; + private _scrollbarDecorationCanvas: CanvasRenderingContext2D | null = null; private _scrollbarDecorationNode: HTMLCanvasElement | undefined; private _scrollbarDecorations: { marker: IMarker, color?: string}[] = []; @@ -46,13 +47,14 @@ export class DecorationService extends Disposable implements IDecorationService if (!this._scrollbarDecorationCanvas) { this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); } + this._refreshScollbarDecorations(); return this._registerScrollbarDecoration(decorationOptions.marker, decorationOptions.scrollbarDecorationColor); } - const decoration = this._instantiationService.createInstance(Decoration, decorationOptions, this._container); - this._decorations.push(decoration); - decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1)); + const bufferDecoration = this._instantiationService.createInstance(BufferDecoration, decorationOptions, this._container); + this._bufferDecorations.push(bufferDecoration); + bufferDecoration.onDispose(() => this._bufferDecorations.splice(this._bufferDecorations.indexOf(bufferDecoration), 1)); this._queueRefresh(); - return decoration; + return bufferDecoration; } private _queueRefresh(): void { @@ -66,18 +68,13 @@ export class DecorationService extends Disposable implements IDecorationService } public refresh(shouldRecreate?: boolean): void { - if (!this._renderService) { - return; - } - for (const decoration of this._decorations) { - decoration.render(this._renderService, shouldRecreate); - } + this._refreshBufferDecorations(shouldRecreate); this._refreshScollbarDecorations(); } public dispose(): void { - for (const decoration of this._decorations) { - decoration.dispose(); + for (const bufferDecoration of this._bufferDecorations) { + bufferDecoration.dispose(); } if (this._screenElement && this._container && this._screenElement.contains(this._container)) { this._screenElement.removeChild(this._container); @@ -88,20 +85,26 @@ export class DecorationService extends Disposable implements IDecorationService private _registerScrollbarDecoration(marker: IMarker, color?: string): IDecoration | undefined { this._scrollbarDecorations.push({ marker, color }); - //TODO: marker on dispose return this._addScrollbarDecoration(marker, color); } - private _refreshScollbarDecorations(): void { - if (!this._scrollbarDecorationCanvas || !this._viewportElement) { + private _refreshBufferDecorations(shouldRecreate?: boolean): void { + if (!this._renderService) { return; } - if (this._scrollbarDecorationNode) { - this._scrollbarDecorationNode.style.width = '7px'; - this._scrollbarDecorationNode.style.height = `${this._viewportElement.clientHeight}px`; - this._scrollbarDecorationNode.width = Math.floor(7*window.devicePixelRatio); - this._scrollbarDecorationNode.height = Math.floor(436*window.devicePixelRatio); + for (const bufferDecoration of this._bufferDecorations) { + bufferDecoration.render(this._renderService, shouldRecreate); } + } + + private _refreshScollbarDecorations(): void { + if (!this._scrollbarDecorationCanvas || !this._viewportElement || !this._scrollbarDecorationNode) { + return; + } + this._scrollbarDecorationNode.style.width = '7px'; + this._scrollbarDecorationNode.style.height = `${this._viewportElement.clientHeight}px`; + this._scrollbarDecorationNode.width = Math.floor(7*window.devicePixelRatio); + this._scrollbarDecorationNode.height = Math.floor(this._viewportElement.clientHeight*window.devicePixelRatio); this._scrollbarDecorationCanvas.clearRect(0, 0, this._scrollbarDecorationCanvas.canvas.width, this._scrollbarDecorationCanvas.canvas.height); for (const scrollbarDecoration of this._scrollbarDecorations) { this._addScrollbarDecoration(scrollbarDecoration.marker, scrollbarDecoration.color); @@ -116,9 +119,15 @@ export class DecorationService extends Disposable implements IDecorationService if (color) { this._scrollbarDecorationCanvas.strokeStyle = color; } - this._scrollbarDecorationCanvas.strokeRect(0, (marker.line / (this._bufferService.buffers.active.lines.length) * Math.floor(436*window.devicePixelRatio)), Math.floor(7*window.devicePixelRatio), window.devicePixelRatio); + this._scrollbarDecorationCanvas.strokeRect( + 0, + (marker.line / this._bufferService.buffers.active.lines.length) * Math.floor(this._viewportElement.clientHeight * window.devicePixelRatio), + Math.floor(7 * window.devicePixelRatio), + window.devicePixelRatio + ); if (this._scrollbarDecorationNode) { - return new ScrollbarDecoration({marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode); + const scrollbarDecoration = new ScrollbarDecoration({ marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode); + scrollbarDecoration.onDispose(() => this._scrollbarDecorationCanvas?.clearRect(0, 0, this._scrollbarDecorationCanvas.canvas.width, this._scrollbarDecorationCanvas.canvas.height)); } return undefined; } @@ -149,7 +158,7 @@ export class ScrollbarDecoration extends Disposable implements IDecoration { } } -export class Decoration extends Disposable implements IDecoration { +export class BufferDecoration extends Disposable implements IDecoration { private readonly _marker: IMarker; private _element: HTMLElement | undefined; From 785a8190a665a408563487e1027131098be6a671 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Mar 2022 20:41:49 -0600 Subject: [PATCH 04/65] clean up --- src/browser/services/DecorationService.ts | 62 ++++++++++++++--------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index f21be5df..bb5bd125 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -10,6 +10,10 @@ import { Disposable } from 'common/Lifecycle'; import { IBufferService, IInstantiationService } from 'common/services/Services'; import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; +const enum ScrollbarConstants { + WIDTH = 7 +} + export class DecorationService extends Disposable implements IDecorationService { private _container: HTMLElement | undefined; @@ -22,7 +26,7 @@ export class DecorationService extends Disposable implements IDecorationService private _scrollbarDecorationCanvas: CanvasRenderingContext2D | null = null; private _scrollbarDecorationNode: HTMLCanvasElement | undefined; - private _scrollbarDecorations: { marker: IMarker, color?: string}[] = []; + private _scrollbarDecorations: { marker: IMarker, color: string }[] = []; constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) { super(); } @@ -57,16 +61,6 @@ export class DecorationService extends Disposable implements IDecorationService return bufferDecoration; } - private _queueRefresh(): void { - if (this._animationFrame !== undefined) { - return; - } - this._animationFrame = window.requestAnimationFrame(() => { - this.refresh(); - this._animationFrame = undefined; - }); - } - public refresh(shouldRecreate?: boolean): void { this._refreshBufferDecorations(shouldRecreate); this._refreshScollbarDecorations(); @@ -83,9 +77,15 @@ export class DecorationService extends Disposable implements IDecorationService this._scrollbarDecorationNode?.remove(); } - private _registerScrollbarDecoration(marker: IMarker, color?: string): IDecoration | undefined { - this._scrollbarDecorations.push({ marker, color }); - return this._addScrollbarDecoration(marker, color); + + private _queueRefresh(): void { + if (this._animationFrame !== undefined) { + return; + } + this._animationFrame = window.requestAnimationFrame(() => { + this.refresh(); + this._animationFrame = undefined; + }); } private _refreshBufferDecorations(shouldRecreate?: boolean): void { @@ -97,37 +97,49 @@ export class DecorationService extends Disposable implements IDecorationService } } + private _registerScrollbarDecoration(marker: IMarker, color: string): IDecoration | undefined { + this._scrollbarDecorations.push({ marker, color }); + return this._addScrollbarDecoration(marker, color); + } + private _refreshScollbarDecorations(): void { if (!this._scrollbarDecorationCanvas || !this._viewportElement || !this._scrollbarDecorationNode) { return; } - this._scrollbarDecorationNode.style.width = '7px'; + this._scrollbarDecorationNode.style.width = `${ScrollbarConstants.WIDTH}px`; this._scrollbarDecorationNode.style.height = `${this._viewportElement.clientHeight}px`; - this._scrollbarDecorationNode.width = Math.floor(7*window.devicePixelRatio); - this._scrollbarDecorationNode.height = Math.floor(this._viewportElement.clientHeight*window.devicePixelRatio); + this._scrollbarDecorationNode.width = Math.floor(ScrollbarConstants.WIDTH * window.devicePixelRatio); + this._scrollbarDecorationNode.height = Math.floor(this._viewportElement.clientHeight * window.devicePixelRatio); this._scrollbarDecorationCanvas.clearRect(0, 0, this._scrollbarDecorationCanvas.canvas.width, this._scrollbarDecorationCanvas.canvas.height); + for (const scrollbarDecoration of this._scrollbarDecorations) { this._addScrollbarDecoration(scrollbarDecoration.marker, scrollbarDecoration.color); } } - private _addScrollbarDecoration(marker: IMarker, color?: string): IDecoration | undefined { - if (!this._scrollbarDecorationCanvas || !this._viewportElement?.clientHeight) { + private _addScrollbarDecoration(marker: IMarker, color: string): IDecoration | undefined { + if (!this._scrollbarDecorationCanvas || !this._scrollbarDecorationNode) { return; } this._scrollbarDecorationCanvas.lineWidth = 1; - if (color) { - this._scrollbarDecorationCanvas.strokeStyle = color; - } + this._scrollbarDecorationCanvas.strokeStyle = color; this._scrollbarDecorationCanvas.strokeRect( 0, - (marker.line / this._bufferService.buffers.active.lines.length) * Math.floor(this._viewportElement.clientHeight * window.devicePixelRatio), - Math.floor(7 * window.devicePixelRatio), + this._scrollbarDecorationNode.height * (marker.line / this._bufferService.buffers.active.lines.length), + this._scrollbarDecorationNode.width, window.devicePixelRatio ); if (this._scrollbarDecorationNode) { const scrollbarDecoration = new ScrollbarDecoration({ marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode); - scrollbarDecoration.onDispose(() => this._scrollbarDecorationCanvas?.clearRect(0, 0, this._scrollbarDecorationCanvas.canvas.width, this._scrollbarDecorationCanvas.canvas.height)); + scrollbarDecoration.onDispose(() => { + this._scrollbarDecorationCanvas?.clearRect( + 0, + 0, + this._scrollbarDecorationCanvas.canvas.width, + this._scrollbarDecorationCanvas.canvas.height + ); + }); + return scrollbarDecoration; } return undefined; } From d46a68592575c533f11373dee04fbef6cb1b9d20 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Mar 2022 20:49:32 -0600 Subject: [PATCH 05/65] more cleanup --- src/browser/services/DecorationService.ts | 83 +++++++++++++---------- src/browser/services/Services.ts | 3 +- 2 files changed, 48 insertions(+), 38 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index bb5bd125..25d413fa 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -38,8 +38,8 @@ export class DecorationService extends Disposable implements IDecorationService this._container = document.createElement('div'); this._container.classList.add('xterm-decoration-container'); screenElement.appendChild(this._container); - this.register(this._renderService.onRenderedBufferChange(() => this.refresh())); - this.register(this._renderService.onDimensionsChange(() => this.refresh(true))); + this.register(this._renderService.onRenderedBufferChange(() => this._refresh())); + this.register(this._renderService.onDimensionsChange(() => this._refresh(true))); this.register(addDisposableDomListener(window, 'resize', () => this._refreshScollbarDecorations())); } @@ -48,22 +48,9 @@ export class DecorationService extends Disposable implements IDecorationService return undefined; } if (decorationOptions.scrollbarDecorationColor) { - if (!this._scrollbarDecorationCanvas) { - this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); - } - this._refreshScollbarDecorations(); return this._registerScrollbarDecoration(decorationOptions.marker, decorationOptions.scrollbarDecorationColor); } - const bufferDecoration = this._instantiationService.createInstance(BufferDecoration, decorationOptions, this._container); - this._bufferDecorations.push(bufferDecoration); - bufferDecoration.onDispose(() => this._bufferDecorations.splice(this._bufferDecorations.indexOf(bufferDecoration), 1)); - this._queueRefresh(); - return bufferDecoration; - } - - public refresh(shouldRecreate?: boolean): void { - this._refreshBufferDecorations(shouldRecreate); - this._refreshScollbarDecorations(); + return this._registerBufferDecoration(decorationOptions); } public dispose(): void { @@ -77,45 +64,44 @@ export class DecorationService extends Disposable implements IDecorationService this._scrollbarDecorationNode?.remove(); } + private _refresh(shouldRecreate?: boolean): void { + this._refreshBufferDecorations(shouldRecreate); + this._refreshScollbarDecorations(); + } private _queueRefresh(): void { if (this._animationFrame !== undefined) { return; } this._animationFrame = window.requestAnimationFrame(() => { - this.refresh(); + this._refresh(); this._animationFrame = undefined; }); } - private _refreshBufferDecorations(shouldRecreate?: boolean): void { - if (!this._renderService) { + private _registerBufferDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { + if (!this._container) { return; } - for (const bufferDecoration of this._bufferDecorations) { - bufferDecoration.render(this._renderService, shouldRecreate); - } + const bufferDecoration = this._instantiationService.createInstance(BufferDecoration, decorationOptions, this._container); + this._bufferDecorations.push(bufferDecoration); + bufferDecoration.onDispose(() => this._bufferDecorations.splice(this._bufferDecorations.indexOf(bufferDecoration), 1)); + this._queueRefresh(); + return bufferDecoration; } private _registerScrollbarDecoration(marker: IMarker, color: string): IDecoration | undefined { + if (!this._scrollbarDecorationNode) { + return; + } + if (!this._scrollbarDecorationCanvas) { + this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); + this._refreshScollbarDecorations(); + } this._scrollbarDecorations.push({ marker, color }); return this._addScrollbarDecoration(marker, color); } - private _refreshScollbarDecorations(): void { - if (!this._scrollbarDecorationCanvas || !this._viewportElement || !this._scrollbarDecorationNode) { - return; - } - this._scrollbarDecorationNode.style.width = `${ScrollbarConstants.WIDTH}px`; - this._scrollbarDecorationNode.style.height = `${this._viewportElement.clientHeight}px`; - this._scrollbarDecorationNode.width = Math.floor(ScrollbarConstants.WIDTH * window.devicePixelRatio); - this._scrollbarDecorationNode.height = Math.floor(this._viewportElement.clientHeight * window.devicePixelRatio); - this._scrollbarDecorationCanvas.clearRect(0, 0, this._scrollbarDecorationCanvas.canvas.width, this._scrollbarDecorationCanvas.canvas.height); - - for (const scrollbarDecoration of this._scrollbarDecorations) { - this._addScrollbarDecoration(scrollbarDecoration.marker, scrollbarDecoration.color); - } - } private _addScrollbarDecoration(marker: IMarker, color: string): IDecoration | undefined { if (!this._scrollbarDecorationCanvas || !this._scrollbarDecorationNode) { @@ -143,6 +129,31 @@ export class DecorationService extends Disposable implements IDecorationService } return undefined; } + + private _refreshBufferDecorations(shouldRecreate?: boolean): void { + if (!this._renderService) { + return; + } + for (const bufferDecoration of this._bufferDecorations) { + bufferDecoration.render(this._renderService, shouldRecreate); + } + } + + private _refreshScollbarDecorations(): void { + if (!this._scrollbarDecorationCanvas || !this._viewportElement || !this._scrollbarDecorationNode) { + return; + } + this._scrollbarDecorationNode.style.width = `${ScrollbarConstants.WIDTH}px`; + this._scrollbarDecorationNode.style.height = `${this._viewportElement.clientHeight}px`; + this._scrollbarDecorationNode.width = Math.floor(ScrollbarConstants.WIDTH * window.devicePixelRatio); + this._scrollbarDecorationNode.height = Math.floor(this._viewportElement.clientHeight * window.devicePixelRatio); + this._scrollbarDecorationCanvas.clearRect(0, 0, this._scrollbarDecorationCanvas.canvas.width, this._scrollbarDecorationCanvas.canvas.height); + + for (const scrollbarDecoration of this._scrollbarDecorations) { + this._addScrollbarDecoration(scrollbarDecoration.marker, scrollbarDecoration.color); + } + } + } export class ScrollbarDecoration extends Disposable implements IDecoration { private readonly _marker: IMarker; diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index caf6ec5a..35943897 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -119,7 +119,6 @@ export interface ICharacterJoinerService { export const IDecorationService = createDecorator('DecorationService'); export interface IDecorationService extends IDisposable { - registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; - refresh(): void; attachToDom(scrollbarDecorationNode: HTMLCanvasElement, screenElement: HTMLElement, viewportElement: HTMLElement, renderService: IRenderService): void; + registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; } From 89112fb1aef20c993bd3a723683f8593f64797e3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Mar 2022 21:31:42 -0600 Subject: [PATCH 06/65] refactor to return / store IDecorations --- src/browser/services/DecorationService.ts | 72 +++++++++++++---------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 25d413fa..33aec4c7 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -23,10 +23,10 @@ export class DecorationService extends Disposable implements IDecorationService private _animationFrame: number | undefined; private readonly _bufferDecorations: BufferDecoration[] = []; + private _scrollDecorations: ScrollbarDecoration[] = []; private _scrollbarDecorationCanvas: CanvasRenderingContext2D | null = null; private _scrollbarDecorationNode: HTMLCanvasElement | undefined; - private _scrollbarDecorations: { marker: IMarker, color: string }[] = []; constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) { super(); } @@ -60,7 +60,10 @@ export class DecorationService extends Disposable implements IDecorationService if (this._screenElement && this._container && this._screenElement.contains(this._container)) { this._screenElement.removeChild(this._container); } - this._scrollbarDecorations = []; + for (const scrollbarDecoration of this._scrollDecorations) { + scrollbarDecoration.dispose(); + } + this._scrollDecorations = []; this._scrollbarDecorationNode?.remove(); } @@ -91,43 +94,26 @@ export class DecorationService extends Disposable implements IDecorationService } private _registerScrollbarDecoration(marker: IMarker, color: string): IDecoration | undefined { - if (!this._scrollbarDecorationNode) { + if (!this._scrollbarDecorationNode || !this._viewportElement) { return; } if (!this._scrollbarDecorationCanvas) { this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); this._refreshScollbarDecorations(); } - this._scrollbarDecorations.push({ marker, color }); - return this._addScrollbarDecoration(marker, color); - } - - - private _addScrollbarDecoration(marker: IMarker, color: string): IDecoration | undefined { - if (!this._scrollbarDecorationCanvas || !this._scrollbarDecorationNode) { - return; - } - this._scrollbarDecorationCanvas.lineWidth = 1; - this._scrollbarDecorationCanvas.strokeStyle = color; - this._scrollbarDecorationCanvas.strokeRect( + this._scrollbarDecorationCanvas!.lineWidth = 1; + this._scrollbarDecorationCanvas!.strokeStyle = color; + this._scrollbarDecorationCanvas!.strokeRect( 0, this._scrollbarDecorationNode.height * (marker.line / this._bufferService.buffers.active.lines.length), this._scrollbarDecorationNode.width, window.devicePixelRatio ); if (this._scrollbarDecorationNode) { - const scrollbarDecoration = new ScrollbarDecoration({ marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode); - scrollbarDecoration.onDispose(() => { - this._scrollbarDecorationCanvas?.clearRect( - 0, - 0, - this._scrollbarDecorationCanvas.canvas.width, - this._scrollbarDecorationCanvas.canvas.height - ); - }); + const scrollbarDecoration = new ScrollbarDecoration({ marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode, this._scrollbarDecorationCanvas!, this._viewportElement, this._bufferService); + this._scrollDecorations.push(scrollbarDecoration); return scrollbarDecoration; } - return undefined; } private _refreshBufferDecorations(shouldRecreate?: boolean): void { @@ -148,21 +134,22 @@ export class DecorationService extends Disposable implements IDecorationService this._scrollbarDecorationNode.width = Math.floor(ScrollbarConstants.WIDTH * window.devicePixelRatio); this._scrollbarDecorationNode.height = Math.floor(this._viewportElement.clientHeight * window.devicePixelRatio); this._scrollbarDecorationCanvas.clearRect(0, 0, this._scrollbarDecorationCanvas.canvas.width, this._scrollbarDecorationCanvas.canvas.height); - - for (const scrollbarDecoration of this._scrollbarDecorations) { - this._addScrollbarDecoration(scrollbarDecoration.marker, scrollbarDecoration.color); + for (const decoration of this._scrollDecorations) { + decoration.render(); } } } export class ScrollbarDecoration extends Disposable implements IDecoration { private readonly _marker: IMarker; - private _element: HTMLElement | undefined; + private _canvas: HTMLCanvasElement | undefined; + private _color: string | undefined; public isDisposed: boolean = false; - public get element(): HTMLElement | undefined { return this._element; } + public get element(): HTMLCanvasElement { return this._canvas!; } public get marker(): IMarker { return this._marker; } + public get color(): string { return this._color!; } private _onDispose = new EventEmitter(); public get onDispose(): IEvent { return this._onDispose.event; } @@ -172,12 +159,33 @@ export class ScrollbarDecoration extends Disposable implements IDecoration { constructor( options: IDecorationOptions, - element: HTMLCanvasElement + canvas: HTMLCanvasElement, + private readonly _ctx: CanvasRenderingContext2D, + private readonly _viewport: HTMLElement, + private readonly _bufferService: IBufferService ) { super(); this._marker = options.marker; - this._element = element; + this._canvas = canvas; + this._color = options.scrollbarDecorationColor; this._marker.onDispose(() => this.dispose()); + this.render(); + } + public render(): void { + this._ctx.lineWidth = 1; + this._ctx.strokeStyle = this.color; + this._ctx.strokeRect( + 0, + this.element.height * (this.marker.line / this._bufferService.buffers.active.lines.length), + this.element.width, + window.devicePixelRatio + ); + } + + public override dispose(): void { + this._ctx.clearRect(0, 0, this._ctx.canvas.width, this._ctx.canvas.height); + + super.dispose(); } } From 249e73cd255ec71c3e6e9ae37149e02910a8ec6b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Mar 2022 21:35:31 -0600 Subject: [PATCH 07/65] delete unused code --- src/browser/services/DecorationService.ts | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 33aec4c7..db8aaee2 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -101,19 +101,9 @@ export class DecorationService extends Disposable implements IDecorationService this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); this._refreshScollbarDecorations(); } - this._scrollbarDecorationCanvas!.lineWidth = 1; - this._scrollbarDecorationCanvas!.strokeStyle = color; - this._scrollbarDecorationCanvas!.strokeRect( - 0, - this._scrollbarDecorationNode.height * (marker.line / this._bufferService.buffers.active.lines.length), - this._scrollbarDecorationNode.width, - window.devicePixelRatio - ); - if (this._scrollbarDecorationNode) { - const scrollbarDecoration = new ScrollbarDecoration({ marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode, this._scrollbarDecorationCanvas!, this._viewportElement, this._bufferService); - this._scrollDecorations.push(scrollbarDecoration); - return scrollbarDecoration; - } + const scrollbarDecoration = new ScrollbarDecoration({ marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode, this._scrollbarDecorationCanvas!, this._bufferService); + this._scrollDecorations.push(scrollbarDecoration); + return scrollbarDecoration; } private _refreshBufferDecorations(shouldRecreate?: boolean): void { @@ -161,7 +151,6 @@ export class ScrollbarDecoration extends Disposable implements IDecoration { options: IDecorationOptions, canvas: HTMLCanvasElement, private readonly _ctx: CanvasRenderingContext2D, - private readonly _viewport: HTMLElement, private readonly _bufferService: IBufferService ) { super(); From 1948ec886f7df322b3ec14fbaa87eaba7acf2872 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Mar 2022 08:27:50 -0600 Subject: [PATCH 08/65] only clear relevant part of canvas on marker dispose --- src/browser/services/DecorationService.ts | 40 ++++++++++++++--------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index db8aaee2..aa3a6e29 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -23,7 +23,7 @@ export class DecorationService extends Disposable implements IDecorationService private _animationFrame: number | undefined; private readonly _bufferDecorations: BufferDecoration[] = []; - private _scrollDecorations: ScrollbarDecoration[] = []; + private _scrollbarDecorations: ScrollbarDecoration[] = []; private _scrollbarDecorationCanvas: CanvasRenderingContext2D | null = null; private _scrollbarDecorationNode: HTMLCanvasElement | undefined; @@ -60,10 +60,10 @@ export class DecorationService extends Disposable implements IDecorationService if (this._screenElement && this._container && this._screenElement.contains(this._container)) { this._screenElement.removeChild(this._container); } - for (const scrollbarDecoration of this._scrollDecorations) { + for (const scrollbarDecoration of this._scrollbarDecorations) { scrollbarDecoration.dispose(); } - this._scrollDecorations = []; + this._scrollbarDecorations = []; this._scrollbarDecorationNode?.remove(); } @@ -86,11 +86,11 @@ export class DecorationService extends Disposable implements IDecorationService if (!this._container) { return; } - const bufferDecoration = this._instantiationService.createInstance(BufferDecoration, decorationOptions, this._container); - this._bufferDecorations.push(bufferDecoration); - bufferDecoration.onDispose(() => this._bufferDecorations.splice(this._bufferDecorations.indexOf(bufferDecoration), 1)); + const decoration = this._instantiationService.createInstance(BufferDecoration, decorationOptions, this._container); + this._bufferDecorations.push(decoration); + decoration.onDispose(() => this._bufferDecorations.splice(this._bufferDecorations.indexOf(decoration), 1)); this._queueRefresh(); - return bufferDecoration; + return decoration; } private _registerScrollbarDecoration(marker: IMarker, color: string): IDecoration | undefined { @@ -101,17 +101,18 @@ export class DecorationService extends Disposable implements IDecorationService this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); this._refreshScollbarDecorations(); } - const scrollbarDecoration = new ScrollbarDecoration({ marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode, this._scrollbarDecorationCanvas!, this._bufferService); - this._scrollDecorations.push(scrollbarDecoration); - return scrollbarDecoration; + const decoration = new ScrollbarDecoration({ marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode, this._scrollbarDecorationCanvas!, this._bufferService); + decoration.onDispose(() => this._scrollbarDecorations.splice(this._scrollbarDecorations.indexOf(decoration), 1)); + this._scrollbarDecorations.push(decoration); + return decoration; } private _refreshBufferDecorations(shouldRecreate?: boolean): void { if (!this._renderService) { return; } - for (const bufferDecoration of this._bufferDecorations) { - bufferDecoration.render(this._renderService, shouldRecreate); + for (const decoration of this._bufferDecorations) { + decoration.render(this._renderService, shouldRecreate); } } @@ -124,7 +125,7 @@ export class DecorationService extends Disposable implements IDecorationService this._scrollbarDecorationNode.width = Math.floor(ScrollbarConstants.WIDTH * window.devicePixelRatio); this._scrollbarDecorationNode.height = Math.floor(this._viewportElement.clientHeight * window.devicePixelRatio); this._scrollbarDecorationCanvas.clearRect(0, 0, this._scrollbarDecorationCanvas.canvas.width, this._scrollbarDecorationCanvas.canvas.height); - for (const decoration of this._scrollDecorations) { + for (const decoration of this._scrollbarDecorations) { decoration.render(); } } @@ -172,8 +173,17 @@ export class ScrollbarDecoration extends Disposable implements IDecoration { } public override dispose(): void { - this._ctx.clearRect(0, 0, this._ctx.canvas.width, this._ctx.canvas.height); - + if (this._isDisposed) { + return; + } + this._ctx.clearRect( + 0, + this.element.height * (this.marker.line / this._bufferService.buffers.active.lines.length), + this.element.width, + window.devicePixelRatio + ); + this.isDisposed = true; + this._onDispose.fire(); super.dispose(); } } From cfd7912afa1c4c35fdebb5cd1217ec147bb28066 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Mar 2022 08:40:40 -0600 Subject: [PATCH 09/65] enable registering a decoration before attach to dom has happened --- src/browser/Terminal.ts | 4 +- src/browser/services/DecorationService.ts | 51 ++++++++++++----------- src/browser/services/Services.ts | 2 +- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index a8263cb5..7dec6323 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -473,7 +473,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this._viewportElement.classList.add('xterm-viewport'); fragment.appendChild(this._viewportElement); - //TODO: make this opt in, must be done before the scroll area in order to show up + // TODO: make this opt in, must be done before the scroll area in order to show up this._scrollbarDecorationNode = document.createElement('canvas'); this._scrollbarDecorationNode.classList.add('xterm-decoration-scrollbar'); this._viewportElement?.appendChild(this._scrollbarDecorationNode); @@ -584,7 +584,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.linkifier.attachToDom(this.element, this._mouseZoneManager); this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService); - this.decorationService.attachToDom(this._scrollbarDecorationNode, this.screenElement, this._viewportElement, this._renderService); + this.decorationService.attachToDom(this._renderService, this.screenElement, this._viewportElement, this._scrollbarDecorationNode); // This event listener must be registered aftre MouseZoneManager is created this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e))); diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index aa3a6e29..6580a2c9 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -7,6 +7,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { IDecorationService, IRenderService } from 'browser/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; +import { BufferService } from 'common/services/BufferService'; import { IBufferService, IInstantiationService } from 'common/services/Services'; import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; @@ -16,35 +17,33 @@ const enum ScrollbarConstants { export class DecorationService extends Disposable implements IDecorationService { - private _container: HTMLElement | undefined; - private _screenElement: HTMLElement | undefined; - private _viewportElement: HTMLElement | undefined; private _renderService: IRenderService | undefined; private _animationFrame: number | undefined; + private _screenElement: HTMLElement | undefined; + private _viewportElement: HTMLElement | undefined; + private _bufferDecorationContainer: HTMLElement | undefined; + private _scrollbarDecorationCanvas: CanvasRenderingContext2D | null = null; + private _scrollbarDecorationNode: HTMLCanvasElement | undefined; + private readonly _bufferDecorations: BufferDecoration[] = []; private _scrollbarDecorations: ScrollbarDecoration[] = []; - private _scrollbarDecorationCanvas: CanvasRenderingContext2D | null = null; - private _scrollbarDecorationNode: HTMLCanvasElement | undefined; + constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService) { super(); } - constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) { super(); } - - public attachToDom(scrollbarDecorationNode: HTMLCanvasElement, screenElement: HTMLElement, viewportElement: HTMLElement, renderService: IRenderService): void { + public attachToDom(renderService: IRenderService, screenElement: HTMLElement, viewportElement: HTMLElement, scrollbarDecorationNode: HTMLCanvasElement): void { this._renderService = renderService; this._screenElement = screenElement; this._viewportElement = viewportElement; this._scrollbarDecorationNode = scrollbarDecorationNode; - this._container = document.createElement('div'); - this._container.classList.add('xterm-decoration-container'); - screenElement.appendChild(this._container); + this.register(this._renderService.onRenderedBufferChange(() => this._refresh())); this.register(this._renderService.onDimensionsChange(() => this._refresh(true))); this.register(addDisposableDomListener(window, 'resize', () => this._refreshScollbarDecorations())); } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - if (decorationOptions.marker.isDisposed || !this._container || !this._scrollbarDecorationNode) { + if (decorationOptions.marker.isDisposed) { return undefined; } if (decorationOptions.scrollbarDecorationColor) { @@ -57,12 +56,12 @@ export class DecorationService extends Disposable implements IDecorationService for (const bufferDecoration of this._bufferDecorations) { bufferDecoration.dispose(); } - if (this._screenElement && this._container && this._screenElement.contains(this._container)) { - this._screenElement.removeChild(this._container); - } for (const scrollbarDecoration of this._scrollbarDecorations) { scrollbarDecoration.dispose(); } + if (this._screenElement && this._bufferDecorationContainer && this._screenElement.contains(this._bufferDecorationContainer)) { + this._screenElement.removeChild(this._bufferDecorationContainer); + } this._scrollbarDecorations = []; this._scrollbarDecorationNode?.remove(); } @@ -83,10 +82,12 @@ export class DecorationService extends Disposable implements IDecorationService } private _registerBufferDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - if (!this._container) { - return; + if (this._screenElement && !this._bufferDecorationContainer) { + this._bufferDecorationContainer = document.createElement('div'); + this._bufferDecorationContainer.classList.add('xterm-decoration-container'); + this._screenElement.appendChild(this._bufferDecorationContainer); } - const decoration = this._instantiationService.createInstance(BufferDecoration, decorationOptions, this._container); + const decoration = new BufferDecoration(this._instantiationService.createInstance(BufferService), decorationOptions, this._bufferDecorationContainer); this._bufferDecorations.push(decoration); decoration.onDispose(() => this._bufferDecorations.splice(this._bufferDecorations.indexOf(decoration), 1)); this._queueRefresh(); @@ -101,7 +102,7 @@ export class DecorationService extends Disposable implements IDecorationService this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); this._refreshScollbarDecorations(); } - const decoration = new ScrollbarDecoration({ marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode, this._scrollbarDecorationCanvas!, this._bufferService); + const decoration = this._instantiationService.createInstance(ScrollbarDecoration, { marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode, this._scrollbarDecorationCanvas!); decoration.onDispose(() => this._scrollbarDecorations.splice(this._scrollbarDecorations.indexOf(decoration), 1)); this._scrollbarDecorations.push(decoration); return decoration; @@ -129,8 +130,8 @@ export class DecorationService extends Disposable implements IDecorationService decoration.render(); } } - } + export class ScrollbarDecoration extends Disposable implements IDecoration { private readonly _marker: IMarker; private _canvas: HTMLCanvasElement | undefined; @@ -152,7 +153,7 @@ export class ScrollbarDecoration extends Disposable implements IDecoration { options: IDecorationOptions, canvas: HTMLCanvasElement, private readonly _ctx: CanvasRenderingContext2D, - private readonly _bufferService: IBufferService + @IBufferService private readonly _bufferService: IBufferService ) { super(); this._marker = options.marker; @@ -209,9 +210,9 @@ export class BufferDecoration extends Disposable implements IDecoration { public height: number; constructor( + private readonly _bufferService: IBufferService, options: IDecorationOptions, - private readonly _container: HTMLElement, - @IBufferService private readonly _bufferService: IBufferService + private readonly _container?: HTMLElement ) { super(); this.x = options.x ?? 0; @@ -236,7 +237,7 @@ export class BufferDecoration extends Disposable implements IDecoration { } private _createElement(renderService: IRenderService, shouldRecreate?: boolean): void { - if (shouldRecreate && this._element && this._container.contains(this._element)) { + if (shouldRecreate && this._element && this._container && this._container.contains(this._element)) { this._container.removeChild(this._element); } this._element = document.createElement('div'); @@ -272,7 +273,7 @@ export class BufferDecoration extends Disposable implements IDecoration { } public override dispose(): void { - if (this.isDisposed) { + if (this.isDisposed || !this._container) { return; } if (this._element && this._container.contains(this._element)) { diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 35943897..6c9d29b5 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -119,6 +119,6 @@ export interface ICharacterJoinerService { export const IDecorationService = createDecorator('DecorationService'); export interface IDecorationService extends IDisposable { - attachToDom(scrollbarDecorationNode: HTMLCanvasElement, screenElement: HTMLElement, viewportElement: HTMLElement, renderService: IRenderService): void; + attachToDom(renderService: IRenderService, screenElement: HTMLElement, viewportElement: HTMLElement, scrollbarDecorationNode: HTMLCanvasElement): void; registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; } From e797070121c6f1e3cf06ec950cbdc89248d48dfc Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Mar 2022 08:42:12 -0600 Subject: [PATCH 10/65] call on render --- src/browser/services/DecorationService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 6580a2c9..39d89294 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -171,6 +171,7 @@ export class ScrollbarDecoration extends Disposable implements IDecoration { this.element.width, window.devicePixelRatio ); + this._onRender.fire(this.element); } public override dispose(): void { From 724bbebc14d92ec4019bf0eeb7b8e85be08652b0 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Mar 2022 08:46:09 -0600 Subject: [PATCH 11/65] assign element in render call --- src/browser/services/DecorationService.ts | 10 ++++++---- typings/xterm.d.ts | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 39d89294..ee1bc2e7 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -134,12 +134,12 @@ export class DecorationService extends Disposable implements IDecorationService export class ScrollbarDecoration extends Disposable implements IDecoration { private readonly _marker: IMarker; - private _canvas: HTMLCanvasElement | undefined; + private _element: HTMLCanvasElement | undefined; private _color: string | undefined; public isDisposed: boolean = false; - public get element(): HTMLCanvasElement { return this._canvas!; } + public get element(): HTMLCanvasElement { return this._element!; } public get marker(): IMarker { return this._marker; } public get color(): string { return this._color!; } @@ -151,18 +151,20 @@ export class ScrollbarDecoration extends Disposable implements IDecoration { constructor( options: IDecorationOptions, - canvas: HTMLCanvasElement, + private readonly _canvas: HTMLCanvasElement, private readonly _ctx: CanvasRenderingContext2D, @IBufferService private readonly _bufferService: IBufferService ) { super(); this._marker = options.marker; - this._canvas = canvas; this._color = options.scrollbarDecorationColor; this._marker.onDispose(() => this.dispose()); this.render(); } public render(): void { + if (!this._element) { + this._element = this._canvas; + } this._ctx.lineWidth = 1; this._ctx.strokeStyle = this.color; this._ctx.strokeRect( diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index eb31c4a0..510c099f 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -427,8 +427,8 @@ declare module 'xterm' { readonly onRender: IEvent; /** - * The HTMLElement that gets created after the - * first _onRender call, or undefined if accessed before + * The HTMLElement that gets created or drawn to (for scrollbar decorations) + * after the first _onRender call, or undefined if accessed before * that. */ readonly element: HTMLElement | undefined; From 89a0cee5a49006bc29096e69e49efeb530a8423f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Mar 2022 11:17:26 -0600 Subject: [PATCH 12/65] when alt buffer is active, hide decorations --- src/browser/services/DecorationService.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index ee1bc2e7..0a54adc6 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -29,7 +29,7 @@ export class DecorationService extends Disposable implements IDecorationService private readonly _bufferDecorations: BufferDecoration[] = []; private _scrollbarDecorations: ScrollbarDecoration[] = []; - constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService) { super(); } + constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) { super(); } public attachToDom(renderService: IRenderService, screenElement: HTMLElement, viewportElement: HTMLElement, scrollbarDecorationNode: HTMLCanvasElement): void { this._renderService = renderService; @@ -40,6 +40,9 @@ export class DecorationService extends Disposable implements IDecorationService this.register(this._renderService.onRenderedBufferChange(() => this._refresh())); this.register(this._renderService.onDimensionsChange(() => this._refresh(true))); this.register(addDisposableDomListener(window, 'resize', () => this._refreshScollbarDecorations())); + this.register(this._bufferService.buffers.onBufferActivate(() => { + this._scrollbarDecorationNode!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; + })); } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { @@ -87,7 +90,7 @@ export class DecorationService extends Disposable implements IDecorationService this._bufferDecorationContainer.classList.add('xterm-decoration-container'); this._screenElement.appendChild(this._bufferDecorationContainer); } - const decoration = new BufferDecoration(this._instantiationService.createInstance(BufferService), decorationOptions, this._bufferDecorationContainer); + const decoration = new BufferDecoration(this._bufferService, decorationOptions, this._bufferDecorationContainer); this._bufferDecorations.push(decoration); decoration.onDispose(() => this._bufferDecorations.splice(this._bufferDecorations.indexOf(decoration), 1)); this._queueRefresh(); @@ -207,6 +210,8 @@ export class BufferDecoration extends Disposable implements IDecoration { private _onRender = new EventEmitter(); public get onRender(): IEvent { return this._onRender.event; } + private _altBufferIsActive: boolean = false; + public x: number; public anchor: 'left' | 'right'; public width: number; @@ -224,6 +229,9 @@ export class BufferDecoration extends Disposable implements IDecoration { this.anchor = options.anchor || 'left'; this.width = options.width || 1; this.height = options.height || 1; + this.register(this._bufferService.buffers.onBufferActivate(() => { + this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt; + })); } public render(renderService: IRenderService, shouldRecreate?: boolean): void { @@ -271,7 +279,7 @@ export class BufferDecoration extends Disposable implements IDecoration { this._element.style.display = 'none'; } else { this._element.style.top = `${line * renderService.dimensions.actualCellHeight}px`; - this._element.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; + this._element.style.display = this._altBufferIsActive ? 'none' : 'block'; } } From 167a6fdbf6a602b930bd421f7792291c1e8600ca Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Mar 2022 12:24:37 -0600 Subject: [PATCH 13/65] use position sticky --- css/xterm.css | 5 ++--- demo/client.ts | 5 +++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/css/xterm.css b/css/xterm.css index 3ae486c4..913cdb99 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -181,7 +181,7 @@ .xterm-decoration-scrollbar { z-index: 7; - position: fixed; + position: sticky; top: 0px; right: 0px; width: 50px; @@ -189,6 +189,5 @@ .xterm-decoration-scrollbar.demo-scrollbar { height: 436px; - left: 872px; - top: 83px; + z-index:10; } diff --git a/demo/client.ts b/demo/client.ts index 7eb9247a..f7de9692 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -553,8 +553,9 @@ function addDecoration() { } function addScrollbarDecoration() { - document.querySelector('.xterm-decoration-scrollbar').classList.add('demo-scrollbar'); - term.registerDecoration({marker: term.addMarker(1), scrollbarDecorationColor: 'red'}); + document.querySelector('.xterm-decoration-scrollbar')?.classList.add('demo-scrollbar'); + const scrollbarDecorationCanvas = term.registerDecoration({marker: term.addMarker(1), scrollbarDecorationColor: 'red'}); + scrollbarDecorationCanvas.element!.style.left = `${scrollbarDecorationCanvas.element!.nextElementSibling!.clientWidth + 89}px`; term.registerDecoration({marker: term.addMarker(3), scrollbarDecorationColor: 'green'}); term.registerDecoration({marker: term.addMarker(5), scrollbarDecorationColor: 'blue'}); } From bdfc82d1b78c5b656c4351bb56849ef5c5039e12 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 10 Mar 2022 10:25:34 -0500 Subject: [PATCH 14/65] delete unused import --- src/browser/services/DecorationService.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 0a54adc6..d264b43c 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -7,7 +7,6 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { IDecorationService, IRenderService } from 'browser/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; -import { BufferService } from 'common/services/BufferService'; import { IBufferService, IInstantiationService } from 'common/services/Services'; import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; From 2f85448e512cc24b0914992c7688fd9cce7c5edd Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 11 Mar 2022 16:53:59 -0500 Subject: [PATCH 15/65] Update css/xterm.css Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- css/xterm.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/css/xterm.css b/css/xterm.css index 913cdb99..215c1446 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -182,8 +182,8 @@ .xterm-decoration-scrollbar { z-index: 7; position: sticky; - top: 0px; - right: 0px; + top: 0; + right: 0; width: 50px; } From 98a03dd477c182707b6e7dd5dfc0dd9f7dd6188f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 11 Mar 2022 17:15:11 -0500 Subject: [PATCH 16/65] re-arrange dom structure --- css/xterm.css | 11 +++-------- demo/client.ts | 3 +-- src/browser/Terminal.ts | 8 +------- src/browser/services/DecorationService.ts | 11 ++++++++--- src/browser/services/Services.ts | 2 +- 5 files changed, 14 insertions(+), 21 deletions(-) diff --git a/css/xterm.css b/css/xterm.css index 913cdb99..ef12f004 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -180,14 +180,9 @@ } .xterm-decoration-scrollbar { - z-index: 7; - position: sticky; + z-index: 6; + position: absolute; top: 0px; right: 0px; width: 50px; -} - -.xterm-decoration-scrollbar.demo-scrollbar { - height: 436px; - z-index:10; -} +} \ No newline at end of file diff --git a/demo/client.ts b/demo/client.ts index f7de9692..0b3ce37e 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -553,9 +553,8 @@ function addDecoration() { } function addScrollbarDecoration() { - document.querySelector('.xterm-decoration-scrollbar')?.classList.add('demo-scrollbar'); const scrollbarDecorationCanvas = term.registerDecoration({marker: term.addMarker(1), scrollbarDecorationColor: 'red'}); - scrollbarDecorationCanvas.element!.style.left = `${scrollbarDecorationCanvas.element!.nextElementSibling!.clientWidth + 89}px`; + scrollbarDecorationCanvas.element!.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 5}px`; term.registerDecoration({marker: term.addMarker(3), scrollbarDecorationColor: 'green'}); term.registerDecoration({marker: term.addMarker(5), scrollbarDecorationColor: 'blue'}); } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 7dec6323..ba0fc93d 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -70,7 +70,6 @@ export class Terminal extends CoreTerminal implements ITerminal { private _viewportElement: HTMLElement | undefined; private _helperContainer: HTMLElement | undefined; private _compositionView: HTMLElement | undefined; - private _scrollbarDecorationNode: HTMLCanvasElement | undefined; // private _visualBellTimer: number; @@ -473,11 +472,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this._viewportElement.classList.add('xterm-viewport'); fragment.appendChild(this._viewportElement); - // TODO: make this opt in, must be done before the scroll area in order to show up - this._scrollbarDecorationNode = document.createElement('canvas'); - this._scrollbarDecorationNode.classList.add('xterm-decoration-scrollbar'); - this._viewportElement?.appendChild(this._scrollbarDecorationNode); - this._viewportScrollArea = document.createElement('div'); this._viewportScrollArea.classList.add('xterm-scroll-area'); this._viewportElement.appendChild(this._viewportScrollArea); @@ -584,7 +578,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.linkifier.attachToDom(this.element, this._mouseZoneManager); this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService); - this.decorationService.attachToDom(this._renderService, this.screenElement, this._viewportElement, this._scrollbarDecorationNode); + this.decorationService.attachToDom(this._renderService, this.screenElement, this._viewportElement); // This event listener must be registered aftre MouseZoneManager is created this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e))); diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index d264b43c..74489345 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -30,11 +30,10 @@ export class DecorationService extends Disposable implements IDecorationService constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) { super(); } - public attachToDom(renderService: IRenderService, screenElement: HTMLElement, viewportElement: HTMLElement, scrollbarDecorationNode: HTMLCanvasElement): void { + public attachToDom(renderService: IRenderService, screenElement: HTMLElement, viewportElement: HTMLElement): void { this._renderService = renderService; this._screenElement = screenElement; this._viewportElement = viewportElement; - this._scrollbarDecorationNode = scrollbarDecorationNode; this.register(this._renderService.onRenderedBufferChange(() => this._refresh())); this.register(this._renderService.onDimensionsChange(() => this._refresh(true))); @@ -97,9 +96,15 @@ export class DecorationService extends Disposable implements IDecorationService } private _registerScrollbarDecoration(marker: IMarker, color: string): IDecoration | undefined { - if (!this._scrollbarDecorationNode || !this._viewportElement) { + if (!this._viewportElement?.parentElement) { return; } + if (!this._scrollbarDecorationNode) { + // TODO: make this opt in, must be done before the scroll area in order to show up + this._scrollbarDecorationNode = document.createElement('canvas'); + this._scrollbarDecorationNode.classList.add('xterm-decoration-scrollbar'); + this._viewportElement.parentElement.appendChild(this._scrollbarDecorationNode); + } if (!this._scrollbarDecorationCanvas) { this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); this._refreshScollbarDecorations(); diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 6c9d29b5..2d1f3d03 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -119,6 +119,6 @@ export interface ICharacterJoinerService { export const IDecorationService = createDecorator('DecorationService'); export interface IDecorationService extends IDisposable { - attachToDom(renderService: IRenderService, screenElement: HTMLElement, viewportElement: HTMLElement, scrollbarDecorationNode: HTMLCanvasElement): void; + attachToDom(renderService: IRenderService, screenElement: HTMLElement, viewportElement: HTMLElement): void; registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; } From 1250ab637a67ef681ec03e5d96354c730c52e36f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 11 Mar 2022 17:52:04 -0500 Subject: [PATCH 17/65] insert before --- src/browser/services/DecorationService.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 74489345..2dddf825 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -103,7 +103,7 @@ export class DecorationService extends Disposable implements IDecorationService // TODO: make this opt in, must be done before the scroll area in order to show up this._scrollbarDecorationNode = document.createElement('canvas'); this._scrollbarDecorationNode.classList.add('xterm-decoration-scrollbar'); - this._viewportElement.parentElement.appendChild(this._scrollbarDecorationNode); + this._viewportElement.parentElement.insertBefore(this._scrollbarDecorationNode, this._viewportElement); } if (!this._scrollbarDecorationCanvas) { this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); @@ -129,9 +129,9 @@ export class DecorationService extends Disposable implements IDecorationService return; } this._scrollbarDecorationNode.style.width = `${ScrollbarConstants.WIDTH}px`; - this._scrollbarDecorationNode.style.height = `${this._viewportElement.clientHeight}px`; + this._scrollbarDecorationNode.style.height = `${this._screenElement!.clientHeight}px`; this._scrollbarDecorationNode.width = Math.floor(ScrollbarConstants.WIDTH * window.devicePixelRatio); - this._scrollbarDecorationNode.height = Math.floor(this._viewportElement.clientHeight * window.devicePixelRatio); + this._scrollbarDecorationNode.height = Math.floor(this._screenElement!.clientHeight * window.devicePixelRatio); this._scrollbarDecorationCanvas.clearRect(0, 0, this._scrollbarDecorationCanvas.canvas.width, this._scrollbarDecorationCanvas.canvas.height); for (const decoration of this._scrollbarDecorations) { decoration.render(); From 4022277cec2fd75265a7faaf36c23139df9d65e1 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 11 Mar 2022 21:52:16 -0500 Subject: [PATCH 18/65] scroll -> overviewRuler --- demo/client.ts | 12 +++--- demo/index.html | 2 +- src/browser/services/DecorationService.ts | 52 +++++++++++------------ typings/xterm.d.ts | 2 +- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 0b3ce37e..586114d1 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -151,7 +151,7 @@ if (document.location.pathname === '/test') { document.getElementById('custom-glyph').addEventListener('click', writeCustomGlyphHandler); document.getElementById('load-test').addEventListener('click', loadTest); document.getElementById('add-decoration').addEventListener('click', addDecoration); - document.getElementById('add-scrollbar-decoration').addEventListener('click', addScrollbarDecoration); + document.getElementById('add-overview-ruler').addEventListener('click', addOverviewRuler); } function createTerminal(): void { @@ -552,10 +552,10 @@ function addDecoration() { }); } -function addScrollbarDecoration() { - const scrollbarDecorationCanvas = term.registerDecoration({marker: term.addMarker(1), scrollbarDecorationColor: 'red'}); - scrollbarDecorationCanvas.element!.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 5}px`; - term.registerDecoration({marker: term.addMarker(3), scrollbarDecorationColor: 'green'}); - term.registerDecoration({marker: term.addMarker(5), scrollbarDecorationColor: 'blue'}); +function addOverviewRuler() { + const canvas = term.registerDecoration({marker: term.addMarker(1), overviewRulerItemColor: 'red'}); + canvas.element!.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 5}px`; + term.registerDecoration({marker: term.addMarker(3), overviewRulerItemColor: 'green'}); + term.registerDecoration({marker: term.addMarker(5), overviewRulerItemColor: 'blue'}); } diff --git a/demo/index.html b/demo/index.html index dab52eec..d2b8d481 100644 --- a/demo/index.html +++ b/demo/index.html @@ -69,7 +69,7 @@ - + diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 2dddf825..2e44b1ec 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -22,11 +22,11 @@ export class DecorationService extends Disposable implements IDecorationService private _screenElement: HTMLElement | undefined; private _viewportElement: HTMLElement | undefined; private _bufferDecorationContainer: HTMLElement | undefined; - private _scrollbarDecorationCanvas: CanvasRenderingContext2D | null = null; - private _scrollbarDecorationNode: HTMLCanvasElement | undefined; + private _overviewRulerCtx: CanvasRenderingContext2D | null = null; + private _overviewRulerCanvas: HTMLCanvasElement | undefined; private readonly _bufferDecorations: BufferDecoration[] = []; - private _scrollbarDecorations: ScrollbarDecoration[] = []; + private _overviewRulerDecorations: ScrollbarDecoration[] = []; constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) { super(); } @@ -39,7 +39,7 @@ export class DecorationService extends Disposable implements IDecorationService this.register(this._renderService.onDimensionsChange(() => this._refresh(true))); this.register(addDisposableDomListener(window, 'resize', () => this._refreshScollbarDecorations())); this.register(this._bufferService.buffers.onBufferActivate(() => { - this._scrollbarDecorationNode!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; + this._overviewRulerCanvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; })); } @@ -47,8 +47,8 @@ export class DecorationService extends Disposable implements IDecorationService if (decorationOptions.marker.isDisposed) { return undefined; } - if (decorationOptions.scrollbarDecorationColor) { - return this._registerScrollbarDecoration(decorationOptions.marker, decorationOptions.scrollbarDecorationColor); + if (decorationOptions.overviewRulerItemColor) { + return this._registerScrollbarDecoration(decorationOptions.marker, decorationOptions.overviewRulerItemColor); } return this._registerBufferDecoration(decorationOptions); } @@ -57,14 +57,14 @@ export class DecorationService extends Disposable implements IDecorationService for (const bufferDecoration of this._bufferDecorations) { bufferDecoration.dispose(); } - for (const scrollbarDecoration of this._scrollbarDecorations) { + for (const scrollbarDecoration of this._overviewRulerDecorations) { scrollbarDecoration.dispose(); } if (this._screenElement && this._bufferDecorationContainer && this._screenElement.contains(this._bufferDecorationContainer)) { this._screenElement.removeChild(this._bufferDecorationContainer); } - this._scrollbarDecorations = []; - this._scrollbarDecorationNode?.remove(); + this._overviewRulerDecorations = []; + this._overviewRulerCanvas?.remove(); } private _refresh(shouldRecreate?: boolean): void { @@ -99,19 +99,19 @@ export class DecorationService extends Disposable implements IDecorationService if (!this._viewportElement?.parentElement) { return; } - if (!this._scrollbarDecorationNode) { + if (!this._overviewRulerCanvas) { // TODO: make this opt in, must be done before the scroll area in order to show up - this._scrollbarDecorationNode = document.createElement('canvas'); - this._scrollbarDecorationNode.classList.add('xterm-decoration-scrollbar'); - this._viewportElement.parentElement.insertBefore(this._scrollbarDecorationNode, this._viewportElement); + this._overviewRulerCanvas = document.createElement('canvas'); + this._overviewRulerCanvas.classList.add('xterm-decoration-scrollbar'); + this._viewportElement.parentElement.insertBefore(this._overviewRulerCanvas, this._viewportElement); } - if (!this._scrollbarDecorationCanvas) { - this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); + if (!this._overviewRulerCtx) { + this._overviewRulerCtx = this._overviewRulerCanvas.getContext('2d'); this._refreshScollbarDecorations(); } - const decoration = this._instantiationService.createInstance(ScrollbarDecoration, { marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode, this._scrollbarDecorationCanvas!); - decoration.onDispose(() => this._scrollbarDecorations.splice(this._scrollbarDecorations.indexOf(decoration), 1)); - this._scrollbarDecorations.push(decoration); + const decoration = this._instantiationService.createInstance(ScrollbarDecoration, { marker, overviewRulerItemColor: color }, this._overviewRulerCanvas, this._overviewRulerCtx!); + decoration.onDispose(() => this._overviewRulerDecorations.splice(this._overviewRulerDecorations.indexOf(decoration), 1)); + this._overviewRulerDecorations.push(decoration); return decoration; } @@ -125,15 +125,15 @@ export class DecorationService extends Disposable implements IDecorationService } private _refreshScollbarDecorations(): void { - if (!this._scrollbarDecorationCanvas || !this._viewportElement || !this._scrollbarDecorationNode) { + if (!this._overviewRulerCtx || !this._viewportElement || !this._overviewRulerCanvas) { return; } - this._scrollbarDecorationNode.style.width = `${ScrollbarConstants.WIDTH}px`; - this._scrollbarDecorationNode.style.height = `${this._screenElement!.clientHeight}px`; - this._scrollbarDecorationNode.width = Math.floor(ScrollbarConstants.WIDTH * window.devicePixelRatio); - this._scrollbarDecorationNode.height = Math.floor(this._screenElement!.clientHeight * window.devicePixelRatio); - this._scrollbarDecorationCanvas.clearRect(0, 0, this._scrollbarDecorationCanvas.canvas.width, this._scrollbarDecorationCanvas.canvas.height); - for (const decoration of this._scrollbarDecorations) { + this._overviewRulerCanvas.style.width = `${ScrollbarConstants.WIDTH}px`; + this._overviewRulerCanvas.style.height = `${this._screenElement!.clientHeight}px`; + this._overviewRulerCanvas.width = Math.floor(ScrollbarConstants.WIDTH * window.devicePixelRatio); + this._overviewRulerCanvas.height = Math.floor(this._screenElement!.clientHeight * window.devicePixelRatio); + this._overviewRulerCtx.clearRect(0, 0, this._overviewRulerCtx.canvas.width, this._overviewRulerCtx.canvas.height); + for (const decoration of this._overviewRulerDecorations) { decoration.render(); } } @@ -164,7 +164,7 @@ export class ScrollbarDecoration extends Disposable implements IDecoration { ) { super(); this._marker = options.marker; - this._color = options.scrollbarDecorationColor; + this._color = options.overviewRulerItemColor; this._marker.onDispose(() => this.dispose()); this.render(); } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 510c099f..cb952e70 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -475,7 +475,7 @@ declare module 'xterm' { * When provided, renders the decoration in the scrollbar * with the given color */ - scrollbarDecorationColor?: string; + overviewRulerItemColor?: string; } /** From 692df151e5813270e6ee86f21eb822bddcda96bc Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 11 Mar 2022 22:34:17 -0500 Subject: [PATCH 19/65] part 1 of massive refactor --- src/browser/services/DecorationService.ts | 211 +++++++++++++--------- 1 file changed, 127 insertions(+), 84 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 2e44b1ec..2daa9e92 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -14,6 +14,110 @@ const enum ScrollbarConstants { WIDTH = 7 } +interface IDecorationRenderer { + refreshDecorations(shouldRecreate?: boolean): void; + registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; +} + +class BufferDecorationRenderer extends Disposable implements IDecorationRenderer { + private _decorationContainer: HTMLElement | undefined; + private readonly _decorations: BufferDecoration[] = []; + private _renderService: IRenderService | undefined; + + constructor( + @IBufferService private readonly _bufferService: IBufferService, + private readonly _screenElement: HTMLElement) { + super(); + this.register(this._bufferService.buffers.onBufferActivate(() => { + // this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; + })); + } + public refreshDecorations(shouldRecreate?: boolean): void { + if (!this._renderService) { + return; + } + for (const decoration of this._decorations) { + decoration.render(this._renderService, shouldRecreate); + } + } + public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { + if (this._screenElement && !this._decorationContainer) { + this._decorationContainer = document.createElement('div'); + this._decorationContainer.classList.add('xterm-decoration-container'); + this._screenElement.appendChild(this._decorationContainer); + } + const decoration = new BufferDecoration(this._bufferService, decorationOptions, this._decorationContainer); + this._decorations.push(decoration); + decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1)); + this.refreshDecorations(); + return decoration; + } + 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(); + } + public attachToDom(renderService: IRenderService): void { + this._renderService = renderService; + } +} + +class OverviewRulerRenderer extends Disposable implements IDecorationRenderer { + private _canvas: HTMLCanvasElement | undefined; + private _ctx: CanvasRenderingContext2D | null = null; + private _decorations: ScrollbarDecoration[] = []; + + constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService, private readonly _viewportElement: HTMLElement, private readonly _screenElement: HTMLElement) { + super(); + this.register(this._bufferService.buffers.onBufferActivate(() => { + this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; + })); + } + public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { + if (!this._viewportElement.parentElement) { + return; + } + if (!this._canvas) { + this._canvas = document.createElement('canvas'); + this._canvas.classList.add('xterm-decoration-scrollbar'); + this._viewportElement.parentElement.insertBefore(this._canvas, this._viewportElement); + } + if (!this._ctx) { + this._ctx = this._canvas.getContext('2d'); + this.refreshDecorations(); + } + const decoration = this._instantiationService.createInstance(ScrollbarDecoration, { marker: decorationOptions.marker, overviewRulerItemColor: decorationOptions.overviewRulerItemColor }, this._canvas, this._ctx!); + decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1)); + this._decorations.push(decoration); + return decoration; + } + public refreshDecorations(): void { + if (!this._canvas || !this._ctx || !this._screenElement) { + return; + } + this._canvas.style.width = `${ScrollbarConstants.WIDTH}px`; + this._canvas.style.height = `${this._screenElement.clientHeight}px`; + this._canvas.width = Math.floor(ScrollbarConstants.WIDTH * window.devicePixelRatio); + this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + for (const decoration of this._decorations) { + decoration.render(); + } + } + public override dispose(): void { + for (const decoration of this._decorations) { + decoration.dispose(); + } + this._decorations = []; + this._canvas?.remove(); + super.dispose(); + } +} + export class DecorationService extends Disposable implements IDecorationService { private _renderService: IRenderService | undefined; @@ -21,55 +125,50 @@ export class DecorationService extends Disposable implements IDecorationService private _screenElement: HTMLElement | undefined; private _viewportElement: HTMLElement | undefined; - private _bufferDecorationContainer: HTMLElement | undefined; - private _overviewRulerCtx: CanvasRenderingContext2D | null = null; - private _overviewRulerCanvas: HTMLCanvasElement | undefined; - private readonly _bufferDecorations: BufferDecoration[] = []; - private _overviewRulerDecorations: ScrollbarDecoration[] = []; + private _overviewRulerRenderer: OverviewRulerRenderer | undefined; + private _bufferDecorationRenderer: BufferDecorationRenderer | undefined; - constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) { super(); } + constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) { + super(); + } public attachToDom(renderService: IRenderService, screenElement: HTMLElement, viewportElement: HTMLElement): void { this._renderService = renderService; this._screenElement = screenElement; this._viewportElement = viewportElement; - - this.register(this._renderService.onRenderedBufferChange(() => this._refresh())); + this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh())); this.register(this._renderService.onDimensionsChange(() => this._refresh(true))); - this.register(addDisposableDomListener(window, 'resize', () => this._refreshScollbarDecorations())); - this.register(this._bufferService.buffers.onBufferActivate(() => { - this._overviewRulerCanvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; - })); + this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh())); + if (!this._bufferDecorationRenderer && this._viewportElement && this._screenElement) { + // TODO: allow registering before the viewport element exists + this._bufferDecorationRenderer = new BufferDecorationRenderer(this._bufferService, this._screenElement); + this._bufferDecorationRenderer.attachToDom(renderService); + } } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { if (decorationOptions.marker.isDisposed) { return undefined; } - if (decorationOptions.overviewRulerItemColor) { - return this._registerScrollbarDecoration(decorationOptions.marker, decorationOptions.overviewRulerItemColor); + + if (decorationOptions.overviewRulerItemColor && this._viewportElement && this._screenElement) { + if (!this._overviewRulerRenderer) { + this._overviewRulerRenderer = new OverviewRulerRenderer(this._instantiationService, this._bufferService, this._viewportElement, this._screenElement); + } + return this._overviewRulerRenderer.registerDecoration(decorationOptions); } - return this._registerBufferDecoration(decorationOptions); + return this._bufferDecorationRenderer?.registerDecoration(decorationOptions); } public dispose(): void { - for (const bufferDecoration of this._bufferDecorations) { - bufferDecoration.dispose(); - } - for (const scrollbarDecoration of this._overviewRulerDecorations) { - scrollbarDecoration.dispose(); - } - if (this._screenElement && this._bufferDecorationContainer && this._screenElement.contains(this._bufferDecorationContainer)) { - this._screenElement.removeChild(this._bufferDecorationContainer); - } - this._overviewRulerDecorations = []; - this._overviewRulerCanvas?.remove(); + this._overviewRulerRenderer?.dispose(); + this._bufferDecorationRenderer?.dispose(); } private _refresh(shouldRecreate?: boolean): void { - this._refreshBufferDecorations(shouldRecreate); - this._refreshScollbarDecorations(); + this._bufferDecorationRenderer?.refreshDecorations(shouldRecreate); + this._overviewRulerRenderer?.refreshDecorations(); } private _queueRefresh(): void { @@ -81,62 +180,6 @@ export class DecorationService extends Disposable implements IDecorationService this._animationFrame = undefined; }); } - - private _registerBufferDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - if (this._screenElement && !this._bufferDecorationContainer) { - this._bufferDecorationContainer = document.createElement('div'); - this._bufferDecorationContainer.classList.add('xterm-decoration-container'); - this._screenElement.appendChild(this._bufferDecorationContainer); - } - const decoration = new BufferDecoration(this._bufferService, decorationOptions, this._bufferDecorationContainer); - this._bufferDecorations.push(decoration); - decoration.onDispose(() => this._bufferDecorations.splice(this._bufferDecorations.indexOf(decoration), 1)); - this._queueRefresh(); - return decoration; - } - - private _registerScrollbarDecoration(marker: IMarker, color: string): IDecoration | undefined { - if (!this._viewportElement?.parentElement) { - return; - } - if (!this._overviewRulerCanvas) { - // TODO: make this opt in, must be done before the scroll area in order to show up - this._overviewRulerCanvas = document.createElement('canvas'); - this._overviewRulerCanvas.classList.add('xterm-decoration-scrollbar'); - this._viewportElement.parentElement.insertBefore(this._overviewRulerCanvas, this._viewportElement); - } - if (!this._overviewRulerCtx) { - this._overviewRulerCtx = this._overviewRulerCanvas.getContext('2d'); - this._refreshScollbarDecorations(); - } - const decoration = this._instantiationService.createInstance(ScrollbarDecoration, { marker, overviewRulerItemColor: color }, this._overviewRulerCanvas, this._overviewRulerCtx!); - decoration.onDispose(() => this._overviewRulerDecorations.splice(this._overviewRulerDecorations.indexOf(decoration), 1)); - this._overviewRulerDecorations.push(decoration); - return decoration; - } - - private _refreshBufferDecorations(shouldRecreate?: boolean): void { - if (!this._renderService) { - return; - } - for (const decoration of this._bufferDecorations) { - decoration.render(this._renderService, shouldRecreate); - } - } - - private _refreshScollbarDecorations(): void { - if (!this._overviewRulerCtx || !this._viewportElement || !this._overviewRulerCanvas) { - return; - } - this._overviewRulerCanvas.style.width = `${ScrollbarConstants.WIDTH}px`; - this._overviewRulerCanvas.style.height = `${this._screenElement!.clientHeight}px`; - this._overviewRulerCanvas.width = Math.floor(ScrollbarConstants.WIDTH * window.devicePixelRatio); - this._overviewRulerCanvas.height = Math.floor(this._screenElement!.clientHeight * window.devicePixelRatio); - this._overviewRulerCtx.clearRect(0, 0, this._overviewRulerCtx.canvas.width, this._overviewRulerCtx.canvas.height); - for (const decoration of this._overviewRulerDecorations) { - decoration.render(); - } - } } export class ScrollbarDecoration extends Disposable implements IDecoration { From 9e598d9fba43562f42ff839183ee54e2e873b895 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 14 Mar 2022 09:30:30 -0400 Subject: [PATCH 20/65] allow setting width --- src/browser/services/DecorationService.ts | 30 ++++++++++++++++++----- typings/xterm.d.ts | 3 ++- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 2daa9e92..f3f5f1f2 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -70,8 +70,17 @@ class OverviewRulerRenderer extends Disposable implements IDecorationRenderer { private _canvas: HTMLCanvasElement | undefined; private _ctx: CanvasRenderingContext2D | null = null; private _decorations: ScrollbarDecoration[] = []; + private _width: number | undefined; + private _anchor: 'right' | 'left' | undefined; + private _x: number | undefined; - constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService, private readonly _viewportElement: HTMLElement, private readonly _screenElement: HTMLElement) { + constructor( + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IBufferService private readonly _bufferService: IBufferService, + @IRenderService private readonly _renderService: IRenderService, + private readonly _viewportElement: HTMLElement, + private readonly _screenElement: HTMLElement + ) { super(); this.register(this._bufferService.buffers.onBufferActivate(() => { this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; @@ -86,6 +95,10 @@ class OverviewRulerRenderer extends Disposable implements IDecorationRenderer { this._canvas.classList.add('xterm-decoration-scrollbar'); this._viewportElement.parentElement.insertBefore(this._canvas, this._viewportElement); } + this._width = decorationOptions.width; + this._anchor = decorationOptions.anchor; + this._x = decorationOptions.x; + if (!this._ctx) { this._ctx = this._canvas.getContext('2d'); this.refreshDecorations(); @@ -96,13 +109,18 @@ class OverviewRulerRenderer extends Disposable implements IDecorationRenderer { return decoration; } public refreshDecorations(): void { - if (!this._canvas || !this._ctx || !this._screenElement) { + if (!this._canvas || !this._ctx || !this._screenElement || !this._renderService) { return; } - this._canvas.style.width = `${ScrollbarConstants.WIDTH}px`; + this._canvas.style.width = `${this._width || ScrollbarConstants.WIDTH}px`; this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.width = Math.floor(ScrollbarConstants.WIDTH * window.devicePixelRatio); + this._canvas.width = Math.floor((this._width || ScrollbarConstants.WIDTH)* window.devicePixelRatio); this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); + if (this._anchor === 'right') { + this._canvas.style.right = this._x ? `${this._x * this._renderService.dimensions.actualCellWidth}px` : ''; + } else { + this._canvas.style.left = this._x ? `${this._x * this._renderService.dimensions.actualCellWidth}px` : ''; + } this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); for (const decoration of this._decorations) { decoration.render(); @@ -148,13 +166,13 @@ export class DecorationService extends Disposable implements IDecorationService } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - if (decorationOptions.marker.isDisposed) { + if (decorationOptions.marker.isDisposed || !this._renderService) { return undefined; } if (decorationOptions.overviewRulerItemColor && this._viewportElement && this._screenElement) { if (!this._overviewRulerRenderer) { - this._overviewRulerRenderer = new OverviewRulerRenderer(this._instantiationService, this._bufferService, this._viewportElement, this._screenElement); + this._overviewRulerRenderer = new OverviewRulerRenderer(this._instantiationService, this._bufferService, this._renderService, this._viewportElement, this._screenElement); } return this._overviewRulerRenderer.registerDecoration(decorationOptions); } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index cb952e70..00f4072f 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -461,7 +461,8 @@ declare module 'xterm' { /** * The width of the decoration in cells, which defaults to - * cell width + * cell width or the width in pixels, when an overlayRulerItemColor + * is provided. */ width?: number; From 3b279e4a4dd9cf49bab49d24f97a354dd30217e6 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 14 Mar 2022 09:52:12 -0400 Subject: [PATCH 21/65] round to the nearest pixel --- src/browser/services/DecorationService.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index f3f5f1f2..181588cd 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -28,9 +28,6 @@ class BufferDecorationRenderer extends Disposable implements IDecorationRenderer @IBufferService private readonly _bufferService: IBufferService, private readonly _screenElement: HTMLElement) { super(); - this.register(this._bufferService.buffers.onBufferActivate(() => { - // this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; - })); } public refreshDecorations(shouldRecreate?: boolean): void { if (!this._renderService) { @@ -207,9 +204,9 @@ export class ScrollbarDecoration extends Disposable implements IDecoration { public isDisposed: boolean = false; - public get element(): HTMLCanvasElement { return this._element!; } + public get element(): HTMLCanvasElement | undefined { return this._element; } public get marker(): IMarker { return this._marker; } - public get color(): string { return this._color!; } + public get color(): string | undefined { return this._color; } private _onDispose = new EventEmitter(); public get onDispose(): IEvent { return this._onDispose.event; } @@ -230,6 +227,9 @@ export class ScrollbarDecoration extends Disposable implements IDecoration { this.render(); } public render(): void { + if (!this.color) { + throw new Error('No color was provided for the overview ruler decoraiton'); + } if (!this._element) { this._element = this._canvas; } @@ -237,21 +237,21 @@ export class ScrollbarDecoration extends Disposable implements IDecoration { this._ctx.strokeStyle = this.color; this._ctx.strokeRect( 0, - this.element.height * (this.marker.line / this._bufferService.buffers.active.lines.length), - this.element.width, + Math.round(this._element.height * (this.marker.line / this._bufferService.buffers.active.lines.length)), + this._element.width, window.devicePixelRatio ); - this._onRender.fire(this.element); + this._onRender.fire(this._element); } public override dispose(): void { - if (this._isDisposed) { + if (this._isDisposed || !this._element) { return; } this._ctx.clearRect( 0, - this.element.height * (this.marker.line / this._bufferService.buffers.active.lines.length), - this.element.width, + Math.round(this._element.height * (this.marker.line / this._bufferService.buffers.active.lines.length)), + this._element.width, window.devicePixelRatio ); this.isDisposed = true; From ced06629d45129115fbb7e59de8dbd5a6eb16680 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 14 Mar 2022 15:11:20 -0400 Subject: [PATCH 22/65] large refactor, broken --- .../Decorations/BufferDecorationRenderer.ts | 162 ++++++++ .../Decorations/OverviewRulerRenderer.ts | 150 ++++++++ src/browser/Terminal.ts | 28 +- src/browser/Types.d.ts | 6 + src/browser/services/DecorationService.ts | 361 ------------------ src/browser/services/Services.ts | 5 +- src/browser/tsconfig.json | 2 +- src/common/services/DecorationService.ts | 81 ++++ src/common/services/Services.ts | 9 +- 9 files changed, 431 insertions(+), 373 deletions(-) create mode 100644 src/browser/Decorations/BufferDecorationRenderer.ts create mode 100644 src/browser/Decorations/OverviewRulerRenderer.ts delete mode 100644 src/browser/services/DecorationService.ts create mode 100644 src/common/services/DecorationService.ts diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts new file mode 100644 index 00000000..56559ad9 --- /dev/null +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -0,0 +1,162 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { addDisposableDomListener } from 'browser/Lifecycle'; +import { IDecorationService, IRenderService } from 'browser/services/Services'; +import { IEvent, EventEmitter } from 'common/EventEmitter'; +import { Disposable } from 'common/Lifecycle'; +import { IBufferService } 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; +} + +export class BufferDecorationRenderer extends Disposable implements IDecorationRenderer { + 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) { + super(); + 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._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) { + return; + } + for (const decoration of this._decorations) { + decoration.render(this._decorationContainer, this._renderService, shouldRecreate); + } + } + + public renderDecoration(decorationOptions: IDecorationOptions): void { + const decoration = new BufferDecoration(this._bufferService, decorationOptions); + if (this._decorationContainer && decoration.element && !this._decorationContainer.contains(decoration.element)) { + this._decorationContainer.append(decoration.element); + } + (decoration as BufferDecoration).render(this._decorationContainer, this._renderService, true); + } + + 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, + 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) { + this._createElement(renderService, shouldRecreate); + } + this._refreshStyle(renderService); + if (this._element) { + this._onRender.fire(this._element); + } + } + + private _createElement(renderService: IRenderService, shouldRecreate?: boolean): void { + 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`; + + if (this.x && this.x > this._bufferService.cols) { + // exceeded the container width, so hide + this._element.style.display = 'none'; + } + if (this.anchor === 'right') { + this._element.style.right = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : ''; + } else { + this._element.style.left = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : ''; + } + } + + private _refreshStyle(renderService: IRenderService): void { + if (!this._element) { + return; + } + const line = this.marker.line - this._bufferService.buffers.active.ydisp; + if (line < 0 || line > this._bufferService.rows) { + // outside of viewport + this._element.style.display = 'none'; + } else { + this._element.style.top = `${line * renderService.dimensions.actualCellHeight}px`; + this._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 new file mode 100644 index 00000000..580a831a --- /dev/null +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -0,0 +1,150 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { addDisposableDomListener } from 'browser/Lifecycle'; +import { IDecorationRenderer } from 'browser/Decorations/BufferDecorationRenderer'; +import { IDecorationService, IRenderService } from 'browser/services/Services'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { Disposable } from 'common/Lifecycle'; +import { IBufferService, IInstantiationService } from 'common/services/Services'; +import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; +const enum ScrollbarConstants { + WIDTH = 7 +} + +export class OverviewRulerRenderer extends Disposable implements IDecorationRenderer { + private _canvas: HTMLCanvasElement; + private _ctx: CanvasRenderingContext2D | null; + private _decorations: ScrollbarDecoration[] = []; + private _width: number | undefined; + private _anchor: 'right' | 'left' | undefined; + private _x: number | undefined; + + constructor( + @IBufferService private readonly _bufferService: IBufferService, + @IRenderService private readonly _renderService: IRenderService, + @IDecorationService private readonly _decorationService: IDecorationService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + private readonly _viewportElement: HTMLElement, + private readonly _screenElement: HTMLElement + ) { + super(); + this._canvas = document.createElement('canvas'); + this._canvas.classList.add('xterm-decoration-scrollbar'); + this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement); + this._ctx = this._canvas.getContext('2d'); + this.refreshDecorations(); + this.register(this._bufferService.buffers.onBufferActivate(() => { + this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; + })); + 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.onDecorationRemoved(d => d.dispose())); + } + public renderDecoration(decorationOptions: IDecorationOptions): void { + if (!this._ctx || !decorationOptions.overviewRulerItemColor) { + return; + } + const decoration = this._instantiationService.createInstance(ScrollbarDecoration, { marker: decorationOptions.marker, overviewRulerItemColor: decorationOptions.overviewRulerItemColor }); + + this._ctx.lineWidth = 1; + this._ctx.strokeStyle = decorationOptions.overviewRulerItemColor; + this._ctx.strokeRect( + 0, + Math.round(this._canvas.height * (decoration.marker.line / this._bufferService.buffers.active.lines.length)), + this._canvas.width, + window.devicePixelRatio + ); + } + + public refreshDecorations(): void { + if (!this._ctx) { + return; + } + this._canvas.style.width = `${this._width || ScrollbarConstants.WIDTH}px`; + this._canvas.style.height = `${this._screenElement.clientHeight}px`; + this._canvas.width = Math.floor((this._width || ScrollbarConstants.WIDTH)* window.devicePixelRatio); + this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); + if (this._anchor === 'right') { + this._canvas.style.right = this._x ? `${this._x * this._renderService.dimensions.actualCellWidth}px` : ''; + } else { + this._canvas.style.left = this._x ? `${this._x * this._renderService.dimensions.actualCellWidth}px` : ''; + } + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + for (const decoration of this._decorations) { + decoration.render(this._ctx, this._canvas); + } + } + public override dispose(): void { + for (const decoration of this._decorations) { + decoration.dispose(); + } + this._decorations = []; + this._canvas?.remove(); + super.dispose(); + } +} +class ScrollbarDecoration extends Disposable implements IDecoration { + private readonly _marker: IMarker; + private _ctx: CanvasRenderingContext2D | undefined; + private _canvas: HTMLCanvasElement | undefined; + private _color: string | undefined; + + public isDisposed: boolean = false; + + public get element(): HTMLCanvasElement | undefined { return this._canvas; } + public get marker(): IMarker { return this._marker; } + public get color(): string | undefined { return this._color; } + + private _onDispose = new EventEmitter(); + public get onDispose(): IEvent { return this._onDispose.event; } + + private _onRender = new EventEmitter(); + public get onRender(): IEvent { return this._onRender.event; } + + constructor( + options: IDecorationOptions, + @IBufferService private readonly _bufferService: IBufferService + ) { + super(); + this._marker = options.marker; + this._color = options.overviewRulerItemColor; + this._marker.onDispose(() => this.dispose()); + } + + public render(ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement): void { + if (!this.color) { + throw new Error('No color was provided for the overview ruler decoraiton'); + } + this._ctx = ctx; + this._canvas = canvas; + ctx.lineWidth = 1; + ctx.strokeStyle = this.color; + ctx.strokeRect( + 0, + Math.round(canvas.height * (this.marker.line / this._bufferService.buffers.active.lines.length)), + canvas.width, + window.devicePixelRatio + ); + this._onRender.fire(canvas); + } + + public override dispose(): void { + if (this._isDisposed || !this._canvas || !this._ctx) { + return; + } + this._ctx.clearRect( + 0, + Math.round(this._canvas.height * (this.marker.line / this._bufferService.buffers.active.lines.length)), + this._canvas.width, + window.devicePixelRatio + ); + this.isDisposed = true; + this._onDispose.fire(); + super.dispose(); + } +} diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index ba0fc93d..39f0a68a 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -45,7 +45,7 @@ import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; -import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService, ICharacterJoinerService, IDecorationService } from 'browser/services/Services'; +import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService, ICharacterJoinerService } from 'browser/services/Services'; import { CharSizeService } from 'browser/services/CharSizeService'; import { IBuffer } from 'common/buffer/Types'; import { MouseService } from 'browser/services/MouseService'; @@ -55,7 +55,10 @@ import { CoreTerminal } from 'common/CoreTerminal'; import { color, rgba } from 'browser/Color'; import { CharacterJoinerService } from 'browser/services/CharacterJoinerService'; import { toRgbString } from 'common/input/XParseColor'; -import { DecorationService } from 'browser/services/DecorationService'; +import { BufferDecorationRenderer } from 'browser/Decorations/BufferDecorationRenderer'; +import { OverviewRulerRenderer } from 'browser/Decorations/OverviewRulerRenderer'; +// import { IDecorationService } from 'common/services/Services'; +import { DecorationService } from 'common/services/DecorationService'; // Let it work inside Node.js for automated testing purposes. const document: Document = (typeof window !== 'undefined') ? window.document : null as any; @@ -71,6 +74,9 @@ export class Terminal extends CoreTerminal implements ITerminal { private _helperContainer: HTMLElement | undefined; private _compositionView: HTMLElement | undefined; + private _overviewRulerRenderer: OverviewRulerRenderer | undefined; + private _bufferDecorationRenderer: BufferDecorationRenderer | undefined; + // private _visualBellTimer: number; public browser: IBrowser = Browser as any; @@ -81,6 +87,7 @@ export class Terminal extends CoreTerminal implements ITerminal { 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; @@ -109,7 +116,6 @@ export class Terminal extends CoreTerminal implements ITerminal { public linkifier: ILinkifier; public linkifier2: ILinkifier2; public viewport: IViewport | undefined; - public decorationService: IDecorationService; private _compositionHelper: ICompositionHelper | undefined; private _mouseZoneManager: IMouseZoneManager | undefined; private _accessibilityManager: AccessibilityManager | undefined; @@ -159,7 +165,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.linkifier = this._instantiationService.createInstance(Linkifier); this.linkifier2 = this.register(this._instantiationService.createInstance(Linkifier2)); - this.decorationService = this.register(this._instantiationService.createInstance(DecorationService)); + this._decorationService = this._instantiationService.createInstance(DecorationService); // Setup InputHandler listeners this.register(this._inputHandler.onRequestBell(() => this.bell())); @@ -577,8 +583,16 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this.onScroll(() => this._mouseZoneManager!.clearAll())); this.linkifier.attachToDom(this.element, this._mouseZoneManager); this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService); - - this.decorationService.attachToDom(this._renderService, this.screenElement, this._viewportElement); + if (this._decorationService) { + this._bufferDecorationRenderer = new BufferDecorationRenderer(this._bufferService, this._renderService, this._decorationService, this.screenElement); + } + // if (this.options.overviewRulerWidth && this._decorationService) { + // this._overviewRulerRenderer = new OverviewRulerRenderer(this._bufferService, this._renderService, this._decorationService, this._viewportElement, this.screenElement); + // } + // this.optionsService.onOptionChange(() => { + // if (!this._overviewRulerRenderer && this.options.overviewRulerWidth && this._renderService && this._viewportElement && this.screenElement && this._decorationService) { + // this._overviewRulerRenderer = new OverviewRulerRenderer(this._bufferService, this._renderService, this._decorationService, this._viewportElement, this.screenElement); + // }}); // This event listener must be registered aftre MouseZoneManager is created this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e))); @@ -1004,7 +1018,7 @@ export class Terminal extends CoreTerminal implements ITerminal { } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - return this.decorationService!.registerDecoration(decorationOptions); + return this._decorationService!.registerDecoration(decorationOptions); } /** diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 35b52d62..b8e1626e 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -9,6 +9,7 @@ import { ICoreTerminal, CharData, ITerminalOptions } from 'common/Types'; import { IMouseService, IRenderService } from './services/Services'; import { IBuffer } from 'common/buffer/Types'; import { IFunctionIdentifier, IParams } from 'common/parser/Types'; +import { createDecorator } from 'common/services/ServiceRegistry'; export interface ITerminal extends IPublicTerminal, ICoreTerminal { element: HTMLElement | undefined; @@ -205,6 +206,11 @@ 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/DecorationService.ts b/src/browser/services/DecorationService.ts deleted file mode 100644 index 181588cd..00000000 --- a/src/browser/services/DecorationService.ts +++ /dev/null @@ -1,361 +0,0 @@ -/** - * Copyright (c) 2022 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { addDisposableDomListener } from 'browser/Lifecycle'; -import { IDecorationService, IRenderService } from 'browser/services/Services'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { Disposable } from 'common/Lifecycle'; -import { IBufferService, IInstantiationService } from 'common/services/Services'; -import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; - -const enum ScrollbarConstants { - WIDTH = 7 -} - -interface IDecorationRenderer { - refreshDecorations(shouldRecreate?: boolean): void; - registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; -} - -class BufferDecorationRenderer extends Disposable implements IDecorationRenderer { - private _decorationContainer: HTMLElement | undefined; - private readonly _decorations: BufferDecoration[] = []; - private _renderService: IRenderService | undefined; - - constructor( - @IBufferService private readonly _bufferService: IBufferService, - private readonly _screenElement: HTMLElement) { - super(); - } - public refreshDecorations(shouldRecreate?: boolean): void { - if (!this._renderService) { - return; - } - for (const decoration of this._decorations) { - decoration.render(this._renderService, shouldRecreate); - } - } - public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - if (this._screenElement && !this._decorationContainer) { - this._decorationContainer = document.createElement('div'); - this._decorationContainer.classList.add('xterm-decoration-container'); - this._screenElement.appendChild(this._decorationContainer); - } - const decoration = new BufferDecoration(this._bufferService, decorationOptions, this._decorationContainer); - this._decorations.push(decoration); - decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1)); - this.refreshDecorations(); - return decoration; - } - 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(); - } - public attachToDom(renderService: IRenderService): void { - this._renderService = renderService; - } -} - -class OverviewRulerRenderer extends Disposable implements IDecorationRenderer { - private _canvas: HTMLCanvasElement | undefined; - private _ctx: CanvasRenderingContext2D | null = null; - private _decorations: ScrollbarDecoration[] = []; - private _width: number | undefined; - private _anchor: 'right' | 'left' | undefined; - private _x: number | undefined; - - constructor( - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IBufferService private readonly _bufferService: IBufferService, - @IRenderService private readonly _renderService: IRenderService, - private readonly _viewportElement: HTMLElement, - private readonly _screenElement: HTMLElement - ) { - super(); - this.register(this._bufferService.buffers.onBufferActivate(() => { - this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; - })); - } - public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - if (!this._viewportElement.parentElement) { - return; - } - if (!this._canvas) { - this._canvas = document.createElement('canvas'); - this._canvas.classList.add('xterm-decoration-scrollbar'); - this._viewportElement.parentElement.insertBefore(this._canvas, this._viewportElement); - } - this._width = decorationOptions.width; - this._anchor = decorationOptions.anchor; - this._x = decorationOptions.x; - - if (!this._ctx) { - this._ctx = this._canvas.getContext('2d'); - this.refreshDecorations(); - } - const decoration = this._instantiationService.createInstance(ScrollbarDecoration, { marker: decorationOptions.marker, overviewRulerItemColor: decorationOptions.overviewRulerItemColor }, this._canvas, this._ctx!); - decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1)); - this._decorations.push(decoration); - return decoration; - } - public refreshDecorations(): void { - if (!this._canvas || !this._ctx || !this._screenElement || !this._renderService) { - return; - } - this._canvas.style.width = `${this._width || ScrollbarConstants.WIDTH}px`; - this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.width = Math.floor((this._width || ScrollbarConstants.WIDTH)* window.devicePixelRatio); - this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); - if (this._anchor === 'right') { - this._canvas.style.right = this._x ? `${this._x * this._renderService.dimensions.actualCellWidth}px` : ''; - } else { - this._canvas.style.left = this._x ? `${this._x * this._renderService.dimensions.actualCellWidth}px` : ''; - } - this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); - for (const decoration of this._decorations) { - decoration.render(); - } - } - public override dispose(): void { - for (const decoration of this._decorations) { - decoration.dispose(); - } - this._decorations = []; - this._canvas?.remove(); - super.dispose(); - } -} - -export class DecorationService extends Disposable implements IDecorationService { - - private _renderService: IRenderService | undefined; - private _animationFrame: number | undefined; - - private _screenElement: HTMLElement | undefined; - private _viewportElement: HTMLElement | undefined; - - private _overviewRulerRenderer: OverviewRulerRenderer | undefined; - private _bufferDecorationRenderer: BufferDecorationRenderer | undefined; - - constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) { - super(); - } - - public attachToDom(renderService: IRenderService, screenElement: HTMLElement, viewportElement: HTMLElement): void { - this._renderService = renderService; - this._screenElement = screenElement; - this._viewportElement = viewportElement; - this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh())); - this.register(this._renderService.onDimensionsChange(() => this._refresh(true))); - this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh())); - if (!this._bufferDecorationRenderer && this._viewportElement && this._screenElement) { - // TODO: allow registering before the viewport element exists - this._bufferDecorationRenderer = new BufferDecorationRenderer(this._bufferService, this._screenElement); - this._bufferDecorationRenderer.attachToDom(renderService); - } - } - - public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - if (decorationOptions.marker.isDisposed || !this._renderService) { - return undefined; - } - - if (decorationOptions.overviewRulerItemColor && this._viewportElement && this._screenElement) { - if (!this._overviewRulerRenderer) { - this._overviewRulerRenderer = new OverviewRulerRenderer(this._instantiationService, this._bufferService, this._renderService, this._viewportElement, this._screenElement); - } - return this._overviewRulerRenderer.registerDecoration(decorationOptions); - } - return this._bufferDecorationRenderer?.registerDecoration(decorationOptions); - } - - public dispose(): void { - this._overviewRulerRenderer?.dispose(); - this._bufferDecorationRenderer?.dispose(); - } - - private _refresh(shouldRecreate?: boolean): void { - this._bufferDecorationRenderer?.refreshDecorations(shouldRecreate); - this._overviewRulerRenderer?.refreshDecorations(); - } - - private _queueRefresh(): void { - if (this._animationFrame !== undefined) { - return; - } - this._animationFrame = window.requestAnimationFrame(() => { - this._refresh(); - this._animationFrame = undefined; - }); - } -} - -export class ScrollbarDecoration extends Disposable implements IDecoration { - private readonly _marker: IMarker; - private _element: HTMLCanvasElement | undefined; - private _color: string | undefined; - - public isDisposed: boolean = false; - - public get element(): HTMLCanvasElement | undefined { return this._element; } - public get marker(): IMarker { return this._marker; } - public get color(): string | undefined { return this._color; } - - private _onDispose = new EventEmitter(); - public get onDispose(): IEvent { return this._onDispose.event; } - - private _onRender = new EventEmitter(); - public get onRender(): IEvent { return this._onRender.event; } - - constructor( - options: IDecorationOptions, - private readonly _canvas: HTMLCanvasElement, - private readonly _ctx: CanvasRenderingContext2D, - @IBufferService private readonly _bufferService: IBufferService - ) { - super(); - this._marker = options.marker; - this._color = options.overviewRulerItemColor; - this._marker.onDispose(() => this.dispose()); - this.render(); - } - public render(): void { - if (!this.color) { - throw new Error('No color was provided for the overview ruler decoraiton'); - } - if (!this._element) { - this._element = this._canvas; - } - this._ctx.lineWidth = 1; - this._ctx.strokeStyle = this.color; - this._ctx.strokeRect( - 0, - Math.round(this._element.height * (this.marker.line / this._bufferService.buffers.active.lines.length)), - this._element.width, - window.devicePixelRatio - ); - this._onRender.fire(this._element); - } - - public override dispose(): void { - if (this._isDisposed || !this._element) { - return; - } - this._ctx.clearRect( - 0, - Math.round(this._element.height * (this.marker.line / this._bufferService.buffers.active.lines.length)), - this._element.width, - window.devicePixelRatio - ); - this.isDisposed = true; - this._onDispose.fire(); - super.dispose(); - } -} - -export class BufferDecoration extends Disposable implements IDecoration { - private readonly _marker: IMarker; - private _element: HTMLElement | undefined; - - 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; } - - private _altBufferIsActive: boolean = false; - - public x: number; - public anchor: 'left' | 'right'; - public width: number; - public height: number; - - constructor( - private readonly _bufferService: IBufferService, - options: IDecorationOptions, - private readonly _container?: HTMLElement - ) { - 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; - this.register(this._bufferService.buffers.onBufferActivate(() => { - this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt; - })); - } - - public render(renderService: IRenderService, shouldRecreate?: boolean): void { - if (!this._element || shouldRecreate) { - this._createElement(renderService, shouldRecreate); - } - if (this._container && this._element && !this._container.contains(this._element)) { - this._container.append(this._element); - } - this._refreshStyle(renderService); - if (this._element) { - this._onRender.fire(this._element); - } - } - - private _createElement(renderService: IRenderService, shouldRecreate?: boolean): void { - 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`; - - if (this.x && this.x > this._bufferService.cols) { - // exceeded the container width, so hide - this._element.style.display = 'none'; - } - if (this.anchor === 'right') { - this._element.style.right = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : ''; - } else { - this._element.style.left = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : ''; - } - } - - private _refreshStyle(renderService: IRenderService): void { - if (!this._element) { - return; - } - const line = this.marker.line - this._bufferService.buffers.active.ydisp; - if (line < 0 || line > this._bufferService.rows) { - // outside of viewport - this._element.style.display = 'none'; - } else { - this._element.style.top = `${line * renderService.dimensions.actualCellHeight}px`; - this._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/services/Services.ts b/src/browser/services/Services.ts index 2d1f3d03..8587a5c9 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -115,10 +115,9 @@ export interface ICharacterJoinerService { deregister(joinerId: number): boolean; getJoinedCharacters(row: number): [number, number][]; } - - export const IDecorationService = createDecorator('DecorationService'); export interface IDecorationService extends IDisposable { - attachToDom(renderService: IRenderService, screenElement: HTMLElement, viewportElement: HTMLElement): void; + readonly onDecorationRegistered: IEvent; + readonly onDecorationRemoved: IEvent; registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; } diff --git a/src/browser/tsconfig.json b/src/browser/tsconfig.json index 212aeab8..fc997477 100644 --- a/src/browser/tsconfig.json +++ b/src/browser/tsconfig.json @@ -18,7 +18,7 @@ "include": [ "./**/*", "../../typings/xterm.d.ts" - ], +], "references": [ { "path": "../common" } ] diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts new file mode 100644 index 00000000..206d50e8 --- /dev/null +++ b/src/common/services/DecorationService.ts @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2022 The xterm.js authors. All rights reserved. + * @license MIT + */ + + +import { EventEmitter } from 'common/EventEmitter'; +import { Disposable } from 'common/Lifecycle'; +import { IBufferService, IInstantiationService, IDecorationService } from 'common/services/Services'; +import { IDecorationOptions, IDecoration, IMarker, IDisposable, 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[] = []; + + constructor() { + super(); + } + + public registerDecoration(options: IDecorationOptions): IDecoration | undefined { + if (options.marker.isDisposed) { + return undefined; + } + const decoration = new Decoration(options); + if (decoration) { + decoration.onDispose(() => { + if (decoration) { + this._decorations.splice(this._decorations.indexOf(decoration), 1); + } + }); + this._decorations.push(decoration); + this._onDecorationRegistered.fire(options); + decoration.onRender(d => { + decoration.setElement(d); + }); + } + return decoration; + } + + public dispose(): void { + for (const decoration of this._decorations) { + this._onDecorationRemoved.fire(decoration); + decoration.dispose(); + } + this._decorations = []; + } + + private _queueRefresh(): void { + if (this._animationFrame !== undefined) { + return; + } + this._animationFrame = window.requestAnimationFrame(() => { + // this._refresh(); + this._animationFrame = undefined; + }); + } +} + +class Decoration implements IDecoration { + public marker: IMarker; + private _onRender = new EventEmitter(); + public get onRender(): IEvent { return this._onRender.event; } + private _onDispose = new EventEmitter(); + public get onDispose(): IEvent { return 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!; + this.element = undefined; + } + public setElement(element: HTMLElement): void { + this.element = element; + } +} diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 90dca988..8bd272f0 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -5,8 +5,9 @@ import { IEvent } from 'common/EventEmitter'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; -import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource } from 'common/Types'; +import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource, IDisposable } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; +import { IDecorationOptions, IDecoration } from 'xterm'; export const IBufferService = createDecorator('BufferService'); export interface IBufferService { @@ -245,6 +246,7 @@ export interface ITerminalOptions { windowsMode: boolean; windowOptions: IWindowOptions; wordSeparator: string; + overviewRulerWidth?: number; [key: string]: any; cancelEvents: boolean; @@ -298,3 +300,8 @@ export interface IUnicodeVersionProvider { readonly version: string; wcwidth(ucs: number): 0 | 1 | 2; } +export interface IDecorationService extends IDisposable { + readonly onDecorationRegistered: IEvent; + readonly onDecorationRemoved: IEvent; + registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; +} From 7dc3332b05d91d5a00857bfb361aab2f309578be Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 14 Mar 2022 15:50:40 -0400 Subject: [PATCH 23/65] commit before reverting --- demo/client.ts | 7 ++++--- .../Decorations/BufferDecorationRenderer.ts | 10 +++++++--- src/browser/Terminal.ts | 14 +++++++------- src/common/services/DecorationService.ts | 14 ++++---------- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 586114d1..0f2148c9 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -547,8 +547,9 @@ function loadTest() { function addDecoration() { const marker = term.addMarker(1); const decoration = term.registerDecoration({ marker }); - decoration.onRender(() => { - decoration.element.style.backgroundColor = 'red'; + decoration.onRender((e) => { + console.log(e); + e.style.backgroundColor = 'red'; }); } @@ -557,5 +558,5 @@ function addOverviewRuler() { canvas.element!.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 5}px`; term.registerDecoration({marker: term.addMarker(3), overviewRulerItemColor: 'green'}); term.registerDecoration({marker: term.addMarker(5), overviewRulerItemColor: 'blue'}); -} +} diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index 56559ad9..efb97c16 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -48,11 +48,14 @@ export class BufferDecorationRenderer extends Disposable implements IDecorationR } public renderDecoration(decorationOptions: IDecorationOptions): void { - const decoration = new BufferDecoration(this._bufferService, decorationOptions); - if (this._decorationContainer && decoration.element && !this._decorationContainer.contains(decoration.element)) { - this._decorationContainer.append(decoration.element); + 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 override dispose(): void { @@ -108,6 +111,7 @@ export class BufferDecoration extends Disposable implements IDecoration { } this._refreshStyle(renderService); if (this._element) { + console.log('firing on render'); this._onRender.fire(this._element); } } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 39f0a68a..2c00d79b 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -586,13 +586,13 @@ export class Terminal extends CoreTerminal implements ITerminal { if (this._decorationService) { this._bufferDecorationRenderer = new BufferDecorationRenderer(this._bufferService, this._renderService, this._decorationService, this.screenElement); } - // if (this.options.overviewRulerWidth && this._decorationService) { - // this._overviewRulerRenderer = new OverviewRulerRenderer(this._bufferService, this._renderService, this._decorationService, this._viewportElement, this.screenElement); - // } - // this.optionsService.onOptionChange(() => { - // if (!this._overviewRulerRenderer && this.options.overviewRulerWidth && this._renderService && this._viewportElement && this.screenElement && this._decorationService) { - // this._overviewRulerRenderer = new OverviewRulerRenderer(this._bufferService, this._renderService, this._decorationService, this._viewportElement, this.screenElement); - // }}); + if (this.options.overviewRulerWidth && this._decorationService) { + this._overviewRulerRenderer = new OverviewRulerRenderer(this._bufferService, this._renderService, this._decorationService, this._instantiationService, this._viewportElement, this.screenElement); + } + this.optionsService.onOptionChange(() => { + if (!this._overviewRulerRenderer && this.options.overviewRulerWidth && this._renderService && this._viewportElement && this.screenElement && this._decorationService) { + this._overviewRulerRenderer = new OverviewRulerRenderer(this._bufferService, this._renderService, this._decorationService, this._instantiationService, this._viewportElement, this.screenElement); + }}); // This event listener must be registered aftre MouseZoneManager is created this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e))); diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 206d50e8..1bd67e0c 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -6,8 +6,8 @@ import { EventEmitter } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; -import { IBufferService, IInstantiationService, IDecorationService } from 'common/services/Services'; -import { IDecorationOptions, IDecoration, IMarker, IDisposable, IEvent } from 'xterm'; +import { IDecorationService } from 'common/services/Services'; +import { IDecorationOptions, IDecoration, IMarker, IEvent } from 'xterm'; export class DecorationService extends Disposable implements IDecorationService { private _animationFrame: number | undefined; @@ -34,9 +34,6 @@ export class DecorationService extends Disposable implements IDecorationService }); this._decorations.push(decoration); this._onDecorationRegistered.fire(options); - decoration.onRender(d => { - decoration.setElement(d); - }); } return decoration; } @@ -71,11 +68,8 @@ class Decoration implements IDecoration { public dispose(): void { throw new Error('Method not implemented.'); } - constructor(decorationOptions?: IDecorationOptions) { - this.marker = decorationOptions?.marker!; + constructor(decorationOptions: IDecorationOptions) { + this.marker = decorationOptions?.marker; this.element = undefined; } - public setElement(element: HTMLElement): void { - this.element = element; - } } From c8d1266f0ae82495f34be7ed6d742a472fb0df77 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 14 Mar 2022 15:24:53 -0700 Subject: [PATCH 24/65] Fix buffer decoration onRender, start using internal decoration --- demo/client.ts | 2 +- .../Decorations/BufferDecorationRenderer.ts | 52 ++++++++++++------- .../Decorations/OverviewRulerRenderer.ts | 14 ++--- src/browser/Terminal.ts | 5 +- src/browser/Types.d.ts | 5 -- src/browser/services/Services.ts | 6 --- src/common/services/DecorationService.ts | 28 +++++----- src/common/services/Services.ts | 12 +++-- 8 files changed, 70 insertions(+), 54 deletions(-) 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; +} 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 25/65] 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 } } From 65b9a6cfc41889a53d11f92a3cd99005fc46ae18 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 14 Mar 2022 15:52:32 -0700 Subject: [PATCH 26/65] Fix dependency injection for decoration service --- src/browser/Decorations/BufferDecorationRenderer.ts | 6 +++--- src/browser/Decorations/OverviewRulerRenderer.ts | 6 +++--- src/browser/Terminal.ts | 6 +++--- src/common/services/DecorationService.ts | 8 +++++--- src/common/services/Services.ts | 1 + 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index 9e3b2d72..2f91d968 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -16,10 +16,10 @@ export class BufferDecorationRenderer extends Disposable { private _altBufferIsActive: boolean = false; constructor( + private readonly _screenElement: HTMLElement, @IBufferService private readonly _bufferService: IBufferService, - @IRenderService private readonly _renderService: IRenderService, - private readonly _decorationService: IDecorationService, - private readonly _screenElement: HTMLElement + @IDecorationService private readonly _decorationService: IDecorationService, + @IRenderService private readonly _renderService: IRenderService ) { super(); diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 2ee0970c..b880fa33 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -23,12 +23,12 @@ export class OverviewRulerRenderer extends Disposable { private _x: number | undefined; constructor( + private readonly _viewportElement: HTMLElement, + private readonly _screenElement: HTMLElement, @IBufferService private readonly _bufferService: IBufferService, - @IRenderService private readonly _renderService: IRenderService, @IDecorationService private readonly _decorationService: IDecorationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - private readonly _viewportElement: HTMLElement, - private readonly _screenElement: HTMLElement + @IRenderService private readonly _renderService: IRenderService ) { super(); this._canvas = document.createElement('canvas'); diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index d07b9c30..3d490d08 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -587,14 +587,14 @@ export class Terminal extends CoreTerminal implements ITerminal { this.linkifier.attachToDom(this.element, this._mouseZoneManager); this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService); if (this._decorationService) { - this._bufferDecorationRenderer = new BufferDecorationRenderer(this._bufferService, this._renderService, this._decorationService, this.screenElement); + this._bufferDecorationRenderer = this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement); } if (this.options.overviewRulerWidth && this._decorationService) { - this._overviewRulerRenderer = new OverviewRulerRenderer(this._bufferService, this._renderService, this._decorationService, this._instantiationService, this._viewportElement, this.screenElement); + this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement); } this.optionsService.onOptionChange(() => { if (!this._overviewRulerRenderer && this.options.overviewRulerWidth && this._renderService && this._viewportElement && this.screenElement && this._decorationService) { - this._overviewRulerRenderer = new OverviewRulerRenderer(this._bufferService, this._renderService, this._decorationService, this._instantiationService, this._viewportElement, this.screenElement); + this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement); }}); // This event listener must be registered aftre MouseZoneManager is created this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e))); diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index af1d30b0..693f8944 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -3,19 +3,21 @@ * @license MIT */ - import { EventEmitter } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IDecorationService, IInternalDecoration } from 'common/services/Services'; import { IDecorationOptions, IDecoration, IMarker, IEvent } from 'xterm'; export class DecorationService extends Disposable implements IDecorationService { + public serviceBrand: any; + + private readonly _decorations: IInternalDecoration[] = []; 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: IInternalDecoration[] = []; public get decorations(): IterableIterator { return this._decorations.values(); } @@ -45,7 +47,7 @@ export class DecorationService extends Disposable implements IDecorationService this._onDecorationRemoved.fire(decoration); decoration.dispose(); } - this._decorations = []; + this._decorations.length = 0; } private _queueRefresh(): void { diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 67bbb5ee..876d90bc 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 { + serviceBrand: undefined; readonly decorations: IterableIterator; readonly onDecorationRegistered: IEvent; readonly onDecorationRemoved: IEvent; From 3dc4f1a7f2edbb06496494679e56d2a4aac18954 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 14 Mar 2022 15:56:27 -0700 Subject: [PATCH 27/65] Remove old decoration elements --- src/browser/Decorations/BufferDecorationRenderer.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index 2f91d968..74fc59a7 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -34,6 +34,7 @@ export class BufferDecorationRenderer extends Disposable { this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt; })); this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh())); + this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); } public override dispose(): void { @@ -101,4 +102,10 @@ export class BufferDecorationRenderer extends Disposable { element.style.display = this._altBufferIsActive ? 'none' : 'block'; } } + + private _removeDecoration(decoration: IInternalDecoration): void { + const element = this._decorationElements.get(decoration); + element?.remove(); + this._decorationElements.delete(decoration); + } } From fc09730ffbbda2727f83534bdea2a793d3dd57d7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 14 Mar 2022 16:07:30 -0700 Subject: [PATCH 28/65] Tidy up DecorationService --- src/common/services/DecorationService.ts | 31 ++++++++---------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 693f8944..f18ad9b5 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -12,7 +12,6 @@ export class DecorationService extends Disposable implements IDecorationService public serviceBrand: any; private readonly _decorations: IInternalDecoration[] = []; - private _animationFrame: number | undefined; private _onDecorationRegistered = this.register(new EventEmitter()); public get onDecorationRegistered(): IEvent { return this._onDecorationRegistered.event; } @@ -49,33 +48,23 @@ export class DecorationService extends Disposable implements IDecorationService } this._decorations.length = 0; } - - private _queueRefresh(): void { - if (this._animationFrame !== undefined) { - return; - } - this._animationFrame = window.requestAnimationFrame(() => { - // this._refresh(); - this._animationFrame = undefined; - }); - } } -class Decoration implements IInternalDecoration { - public marker: IMarker; - public readonly onRenderEmitter = new EventEmitter(); - public readonly onRender = this.onRenderEmitter.event; - private _onDispose = new EventEmitter(); - public readonly onDispose = this._onDispose.event; +class Decoration extends Disposable implements IInternalDecoration { + public readonly marker: IMarker; public element: HTMLElement | undefined; public isDisposed: boolean = false; - public dispose(): void { - throw new Error('Method not implemented.'); - } + + public readonly onRenderEmitter = this.register(new EventEmitter()); + public readonly onRender = this.onRenderEmitter.event; + private _onDispose = this.register(new EventEmitter()); + public readonly onDispose = this._onDispose.event; + constructor( public readonly options: IDecorationOptions ) { + super(); this.marker = options.marker; - this.element = undefined; + // TODO: Make sure dispose doesn't need to do anything else? } } From bfb048110feb3aa09c3055d58fc5a03cff2868e3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 08:57:15 -0400 Subject: [PATCH 29/65] register service --- demo/client.ts | 1 - src/browser/Terminal.ts | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 55e97ed4..7eba89a5 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -548,7 +548,6 @@ function addDecoration() { const marker = term.addMarker(1); const decoration = term.registerDecoration({ marker }); decoration.onRender((e) => { - console.log('onRender', e); e.style.backgroundColor = 'red'; }); } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 3d490d08..b1f96ee5 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -57,8 +57,8 @@ import { CharacterJoinerService } from 'browser/services/CharacterJoinerService' import { toRgbString } from 'common/input/XParseColor'; import { BufferDecorationRenderer } from 'browser/Decorations/BufferDecorationRenderer'; import { OverviewRulerRenderer } from 'browser/Decorations/OverviewRulerRenderer'; -// import { IDecorationService } from 'common/services/Services'; import { DecorationService } from 'common/services/DecorationService'; +import { IDecorationService } from 'common/services/Services'; // Let it work inside Node.js for automated testing purposes. const document: Document = (typeof window !== 'undefined') ? window.document : null as any; @@ -169,6 +169,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.linkifier = this._instantiationService.createInstance(Linkifier); this.linkifier2 = this.register(this._instantiationService.createInstance(Linkifier2)); this._decorationService = this._instantiationService.createInstance(DecorationService); + this._instantiationService.setService(IDecorationService, this._decorationService); // Setup InputHandler listeners this.register(this._inputHandler.onRequestBell(() => this.bell())); From 079bbc8242307a828911b4ef4b7380c5e330441a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 10:27:01 -0400 Subject: [PATCH 30/65] get it working in demo with overviewRulerwidth not considered" --- demo/client.ts | 4 +- .../Decorations/OverviewRulerRenderer.ts | 158 +++++++----------- src/browser/Terminal.ts | 19 +-- src/common/services/OptionsService.ts | 3 +- typings/xterm.d.ts | 17 +- 5 files changed, 88 insertions(+), 113 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 7eba89a5..f6223f2c 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -554,8 +554,10 @@ function addDecoration() { function addOverviewRuler() { const canvas = term.registerDecoration({marker: term.addMarker(1), overviewRulerItemColor: 'red'}); - canvas.element!.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 5}px`; term.registerDecoration({marker: term.addMarker(3), overviewRulerItemColor: 'green'}); term.registerDecoration({marker: term.addMarker(5), overviewRulerItemColor: 'blue'}); + canvas.onRender((e) => { + e.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 5}px`; + }); } diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index b880fa33..8a78bb78 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -5,36 +5,37 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; 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'; +import { IBufferService, IDecorationService, IInternalDecoration, IOptionsService } from 'common/services/Services'; const enum ScrollbarConstants { - WIDTH = 7 + WIDTH = 15 } export class OverviewRulerRenderer extends Disposable { private _canvas: HTMLCanvasElement; private _ctx: CanvasRenderingContext2D | null; - private _decorations: ScrollbarDecoration[] = []; - private _width: number | undefined; - private _anchor: 'right' | 'left' | undefined; - private _x: number | undefined; + private readonly _decorationElements: Map = new Map(); + + private _animationFrame: number | undefined; constructor( private readonly _viewportElement: HTMLElement, private readonly _screenElement: HTMLElement, @IBufferService private readonly _bufferService: IBufferService, @IDecorationService private readonly _decorationService: IDecorationService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IRenderService private readonly _renderService: IRenderService + @IRenderService private readonly _renderService: IRenderService, + @IOptionsService private readonly _optionsService: IOptionsService ) { super(); this._canvas = document.createElement('canvas'); this._canvas.classList.add('xterm-decoration-scrollbar'); this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement); this._ctx = this._canvas.getContext('2d'); + this._canvas.style.width = `${this._optionsService.options.overviewRulerWidth || ScrollbarConstants.WIDTH}px`; + this._canvas.style.height = `${this._screenElement.clientHeight}px`; + this._canvas.width = Math.floor((this._optionsService.options.overviewRulerWidth|| ScrollbarConstants.WIDTH)* window.devicePixelRatio); + this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); this.refreshDecorations(); this.register(this._bufferService.buffers.onBufferActivate(() => { this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; @@ -42,111 +43,80 @@ export class OverviewRulerRenderer extends Disposable { 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.registerDecoration(e))); - this.register(this._decorationService.onDecorationRemoved(d => d.dispose())); + this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh())); + this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); } - public registerDecoration(decoration: IInternalDecoration): void { - if (!this._ctx || !decoration.options.overviewRulerItemColor) { + + public override dispose(): void { + for (const decoration of this._decorationElements) { + this._ctx?.clearRect( + 0, + Math.round(this._canvas.height * (decoration[0].marker.line / this._bufferService.buffers.active.lines.length)), + this._canvas.width, + window.devicePixelRatio + ); + } + this._decorationElements.clear(); + this._canvas?.remove(); + super.dispose(); + } + + private _refreshStyle(decoration: IInternalDecoration): void { + if (!this._ctx) { + return; + } + if (decoration.options.anchor === 'right') { + this._canvas.style.right = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : ''; + } else { + this._canvas.style.left = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : ''; + } + if (!decoration.options.overviewRulerItemColor) { + this._decorationElements.delete(decoration); return; } - - // 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 = decoration.options.overviewRulerItemColor; this._ctx.strokeRect( 0, - Math.round(this._canvas.height * (decoration.marker.line / this._bufferService.buffers.active.lines.length)), + Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), this._canvas.width, window.devicePixelRatio ); } public refreshDecorations(): void { - if (!this._ctx) { - return; - } - this._canvas.style.width = `${this._width || ScrollbarConstants.WIDTH}px`; + this._canvas.style.width = `${this._canvas.width || ScrollbarConstants.WIDTH}px`; this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.width = Math.floor((this._width || ScrollbarConstants.WIDTH)* window.devicePixelRatio); + this._canvas.width = Math.floor((this._canvas.width || ScrollbarConstants.WIDTH)* window.devicePixelRatio); this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); - if (this._anchor === 'right') { - this._canvas.style.right = this._x ? `${this._x * this._renderService.dimensions.actualCellWidth}px` : ''; - } else { - this._canvas.style.left = this._x ? `${this._x * this._renderService.dimensions.actualCellWidth}px` : ''; - } - this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); - for (const decoration of this._decorations) { - decoration.render(this._ctx, this._canvas); + + for (const decoration of this._decorationService.decorations) { + this._renderDecoration(decoration); } } - public override dispose(): void { - for (const decoration of this._decorations) { - decoration.dispose(); + + private _renderDecoration(decoration: IInternalDecoration): void { + const element = this._decorationElements.get(decoration); + if (!element) { + this._decorationElements.set(decoration, this._canvas); } - this._decorations = []; - this._canvas?.remove(); - super.dispose(); - } -} -class ScrollbarDecoration extends Disposable implements IDecoration { - private readonly _marker: IMarker; - private _ctx: CanvasRenderingContext2D | undefined; - private _canvas: HTMLCanvasElement | undefined; - private _color: string | undefined; - - public isDisposed: boolean = false; - - public get element(): HTMLCanvasElement | undefined { return this._canvas; } - public get marker(): IMarker { return this._marker; } - public get color(): string | undefined { return this._color; } - - private _onDispose = new EventEmitter(); - public get onDispose(): IEvent { return this._onDispose.event; } - - private _onRender = new EventEmitter(); - public get onRender(): IEvent { return this._onRender.event; } - - constructor( - options: IDecorationOptions, - @IBufferService private readonly _bufferService: IBufferService - ) { - super(); - this._marker = options.marker; - this._color = options.overviewRulerItemColor; - this._marker.onDispose(() => this.dispose()); + this._refreshStyle(decoration); + decoration.onRenderEmitter.fire(this._canvas); } - public render(ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement): void { - if (!this.color) { - throw new Error('No color was provided for the overview ruler decoraiton'); - } - this._ctx = ctx; - this._canvas = canvas; - ctx.lineWidth = 1; - ctx.strokeStyle = this.color; - ctx.strokeRect( - 0, - Math.round(canvas.height * (this.marker.line / this._bufferService.buffers.active.lines.length)), - canvas.width, - window.devicePixelRatio - ); - this._onRender.fire(canvas); - } - - public override dispose(): void { - if (this._isDisposed || !this._canvas || !this._ctx) { + private _queueRefresh(): void { + if (this._animationFrame !== undefined) { return; } - this._ctx.clearRect( - 0, - Math.round(this._canvas.height * (this.marker.line / this._bufferService.buffers.active.lines.length)), - this._canvas.width, - window.devicePixelRatio - ); - this.isDisposed = true; - this._onDispose.fire(); - super.dispose(); + this._animationFrame = window.requestAnimationFrame(() => { + this.refreshDecorations(); + this._animationFrame = undefined; + }); + } + + private _removeDecoration(decoration: IInternalDecoration): void { + const element = this._decorationElements.get(decoration); + element?.remove(); + this._decorationElements.delete(decoration); } } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index b1f96ee5..6d0aabb1 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -75,7 +75,6 @@ export class Terminal extends CoreTerminal implements ITerminal { private _compositionView: HTMLElement | undefined; private _overviewRulerRenderer: OverviewRulerRenderer | undefined; - private _bufferDecorationRenderer: BufferDecorationRenderer | undefined; // private _visualBellTimer: number; @@ -587,16 +586,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this.onScroll(() => this._mouseZoneManager!.clearAll())); this.linkifier.attachToDom(this.element, this._mouseZoneManager); this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService); - if (this._decorationService) { - this._bufferDecorationRenderer = this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement); - } - if (this.options.overviewRulerWidth && this._decorationService) { - this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement); - } - this.optionsService.onOptionChange(() => { - if (!this._overviewRulerRenderer && this.options.overviewRulerWidth && this._renderService && this._viewportElement && this.screenElement && this._decorationService) { - this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement); - }}); + this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement); // This event listener must be registered aftre MouseZoneManager is created this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e))); @@ -614,6 +604,13 @@ export class Terminal extends CoreTerminal implements ITerminal { this._accessibilityManager = new AccessibilityManager(this, this._renderService); } + // if (this.options.overviewRulerWidth) { + this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement); + // } + this.optionsService.onOptionChange(() => { + if (!this._overviewRulerRenderer && this.options.overviewRulerWidth && this._viewportElement && this.screenElement) { + this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement); + }}); // Measure the character size this._charSizeService.measure(); diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 43fe9981..4008a431 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -52,7 +52,8 @@ export const DEFAULT_OPTIONS: Readonly = { altClickMovesCursor: true, convertEol: false, termName: 'xterm', - cancelEvents: false + cancelEvents: false, + overviewRulerWidth: undefined }; const FONT_WEIGHT_OPTIONS: Extract[] = ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900']; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 00f4072f..986e77c9 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -266,6 +266,11 @@ declare module 'xterm' { * All features are disabled by default for security reasons. */ windowOptions?: IWindowOptions; + + /** + * The width, in pixels, of the canvas for the overview ruler. + */ + overviewRulerWidth?: number; } /** @@ -394,7 +399,7 @@ declare module 'xterm' { } /** - * Represents a disposable with an + * Represents a disposable with an * @param onDispose event listener and * @param isDisposed property. */ @@ -436,7 +441,7 @@ declare module 'xterm' { /** * Options provided when registering a decoration - * containing a @param marker, @param anchor, + * containing a @param marker, @param anchor, * @param x offset from the anchor, @param width in cells * and @param height in cells. */ @@ -455,19 +460,19 @@ declare module 'xterm' { /** * The x position offset relative to the anchor - */ + */ x?: number; /** - * The width of the decoration in cells, which defaults to + * The width of the decoration in cells, which defaults to * cell width or the width in pixels, when an overlayRulerItemColor * is provided. */ width?: number; /** - * The height of the decoration in cells, which defaults to + * The height of the decoration in cells, which defaults to * cell height */ height?: number; @@ -946,7 +951,7 @@ declare module 'xterm' { /** * (EXPERIMENTAL) Adds a decoration to the terminal using - * @param decorationOptions, which takes a marker and an optional anchor, + * @param decorationOptions, which takes a marker and an optional anchor, * width, height, and x offset from the anchor. Returns the decoration or * undefined if the alt buffer is active or the marker has already been disposed of. * @throws when options include a negative x offset. From 3e47c96445e6baa448707ff3456ed09da166b6fa Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 10:31:01 -0400 Subject: [PATCH 31/65] get demo to work the right way --- demo/client.ts | 1 + src/browser/Terminal.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index f6223f2c..e1347e3d 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -553,6 +553,7 @@ function addDecoration() { } function addOverviewRuler() { + term.options['overviewRulerWidth'] = 15; const canvas = term.registerDecoration({marker: term.addMarker(1), overviewRulerItemColor: 'red'}); term.registerDecoration({marker: term.addMarker(3), overviewRulerItemColor: 'green'}); term.registerDecoration({marker: term.addMarker(5), overviewRulerItemColor: 'blue'}); diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 6d0aabb1..c2fb1500 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -604,9 +604,9 @@ export class Terminal extends CoreTerminal implements ITerminal { this._accessibilityManager = new AccessibilityManager(this, this._renderService); } - // if (this.options.overviewRulerWidth) { - this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement); - // } + if (this.options.overviewRulerWidth) { + this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement); + } this.optionsService.onOptionChange(() => { if (!this._overviewRulerRenderer && this.options.overviewRulerWidth && this._viewportElement && this.screenElement) { this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement); From 39f9c3507bcd856441e3bf1db627550b164fc625 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 10:37:38 -0400 Subject: [PATCH 32/65] rename css, fix background color getting applied --- css/xterm.css | 4 ++-- demo/client.ts | 4 +++- src/browser/Decorations/OverviewRulerRenderer.ts | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/css/xterm.css b/css/xterm.css index 44bb062a..0ca59399 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -179,10 +179,10 @@ position: absolute; } -.xterm-decoration-scrollbar { +.xterm-decoration-overview-ruler { z-index: 7; position: absolute; top: 0; right: 0; width: 50px; -} \ No newline at end of file +} diff --git a/demo/client.ts b/demo/client.ts index e1347e3d..b80e2e76 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -548,7 +548,9 @@ function addDecoration() { const marker = term.addMarker(1); const decoration = term.registerDecoration({ marker }); decoration.onRender((e) => { - e.style.backgroundColor = 'red'; + if (e.classList.value === 'xterm-decoration') { + e.style.backgroundColor = 'red'; + } }); } diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 8a78bb78..083db5e9 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -29,7 +29,7 @@ export class OverviewRulerRenderer extends Disposable { ) { super(); this._canvas = document.createElement('canvas'); - this._canvas.classList.add('xterm-decoration-scrollbar'); + this._canvas.classList.add('xterm-decoration-overview-ruler'); this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement); this._ctx = this._canvas.getContext('2d'); this._canvas.style.width = `${this._optionsService.options.overviewRulerWidth || ScrollbarConstants.WIDTH}px`; From 4ef5f6ae86d8c25d625a108a95608f0cbd2ac149 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 10:53:32 -0400 Subject: [PATCH 33/65] add listener for on option change --- src/browser/Decorations/OverviewRulerRenderer.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 083db5e9..7d8475f2 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -45,6 +45,11 @@ export class OverviewRulerRenderer extends Disposable { this.register(addDisposableDomListener(window, 'resize', () => this.refreshDecorations())); this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh())); this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); + this.register(this._optionsService.onOptionChange(o => { + if (o === 'overviewRulerWidth') { + this.refreshDecorations(); + } + })); } public override dispose(): void { @@ -85,9 +90,9 @@ export class OverviewRulerRenderer extends Disposable { } public refreshDecorations(): void { - this._canvas.style.width = `${this._canvas.width || ScrollbarConstants.WIDTH}px`; + this._canvas.style.width = `${this._optionsService.options.overviewRulerWidth || ScrollbarConstants.WIDTH}px`; this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.width = Math.floor((this._canvas.width || ScrollbarConstants.WIDTH)* window.devicePixelRatio); + this._canvas.width = Math.floor((this._optionsService.options.overviewRulerWidth || ScrollbarConstants.WIDTH)* window.devicePixelRatio); this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); for (const decoration of this._decorationService.decorations) { From 520d5dd9eb848db6f4ae5949ea59d3abe1c53e19 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 11:37:20 -0400 Subject: [PATCH 34/65] delete unused css --- css/xterm.css | 1 - 1 file changed, 1 deletion(-) diff --git a/css/xterm.css b/css/xterm.css index 0ca59399..7432fbb1 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -184,5 +184,4 @@ position: absolute; top: 0; right: 0; - width: 50px; } From f98b4ce1bafae14b63f32342e429c0dc41ecd242 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 12:16:33 -0400 Subject: [PATCH 35/65] properly dispose of buffer decoration --- src/browser/Decorations/BufferDecorationRenderer.ts | 1 + src/common/services/DecorationService.ts | 7 ++++++- test/api/Terminal.api.ts | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index 74fc59a7..26166c77 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -63,6 +63,7 @@ export class BufferDecorationRenderer extends Disposable { let element = this._decorationElements.get(decoration); if (!element) { element = this._createElement(decoration); + decoration.onDispose(() => this._removeDecoration(decoration)); this._decorationElements.set(decoration, element); this._container.appendChild(element); } diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index f18ad9b5..d561e91b 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -65,6 +65,11 @@ class Decoration extends Disposable implements IInternalDecoration { ) { super(); this.marker = options.marker; - // TODO: Make sure dispose doesn't need to do anything else? + this.marker.onDispose(() => this.dispose()); + } + public override dispose(): void { + this.element?.remove(); + this._onDispose.fire(); + super.dispose(); } } diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index ee6a0cfa..a73cdc19 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -574,7 +574,7 @@ describe('API Integration Tests', function(): void { await page.evaluate(`window.term.scrollLines(10)`); await page.evaluate(`window.term.addMarker(3)`); await page.evaluate(`window.term.addMarker(4)`); - await page.evaluate(` + await page.evaluate(` for (let i = 0; i < window.term.markers.length; ++i) { const marker = window.term.markers[i]; marker.onDispose(() => window.disposeStack.push(marker)); From 80e7963dbe6288a9109adb5190092cc85f45e36c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 12:43:03 -0400 Subject: [PATCH 36/65] fix tests and add one --- test/api/Terminal.api.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index a73cdc19..c1412223 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -732,7 +732,7 @@ describe('API Integration Tests', function(): void { }); describe('registerDecoration', () => { - it('should register decorations and render them', async () => { + it('should register decorations and not render them', async () => { await openTerminal(page); await writeSync(page, '\\n\\n\\n\\n'); await writeSync(page, '\\n\\n\\n\\n'); @@ -742,6 +742,18 @@ describe('API Integration Tests', function(): void { await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`); await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`); await page.evaluate(`window.term.resize(10, 5)`); + assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 0); + }); + it('should register decorations and render them when open is called', async () => { + await openTerminal(page); + await writeSync(page, '\\n\\n\\n\\n'); + await writeSync(page, '\\n\\n\\n\\n'); + await writeSync(page, '\\n\\n\\n\\n'); + await page.evaluate(`window.marker1 = window.term.addMarker(1)`); + await page.evaluate(`window.marker2 = window.term.addMarker(2)`); + await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`); + await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`); + await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 2); }); it('on resize should dispose of the old decoration and create a new one', async () => { @@ -750,6 +762,7 @@ describe('API Integration Tests', function(): void { await writeSync(page, '\\n\\n\\n\\n'); await page.evaluate(`window.marker = window.term.addMarker(1)`); await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker })`); + await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); await page.evaluate(`window.term.resize(10, 5)`); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 1); }); From e7e9d5ca4c5ef799b20f05d099592656944749e4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 12:56:34 -0400 Subject: [PATCH 37/65] remove test --- demo/client.ts | 6 +++--- src/browser/Decorations/BufferDecorationRenderer.ts | 3 +++ src/browser/Decorations/OverviewRulerRenderer.ts | 4 ++-- src/common/services/DecorationService.ts | 3 ++- test/api/Terminal.api.ts | 12 ------------ typings/xterm.d.ts | 8 +++++--- 6 files changed, 15 insertions(+), 21 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index b80e2e76..b219b733 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -556,9 +556,9 @@ function addDecoration() { function addOverviewRuler() { term.options['overviewRulerWidth'] = 15; - const canvas = term.registerDecoration({marker: term.addMarker(1), overviewRulerItemColor: 'red'}); - term.registerDecoration({marker: term.addMarker(3), overviewRulerItemColor: 'green'}); - term.registerDecoration({marker: term.addMarker(5), overviewRulerItemColor: 'blue'}); + const canvas = term.registerDecoration({marker: term.addMarker(1), { color }: 'red'}); + term.registerDecoration({marker: term.addMarker(3), { color }: 'green'}); + term.registerDecoration({marker: term.addMarker(5), { color }: 'blue'}); canvas.onRender((e) => { e.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 5}px`; }); diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index 26166c77..8821ccc1 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -106,6 +106,9 @@ export class BufferDecorationRenderer extends Disposable { private _removeDecoration(decoration: IInternalDecoration): void { const element = this._decorationElements.get(decoration); + if (element && this._container && this._container.contains(element)) { + this._container.removeChild(element); + } element?.remove(); this._decorationElements.delete(decoration); } diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 7d8475f2..45f30c35 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -75,12 +75,12 @@ export class OverviewRulerRenderer extends Disposable { } else { this._canvas.style.left = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : ''; } - if (!decoration.options.overviewRulerItemColor) { + if (!decoration.options.overviewRulerOptions?.color) { this._decorationElements.delete(decoration); return; } this._ctx.lineWidth = 1; - this._ctx.strokeStyle = decoration.options.overviewRulerItemColor; + this._ctx.strokeStyle = decoration.options.overviewRulerOptions.color; this._ctx.strokeRect( 0, Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index d561e91b..9d7e3314 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -68,8 +68,9 @@ class Decoration extends Disposable implements IInternalDecoration { this.marker.onDispose(() => this.dispose()); } public override dispose(): void { - this.element?.remove(); this._onDispose.fire(); + this.element?.remove(); + this.element = undefined; super.dispose(); } } diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index c1412223..0a6732d7 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -744,18 +744,6 @@ describe('API Integration Tests', function(): void { await page.evaluate(`window.term.resize(10, 5)`); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 0); }); - it('should register decorations and render them when open is called', async () => { - await openTerminal(page); - await writeSync(page, '\\n\\n\\n\\n'); - await writeSync(page, '\\n\\n\\n\\n'); - await writeSync(page, '\\n\\n\\n\\n'); - await page.evaluate(`window.marker1 = window.term.addMarker(1)`); - await page.evaluate(`window.marker2 = window.term.addMarker(2)`); - await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`); - await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`); - await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); - assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 2); - }); it('on resize should dispose of the old decoration and create a new one', async () => { await openTerminal(page); await writeSync(page, '\\n\\n\\n\\n'); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 986e77c9..c6062967 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -478,10 +478,12 @@ declare module 'xterm' { height?: number; /** - * When provided, renders the decoration in the scrollbar - * with the given color + * Renders the decoration in the scrollbar + * with the given @param color and optional @param position. + * If @param position is not set, it will span the full @param overviewRulerWidth, which + * must be provided via @TerminalOptions for this to work. */ - overviewRulerItemColor?: string; + overviewRulerOptions?: { color: string; position?: 'left' | 'center' | 'right'} } /** From df929f2dd5b65390e82f422c4125cd75eae629ba Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 13:07:49 -0400 Subject: [PATCH 38/65] add position --- demo/client.ts | 9 ++++++--- src/browser/Decorations/OverviewRulerRenderer.ts | 8 +++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index b219b733..70588213 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -556,9 +556,12 @@ function addDecoration() { function addOverviewRuler() { term.options['overviewRulerWidth'] = 15; - const canvas = term.registerDecoration({marker: term.addMarker(1), { color }: 'red'}); - term.registerDecoration({marker: term.addMarker(3), { color }: 'green'}); - term.registerDecoration({marker: term.addMarker(5), { color }: 'blue'}); + const canvas = term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: 'red' }}); + term.registerDecoration({marker: term.addMarker(3), overviewRulerOptions: { color: 'green' }}); + term.registerDecoration({marker: term.addMarker(5), overviewRulerOptions: { color: 'blue' }}); + term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: 'red', position: 'left' }}); + term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: 'green', position: 'center' }}); + term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: 'blue', position: 'right' }}); canvas.onRender((e) => { e.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 5}px`; }); diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 45f30c35..eb58bc10 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -67,7 +67,7 @@ export class OverviewRulerRenderer extends Disposable { } private _refreshStyle(decoration: IInternalDecoration): void { - if (!this._ctx) { + if (!this._ctx || !this._optionsService.options.overviewRulerWidth) { return; } if (decoration.options.anchor === 'right') { @@ -81,10 +81,12 @@ export class OverviewRulerRenderer extends Disposable { } this._ctx.lineWidth = 1; this._ctx.strokeStyle = decoration.options.overviewRulerOptions.color; + const size = Math.floor(this._optionsService.options.overviewRulerWidth / 3); + const position = decoration.options.overviewRulerOptions.position; this._ctx.strokeRect( - 0, + !position || position === 'left' ? 0 : position === 'right' ? size * 2 + 1: size, Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), - this._canvas.width, + !position ? this._canvas.width : position === 'center' ? size + 1 : size, window.devicePixelRatio ); } From bd21c6afa59f9c8b8517365df89caaa4ac60ff0d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 13:15:43 -0400 Subject: [PATCH 39/65] adjust test --- demo/client.ts | 2 +- test/api/Terminal.api.ts | 14 ++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 70588213..f3f11806 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -555,7 +555,7 @@ function addDecoration() { } function addOverviewRuler() { - term.options['overviewRulerWidth'] = 15; + term.options['overviewRulerWidth'] = 13; const canvas = term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: 'red' }}); term.registerDecoration({marker: term.addMarker(3), overviewRulerOptions: { color: 'green' }}); term.registerDecoration({marker: term.addMarker(5), overviewRulerOptions: { color: 'blue' }}); diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 0a6732d7..f13b2ada 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -732,7 +732,7 @@ describe('API Integration Tests', function(): void { }); describe('registerDecoration', () => { - it('should register decorations and not render them', async () => { + it('should register decorations and render them', async () => { await openTerminal(page); await writeSync(page, '\\n\\n\\n\\n'); await writeSync(page, '\\n\\n\\n\\n'); @@ -742,17 +742,7 @@ describe('API Integration Tests', function(): void { await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`); await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`); await page.evaluate(`window.term.resize(10, 5)`); - assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 0); - }); - it('on resize should dispose of the old decoration and create a new one', async () => { - await openTerminal(page); - await writeSync(page, '\\n\\n\\n\\n'); - await writeSync(page, '\\n\\n\\n\\n'); - await page.evaluate(`window.marker = window.term.addMarker(1)`); - await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker })`); - await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); - await page.evaluate(`window.term.resize(10, 5)`); - assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 1); + assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 2); }); it('should return undefined when the marker has already been disposed of', async () => { await openTerminal(page); From 71649f5e407f0fc1b6bf55966bd2b32794f1aff0 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 13:21:34 -0400 Subject: [PATCH 40/65] fix tests --- test/api/Terminal.api.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index f13b2ada..f2abf2f8 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -731,31 +731,26 @@ describe('API Integration Tests', function(): void { await pollFor(page, `window.term._core._renderService.dimensions.actualCellWidth > 0`, true); }); - describe('registerDecoration', () => { - it('should register decorations and render them', async () => { - await openTerminal(page); - await writeSync(page, '\\n\\n\\n\\n'); - await writeSync(page, '\\n\\n\\n\\n'); - await writeSync(page, '\\n\\n\\n\\n'); + describe.only('registerDecoration', () => { + it('should register decorations and render them when terminal open is called', async () => { + await page.evaluate(`window.term = new Terminal({})`); + await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); + await page.waitForSelector('.xterm-text-layer'); await page.evaluate(`window.marker1 = window.term.addMarker(1)`); await page.evaluate(`window.marker2 = window.term.addMarker(2)`); await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`); await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`); - await page.evaluate(`window.term.resize(10, 5)`); + await openTerminal(page); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 2); }); it('should return undefined when the marker has already been disposed of', async () => { await openTerminal(page); - await writeSync(page, '\\n\\n\\n\\n'); - await writeSync(page, '\\n\\n\\n\\n'); await page.evaluate(`window.marker = window.term.addMarker(1)`); await page.evaluate(`window.marker.dispose()`); assert.equal(await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker });`), undefined); }); it('should throw when a negative x offset is provided', async () => { await openTerminal(page); - await writeSync(page, '\\n\\n\\n\\n'); - await writeSync(page, '\\n\\n\\n\\n'); await page.evaluate(`window.marker = window.term.addMarker(1)`); await page.evaluate(` try { From adaee5e495785769d8752a075e630445e87ab0bb Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 13:23:09 -0400 Subject: [PATCH 41/65] use poll for instead --- test/api/Terminal.api.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index f2abf2f8..0e50e7fc 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -741,13 +741,13 @@ describe('API Integration Tests', function(): void { await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`); await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`); await openTerminal(page); - assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 2); + await pollFor(page, `document.querySelectorAll('.xterm-screen .xterm-decoration').length`, 2); }); it('should return undefined when the marker has already been disposed of', async () => { await openTerminal(page); await page.evaluate(`window.marker = window.term.addMarker(1)`); await page.evaluate(`window.marker.dispose()`); - assert.equal(await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker });`), undefined); + await pollFor(page, `window.decoration = window.term.registerDecoration({ marker: window.marker });`, undefined); }); it('should throw when a negative x offset is provided', async () => { await openTerminal(page); From 1905d7d8cad1f318887bb38065e95344e28a8e3d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 13:28:50 -0400 Subject: [PATCH 42/65] add overview ruler tests --- test/api/Terminal.api.ts | 82 ++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 28 deletions(-) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 0e50e7fc..43f1e97d 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -731,35 +731,61 @@ describe('API Integration Tests', function(): void { await pollFor(page, `window.term._core._renderService.dimensions.actualCellWidth > 0`, true); }); - describe.only('registerDecoration', () => { - it('should register decorations and render them when terminal open is called', async () => { - await page.evaluate(`window.term = new Terminal({})`); - await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); - await page.waitForSelector('.xterm-text-layer'); - await page.evaluate(`window.marker1 = window.term.addMarker(1)`); - await page.evaluate(`window.marker2 = window.term.addMarker(2)`); - await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`); - await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`); - await openTerminal(page); - await pollFor(page, `document.querySelectorAll('.xterm-screen .xterm-decoration').length`, 2); + describe('registerDecoration', () => { + describe('bufferDecorations', () => { + it('should register decorations and render them when terminal open is called', async () => { + await page.evaluate(`window.term = new Terminal({})`); + await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); + await page.waitForSelector('.xterm-text-layer'); + await page.evaluate(`window.marker1 = window.term.addMarker(1)`); + await page.evaluate(`window.marker2 = window.term.addMarker(2)`); + await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`); + await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`); + await openTerminal(page); + await pollFor(page, `document.querySelectorAll('.xterm-screen .xterm-decoration').length`, 2); + }); + it('should return undefined when the marker has already been disposed of', async () => { + await openTerminal(page); + await page.evaluate(`window.marker = window.term.addMarker(1)`); + await page.evaluate(`window.marker.dispose()`); + await pollFor(page, `window.decoration = window.term.registerDecoration({ marker: window.marker });`, undefined); + }); + it('should throw when a negative x offset is provided', async () => { + await openTerminal(page); + await page.evaluate(`window.marker = window.term.addMarker(1)`); + await page.evaluate(` + try { + window.decoration = window.term.registerDecoration({ marker: window.marker, x: -2 }); + } catch (e) { + window.throwMessage = e.message; + } + `); + await pollFor(page, 'window.throwMessage', 'This API only accepts positive integers'); + }); }); - it('should return undefined when the marker has already been disposed of', async () => { - await openTerminal(page); - await page.evaluate(`window.marker = window.term.addMarker(1)`); - await page.evaluate(`window.marker.dispose()`); - await pollFor(page, `window.decoration = window.term.registerDecoration({ marker: window.marker });`, undefined); - }); - it('should throw when a negative x offset is provided', async () => { - await openTerminal(page); - await page.evaluate(`window.marker = window.term.addMarker(1)`); - await page.evaluate(` - try { - window.decoration = window.term.registerDecoration({ marker: window.marker, x: -2 }); - } catch (e) { - window.throwMessage = e.message; - } - `); - await pollFor(page, 'window.throwMessage', 'This API only accepts positive integers'); + describe('overviewRulerDecorations', () => { + it('should not add an overview ruler when width is not set', async () => { + await page.evaluate(`window.term = new Terminal({})`); + await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); + await page.waitForSelector('.xterm-text-layer'); + await page.evaluate(`window.marker1 = window.term.addMarker(1)`); + await page.evaluate(`window.marker2 = window.term.addMarker(2)`); + await page.evaluate(`window.term.registerDecoration({ marker: window.marker1, overviewRulerOptions: { color: 'red' } })`); + await page.evaluate(`window.term.registerDecoration({ marker: window.marker2, overviewRulerOptions: { color: 'blue' } })`); + await openTerminal(page); + await pollFor(page, `document.querySelectorAll('.xterm-decoration-overview-ruler').length`, 0); + }); + it('should add an overview ruler when width is set', async () => { + await page.evaluate(`window.term = new Terminal({ overviewRulerWidth: 15 })`); + await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); + await page.waitForSelector('.xterm-text-layer'); + await page.evaluate(`window.marker1 = window.term.addMarker(1)`); + await page.evaluate(`window.marker2 = window.term.addMarker(2)`); + await page.evaluate(`window.term.registerDecoration({ marker: window.marker1, overviewRulerOptions: { color: 'red' } })`); + await page.evaluate(`window.term.registerDecoration({ marker: window.marker2, overviewRulerOptions: { color: 'blue' } })`); + await openTerminal(page); + await pollFor(page, `document.querySelectorAll('.xterm-decoration-overview-ruler').length`, 1); + }); }); }); From 21263a5f60f390e33644a3be0d851719a7a0b8b3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 14:54:37 -0400 Subject: [PATCH 43/65] cleanup --- demo/client.ts | 12 ++++++------ .../Decorations/BufferDecorationRenderer.ts | 8 +++----- src/browser/Decorations/OverviewRulerRenderer.ts | 16 +++++++--------- src/common/services/DecorationService.ts | 3 --- typings/xterm.d.ts | 2 +- 5 files changed, 17 insertions(+), 24 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index f3f11806..047cd602 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -556,12 +556,12 @@ function addDecoration() { function addOverviewRuler() { term.options['overviewRulerWidth'] = 13; - const canvas = term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: 'red' }}); - term.registerDecoration({marker: term.addMarker(3), overviewRulerOptions: { color: 'green' }}); - term.registerDecoration({marker: term.addMarker(5), overviewRulerOptions: { color: 'blue' }}); - term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: 'red', position: 'left' }}); - term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: 'green', position: 'center' }}); - term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: 'blue', position: 'right' }}); + const canvas = term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: '#ef2929' }}); + term.registerDecoration({marker: term.addMarker(3), overviewRulerOptions: { color: '#8ae234' }}); + term.registerDecoration({marker: term.addMarker(5), overviewRulerOptions: { color: '#729fcf' }}); + term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#ef2929', position: 'left' }}); + term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#8ae234', position: 'center' }}); + term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#729fcf', position: 'right' }}); canvas.onRender((e) => { e.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 5}px`; }); diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index 8821ccc1..116c09a5 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -64,6 +64,8 @@ export class BufferDecorationRenderer extends Disposable { if (!element) { element = this._createElement(decoration); decoration.onDispose(() => this._removeDecoration(decoration)); + decoration.marker.onDispose(() => decoration.dispose()); + decoration.element = element; this._decorationElements.set(decoration, element); this._container.appendChild(element); } @@ -105,11 +107,7 @@ export class BufferDecorationRenderer extends Disposable { } private _removeDecoration(decoration: IInternalDecoration): void { - const element = this._decorationElements.get(decoration); - if (element && this._container && this._container.contains(element)) { - this._container.removeChild(element); - } - element?.remove(); + this._decorationElements.get(decoration)?.remove(); this._decorationElements.delete(decoration); } } diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index eb58bc10..a9439d9f 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -8,15 +8,13 @@ import { IRenderService } from 'browser/services/Services'; import { Disposable } from 'common/Lifecycle'; import { IBufferService, IDecorationService, IInternalDecoration, IOptionsService } from 'common/services/Services'; -const enum ScrollbarConstants { - WIDTH = 15 -} - export class OverviewRulerRenderer extends Disposable { private _canvas: HTMLCanvasElement; private _ctx: CanvasRenderingContext2D | null; private readonly _decorationElements: Map = new Map(); - + private get _width(): number { + return this._optionsService.options.overviewRulerWidth || 0; + } private _animationFrame: number | undefined; constructor( @@ -32,9 +30,9 @@ export class OverviewRulerRenderer extends Disposable { this._canvas.classList.add('xterm-decoration-overview-ruler'); this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement); this._ctx = this._canvas.getContext('2d'); - this._canvas.style.width = `${this._optionsService.options.overviewRulerWidth || ScrollbarConstants.WIDTH}px`; + this._canvas.style.width = `${this._width}px`; this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.width = Math.floor((this._optionsService.options.overviewRulerWidth|| ScrollbarConstants.WIDTH)* window.devicePixelRatio); + this._canvas.width = Math.floor((this._width)* window.devicePixelRatio); this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); this.refreshDecorations(); this.register(this._bufferService.buffers.onBufferActivate(() => { @@ -92,9 +90,9 @@ export class OverviewRulerRenderer extends Disposable { } public refreshDecorations(): void { - this._canvas.style.width = `${this._optionsService.options.overviewRulerWidth || ScrollbarConstants.WIDTH}px`; + this._canvas.style.width = `${this._width}px`; this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.width = Math.floor((this._optionsService.options.overviewRulerWidth || ScrollbarConstants.WIDTH)* window.devicePixelRatio); + this._canvas.width = Math.floor((this._width)* window.devicePixelRatio); this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); for (const decoration of this._decorationService.decorations) { diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 9d7e3314..911fd369 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -65,12 +65,9 @@ class Decoration extends Disposable implements IInternalDecoration { ) { super(); this.marker = options.marker; - this.marker.onDispose(() => this.dispose()); } public override dispose(): void { this._onDispose.fire(); - this.element?.remove(); - this.element = undefined; super.dispose(); } } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index c6062967..0dce1ce6 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -436,7 +436,7 @@ declare module 'xterm' { * after the first _onRender call, or undefined if accessed before * that. */ - readonly element: HTMLElement | undefined; + element: HTMLElement | undefined; } /** From a93f81c270798e2d37ec2c615f34eed6cd031790 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 14:57:34 -0400 Subject: [PATCH 44/65] Update typings/xterm.d.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- typings/xterm.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 0dce1ce6..0703021a 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -432,8 +432,8 @@ declare module 'xterm' { readonly onRender: IEvent; /** - * The HTMLElement that gets created or drawn to (for scrollbar decorations) - * after the first _onRender call, or undefined if accessed before + * The element that the decoration is rendered to. This will be undefined + * until it is rendered for the first time by @{link IDecoration.onRender}. * that. */ element: HTMLElement | undefined; From 8488f1ad6312c36c8f39307836d4782c522a8653 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 14:59:09 -0400 Subject: [PATCH 45/65] Update src/browser/Decorations/OverviewRulerRenderer.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/browser/Decorations/OverviewRulerRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index a9439d9f..87ec3589 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -73,7 +73,7 @@ export class OverviewRulerRenderer extends Disposable { } else { this._canvas.style.left = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : ''; } - if (!decoration.options.overviewRulerOptions?.color) { + if (!decoration.options.overviewRulerOptions) { this._decorationElements.delete(decoration); return; } From 5b8777267a5566e7d52395e4008588241f2ee153 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 14:59:17 -0400 Subject: [PATCH 46/65] Update typings/xterm.d.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- typings/xterm.d.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 0703021a..193f2ab9 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -465,9 +465,7 @@ declare module 'xterm' { /** - * The width of the decoration in cells, which defaults to - * cell width or the width in pixels, when an overlayRulerItemColor - * is provided. + * The width of the decoration in cells, defaults to 1. */ width?: number; From 45ecc837e40740e3c01b0b787783d1380045c334 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 14:59:23 -0400 Subject: [PATCH 47/65] Update typings/xterm.d.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- typings/xterm.d.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 193f2ab9..05a1e786 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -470,8 +470,7 @@ declare module 'xterm' { width?: number; /** - * The height of the decoration in cells, which defaults to - * cell height + * The height of the decoration in cells, defaults to 1. */ height?: number; From b146ffd30f3a72652404dcefc221af456d1a93a2 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 14:59:43 -0400 Subject: [PATCH 48/65] Update src/browser/Decorations/OverviewRulerRenderer.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/browser/Decorations/OverviewRulerRenderer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 87ec3589..04d2cb47 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -79,7 +79,8 @@ export class OverviewRulerRenderer extends Disposable { } this._ctx.lineWidth = 1; this._ctx.strokeStyle = decoration.options.overviewRulerOptions.color; - const size = Math.floor(this._optionsService.options.overviewRulerWidth / 3); + const outerSize = Math.floor(this._optionsService.options.overviewRulerWidth / 3); + const innerSize = Math.ceil(this._optionsService.options.overviewRulerWidth / 3); const position = decoration.options.overviewRulerOptions.position; this._ctx.strokeRect( !position || position === 'left' ? 0 : position === 'right' ? size * 2 + 1: size, From fdc2bc49604280985bdf094905abab6421d59880 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 14:59:48 -0400 Subject: [PATCH 49/65] Update typings/xterm.d.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- typings/xterm.d.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 05a1e786..b7c89319 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -475,10 +475,11 @@ declare module 'xterm' { height?: number; /** - * Renders the decoration in the scrollbar - * with the given @param color and optional @param position. - * If @param position is not set, it will span the full @param overviewRulerWidth, which - * must be provided via @TerminalOptions for this to work. + * When defined, renders the decoration in the overview ruler to the right + * of the terminal. {@link ITerminalOptions.overviewRulerWidth} must be set + * in order to see the overview ruler. + * @param color The color of the decoration. + * @param position The position of the decoration. */ overviewRulerOptions?: { color: string; position?: 'left' | 'center' | 'right'} } From cee2411fb445885461fdac2e619c31db41b85155 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 15:00:05 -0400 Subject: [PATCH 50/65] more cleanup --- .../Decorations/OverviewRulerRenderer.ts | 29 ++++++++++--------- typings/xterm.d.ts | 12 ++++---- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index a9439d9f..54862fb0 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -9,8 +9,8 @@ import { Disposable } from 'common/Lifecycle'; import { IBufferService, IDecorationService, IInternalDecoration, IOptionsService } from 'common/services/Services'; export class OverviewRulerRenderer extends Disposable { - private _canvas: HTMLCanvasElement; - private _ctx: CanvasRenderingContext2D | null; + private readonly _canvas: HTMLCanvasElement; + private readonly _ctx: CanvasRenderingContext2D; private readonly _decorationElements: Map = new Map(); private get _width(): number { return this._optionsService.options.overviewRulerWidth || 0; @@ -29,23 +29,27 @@ export class OverviewRulerRenderer extends Disposable { this._canvas = document.createElement('canvas'); this._canvas.classList.add('xterm-decoration-overview-ruler'); this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement); - this._ctx = this._canvas.getContext('2d'); + const ctx = this._canvas.getContext('2d'); + if (!ctx) { + throw new Error('Ctx cannot be null'); + } else { + this._ctx = ctx; + } this._canvas.style.width = `${this._width}px`; this._canvas.style.height = `${this._screenElement.clientHeight}px`; this._canvas.width = Math.floor((this._width)* window.devicePixelRatio); this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); - this.refreshDecorations(); this.register(this._bufferService.buffers.onBufferActivate(() => { this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; })); - 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._decorationService.onDecorationRegistered(() => this._queueRefresh())); this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); this.register(this._optionsService.onOptionChange(o => { if (o === 'overviewRulerWidth') { - this.refreshDecorations(); + this._queueRefresh(); } })); } @@ -65,9 +69,6 @@ export class OverviewRulerRenderer extends Disposable { } private _refreshStyle(decoration: IInternalDecoration): void { - if (!this._ctx || !this._optionsService.options.overviewRulerWidth) { - return; - } if (decoration.options.anchor === 'right') { this._canvas.style.right = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : ''; } else { @@ -79,7 +80,7 @@ export class OverviewRulerRenderer extends Disposable { } this._ctx.lineWidth = 1; this._ctx.strokeStyle = decoration.options.overviewRulerOptions.color; - const size = Math.floor(this._optionsService.options.overviewRulerWidth / 3); + const size = Math.floor(this._width / 3); const position = decoration.options.overviewRulerOptions.position; this._ctx.strokeRect( !position || position === 'left' ? 0 : position === 'right' ? size * 2 + 1: size, @@ -89,7 +90,7 @@ export class OverviewRulerRenderer extends Disposable { ); } - public refreshDecorations(): void { + private _refreshDecorations(): void { this._canvas.style.width = `${this._width}px`; this._canvas.style.height = `${this._screenElement.clientHeight}px`; this._canvas.width = Math.floor((this._width)* window.devicePixelRatio); @@ -114,7 +115,7 @@ export class OverviewRulerRenderer extends Disposable { return; } this._animationFrame = window.requestAnimationFrame(() => { - this.refreshDecorations(); + this._refreshDecorations(); this._animationFrame = undefined; }); } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 0dce1ce6..e1c51934 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -450,18 +450,18 @@ declare module 'xterm' { * The line in the terminal where * the decoration will be displayed */ - marker: IMarker; + readonly marker: IMarker; /* * Where the decoration will be anchored - * defaults to the left edge */ - anchor?: 'right' | 'left'; + readonly anchor?: 'right' | 'left'; /** * The x position offset relative to the anchor */ - x?: number; + readonly x?: number; /** @@ -469,13 +469,13 @@ declare module 'xterm' { * cell width or the width in pixels, when an overlayRulerItemColor * is provided. */ - width?: number; + readonly width?: number; /** * The height of the decoration in cells, which defaults to * cell height */ - height?: number; + readonly height?: number; /** * Renders the decoration in the scrollbar @@ -483,7 +483,7 @@ declare module 'xterm' { * If @param position is not set, it will span the full @param overviewRulerWidth, which * must be provided via @TerminalOptions for this to work. */ - overviewRulerOptions?: { color: string; position?: 'left' | 'center' | 'right'} + readonly overviewRulerOptions?: { color: string; position?: 'left' | 'center' | 'right'} } /** From 4cc0db42734895b7bd82ac5d151c56af6c20076a Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 17:06:10 -0400 Subject: [PATCH 51/65] Update src/browser/Decorations/OverviewRulerRenderer.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/browser/Decorations/OverviewRulerRenderer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index d6ac4941..a797a746 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -122,8 +122,7 @@ export class OverviewRulerRenderer extends Disposable { } private _removeDecoration(decoration: IInternalDecoration): void { - const element = this._decorationElements.get(decoration); - element?.remove(); + this._decorationElements.get(decoration)?.remove(); this._decorationElements.delete(decoration); } } From e7baccdbcd200cd8da9cda970c1c3fb3ab4b1d2a Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 17:07:01 -0400 Subject: [PATCH 52/65] Update src/browser/Terminal.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/browser/Terminal.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index c2fb1500..98ed8b9a 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -82,11 +82,8 @@ export class Terminal extends CoreTerminal implements ITerminal { private _customKeyEventHandler: CustomKeyEventHandler | undefined; - // TODO: Move into CoreTerminal.ts - // common services - private _decorationService: DecorationService; - // browser services + private _decorationService: DecorationService; private _charSizeService: ICharSizeService | undefined; private _mouseService: IMouseService | undefined; private _renderService: IRenderService | undefined; From a9a6920b1303abb824abc9ae223ddc80221f19ef Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 17:08:01 -0400 Subject: [PATCH 53/65] more cleanup --- src/browser/Decorations/BufferDecorationRenderer.ts | 1 + typings/xterm.d.ts | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index 116c09a5..7cc2ee83 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -109,5 +109,6 @@ export class BufferDecorationRenderer extends Disposable { private _removeDecoration(decoration: IInternalDecoration): void { this._decorationElements.get(decoration)?.remove(); this._decorationElements.delete(decoration); + decoration.dispose(); } } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index a86d80c8..940e3a48 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -439,11 +439,8 @@ declare module 'xterm' { element: HTMLElement | undefined; } - /** - * Options provided when registering a decoration - * containing a @param marker, @param anchor, - * @param x offset from the anchor, @param width in cells - * and @param height in cells. + /* + * Options that define the presentation of the decoration. */ export interface IDecorationOptions { /** From 8ccbef850eeab0f94c829e71596835e4ab1f5da6 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 17:38:15 -0400 Subject: [PATCH 54/65] only update canvas dimensions when needed --- .../Decorations/BufferDecorationRenderer.ts | 1 - .../Decorations/OverviewRulerRenderer.ts | 27 +++++++++---------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index 7cc2ee83..116c09a5 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -109,6 +109,5 @@ export class BufferDecorationRenderer extends Disposable { private _removeDecoration(decoration: IInternalDecoration): void { this._decorationElements.get(decoration)?.remove(); this._decorationElements.delete(decoration); - decoration.dispose(); } } diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index a797a746..d6d450c3 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -35,16 +35,13 @@ export class OverviewRulerRenderer extends Disposable { } else { this._ctx = ctx; } - this._canvas.style.width = `${this._width}px`; - this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.width = Math.floor((this._width)* window.devicePixelRatio); - this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); + this._queueRefresh(true); this.register(this._bufferService.buffers.onBufferActivate(() => { this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; })); this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh())); - this.register(this._renderService.onDimensionsChange(() => this._queueRefresh())); - this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh())); + this.register(this._renderService.onDimensionsChange(() => this._queueRefresh(true))); + this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh(true))); this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh())); this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); this.register(this._optionsService.onOptionChange(o => { @@ -91,12 +88,14 @@ export class OverviewRulerRenderer extends Disposable { ); } - private _refreshDecorations(): void { - this._canvas.style.width = `${this._width}px`; - this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.width = Math.floor((this._width)* window.devicePixelRatio); - this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); - + private _refreshDecorations(updateCanvasDimensions?: boolean): void { + if (updateCanvasDimensions) { + this._canvas.style.width = `${this._width}px`; + this._canvas.style.height = `${this._screenElement.clientHeight}px`; + this._canvas.width = Math.floor((this._width)* window.devicePixelRatio); + this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); + } + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); for (const decoration of this._decorationService.decorations) { this._renderDecoration(decoration); } @@ -111,12 +110,12 @@ export class OverviewRulerRenderer extends Disposable { decoration.onRenderEmitter.fire(this._canvas); } - private _queueRefresh(): void { + private _queueRefresh(updateCanvasDimensions?: boolean): void { if (this._animationFrame !== undefined) { return; } this._animationFrame = window.requestAnimationFrame(() => { - this._refreshDecorations(); + this._refreshDecorations(updateCanvasDimensions); this._animationFrame = undefined; }); } From 53c432df272bbf65de0d2b9a70c47343260e3fce Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 17:58:44 -0400 Subject: [PATCH 55/65] add condition for when to update anchor --- demo/client.ts | 2 +- .../Decorations/OverviewRulerRenderer.ts | 53 ++++++++++++------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 047cd602..d8d792f1 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -563,7 +563,7 @@ function addOverviewRuler() { term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#8ae234', position: 'center' }}); term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#729fcf', position: 'right' }}); canvas.onRender((e) => { - e.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 5}px`; + e.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 1}px`; }); } diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index d6d450c3..865ac990 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -8,6 +8,16 @@ import { IRenderService } from 'browser/services/Services'; import { Disposable } from 'common/Lifecycle'; import { IBufferService, IDecorationService, IInternalDecoration, IOptionsService } from 'common/services/Services'; +// This is used to reduce memory usage +// when refreshStyle is called +// by storing and updating +// the sizes of the decorations to be drawn +const workArray = new Uint16Array(3); +const enum WorkIndex { + OUTER_SIZE = 0, + INNER_SIZE = 0 +} + export class OverviewRulerRenderer extends Disposable { private readonly _canvas: HTMLCanvasElement; private readonly _ctx: CanvasRenderingContext2D; @@ -40,15 +50,21 @@ export class OverviewRulerRenderer extends Disposable { this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; })); this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh())); - this.register(this._renderService.onDimensionsChange(() => this._queueRefresh(true))); + this.register(this._renderService.onDimensionsChange(() => this._queueRefresh(true, true))); this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh(true))); - this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh())); + this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh(undefined, true))); this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); this.register(this._optionsService.onOptionChange(o => { - if (o === 'overviewRulerWidth') { + if (o === 'overviewRulerWidth' && this._optionsService.options.overviewRulerWidth) { + workArray[WorkIndex.OUTER_SIZE] = Math.floor(this._optionsService.options.overviewRulerWidth / 3); + workArray[WorkIndex.INNER_SIZE] = Math.ceil(this._optionsService.options.overviewRulerWidth / 3); this._queueRefresh(); } })); + if (this._optionsService.options.overviewRulerWidth) { + workArray[WorkIndex.OUTER_SIZE] = Math.floor(this._optionsService.options.overviewRulerWidth / 3); + workArray[WorkIndex.INNER_SIZE] = Math.ceil(this._optionsService.options.overviewRulerWidth / 3); + } } public override dispose(): void { @@ -65,11 +81,13 @@ export class OverviewRulerRenderer extends Disposable { super.dispose(); } - private _refreshStyle(decoration: IInternalDecoration): void { - if (decoration.options.anchor === 'right') { - this._canvas.style.right = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : ''; - } else { - this._canvas.style.left = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : ''; + private _refreshStyle(decoration: IInternalDecoration, updateAnchor?: boolean): void { + if (updateAnchor) { + if (decoration.options.anchor === 'right') { + this._canvas.style.right = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : ''; + } else { + this._canvas.style.left = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : ''; + } } if (!decoration.options.overviewRulerOptions) { this._decorationElements.delete(decoration); @@ -77,18 +95,15 @@ export class OverviewRulerRenderer extends Disposable { } this._ctx.lineWidth = 1; this._ctx.strokeStyle = decoration.options.overviewRulerOptions.color; - const outerSize = Math.floor(this._width / 3); - const innerSize = Math.ceil(this._width / 3); - const position = decoration.options.overviewRulerOptions.position; this._ctx.strokeRect( - !position || position === 'left' ? 0 : position === 'right' ? outerSize + innerSize: outerSize, + !decoration.options.overviewRulerOptions.position || decoration.options.overviewRulerOptions.position === 'left' ? 0 : decoration.options.overviewRulerOptions.position === 'right' ? workArray[WorkIndex.OUTER_SIZE] + workArray[WorkIndex.INNER_SIZE]: workArray[WorkIndex.OUTER_SIZE], Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), - !position ? this._canvas.width : position === 'center' ? innerSize : outerSize, + !decoration.options.overviewRulerOptions.position ? this._canvas.width : decoration.options.overviewRulerOptions.position === 'center' ? workArray[WorkIndex.INNER_SIZE] : workArray[WorkIndex.OUTER_SIZE], window.devicePixelRatio ); } - private _refreshDecorations(updateCanvasDimensions?: boolean): void { + private _refreshDecorations(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void { if (updateCanvasDimensions) { this._canvas.style.width = `${this._width}px`; this._canvas.style.height = `${this._screenElement.clientHeight}px`; @@ -97,25 +112,25 @@ export class OverviewRulerRenderer extends Disposable { } this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); for (const decoration of this._decorationService.decorations) { - this._renderDecoration(decoration); + this._renderDecoration(decoration, updateAnchor); } } - private _renderDecoration(decoration: IInternalDecoration): void { + private _renderDecoration(decoration: IInternalDecoration, updateAnchor?: boolean): void { const element = this._decorationElements.get(decoration); if (!element) { this._decorationElements.set(decoration, this._canvas); } - this._refreshStyle(decoration); + this._refreshStyle(decoration, updateAnchor); decoration.onRenderEmitter.fire(this._canvas); } - private _queueRefresh(updateCanvasDimensions?: boolean): void { + private _queueRefresh(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void { if (this._animationFrame !== undefined) { return; } this._animationFrame = window.requestAnimationFrame(() => { - this._refreshDecorations(updateCanvasDimensions); + this._refreshDecorations(updateCanvasDimensions, updateAnchor); this._animationFrame = undefined; }); } From b05db37ac13590bd125ad4014dee84e5b0b789c3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 18:12:19 -0400 Subject: [PATCH 56/65] adjust size based on position --- demo/client.ts | 7 ++----- src/browser/Decorations/OverviewRulerRenderer.ts | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index d8d792f1..3d3a3c3f 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -555,15 +555,12 @@ function addDecoration() { } function addOverviewRuler() { - term.options['overviewRulerWidth'] = 13; - const canvas = term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: '#ef2929' }}); + term.options['overviewRulerWidth'] = 12; + term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: '#ef2929' }}); term.registerDecoration({marker: term.addMarker(3), overviewRulerOptions: { color: '#8ae234' }}); term.registerDecoration({marker: term.addMarker(5), overviewRulerOptions: { color: '#729fcf' }}); term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#ef2929', position: 'left' }}); term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#8ae234', position: 'center' }}); term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#729fcf', position: 'right' }}); - canvas.onRender((e) => { - e.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 1}px`; - }); } diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 865ac990..e22a3257 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -93,7 +93,7 @@ export class OverviewRulerRenderer extends Disposable { this._decorationElements.delete(decoration); return; } - this._ctx.lineWidth = 1; + this._ctx.lineWidth = !decoration.options.overviewRulerOptions.position ? 2 : 6; this._ctx.strokeStyle = decoration.options.overviewRulerOptions.color; this._ctx.strokeRect( !decoration.options.overviewRulerOptions.position || decoration.options.overviewRulerOptions.position === 'left' ? 0 : decoration.options.overviewRulerOptions.position === 'right' ? workArray[WorkIndex.OUTER_SIZE] + workArray[WorkIndex.INNER_SIZE]: workArray[WorkIndex.OUTER_SIZE], From d92e63648269f3df7efb0635e677e3b4c1e51866 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 18:12:48 -0400 Subject: [PATCH 57/65] Update src/browser/Terminal.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/browser/Terminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 98ed8b9a..d69e5d6f 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -583,7 +583,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this.onScroll(() => this._mouseZoneManager!.clearAll())); this.linkifier.attachToDom(this.element, this._mouseZoneManager); this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService); - this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement); + this.register(this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement)); // This event listener must be registered aftre MouseZoneManager is created this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e))); From 035a7f710ed59623572fd4983ab94492950da293 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 18:12:58 -0400 Subject: [PATCH 58/65] Update src/browser/Terminal.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/browser/Terminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index d69e5d6f..31ef1287 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -1016,7 +1016,7 @@ export class Terminal extends CoreTerminal implements ITerminal { } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - return this._decorationService!.registerDecoration(decorationOptions); + return this._decorationService.registerDecoration(decorationOptions); } /** From 5777fbc60809fbc271f7f24424c58017a87ee4b5 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 18:13:10 -0400 Subject: [PATCH 59/65] Update src/browser/tsconfig.json Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/browser/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/tsconfig.json b/src/browser/tsconfig.json index fc997477..212aeab8 100644 --- a/src/browser/tsconfig.json +++ b/src/browser/tsconfig.json @@ -18,7 +18,7 @@ "include": [ "./**/*", "../../typings/xterm.d.ts" -], + ], "references": [ { "path": "../common" } ] From 6f5008dba691b89943b468cc91321bb5d95d6af0 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 18:13:23 -0400 Subject: [PATCH 60/65] Update typings/xterm.d.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- typings/xterm.d.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 940e3a48..64ef2a08 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -268,7 +268,8 @@ declare module 'xterm' { windowOptions?: IWindowOptions; /** - * The width, in pixels, of the canvas for the overview ruler. + * The width, in pixels, of the canvas for the overview ruler. The overview + * ruler will be hidden when not set. */ overviewRulerWidth?: number; } From a4f830aa5d521cd8acfa8e9a920793806757f6fb Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 18:13:36 -0400 Subject: [PATCH 61/65] Update typings/xterm.d.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- typings/xterm.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 64ef2a08..24ba940e 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -400,7 +400,7 @@ declare module 'xterm' { } /** - * Represents a disposable with an + * Represents a disposable that tracks is disposed state. * @param onDispose event listener and * @param isDisposed property. */ From 22d889ab07ff0b879e6f4408eed5c5cdeb8b088a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 19:15:59 -0400 Subject: [PATCH 62/65] use this._width instead of options --- .../Decorations/OverviewRulerRenderer.ts | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index e22a3257..bffdbb66 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -12,8 +12,8 @@ import { IBufferService, IDecorationService, IInternalDecoration, IOptionsServic // when refreshStyle is called // by storing and updating // the sizes of the decorations to be drawn -const workArray = new Uint16Array(3); -const enum WorkIndex { +const renderSizes = new Uint16Array(3); +const enum SizeIndex { OUTER_SIZE = 0, INNER_SIZE = 0 } @@ -55,16 +55,14 @@ export class OverviewRulerRenderer extends Disposable { this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh(undefined, true))); this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); this.register(this._optionsService.onOptionChange(o => { - if (o === 'overviewRulerWidth' && this._optionsService.options.overviewRulerWidth) { - workArray[WorkIndex.OUTER_SIZE] = Math.floor(this._optionsService.options.overviewRulerWidth / 3); - workArray[WorkIndex.INNER_SIZE] = Math.ceil(this._optionsService.options.overviewRulerWidth / 3); + if (o === 'overviewRulerWidth') { + renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._width / 3); + renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._width / 3); this._queueRefresh(); } })); - if (this._optionsService.options.overviewRulerWidth) { - workArray[WorkIndex.OUTER_SIZE] = Math.floor(this._optionsService.options.overviewRulerWidth / 3); - workArray[WorkIndex.INNER_SIZE] = Math.ceil(this._optionsService.options.overviewRulerWidth / 3); - } + renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._width / 3); + renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._width / 3); } public override dispose(): void { @@ -95,10 +93,11 @@ export class OverviewRulerRenderer extends Disposable { } this._ctx.lineWidth = !decoration.options.overviewRulerOptions.position ? 2 : 6; this._ctx.strokeStyle = decoration.options.overviewRulerOptions.color; + this._ctx.strokeRect( - !decoration.options.overviewRulerOptions.position || decoration.options.overviewRulerOptions.position === 'left' ? 0 : decoration.options.overviewRulerOptions.position === 'right' ? workArray[WorkIndex.OUTER_SIZE] + workArray[WorkIndex.INNER_SIZE]: workArray[WorkIndex.OUTER_SIZE], + !decoration.options.overviewRulerOptions.position || decoration.options.overviewRulerOptions.position === 'left' ? 0 : decoration.options.overviewRulerOptions.position === 'right' ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE]: renderSizes[SizeIndex.OUTER_SIZE], Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), - !decoration.options.overviewRulerOptions.position ? this._canvas.width : decoration.options.overviewRulerOptions.position === 'center' ? workArray[WorkIndex.INNER_SIZE] : workArray[WorkIndex.OUTER_SIZE], + !decoration.options.overviewRulerOptions.position ? this._width : decoration.options.overviewRulerOptions.position === 'center' ? renderSizes[SizeIndex.INNER_SIZE] : renderSizes[SizeIndex.OUTER_SIZE], window.devicePixelRatio ); } From 963ed594591d21bdfa0fa95ae4e61c677d64c4da Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 19:29:33 -0400 Subject: [PATCH 63/65] tweak demo --- demo/client.ts | 2 +- src/browser/Decorations/OverviewRulerRenderer.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 3d3a3c3f..bbec94c0 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -555,7 +555,7 @@ function addDecoration() { } function addOverviewRuler() { - term.options['overviewRulerWidth'] = 12; + term.options['overviewRulerWidth'] = 15; term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: '#ef2929' }}); term.registerDecoration({marker: term.addMarker(3), overviewRulerOptions: { color: '#8ae234' }}); term.registerDecoration({marker: term.addMarker(5), overviewRulerOptions: { color: '#729fcf' }}); diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index bffdbb66..a46f9872 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -93,7 +93,6 @@ export class OverviewRulerRenderer extends Disposable { } this._ctx.lineWidth = !decoration.options.overviewRulerOptions.position ? 2 : 6; this._ctx.strokeStyle = decoration.options.overviewRulerOptions.color; - this._ctx.strokeRect( !decoration.options.overviewRulerOptions.position || decoration.options.overviewRulerOptions.position === 'left' ? 0 : decoration.options.overviewRulerOptions.position === 'right' ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE]: renderSizes[SizeIndex.OUTER_SIZE], Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), From c277fb742d03be07b87d29a519fb5dee41e158c8 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 20:50:13 -0400 Subject: [PATCH 64/65] add overviewRuler to addDecoration --- demo/client.ts | 4 +++- src/browser/Decorations/OverviewRulerRenderer.ts | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index bbec94c0..b8023f27 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -545,13 +545,15 @@ function loadTest() { } function addDecoration() { + term.options['overviewRulerWidth'] = 15; const marker = term.addMarker(1); const decoration = term.registerDecoration({ marker }); decoration.onRender((e) => { if (e.classList.value === 'xterm-decoration') { - e.style.backgroundColor = 'red'; + e.style.backgroundColor = '#ef2929'; } }); + term.registerDecoration({marker, overviewRulerOptions: { color: '#ef2929'}}) } function addOverviewRuler() { diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index a46f9872..9f8ada32 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -61,6 +61,7 @@ export class OverviewRulerRenderer extends Disposable { this._queueRefresh(); } })); + console.log(this._width/3); renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._width / 3); renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._width / 3); } @@ -96,7 +97,7 @@ export class OverviewRulerRenderer extends Disposable { this._ctx.strokeRect( !decoration.options.overviewRulerOptions.position || decoration.options.overviewRulerOptions.position === 'left' ? 0 : decoration.options.overviewRulerOptions.position === 'right' ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE]: renderSizes[SizeIndex.OUTER_SIZE], Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), - !decoration.options.overviewRulerOptions.position ? this._width : decoration.options.overviewRulerOptions.position === 'center' ? renderSizes[SizeIndex.INNER_SIZE] : renderSizes[SizeIndex.OUTER_SIZE], + !decoration.options.overviewRulerOptions.position ? this._width : decoration.options.overviewRulerOptions.position === 'center' ? renderSizes[SizeIndex.INNER_SIZE]: renderSizes[SizeIndex.OUTER_SIZE], window.devicePixelRatio ); } From a0a7abbf497b3af8439e3d801e9b01dd6095d45f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 21:05:14 -0400 Subject: [PATCH 65/65] add overview options to decoration in demo --- demo/client.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index b8023f27..2ae649e4 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -547,13 +547,12 @@ function loadTest() { function addDecoration() { term.options['overviewRulerWidth'] = 15; const marker = term.addMarker(1); - const decoration = term.registerDecoration({ marker }); + const decoration = term.registerDecoration({ marker, overviewRulerOptions: { color: '#ef2929'} }); decoration.onRender((e) => { if (e.classList.value === 'xterm-decoration') { e.style.backgroundColor = '#ef2929'; } }); - term.registerDecoration({marker, overviewRulerOptions: { color: '#ef2929'}}) } function addOverviewRuler() {