From c5810e6d545f39b00f9e5100cd24e10406b05e92 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 27 Jan 2022 13:20:02 -0600 Subject: [PATCH 01/59] Take 1 of api --- typings/xterm.d.ts | 70 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 39245afe..83c1d10c 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -405,6 +405,69 @@ declare module 'xterm' { onDispose: IEvent; } + /** + * Represents a decoration in the terminal that is associated with a particular marker. + */ + export interface IDecoration extends IDisposable { + /** + * Whether this decoration is disposed. + */ + readonly isDisposed: boolean; + + /** + * The actual line index in the buffer at this point in time. This is set to + * -1 if the decoration has been disposed. + */ + readonly line: number; + + /** + * An event fired when the decoration + * is rendered, returns the dom element + * associated with the decoration. + */ + onRender: IEvent; + } + + export interface IDecorationOptions extends IDisposable { + /** + * The line in the terminal where + * the decoration will be displayed + */ + startMarker: IMarker; + + /** + * The number of milliseconds the decoration + * should be displayed for. + */ + displayDuration?: number; + + /** + * The color of the decoration + */ + color?: string + } + + export interface IBufferDecorationOptions extends IDecorationOptions { + /** + * The type of buffer decoration + */ + type: 'button' | 'box-border'; + + /* + * The x position for the decoration. + * Defaults to the right edge. + */ + position?: number; + } + + export interface IGutterDecorationOptions extends IDecorationOptions { + /** + * The end line in the terminal for + * the decoration + */ + endMarker: IMarker; + } + /** * The set of localizable strings. */ @@ -870,6 +933,13 @@ declare module 'xterm' { */ addMarker(cursorYOffset: number): IMarker | undefined; + /** + * (EXPERIMENTAL) Adds a decoration as configured with @param decorationOptions to the + * normal buffer or gutter and returns it. + * If the alt buffer is active or the decoration is invalid, undefined is returned. + */ + registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration | undefined; + /** * Gets whether the terminal has an active selection. */ From ebdd07b8ae0e34027409c7e4bbb6d6b83e4e4100 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Sat, 29 Jan 2022 00:07:51 -0600 Subject: [PATCH 02/59] get button to work --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 14 +- src/browser/Terminal.ts | 5 +- src/browser/TestUtils.test.ts | 11 +- src/browser/Types.d.ts | 3 +- src/browser/public/Terminal.ts | 6 +- src/browser/renderer/DecorationRenderLayer.ts | 128 ++++++++++++++++++ src/browser/renderer/Renderer.ts | 13 +- src/browser/renderer/Types.d.ts | 2 + src/browser/renderer/dom/DomRenderer.ts | 10 ++ src/browser/services/RenderService.ts | 5 + src/browser/services/Services.ts | 3 +- typings/xterm.d.ts | 9 +- 12 files changed, 199 insertions(+), 10 deletions(-) create mode 100644 src/browser/renderer/DecorationRenderLayer.ts diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index af1591a8..b1aa03ec 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -13,7 +13,7 @@ import { IWebGL2RenderingContext } from './Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; import { Disposable } from 'common/Lifecycle'; import { Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; -import { Terminal, IEvent } from 'xterm'; +import { Terminal, IEvent, IBufferDecorationOptions, IDecoration, IGutterDecorationOptions } from 'xterm'; import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; import { ITerminal, IColorSet } from 'browser/Types'; @@ -23,6 +23,8 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { ICharacterJoinerService } from 'browser/services/Services'; import { CharData, ICellData } from 'common/Types'; import { AttributeData } from 'common/buffer/AttributeData'; +import { DecorationRenderLayer } from 'browser/renderer/DecorationRenderLayer'; +import { IBufferService } from 'common/services/Services'; export class WebglRenderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; @@ -57,10 +59,10 @@ export class WebglRenderer extends Disposable implements IRenderer { super(); this._core = (this._terminal as any)._core; - this._renderLayers = [ new LinkRenderLayer(this._core.screenElement!, 2, this._colors, this._core), new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._colors, this._core, this._onRequestRedraw) + // new DecorationRenderLayer(this._core.screenElement!, 3, this._colors, this._id, this._onRequestRedraw) ]; this.dimensions = { scaledCharWidth: 0, @@ -116,6 +118,14 @@ export class WebglRenderer extends Disposable implements IRenderer { return this._charAtlas?.cacheCanvas; } + public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration { + const decorationLayer = this._renderLayers.find(l => l instanceof DecorationRenderLayer); + if (decorationLayer instanceof DecorationRenderLayer) { + return decorationLayer.registerDecoration(decorationOptions); + } + throw new Error('no decoration layer'); + } + public setColors(colors: IColorSet): void { this._colors = colors; // Clear layers and force a full render diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index fe5c7f79..fb26a2eb 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -37,7 +37,7 @@ import * as Strings from 'browser/LocalizableStrings'; import { SoundService } from 'browser/services/SoundService'; import { MouseZoneManager } from 'browser/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; -import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider } from 'xterm'; +import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider, IBufferDecorationOptions, IDecoration, IGutterDecorationOptions } from 'xterm'; import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; import { KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, ColorRequestType } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; @@ -998,6 +998,9 @@ export class Terminal extends CoreTerminal implements ITerminal { return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset); } + public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration | undefined { + return this._renderService?.registerDecoration(decorationOptions); + } /** * Gets whether the terminal has an active selection. */ diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 8fdf458e..c42e4a55 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IDisposable, IMarker, ISelectionPosition, ILinkProvider } from 'xterm'; +import { IDisposable, IMarker, ISelectionPosition, ILinkProvider, IBufferDecorationOptions, IDecoration, IGutterDecorationOptions, IDecorationOptions } from 'xterm'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { ICharacterJoinerService, ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; @@ -102,6 +102,9 @@ export class MockTerminal implements ITerminal { public registerLinkProvider(linkProvider: ILinkProvider): IDisposable { throw new Error('Method not implemented.'); } + public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration | undefined { + throw new Error('Method not implemented.'); + } public hasSelection(): boolean { throw new Error('Method not implemented.'); } @@ -290,6 +293,9 @@ export class MockRenderer implements IRenderer { public onDevicePixelRatioChange(): void { } public clear(): void { } public renderRows(start: number, end: number): void { } + public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration { + throw new Error('Method not implemented.'); + } } export class MockViewport implements IViewport { @@ -419,6 +425,9 @@ export class MockRenderService implements IRenderService { public dispose(): void { throw new Error('Method not implemented.'); } + public registerDecoration(decorationOptions: IDecorationOptions): IDecoration { + throw new Error('Method not implemented.'); + } } export class MockCharacterJoinerService implements ICharacterJoinerService { diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index a6165840..779a9454 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IDisposable, IMarker, ISelectionPosition } from 'xterm'; +import { IBufferDecorationOptions, IDecoration, IDisposable, IGutterDecorationOptions, IMarker, ISelectionPosition } from 'xterm'; import { IEvent } from 'common/EventEmitter'; import { ICoreTerminal, CharData, ITerminalOptions } from 'common/Types'; import { IMouseService, IRenderService } from './services/Services'; @@ -61,6 +61,7 @@ export interface IPublicTerminal extends IDisposable { registerCharacterJoiner(handler: (text: string) => [number, number][]): number; deregisterCharacterJoiner(joinerId: number): void; addMarker(cursorYOffset: number): IMarker | undefined; + registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration | undefined; hasSelection(): boolean; getSelection(): string; getSelectionPosition(): ISelectionPosition | undefined; diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 117805f9..1f3c32fe 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal as ITerminalApi, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes } from 'xterm'; +import { Terminal as ITerminalApi, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes, IBufferDecorationOptions, IDecoration, IGutterDecorationOptions } from 'xterm'; import { ITerminal } from 'browser/Types'; import { Terminal as TerminalCore } from 'browser/Terminal'; import * as Strings from 'browser/LocalizableStrings'; @@ -171,6 +171,10 @@ export class Terminal implements ITerminalApi { this._verifyIntegers(cursorYOffset); return this._core.addMarker(cursorYOffset); } + public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration | undefined { + this._checkProposedApi(); + return this._core.registerDecoration(decorationOptions); + } public addMarker(cursorYOffset: number): IMarker | undefined { return this.registerMarker(cursorYOffset); } diff --git a/src/browser/renderer/DecorationRenderLayer.ts b/src/browser/renderer/DecorationRenderLayer.ts new file mode 100644 index 00000000..fd5d32da --- /dev/null +++ b/src/browser/renderer/DecorationRenderLayer.ts @@ -0,0 +1,128 @@ +/** + * Copyright (c) 2022 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { addDisposableDomListener } from 'browser/Lifecycle'; +import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer'; +import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types'; +import { IColorSet } from 'browser/Types'; +import { CellData } from 'common/buffer/CellData'; +import { Marker } from 'common/buffer/Marker'; +import { EventEmitter, IEventEmitter } from 'common/EventEmitter'; +import { Disposable } from 'common/Lifecycle'; +import { IBufferService, IOptionsService } from 'common/services/Services'; +import { ICellData } from 'common/Types'; +import { IBufferDecorationOptions, IDecoration, IEvent, IGutterDecorationOptions } from 'xterm'; + +const enum DefaultButton { + WIDTH = 2, + HEIGHT = 1, + COLS = 87, + MARGIN_RIGHT = 3, + COLOR = '#4B9CD3' +} +export class DecorationRenderLayer extends BaseRenderLayer { + private _cell: ICellData = new CellData(); + constructor( + container: HTMLElement, + zIndex: number, + colors: IColorSet, + rendererId: number, + private _onRequestRedraw: IEventEmitter, + @IBufferService bufferService: IBufferService, + @IOptionsService optionsService: IOptionsService + ) { + super(container, 'decoration', zIndex, true, colors, rendererId, bufferService, optionsService); + this.onFocus(); + } + public reset(): void { + + } + + public override onFocus(): void { + this.registerDecoration({ type: 'IBufferDecorationOptions', startMarker: new Marker(1), shape: 'button' }); + this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y }); + } + + public override resize(dim: IRenderDimensions): void { + super.resize(dim); + this._clearCells(this._bufferService.cols - DefaultButton.MARGIN_RIGHT, 1, 2, 1); + this.registerDecoration({ type: 'IBufferDecorationOptions', startMarker: new Marker(1), shape: 'button' }); + this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y }); + } + + public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration { + if (decorationOptions.type === 'IBufferDecorationOptions') { + const bufferDecoration = new BufferDecoration(decorationOptions.startMarker.line, this._ctx.canvas); + if ('shape' in decorationOptions && decorationOptions.shape === 'button') { + this._ctx.save(); + const color = decorationOptions.color || DefaultButton.COLOR; + const x = 'position' in decorationOptions && decorationOptions.position ? decorationOptions.position : this._bufferService.cols - ((this._bufferService.cols/DefaultButton.COLS) * DefaultButton.MARGIN_RIGHT); + if (x && color) { + this._ctx.fillStyle = color; + this._fillCells(x, decorationOptions.startMarker.line, DefaultButton.WIDTH, DefaultButton.HEIGHT); + this._ctx.fillStyle = color; + this._fillCharTrueColor(this._cell, x, decorationOptions.startMarker.line); + addDisposableDomListener(this._ctx.canvas, 'click', e => { + e.stopPropagation(); + const { x, y } = getRelativeClickPosition(this._ctx.canvas, e); + if (this._ctx.isPointInPath(x, y)) { + console.log('clicked button'); + } + }); + } + this._ctx.restore(); + return bufferDecoration; + } + throw new Error('Border box type not yet implemented'); + } throw new Error('Gutter decoration not yet implemented'); + } +} + +function getRelativeClickPosition(canvas: HTMLCanvasElement, event: MouseEvent): { x: number, y: number } { + const rect = canvas.getBoundingClientRect(); + const y = event.clientY - rect.top; + const x = event.clientX - rect.left; + return { x, y }; +} + +export class BufferDecoration extends Disposable implements IDecoration { + private static _nextId = 1; + + private _element: HTMLElement | undefined; + private _id: number = BufferDecoration._nextId++; + public isDisposed: boolean = false; + + public get id(): number { return this._id; } + + public get element(): HTMLElement { return this.element!; } + + 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( + public line: number, + container: HTMLElement + ) { + super(); + this._element = document.createElement('menu'); + this._element.textContent = 'hello'; + this._element.id = 'decoration' + this._id; + container.appendChild(this._element); + } + + public dispose(): void { + if (this.isDisposed) { + return; + } + this.isDisposed = true; + this.line = -1; + // Emit before super.dispose such that dispose listeners get a change to react + this._onDispose.fire(); + super.dispose(); + } +} diff --git a/src/browser/renderer/Renderer.ts b/src/browser/renderer/Renderer.ts index 7a64257d..4829e0bb 100644 --- a/src/browser/renderer/Renderer.ts +++ b/src/browser/renderer/Renderer.ts @@ -14,6 +14,8 @@ import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services import { IBufferService, IOptionsService, ICoreService, IInstantiationService } from 'common/services/Services'; import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache'; import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { DecorationRenderLayer } from 'browser/renderer/DecorationRenderLayer'; +import { IBufferDecorationOptions, IGutterDecorationOptions, IDecoration } from 'xterm'; let nextRendererId = 1; @@ -44,7 +46,8 @@ export class Renderer extends Disposable implements IRenderer { instantiationService.createInstance(TextRenderLayer, this._screenElement, 0, this._colors, allowTransparency, this._id), instantiationService.createInstance(SelectionRenderLayer, this._screenElement, 1, this._colors, this._id), instantiationService.createInstance(LinkRenderLayer, this._screenElement, 2, this._colors, this._id, linkifier, linkifier2), - instantiationService.createInstance(CursorRenderLayer, this._screenElement, 3, this._colors, this._id, this._onRequestRedraw) + instantiationService.createInstance(CursorRenderLayer, this._screenElement, 3, this._colors, this._id, this._onRequestRedraw), + instantiationService.createInstance(DecorationRenderLayer, this._screenElement, 4, this._colors, this._id, this._onRequestRedraw) ]; this.dimensions = { scaledCharWidth: 0, @@ -65,6 +68,14 @@ export class Renderer extends Disposable implements IRenderer { this.onOptionsChanged(); } + public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration { + const decorationLayer = this._renderLayers.find(l => l instanceof DecorationRenderLayer); + if (decorationLayer instanceof DecorationRenderLayer) { + return decorationLayer.registerDecoration(decorationOptions); + } + throw new Error('no decoration layer'); + } + public dispose(): void { for (const l of this._renderLayers) { l.dispose(); diff --git a/src/browser/renderer/Types.d.ts b/src/browser/renderer/Types.d.ts index 6818a926..8a328686 100644 --- a/src/browser/renderer/Types.d.ts +++ b/src/browser/renderer/Types.d.ts @@ -6,6 +6,7 @@ import { IDisposable } from 'common/Types'; import { IColorSet } from 'browser/Types'; import { IEvent } from 'common/EventEmitter'; +import { IBufferDecorationOptions, IDecoration, IGutterDecorationOptions } from 'xterm'; export interface IRenderDimensions { scaledCharWidth: number; @@ -53,6 +54,7 @@ export interface IRenderer extends IDisposable { clear(): void; renderRows(start: number, end: number): void; clearTextureAtlas?(): void; + registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration; } export interface IRenderLayer extends IDisposable { diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index ee283399..58c54f65 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -13,6 +13,8 @@ import { IOptionsService, IBufferService, IInstantiationService } from 'common/s import { EventEmitter, IEvent } from 'common/EventEmitter'; import { color } from 'browser/Color'; import { removeElementFromParent } from 'browser/Dom'; +import { DecorationRenderLayer } from 'browser/renderer/DecorationRenderLayer'; +import { IBufferDecorationOptions, IGutterDecorationOptions, IDecoration } from 'xterm'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; @@ -151,6 +153,14 @@ export class DomRenderer extends Disposable implements IRenderer { this._injectCss(); } + public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration { + // const decorationLayer = this._renderLayers.find(l => l instanceof DecorationRenderLayer); + // if (decorationLayer instanceof DecorationRenderLayer) { + // return decorationLayer.registerDecoration(decorationOptions); + // } + throw new Error('no decoration layer'); + } + private _injectCss(): void { if (!this._themeStyleElement) { this._themeStyleElement = document.createElement('style'); diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index b8283e0e..526bffa3 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -12,6 +12,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { IColorSet, IRenderDebouncer } from 'browser/Types'; import { IOptionsService, IBufferService } from 'common/services/Services'; import { ICharSizeService, IRenderService } from 'browser/services/Services'; +import { IDecorationOptions, IDecoration, IGutterDecorationOptions, IBufferDecorationOptions } from 'xterm'; interface ISelectionState { start: [number, number] | undefined; @@ -85,6 +86,10 @@ export class RenderService extends Disposable implements IRenderService { } } + public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration { + return this._renderer.registerDecoration(decorationOptions); + } + private _onIntersectionChange(entry: IntersectionObserverEntry): void { this._isPaused = entry.isIntersecting === undefined ? (entry.intersectionRatio === 0) : !entry.isIntersecting; diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 4928fa28..c3ec2eb5 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -9,6 +9,7 @@ import { IColorSet } from 'browser/Types'; import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; import { IDisposable } from 'common/Types'; +import { IDecoration, IDecorationOptions } from 'xterm'; export const ICharSizeService = createDecorator('CharSizeService'); export interface ICharSizeService { @@ -51,7 +52,7 @@ export interface IRenderService extends IDisposable { onRefreshRequest: IEvent<{ start: number, end: number }>; dimensions: IRenderDimensions; - + registerDecoration(decorationOptions: IDecorationOptions): IDecoration; refreshRows(start: number, end: number): void; clearTextureAtlas(): void; resize(cols: number, rows: number): void; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 83c1d10c..6b839299 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -428,7 +428,12 @@ declare module 'xterm' { onRender: IEvent; } - export interface IDecorationOptions extends IDisposable { + export interface IDecorationOptions { + /** + * The type of decoration options + */ + type: 'IBufferDecorationOptions' | 'IGutterDecorationOptions'; + /** * The line in the terminal where * the decoration will be displayed @@ -451,7 +456,7 @@ declare module 'xterm' { /** * The type of buffer decoration */ - type: 'button' | 'box-border'; + shape: 'button' | 'box-border'; /* * The x position for the decoration. From 00ce85166e2b27ef530cff3f8e9124ff366f5680 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Sat, 29 Jan 2022 22:34:16 -0600 Subject: [PATCH 03/59] better button --- demo/style.css | 6 ++++ src/browser/renderer/BaseRenderLayer.ts | 2 ++ src/browser/renderer/DecorationRenderLayer.ts | 32 ++++++++----------- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/demo/style.css b/demo/style.css index cebd08e0..d490d953 100644 --- a/demo/style.css +++ b/demo/style.css @@ -76,6 +76,12 @@ pre { background-color: #ddd; } +.decoration:hover, +.decoration:focus { + box-shadow: 0 0.5em 0.5em -0.4em var(--hover); + transform: translateY(-0.25em); +} + /* Create an active/current tablink class */ .tab button.active { background-color: #ccc; diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 629e9436..69f0efc2 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -150,6 +150,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param height The number of rows to fill. */ protected _fillCells(x: number, y: number, width: number, height: number): void { + console.log(x*this._scaledCellWidth); + console.log(y*this._scaledCellHeight); this._ctx.fillRect( x * this._scaledCellWidth, y * this._scaledCellHeight, diff --git a/src/browser/renderer/DecorationRenderLayer.ts b/src/browser/renderer/DecorationRenderLayer.ts index fd5d32da..e6c6342b 100644 --- a/src/browser/renderer/DecorationRenderLayer.ts +++ b/src/browser/renderer/DecorationRenderLayer.ts @@ -47,31 +47,18 @@ export class DecorationRenderLayer extends BaseRenderLayer { public override resize(dim: IRenderDimensions): void { super.resize(dim); - this._clearCells(this._bufferService.cols - DefaultButton.MARGIN_RIGHT, 1, 2, 1); + this._clearCells(this._bufferService.cols - DefaultButton.MARGIN_RIGHT, 1, 1, 1); this.registerDecoration({ type: 'IBufferDecorationOptions', startMarker: new Marker(1), shape: 'button' }); this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y }); } public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration { if (decorationOptions.type === 'IBufferDecorationOptions') { - const bufferDecoration = new BufferDecoration(decorationOptions.startMarker.line, this._ctx.canvas); + const color = decorationOptions.color || DefaultButton.COLOR; + const bufferDecoration = new BufferDecoration(decorationOptions.startMarker.line, color, this._ctx.canvas); if ('shape' in decorationOptions && decorationOptions.shape === 'button') { this._ctx.save(); - const color = decorationOptions.color || DefaultButton.COLOR; const x = 'position' in decorationOptions && decorationOptions.position ? decorationOptions.position : this._bufferService.cols - ((this._bufferService.cols/DefaultButton.COLS) * DefaultButton.MARGIN_RIGHT); - if (x && color) { - this._ctx.fillStyle = color; - this._fillCells(x, decorationOptions.startMarker.line, DefaultButton.WIDTH, DefaultButton.HEIGHT); - this._ctx.fillStyle = color; - this._fillCharTrueColor(this._cell, x, decorationOptions.startMarker.line); - addDisposableDomListener(this._ctx.canvas, 'click', e => { - e.stopPropagation(); - const { x, y } = getRelativeClickPosition(this._ctx.canvas, e); - if (this._ctx.isPointInPath(x, y)) { - console.log('clicked button'); - } - }); - } this._ctx.restore(); return bufferDecoration; } @@ -106,13 +93,22 @@ export class BufferDecoration extends Disposable implements IDecoration { constructor( public line: number, + color: string, container: HTMLElement ) { super(); this._element = document.createElement('menu'); - this._element.textContent = 'hello'; + this._element.classList.add('decoration'); this._element.id = 'decoration' + this._id; - container.appendChild(this._element); + this._element.style.background = color; + this._element.style.width = '1px'; + this._element.style.height = '32px'; + this._element.style.borderRadius = '64px'; + this._element.style.border = `4px solid white`; + this._element.style.zIndex = '6'; + this._element.style.position = 'absolute'; + addDisposableDomListener(this._element, 'click', e => console.log('circle')); + container.parentElement!.append(this._element); } public dispose(): void { From f9ac540031bc3cacd298d0cb0814e8d2a838fbb6 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Sun, 30 Jan 2022 00:28:01 -0600 Subject: [PATCH 04/59] polish --- src/browser/renderer/BaseRenderLayer.ts | 2 - src/browser/renderer/DecorationRenderLayer.ts | 83 ++++++++----------- typings/xterm.d.ts | 4 - 3 files changed, 34 insertions(+), 55 deletions(-) diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 69f0efc2..629e9436 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -150,8 +150,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param height The number of rows to fill. */ protected _fillCells(x: number, y: number, width: number, height: number): void { - console.log(x*this._scaledCellWidth); - console.log(y*this._scaledCellHeight); this._ctx.fillRect( x * this._scaledCellWidth, y * this._scaledCellHeight, diff --git a/src/browser/renderer/DecorationRenderLayer.ts b/src/browser/renderer/DecorationRenderLayer.ts index e6c6342b..29db0d27 100644 --- a/src/browser/renderer/DecorationRenderLayer.ts +++ b/src/browser/renderer/DecorationRenderLayer.ts @@ -23,7 +23,6 @@ const enum DefaultButton { COLOR = '#4B9CD3' } export class DecorationRenderLayer extends BaseRenderLayer { - private _cell: ICellData = new CellData(); constructor( container: HTMLElement, zIndex: number, @@ -34,56 +33,34 @@ export class DecorationRenderLayer extends BaseRenderLayer { @IOptionsService optionsService: IOptionsService ) { super(container, 'decoration', zIndex, true, colors, rendererId, bufferService, optionsService); - this.onFocus(); + this.registerDecoration({ startMarker: new Marker(1), shape: 'button' }); + this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y }); } public reset(): void { } - public override onFocus(): void { - this.registerDecoration({ type: 'IBufferDecorationOptions', startMarker: new Marker(1), shape: 'button' }); - this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y }); - } - - public override resize(dim: IRenderDimensions): void { - super.resize(dim); - this._clearCells(this._bufferService.cols - DefaultButton.MARGIN_RIGHT, 1, 1, 1); - this.registerDecoration({ type: 'IBufferDecorationOptions', startMarker: new Marker(1), shape: 'button' }); - this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y }); - } - public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration { - if (decorationOptions.type === 'IBufferDecorationOptions') { - const color = decorationOptions.color || DefaultButton.COLOR; - const bufferDecoration = new BufferDecoration(decorationOptions.startMarker.line, color, this._ctx.canvas); - if ('shape' in decorationOptions && decorationOptions.shape === 'button') { - this._ctx.save(); - const x = 'position' in decorationOptions && decorationOptions.position ? decorationOptions.position : this._bufferService.cols - ((this._bufferService.cols/DefaultButton.COLS) * DefaultButton.MARGIN_RIGHT); - this._ctx.restore(); - return bufferDecoration; - } - throw new Error('Border box type not yet implemented'); - } throw new Error('Gutter decoration not yet implemented'); + if ('shape' in decorationOptions) { + return new BufferDecoration(decorationOptions, this._ctx.canvas); + } + throw new Error('Gutter decoration not yet implemented'); } } -function getRelativeClickPosition(canvas: HTMLCanvasElement, event: MouseEvent): { x: number, y: number } { - const rect = canvas.getBoundingClientRect(); - const y = event.clientY - rect.top; - const x = event.clientX - rect.left; - return { x, y }; -} - -export class BufferDecoration extends Disposable implements IDecoration { +class BufferDecoration extends Disposable implements IDecoration { private static _nextId = 1; private _element: HTMLElement | undefined; private _id: number = BufferDecoration._nextId++; + private _line: number; public isDisposed: boolean = false; public get id(): number { return this._id; } - public get element(): HTMLElement { return this.element!; } + public get line(): number { return this._line; } + + public get element(): HTMLElement { return this._element!; } private _onDispose = new EventEmitter(); public get onDispose(): IEvent { return this._onDispose.event; } @@ -92,23 +69,31 @@ export class BufferDecoration extends Disposable implements IDecoration { public get onRender(): IEvent { return this._onRender.event; } constructor( - public line: number, - color: string, + decorationOptions: IBufferDecorationOptions, container: HTMLElement ) { super(); - this._element = document.createElement('menu'); - this._element.classList.add('decoration'); - this._element.id = 'decoration' + this._id; - this._element.style.background = color; - this._element.style.width = '1px'; - this._element.style.height = '32px'; - this._element.style.borderRadius = '64px'; - this._element.style.border = `4px solid white`; - this._element.style.zIndex = '6'; - this._element.style.position = 'absolute'; - addDisposableDomListener(this._element, 'click', e => console.log('circle')); - container.parentElement!.append(this._element); + this._line = decorationOptions.startMarker.line; + if (decorationOptions.shape === 'button') { + const color = decorationOptions.color || DefaultButton.COLOR; + this._element = document.createElement('menu'); + this._element.classList.add('button-buffer-decoration'); + this._element.id = 'button-buffer-decoration-' + this._id; + this._element.style.background = color; + this._element.style.width = '1px'; + this._element.style.height = '32px'; + this._element.style.borderRadius = '64px'; + this._element.style.border = `4px solid white`; + this._element.style.zIndex = '6'; + this._element.style.position = 'absolute'; + this._element.style.top = '0px'; + this._element.style.right = '5px'; + addDisposableDomListener(this._element, 'click', e => console.log('circle')); + container.parentElement!.append(this._element); + this._onRender.fire(this._element); + } else { + throw new Error('only shape that has been implemented so far is button'); + } } public dispose(): void { @@ -116,7 +101,7 @@ export class BufferDecoration extends Disposable implements IDecoration { return; } this.isDisposed = true; - this.line = -1; + this._line = -1; // Emit before super.dispose such that dispose listeners get a change to react this._onDispose.fire(); super.dispose(); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 6b839299..0f01317f 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -429,10 +429,6 @@ declare module 'xterm' { } export interface IDecorationOptions { - /** - * The type of decoration options - */ - type: 'IBufferDecorationOptions' | 'IGutterDecorationOptions'; /** * The line in the terminal where From d7854bc154a47e5b0a24839a230f43f5e250c862 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Sun, 30 Jan 2022 00:29:02 -0600 Subject: [PATCH 05/59] remove unused imports --- src/browser/renderer/DecorationRenderLayer.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/browser/renderer/DecorationRenderLayer.ts b/src/browser/renderer/DecorationRenderLayer.ts index 29db0d27..5d088241 100644 --- a/src/browser/renderer/DecorationRenderLayer.ts +++ b/src/browser/renderer/DecorationRenderLayer.ts @@ -5,21 +5,15 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer'; -import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types'; +import { IRequestRedrawEvent } from 'browser/renderer/Types'; import { IColorSet } from 'browser/Types'; -import { CellData } from 'common/buffer/CellData'; import { Marker } from 'common/buffer/Marker'; import { EventEmitter, IEventEmitter } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IBufferService, IOptionsService } from 'common/services/Services'; -import { ICellData } from 'common/Types'; import { IBufferDecorationOptions, IDecoration, IEvent, IGutterDecorationOptions } from 'xterm'; const enum DefaultButton { - WIDTH = 2, - HEIGHT = 1, - COLS = 87, - MARGIN_RIGHT = 3, COLOR = '#4B9CD3' } export class DecorationRenderLayer extends BaseRenderLayer { From 9b4801fb43d0286feb0f26e98549609e1ce0843d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Sun, 30 Jan 2022 00:36:31 -0600 Subject: [PATCH 06/59] more polish --- demo/style.css | 4 ++-- src/browser/renderer/DecorationRenderLayer.ts | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/demo/style.css b/demo/style.css index d490d953..1abcf08d 100644 --- a/demo/style.css +++ b/demo/style.css @@ -76,8 +76,8 @@ pre { background-color: #ddd; } -.decoration:hover, -.decoration:focus { +.button-buffer-decoration:hover, +.button-buffer-decoration:focus { box-shadow: 0 0.5em 0.5em -0.4em var(--hover); transform: translateY(-0.25em); } diff --git a/src/browser/renderer/DecorationRenderLayer.ts b/src/browser/renderer/DecorationRenderLayer.ts index 5d088241..39254d9c 100644 --- a/src/browser/renderer/DecorationRenderLayer.ts +++ b/src/browser/renderer/DecorationRenderLayer.ts @@ -14,7 +14,7 @@ import { IBufferService, IOptionsService } from 'common/services/Services'; import { IBufferDecorationOptions, IDecoration, IEvent, IGutterDecorationOptions } from 'xterm'; const enum DefaultButton { - COLOR = '#4B9CD3' + COLOR = '#5DA5D5' } export class DecorationRenderLayer extends BaseRenderLayer { constructor( @@ -28,7 +28,6 @@ export class DecorationRenderLayer extends BaseRenderLayer { ) { super(container, 'decoration', zIndex, true, colors, rendererId, bufferService, optionsService); this.registerDecoration({ startMarker: new Marker(1), shape: 'button' }); - this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y }); } public reset(): void { @@ -82,7 +81,7 @@ class BufferDecoration extends Disposable implements IDecoration { this._element.style.position = 'absolute'; this._element.style.top = '0px'; this._element.style.right = '5px'; - addDisposableDomListener(this._element, 'click', e => console.log('circle')); + addDisposableDomListener(this._element, 'click', () => console.log('circle')); container.parentElement!.append(this._element); this._onRender.fire(this._element); } else { From c5a0711f846e42510ba4a55b3aa11d7d1e08ebec Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 31 Jan 2022 12:53:07 -0600 Subject: [PATCH 07/59] clean up --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 6 +- src/browser/Terminal.ts | 4 +- src/browser/TestUtils.test.ts | 8 +- src/browser/Types.d.ts | 4 +- src/browser/public/Terminal.ts | 4 +- src/browser/renderer/DecorationRenderLayer.ts | 79 ++++++----- src/browser/renderer/Renderer.ts | 5 +- src/browser/renderer/Types.d.ts | 4 +- src/browser/renderer/dom/DomRenderer.ts | 4 +- src/browser/services/RenderService.ts | 4 +- src/browser/services/Services.ts | 4 +- typings/xterm.d.ts | 127 +++++++++--------- 12 files changed, 135 insertions(+), 118 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index b1aa03ec..4d8fa11c 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -13,7 +13,7 @@ import { IWebGL2RenderingContext } from './Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; import { Disposable } from 'common/Lifecycle'; import { Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; -import { Terminal, IEvent, IBufferDecorationOptions, IDecoration, IGutterDecorationOptions } from 'xterm'; +import { Terminal, IEvent, IBufferDecorationOptions, IDecoration } from 'xterm'; import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; import { ITerminal, IColorSet } from 'browser/Types'; @@ -118,12 +118,12 @@ export class WebglRenderer extends Disposable implements IRenderer { return this._charAtlas?.cacheCanvas; } - public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration { + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { const decorationLayer = this._renderLayers.find(l => l instanceof DecorationRenderLayer); if (decorationLayer instanceof DecorationRenderLayer) { return decorationLayer.registerDecoration(decorationOptions); } - throw new Error('no decoration layer'); + return undefined; } public setColors(colors: IColorSet): void { diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index fb26a2eb..8026175e 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -37,7 +37,7 @@ import * as Strings from 'browser/LocalizableStrings'; import { SoundService } from 'browser/services/SoundService'; import { MouseZoneManager } from 'browser/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; -import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider, IBufferDecorationOptions, IDecoration, IGutterDecorationOptions } from 'xterm'; +import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider, IBufferDecorationOptions, IDecoration } from 'xterm'; import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; import { KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, ColorRequestType } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; @@ -998,7 +998,7 @@ export class Terminal extends CoreTerminal implements ITerminal { return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset); } - public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration | undefined { + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { return this._renderService?.registerDecoration(decorationOptions); } /** diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index c42e4a55..1a29c186 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IDisposable, IMarker, ISelectionPosition, ILinkProvider, IBufferDecorationOptions, IDecoration, IGutterDecorationOptions, IDecorationOptions } from 'xterm'; +import { IDisposable, IMarker, ISelectionPosition, ILinkProvider, IBufferDecorationOptions, IDecoration } from 'xterm'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { ICharacterJoinerService, ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; @@ -102,7 +102,7 @@ export class MockTerminal implements ITerminal { public registerLinkProvider(linkProvider: ILinkProvider): IDisposable { throw new Error('Method not implemented.'); } - public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration | undefined { + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { throw new Error('Method not implemented.'); } public hasSelection(): boolean { @@ -293,7 +293,7 @@ export class MockRenderer implements IRenderer { public onDevicePixelRatioChange(): void { } public clear(): void { } public renderRows(start: number, end: number): void { } - public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration { + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration { throw new Error('Method not implemented.'); } } @@ -425,7 +425,7 @@ export class MockRenderService implements IRenderService { public dispose(): void { throw new Error('Method not implemented.'); } - public registerDecoration(decorationOptions: IDecorationOptions): IDecoration { + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration { throw new Error('Method not implemented.'); } } diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 779a9454..69ba3047 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IBufferDecorationOptions, IDecoration, IDisposable, IGutterDecorationOptions, IMarker, ISelectionPosition } from 'xterm'; +import { IBufferDecorationOptions, IDecoration, IDisposable, IMarker, ISelectionPosition } from 'xterm'; import { IEvent } from 'common/EventEmitter'; import { ICoreTerminal, CharData, ITerminalOptions } from 'common/Types'; import { IMouseService, IRenderService } from './services/Services'; @@ -61,7 +61,7 @@ export interface IPublicTerminal extends IDisposable { registerCharacterJoiner(handler: (text: string) => [number, number][]): number; deregisterCharacterJoiner(joinerId: number): void; addMarker(cursorYOffset: number): IMarker | undefined; - registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration | undefined; + registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined; hasSelection(): boolean; getSelection(): string; getSelectionPosition(): ISelectionPosition | undefined; diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 1f3c32fe..c641f5f1 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal as ITerminalApi, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes, IBufferDecorationOptions, IDecoration, IGutterDecorationOptions } from 'xterm'; +import { Terminal as ITerminalApi, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes, IBufferDecorationOptions, IDecoration } from 'xterm'; import { ITerminal } from 'browser/Types'; import { Terminal as TerminalCore } from 'browser/Terminal'; import * as Strings from 'browser/LocalizableStrings'; @@ -171,7 +171,7 @@ export class Terminal implements ITerminalApi { this._verifyIntegers(cursorYOffset); return this._core.addMarker(cursorYOffset); } - public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration | undefined { + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { this._checkProposedApi(); return this._core.registerDecoration(decorationOptions); } diff --git a/src/browser/renderer/DecorationRenderLayer.ts b/src/browser/renderer/DecorationRenderLayer.ts index 39254d9c..fd01c275 100644 --- a/src/browser/renderer/DecorationRenderLayer.ts +++ b/src/browser/renderer/DecorationRenderLayer.ts @@ -3,20 +3,19 @@ * @license MIT */ -import { addDisposableDomListener } from 'browser/Lifecycle'; import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer'; import { IRequestRedrawEvent } from 'browser/renderer/Types'; import { IColorSet } from 'browser/Types'; -import { Marker } from 'common/buffer/Marker'; import { EventEmitter, IEventEmitter } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IBufferService, IOptionsService } from 'common/services/Services'; -import { IBufferDecorationOptions, IDecoration, IEvent, IGutterDecorationOptions } from 'xterm'; +import { IBufferDecorationOptions, IDecoration, IEvent, IMarker } from 'xterm'; const enum DefaultButton { COLOR = '#5DA5D5' } export class DecorationRenderLayer extends BaseRenderLayer { + private _decorations: IDecoration[] = []; constructor( container: HTMLElement, zIndex: number, @@ -27,33 +26,37 @@ export class DecorationRenderLayer extends BaseRenderLayer { @IOptionsService optionsService: IOptionsService ) { super(container, 'decoration', zIndex, true, colors, rendererId, bufferService, optionsService); - this.registerDecoration({ startMarker: new Marker(1), shape: 'button' }); + // this.registerDecoration({ startMarker: new Marker(1), shape: 'button' }); } + + public onGridChanged(startRow: number, endRow: number): void { + for (const decoration of this._decorations) { + (decoration as BufferDecoration).render(); + } + } + public reset(): void { } - public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration { - if ('shape' in decorationOptions) { - return new BufferDecoration(decorationOptions, this._ctx.canvas); + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { + if (decorationOptions.marker.isDisposed) { + return undefined; } - throw new Error('Gutter decoration not yet implemented'); + return new BufferDecoration(decorationOptions, this._ctx.canvas); } } class BufferDecoration extends Disposable implements IDecoration { private static _nextId = 1; - + private _marker: IMarker; private _element: HTMLElement | undefined; private _id: number = BufferDecoration._nextId++; - private _line: number; public isDisposed: boolean = false; public get id(): number { return this._id; } - - public get line(): number { return this._line; } - public get element(): HTMLElement { return this._element!; } + public get marker(): IMarker { return this._marker; } private _onDispose = new EventEmitter(); public get onDispose(): IEvent { return this._onDispose.event; } @@ -63,29 +66,29 @@ class BufferDecoration extends Disposable implements IDecoration { constructor( decorationOptions: IBufferDecorationOptions, - container: HTMLElement + private readonly _container: HTMLElement ) { super(); - this._line = decorationOptions.startMarker.line; - if (decorationOptions.shape === 'button') { - const color = decorationOptions.color || DefaultButton.COLOR; - this._element = document.createElement('menu'); - this._element.classList.add('button-buffer-decoration'); - this._element.id = 'button-buffer-decoration-' + this._id; - this._element.style.background = color; - this._element.style.width = '1px'; - this._element.style.height = '32px'; - this._element.style.borderRadius = '64px'; - this._element.style.border = `4px solid white`; - this._element.style.zIndex = '6'; - this._element.style.position = 'absolute'; - this._element.style.top = '0px'; + + this._marker = decorationOptions.marker; + const color = DefaultButton.COLOR; + this._element = document.createElement('menu'); + this._element.classList.add('button-buffer-decoration'); + this._element.id = 'button-buffer-decoration-' + this._id; + this._element.style.background = color; + this._element.style.width = '1px'; + this._element.style.height = '32px'; + this._element.style.borderRadius = '64px'; + this._element.style.border = `4px solid white`; + this._element.style.zIndex = '6'; + this._element.style.position = 'absolute'; + if (decorationOptions.anchor === 'right') { this._element.style.right = '5px'; - addDisposableDomListener(this._element, 'click', () => console.log('circle')); - container.parentElement!.append(this._element); - this._onRender.fire(this._element); } else { - throw new Error('only shape that has been implemented so far is button'); + this._element.style.left = '5px'; + } + if (this._container.parentElement && this._element) { + this._container.parentElement.append(this._element); } } @@ -94,9 +97,19 @@ class BufferDecoration extends Disposable implements IDecoration { return; } this.isDisposed = true; - this._line = -1; + this._marker.dispose(); // Emit before super.dispose such that dispose listeners get a change to react this._onDispose.fire(); super.dispose(); } + + public render(): void { + if (!this._element) { + return; + } + if (this._container.parentElement && !this._container.parentElement.contains(this._element)) { + this._container.parentElement.append(this._element); + } + this._onRender.fire(this._element); + } } diff --git a/src/browser/renderer/Renderer.ts b/src/browser/renderer/Renderer.ts index 4829e0bb..3e96fae1 100644 --- a/src/browser/renderer/Renderer.ts +++ b/src/browser/renderer/Renderer.ts @@ -15,7 +15,7 @@ import { IBufferService, IOptionsService, ICoreService, IInstantiationService } import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { DecorationRenderLayer } from 'browser/renderer/DecorationRenderLayer'; -import { IBufferDecorationOptions, IGutterDecorationOptions, IDecoration } from 'xterm'; +import { IBufferDecorationOptions, IDecoration } from 'xterm'; let nextRendererId = 1; @@ -68,12 +68,11 @@ export class Renderer extends Disposable implements IRenderer { this.onOptionsChanged(); } - public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration { + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { const decorationLayer = this._renderLayers.find(l => l instanceof DecorationRenderLayer); if (decorationLayer instanceof DecorationRenderLayer) { return decorationLayer.registerDecoration(decorationOptions); } - throw new Error('no decoration layer'); } public dispose(): void { diff --git a/src/browser/renderer/Types.d.ts b/src/browser/renderer/Types.d.ts index 8a328686..97e43b61 100644 --- a/src/browser/renderer/Types.d.ts +++ b/src/browser/renderer/Types.d.ts @@ -6,7 +6,7 @@ import { IDisposable } from 'common/Types'; import { IColorSet } from 'browser/Types'; import { IEvent } from 'common/EventEmitter'; -import { IBufferDecorationOptions, IDecoration, IGutterDecorationOptions } from 'xterm'; +import { IBufferDecorationOptions, IDecoration } from 'xterm'; export interface IRenderDimensions { scaledCharWidth: number; @@ -54,7 +54,7 @@ export interface IRenderer extends IDisposable { clear(): void; renderRows(start: number, end: number): void; clearTextureAtlas?(): void; - registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration; + registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined; } export interface IRenderLayer extends IDisposable { diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 58c54f65..ba23e573 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -14,7 +14,7 @@ import { EventEmitter, IEvent } from 'common/EventEmitter'; import { color } from 'browser/Color'; import { removeElementFromParent } from 'browser/Dom'; import { DecorationRenderLayer } from 'browser/renderer/DecorationRenderLayer'; -import { IBufferDecorationOptions, IGutterDecorationOptions, IDecoration } from 'xterm'; +import { IBufferDecorationOptions, IDecoration } from 'xterm'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; @@ -153,7 +153,7 @@ export class DomRenderer extends Disposable implements IRenderer { this._injectCss(); } - public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration { + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration { // const decorationLayer = this._renderLayers.find(l => l instanceof DecorationRenderLayer); // if (decorationLayer instanceof DecorationRenderLayer) { // return decorationLayer.registerDecoration(decorationOptions); diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 526bffa3..e2f6300b 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -12,7 +12,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { IColorSet, IRenderDebouncer } from 'browser/Types'; import { IOptionsService, IBufferService } from 'common/services/Services'; import { ICharSizeService, IRenderService } from 'browser/services/Services'; -import { IDecorationOptions, IDecoration, IGutterDecorationOptions, IBufferDecorationOptions } from 'xterm'; +import { IDecoration, IBufferDecorationOptions } from 'xterm'; interface ISelectionState { start: [number, number] | undefined; @@ -86,7 +86,7 @@ export class RenderService extends Disposable implements IRenderService { } } - public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration { + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { return this._renderer.registerDecoration(decorationOptions); } diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index c3ec2eb5..40cb08d2 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -9,7 +9,7 @@ import { IColorSet } from 'browser/Types'; import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; import { IDisposable } from 'common/Types'; -import { IDecoration, IDecorationOptions } from 'xterm'; +import { IBufferDecorationOptions, IDecoration } from 'xterm'; export const ICharSizeService = createDecorator('CharSizeService'); export interface ICharSizeService { @@ -52,7 +52,7 @@ export interface IRenderService extends IDisposable { onRefreshRequest: IEvent<{ start: number, end: number }>; dimensions: IRenderDimensions; - registerDecoration(decorationOptions: IDecorationOptions): IDecoration; + registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined; refreshRows(start: number, end: number): void; clearTextureAtlas(): void; resize(cols: number, rows: number): void; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 0f01317f..52397e55 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -380,94 +380,99 @@ declare module 'xterm' { * is trimmed and lines are added or removed. This is a single line that may * be part of a larger wrapped line. */ - export interface IMarker extends IDisposable { + export interface IMarker extends IDisposableWithEvent { /** * A unique identifier for this marker. */ readonly id: number; - /** - * Whether this marker is disposed. - */ - readonly isDisposed: boolean; - /** * The actual line index in the buffer at this point in time. This is set to * -1 if the marker has been disposed. */ readonly line: number; - - /** - * Event listener to get notified when the marker gets disposed. Automatic disposal - * might happen for a marker, that got invalidated by scrolling out or removal of - * a line from the buffer. - */ - onDispose: IEvent; } /** - * Represents a decoration in the terminal that is associated with a particular marker. + * Represents a decoration in the terminal that is associated with a particular marker and DOM element. */ - export interface IDecoration extends IDisposable { - /** - * Whether this decoration is disposed. + export interface IDecoration extends IDisposableWithEvent { + /* + * The marker for the decoration in the terminal. */ - readonly isDisposed: boolean; + readonly marker: IMarker; /** - * The actual line index in the buffer at this point in time. This is set to - * -1 if the decoration has been disposed. - */ - readonly line: number; - - /** - * An event fired when the decoration - * is rendered, returns the dom element + * An event fired when the decoration + * is rendered, returns the dom element * associated with the decoration. */ onRender: IEvent; - } - - export interface IDecorationOptions { /** - * The line in the terminal where - * the decoration will be displayed - */ - startMarker: IMarker; - - /** - * The number of milliseconds the decoration - * should be displayed for. - */ - displayDuration?: number; - - /** - * The color of the decoration + * The HTMLElement that gets created after the + * first _onRender call, or undefined if accessed before + * that. */ - color?: string + element: HTMLElement | undefined; } - export interface IBufferDecorationOptions extends IDecorationOptions { + export interface IDisposableWithEvent extends IDisposable { /** - * The type of buffer decoration - */ - shape: 'button' | 'box-border'; + * Event listener to get notified when this gets disposed. + */ + onDispose: IEvent; + /** + * Whether this is disposed. + */ + readonly isDisposed: boolean; + } + + + export interface IBufferDecorationOptions { /* - * The x position for the decoration. - * Defaults to the right edge. + * Where the decoration will be anchored - + * defaults to the left edge. */ - position?: number; + anchor?: 'right' | 'left'; + + /** + * The line in the terminal where + * the decoration will be displayed + */ + marker: IMarker; + + /** + * The width of the decoration, which defaults to + * cell width + */ + width?: number; + + /** + * The height of the decoration, which defaults to + * cell height + */ + height?: number; + + /** + * The x position offset relative to the anchor + */ + x?: number; } - export interface IGutterDecorationOptions extends IDecorationOptions { - /** - * The end line in the terminal for - * the decoration - */ - endMarker: IMarker; - } + // export interface IGutterDecorationOptions { + // /** + // * The line in the terminal where + // * the decoration will be displayed + // */ + // startMarker: IMarker; + // /** + // * The end line in the terminal for + // * the decoration + // */ + // endMarker: IMarker; + // } /** * The set of localizable strings. @@ -935,11 +940,11 @@ declare module 'xterm' { addMarker(cursorYOffset: number): IMarker | undefined; /** - * (EXPERIMENTAL) Adds a decoration as configured with @param decorationOptions to the - * normal buffer or gutter and returns it. - * If the alt buffer is active or the decoration is invalid, undefined is returned. + * (EXPERIMENTAL) Adds a decoration to the terminal using + * @param decorationOptions, which takes a markers + * and a horizontal aligntment */ - registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration | undefined; + registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined; /** * Gets whether the terminal has an active selection. From fc6245122df73e56e10ab8048414ab1abe9b1d19 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 31 Jan 2022 13:08:09 -0600 Subject: [PATCH 08/59] more cleanup --- typings/xterm.d.ts | 70 ++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 52397e55..22eba5d1 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -393,6 +393,23 @@ declare module 'xterm' { readonly line: number; } + /** + * Represents a disposable with an + * @param onDispose event listener and + * @param isDisposed property. + */ + export interface IDisposableWithEvent extends IDisposable { + /** + * Event listener to get notified when this gets disposed. + */ + onDispose: IEvent; + + /** + * Whether this is disposed. + */ + readonly isDisposed: boolean; + } + /** * Represents a decoration in the terminal that is associated with a particular marker and DOM element. */ @@ -417,32 +434,19 @@ declare module 'xterm' { element: HTMLElement | undefined; } - export interface IDisposableWithEvent extends IDisposable { - /** - * Event listener to get notified when this gets disposed. - */ - onDispose: IEvent; - - /** - * Whether this is disposed. - */ - readonly isDisposed: boolean; - } - - export interface IBufferDecorationOptions { - /* - * Where the decoration will be anchored - - * defaults to the left edge. - */ - anchor?: 'right' | 'left'; - /** * The line in the terminal where * the decoration will be displayed */ marker: IMarker; + /* + * Where the decoration will be anchored - + * defaults to the left edge + */ + anchor?: 'right' | 'left'; + /** * The width of the decoration, which defaults to * cell width @@ -461,18 +465,18 @@ declare module 'xterm' { x?: number; } - // export interface IGutterDecorationOptions { - // /** - // * The line in the terminal where - // * the decoration will be displayed - // */ - // startMarker: IMarker; - // /** - // * The end line in the terminal for - // * the decoration - // */ - // endMarker: IMarker; - // } + export interface IGutterDecorationOptions { + /** + * The line in the terminal where + * the decoration will be displayed + */ + startMarker: IMarker; + /** + * The end line in the terminal for + * the decoration + */ + endMarker: IMarker; + } /** * The set of localizable strings. @@ -941,8 +945,8 @@ declare module 'xterm' { /** * (EXPERIMENTAL) Adds a decoration to the terminal using - * @param decorationOptions, which takes a markers - * and a horizontal aligntment + * @param decorationOptions, which takes a marker and an optional anchor, + * width, height, and x offset from the anchor */ registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined; From 7d7726817a466a1450fa7068f93d057d159e324a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 31 Jan 2022 14:24:24 -0600 Subject: [PATCH 09/59] improve docs --- src/browser/renderer/DecorationRenderLayer.ts | 11 +++++------ typings/xterm.d.ts | 19 ++++--------------- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/browser/renderer/DecorationRenderLayer.ts b/src/browser/renderer/DecorationRenderLayer.ts index fd01c275..621b616e 100644 --- a/src/browser/renderer/DecorationRenderLayer.ts +++ b/src/browser/renderer/DecorationRenderLayer.ts @@ -69,23 +69,22 @@ class BufferDecoration extends Disposable implements IDecoration { private readonly _container: HTMLElement ) { super(); - this._marker = decorationOptions.marker; const color = DefaultButton.COLOR; - this._element = document.createElement('menu'); + this._element = document.createElement('div'); this._element.classList.add('button-buffer-decoration'); this._element.id = 'button-buffer-decoration-' + this._id; this._element.style.background = color; - this._element.style.width = '1px'; - this._element.style.height = '32px'; + this._element.style.width = '24px'; + this._element.style.height = '24px'; this._element.style.borderRadius = '64px'; this._element.style.border = `4px solid white`; this._element.style.zIndex = '6'; this._element.style.position = 'absolute'; if (decorationOptions.anchor === 'right') { - this._element.style.right = '5px'; + this._element.style.right = `${decorationOptions.x}px` || '5px'; } else { - this._element.style.left = '5px'; + this._element.style.right = `${decorationOptions.x}px` || '5px'; } if (this._container.parentElement && this._element) { this._container.parentElement.append(this._element); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 22eba5d1..a6ff47ad 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -424,14 +424,14 @@ declare module 'xterm' { * is rendered, returns the dom element * associated with the decoration. */ - onRender: IEvent; + readonly onRender: IEvent; /** * The HTMLElement that gets created after the * first _onRender call, or undefined if accessed before * that. */ - element: HTMLElement | undefined; + readonly element: HTMLElement | undefined; } export interface IBufferDecorationOptions { @@ -447,18 +447,6 @@ declare module 'xterm' { */ anchor?: 'right' | 'left'; - /** - * The width of the decoration, which defaults to - * cell width - */ - width?: number; - - /** - * The height of the decoration, which defaults to - * cell height - */ - height?: number; - /** * The x position offset relative to the anchor */ @@ -946,7 +934,8 @@ declare module 'xterm' { /** * (EXPERIMENTAL) Adds a decoration to the terminal using * @param decorationOptions, which takes a marker and an optional anchor, - * width, height, and x offset from the anchor + * width, height, and x offset from the anchor. Returns the decoration or + * undefined if the marker has already been disposed of. */ registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined; From 5ccb41b9b9e84f748278125a9b552efa24911b3c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 1 Feb 2022 10:32:09 -0600 Subject: [PATCH 10/59] re add height and width --- src/browser/renderer/DecorationRenderLayer.ts | 8 ++++---- typings/xterm.d.ts | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/browser/renderer/DecorationRenderLayer.ts b/src/browser/renderer/DecorationRenderLayer.ts index 621b616e..e1476d16 100644 --- a/src/browser/renderer/DecorationRenderLayer.ts +++ b/src/browser/renderer/DecorationRenderLayer.ts @@ -75,16 +75,16 @@ class BufferDecoration extends Disposable implements IDecoration { this._element.classList.add('button-buffer-decoration'); this._element.id = 'button-buffer-decoration-' + this._id; this._element.style.background = color; - this._element.style.width = '24px'; - this._element.style.height = '24px'; + this._element.style.width = '32px'; + this._element.style.height = '32px'; this._element.style.borderRadius = '64px'; this._element.style.border = `4px solid white`; this._element.style.zIndex = '6'; this._element.style.position = 'absolute'; if (decorationOptions.anchor === 'right') { - this._element.style.right = `${decorationOptions.x}px` || '5px'; + this._element.style.right = decorationOptions.x ? `${decorationOptions.x}px` : '5px'; } else { - this._element.style.right = `${decorationOptions.x}px` || '5px'; + this._element.style.right = decorationOptions.x ? `${decorationOptions.x}px` : '5px'; } if (this._container.parentElement && this._element) { this._container.parentElement.append(this._element); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index a6ff47ad..a7f80a15 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -451,6 +451,20 @@ declare module 'xterm' { * The x position offset relative to the anchor */ x?: number; + + + /** + * The width of the decoration in cells, which defaults to + * cell width + */ + width?: number; + + /** + * The height of the decoration in cells, which defaults to + * cell height + */ + height?: number; + } export interface IGutterDecorationOptions { From 49085bbd5b43c6d14d71dd93c3eba2940d0b1589 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 2 Feb 2022 20:51:28 -0600 Subject: [PATCH 11/59] use decorations service instead of decorations render layer --- src/browser/Terminal.ts | 6 ++- src/browser/renderer/Renderer.ts | 15 ++---- src/browser/renderer/Types.d.ts | 1 - src/browser/renderer/dom/DomRenderer.ts | 1 - .../DecorationsService.ts} | 51 +++++++------------ src/browser/services/RenderService.ts | 4 -- src/browser/services/Services.ts | 1 - 7 files changed, 26 insertions(+), 53 deletions(-) rename src/browser/{renderer/DecorationRenderLayer.ts => services/DecorationsService.ts} (65%) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 8026175e..7757e0b7 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -55,6 +55,7 @@ import { CoreTerminal } from 'common/CoreTerminal'; import { color, rgba } from 'browser/Color'; import { CharacterJoinerService } from 'browser/services/CharacterJoinerService'; import { toRgbString } from 'common/input/XParseColor'; +import { DecorationsService, IDecorationsService } from 'browser/services/DecorationsService'; // Let it work inside Node.js for automated testing purposes. const document: Document = (typeof window !== 'undefined') ? window.document : null as any; @@ -80,6 +81,7 @@ export class Terminal extends CoreTerminal implements ITerminal { private _charSizeService: ICharSizeService | undefined; private _mouseService: IMouseService | undefined; private _renderService: IRenderService | undefined; + private _decorationsService: IDecorationsService | undefined; private _characterJoinerService: ICharacterJoinerService | undefined; private _selectionService: ISelectionService | undefined; private _soundService: ISoundService | undefined; @@ -513,6 +515,8 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._renderService.onRenderedBufferChange(e => this._onRender.fire(e))); this.onResize(e => this._renderService!.resize(e.cols, e.rows)); + this._decorationsService = this.register(this._instantiationService.createInstance(DecorationsService, this.screenElement)); + this._compositionView = document.createElement('div'); this._compositionView.classList.add('composition-view'); this._compositionHelper = this._instantiationService.createInstance(CompositionHelper, this.textarea, this._compositionView); @@ -999,7 +1003,7 @@ export class Terminal extends CoreTerminal implements ITerminal { } public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { - return this._renderService?.registerDecoration(decorationOptions); + return this._decorationsService?.registerDecoration(decorationOptions); } /** * Gets whether the terminal has an active selection. diff --git a/src/browser/renderer/Renderer.ts b/src/browser/renderer/Renderer.ts index 3e96fae1..305808a2 100644 --- a/src/browser/renderer/Renderer.ts +++ b/src/browser/renderer/Renderer.ts @@ -10,11 +10,10 @@ import { IRenderLayer, IRenderer, IRenderDimensions, IRequestRedrawEvent } from import { LinkRenderLayer } from 'browser/renderer/LinkRenderLayer'; import { Disposable } from 'common/Lifecycle'; import { IColorSet, ILinkifier, ILinkifier2 } from 'browser/Types'; -import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services'; -import { IBufferService, IOptionsService, ICoreService, IInstantiationService } from 'common/services/Services'; +import { ICharSizeService } from 'browser/services/Services'; +import { IBufferService, IOptionsService, IInstantiationService } from 'common/services/Services'; import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { DecorationRenderLayer } from 'browser/renderer/DecorationRenderLayer'; import { IBufferDecorationOptions, IDecoration } from 'xterm'; let nextRendererId = 1; @@ -46,8 +45,7 @@ export class Renderer extends Disposable implements IRenderer { instantiationService.createInstance(TextRenderLayer, this._screenElement, 0, this._colors, allowTransparency, this._id), instantiationService.createInstance(SelectionRenderLayer, this._screenElement, 1, this._colors, this._id), instantiationService.createInstance(LinkRenderLayer, this._screenElement, 2, this._colors, this._id, linkifier, linkifier2), - instantiationService.createInstance(CursorRenderLayer, this._screenElement, 3, this._colors, this._id, this._onRequestRedraw), - instantiationService.createInstance(DecorationRenderLayer, this._screenElement, 4, this._colors, this._id, this._onRequestRedraw) + instantiationService.createInstance(CursorRenderLayer, this._screenElement, 3, this._colors, this._id, this._onRequestRedraw) ]; this.dimensions = { scaledCharWidth: 0, @@ -68,13 +66,6 @@ export class Renderer extends Disposable implements IRenderer { this.onOptionsChanged(); } - public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { - const decorationLayer = this._renderLayers.find(l => l instanceof DecorationRenderLayer); - if (decorationLayer instanceof DecorationRenderLayer) { - return decorationLayer.registerDecoration(decorationOptions); - } - } - public dispose(): void { for (const l of this._renderLayers) { l.dispose(); diff --git a/src/browser/renderer/Types.d.ts b/src/browser/renderer/Types.d.ts index 97e43b61..c5387173 100644 --- a/src/browser/renderer/Types.d.ts +++ b/src/browser/renderer/Types.d.ts @@ -54,7 +54,6 @@ export interface IRenderer extends IDisposable { clear(): void; renderRows(start: number, end: number): void; clearTextureAtlas?(): void; - registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined; } export interface IRenderLayer extends IDisposable { diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index ba23e573..2057c791 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -13,7 +13,6 @@ import { IOptionsService, IBufferService, IInstantiationService } from 'common/s import { EventEmitter, IEvent } from 'common/EventEmitter'; import { color } from 'browser/Color'; import { removeElementFromParent } from 'browser/Dom'; -import { DecorationRenderLayer } from 'browser/renderer/DecorationRenderLayer'; import { IBufferDecorationOptions, IDecoration } from 'xterm'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; diff --git a/src/browser/renderer/DecorationRenderLayer.ts b/src/browser/services/DecorationsService.ts similarity index 65% rename from src/browser/renderer/DecorationRenderLayer.ts rename to src/browser/services/DecorationsService.ts index e1476d16..d168e39b 100644 --- a/src/browser/renderer/DecorationRenderLayer.ts +++ b/src/browser/services/DecorationsService.ts @@ -3,50 +3,35 @@ * @license MIT */ -import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer'; -import { IRequestRedrawEvent } from 'browser/renderer/Types'; -import { IColorSet } from 'browser/Types'; -import { EventEmitter, IEventEmitter } from 'common/EventEmitter'; +import { IRenderDimensions } from 'browser/renderer/Types'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; -import { IBufferService, IOptionsService } from 'common/services/Services'; -import { IBufferDecorationOptions, IDecoration, IEvent, IMarker } from 'xterm'; +import { createDecorator } from 'common/services/ServiceRegistry'; +import { IDisposable } from 'common/Types'; +import { IBufferDecorationOptions, IDecoration, IMarker } from 'xterm'; + +export interface IDecorationsService extends IDisposable { + registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined; +} const enum DefaultButton { COLOR = '#5DA5D5' } -export class DecorationRenderLayer extends BaseRenderLayer { - private _decorations: IDecoration[] = []; - constructor( - container: HTMLElement, - zIndex: number, - colors: IColorSet, - rendererId: number, - private _onRequestRedraw: IEventEmitter, - @IBufferService bufferService: IBufferService, - @IOptionsService optionsService: IOptionsService - ) { - super(container, 'decoration', zIndex, true, colors, rendererId, bufferService, optionsService); - // this.registerDecoration({ startMarker: new Marker(1), shape: 'button' }); + +export class DecorationsService extends Disposable implements IDecorationsService { + constructor(private readonly _screenElement: HTMLElement) { + super(); } - - public onGridChanged(startRow: number, endRow: number): void { - for (const decoration of this._decorations) { - (decoration as BufferDecoration).render(); - } - } - - public reset(): void { - - } - public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { if (decorationOptions.marker.isDisposed) { return undefined; } - return new BufferDecoration(decorationOptions, this._ctx.canvas); + return new BufferDecoration(decorationOptions, this._screenElement); } } + +export const IDecorationsService = createDecorator('DecorationsService'); class BufferDecoration extends Disposable implements IDecoration { private static _nextId = 1; private _marker: IMarker; @@ -86,8 +71,8 @@ class BufferDecoration extends Disposable implements IDecoration { } else { this._element.style.right = decorationOptions.x ? `${decorationOptions.x}px` : '5px'; } - if (this._container.parentElement && this._element) { - this._container.parentElement.append(this._element); + if (this._container && this._element) { + this._container.append(this._element); } } diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index e2f6300b..d236135a 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -86,10 +86,6 @@ export class RenderService extends Disposable implements IRenderService { } } - public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { - return this._renderer.registerDecoration(decorationOptions); - } - private _onIntersectionChange(entry: IntersectionObserverEntry): void { this._isPaused = entry.isIntersecting === undefined ? (entry.intersectionRatio === 0) : !entry.isIntersecting; diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 40cb08d2..8b7a0a77 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -52,7 +52,6 @@ export interface IRenderService extends IDisposable { onRefreshRequest: IEvent<{ start: number, end: number }>; dimensions: IRenderDimensions; - registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined; refreshRows(start: number, end: number): void; clearTextureAtlas(): void; resize(cols: number, rows: number): void; From ad59db9a8a52d448f173304c5a22cac944301264 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 2 Feb 2022 22:33:05 -0600 Subject: [PATCH 12/59] try --- demo/style.css | 6 --- src/browser/Terminal.ts | 28 ++++++++++++- src/browser/services/DecorationsService.ts | 47 +++++++++++++++------- 3 files changed, 58 insertions(+), 23 deletions(-) diff --git a/demo/style.css b/demo/style.css index 1abcf08d..cebd08e0 100644 --- a/demo/style.css +++ b/demo/style.css @@ -76,12 +76,6 @@ pre { background-color: #ddd; } -.button-buffer-decoration:hover, -.button-buffer-decoration:focus { - box-shadow: 0 0.5em 0.5em -0.4em var(--hover); - transform: translateY(-0.25em); -} - /* Create an active/current tablink class */ .tab button.active { background-color: #ccc; diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 7757e0b7..e9259266 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -569,8 +569,11 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._onScroll.event(ev => { this.viewport!.syncScrollArea(); this._selectionService!.refresh(); + this._decorationsService!.refresh(ev.position); + })); + this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => { + this._selectionService!.refresh(); })); - this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => this._selectionService!.refresh())); this._mouseZoneManager = this._instantiationService.createInstance(MouseZoneManager, this.element, this.screenElement); this.register(this._mouseZoneManager); @@ -1003,7 +1006,28 @@ export class Terminal extends CoreTerminal implements ITerminal { } public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { - return this._decorationsService?.registerDecoration(decorationOptions); + if (!this._renderService) { + throw new Error('cannot register a decoration without a render service'); + } + + if (!this._decorationsService) { + throw new Error('cannot register a decoration without a decorations service'); + } + + const { actualCellWidth, actualCellHeight } = this._renderService.dimensions; + if (actualCellWidth) { + decorationOptions.width = decorationOptions.width ? decorationOptions.width * actualCellWidth : actualCellWidth; + } else { + throw new Error('unknown cell width'); + } + + if (actualCellHeight) { + decorationOptions.height = decorationOptions.height ? decorationOptions.height * actualCellHeight : actualCellHeight; + } else { + throw new Error('unknown cell height'); + } + + return this._decorationsService.registerDecoration(decorationOptions, actualCellWidth, actualCellHeight); } /** * Gets whether the terminal has an active selection. diff --git a/src/browser/services/DecorationsService.ts b/src/browser/services/DecorationsService.ts index d168e39b..eb1b766b 100644 --- a/src/browser/services/DecorationsService.ts +++ b/src/browser/services/DecorationsService.ts @@ -3,15 +3,16 @@ * @license MIT */ -import { IRenderDimensions } from 'browser/renderer/Types'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { createDecorator } from 'common/services/ServiceRegistry'; +import { IBufferService } from 'common/services/Services'; import { IDisposable } from 'common/Types'; import { IBufferDecorationOptions, IDecoration, IMarker } from 'xterm'; export interface IDecorationsService extends IDisposable { - registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined; + registerDecoration(decorationOptions: IBufferDecorationOptions, cellWidth: number, cellHeight: number): IDecoration | undefined; + refresh(y: number): void; } const enum DefaultButton { @@ -19,14 +20,34 @@ const enum DefaultButton { } export class DecorationsService extends Disposable implements IDecorationsService { - constructor(private readonly _screenElement: HTMLElement) { + private _decorations: BufferDecoration[] = []; + private _cellWidth: number = 0; + private _cellHeight: number = 0; + constructor(private readonly _screenElement: HTMLElement, @IBufferService private readonly _bufferService: IBufferService) { super(); } - public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { + public registerDecoration(decorationOptions: IBufferDecorationOptions, cellWidth: number, cellHeight: number): IDecoration | undefined { if (decorationOptions.marker.isDisposed) { return undefined; } - return new BufferDecoration(decorationOptions, this._screenElement); + this._cellWidth = cellWidth; + this._cellHeight = cellHeight; + const bufferDecoration = new BufferDecoration(decorationOptions, this._screenElement, this._bufferService.buffers.active.y!); + this._decorations.push(bufferDecoration); + return bufferDecoration; + } + + public refresh(y: number): void { + for (const decoration of this._decorations) { + if (decoration.marker.line < y) { + console.log(decoration.marker.line, y); + console.log('scrolled', y); + console.log('y', this._bufferService.buffers.active.y); + console.log('ybase', this._bufferService.buffers.active.ybase); + decoration.element.style.bottom = `${(this._bufferService.buffers.active.ybase - decoration.marker.line)*this._cellHeight}px`; + decoration.element.style.top = ''; + } + } } @@ -51,25 +72,21 @@ class BufferDecoration extends Disposable implements IDecoration { constructor( decorationOptions: IBufferDecorationOptions, - private readonly _container: HTMLElement + private readonly _container: HTMLElement, + y: number ) { super(); this._marker = decorationOptions.marker; - const color = DefaultButton.COLOR; this._element = document.createElement('div'); - this._element.classList.add('button-buffer-decoration'); - this._element.id = 'button-buffer-decoration-' + this._id; - this._element.style.background = color; - this._element.style.width = '32px'; - this._element.style.height = '32px'; - this._element.style.borderRadius = '64px'; - this._element.style.border = `4px solid white`; + this._element.style.width = `${decorationOptions.width}px`; + this._element.style.height = `${decorationOptions.height}px`; this._element.style.zIndex = '6'; + this._element.style.top = `${this._marker.line*decorationOptions.height!}px`; this._element.style.position = 'absolute'; if (decorationOptions.anchor === 'right') { this._element.style.right = decorationOptions.x ? `${decorationOptions.x}px` : '5px'; } else { - this._element.style.right = decorationOptions.x ? `${decorationOptions.x}px` : '5px'; + this._element.style.left = decorationOptions.x ? `${decorationOptions.x}px` : '5px'; } if (this._container && this._element) { this._container.append(this._element); From 912c33ca67e04a734c0b7b5e8628bec1a235c13c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 3 Feb 2022 13:33:30 -0600 Subject: [PATCH 13/59] get it to wrk --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 11 +- src/browser/Terminal.ts | 22 +--- src/browser/services/DecorationsService.ts | 119 +++++++++++------- 3 files changed, 80 insertions(+), 72 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 4d8fa11c..be51d5a6 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -23,7 +23,6 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { ICharacterJoinerService } from 'browser/services/Services'; import { CharData, ICellData } from 'common/Types'; import { AttributeData } from 'common/buffer/AttributeData'; -import { DecorationRenderLayer } from 'browser/renderer/DecorationRenderLayer'; import { IBufferService } from 'common/services/Services'; export class WebglRenderer extends Disposable implements IRenderer { @@ -117,15 +116,7 @@ export class WebglRenderer extends Disposable implements IRenderer { public get textureAtlas(): HTMLCanvasElement | undefined { return this._charAtlas?.cacheCanvas; } - - public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { - const decorationLayer = this._renderLayers.find(l => l instanceof DecorationRenderLayer); - if (decorationLayer instanceof DecorationRenderLayer) { - return decorationLayer.registerDecoration(decorationOptions); - } - return undefined; - } - + public setColors(colors: IColorSet): void { this._colors = colors; // Clear layers and force a full render diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index e9259266..aafb8a4e 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -569,10 +569,11 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._onScroll.event(ev => { this.viewport!.syncScrollArea(); this._selectionService!.refresh(); - this._decorationsService!.refresh(ev.position); + this._decorationsService!.refresh(); })); this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => { this._selectionService!.refresh(); + this._decorationsService!.refresh(); })); this._mouseZoneManager = this._instantiationService.createInstance(MouseZoneManager, this.element, this.screenElement); @@ -1006,28 +1007,11 @@ export class Terminal extends CoreTerminal implements ITerminal { } public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { - if (!this._renderService) { - throw new Error('cannot register a decoration without a render service'); - } - if (!this._decorationsService) { throw new Error('cannot register a decoration without a decorations service'); } - const { actualCellWidth, actualCellHeight } = this._renderService.dimensions; - if (actualCellWidth) { - decorationOptions.width = decorationOptions.width ? decorationOptions.width * actualCellWidth : actualCellWidth; - } else { - throw new Error('unknown cell width'); - } - - if (actualCellHeight) { - decorationOptions.height = decorationOptions.height ? decorationOptions.height * actualCellHeight : actualCellHeight; - } else { - throw new Error('unknown cell height'); - } - - return this._decorationsService.registerDecoration(decorationOptions, actualCellWidth, actualCellHeight); + return this._decorationsService.registerDecoration(decorationOptions); } /** * Gets whether the terminal has an active selection. diff --git a/src/browser/services/DecorationsService.ts b/src/browser/services/DecorationsService.ts index eb1b766b..00edf569 100644 --- a/src/browser/services/DecorationsService.ts +++ b/src/browser/services/DecorationsService.ts @@ -3,6 +3,7 @@ * @license MIT */ +import { IRenderService } from 'browser/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { createDecorator } from 'common/services/ServiceRegistry'; @@ -11,8 +12,9 @@ import { IDisposable } from 'common/Types'; import { IBufferDecorationOptions, IDecoration, IMarker } from 'xterm'; export interface IDecorationsService extends IDisposable { - registerDecoration(decorationOptions: IBufferDecorationOptions, cellWidth: number, cellHeight: number): IDecoration | undefined; - refresh(y: number): void; + registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined; + refresh(): void; + dispose(): void; } const enum DefaultButton { @@ -21,37 +23,65 @@ const enum DefaultButton { export class DecorationsService extends Disposable implements IDecorationsService { private _decorations: BufferDecoration[] = []; - private _cellWidth: number = 0; - private _cellHeight: number = 0; - constructor(private readonly _screenElement: HTMLElement, @IBufferService private readonly _bufferService: IBufferService) { + private _animationFrame: number | undefined; + constructor(private readonly _screenElement: HTMLElement, @IBufferService private readonly _bufferService: IBufferService, @IRenderService private readonly _renderService: IRenderService) { super(); } - public registerDecoration(decorationOptions: IBufferDecorationOptions, cellWidth: number, cellHeight: number): IDecoration | undefined { + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { if (decorationOptions.marker.isDisposed) { return undefined; } - this._cellWidth = cellWidth; - this._cellHeight = cellHeight; - const bufferDecoration = new BufferDecoration(decorationOptions, this._screenElement, this._bufferService.buffers.active.y!); + this._resolveDimensions(decorationOptions); + const bufferDecoration = new BufferDecoration(decorationOptions, this._screenElement, this._renderService); this._decorations.push(bufferDecoration); return bufferDecoration; } - public refresh(y: number): void { + public refresh(): void { + if (this._animationFrame) { + return; + } + + this._animationFrame = window.requestAnimationFrame(() => this._refresh()); + } + + private _refresh(): void { for (const decoration of this._decorations) { - if (decoration.marker.line < y) { - console.log(decoration.marker.line, y); - console.log('scrolled', y); - console.log('y', this._bufferService.buffers.active.y); - console.log('ybase', this._bufferService.buffers.active.ybase); - decoration.element.style.bottom = `${(this._bufferService.buffers.active.ybase - decoration.marker.line)*this._cellHeight}px`; - decoration.element.style.top = ''; + const adjustedLine = decoration.marker.line - this._bufferService.buffers.active.ydisp; + if (adjustedLine < 0 || adjustedLine > this._bufferService.rows) { + console.log('hide', decoration.id, decoration.marker.line,this._bufferService.buffers.active.ydisp, this._bufferService.rows); + decoration.element.style.display = 'none'; + } else { + console.log('make visible', decoration.id, adjustedLine*this._renderService.dimensions.scaledCharHeight); + decoration.element.style.top = `${(adjustedLine)*this._renderService.dimensions.scaledCellHeight}px`; + decoration.element.style.display = 'block'; + } + } + this._animationFrame = undefined; + } + + private _resolveDimensions(decorationOptions: IBufferDecorationOptions): void { + if (this._renderService.dimensions.scaledCellWidth) { + decorationOptions.width = decorationOptions.width ? decorationOptions.width * this._renderService.dimensions.scaledCellWidth : this._renderService.dimensions.scaledCellWidth; + } else { + throw new Error('unknown cell width'); + } + + if (this._renderService.dimensions.scaledCellHeight) { + decorationOptions.height = decorationOptions.height ? decorationOptions.height * this._renderService.dimensions.scaledCellHeight : this._renderService.dimensions.scaledCellHeight; + } else { + throw new Error('unknown cell height'); + } + } + + public dispose(): void { + if (this._animationFrame) { + window.cancelAnimationFrame(this._animationFrame); + this._animationFrame = undefined; } - } } - export const IDecorationsService = createDecorator('DecorationsService'); class BufferDecoration extends Disposable implements IDecoration { private static _nextId = 1; @@ -71,26 +101,14 @@ class BufferDecoration extends Disposable implements IDecoration { public get onRender(): IEvent { return this._onRender.event; } constructor( - decorationOptions: IBufferDecorationOptions, - private readonly _container: HTMLElement, - y: number + private readonly _decorationOptions: IBufferDecorationOptions, + private readonly _screenElement: HTMLElement, + private readonly _renderService: IRenderService ) { super(); - this._marker = decorationOptions.marker; - this._element = document.createElement('div'); - this._element.style.width = `${decorationOptions.width}px`; - this._element.style.height = `${decorationOptions.height}px`; - this._element.style.zIndex = '6'; - this._element.style.top = `${this._marker.line*decorationOptions.height!}px`; - this._element.style.position = 'absolute'; - if (decorationOptions.anchor === 'right') { - this._element.style.right = decorationOptions.x ? `${decorationOptions.x}px` : '5px'; - } else { - this._element.style.left = decorationOptions.x ? `${decorationOptions.x}px` : '5px'; - } - if (this._container && this._element) { - this._container.append(this._element); - } + this._marker = _decorationOptions.marker; + this._createElement(); + this._render(); } public dispose(): void { @@ -104,13 +122,28 @@ class BufferDecoration extends Disposable implements IDecoration { super.dispose(); } - public render(): void { - if (!this._element) { - return; + private _createElement(): void { + this._element = document.createElement('div'); + this._element.classList.add('xterm-decoration'); + this._element.style.width = `${this._decorationOptions.width}px`; + this._element.style.height = `${this._decorationOptions.height}px`; + this._element.style.top = `${this._marker.line * this._renderService.dimensions.scaledCellHeight}px`; + this._element.style.zIndex = '6'; + this._element.style.position = 'absolute'; + if (this._decorationOptions.x && this._decorationOptions.x < 0) { + throw new Error(`cannot create a decoration with a negative x offset: ${this._decorationOptions.x}`); } - if (this._container.parentElement && !this._container.parentElement.contains(this._element)) { - this._container.parentElement.append(this._element); + if (this._decorationOptions.anchor === 'right') { + this._element.style.right = this._decorationOptions.x ? `${this._decorationOptions.x * this._renderService.dimensions.scaledCellWidth}px` : ''; + } else { + this._element.style.left = this._decorationOptions.x ? `${this._decorationOptions.x * this._renderService.dimensions.scaledCellWidth}px` : ''; + } + } + + private _render(): void { + if (this._screenElement && this._element) { + this._screenElement.append(this._element); + this._onRender.fire(this._element); } - this._onRender.fire(this._element); } } From b2f541d4a69566bbae1ac425b52e305a902219af Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 3 Feb 2022 13:36:11 -0600 Subject: [PATCH 14/59] clean up --- src/browser/services/DecorationsService.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/browser/services/DecorationsService.ts b/src/browser/services/DecorationsService.ts index 00edf569..ecb60020 100644 --- a/src/browser/services/DecorationsService.ts +++ b/src/browser/services/DecorationsService.ts @@ -17,16 +17,19 @@ export interface IDecorationsService extends IDisposable { dispose(): void; } -const enum DefaultButton { - COLOR = '#5DA5D5' -} - export class DecorationsService extends Disposable implements IDecorationsService { + private _decorations: BufferDecoration[] = []; private _animationFrame: number | undefined; - constructor(private readonly _screenElement: HTMLElement, @IBufferService private readonly _bufferService: IBufferService, @IRenderService private readonly _renderService: IRenderService) { + + constructor( + private readonly _screenElement: HTMLElement, + @IBufferService private readonly _bufferService: IBufferService, + @IRenderService private readonly _renderService: IRenderService + ) { super(); } + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { if (decorationOptions.marker.isDisposed) { return undefined; @@ -47,13 +50,11 @@ export class DecorationsService extends Disposable implements IDecorationsServic private _refresh(): void { for (const decoration of this._decorations) { - const adjustedLine = decoration.marker.line - this._bufferService.buffers.active.ydisp; - if (adjustedLine < 0 || adjustedLine > this._bufferService.rows) { - console.log('hide', decoration.id, decoration.marker.line,this._bufferService.buffers.active.ydisp, this._bufferService.rows); + const line = decoration.marker.line - this._bufferService.buffers.active.ydisp; + if (line < 0 || line > this._bufferService.rows) { decoration.element.style.display = 'none'; } else { - console.log('make visible', decoration.id, adjustedLine*this._renderService.dimensions.scaledCharHeight); - decoration.element.style.top = `${(adjustedLine)*this._renderService.dimensions.scaledCellHeight}px`; + decoration.element.style.top = `${(line)*this._renderService.dimensions.scaledCellHeight}px`; decoration.element.style.display = 'block'; } } From 42d403807c290498e5715b3f0438a27f6f2c919e Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 3 Feb 2022 13:44:08 -0600 Subject: [PATCH 15/59] use css class for statics --- css/xterm.css | 5 +++++ src/browser/services/DecorationsService.ts | 6 ++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/css/xterm.css b/css/xterm.css index 38e27a00..4e1aad14 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -173,3 +173,8 @@ .xterm-strikethrough { text-decoration: line-through; } + +.xterm-screen canvas .xterm-decoration { + z-index: 6; + position: absolute; +} diff --git a/src/browser/services/DecorationsService.ts b/src/browser/services/DecorationsService.ts index ecb60020..a8a10916 100644 --- a/src/browser/services/DecorationsService.ts +++ b/src/browser/services/DecorationsService.ts @@ -54,7 +54,7 @@ export class DecorationsService extends Disposable implements IDecorationsServic if (line < 0 || line > this._bufferService.rows) { decoration.element.style.display = 'none'; } else { - decoration.element.style.top = `${(line)*this._renderService.dimensions.scaledCellHeight}px`; + decoration.element.style.top = `${line *this._renderService.dimensions.scaledCellHeight}px`; decoration.element.style.display = 'block'; } } @@ -129,10 +129,8 @@ class BufferDecoration extends Disposable implements IDecoration { this._element.style.width = `${this._decorationOptions.width}px`; this._element.style.height = `${this._decorationOptions.height}px`; this._element.style.top = `${this._marker.line * this._renderService.dimensions.scaledCellHeight}px`; - this._element.style.zIndex = '6'; - this._element.style.position = 'absolute'; if (this._decorationOptions.x && this._decorationOptions.x < 0) { - throw new Error(`cannot create a decoration with a negative x offset: ${this._decorationOptions.x}`); + throw new Error(`Cannot create a decoration with a negative x offset: ${this._decorationOptions.x}`); } if (this._decorationOptions.anchor === 'right') { this._element.style.right = this._decorationOptions.x ? `${this._decorationOptions.x * this._renderService.dimensions.scaledCellWidth}px` : ''; From 8f7db4b270379caefb6f32842c70c5aa0bc2eda2 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 4 Feb 2022 15:53:31 -0600 Subject: [PATCH 16/59] fix buffers not clearing --- src/browser/Terminal.ts | 1 + src/common/buffer/Buffer.ts | 15 ++++++++++++++- src/common/buffer/Types.d.ts | 3 ++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index aafb8a4e..cf95af59 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -1308,6 +1308,7 @@ export class Terminal extends CoreTerminal implements ITerminal { // Don't clear if it's already clear return; } + this.buffer.clearMarkers(); this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)!); this.buffer.lines.length = 1; this.buffer.ydisp = 0; diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index 02ce7c81..e348ad4c 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -16,6 +16,7 @@ import { DEFAULT_CHARSET } from 'common/data/Charsets'; import { ExtendedAttrs } from 'common/buffer/AttributeData'; export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1 +const enum BufferState { CLEARING = 'clearing' } /** * This class represents a terminal buffer (an internal state of the terminal), where the @@ -43,6 +44,7 @@ export class Buffer implements IBuffer { private _whitespaceCell: ICellData = CellData.fromCharData([0, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE]); private _cols: number; private _rows: number; + private _state: string | undefined; constructor( private _hasScrollback: boolean, @@ -584,6 +586,15 @@ export class Buffer implements IBuffer { return x >= this._cols ? this._cols - 1 : x < 0 ? 0 : x; } + public clearMarkers(): void { + this._state = BufferState.CLEARING; + for (const marker of this.markers) { + marker.dispose(); + } + this.markers = []; + this._state = undefined; + } + public addMarker(y: number): Marker { const marker = new Marker(y); this.markers.push(marker); @@ -615,7 +626,9 @@ export class Buffer implements IBuffer { } private _removeMarker(marker: Marker): void { - this.markers.splice(this.markers.indexOf(marker), 1); + if (this._state !== BufferState.CLEARING) { + this.markers.splice(this.markers.indexOf(marker), 1); + } } public iterator(trimRight: boolean, startIndex?: number, endIndex?: number, startOverscan?: number, endOverscan?: number): IBufferStringIterator { diff --git a/src/common/buffer/Types.d.ts b/src/common/buffer/Types.d.ts index cbf40a03..fc97020c 100644 --- a/src/common/buffer/Types.d.ts +++ b/src/common/buffer/Types.d.ts @@ -10,7 +10,7 @@ import { IEvent } from 'common/EventEmitter'; export type BufferIndex = [number, number]; export interface IBufferStringIteratorResult { - range: {first: number, last: number}; + range: { first: number, last: number }; content: string; } @@ -45,6 +45,7 @@ export interface IBuffer { getNullCell(attr?: IAttributeData): ICellData; getWhitespaceCell(attr?: IAttributeData): ICellData; addMarker(y: number): IMarker; + clearMarkers(): void; } export interface IBufferSet extends IDisposable { From ac6877aba7c760f9a196e01f9b6b675105497914 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 7 Feb 2022 10:14:56 -0600 Subject: [PATCH 17/59] clean up --- src/browser/services/DecorationsService.ts | 37 ++++++++++------------ 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/src/browser/services/DecorationsService.ts b/src/browser/services/DecorationsService.ts index a8a10916..6205f42f 100644 --- a/src/browser/services/DecorationsService.ts +++ b/src/browser/services/DecorationsService.ts @@ -34,7 +34,6 @@ export class DecorationsService extends Disposable implements IDecorationsServic if (decorationOptions.marker.isDisposed) { return undefined; } - this._resolveDimensions(decorationOptions); const bufferDecoration = new BufferDecoration(decorationOptions, this._screenElement, this._renderService); this._decorations.push(bufferDecoration); return bufferDecoration; @@ -54,27 +53,13 @@ export class DecorationsService extends Disposable implements IDecorationsServic if (line < 0 || line > this._bufferService.rows) { decoration.element.style.display = 'none'; } else { - decoration.element.style.top = `${line *this._renderService.dimensions.scaledCellHeight}px`; + decoration.element.style.top = `${line * this._renderService.dimensions.scaledCellHeight}px`; decoration.element.style.display = 'block'; } } this._animationFrame = undefined; } - private _resolveDimensions(decorationOptions: IBufferDecorationOptions): void { - if (this._renderService.dimensions.scaledCellWidth) { - decorationOptions.width = decorationOptions.width ? decorationOptions.width * this._renderService.dimensions.scaledCellWidth : this._renderService.dimensions.scaledCellWidth; - } else { - throw new Error('unknown cell width'); - } - - if (this._renderService.dimensions.scaledCellHeight) { - decorationOptions.height = decorationOptions.height ? decorationOptions.height * this._renderService.dimensions.scaledCellHeight : this._renderService.dimensions.scaledCellHeight; - } else { - throw new Error('unknown cell height'); - } - } - public dispose(): void { if (this._animationFrame) { window.cancelAnimationFrame(this._animationFrame); @@ -126,12 +111,9 @@ class BufferDecoration extends Disposable implements IDecoration { private _createElement(): void { this._element = document.createElement('div'); this._element.classList.add('xterm-decoration'); + this._resolveDimensions(); this._element.style.width = `${this._decorationOptions.width}px`; this._element.style.height = `${this._decorationOptions.height}px`; - this._element.style.top = `${this._marker.line * this._renderService.dimensions.scaledCellHeight}px`; - if (this._decorationOptions.x && this._decorationOptions.x < 0) { - throw new Error(`Cannot create a decoration with a negative x offset: ${this._decorationOptions.x}`); - } if (this._decorationOptions.anchor === 'right') { this._element.style.right = this._decorationOptions.x ? `${this._decorationOptions.x * this._renderService.dimensions.scaledCellWidth}px` : ''; } else { @@ -139,6 +121,21 @@ class BufferDecoration extends Disposable implements IDecoration { } } + private _resolveDimensions(): void { + if (this._renderService.dimensions.scaledCellWidth) { + this._decorationOptions.width = this._decorationOptions.width ? this._decorationOptions.width * this._renderService.dimensions.scaledCellWidth : this._renderService.dimensions.scaledCellWidth; + } else { + throw new Error('unknown cell width'); + } + + if (this._renderService.dimensions.scaledCellHeight) { + this._decorationOptions.height = this._decorationOptions.height ? this._decorationOptions.height * this._renderService.dimensions.scaledCellHeight : this._renderService.dimensions.scaledCellHeight; + } else { + throw new Error('unknown cell height'); + } + } + + private _render(): void { if (this._screenElement && this._element) { this._screenElement.append(this._element); From b226eb2d6497304d86396d8dba97f206a2ef7d1c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 7 Feb 2022 14:49:11 -0600 Subject: [PATCH 18/59] throw for negative x --- src/browser/services/DecorationsService.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/browser/services/DecorationsService.ts b/src/browser/services/DecorationsService.ts index 6205f42f..6bac2fea 100644 --- a/src/browser/services/DecorationsService.ts +++ b/src/browser/services/DecorationsService.ts @@ -7,7 +7,7 @@ import { IRenderService } from 'browser/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { createDecorator } from 'common/services/ServiceRegistry'; -import { IBufferService } from 'common/services/Services'; +import { IBufferService, ILogService } from 'common/services/Services'; import { IDisposable } from 'common/Types'; import { IBufferDecorationOptions, IDecoration, IMarker } from 'xterm'; @@ -28,6 +28,7 @@ export class DecorationsService extends Disposable implements IDecorationsServic @IRenderService private readonly _renderService: IRenderService ) { super(); + this._renderService.onRefreshRequest(() => this._refresh()); } public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { @@ -43,7 +44,6 @@ export class DecorationsService extends Disposable implements IDecorationsServic if (this._animationFrame) { return; } - this._animationFrame = window.requestAnimationFrame(() => this._refresh()); } @@ -114,6 +114,11 @@ class BufferDecoration extends Disposable implements IDecoration { this._resolveDimensions(); this._element.style.width = `${this._decorationOptions.width}px`; this._element.style.height = `${this._decorationOptions.height}px`; + + if (this._decorationOptions.x && this._decorationOptions.x < 0) { + throw new Error(`Decoration options x value cannot be negative, but was ${this._decorationOptions.x}`); + } + if (this._decorationOptions.anchor === 'right') { this._element.style.right = this._decorationOptions.x ? `${this._decorationOptions.x * this._renderService.dimensions.scaledCellWidth}px` : ''; } else { From 8bf3cd1102f2d36c8ec764782f5b9d23a7d04982 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 7 Feb 2022 16:06:16 -0600 Subject: [PATCH 19/59] add to test --- src/browser/TestUtils.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 1a29c186..61b6c0d7 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -257,6 +257,9 @@ export class MockBuffer implements IBuffer { public getWhitespaceCell(attr?: IAttributeData): ICellData { throw new Error('Method not implemented.'); } + public clearMarkers(): void { + throw new Error('Method not implemented.'); + } } export class MockRenderer implements IRenderer { From 8b540c5f3b6bc6a020b3aba6e1ef407e86d9fedb Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 7 Feb 2022 16:37:34 -0600 Subject: [PATCH 20/59] set initial top value --- src/browser/services/DecorationsService.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/browser/services/DecorationsService.ts b/src/browser/services/DecorationsService.ts index 6bac2fea..ff617cb1 100644 --- a/src/browser/services/DecorationsService.ts +++ b/src/browser/services/DecorationsService.ts @@ -28,14 +28,13 @@ export class DecorationsService extends Disposable implements IDecorationsServic @IRenderService private readonly _renderService: IRenderService ) { super(); - this._renderService.onRefreshRequest(() => this._refresh()); } public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { if (decorationOptions.marker.isDisposed) { return undefined; } - const bufferDecoration = new BufferDecoration(decorationOptions, this._screenElement, this._renderService); + const bufferDecoration = new BufferDecoration(decorationOptions, this._screenElement, this._renderService, this._bufferService); this._decorations.push(bufferDecoration); return bufferDecoration; } @@ -89,7 +88,8 @@ class BufferDecoration extends Disposable implements IDecoration { constructor( private readonly _decorationOptions: IBufferDecorationOptions, private readonly _screenElement: HTMLElement, - private readonly _renderService: IRenderService + private readonly _renderService: IRenderService, + private readonly _bufferService: IBufferService ) { super(); this._marker = _decorationOptions.marker; @@ -114,7 +114,7 @@ class BufferDecoration extends Disposable implements IDecoration { this._resolveDimensions(); this._element.style.width = `${this._decorationOptions.width}px`; this._element.style.height = `${this._decorationOptions.height}px`; - + this._element.style.top = `${(this.marker.line - this._bufferService.buffers.active.ydisp) * this._renderService.dimensions.scaledCellHeight}px`; if (this._decorationOptions.x && this._decorationOptions.x < 0) { throw new Error(`Decoration options x value cannot be negative, but was ${this._decorationOptions.x}`); } From cae9f44f5c065d04588ee5ddaa4a7266a70a3b86 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Feb 2022 11:45:22 -0600 Subject: [PATCH 21/59] remove mention of buffer --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 2 +- src/browser/Terminal.ts | 4 ++-- src/browser/TestUtils.test.ts | 8 +++---- src/browser/Types.d.ts | 4 ++-- src/browser/public/Terminal.ts | 4 ++-- src/browser/renderer/Renderer.ts | 2 +- src/browser/renderer/Types.d.ts | 2 +- src/browser/renderer/dom/DomRenderer.ts | 4 ++-- src/browser/services/DecorationsService.ts | 22 +++++++++---------- src/browser/services/RenderService.ts | 2 +- src/browser/services/Services.ts | 2 +- typings/xterm.d.ts | 17 ++------------ 12 files changed, 30 insertions(+), 43 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 92b28aa4..a1c1751f 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -13,7 +13,7 @@ import { IWebGL2RenderingContext } from './Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; import { Disposable } from 'common/Lifecycle'; import { Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; -import { Terminal, IEvent, IBufferDecorationOptions, IDecoration } from 'xterm'; +import { Terminal, IEvent, IDecorationOptions, IDecoration } from 'xterm'; import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; import { ITerminal, IColorSet } from 'browser/Types'; diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index cf95af59..e2a29f02 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -37,7 +37,7 @@ import * as Strings from 'browser/LocalizableStrings'; import { SoundService } from 'browser/services/SoundService'; import { MouseZoneManager } from 'browser/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; -import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider, IBufferDecorationOptions, IDecoration } from 'xterm'; +import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm'; import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; import { KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, ColorRequestType } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; @@ -1006,7 +1006,7 @@ export class Terminal extends CoreTerminal implements ITerminal { return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset); } - public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { + public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { if (!this._decorationsService) { throw new Error('cannot register a decoration without a decorations service'); } diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 61b6c0d7..d6d4b95e 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IDisposable, IMarker, ISelectionPosition, ILinkProvider, IBufferDecorationOptions, IDecoration } from 'xterm'; +import { IDisposable, IMarker, ISelectionPosition, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { ICharacterJoinerService, ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; @@ -102,7 +102,7 @@ export class MockTerminal implements ITerminal { public registerLinkProvider(linkProvider: ILinkProvider): IDisposable { throw new Error('Method not implemented.'); } - public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { + public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { throw new Error('Method not implemented.'); } public hasSelection(): boolean { @@ -296,7 +296,7 @@ export class MockRenderer implements IRenderer { public onDevicePixelRatioChange(): void { } public clear(): void { } public renderRows(start: number, end: number): void { } - public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration { + public registerDecoration(decorationOptions: IDecorationOptions): IDecoration { throw new Error('Method not implemented.'); } } @@ -428,7 +428,7 @@ export class MockRenderService implements IRenderService { public dispose(): void { throw new Error('Method not implemented.'); } - public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration { + public registerDecoration(decorationOptions: IDecorationOptions): IDecoration { throw new Error('Method not implemented.'); } } diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 69ba3047..f6152659 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IBufferDecorationOptions, IDecoration, IDisposable, IMarker, ISelectionPosition } from 'xterm'; +import { IDecorationOptions, IDecoration, IDisposable, IMarker, ISelectionPosition } from 'xterm'; import { IEvent } from 'common/EventEmitter'; import { ICoreTerminal, CharData, ITerminalOptions } from 'common/Types'; import { IMouseService, IRenderService } from './services/Services'; @@ -61,7 +61,7 @@ export interface IPublicTerminal extends IDisposable { registerCharacterJoiner(handler: (text: string) => [number, number][]): number; deregisterCharacterJoiner(joinerId: number): void; addMarker(cursorYOffset: number): IMarker | undefined; - registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined; + registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; hasSelection(): boolean; getSelection(): string; getSelectionPosition(): ISelectionPosition | undefined; diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index c641f5f1..1c4c653b 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal as ITerminalApi, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes, IBufferDecorationOptions, IDecoration } from 'xterm'; +import { Terminal as ITerminalApi, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes, IDecorationOptions, IDecoration } from 'xterm'; import { ITerminal } from 'browser/Types'; import { Terminal as TerminalCore } from 'browser/Terminal'; import * as Strings from 'browser/LocalizableStrings'; @@ -171,7 +171,7 @@ export class Terminal implements ITerminalApi { this._verifyIntegers(cursorYOffset); return this._core.addMarker(cursorYOffset); } - public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { + public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { this._checkProposedApi(); return this._core.registerDecoration(decorationOptions); } diff --git a/src/browser/renderer/Renderer.ts b/src/browser/renderer/Renderer.ts index 305808a2..a58893b4 100644 --- a/src/browser/renderer/Renderer.ts +++ b/src/browser/renderer/Renderer.ts @@ -14,7 +14,7 @@ import { ICharSizeService } from 'browser/services/Services'; import { IBufferService, IOptionsService, IInstantiationService } from 'common/services/Services'; import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { IBufferDecorationOptions, IDecoration } from 'xterm'; +import { IDecorationOptions, IDecoration } from 'xterm'; let nextRendererId = 1; diff --git a/src/browser/renderer/Types.d.ts b/src/browser/renderer/Types.d.ts index c5387173..ab6f8df1 100644 --- a/src/browser/renderer/Types.d.ts +++ b/src/browser/renderer/Types.d.ts @@ -6,7 +6,7 @@ import { IDisposable } from 'common/Types'; import { IColorSet } from 'browser/Types'; import { IEvent } from 'common/EventEmitter'; -import { IBufferDecorationOptions, IDecoration } from 'xterm'; +import { IDecorationOptions, IDecoration } from 'xterm'; export interface IRenderDimensions { scaledCharWidth: number; diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 2057c791..d3d3fb3c 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -13,7 +13,7 @@ import { IOptionsService, IBufferService, IInstantiationService } from 'common/s import { EventEmitter, IEvent } from 'common/EventEmitter'; import { color } from 'browser/Color'; import { removeElementFromParent } from 'browser/Dom'; -import { IBufferDecorationOptions, IDecoration } from 'xterm'; +import { IDecorationOptions, IDecoration } from 'xterm'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; @@ -152,7 +152,7 @@ export class DomRenderer extends Disposable implements IRenderer { this._injectCss(); } - public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration { + public registerDecoration(decorationOptions: IDecorationOptions): IDecoration { // const decorationLayer = this._renderLayers.find(l => l instanceof DecorationRenderLayer); // if (decorationLayer instanceof DecorationRenderLayer) { // return decorationLayer.registerDecoration(decorationOptions); diff --git a/src/browser/services/DecorationsService.ts b/src/browser/services/DecorationsService.ts index ff617cb1..83b94a20 100644 --- a/src/browser/services/DecorationsService.ts +++ b/src/browser/services/DecorationsService.ts @@ -7,19 +7,19 @@ import { IRenderService } from 'browser/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { createDecorator } from 'common/services/ServiceRegistry'; -import { IBufferService, ILogService } from 'common/services/Services'; +import { IBufferService } from 'common/services/Services'; import { IDisposable } from 'common/Types'; -import { IBufferDecorationOptions, IDecoration, IMarker } from 'xterm'; +import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; export interface IDecorationsService extends IDisposable { - registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined; + registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; refresh(): void; dispose(): void; } export class DecorationsService extends Disposable implements IDecorationsService { - private _decorations: BufferDecoration[] = []; + private _decorations: Decoration[] = []; private _animationFrame: number | undefined; constructor( @@ -30,13 +30,13 @@ export class DecorationsService extends Disposable implements IDecorationsServic super(); } - public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { + public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { if (decorationOptions.marker.isDisposed) { return undefined; } - const bufferDecoration = new BufferDecoration(decorationOptions, this._screenElement, this._renderService, this._bufferService); - this._decorations.push(bufferDecoration); - return bufferDecoration; + const decoration = new Decoration(decorationOptions, this._screenElement, this._renderService, this._bufferService); + this._decorations.push(decoration); + return decoration; } public refresh(): void { @@ -68,11 +68,11 @@ export class DecorationsService extends Disposable implements IDecorationsServic } export const IDecorationsService = createDecorator('DecorationsService'); -class BufferDecoration extends Disposable implements IDecoration { +class Decoration extends Disposable implements IDecoration { private static _nextId = 1; private _marker: IMarker; private _element: HTMLElement | undefined; - private _id: number = BufferDecoration._nextId++; + private _id: number = Decoration._nextId++; public isDisposed: boolean = false; public get id(): number { return this._id; } @@ -86,7 +86,7 @@ class BufferDecoration extends Disposable implements IDecoration { public get onRender(): IEvent { return this._onRender.event; } constructor( - private readonly _decorationOptions: IBufferDecorationOptions, + private readonly _decorationOptions: IDecorationOptions, private readonly _screenElement: HTMLElement, private readonly _renderService: IRenderService, private readonly _bufferService: IBufferService diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 79faf18a..8c5ff24d 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -12,7 +12,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { IColorSet, IRenderDebouncer } from 'browser/Types'; import { IOptionsService, IBufferService } from 'common/services/Services'; import { ICharSizeService, IRenderService } from 'browser/services/Services'; -import { IDecoration, IBufferDecorationOptions } from 'xterm'; +import { IDecoration, IDecorationOptions } from 'xterm'; interface ISelectionState { start: [number, number] | undefined; diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 8b7a0a77..25775d05 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -9,7 +9,7 @@ import { IColorSet } from 'browser/Types'; import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; import { IDisposable } from 'common/Types'; -import { IBufferDecorationOptions, IDecoration } from 'xterm'; +import { IDecorationOptions, IDecoration } from 'xterm'; export const ICharSizeService = createDecorator('CharSizeService'); export interface ICharSizeService { diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index a7f80a15..0b3db559 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -434,7 +434,7 @@ declare module 'xterm' { readonly element: HTMLElement | undefined; } - export interface IBufferDecorationOptions { + export interface IDecorationOptions { /** * The line in the terminal where * the decoration will be displayed @@ -467,19 +467,6 @@ declare module 'xterm' { } - export interface IGutterDecorationOptions { - /** - * The line in the terminal where - * the decoration will be displayed - */ - startMarker: IMarker; - /** - * The end line in the terminal for - * the decoration - */ - endMarker: IMarker; - } - /** * The set of localizable strings. */ @@ -951,7 +938,7 @@ declare module 'xterm' { * width, height, and x offset from the anchor. Returns the decoration or * undefined if the marker has already been disposed of. */ - registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined; + registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; /** * Gets whether the terminal has an active selection. From 19b9d0eadd7c271d5d83b31238f303e35a3d61d4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Feb 2022 12:05:38 -0600 Subject: [PATCH 22/59] cleanup --- src/browser/Types.d.ts | 2 +- src/browser/renderer/dom/DomRenderer.ts | 8 -------- src/browser/services/DecorationsService.ts | 1 - src/browser/services/Services.ts | 1 - 4 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index f6152659..35b52d62 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -7,7 +7,7 @@ import { IDecorationOptions, IDecoration, IDisposable, IMarker, ISelectionPositi import { IEvent } from 'common/EventEmitter'; import { ICoreTerminal, CharData, ITerminalOptions } from 'common/Types'; import { IMouseService, IRenderService } from './services/Services'; -import { IBuffer, IBufferSet } from 'common/buffer/Types'; +import { IBuffer } from 'common/buffer/Types'; import { IFunctionIdentifier, IParams } from 'common/parser/Types'; export interface ITerminal extends IPublicTerminal, ICoreTerminal { diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index d3d3fb3c..6c9f5e2f 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -152,14 +152,6 @@ export class DomRenderer extends Disposable implements IRenderer { this._injectCss(); } - public registerDecoration(decorationOptions: IDecorationOptions): IDecoration { - // const decorationLayer = this._renderLayers.find(l => l instanceof DecorationRenderLayer); - // if (decorationLayer instanceof DecorationRenderLayer) { - // return decorationLayer.registerDecoration(decorationOptions); - // } - throw new Error('no decoration layer'); - } - private _injectCss(): void { if (!this._themeStyleElement) { this._themeStyleElement = document.createElement('style'); diff --git a/src/browser/services/DecorationsService.ts b/src/browser/services/DecorationsService.ts index 83b94a20..cfa69fb5 100644 --- a/src/browser/services/DecorationsService.ts +++ b/src/browser/services/DecorationsService.ts @@ -140,7 +140,6 @@ class Decoration extends Disposable implements IDecoration { } } - private _render(): void { if (this._screenElement && this._element) { this._screenElement.append(this._element); diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 25775d05..a085446b 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -50,7 +50,6 @@ export interface IRenderService extends IDisposable { */ onRenderedBufferChange: IEvent<{ start: number, end: number }>; onRefreshRequest: IEvent<{ start: number, end: number }>; - dimensions: IRenderDimensions; refreshRows(start: number, end: number): void; clearTextureAtlas(): void; From 32993158223c1d77c3ba15624f5b269a2998e75a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Feb 2022 12:26:19 -0600 Subject: [PATCH 23/59] remove elt from screen on dispose --- src/browser/services/DecorationsService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/browser/services/DecorationsService.ts b/src/browser/services/DecorationsService.ts index cfa69fb5..610355de 100644 --- a/src/browser/services/DecorationsService.ts +++ b/src/browser/services/DecorationsService.ts @@ -101,6 +101,7 @@ class Decoration extends Disposable implements IDecoration { if (this.isDisposed) { return; } + this._screenElement.removeChild(this.element); this.isDisposed = true; this._marker.dispose(); // Emit before super.dispose such that dispose listeners get a change to react From 094763ea8bca33312da2806e02fdae8442a422fe Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Feb 2022 14:03:49 -0600 Subject: [PATCH 24/59] Revert changes to webglRenderer --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index a1c1751f..62a10187 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -13,7 +13,7 @@ import { IWebGL2RenderingContext } from './Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; import { Disposable } from 'common/Lifecycle'; import { Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; -import { Terminal, IEvent, IDecorationOptions, IDecoration } from 'xterm'; +import { Terminal, IEvent } from 'xterm'; import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; import { ITerminal, IColorSet } from 'browser/Types'; @@ -23,7 +23,6 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { ICharacterJoinerService } from 'browser/services/Services'; import { CharData, ICellData } from 'common/Types'; import { AttributeData } from 'common/buffer/AttributeData'; -import { IBufferService } from 'common/services/Services'; export class WebglRenderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; @@ -58,10 +57,10 @@ export class WebglRenderer extends Disposable implements IRenderer { super(); this._core = (this._terminal as any)._core; + this._renderLayers = [ new LinkRenderLayer(this._core.screenElement!, 2, this._colors, this._core), new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._colors, this._core, this._onRequestRedraw) - // new DecorationRenderLayer(this._core.screenElement!, 3, this._colors, this._id, this._onRequestRedraw) ]; this.dimensions = { scaledCharWidth: 0, @@ -116,7 +115,7 @@ export class WebglRenderer extends Disposable implements IRenderer { public get textureAtlas(): HTMLCanvasElement | undefined { return this._charAtlas?.cacheCanvas; } - + public setColors(colors: IColorSet): void { this._colors = colors; // Clear layers and force a full render @@ -338,8 +337,8 @@ export class WebglRenderer extends Disposable implements IRenderer { // Nothing has changed, no updates needed if (this._model.cells[i] === code && - this._model.cells[i + RENDER_MODEL_BG_OFFSET] === cell.bg && - this._model.cells[i + RENDER_MODEL_FG_OFFSET] === cell.fg) { + this._model.cells[i + RENDER_MODEL_BG_OFFSET] === cell.bg && + this._model.cells[i + RENDER_MODEL_FG_OFFSET] === cell.fg) { continue; } From 275ae6b2d3af95bd54021199b1cb282b91d4a5a4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Feb 2022 14:20:18 -0600 Subject: [PATCH 25/59] DecorationsService -> DecorationService --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 4 ++-- src/browser/Terminal.ts | 15 +++++++------ src/browser/TestUtils.test.ts | 6 ++--- src/browser/renderer/Types.d.ts | 1 - src/browser/renderer/dom/DomRenderer.ts | 1 - src/browser/services/DecorationsService.ts | 22 +++++++------------ typings/xterm.d.ts | 3 ++- 7 files changed, 23 insertions(+), 29 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 62a10187..24e55fed 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -337,8 +337,8 @@ export class WebglRenderer extends Disposable implements IRenderer { // Nothing has changed, no updates needed if (this._model.cells[i] === code && - this._model.cells[i + RENDER_MODEL_BG_OFFSET] === cell.bg && - this._model.cells[i + RENDER_MODEL_FG_OFFSET] === cell.fg) { + this._model.cells[i + RENDER_MODEL_BG_OFFSET] === cell.bg && + this._model.cells[i + RENDER_MODEL_FG_OFFSET] === cell.fg) { continue; } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index e2a29f02..abf985b7 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -55,7 +55,7 @@ import { CoreTerminal } from 'common/CoreTerminal'; import { color, rgba } from 'browser/Color'; import { CharacterJoinerService } from 'browser/services/CharacterJoinerService'; import { toRgbString } from 'common/input/XParseColor'; -import { DecorationsService, IDecorationsService } from 'browser/services/DecorationsService'; +import { DecorationService, IDecorationService } from 'browser/services/DecorationsService'; // Let it work inside Node.js for automated testing purposes. const document: Document = (typeof window !== 'undefined') ? window.document : null as any; @@ -81,7 +81,7 @@ export class Terminal extends CoreTerminal implements ITerminal { private _charSizeService: ICharSizeService | undefined; private _mouseService: IMouseService | undefined; private _renderService: IRenderService | undefined; - private _decorationsService: IDecorationsService | undefined; + private _decorationsService: IDecorationService | undefined; private _characterJoinerService: ICharacterJoinerService | undefined; private _selectionService: ISelectionService | undefined; private _soundService: ISoundService | undefined; @@ -515,7 +515,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._renderService.onRenderedBufferChange(e => this._onRender.fire(e))); this.onResize(e => this._renderService!.resize(e.cols, e.rows)); - this._decorationsService = this.register(this._instantiationService.createInstance(DecorationsService, this.screenElement)); + this._decorationsService = this.register(this._instantiationService.createInstance(DecorationService, this.screenElement)); this._compositionView = document.createElement('div'); this._compositionView.classList.add('composition-view'); @@ -1007,12 +1007,13 @@ export class Terminal extends CoreTerminal implements ITerminal { } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - if (!this._decorationsService) { - throw new Error('cannot register a decoration without a decorations service'); + // Disallow decorations on the alt buffer + if (this.buffer !== this.buffers.normal) { + return undefined; } - - return this._decorationsService.registerDecoration(decorationOptions); + return this._decorationsService!.registerDecoration(decorationOptions); } + /** * Gets whether the terminal has an active selection. */ diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index d6d4b95e..10a1435b 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -286,6 +286,9 @@ export class MockRenderer implements IRenderer { public setColors(colors: IColorSet): void { throw new Error('Method not implemented.'); } + public registerDecoration(decorationOptions: IDecorationOptions): IDecoration { + throw new Error('Method not implemented.'); + } public onResize(cols: number, rows: number): void { } public onCharSizeChanged(): void { } public onBlur(): void { } @@ -296,9 +299,6 @@ export class MockRenderer implements IRenderer { public onDevicePixelRatioChange(): void { } public clear(): void { } public renderRows(start: number, end: number): void { } - public registerDecoration(decorationOptions: IDecorationOptions): IDecoration { - throw new Error('Method not implemented.'); - } } export class MockViewport implements IViewport { diff --git a/src/browser/renderer/Types.d.ts b/src/browser/renderer/Types.d.ts index ab6f8df1..6818a926 100644 --- a/src/browser/renderer/Types.d.ts +++ b/src/browser/renderer/Types.d.ts @@ -6,7 +6,6 @@ import { IDisposable } from 'common/Types'; import { IColorSet } from 'browser/Types'; import { IEvent } from 'common/EventEmitter'; -import { IDecorationOptions, IDecoration } from 'xterm'; export interface IRenderDimensions { scaledCharWidth: number; diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 6c9f5e2f..ee283399 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -13,7 +13,6 @@ import { IOptionsService, IBufferService, IInstantiationService } from 'common/s import { EventEmitter, IEvent } from 'common/EventEmitter'; import { color } from 'browser/Color'; import { removeElementFromParent } from 'browser/Dom'; -import { IDecorationOptions, IDecoration } from 'xterm'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; diff --git a/src/browser/services/DecorationsService.ts b/src/browser/services/DecorationsService.ts index 610355de..36aabf9a 100644 --- a/src/browser/services/DecorationsService.ts +++ b/src/browser/services/DecorationsService.ts @@ -11,13 +11,13 @@ import { IBufferService } from 'common/services/Services'; import { IDisposable } from 'common/Types'; import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; -export interface IDecorationsService extends IDisposable { +export interface IDecorationService extends IDisposable { registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; refresh(): void; dispose(): void; } -export class DecorationsService extends Disposable implements IDecorationsService { +export class DecorationService extends Disposable implements IDecorationService { private _decorations: Decoration[] = []; private _animationFrame: number | undefined; @@ -67,7 +67,7 @@ export class DecorationsService extends Disposable implements IDecorationsServic } } -export const IDecorationsService = createDecorator('DecorationsService'); +export const IDecorationService = createDecorator('DecorationsService'); class Decoration extends Disposable implements IDecoration { private static _nextId = 1; private _marker: IMarker; @@ -117,7 +117,7 @@ class Decoration extends Disposable implements IDecoration { this._element.style.height = `${this._decorationOptions.height}px`; this._element.style.top = `${(this.marker.line - this._bufferService.buffers.active.ydisp) * this._renderService.dimensions.scaledCellHeight}px`; if (this._decorationOptions.x && this._decorationOptions.x < 0) { - throw new Error(`Decoration options x value cannot be negative, but was ${this._decorationOptions.x}`); + throw new Error(`Decoration options x value cannot be negative, but was ${this._decorationOptions.x}.`); } if (this._decorationOptions.anchor === 'right') { @@ -128,17 +128,11 @@ class Decoration extends Disposable implements IDecoration { } private _resolveDimensions(): void { - if (this._renderService.dimensions.scaledCellWidth) { - this._decorationOptions.width = this._decorationOptions.width ? this._decorationOptions.width * this._renderService.dimensions.scaledCellWidth : this._renderService.dimensions.scaledCellWidth; - } else { - throw new Error('unknown cell width'); - } - - if (this._renderService.dimensions.scaledCellHeight) { - this._decorationOptions.height = this._decorationOptions.height ? this._decorationOptions.height * this._renderService.dimensions.scaledCellHeight : this._renderService.dimensions.scaledCellHeight; - } else { - throw new Error('unknown cell height'); + if (!this._renderService.dimensions.scaledCellWidth || !this._renderService.dimensions.scaledCellHeight) { + throw new Error(`Cannot resolve dimensions for decoration when scaled cell dimensions are undefined ${this._renderService.dimensions}.`); } + this._decorationOptions.width = this._decorationOptions.width ? this._decorationOptions.width * this._renderService.dimensions.scaledCellWidth : this._renderService.dimensions.scaledCellWidth; + this._decorationOptions.height = this._decorationOptions.height ? this._decorationOptions.height * this._renderService.dimensions.scaledCellHeight : this._renderService.dimensions.scaledCellHeight; } private _render(): void { diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 0b3db559..91b992f9 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -936,7 +936,8 @@ declare module 'xterm' { * (EXPERIMENTAL) Adds a decoration to the terminal using * @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 marker has already been disposed of. + * undefined if the alt buffer is active or the marker has already been disposed of. + * @throws if the @param decorationOptions includes a negative x offset. */ registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; From 01b66b756f1d8ad5fb903c568c7d8598692febb6 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Feb 2022 14:22:37 -0600 Subject: [PATCH 26/59] revert formatting changes --- src/browser/services/Services.ts | 2 ++ src/common/buffer/Buffer.ts | 2 -- src/common/buffer/Types.d.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index a085446b..9dda0a82 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -50,7 +50,9 @@ export interface IRenderService extends IDisposable { */ onRenderedBufferChange: IEvent<{ start: number, end: number }>; onRefreshRequest: IEvent<{ start: number, end: number }>; + dimensions: IRenderDimensions; + refreshRows(start: number, end: number): void; clearTextureAtlas(): void; resize(cols: number, rows: number): void; diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index 2d50e8a0..9b25595f 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -16,8 +16,6 @@ import { DEFAULT_CHARSET } from 'common/data/Charsets'; import { ExtendedAttrs } from 'common/buffer/AttributeData'; export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1 -const enum BufferState { CLEARING = 'clearing' } - /** * This class represents a terminal buffer (an internal state of the terminal), where the * following information is stored (in high-level): diff --git a/src/common/buffer/Types.d.ts b/src/common/buffer/Types.d.ts index fc97020c..9259d46d 100644 --- a/src/common/buffer/Types.d.ts +++ b/src/common/buffer/Types.d.ts @@ -10,7 +10,7 @@ import { IEvent } from 'common/EventEmitter'; export type BufferIndex = [number, number]; export interface IBufferStringIteratorResult { - range: { first: number, last: number }; + range: {first: number, last: number}; content: string; } From 3e5ecbf52b7c6f976ed05cc50946dd8b247d0f22 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Feb 2022 14:23:59 -0600 Subject: [PATCH 27/59] more formatting --- src/browser/services/RenderService.ts | 1 - src/browser/services/Services.ts | 1 - src/common/buffer/Buffer.ts | 1 + 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 8c5ff24d..da458abc 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -12,7 +12,6 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { IColorSet, IRenderDebouncer } from 'browser/Types'; import { IOptionsService, IBufferService } from 'common/services/Services'; import { ICharSizeService, IRenderService } from 'browser/services/Services'; -import { IDecoration, IDecorationOptions } from 'xterm'; interface ISelectionState { start: [number, number] | undefined; diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 9dda0a82..4928fa28 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -9,7 +9,6 @@ import { IColorSet } from 'browser/Types'; import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; import { IDisposable } from 'common/Types'; -import { IDecorationOptions, IDecoration } from 'xterm'; export const ICharSizeService = createDecorator('CharSizeService'); export interface ICharSizeService { diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index 9b25595f..8addf45a 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -16,6 +16,7 @@ import { DEFAULT_CHARSET } from 'common/data/Charsets'; import { ExtendedAttrs } from 'common/buffer/AttributeData'; export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1 + /** * This class represents a terminal buffer (an internal state of the terminal), where the * following information is stored (in high-level): From 723e9158958a6544a278befa41299965221a93bd Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Feb 2022 14:25:24 -0600 Subject: [PATCH 28/59] more decorationsService -> singular --- src/browser/Terminal.ts | 12 ++++++------ .../{DecorationsService.ts => DecorationService.ts} | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) rename src/browser/services/{DecorationsService.ts => DecorationService.ts} (99%) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index abf985b7..fec8ee2a 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -55,7 +55,7 @@ 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, IDecorationService } from 'browser/services/DecorationsService'; +import { DecorationService, IDecorationService } from 'browser/services/DecorationService'; // Let it work inside Node.js for automated testing purposes. const document: Document = (typeof window !== 'undefined') ? window.document : null as any; @@ -81,7 +81,7 @@ export class Terminal extends CoreTerminal implements ITerminal { private _charSizeService: ICharSizeService | undefined; private _mouseService: IMouseService | undefined; private _renderService: IRenderService | undefined; - private _decorationsService: IDecorationService | undefined; + private _decorationService: IDecorationService | undefined; private _characterJoinerService: ICharacterJoinerService | undefined; private _selectionService: ISelectionService | undefined; private _soundService: ISoundService | undefined; @@ -515,7 +515,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._renderService.onRenderedBufferChange(e => this._onRender.fire(e))); this.onResize(e => this._renderService!.resize(e.cols, e.rows)); - this._decorationsService = this.register(this._instantiationService.createInstance(DecorationService, this.screenElement)); + this._decorationService = this.register(this._instantiationService.createInstance(DecorationService, this.screenElement)); this._compositionView = document.createElement('div'); this._compositionView.classList.add('composition-view'); @@ -569,11 +569,11 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._onScroll.event(ev => { this.viewport!.syncScrollArea(); this._selectionService!.refresh(); - this._decorationsService!.refresh(); + this._decorationService!.refresh(); })); this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => { this._selectionService!.refresh(); - this._decorationsService!.refresh(); + this._decorationService!.refresh(); })); this._mouseZoneManager = this._instantiationService.createInstance(MouseZoneManager, this.element, this.screenElement); @@ -1011,7 +1011,7 @@ export class Terminal extends CoreTerminal implements ITerminal { if (this.buffer !== this.buffers.normal) { return undefined; } - return this._decorationsService!.registerDecoration(decorationOptions); + return this._decorationService!.registerDecoration(decorationOptions); } /** diff --git a/src/browser/services/DecorationsService.ts b/src/browser/services/DecorationService.ts similarity index 99% rename from src/browser/services/DecorationsService.ts rename to src/browser/services/DecorationService.ts index 36aabf9a..f3f1c3fa 100644 --- a/src/browser/services/DecorationsService.ts +++ b/src/browser/services/DecorationService.ts @@ -67,7 +67,7 @@ export class DecorationService extends Disposable implements IDecorationService } } -export const IDecorationService = createDecorator('DecorationsService'); +export const IDecorationService = createDecorator('DecorationService'); class Decoration extends Disposable implements IDecoration { private static _nextId = 1; private _marker: IMarker; From 571bf9e533a1a0f1ffa0af010eee50c959810f5b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Feb 2022 14:32:29 -0600 Subject: [PATCH 29/59] fix bouncing on re render --- src/browser/Terminal.ts | 6 +----- src/browser/services/DecorationService.ts | 21 ++++++--------------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index fec8ee2a..14a107f9 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -569,12 +569,8 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._onScroll.event(ev => { this.viewport!.syncScrollArea(); this._selectionService!.refresh(); - this._decorationService!.refresh(); - })); - this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => { - this._selectionService!.refresh(); - this._decorationService!.refresh(); })); + this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => this._selectionService!.refresh())); this._mouseZoneManager = this._instantiationService.createInstance(MouseZoneManager, this.element, this.screenElement); this.register(this._mouseZoneManager); diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index f3f1c3fa..907d568c 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -20,7 +20,6 @@ export interface IDecorationService extends IDisposable { export class DecorationService extends Disposable implements IDecorationService { private _decorations: Decoration[] = []; - private _animationFrame: number | undefined; constructor( private readonly _screenElement: HTMLElement, @@ -28,6 +27,7 @@ export class DecorationService extends Disposable implements IDecorationService @IRenderService private readonly _renderService: IRenderService ) { super(); + this._renderService.onRefreshRequest(() => this.refresh()); } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { @@ -40,29 +40,20 @@ export class DecorationService extends Disposable implements IDecorationService } public refresh(): void { - if (this._animationFrame) { - return; - } - this._animationFrame = window.requestAnimationFrame(() => this._refresh()); - } - - private _refresh(): void { for (const decoration of this._decorations) { - const line = decoration.marker.line - this._bufferService.buffers.active.ydisp; - if (line < 0 || line > this._bufferService.rows) { + if ((decoration.marker.line - this._bufferService.buffers.active.ydisp) < 0 || (decoration.marker.line - this._bufferService.buffers.active.ydisp) > this._bufferService.rows) { + // outside of viewport decoration.element.style.display = 'none'; } else { - decoration.element.style.top = `${line * this._renderService.dimensions.scaledCellHeight}px`; + decoration.element.style.top = `${(decoration.marker.line - this._bufferService.buffers.active.ydisp) * this._renderService.dimensions.scaledCellHeight}px`; decoration.element.style.display = 'block'; } } - this._animationFrame = undefined; } public dispose(): void { - if (this._animationFrame) { - window.cancelAnimationFrame(this._animationFrame); - this._animationFrame = undefined; + for (const decoration of this._decorations) { + decoration.dispose(); } } } From d8234affa4ebd4d392732197652f3e175d671d3f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Feb 2022 14:35:33 -0600 Subject: [PATCH 30/59] move listener into terminal.ts --- src/browser/Terminal.ts | 1 + src/browser/services/DecorationService.ts | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 14a107f9..02f039f7 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -516,6 +516,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.onResize(e => this._renderService!.resize(e.cols, e.rows)); this._decorationService = this.register(this._instantiationService.createInstance(DecorationService, this.screenElement)); + this.register(this._renderService.onRefreshRequest(() => this._decorationService?.refresh())); this._compositionView = document.createElement('div'); this._compositionView.classList.add('composition-view'); diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 907d568c..cb3a9643 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -27,7 +27,6 @@ export class DecorationService extends Disposable implements IDecorationService @IRenderService private readonly _renderService: IRenderService ) { super(); - this._renderService.onRefreshRequest(() => this.refresh()); } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { From 58c2ba531061e9c71429e1f3b01d0eba9b7387bd Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Feb 2022 14:41:33 -0600 Subject: [PATCH 31/59] remove empty line --- typings/xterm.d.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 91b992f9..5ba7dcf5 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -464,7 +464,6 @@ declare module 'xterm' { * cell height */ height?: number; - } /** From a9722371e180a560eb21175bfbc953b7835d043e Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Feb 2022 15:14:36 -0600 Subject: [PATCH 32/59] add decoration test to demo --- demo/client.ts | 8 ++++++++ demo/index.html | 1 + 2 files changed, 9 insertions(+) diff --git a/demo/client.ts b/demo/client.ts index 58d719a1..0ca4d4bf 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -149,6 +149,7 @@ if (document.location.pathname === '/test') { document.getElementById('serialize').addEventListener('click', serializeButtonHandler); document.getElementById('custom-glyph').addEventListener('click', writeCustomGlyphHandler); document.getElementById('load-test').addEventListener('click', loadTest); + document.getElementById('decoration').addEventListener('click', decoration); } function createTerminal(): void { @@ -525,3 +526,10 @@ function loadTest() { term._core._onData.fire('\x03'); }); } + +function decoration() { + const marker = term.addMarker(1); + const decoration = term.registerDecoration({ marker }); + decoration.element.style.backgroundColor = 'red'; + decoration.element.style.position = 'absolute'; +} diff --git a/demo/index.html b/demo/index.html index 9c86783b..24ee68b5 100644 --- a/demo/index.html +++ b/demo/index.html @@ -64,6 +64,7 @@ + From 5fd7216bf294cfeb086b69699e4d9b0650c16ea9 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Feb 2022 15:57:43 -0600 Subject: [PATCH 33/59] add api tests --- css/xterm.css | 2 +- demo/client.ts | 3 +-- test/api/Terminal.api.ts | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/css/xterm.css b/css/xterm.css index 4e1aad14..ac8cd45b 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -174,7 +174,7 @@ text-decoration: line-through; } -.xterm-screen canvas .xterm-decoration { +#terminal-container > div > div.xterm-screen > div.xterm-decoration { z-index: 6; position: absolute; } diff --git a/demo/client.ts b/demo/client.ts index 0ca4d4bf..193b78b0 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -531,5 +531,4 @@ function decoration() { const marker = term.addMarker(1); const decoration = term.registerDecoration({ marker }); decoration.element.style.backgroundColor = 'red'; - decoration.element.style.position = 'absolute'; -} +} \ No newline at end of file diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 1fe75456..c10094ca 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -731,6 +731,28 @@ describe('API Integration Tests', function(): void { await pollFor(page, `window.term._core._renderService.dimensions.actualCellWidth > 0`, true); }); + describe.only('registerDecoration', () => { + it('should register a decoration', async () => { + await openTerminal(page); + await page.evaluate(`window.marker = window.term.addMarker(1)`); + await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker });`); + assert.notEqual(await page.evaluate(`document.querySelector('.xterm-screen .xterm-decoration')`), undefined); + }); + 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); + assert.equal(await page.evaluate(`document.querySelector('.xterm-screen .xterm-decoration')`), undefined); + }); + it.skip('should throw when a negative x offset is provided', async () => { + await openTerminal(page); + await page.evaluate(`window.marker = window.term.addMarker(1)`); + assert.throws(async () => await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker, x: -2 });`)); + assert.equal(await page.evaluate(`document.querySelector('.xterm-screen .xterm-decoration')`), undefined); + }); + }); + describe('registerLinkProvider', () => { it('should fire provideLinks when hovering cells', async () => { await openTerminal(page, { rendererType: 'dom' }); From 88bd33f2d79f21794fc166c252601f8e1131909b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Feb 2022 17:27:15 -0600 Subject: [PATCH 34/59] bring jumpyness back --- src/browser/Terminal.ts | 1 - src/browser/services/DecorationService.ts | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 02f039f7..14a107f9 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -516,7 +516,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this.onResize(e => this._renderService!.resize(e.cols, e.rows)); this._decorationService = this.register(this._instantiationService.createInstance(DecorationService, this.screenElement)); - this.register(this._renderService.onRefreshRequest(() => this._decorationService?.refresh())); this._compositionView = document.createElement('div'); this._compositionView.classList.add('composition-view'); diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index cb3a9643..cfe10045 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -27,6 +27,7 @@ export class DecorationService extends Disposable implements IDecorationService @IRenderService private readonly _renderService: IRenderService ) { super(); + this.register(this._renderService.onRenderedBufferChange(() => this.refresh())); } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { From ad9818fb1a1f754fc584cf823364fba58187cba7 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 8 Feb 2022 20:29:20 -0600 Subject: [PATCH 35/59] 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 5ba7dcf5..77fae602 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -936,7 +936,7 @@ declare module 'xterm' { * @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 if the @param decorationOptions includes a negative x offset. + * @throws when options include a negative x offset. */ registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; From 53606820a7f11e8a3d536a44d7cc87aa875da581 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 8 Feb 2022 21:02:15 -0600 Subject: [PATCH 36/59] Update src/browser/services/DecorationService.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/browser/services/DecorationService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index cfe10045..992cd339 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -19,7 +19,7 @@ export interface IDecorationService extends IDisposable { export class DecorationService extends Disposable implements IDecorationService { - private _decorations: Decoration[] = []; + private readonly _decorations: Decoration[] = []; constructor( private readonly _screenElement: HTMLElement, From f2991a5636d8791c900185ea8f2f6c6f206a25cc Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Feb 2022 21:10:34 -0600 Subject: [PATCH 37/59] cleanup --- css/xterm.css | 2 +- demo/client.ts | 4 +- src/browser/Terminal.ts | 13 +++--- src/browser/public/Terminal.ts | 3 ++ src/browser/services/DecorationService.ts | 54 ++++++++++------------- src/browser/services/Services.ts | 9 ++++ test/api/Terminal.api.ts | 13 ++++-- typings/xterm.d.ts | 6 +++ 8 files changed, 62 insertions(+), 42 deletions(-) diff --git a/css/xterm.css b/css/xterm.css index ac8cd45b..f8bfcd23 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -174,7 +174,7 @@ text-decoration: line-through; } -#terminal-container > div > div.xterm-screen > div.xterm-decoration { +.xterm-screen .xterm-decorations .xterm-decoration { z-index: 6; position: absolute; } diff --git a/demo/client.ts b/demo/client.ts index 193b78b0..977bcfd8 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -149,7 +149,7 @@ if (document.location.pathname === '/test') { document.getElementById('serialize').addEventListener('click', serializeButtonHandler); document.getElementById('custom-glyph').addEventListener('click', writeCustomGlyphHandler); document.getElementById('load-test').addEventListener('click', loadTest); - document.getElementById('decoration').addEventListener('click', decoration); + document.getElementById('decoration').addEventListener('click', addDecoration); } function createTerminal(): void { @@ -527,7 +527,7 @@ function loadTest() { }); } -function decoration() { +function addDecoration() { const marker = term.addMarker(1); const decoration = term.registerDecoration({ marker }); decoration.element.style.backgroundColor = 'red'; diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 14a107f9..bd79b7c5 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 } from 'browser/services/Services'; +import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService, ICharacterJoinerService, IDecorationService } 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,7 @@ 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, IDecorationService } from 'browser/services/DecorationService'; +import { DecorationService } from 'browser/services/DecorationService'; // Let it work inside Node.js for automated testing purposes. const document: Document = (typeof window !== 'undefined') ? window.document : null as any; @@ -81,7 +81,6 @@ export class Terminal extends CoreTerminal implements ITerminal { private _charSizeService: ICharSizeService | undefined; private _mouseService: IMouseService | undefined; private _renderService: IRenderService | undefined; - private _decorationService: IDecorationService | undefined; private _characterJoinerService: ICharacterJoinerService | undefined; private _selectionService: ISelectionService | undefined; private _soundService: ISoundService | undefined; @@ -110,6 +109,7 @@ 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; @@ -174,6 +174,8 @@ export class Terminal extends CoreTerminal implements ITerminal { // Setup listeners this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows))); + + this.decorationService = this.register(this._instantiationService.createInstance(DecorationService)); } /** @@ -515,8 +517,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._renderService.onRenderedBufferChange(e => this._onRender.fire(e))); this.onResize(e => this._renderService!.resize(e.cols, e.rows)); - this._decorationService = this.register(this._instantiationService.createInstance(DecorationService, this.screenElement)); - this._compositionView = document.createElement('div'); this._compositionView.classList.add('composition-view'); this._compositionHelper = this._instantiationService.createInstance(CompositionHelper, this.textarea, this._compositionView); @@ -578,6 +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.screenElement); // This event listener must be registered aftre MouseZoneManager is created this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e))); @@ -1007,7 +1008,7 @@ export class Terminal extends CoreTerminal implements ITerminal { if (this.buffer !== this.buffers.normal) { return undefined; } - return this._decorationService!.registerDecoration(decorationOptions); + return this.decorationService!.registerDecoration(decorationOptions); } /** diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 1c4c653b..ff2f88cf 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -173,6 +173,9 @@ export class Terminal implements ITerminalApi { } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { this._checkProposedApi(); + if (decorationOptions.x) { + this._verifyIntegers(decorationOptions.x); + } return this._core.registerDecoration(decorationOptions); } public addMarker(cursorYOffset: number): IMarker | undefined { diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index cfe10045..8a6b96da 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -3,26 +3,19 @@ * @license MIT */ -import { IRenderService } from 'browser/services/Services'; +import { IDecorationService, IRenderService } from 'browser/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; -import { createDecorator } from 'common/services/ServiceRegistry'; import { IBufferService } from 'common/services/Services'; -import { IDisposable } from 'common/Types'; import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; -export interface IDecorationService extends IDisposable { - registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; - refresh(): void; - dispose(): void; -} - export class DecorationService extends Disposable implements IDecorationService { private _decorations: Decoration[] = []; + private _screenElement: HTMLElement | undefined; + constructor( - private readonly _screenElement: HTMLElement, @IBufferService private readonly _bufferService: IBufferService, @IRenderService private readonly _renderService: IRenderService ) { @@ -30,8 +23,12 @@ export class DecorationService extends Disposable implements IDecorationService this.register(this._renderService.onRenderedBufferChange(() => this.refresh())); } + public attachToDom(screenElement: HTMLElement): void { + this._screenElement = screenElement; + } + public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - if (decorationOptions.marker.isDisposed) { + if (decorationOptions.marker.isDisposed || !this._screenElement) { return undefined; } const decoration = new Decoration(decorationOptions, this._screenElement, this._renderService, this._bufferService); @@ -58,15 +55,14 @@ export class DecorationService extends Disposable implements IDecorationService } } -export const IDecorationService = createDecorator('DecorationService'); class Decoration extends Disposable implements IDecoration { - private static _nextId = 1; + public readonly id: number = Decoration._nextId++; + private static _nextId: number = 1; + private _marker: IMarker; private _element: HTMLElement | undefined; - private _id: number = Decoration._nextId++; public isDisposed: boolean = false; - public get id(): number { return this._id; } public get element(): HTMLElement { return this._element!; } public get marker(): IMarker { return this._marker; } @@ -86,18 +82,19 @@ class Decoration extends Disposable implements IDecoration { this._marker = _decorationOptions.marker; this._createElement(); this._render(); - } - - public dispose(): void { - if (this.isDisposed) { - return; - } - this._screenElement.removeChild(this.element); - this.isDisposed = true; - this._marker.dispose(); - // Emit before super.dispose such that dispose listeners get a change to react - this._onDispose.fire(); - super.dispose(); + this.register({ + dispose: () => { + if (this.isDisposed) { + return; + } + this._screenElement.removeChild(this.element); + this.isDisposed = true; + this._marker.dispose(); + // Emit before super.dispose such that dispose listeners get a change to react + this._onDispose.fire(); + super.dispose(); + } + }); } private _createElement(): void { @@ -119,9 +116,6 @@ class Decoration extends Disposable implements IDecoration { } private _resolveDimensions(): void { - if (!this._renderService.dimensions.scaledCellWidth || !this._renderService.dimensions.scaledCellHeight) { - throw new Error(`Cannot resolve dimensions for decoration when scaled cell dimensions are undefined ${this._renderService.dimensions}.`); - } this._decorationOptions.width = this._decorationOptions.width ? this._decorationOptions.width * this._renderService.dimensions.scaledCellWidth : this._renderService.dimensions.scaledCellWidth; this._decorationOptions.height = this._decorationOptions.height ? this._decorationOptions.height * this._renderService.dimensions.scaledCellHeight : this._renderService.dimensions.scaledCellHeight; } diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 4928fa28..5bab6886 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -9,6 +9,7 @@ import { IColorSet } from 'browser/Types'; import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; import { IDisposable } from 'common/Types'; +import { IDecorationOptions, IDecoration } from 'xterm'; export const ICharSizeService = createDecorator('CharSizeService'); export interface ICharSizeService { @@ -113,3 +114,11 @@ export interface ICharacterJoinerService { deregister(joinerId: number): boolean; getJoinedCharacters(row: number): [number, number][]; } + + +export const IDecorationService = createDecorator('DecorationService'); +export interface IDecorationService extends IDisposable { + registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; + refresh(): void; + attachToDom(screenElement: HTMLElement): void; +} diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index c10094ca..22964998 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -731,7 +731,7 @@ describe('API Integration Tests', function(): void { await pollFor(page, `window.term._core._renderService.dimensions.actualCellWidth > 0`, true); }); - describe.only('registerDecoration', () => { + describe('registerDecoration', () => { it('should register a decoration', async () => { await openTerminal(page); await page.evaluate(`window.marker = window.term.addMarker(1)`); @@ -745,10 +745,17 @@ describe('API Integration Tests', function(): void { assert.equal(await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker });`), undefined); assert.equal(await page.evaluate(`document.querySelector('.xterm-screen .xterm-decoration')`), undefined); }); - it.skip('should throw when a negative x offset is provided', async () => { + it('should throw when a negative x offset is provided', async () => { await openTerminal(page); await page.evaluate(`window.marker = window.term.addMarker(1)`); - assert.throws(async () => await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker, x: -2 });`)); + 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', 'Decoration options x value cannot be negative, but was -2.'); assert.equal(await page.evaluate(`document.querySelector('.xterm-screen .xterm-decoration')`), undefined); }); }); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 5ba7dcf5..0a307f31 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -434,6 +434,12 @@ declare module 'xterm' { readonly 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. + */ export interface IDecorationOptions { /** * The line in the terminal where From a9b496f06f48edaebd0d6b5b368e7b1914b1f849 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 8 Feb 2022 21:18:31 -0600 Subject: [PATCH 38/59] Update src/browser/services/DecorationService.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/browser/services/DecorationService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 992cd339..cc059d21 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -61,9 +61,9 @@ export class DecorationService extends Disposable implements IDecorationService export const IDecorationService = createDecorator('DecorationService'); class Decoration extends Disposable implements IDecoration { private static _nextId = 1; - private _marker: IMarker; + private readonly _marker: IMarker; private _element: HTMLElement | undefined; - private _id: number = Decoration._nextId++; + private readonly _id: number = Decoration._nextId++; public isDisposed: boolean = false; public get id(): number { return this._id; } From cfe306fa325b3dadf33bbe9065b1a09923cfd57f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Feb 2022 21:30:11 -0600 Subject: [PATCH 39/59] break things --- src/browser/services/DecorationService.ts | 33 ++++++++++++++--------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 8a6b96da..8cbaaa32 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -6,7 +6,7 @@ import { IDecorationService, IRenderService } from 'browser/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; -import { IBufferService } from 'common/services/Services'; +import { IBufferService, IInstantiationService } from 'common/services/Services'; import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; export class DecorationService extends Disposable implements IDecorationService { @@ -14,10 +14,10 @@ export class DecorationService extends Disposable implements IDecorationService private _decorations: Decoration[] = []; private _screenElement: HTMLElement | undefined; - constructor( @IBufferService private readonly _bufferService: IBufferService, - @IRenderService private readonly _renderService: IRenderService + @IRenderService private readonly _renderService: IRenderService, + @IInstantiationService private readonly _instantiationService: IInstantiationService ) { super(); this.register(this._renderService.onRenderedBufferChange(() => this.refresh())); @@ -31,18 +31,23 @@ export class DecorationService extends Disposable implements IDecorationService if (decorationOptions.marker.isDisposed || !this._screenElement) { return undefined; } - const decoration = new Decoration(decorationOptions, this._screenElement, this._renderService, this._bufferService); + const decoration = this._instantiationService.createInstance(Decoration, decorationOptions, this._screenElement); this._decorations.push(decoration); + decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1)); return decoration; } public refresh(): void { for (const decoration of this._decorations) { - if ((decoration.marker.line - this._bufferService.buffers.active.ydisp) < 0 || (decoration.marker.line - this._bufferService.buffers.active.ydisp) > this._bufferService.rows) { + if (!decoration.element) { + continue; + } + const line = decoration.marker.line - this._bufferService.buffers.active.ydisp; + if (line < 0 || line > this._bufferService.rows) { // outside of viewport decoration.element.style.display = 'none'; } else { - decoration.element.style.top = `${(decoration.marker.line - this._bufferService.buffers.active.ydisp) * this._renderService.dimensions.scaledCellHeight}px`; + decoration.element.style.top = `${line * this._renderService.dimensions.scaledCellHeight}px`; decoration.element.style.display = 'block'; } } @@ -63,7 +68,7 @@ class Decoration extends Disposable implements IDecoration { private _element: HTMLElement | undefined; public isDisposed: boolean = false; - public get element(): HTMLElement { return this._element!; } + public get element(): HTMLElement | undefined { return this._element; } public get marker(): IMarker { return this._marker; } private _onDispose = new EventEmitter(); @@ -75,16 +80,17 @@ class Decoration extends Disposable implements IDecoration { constructor( private readonly _decorationOptions: IDecorationOptions, private readonly _screenElement: HTMLElement, - private readonly _renderService: IRenderService, - private readonly _bufferService: IBufferService + @IBufferService private readonly _bufferService: IBufferService, + @IRenderService private readonly _renderService: IRenderService ) { super(); this._marker = _decorationOptions.marker; - this._createElement(); - this._render(); + if (this._marker.line - this._bufferService.buffers.active.ydisp >= 0 && this._marker.line - this._bufferService.buffers.active.ydisp < this._bufferService.rows) { + this._render(); + } this.register({ dispose: () => { - if (this.isDisposed) { + if (this.isDisposed || !this.element) { return; } this._screenElement.removeChild(this.element); @@ -121,6 +127,9 @@ class Decoration extends Disposable implements IDecoration { } private _render(): void { + if (!this._element) { + this._createElement(); + } if (this._screenElement && this._element) { this._screenElement.append(this._element); this._onRender.fire(this._element); From ae958c2b120b0e4343b3304570affa0ff663a62d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Feb 2022 21:41:26 -0600 Subject: [PATCH 40/59] fix the problem --- src/browser/Terminal.ts | 2 +- src/browser/services/DecorationService.ts | 14 ++++++++++---- src/browser/services/Services.ts | 3 ++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index bd79b7c5..f1c799f2 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -578,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.screenElement); + this.decorationService.attachToDom(this.screenElement, this._renderService, this._bufferService); // 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 cd32743b..2fea87c3 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -14,17 +14,20 @@ export class DecorationService extends Disposable implements IDecorationService private readonly _decorations: Decoration[] = []; private _screenElement: HTMLElement | undefined; + private _renderService: IRenderService | undefined; + private _bufferService: IBufferService | undefined; + constructor( - @IBufferService private readonly _bufferService: IBufferService, - @IRenderService private readonly _renderService: IRenderService, @IInstantiationService private readonly _instantiationService: IInstantiationService ) { super(); - this.register(this._renderService.onRenderedBufferChange(() => this.refresh())); } - public attachToDom(screenElement: HTMLElement): void { + public attachToDom(screenElement: HTMLElement, renderService: IRenderService, bufferService: IBufferService): void { this._screenElement = screenElement; + this._renderService = renderService; + this._bufferService = bufferService; + this.register(this._renderService.onRenderedBufferChange(() => this.refresh())); } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { @@ -38,6 +41,9 @@ export class DecorationService extends Disposable implements IDecorationService } public refresh(): void { + if (!this._bufferService || !this._renderService) { + return; + } for (const decoration of this._decorations) { if (!decoration.element) { continue; diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 5bab6886..7faf3f0f 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -10,6 +10,7 @@ import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectio import { createDecorator } from 'common/services/ServiceRegistry'; import { IDisposable } from 'common/Types'; import { IDecorationOptions, IDecoration } from 'xterm'; +import { IBufferService } from 'common/services/Services'; export const ICharSizeService = createDecorator('CharSizeService'); export interface ICharSizeService { @@ -120,5 +121,5 @@ export const IDecorationService = createDecorator('Decoratio export interface IDecorationService extends IDisposable { registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; refresh(): void; - attachToDom(screenElement: HTMLElement): void; + attachToDom(screenElement: HTMLElement, renderService: IRenderService, bufferService: IBufferService): void; } From fb4b19a393b1c032d510dc5f54b57677ea942291 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Feb 2022 08:47:10 -0600 Subject: [PATCH 41/59] polish --- demo/client.ts | 4 ++-- demo/index.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 977bcfd8..cafbcd0b 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -149,7 +149,7 @@ if (document.location.pathname === '/test') { document.getElementById('serialize').addEventListener('click', serializeButtonHandler); document.getElementById('custom-glyph').addEventListener('click', writeCustomGlyphHandler); document.getElementById('load-test').addEventListener('click', loadTest); - document.getElementById('decoration').addEventListener('click', addDecoration); + document.getElementById('add-decoration').addEventListener('click', addDecoration); } function createTerminal(): void { @@ -531,4 +531,4 @@ function addDecoration() { const marker = term.addMarker(1); const decoration = term.registerDecoration({ marker }); decoration.element.style.backgroundColor = 'red'; -} \ No newline at end of file +} diff --git a/demo/index.html b/demo/index.html index 24ee68b5..aa28000b 100644 --- a/demo/index.html +++ b/demo/index.html @@ -64,7 +64,7 @@ - + From 591f32d3eb844b27ef64b2a689a34d5552e55837 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 9 Feb 2022 08:51:05 -0600 Subject: [PATCH 42/59] 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 f1c799f2..0e030c1f 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -1008,7 +1008,7 @@ export class Terminal extends CoreTerminal implements ITerminal { if (this.buffer !== this.buffers.normal) { return undefined; } - return this.decorationService!.registerDecoration(decorationOptions); + return this.decorationService.registerDecoration(decorationOptions); } /** From db9bdd46b4b633cc24f469366bc7d4158e3b9724 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Feb 2022 08:56:45 -0600 Subject: [PATCH 43/59] fix some things --- css/xterm.css | 2 +- src/browser/Terminal.ts | 7 +------ src/browser/public/Terminal.ts | 10 +++++++--- src/browser/services/DecorationService.ts | 3 --- test/api/Terminal.api.ts | 2 +- 5 files changed, 10 insertions(+), 14 deletions(-) diff --git a/css/xterm.css b/css/xterm.css index f8bfcd23..956f6675 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -174,7 +174,7 @@ text-decoration: line-through; } -.xterm-screen .xterm-decorations .xterm-decoration { +.xterm-screen .xterm-decoration { z-index: 6; position: absolute; } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index f1c799f2..703c995b 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -159,6 +159,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)); // Setup InputHandler listeners this.register(this._inputHandler.onRequestBell(() => this.bell())); @@ -174,8 +175,6 @@ export class Terminal extends CoreTerminal implements ITerminal { // Setup listeners this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows))); - - this.decorationService = this.register(this._instantiationService.createInstance(DecorationService)); } /** @@ -1004,10 +1003,6 @@ export class Terminal extends CoreTerminal implements ITerminal { } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - // Disallow decorations on the alt buffer - if (this.buffer !== this.buffers.normal) { - return undefined; - } return this.decorationService!.registerDecoration(decorationOptions); } diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index ff2f88cf..7b2e7ee6 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -173,9 +173,7 @@ export class Terminal implements ITerminalApi { } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { this._checkProposedApi(); - if (decorationOptions.x) { - this._verifyIntegers(decorationOptions.x); - } + this._verifyPositiveInteger(decorationOptions.x); return this._core.registerDecoration(decorationOptions); } public addMarker(cursorYOffset: number): IMarker | undefined { @@ -288,4 +286,10 @@ export class Terminal implements ITerminalApi { } } } + + private _verifyPositiveInteger(value?: number): void { + if (value && (value === Infinity || isNaN(value) || value % 1 !== 0 || value < 0)) { + throw new Error('This API only accepts positive integers'); + } + } } diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 2fea87c3..08d570e3 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -115,9 +115,6 @@ class Decoration extends Disposable implements IDecoration { this._element.style.width = `${this._decorationOptions.width}px`; this._element.style.height = `${this._decorationOptions.height}px`; this._element.style.top = `${(this.marker.line - this._bufferService.buffers.active.ydisp) * this._renderService.dimensions.scaledCellHeight}px`; - if (this._decorationOptions.x && this._decorationOptions.x < 0) { - throw new Error(`Decoration options x value cannot be negative, but was ${this._decorationOptions.x}.`); - } if (this._decorationOptions.anchor === 'right') { this._element.style.right = this._decorationOptions.x ? `${this._decorationOptions.x * this._renderService.dimensions.scaledCellWidth}px` : ''; diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 22964998..2f428803 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -755,7 +755,7 @@ describe('API Integration Tests', function(): void { window.throwMessage = e.message; } `); - await pollFor(page, 'window.throwMessage', 'Decoration options x value cannot be negative, but was -2.'); + await pollFor(page, 'window.throwMessage', 'This API only accepts positive integers'); assert.equal(await page.evaluate(`document.querySelector('.xterm-screen .xterm-decoration')`), undefined); }); }); From 8e75cecba393412b9627df31b760bed8f1f38e63 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Feb 2022 09:44:24 -0600 Subject: [PATCH 44/59] change how rendering happens --- src/browser/services/DecorationService.ts | 89 +++++++++++------------ 1 file changed, 43 insertions(+), 46 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 08d570e3..868ec1be 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -6,7 +6,7 @@ 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 { IBufferService } from 'common/services/Services'; import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; export class DecorationService extends Disposable implements IDecorationService { @@ -18,7 +18,6 @@ export class DecorationService extends Disposable implements IDecorationService private _bufferService: IBufferService | undefined; constructor( - @IInstantiationService private readonly _instantiationService: IInstantiationService ) { super(); } @@ -27,6 +26,7 @@ export class DecorationService extends Disposable implements IDecorationService this._screenElement = screenElement; this._renderService = renderService; this._bufferService = bufferService; + this.refresh(); this.register(this._renderService.onRenderedBufferChange(() => this.refresh())); } @@ -34,7 +34,7 @@ export class DecorationService extends Disposable implements IDecorationService if (decorationOptions.marker.isDisposed || !this._screenElement) { return undefined; } - const decoration = this._instantiationService.createInstance(Decoration, decorationOptions, this._screenElement); + const decoration = new Decoration(decorationOptions, this._screenElement); this._decorations.push(decoration); decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1)); return decoration; @@ -45,17 +45,7 @@ export class DecorationService extends Disposable implements IDecorationService return; } for (const decoration of this._decorations) { - if (!decoration.element) { - continue; - } - const line = decoration.marker.line - this._bufferService.buffers.active.ydisp; - if (line < 0 || line > this._bufferService.rows) { - // outside of viewport - decoration.element.style.display = 'none'; - } else { - decoration.element.style.top = `${line * this._renderService.dimensions.scaledCellHeight}px`; - decoration.element.style.display = 'block'; - } + decoration.render(this._bufferService, this._renderService); } } @@ -84,21 +74,42 @@ class Decoration extends Disposable implements IDecoration { constructor( private readonly _decorationOptions: IDecorationOptions, - private readonly _screenElement: HTMLElement, - @IBufferService private readonly _bufferService: IBufferService, - @IRenderService private readonly _renderService: IRenderService + private readonly _screenElement: HTMLElement ) { super(); this._marker = _decorationOptions.marker; - if (this._marker.line - this._bufferService.buffers.active.ydisp >= 0 && this._marker.line - this._bufferService.buffers.active.ydisp < this._bufferService.rows) { - this._render(); + } + + public render(bufferService: IBufferService, renderService: IRenderService): void { + if (!this._element) { + this._createElement(bufferService, renderService); + } + if (this._screenElement && this._element && !this._screenElement.contains(this._element)) { + this._screenElement.append(this._element); + } + this._refreshStyle(bufferService, renderService); + this._onRender.fire(this._element!); + } + + private _createElement(bufferService: IBufferService, renderService: IRenderService): void { + this._element = document.createElement('div'); + this._element.classList.add('xterm-decoration'); + this._resolveDimensions(renderService); + this._element.style.width = `${this._decorationOptions.width}px`; + this._element.style.height = `${this._decorationOptions.height}px`; + this._element.style.top = `${(this.marker.line - bufferService.buffers.active.ydisp) * renderService.dimensions.scaledCellHeight}px`; + + if (this._decorationOptions.anchor === 'right') { + this._element.style.right = this._decorationOptions.x ? `${this._decorationOptions.x * renderService.dimensions.scaledCellWidth}px` : ''; + } else { + this._element.style.left = this._decorationOptions.x ? `${this._decorationOptions.x * renderService.dimensions.scaledCellWidth}px` : ''; } this.register({ dispose: () => { - if (this.isDisposed || !this.element) { + if (this.isDisposed) { return; } - this._screenElement.removeChild(this.element); + this._screenElement.removeChild(this._element!); this.isDisposed = true; this._marker.dispose(); // Emit before super.dispose such that dispose listeners get a change to react @@ -108,33 +119,19 @@ class Decoration extends Disposable implements IDecoration { }); } - private _createElement(): void { - this._element = document.createElement('div'); - this._element.classList.add('xterm-decoration'); - this._resolveDimensions(); - this._element.style.width = `${this._decorationOptions.width}px`; - this._element.style.height = `${this._decorationOptions.height}px`; - this._element.style.top = `${(this.marker.line - this._bufferService.buffers.active.ydisp) * this._renderService.dimensions.scaledCellHeight}px`; + private _resolveDimensions(renderService: IRenderService): void { + this._decorationOptions.width = this._decorationOptions.width ? this._decorationOptions.width * renderService.dimensions.scaledCellWidth : renderService.dimensions.scaledCellWidth; + this._decorationOptions.height = this._decorationOptions.height ? this._decorationOptions.height * renderService.dimensions.scaledCellHeight : renderService.dimensions.scaledCellHeight; + } - if (this._decorationOptions.anchor === 'right') { - this._element.style.right = this._decorationOptions.x ? `${this._decorationOptions.x * this._renderService.dimensions.scaledCellWidth}px` : ''; + private _refreshStyle(bufferService: IBufferService, renderService: IRenderService): void { + const line = this.marker.line - bufferService.buffers.active.ydisp; + if (line < 0 || line > bufferService.rows) { + // outside of viewport + this._element!.style.display = 'none'; } else { - this._element.style.left = this._decorationOptions.x ? `${this._decorationOptions.x * this._renderService.dimensions.scaledCellWidth}px` : ''; - } - } - - private _resolveDimensions(): void { - this._decorationOptions.width = this._decorationOptions.width ? this._decorationOptions.width * this._renderService.dimensions.scaledCellWidth : this._renderService.dimensions.scaledCellWidth; - this._decorationOptions.height = this._decorationOptions.height ? this._decorationOptions.height * this._renderService.dimensions.scaledCellHeight : this._renderService.dimensions.scaledCellHeight; - } - - private _render(): void { - if (!this._element) { - this._createElement(); - } - if (this._screenElement && this._element) { - this._screenElement.append(this._element); - this._onRender.fire(this._element); + this._element!.style.top = `${line * renderService.dimensions.scaledCellHeight}px`; + this._element!.style.display = 'block'; } } } From 313cb2fe0980ee898adebd0c69fc05d670022a7c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Feb 2022 09:55:30 -0600 Subject: [PATCH 45/59] add a container --- css/xterm.css | 2 +- src/browser/services/DecorationService.ts | 19 ++++++++++--------- test/api/Terminal.api.ts | 4 ++-- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/css/xterm.css b/css/xterm.css index 956f6675..ab3965b4 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -174,7 +174,7 @@ text-decoration: line-through; } -.xterm-screen .xterm-decoration { +.xterm-screen .xterm-decoration-container .xterm-decoration { z-index: 6; position: absolute; } diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 868ec1be..a33d14bc 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -12,8 +12,7 @@ import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; export class DecorationService extends Disposable implements IDecorationService { private readonly _decorations: Decoration[] = []; - private _screenElement: HTMLElement | undefined; - + private _container: HTMLElement | undefined; private _renderService: IRenderService | undefined; private _bufferService: IBufferService | undefined; @@ -23,18 +22,20 @@ export class DecorationService extends Disposable implements IDecorationService } public attachToDom(screenElement: HTMLElement, renderService: IRenderService, bufferService: IBufferService): void { - this._screenElement = screenElement; this._renderService = renderService; this._bufferService = bufferService; + this._container = document.createElement('div'); + this._container.classList.add('xterm-decoration-container'); + screenElement.appendChild(this._container); this.refresh(); this.register(this._renderService.onRenderedBufferChange(() => this.refresh())); } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - if (decorationOptions.marker.isDisposed || !this._screenElement) { + if (decorationOptions.marker.isDisposed || !this._container) { return undefined; } - const decoration = new Decoration(decorationOptions, this._screenElement); + const decoration = new Decoration(decorationOptions, this._container); this._decorations.push(decoration); decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1)); return decoration; @@ -74,7 +75,7 @@ class Decoration extends Disposable implements IDecoration { constructor( private readonly _decorationOptions: IDecorationOptions, - private readonly _screenElement: HTMLElement + private readonly _container: HTMLElement ) { super(); this._marker = _decorationOptions.marker; @@ -84,8 +85,8 @@ class Decoration extends Disposable implements IDecoration { if (!this._element) { this._createElement(bufferService, renderService); } - if (this._screenElement && this._element && !this._screenElement.contains(this._element)) { - this._screenElement.append(this._element); + if (this._container && this._element && !this._container.contains(this._element)) { + this._container.append(this._element); } this._refreshStyle(bufferService, renderService); this._onRender.fire(this._element!); @@ -109,7 +110,7 @@ class Decoration extends Disposable implements IDecoration { if (this.isDisposed) { return; } - this._screenElement.removeChild(this._element!); + this._container.removeChild(this._element!); this.isDisposed = true; this._marker.dispose(); // Emit before super.dispose such that dispose listeners get a change to react diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 2f428803..e5a04667 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -731,7 +731,7 @@ describe('API Integration Tests', function(): void { await pollFor(page, `window.term._core._renderService.dimensions.actualCellWidth > 0`, true); }); - describe('registerDecoration', () => { + describe.only('registerDecoration', () => { it('should register a decoration', async () => { await openTerminal(page); await page.evaluate(`window.marker = window.term.addMarker(1)`); @@ -756,7 +756,7 @@ describe('API Integration Tests', function(): void { } `); await pollFor(page, 'window.throwMessage', 'This API only accepts positive integers'); - assert.equal(await page.evaluate(`document.querySelector('.xterm-screen .xterm-decoration')`), undefined); + assert.equal(await page.evaluate(`document.querySelector('.xterm-screen .xterm-decoration-container .xterm-decoration')`), undefined); }); }); From 95c9fe11fb63d57e9968898d25d2c70b3b1d95e6 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Feb 2022 10:07:20 -0600 Subject: [PATCH 46/59] save options in constructor --- src/browser/services/DecorationService.ts | 29 +++++++++++++---------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index a33d14bc..bdba18b5 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -73,12 +73,21 @@ class Decoration 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; + public height: number; + constructor( - private readonly _decorationOptions: IDecorationOptions, + options: IDecorationOptions, private readonly _container: HTMLElement ) { super(); - this._marker = _decorationOptions.marker; + this.x = options.x ?? 0; + this._marker = options.marker; + this.anchor = options.anchor || 'left'; + this.width = options.width || 1; + this.height = options.height || 1; } public render(bufferService: IBufferService, renderService: IRenderService): void { @@ -95,15 +104,14 @@ class Decoration extends Disposable implements IDecoration { private _createElement(bufferService: IBufferService, renderService: IRenderService): void { this._element = document.createElement('div'); this._element.classList.add('xterm-decoration'); - this._resolveDimensions(renderService); - this._element.style.width = `${this._decorationOptions.width}px`; - this._element.style.height = `${this._decorationOptions.height}px`; + this._element.style.width = `${this.width * renderService.dimensions.scaledCellWidth}px`; + this._element.style.height = `${this.height * renderService.dimensions.scaledCellHeight}px`; this._element.style.top = `${(this.marker.line - bufferService.buffers.active.ydisp) * renderService.dimensions.scaledCellHeight}px`; - if (this._decorationOptions.anchor === 'right') { - this._element.style.right = this._decorationOptions.x ? `${this._decorationOptions.x * renderService.dimensions.scaledCellWidth}px` : ''; + if (this.anchor === 'right') { + this._element.style.right = this.x ? `${this.x * renderService.dimensions.scaledCellWidth}px` : ''; } else { - this._element.style.left = this._decorationOptions.x ? `${this._decorationOptions.x * renderService.dimensions.scaledCellWidth}px` : ''; + this._element.style.left = this.x ? `${this.x * renderService.dimensions.scaledCellWidth}px` : ''; } this.register({ dispose: () => { @@ -120,11 +128,6 @@ class Decoration extends Disposable implements IDecoration { }); } - private _resolveDimensions(renderService: IRenderService): void { - this._decorationOptions.width = this._decorationOptions.width ? this._decorationOptions.width * renderService.dimensions.scaledCellWidth : renderService.dimensions.scaledCellWidth; - this._decorationOptions.height = this._decorationOptions.height ? this._decorationOptions.height * renderService.dimensions.scaledCellHeight : renderService.dimensions.scaledCellHeight; - } - private _refreshStyle(bufferService: IBufferService, renderService: IRenderService): void { const line = this.marker.line - bufferService.buffers.active.ydisp; if (line < 0 || line > bufferService.rows) { From 3e204584d92663d2b0302ac55e0c8a277ba0bd3a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Feb 2022 10:12:25 -0600 Subject: [PATCH 47/59] refresh on dimensions changed --- src/browser/services/DecorationService.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index bdba18b5..a1b47ddb 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -29,6 +29,7 @@ export class DecorationService extends Disposable implements IDecorationService screenElement.appendChild(this._container); this.refresh(); this.register(this._renderService.onRenderedBufferChange(() => this.refresh())); + this.register(this._renderService.onDimensionsChange(() => this.refresh(true))); } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { @@ -41,12 +42,12 @@ export class DecorationService extends Disposable implements IDecorationService return decoration; } - public refresh(): void { + public refresh(recreate?: boolean): void { if (!this._bufferService || !this._renderService) { return; } for (const decoration of this._decorations) { - decoration.render(this._bufferService, this._renderService); + decoration.render(this._bufferService, this._renderService, recreate); } } @@ -90,9 +91,9 @@ class Decoration extends Disposable implements IDecoration { this.height = options.height || 1; } - public render(bufferService: IBufferService, renderService: IRenderService): void { - if (!this._element) { - this._createElement(bufferService, renderService); + public render(bufferService: IBufferService, renderService: IRenderService, recreate?: boolean): void { + if (!this._element || recreate) { + this._createElement(bufferService, renderService, recreate); } if (this._container && this._element && !this._container.contains(this._element)) { this._container.append(this._element); @@ -101,7 +102,10 @@ class Decoration extends Disposable implements IDecoration { this._onRender.fire(this._element!); } - private _createElement(bufferService: IBufferService, renderService: IRenderService): void { + private _createElement(bufferService: IBufferService, renderService: IRenderService, recreate?: boolean): void { + if (recreate) { + this._container.removeChild(this._element!); + } this._element = document.createElement('div'); this._element.classList.add('xterm-decoration'); this._element.style.width = `${this.width * renderService.dimensions.scaledCellWidth}px`; From e2a402d80e482b96bac8cefc897f71574159ac46 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Feb 2022 11:51:25 -0600 Subject: [PATCH 48/59] add tests --- demo/client.ts | 5 ++- src/browser/services/DecorationService.ts | 7 ++-- test/api/Terminal.api.ts | 39 +++++++++++++++++++---- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index cafbcd0b..a8283461 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -530,5 +530,8 @@ function loadTest() { function addDecoration() { const marker = term.addMarker(1); const decoration = term.registerDecoration({ marker }); - decoration.element.style.backgroundColor = 'red'; + term.write(''); + decoration.onRender(() => { + decoration.element.style.backgroundColor = 'red'; + }); } diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index a1b47ddb..bea34d9a 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -57,8 +57,7 @@ export class DecorationService extends Disposable implements IDecorationService } } } - -class Decoration extends Disposable implements IDecoration { +export class Decoration extends Disposable implements IDecoration { private static _nextId = 1; private readonly _marker: IMarker; private _element: HTMLElement | undefined; @@ -103,8 +102,8 @@ class Decoration extends Disposable implements IDecoration { } private _createElement(bufferService: IBufferService, renderService: IRenderService, recreate?: boolean): void { - if (recreate) { - this._container.removeChild(this._element!); + if (recreate && this._element) { + this._container.removeChild(this._element); } this._element = document.createElement('div'); this._element.classList.add('xterm-decoration'); diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index e5a04667..aab56a09 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -731,22 +731,50 @@ describe('API Integration Tests', function(): void { await pollFor(page, `window.term._core._renderService.dimensions.actualCellWidth > 0`, true); }); - describe.only('registerDecoration', () => { - it('should register a decoration', async () => { + describe('registerDecoration', () => { + it('should register a decoration but not add the element until the first refresh call', 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 });`); - assert.notEqual(await page.evaluate(`document.querySelector('.xterm-screen .xterm-decoration')`), undefined); + await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker }); + window.decoration.onRender(() => { + window.rendered = true; + });`); + assert.equal(await page.evaluate(`window.rendered`), undefined); + }); + it('should register a decoration and render it', 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 }); + window.decoration.onRender(() => { + window.rendered = true; + });`); + assert.equal(await page.evaluate(`window.rendered`), true); + }); + 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.resize(10, 5)`); + assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 1); }); 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); - assert.equal(await page.evaluate(`document.querySelector('.xterm-screen .xterm-decoration')`), 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 { @@ -756,7 +784,6 @@ describe('API Integration Tests', function(): void { } `); await pollFor(page, 'window.throwMessage', 'This API only accepts positive integers'); - assert.equal(await page.evaluate(`document.querySelector('.xterm-screen .xterm-decoration-container .xterm-decoration')`), undefined); }); }); From 87675649e137de9e5284fdd51e8bbc90179ff1ab Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Feb 2022 11:55:17 -0600 Subject: [PATCH 49/59] hide if invalid x --- src/browser/public/Terminal.ts | 10 ++++++---- src/browser/services/DecorationService.ts | 3 +++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 7b2e7ee6..9571afa6 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -173,7 +173,7 @@ export class Terminal implements ITerminalApi { } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { this._checkProposedApi(); - this._verifyPositiveInteger(decorationOptions.x); + this._verifyPositiveIntegers(decorationOptions.x ?? 0, decorationOptions.width ?? 0, decorationOptions.height ?? 0); return this._core.registerDecoration(decorationOptions); } public addMarker(cursorYOffset: number): IMarker | undefined { @@ -287,9 +287,11 @@ export class Terminal implements ITerminalApi { } } - private _verifyPositiveInteger(value?: number): void { - if (value && (value === Infinity || isNaN(value) || value % 1 !== 0 || value < 0)) { - throw new Error('This API only accepts positive integers'); + private _verifyPositiveIntegers(...values: number[]): void { + for (const value of values) { + if (value && (value === Infinity || isNaN(value) || value % 1 !== 0 || value < 0)) { + throw new Error('This API only accepts positive integers'); + } } } } diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index bea34d9a..08e8e9c7 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -111,6 +111,9 @@ export class Decoration extends Disposable implements IDecoration { this._element.style.height = `${this.height * renderService.dimensions.scaledCellHeight}px`; this._element.style.top = `${(this.marker.line - bufferService.buffers.active.ydisp) * renderService.dimensions.scaledCellHeight}px`; + if (this.x && this.x > bufferService.cols) { + this._element!.style.display = 'none'; + } if (this.anchor === 'right') { this._element.style.right = this.x ? `${this.x * renderService.dimensions.scaledCellWidth}px` : ''; } else { From 93bfc18237f4ca063ceaa1c13901ba60cd7dae62 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Feb 2022 12:10:50 -0600 Subject: [PATCH 50/59] fix test --- test/api/Terminal.api.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index aab56a09..20332df3 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -748,11 +748,8 @@ describe('API Integration Tests', function(): void { 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 }); - window.decoration.onRender(() => { - window.rendered = true; - });`); - assert.equal(await page.evaluate(`window.rendered`), true); + await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker }`); + assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 1); }); it('on resize should dispose of the old decoration and create a new one', async () => { await openTerminal(page); From e5d4e96aadf2c475c9717b0060669ee47ab8e7cf Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Feb 2022 14:30:53 -0600 Subject: [PATCH 51/59] fix test --- test/api/Terminal.api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 20332df3..3604fe60 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -748,7 +748,7 @@ describe('API Integration Tests', function(): void { 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.decoration = window.term.registerDecoration({ marker: window.marker })`); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 1); }); it('on resize should dispose of the old decoration and create a new one', async () => { From 6a7901748406890bf1bdc8d29b30763c6eb76ce6 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Feb 2022 14:33:33 -0600 Subject: [PATCH 52/59] on dispose, remove container --- src/browser/services/DecorationService.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 08e8e9c7..4073b03c 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -13,6 +13,7 @@ export class DecorationService extends Disposable implements IDecorationService private readonly _decorations: Decoration[] = []; private _container: HTMLElement | undefined; + private _screenElement: HTMLElement | undefined; private _renderService: IRenderService | undefined; private _bufferService: IBufferService | undefined; @@ -24,6 +25,7 @@ export class DecorationService extends Disposable implements IDecorationService public attachToDom(screenElement: HTMLElement, renderService: IRenderService, bufferService: IBufferService): void { this._renderService = renderService; this._bufferService = bufferService; + this._screenElement = screenElement; this._container = document.createElement('div'); this._container.classList.add('xterm-decoration-container'); screenElement.appendChild(this._container); @@ -55,6 +57,9 @@ export class DecorationService extends Disposable implements IDecorationService for (const decoration of this._decorations) { decoration.dispose(); } + if (this._container) { + this._screenElement?.removeChild(this._container); + } } } export class Decoration extends Disposable implements IDecoration { From c7df6e3c2ebb009d8ea9ffde3bcce24c8303cffa Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Feb 2022 14:50:02 -0600 Subject: [PATCH 53/59] inject bufferService --- src/browser/services/DecorationService.ts | 34 +++++++++++------------ 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 4073b03c..f29ec7ea 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -6,7 +6,7 @@ import { IDecorationService, IRenderService } from 'browser/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; -import { IBufferService } from 'common/services/Services'; +import { IBufferService, IInstantiationService } from 'common/services/Services'; import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; export class DecorationService extends Disposable implements IDecorationService { @@ -15,16 +15,15 @@ export class DecorationService extends Disposable implements IDecorationService private _container: HTMLElement | undefined; private _screenElement: HTMLElement | undefined; private _renderService: IRenderService | undefined; - private _bufferService: IBufferService | undefined; constructor( - ) { + @IBufferService private readonly _bufferService: IBufferService, + @IInstantiationService private readonly _instantiationService: IInstantiationService) { super(); } - public attachToDom(screenElement: HTMLElement, renderService: IRenderService, bufferService: IBufferService): void { + public attachToDom(screenElement: HTMLElement, renderService: IRenderService): void { this._renderService = renderService; - this._bufferService = bufferService; this._screenElement = screenElement; this._container = document.createElement('div'); this._container.classList.add('xterm-decoration-container'); @@ -38,7 +37,7 @@ export class DecorationService extends Disposable implements IDecorationService if (decorationOptions.marker.isDisposed || !this._container) { return undefined; } - const decoration = new Decoration(decorationOptions, this._container); + const decoration = this._instantiationService.createInstance(Decoration, decorationOptions, this._container); this._decorations.push(decoration); decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1)); return decoration; @@ -49,7 +48,7 @@ export class DecorationService extends Disposable implements IDecorationService return; } for (const decoration of this._decorations) { - decoration.render(this._bufferService, this._renderService, recreate); + decoration.render(this._renderService, recreate); } } @@ -85,7 +84,8 @@ export class Decoration extends Disposable implements IDecoration { constructor( options: IDecorationOptions, - private readonly _container: HTMLElement + private readonly _container: HTMLElement, + @IBufferService private readonly _bufferService: IBufferService ) { super(); this.x = options.x ?? 0; @@ -95,18 +95,18 @@ export class Decoration extends Disposable implements IDecoration { this.height = options.height || 1; } - public render(bufferService: IBufferService, renderService: IRenderService, recreate?: boolean): void { + public render(renderService: IRenderService, recreate?: boolean): void { if (!this._element || recreate) { - this._createElement(bufferService, renderService, recreate); + this._createElement(renderService, recreate); } if (this._container && this._element && !this._container.contains(this._element)) { this._container.append(this._element); } - this._refreshStyle(bufferService, renderService); + this._refreshStyle(renderService); this._onRender.fire(this._element!); } - private _createElement(bufferService: IBufferService, renderService: IRenderService, recreate?: boolean): void { + private _createElement(renderService: IRenderService, recreate?: boolean): void { if (recreate && this._element) { this._container.removeChild(this._element); } @@ -114,9 +114,9 @@ export class Decoration extends Disposable implements IDecoration { this._element.classList.add('xterm-decoration'); this._element.style.width = `${this.width * renderService.dimensions.scaledCellWidth}px`; this._element.style.height = `${this.height * renderService.dimensions.scaledCellHeight}px`; - this._element.style.top = `${(this.marker.line - bufferService.buffers.active.ydisp) * renderService.dimensions.scaledCellHeight}px`; + this._element.style.top = `${(this.marker.line - this._bufferService.buffers.active.ydisp) * renderService.dimensions.scaledCellHeight}px`; - if (this.x && this.x > bufferService.cols) { + if (this.x && this.x > this._bufferService.cols) { this._element!.style.display = 'none'; } if (this.anchor === 'right') { @@ -139,9 +139,9 @@ export class Decoration extends Disposable implements IDecoration { }); } - private _refreshStyle(bufferService: IBufferService, renderService: IRenderService): void { - const line = this.marker.line - bufferService.buffers.active.ydisp; - if (line < 0 || line > bufferService.rows) { + private _refreshStyle(renderService: IRenderService): void { + 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 { From bf5fed083b97d067734809e5d585152bf8084ca8 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Feb 2022 14:55:24 -0600 Subject: [PATCH 54/59] fix test --- test/api/Terminal.api.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 3604fe60..eedf72c2 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -749,6 +749,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 writeSync(page, '\\n\\n\\n\\n'); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 1); }); it('on resize should dispose of the old decoration and create a new one', async () => { From 74246497a4e38b4efcb15c79ab5e6c07f9e9df19 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Feb 2022 15:29:28 -0600 Subject: [PATCH 55/59] get term to scroll so it refreshes --- test/api/Terminal.api.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index eedf72c2..b4b2ea06 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -745,11 +745,14 @@ describe('API Integration Tests', function(): void { }); it('should register a decoration and render it', async () => { await openTerminal(page); - await writeSync(page, '\\n\\n\\n\\n'); - await writeSync(page, '\\n\\n\\n\\n'); + await openTerminal(page, { rows: 5 }); + await page.evaluate(` + for (let i = 0; i < 4; i++) { + window.term.writeln('foo'); + } + `); await page.evaluate(`window.marker = window.term.addMarker(1)`); await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker })`); - await writeSync(page, '\\n\\n\\n\\n'); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 1); }); it('on resize should dispose of the old decoration and create a new one', async () => { From b3b9f3c7cd5c48bf6f29cf783886178776a96d39 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Feb 2022 17:17:36 -0600 Subject: [PATCH 56/59] add poll for --- test/api/Terminal.api.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index b4b2ea06..8ef7cac6 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -745,7 +745,9 @@ describe('API Integration Tests', function(): void { }); it('should register a decoration and render it', async () => { await openTerminal(page); + this.retries(3); await openTerminal(page, { rows: 5 }); + await timeout(20); await page.evaluate(` for (let i = 0; i < 4; i++) { window.term.writeln('foo'); @@ -753,7 +755,7 @@ describe('API Integration Tests', function(): void { `); await page.evaluate(`window.marker = window.term.addMarker(1)`); await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker })`); - assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 1); + await pollFor(page, `document.querySelectorAll('.xterm-screen .xterm-decoration').length`, 1); }); it('on resize should dispose of the old decoration and create a new one', async () => { await openTerminal(page); From 881bc4f0a8ff7f27c6a29fe604f2ae7a9009c133 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Feb 2022 17:37:07 -0600 Subject: [PATCH 57/59] different approach --- test/api/Terminal.api.ts | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 8ef7cac6..56ff7d2d 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -743,19 +743,17 @@ describe('API Integration Tests', function(): void { });`); assert.equal(await page.evaluate(`window.rendered`), undefined); }); - it('should register a decoration and render it', async () => { + it('should register decorations and render them', async () => { await openTerminal(page); - this.retries(3); - await openTerminal(page, { rows: 5 }); - await timeout(20); - await page.evaluate(` - for (let i = 0; i < 4; i++) { - window.term.writeln('foo'); - } - `); - await page.evaluate(`window.marker = window.term.addMarker(1)`); - await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker })`); - await pollFor(page, `document.querySelectorAll('.xterm-screen .xterm-decoration').length`, 1); + 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.resize(10, 5)`); + 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); From 14b71b611e8ac11f13aaa8f85fc682d52e9e80d9 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Feb 2022 18:38:47 -0600 Subject: [PATCH 58/59] fix test --- test/api/Terminal.api.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 56ff7d2d..7c6daf26 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -737,11 +737,8 @@ describe('API Integration Tests', function(): void { 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 }); - window.decoration.onRender(() => { - window.rendered = true; - });`); - assert.equal(await page.evaluate(`window.rendered`), undefined); + await page.evaluate(`window.term.registerDecoration({ marker: window.marker })`); + assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 0); }); it('should register decorations and render them', async () => { await openTerminal(page); From 9ed95206c1231bb6dc23793876d811de67270e89 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Feb 2022 18:48:41 -0600 Subject: [PATCH 59/59] try to fix tests --- test/api/Terminal.api.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 7c6daf26..ee6a0cfa 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -732,14 +732,6 @@ describe('API Integration Tests', function(): void { }); describe('registerDecoration', () => { - it('should register a decoration but not add the element until the first refresh call', 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.term.registerDecoration({ marker: window.marker })`); - assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 0); - }); it('should register decorations and render them', async () => { await openTerminal(page); await writeSync(page, '\\n\\n\\n\\n');