diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index af1591a897..be51d5a6ba 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 } from 'xterm'; import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; import { ITerminal, IColorSet } from 'browser/Types'; @@ -23,6 +23,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { ICharacterJoinerService } from 'browser/services/Services'; import { CharData, ICellData } from 'common/Types'; import { AttributeData } from 'common/buffer/AttributeData'; +import { IBufferService } from 'common/services/Services'; export class WebglRenderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; @@ -57,10 +58,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, @@ -115,7 +116,7 @@ export class WebglRenderer extends Disposable implements IRenderer { public get textureAtlas(): HTMLCanvasElement | undefined { return this._charAtlas?.cacheCanvas; } - + public setColors(colors: IColorSet): void { this._colors = colors; // Clear layers and force a full render 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/Terminal.ts b/src/browser/Terminal.ts index fe5c7f79f9..cf95af5954 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 } 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'; @@ -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); @@ -565,8 +569,12 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._onScroll.event(ev => { this.viewport!.syncScrollArea(); this._selectionService!.refresh(); + this._decorationsService!.refresh(); + })); + this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => { + this._selectionService!.refresh(); + this._decorationsService!.refresh(); })); - this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => this._selectionService!.refresh())); this._mouseZoneManager = this._instantiationService.createInstance(MouseZoneManager, this.element, this.screenElement); this.register(this._mouseZoneManager); @@ -998,6 +1006,13 @@ export class Terminal extends CoreTerminal implements ITerminal { return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset); } + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { + if (!this._decorationsService) { + throw new Error('cannot register a decoration without a decorations service'); + } + + return this._decorationsService.registerDecoration(decorationOptions); + } /** * Gets whether the terminal has an active selection. */ @@ -1293,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/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 8fdf458e6d..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 } 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,6 +102,9 @@ export class MockTerminal implements ITerminal { public registerLinkProvider(linkProvider: ILinkProvider): IDisposable { throw new Error('Method not implemented.'); } + public registerDecoration(decorationOptions: IBufferDecorationOptions): 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): 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: IBufferDecorationOptions): 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..69ba3047e1 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, 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): 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..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 } 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,6 +171,10 @@ export class Terminal implements ITerminalApi { this._verifyIntegers(cursorYOffset); return this._core.addMarker(cursorYOffset); } + public registerDecoration(decorationOptions: IBufferDecorationOptions): 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/Renderer.ts b/src/browser/renderer/Renderer.ts index 7a64257da7..305808a2a2 100644 --- a/src/browser/renderer/Renderer.ts +++ b/src/browser/renderer/Renderer.ts @@ -10,10 +10,11 @@ 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 { IBufferDecorationOptions, IDecoration } from 'xterm'; let nextRendererId = 1; diff --git a/src/browser/renderer/Types.d.ts b/src/browser/renderer/Types.d.ts index 6818a92673..c53871734a 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 } from 'xterm'; export interface IRenderDimensions { scaledCharWidth: number; diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index ee28339946..2057c791e0 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -13,6 +13,7 @@ import { IOptionsService, IBufferService, IInstantiationService } from 'common/s import { EventEmitter, IEvent } from 'common/EventEmitter'; import { color } from 'browser/Color'; import { removeElementFromParent } from 'browser/Dom'; +import { IBufferDecorationOptions, IDecoration } from 'xterm'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; @@ -151,6 +152,14 @@ export class DomRenderer extends Disposable implements IRenderer { this._injectCss(); } + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration { + // const decorationLayer = this._renderLayers.find(l => l instanceof DecorationRenderLayer); + // if (decorationLayer instanceof DecorationRenderLayer) { + // return decorationLayer.registerDecoration(decorationOptions); + // } + throw new Error('no decoration layer'); + } + private _injectCss(): void { if (!this._themeStyleElement) { this._themeStyleElement = document.createElement('style'); diff --git a/src/browser/services/DecorationsService.ts b/src/browser/services/DecorationsService.ts new file mode 100644 index 0000000000..a8a10916bb --- /dev/null +++ b/src/browser/services/DecorationsService.ts @@ -0,0 +1,148 @@ +/** + * Copyright (c) 2022 The xterm.js authors. All rights reserved. + * @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'; +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; + refresh(): void; + dispose(): void; +} + +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 + ) { + super(); + } + + public registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined { + if (decorationOptions.marker.isDisposed) { + return undefined; + } + this._resolveDimensions(decorationOptions); + const bufferDecoration = new BufferDecoration(decorationOptions, this._screenElement, this._renderService); + this._decorations.push(bufferDecoration); + return bufferDecoration; + } + + public refresh(): void { + if (this._animationFrame) { + return; + } + + this._animationFrame = window.requestAnimationFrame(() => this._refresh()); + } + + private _refresh(): void { + for (const decoration of this._decorations) { + const line = decoration.marker.line - this._bufferService.buffers.active.ydisp; + if (line < 0 || line > this._bufferService.rows) { + decoration.element.style.display = 'none'; + } else { + decoration.element.style.top = `${line *this._renderService.dimensions.scaledCellHeight}px`; + decoration.element.style.display = 'block'; + } + } + this._animationFrame = undefined; + } + + private _resolveDimensions(decorationOptions: IBufferDecorationOptions): void { + if (this._renderService.dimensions.scaledCellWidth) { + decorationOptions.width = decorationOptions.width ? decorationOptions.width * this._renderService.dimensions.scaledCellWidth : this._renderService.dimensions.scaledCellWidth; + } else { + throw new Error('unknown cell width'); + } + + if (this._renderService.dimensions.scaledCellHeight) { + decorationOptions.height = decorationOptions.height ? decorationOptions.height * this._renderService.dimensions.scaledCellHeight : this._renderService.dimensions.scaledCellHeight; + } else { + throw new Error('unknown cell height'); + } + } + + public dispose(): void { + if (this._animationFrame) { + window.cancelAnimationFrame(this._animationFrame); + this._animationFrame = undefined; + } + } +} + +export const IDecorationsService = createDecorator('DecorationsService'); +class BufferDecoration extends Disposable implements IDecoration { + private static _nextId = 1; + private _marker: IMarker; + 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!; } + public get marker(): IMarker { return this._marker; } + + private _onDispose = new EventEmitter(); + public get onDispose(): IEvent { return this._onDispose.event; } + + private _onRender = new EventEmitter(); + public get onRender(): IEvent { return this._onRender.event; } + + constructor( + private readonly _decorationOptions: IBufferDecorationOptions, + private readonly _screenElement: HTMLElement, + private readonly _renderService: IRenderService + ) { + super(); + this._marker = _decorationOptions.marker; + this._createElement(); + this._render(); + } + + public dispose(): void { + if (this.isDisposed) { + return; + } + this.isDisposed = true; + this._marker.dispose(); + // Emit before super.dispose such that dispose listeners get a change to react + this._onDispose.fire(); + super.dispose(); + } + + private _createElement(): void { + 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`; + if (this._decorationOptions.x && this._decorationOptions.x < 0) { + throw new Error(`Cannot create a decoration with a negative x offset: ${this._decorationOptions.x}`); + } + if (this._decorationOptions.anchor === 'right') { + this._element.style.right = this._decorationOptions.x ? `${this._decorationOptions.x * this._renderService.dimensions.scaledCellWidth}px` : ''; + } else { + 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); + } + } +} diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index b8283e0e69..d236135a7d 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 { IDecoration, IBufferDecorationOptions } from 'xterm'; interface ISelectionState { start: [number, number] | undefined; diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 4928fa28ec..8b7a0a77a3 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 { IBufferDecorationOptions, IDecoration } from 'xterm'; export const ICharSizeService = createDecorator('CharSizeService'); export interface ICharSizeService { @@ -51,7 +52,6 @@ export interface IRenderService extends IDisposable { onRefreshRequest: IEvent<{ start: number, end: number }>; dimensions: IRenderDimensions; - refreshRows(start: number, end: number): void; clearTextureAtlas(): void; resize(cols: number, rows: number): void; diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index 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 { diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 39245afe5e..a7f80a159c 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -380,31 +380,106 @@ 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; + } + /** + * Represents a disposable with an + * @param onDispose event listener and + * @param isDisposed property. + */ + export interface IDisposableWithEvent extends IDisposable { /** - * 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. + * 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. + */ + export interface IDecoration extends IDisposableWithEvent { + /* + * The marker for the decoration in the terminal. + */ + readonly marker: IMarker; + + /** + * An event fired when the decoration + * is rendered, returns the dom element + * associated with the decoration. + */ + readonly onRender: IEvent; + + /** + * The HTMLElement that gets created after the + * first _onRender call, or undefined if accessed before + * that. + */ + readonly element: HTMLElement | undefined; + } + + export interface IBufferDecorationOptions { + /** + * The line in the terminal where + * the decoration will be displayed + */ + marker: IMarker; + + /* + * Where the decoration will be anchored - + * defaults to the left edge + */ + anchor?: 'right' | 'left'; + + /** + * The 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 { + /** + * 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. */ @@ -870,6 +945,14 @@ declare module 'xterm' { */ addMarker(cursorYOffset: number): IMarker | undefined; + /** + * (EXPERIMENTAL) Adds a decoration to the terminal using + * @param decorationOptions, which takes a marker and an optional anchor, + * width, height, and x offset from the anchor. Returns the decoration or + * undefined if the marker has already been disposed of. + */ + registerDecoration(decorationOptions: IBufferDecorationOptions): IDecoration | undefined; + /** * Gets whether the terminal has an active selection. */