diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index b1aa03ec..4d8fa11c 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -13,7 +13,7 @@ import { IWebGL2RenderingContext } from './Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; import { Disposable } from 'common/Lifecycle'; import { Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; -import { Terminal, IEvent, IBufferDecorationOptions, IDecoration, IGutterDecorationOptions } from 'xterm'; +import { Terminal, IEvent, IBufferDecorationOptions, IDecoration } from 'xterm'; import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; import { ITerminal, IColorSet } from 'browser/Types'; @@ -118,12 +118,12 @@ export class WebglRenderer extends Disposable implements IRenderer { return this._charAtlas?.cacheCanvas; } - public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration { + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { const decorationLayer = this._renderLayers.find(l => l instanceof DecorationRenderLayer); if (decorationLayer instanceof DecorationRenderLayer) { return decorationLayer.registerDecoration(decorationOptions); } - throw new Error('no decoration layer'); + return undefined; } public setColors(colors: IColorSet): void { diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index fb26a2eb..8026175e 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -37,7 +37,7 @@ import * as Strings from 'browser/LocalizableStrings'; import { SoundService } from 'browser/services/SoundService'; import { MouseZoneManager } from 'browser/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; -import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider, IBufferDecorationOptions, IDecoration, IGutterDecorationOptions } from 'xterm'; +import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider, IBufferDecorationOptions, IDecoration } from 'xterm'; import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; import { KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, ColorRequestType } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; @@ -998,7 +998,7 @@ export class Terminal extends CoreTerminal implements ITerminal { return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset); } - public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration | undefined { + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { return this._renderService?.registerDecoration(decorationOptions); } /** diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index c42e4a55..1a29c186 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IDisposable, IMarker, ISelectionPosition, ILinkProvider, IBufferDecorationOptions, IDecoration, IGutterDecorationOptions, IDecorationOptions } from 'xterm'; +import { IDisposable, IMarker, ISelectionPosition, ILinkProvider, IBufferDecorationOptions, IDecoration } from 'xterm'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { ICharacterJoinerService, ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; @@ -102,7 +102,7 @@ export class MockTerminal implements ITerminal { public registerLinkProvider(linkProvider: ILinkProvider): IDisposable { throw new Error('Method not implemented.'); } - public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration | undefined { + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { throw new Error('Method not implemented.'); } public hasSelection(): boolean { @@ -293,7 +293,7 @@ export class MockRenderer implements IRenderer { public onDevicePixelRatioChange(): void { } public clear(): void { } public renderRows(start: number, end: number): void { } - public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration { + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration { throw new Error('Method not implemented.'); } } @@ -425,7 +425,7 @@ export class MockRenderService implements IRenderService { public dispose(): void { throw new Error('Method not implemented.'); } - public registerDecoration(decorationOptions: IDecorationOptions): IDecoration { + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration { throw new Error('Method not implemented.'); } } diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 779a9454..69ba3047 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IBufferDecorationOptions, IDecoration, IDisposable, IGutterDecorationOptions, IMarker, ISelectionPosition } from 'xterm'; +import { IBufferDecorationOptions, IDecoration, IDisposable, IMarker, ISelectionPosition } from 'xterm'; import { IEvent } from 'common/EventEmitter'; import { ICoreTerminal, CharData, ITerminalOptions } from 'common/Types'; import { IMouseService, IRenderService } from './services/Services'; @@ -61,7 +61,7 @@ export interface IPublicTerminal extends IDisposable { registerCharacterJoiner(handler: (text: string) => [number, number][]): number; deregisterCharacterJoiner(joinerId: number): void; addMarker(cursorYOffset: number): IMarker | undefined; - registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration | undefined; + registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined; hasSelection(): boolean; getSelection(): string; getSelectionPosition(): ISelectionPosition | undefined; diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 1f3c32fe..c641f5f1 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal as ITerminalApi, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes, IBufferDecorationOptions, IDecoration, IGutterDecorationOptions } from 'xterm'; +import { Terminal as ITerminalApi, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes, IBufferDecorationOptions, IDecoration } from 'xterm'; import { ITerminal } from 'browser/Types'; import { Terminal as TerminalCore } from 'browser/Terminal'; import * as Strings from 'browser/LocalizableStrings'; @@ -171,7 +171,7 @@ export class Terminal implements ITerminalApi { this._verifyIntegers(cursorYOffset); return this._core.addMarker(cursorYOffset); } - public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration | undefined { + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { this._checkProposedApi(); return this._core.registerDecoration(decorationOptions); } diff --git a/src/browser/renderer/DecorationRenderLayer.ts b/src/browser/renderer/DecorationRenderLayer.ts index 39254d9c..fd01c275 100644 --- a/src/browser/renderer/DecorationRenderLayer.ts +++ b/src/browser/renderer/DecorationRenderLayer.ts @@ -3,20 +3,19 @@ * @license MIT */ -import { addDisposableDomListener } from 'browser/Lifecycle'; import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer'; import { IRequestRedrawEvent } from 'browser/renderer/Types'; import { IColorSet } from 'browser/Types'; -import { Marker } from 'common/buffer/Marker'; import { EventEmitter, IEventEmitter } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IBufferService, IOptionsService } from 'common/services/Services'; -import { IBufferDecorationOptions, IDecoration, IEvent, IGutterDecorationOptions } from 'xterm'; +import { IBufferDecorationOptions, IDecoration, IEvent, IMarker } from 'xterm'; const enum DefaultButton { COLOR = '#5DA5D5' } export class DecorationRenderLayer extends BaseRenderLayer { + private _decorations: IDecoration[] = []; constructor( container: HTMLElement, zIndex: number, @@ -27,33 +26,37 @@ export class DecorationRenderLayer extends BaseRenderLayer { @IOptionsService optionsService: IOptionsService ) { super(container, 'decoration', zIndex, true, colors, rendererId, bufferService, optionsService); - this.registerDecoration({ startMarker: new Marker(1), shape: 'button' }); + // this.registerDecoration({ startMarker: new Marker(1), shape: 'button' }); } + + public onGridChanged(startRow: number, endRow: number): void { + for (const decoration of this._decorations) { + (decoration as BufferDecoration).render(); + } + } + public reset(): void { } - public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration { - if ('shape' in decorationOptions) { - return new BufferDecoration(decorationOptions, this._ctx.canvas); + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { + if (decorationOptions.marker.isDisposed) { + return undefined; } - throw new Error('Gutter decoration not yet implemented'); + return new BufferDecoration(decorationOptions, this._ctx.canvas); } } class BufferDecoration extends Disposable implements IDecoration { private static _nextId = 1; - + private _marker: IMarker; private _element: HTMLElement | undefined; private _id: number = BufferDecoration._nextId++; - private _line: number; public isDisposed: boolean = false; public get id(): number { return this._id; } - - public get line(): number { return this._line; } - public get element(): HTMLElement { return this._element!; } + public get marker(): IMarker { return this._marker; } private _onDispose = new EventEmitter(); public get onDispose(): IEvent { return this._onDispose.event; } @@ -63,29 +66,29 @@ class BufferDecoration extends Disposable implements IDecoration { constructor( decorationOptions: IBufferDecorationOptions, - container: HTMLElement + private readonly _container: HTMLElement ) { super(); - this._line = decorationOptions.startMarker.line; - if (decorationOptions.shape === 'button') { - const color = decorationOptions.color || DefaultButton.COLOR; - this._element = document.createElement('menu'); - this._element.classList.add('button-buffer-decoration'); - this._element.id = 'button-buffer-decoration-' + this._id; - this._element.style.background = color; - this._element.style.width = '1px'; - this._element.style.height = '32px'; - this._element.style.borderRadius = '64px'; - this._element.style.border = `4px solid white`; - this._element.style.zIndex = '6'; - this._element.style.position = 'absolute'; - this._element.style.top = '0px'; + + this._marker = decorationOptions.marker; + const color = DefaultButton.COLOR; + this._element = document.createElement('menu'); + this._element.classList.add('button-buffer-decoration'); + this._element.id = 'button-buffer-decoration-' + this._id; + this._element.style.background = color; + this._element.style.width = '1px'; + this._element.style.height = '32px'; + this._element.style.borderRadius = '64px'; + this._element.style.border = `4px solid white`; + this._element.style.zIndex = '6'; + this._element.style.position = 'absolute'; + if (decorationOptions.anchor === 'right') { this._element.style.right = '5px'; - addDisposableDomListener(this._element, 'click', () => console.log('circle')); - container.parentElement!.append(this._element); - this._onRender.fire(this._element); } else { - throw new Error('only shape that has been implemented so far is button'); + this._element.style.left = '5px'; + } + if (this._container.parentElement && this._element) { + this._container.parentElement.append(this._element); } } @@ -94,9 +97,19 @@ class BufferDecoration extends Disposable implements IDecoration { return; } this.isDisposed = true; - this._line = -1; + this._marker.dispose(); // Emit before super.dispose such that dispose listeners get a change to react this._onDispose.fire(); super.dispose(); } + + public render(): void { + if (!this._element) { + return; + } + if (this._container.parentElement && !this._container.parentElement.contains(this._element)) { + this._container.parentElement.append(this._element); + } + this._onRender.fire(this._element); + } } diff --git a/src/browser/renderer/Renderer.ts b/src/browser/renderer/Renderer.ts index 4829e0bb..3e96fae1 100644 --- a/src/browser/renderer/Renderer.ts +++ b/src/browser/renderer/Renderer.ts @@ -15,7 +15,7 @@ import { IBufferService, IOptionsService, ICoreService, IInstantiationService } import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { DecorationRenderLayer } from 'browser/renderer/DecorationRenderLayer'; -import { IBufferDecorationOptions, IGutterDecorationOptions, IDecoration } from 'xterm'; +import { IBufferDecorationOptions, IDecoration } from 'xterm'; let nextRendererId = 1; @@ -68,12 +68,11 @@ export class Renderer extends Disposable implements IRenderer { this.onOptionsChanged(); } - public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration { + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { const decorationLayer = this._renderLayers.find(l => l instanceof DecorationRenderLayer); if (decorationLayer instanceof DecorationRenderLayer) { return decorationLayer.registerDecoration(decorationOptions); } - throw new Error('no decoration layer'); } public dispose(): void { diff --git a/src/browser/renderer/Types.d.ts b/src/browser/renderer/Types.d.ts index 8a328686..97e43b61 100644 --- a/src/browser/renderer/Types.d.ts +++ b/src/browser/renderer/Types.d.ts @@ -6,7 +6,7 @@ import { IDisposable } from 'common/Types'; import { IColorSet } from 'browser/Types'; import { IEvent } from 'common/EventEmitter'; -import { IBufferDecorationOptions, IDecoration, IGutterDecorationOptions } from 'xterm'; +import { IBufferDecorationOptions, IDecoration } from 'xterm'; export interface IRenderDimensions { scaledCharWidth: number; @@ -54,7 +54,7 @@ export interface IRenderer extends IDisposable { clear(): void; renderRows(start: number, end: number): void; clearTextureAtlas?(): void; - registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration; + registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined; } export interface IRenderLayer extends IDisposable { diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 58c54f65..ba23e573 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -14,7 +14,7 @@ import { EventEmitter, IEvent } from 'common/EventEmitter'; import { color } from 'browser/Color'; import { removeElementFromParent } from 'browser/Dom'; import { DecorationRenderLayer } from 'browser/renderer/DecorationRenderLayer'; -import { IBufferDecorationOptions, IGutterDecorationOptions, IDecoration } from 'xterm'; +import { IBufferDecorationOptions, IDecoration } from 'xterm'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; @@ -153,7 +153,7 @@ export class DomRenderer extends Disposable implements IRenderer { this._injectCss(); } - public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration { + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration { // const decorationLayer = this._renderLayers.find(l => l instanceof DecorationRenderLayer); // if (decorationLayer instanceof DecorationRenderLayer) { // return decorationLayer.registerDecoration(decorationOptions); diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 526bffa3..e2f6300b 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -12,7 +12,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { IColorSet, IRenderDebouncer } from 'browser/Types'; import { IOptionsService, IBufferService } from 'common/services/Services'; import { ICharSizeService, IRenderService } from 'browser/services/Services'; -import { IDecorationOptions, IDecoration, IGutterDecorationOptions, IBufferDecorationOptions } from 'xterm'; +import { IDecoration, IBufferDecorationOptions } from 'xterm'; interface ISelectionState { start: [number, number] | undefined; @@ -86,7 +86,7 @@ export class RenderService extends Disposable implements IRenderService { } } - public registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration { + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { return this._renderer.registerDecoration(decorationOptions); } diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index c3ec2eb5..40cb08d2 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -9,7 +9,7 @@ import { IColorSet } from 'browser/Types'; import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; import { IDisposable } from 'common/Types'; -import { IDecoration, IDecorationOptions } from 'xterm'; +import { IBufferDecorationOptions, IDecoration } from 'xterm'; export const ICharSizeService = createDecorator('CharSizeService'); export interface ICharSizeService { @@ -52,7 +52,7 @@ export interface IRenderService extends IDisposable { onRefreshRequest: IEvent<{ start: number, end: number }>; dimensions: IRenderDimensions; - registerDecoration(decorationOptions: IDecorationOptions): IDecoration; + registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined; refreshRows(start: number, end: number): void; clearTextureAtlas(): void; resize(cols: number, rows: number): void; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 0f01317f..52397e55 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -380,94 +380,99 @@ declare module 'xterm' { * is trimmed and lines are added or removed. This is a single line that may * be part of a larger wrapped line. */ - export interface IMarker extends IDisposable { + export interface IMarker extends IDisposableWithEvent { /** * A unique identifier for this marker. */ readonly id: number; - /** - * Whether this marker is disposed. - */ - readonly isDisposed: boolean; - /** * The actual line index in the buffer at this point in time. This is set to * -1 if the marker has been disposed. */ readonly line: number; - - /** - * Event listener to get notified when the marker gets disposed. Automatic disposal - * might happen for a marker, that got invalidated by scrolling out or removal of - * a line from the buffer. - */ - onDispose: IEvent; } /** - * Represents a decoration in the terminal that is associated with a particular marker. + * Represents a decoration in the terminal that is associated with a particular marker and DOM element. */ - export interface IDecoration extends IDisposable { - /** - * Whether this decoration is disposed. + export interface IDecoration extends IDisposableWithEvent { + /* + * The marker for the decoration in the terminal. */ - readonly isDisposed: boolean; + readonly marker: IMarker; /** - * The actual line index in the buffer at this point in time. This is set to - * -1 if the decoration has been disposed. - */ - readonly line: number; - - /** - * An event fired when the decoration - * is rendered, returns the dom element + * An event fired when the decoration + * is rendered, returns the dom element * associated with the decoration. */ onRender: IEvent; - } - - export interface IDecorationOptions { /** - * The line in the terminal where - * the decoration will be displayed - */ - startMarker: IMarker; - - /** - * The number of milliseconds the decoration - * should be displayed for. - */ - displayDuration?: number; - - /** - * The color of the decoration + * The HTMLElement that gets created after the + * first _onRender call, or undefined if accessed before + * that. */ - color?: string + element: HTMLElement | undefined; } - export interface IBufferDecorationOptions extends IDecorationOptions { + export interface IDisposableWithEvent extends IDisposable { /** - * The type of buffer decoration - */ - shape: 'button' | 'box-border'; + * Event listener to get notified when this gets disposed. + */ + onDispose: IEvent; + /** + * Whether this is disposed. + */ + readonly isDisposed: boolean; + } + + + export interface IBufferDecorationOptions { /* - * The x position for the decoration. - * Defaults to the right edge. + * Where the decoration will be anchored - + * defaults to the left edge. */ - position?: number; + anchor?: 'right' | 'left'; + + /** + * The line in the terminal where + * the decoration will be displayed + */ + marker: IMarker; + + /** + * The width of the decoration, which defaults to + * cell width + */ + width?: number; + + /** + * The height of the decoration, which defaults to + * cell height + */ + height?: number; + + /** + * The x position offset relative to the anchor + */ + x?: number; } - export interface IGutterDecorationOptions extends IDecorationOptions { - /** - * The end line in the terminal for - * the decoration - */ - endMarker: IMarker; - } + // export interface IGutterDecorationOptions { + // /** + // * The line in the terminal where + // * the decoration will be displayed + // */ + // startMarker: IMarker; + // /** + // * The end line in the terminal for + // * the decoration + // */ + // endMarker: IMarker; + // } /** * The set of localizable strings. @@ -935,11 +940,11 @@ declare module 'xterm' { addMarker(cursorYOffset: number): IMarker | undefined; /** - * (EXPERIMENTAL) Adds a decoration as configured with @param decorationOptions to the - * normal buffer or gutter and returns it. - * If the alt buffer is active or the decoration is invalid, undefined is returned. + * (EXPERIMENTAL) Adds a decoration to the terminal using + * @param decorationOptions, which takes a markers + * and a horizontal aligntment */ - registerDecoration(decorationOptions: IBufferDecorationOptions | IGutterDecorationOptions): IDecoration | undefined; + registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined; /** * Gets whether the terminal has an active selection.