From c5810e6d545f39b00f9e5100cd24e10406b05e92 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 27 Jan 2022 13:20:02 -0600 Subject: [PATCH 01/16] 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 39245afe5e..83c1d10c9b 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/16] 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 af1591a897..b1aa03ecf7 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 fe5c7f79f9..fb26a2eb5e 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 8fdf458e6d..c42e4a552f 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 a61658406c..779a9454c4 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 117805f929..1f3c32febf 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 0000000000..fd5d32da70 --- /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 7a64257da7..4829e0bb49 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 6818a92673..8a32868660 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 ee28339946..58c54f65ac 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 b8283e0e69..526bffa308 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 4928fa28ec..c3ec2eb5ba 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 83c1d10c9b..6b8392990c 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/16] 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 cebd08e01c..d490d953db 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 629e94364a..69f0efc2cd 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 fd5d32da70..e6c6342b80 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/16] 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 69f0efc2cd..629e94364a 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 e6c6342b80..29db0d27d8 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(); - } - public reset(): void { - - } - - public override onFocus(): void { - this.registerDecoration({ type: 'IBufferDecorationOptions', startMarker: new Marker(1), shape: 'button' }); + 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 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 6b8392990c..0f01317f5e 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/16] 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 29db0d27d8..5d088241bc 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/16] 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 d490d953db..1abcf08d86 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 5d088241bc..39254d9c51 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/16] 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 | 117 +++++++++--------- 12 files changed, 130 insertions(+), 113 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index b1aa03ecf7..4d8fa11caa 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 fb26a2eb5e..8026175efa 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 c42e4a552f..1a29c18665 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 779a9454c4..69ba3047e1 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 1f3c32febf..c641f5f155 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 39254d9c51..fd01c27532 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 4829e0bb49..3e96fae15d 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 8a32868660..97e43b613a 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 58c54f65ac..ba23e5734a 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 526bffa308..e2f6300b05 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 c3ec2eb5ba..40cb08d250 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 0f01317f5e..52397e55d2 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -380,95 +380,100 @@ 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. + * An event fired when the decoration + * is rendered, returns the dom element + * associated with the decoration. */ - readonly line: number; + onRender: IEvent; /** - * An event fired when the decoration - * is rendered, returns the dom element - * associated with the decoration. + * The HTMLElement that gets created after the + * first _onRender call, or undefined if accessed before + * that. */ - onRender: IEvent; + element: HTMLElement | undefined; } - export interface IDecorationOptions { - + export interface IDisposableWithEvent extends IDisposable { /** - * The line in the terminal where - * the decoration will be displayed - */ - startMarker: IMarker; + * Event listener to get notified when this gets disposed. + */ + onDispose: IEvent; /** - * The number of milliseconds the decoration - * should be displayed for. - */ - displayDuration?: number; + * 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 color of the decoration + * The line in the terminal where + * the decoration will be displayed */ - color?: string - } + marker: IMarker; - export interface IBufferDecorationOptions extends IDecorationOptions { /** - * The type of buffer decoration - */ - shape: 'button' | 'box-border'; + * The width of the decoration, which defaults to + * cell width + */ + width?: number; - /* - * The x position for the decoration. - * Defaults to the right edge. + /** + * The height of the decoration, which defaults to + * cell height */ - position?: number; - } + height?: number; - export interface IGutterDecorationOptions extends IDecorationOptions { /** - * The end line in the terminal for - * the decoration - */ - endMarker: IMarker; + * The x position offset relative to the anchor + */ + 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; + // } + /** * 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/16] more cleanup --- typings/xterm.d.ts | 68 ++++++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 52397e55d2..22eba5d131 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; - + export interface IBufferDecorationOptions { /** - * Whether this is disposed. + * The line in the terminal where + * the decoration will be displayed */ - readonly isDisposed: boolean; - } - + marker: IMarker; - export interface IBufferDecorationOptions { /* * Where the decoration will be anchored - - * defaults to the left edge. + * defaults to the left edge */ 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 @@ -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/16] 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 fd01c27532..621b616e49 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 22eba5d131..a6ff47ada0 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/16] 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 621b616e49..e1476d16d9 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 a6ff47ada0..a7f80a159c 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/16] 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} | 49 +++++++------------ src/browser/services/RenderService.ts | 4 -- src/browser/services/Services.ts | 1 - 7 files changed, 25 insertions(+), 52 deletions(-) rename src/browser/{renderer/DecorationRenderLayer.ts => services/DecorationsService.ts} (65%) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 8026175efa..7757e0b7ea 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 3e96fae15d..305808a2a2 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 97e43b613a..c53871734a 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 ba23e5734a..2057c791e0 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 e1476d16d9..d168e39b66 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' }); - } - - public onGridChanged(startRow: number, endRow: number): void { - for (const decoration of this._decorations) { - (decoration as BufferDecoration).render(); - } - } - - public reset(): void { +export class DecorationsService extends Disposable implements IDecorationsService { + constructor(private readonly _screenElement: HTMLElement) { + super(); } - 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 e2f6300b05..d236135a7d 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 40cb08d250..8b7a0a77a3 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/16] 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 1abcf08d86..cebd08e01c 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 7757e0b7ea..e925926619 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 d168e39b66..eb1b766bdc 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/16] 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 4d8fa11caa..be51d5a6ba 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 e925926619..aafb8a4eef 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 eb1b766bdc..00edf56900 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,36 +23,64 @@ 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 { @@ -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/16] 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 00edf56900..ecb60020c9 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/16] 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 38e27a006c..4e1aad148d 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 ecb60020c9..a8a10916bb 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 5ca8c405d165ccff0e57ae1b1305e7d048691bc0 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 4 Feb 2022 15:53:31 -0600 Subject: [PATCH 16/16] 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 aafb8a4eef..cf95af5954 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 02ce7c8168..e348ad4cda 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 cbf40a03d3..fc97020c31 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 {