UNPKG

xterm

Version:

Full xterm terminal, in your browser

141 lines (122 loc) 4.76 kB
/** * Copyright (c) 2022 The xterm.js authors. All rights reserved. * @license MIT */ import { css } from 'common/Color'; import { EventEmitter } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { IDecorationService, IInternalDecoration } from 'common/services/Services'; import { SortedList } from 'common/SortedList'; import { IColor } from 'common/Types'; import { IDecoration, IDecorationOptions, IMarker } from 'xterm'; // Work variables to avoid garbage collection let $xmin = 0; let $xmax = 0; export class DecorationService extends Disposable implements IDecorationService { public serviceBrand: any; /** * A list of all decorations, sorted by the marker's line value. This relies on the fact that * while marker line values do change, they should all change by the same amount so this should * never become out of order. */ private readonly _decorations: SortedList<IInternalDecoration> = new SortedList(e => e?.marker.line); private readonly _onDecorationRegistered = this.register(new EventEmitter<IInternalDecoration>()); public readonly onDecorationRegistered = this._onDecorationRegistered.event; private readonly _onDecorationRemoved = this.register(new EventEmitter<IInternalDecoration>()); public readonly onDecorationRemoved = this._onDecorationRemoved.event; public get decorations(): IterableIterator<IInternalDecoration> { return this._decorations.values(); } constructor() { super(); this.register(toDisposable(() => this.reset())); } public registerDecoration(options: IDecorationOptions): IDecoration | undefined { if (options.marker.isDisposed) { return undefined; } const decoration = new Decoration(options); if (decoration) { const markerDispose = decoration.marker.onDispose(() => decoration.dispose()); decoration.onDispose(() => { if (decoration) { if (this._decorations.delete(decoration)) { this._onDecorationRemoved.fire(decoration); } markerDispose.dispose(); } }); this._decorations.insert(decoration); this._onDecorationRegistered.fire(decoration); } return decoration; } public reset(): void { for (const d of this._decorations.values()) { d.dispose(); } this._decorations.clear(); } public *getDecorationsAtCell(x: number, line: number, layer?: 'bottom' | 'top'): IterableIterator<IInternalDecoration> { let xmin = 0; let xmax = 0; for (const d of this._decorations.getKeyIterator(line)) { xmin = d.options.x ?? 0; xmax = xmin + (d.options.width ?? 1); if (x >= xmin && x < xmax && (!layer || (d.options.layer ?? 'bottom') === layer)) { yield d; } } } public forEachDecorationAtCell(x: number, line: number, layer: 'bottom' | 'top' | undefined, callback: (decoration: IInternalDecoration) => void): void { this._decorations.forEachByKey(line, d => { $xmin = d.options.x ?? 0; $xmax = $xmin + (d.options.width ?? 1); if (x >= $xmin && x < $xmax && (!layer || (d.options.layer ?? 'bottom') === layer)) { callback(d); } }); } } class Decoration extends Disposable implements IInternalDecoration { public readonly marker: IMarker; public element: HTMLElement | undefined; public get isDisposed(): boolean { return this._isDisposed; } public readonly onRenderEmitter = this.register(new EventEmitter<HTMLElement>()); public readonly onRender = this.onRenderEmitter.event; private readonly _onDispose = this.register(new EventEmitter<void>()); public readonly onDispose = this._onDispose.event; private _cachedBg: IColor | undefined | null = null; public get backgroundColorRGB(): IColor | undefined { if (this._cachedBg === null) { if (this.options.backgroundColor) { this._cachedBg = css.toColor(this.options.backgroundColor); } else { this._cachedBg = undefined; } } return this._cachedBg; } private _cachedFg: IColor | undefined | null = null; public get foregroundColorRGB(): IColor | undefined { if (this._cachedFg === null) { if (this.options.foregroundColor) { this._cachedFg = css.toColor(this.options.foregroundColor); } else { this._cachedFg = undefined; } } return this._cachedFg; } constructor( public readonly options: IDecorationOptions ) { super(); this.marker = options.marker; if (this.options.overviewRulerOptions && !this.options.overviewRulerOptions.position) { this.options.overviewRulerOptions.position = 'full'; } } public override dispose(): void { this._onDispose.fire(); super.dispose(); } }