Merge branch 'master' into htmlserialize
This commit is contained in:
@@ -173,3 +173,8 @@
|
|||||||
.xterm-strikethrough {
|
.xterm-strikethrough {
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.xterm-screen .xterm-decoration-container .xterm-decoration {
|
||||||
|
z-index: 6;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ if (document.location.pathname === '/test') {
|
|||||||
document.getElementById('htmlserialize').addEventListener('click', htmlSerializeButtonHandler);
|
document.getElementById('htmlserialize').addEventListener('click', htmlSerializeButtonHandler);
|
||||||
document.getElementById('custom-glyph').addEventListener('click', writeCustomGlyphHandler);
|
document.getElementById('custom-glyph').addEventListener('click', writeCustomGlyphHandler);
|
||||||
document.getElementById('load-test').addEventListener('click', loadTest);
|
document.getElementById('load-test').addEventListener('click', loadTest);
|
||||||
|
document.getElementById('add-decoration').addEventListener('click', addDecoration);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTerminal(): void {
|
function createTerminal(): void {
|
||||||
@@ -541,3 +542,12 @@ function loadTest() {
|
|||||||
term._core._onData.fire('\x03');
|
term._core._onData.fire('\x03');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addDecoration() {
|
||||||
|
const marker = term.addMarker(1);
|
||||||
|
const decoration = term.registerDecoration({ marker });
|
||||||
|
term.write('');
|
||||||
|
decoration.onRender(() => {
|
||||||
|
decoration.element.style.backgroundColor = 'red';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -68,6 +68,7 @@
|
|||||||
<button id="dispose" title="This is used to testing memory leaks">Dispose terminal</button>
|
<button id="dispose" title="This is used to testing memory leaks">Dispose terminal</button>
|
||||||
<button id="custom-glyph" title="Write custom box drawing and block element characters to the terminal">Test custom glyphs</button>
|
<button id="custom-glyph" title="Write custom box drawing and block element characters to the terminal">Test custom glyphs</button>
|
||||||
<button id="load-test" title="Write several MB of data to simulate a lot of data coming from the process">Load test</button>
|
<button id="load-test" title="Write several MB of data to simulate a lot of data coming from the process">Load test</button>
|
||||||
|
<button id="add-decoration" title="Add a decoration to the terminal">Decoration</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+10
-2
@@ -37,7 +37,7 @@ import * as Strings from 'browser/LocalizableStrings';
|
|||||||
import { SoundService } from 'browser/services/SoundService';
|
import { SoundService } from 'browser/services/SoundService';
|
||||||
import { MouseZoneManager } from 'browser/MouseZoneManager';
|
import { MouseZoneManager } from 'browser/MouseZoneManager';
|
||||||
import { AccessibilityManager } from './AccessibilityManager';
|
import { AccessibilityManager } from './AccessibilityManager';
|
||||||
import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider } from 'xterm';
|
import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm';
|
||||||
import { DomRenderer } from 'browser/renderer/dom/DomRenderer';
|
import { DomRenderer } from 'browser/renderer/dom/DomRenderer';
|
||||||
import { KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, ColorRequestType } from 'common/Types';
|
import { KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, ColorRequestType } from 'common/Types';
|
||||||
import { evaluateKeyboardEvent } from 'common/input/Keyboard';
|
import { evaluateKeyboardEvent } from 'common/input/Keyboard';
|
||||||
@@ -45,7 +45,7 @@ import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
|
|||||||
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||||
import { ColorManager } from 'browser/ColorManager';
|
import { ColorManager } from 'browser/ColorManager';
|
||||||
import { RenderService } from 'browser/services/RenderService';
|
import { RenderService } from 'browser/services/RenderService';
|
||||||
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService, ICharacterJoinerService } from 'browser/services/Services';
|
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService, ICharacterJoinerService, IDecorationService } from 'browser/services/Services';
|
||||||
import { CharSizeService } from 'browser/services/CharSizeService';
|
import { CharSizeService } from 'browser/services/CharSizeService';
|
||||||
import { IBuffer } from 'common/buffer/Types';
|
import { IBuffer } from 'common/buffer/Types';
|
||||||
import { MouseService } from 'browser/services/MouseService';
|
import { MouseService } from 'browser/services/MouseService';
|
||||||
@@ -55,6 +55,7 @@ import { CoreTerminal } from 'common/CoreTerminal';
|
|||||||
import { color, rgba } from 'browser/Color';
|
import { color, rgba } from 'browser/Color';
|
||||||
import { CharacterJoinerService } from 'browser/services/CharacterJoinerService';
|
import { CharacterJoinerService } from 'browser/services/CharacterJoinerService';
|
||||||
import { toRgbString } from 'common/input/XParseColor';
|
import { toRgbString } from 'common/input/XParseColor';
|
||||||
|
import { DecorationService } from 'browser/services/DecorationService';
|
||||||
|
|
||||||
// Let it work inside Node.js for automated testing purposes.
|
// Let it work inside Node.js for automated testing purposes.
|
||||||
const document: Document = (typeof window !== 'undefined') ? window.document : null as any;
|
const document: Document = (typeof window !== 'undefined') ? window.document : null as any;
|
||||||
@@ -108,6 +109,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|||||||
public linkifier: ILinkifier;
|
public linkifier: ILinkifier;
|
||||||
public linkifier2: ILinkifier2;
|
public linkifier2: ILinkifier2;
|
||||||
public viewport: IViewport | undefined;
|
public viewport: IViewport | undefined;
|
||||||
|
public decorationService: IDecorationService;
|
||||||
private _compositionHelper: ICompositionHelper | undefined;
|
private _compositionHelper: ICompositionHelper | undefined;
|
||||||
private _mouseZoneManager: IMouseZoneManager | undefined;
|
private _mouseZoneManager: IMouseZoneManager | undefined;
|
||||||
private _accessibilityManager: AccessibilityManager | undefined;
|
private _accessibilityManager: AccessibilityManager | undefined;
|
||||||
@@ -157,6 +159,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|||||||
|
|
||||||
this.linkifier = this._instantiationService.createInstance(Linkifier);
|
this.linkifier = this._instantiationService.createInstance(Linkifier);
|
||||||
this.linkifier2 = this.register(this._instantiationService.createInstance(Linkifier2));
|
this.linkifier2 = this.register(this._instantiationService.createInstance(Linkifier2));
|
||||||
|
this.decorationService = this.register(this._instantiationService.createInstance(DecorationService));
|
||||||
|
|
||||||
// Setup InputHandler listeners
|
// Setup InputHandler listeners
|
||||||
this.register(this._inputHandler.onRequestBell(() => this.bell()));
|
this.register(this._inputHandler.onRequestBell(() => this.bell()));
|
||||||
@@ -574,6 +577,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|||||||
this.linkifier.attachToDom(this.element, this._mouseZoneManager);
|
this.linkifier.attachToDom(this.element, this._mouseZoneManager);
|
||||||
this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService);
|
this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService);
|
||||||
|
|
||||||
|
this.decorationService.attachToDom(this.screenElement, this._renderService, this._bufferService);
|
||||||
// This event listener must be registered aftre MouseZoneManager is created
|
// This event listener must be registered aftre MouseZoneManager is created
|
||||||
this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e)));
|
this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e)));
|
||||||
|
|
||||||
@@ -998,6 +1002,10 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|||||||
return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset);
|
return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {
|
||||||
|
return this.decorationService!.registerDecoration(decorationOptions);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets whether the terminal has an active selection.
|
* Gets whether the terminal has an active selection.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IDisposable, IMarker, ISelectionPosition, ILinkProvider } from 'xterm';
|
import { IDisposable, IMarker, ISelectionPosition, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm';
|
||||||
import { IEvent, EventEmitter } from 'common/EventEmitter';
|
import { IEvent, EventEmitter } from 'common/EventEmitter';
|
||||||
import { ICharacterJoinerService, ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services';
|
import { ICharacterJoinerService, ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services';
|
||||||
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types';
|
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types';
|
||||||
@@ -102,6 +102,9 @@ export class MockTerminal implements ITerminal {
|
|||||||
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
|
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
public hasSelection(): boolean {
|
public hasSelection(): boolean {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
@@ -283,6 +286,9 @@ export class MockRenderer implements IRenderer {
|
|||||||
public setColors(colors: IColorSet): void {
|
public setColors(colors: IColorSet): void {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
public onResize(cols: number, rows: number): void { }
|
public onResize(cols: number, rows: number): void { }
|
||||||
public onCharSizeChanged(): void { }
|
public onCharSizeChanged(): void { }
|
||||||
public onBlur(): void { }
|
public onBlur(): void { }
|
||||||
@@ -422,6 +428,9 @@ export class MockRenderService implements IRenderService {
|
|||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MockCharacterJoinerService implements ICharacterJoinerService {
|
export class MockCharacterJoinerService implements ICharacterJoinerService {
|
||||||
|
|||||||
Vendored
+3
-2
@@ -3,11 +3,11 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IDisposable, IMarker, ISelectionPosition } from 'xterm';
|
import { IDecorationOptions, IDecoration, IDisposable, IMarker, ISelectionPosition } from 'xterm';
|
||||||
import { IEvent } from 'common/EventEmitter';
|
import { IEvent } from 'common/EventEmitter';
|
||||||
import { ICoreTerminal, CharData, ITerminalOptions } from 'common/Types';
|
import { ICoreTerminal, CharData, ITerminalOptions } from 'common/Types';
|
||||||
import { IMouseService, IRenderService } from './services/Services';
|
import { IMouseService, IRenderService } from './services/Services';
|
||||||
import { IBuffer, IBufferSet } from 'common/buffer/Types';
|
import { IBuffer } from 'common/buffer/Types';
|
||||||
import { IFunctionIdentifier, IParams } from 'common/parser/Types';
|
import { IFunctionIdentifier, IParams } from 'common/parser/Types';
|
||||||
|
|
||||||
export interface ITerminal extends IPublicTerminal, ICoreTerminal {
|
export interface ITerminal extends IPublicTerminal, ICoreTerminal {
|
||||||
@@ -61,6 +61,7 @@ export interface IPublicTerminal extends IDisposable {
|
|||||||
registerCharacterJoiner(handler: (text: string) => [number, number][]): number;
|
registerCharacterJoiner(handler: (text: string) => [number, number][]): number;
|
||||||
deregisterCharacterJoiner(joinerId: number): void;
|
deregisterCharacterJoiner(joinerId: number): void;
|
||||||
addMarker(cursorYOffset: number): IMarker | undefined;
|
addMarker(cursorYOffset: number): IMarker | undefined;
|
||||||
|
registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined;
|
||||||
hasSelection(): boolean;
|
hasSelection(): boolean;
|
||||||
getSelection(): string;
|
getSelection(): string;
|
||||||
getSelectionPosition(): ISelectionPosition | undefined;
|
getSelectionPosition(): ISelectionPosition | undefined;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* @license MIT
|
* @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, IDecorationOptions, IDecoration } from 'xterm';
|
||||||
import { ITerminal } from 'browser/Types';
|
import { ITerminal } from 'browser/Types';
|
||||||
import { Terminal as TerminalCore } from 'browser/Terminal';
|
import { Terminal as TerminalCore } from 'browser/Terminal';
|
||||||
import * as Strings from 'browser/LocalizableStrings';
|
import * as Strings from 'browser/LocalizableStrings';
|
||||||
@@ -171,6 +171,11 @@ export class Terminal implements ITerminalApi {
|
|||||||
this._verifyIntegers(cursorYOffset);
|
this._verifyIntegers(cursorYOffset);
|
||||||
return this._core.addMarker(cursorYOffset);
|
return this._core.addMarker(cursorYOffset);
|
||||||
}
|
}
|
||||||
|
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {
|
||||||
|
this._checkProposedApi();
|
||||||
|
this._verifyPositiveIntegers(decorationOptions.x ?? 0, decorationOptions.width ?? 0, decorationOptions.height ?? 0);
|
||||||
|
return this._core.registerDecoration(decorationOptions);
|
||||||
|
}
|
||||||
public addMarker(cursorYOffset: number): IMarker | undefined {
|
public addMarker(cursorYOffset: number): IMarker | undefined {
|
||||||
return this.registerMarker(cursorYOffset);
|
return this.registerMarker(cursorYOffset);
|
||||||
}
|
}
|
||||||
@@ -281,4 +286,12 @@ export class Terminal implements ITerminalApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _verifyPositiveIntegers(...values: number[]): void {
|
||||||
|
for (const value of values) {
|
||||||
|
if (value && (value === Infinity || isNaN(value) || value % 1 !== 0 || value < 0)) {
|
||||||
|
throw new Error('This API only accepts positive integers');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,11 @@ import { IRenderLayer, IRenderer, IRenderDimensions, IRequestRedrawEvent } from
|
|||||||
import { LinkRenderLayer } from 'browser/renderer/LinkRenderLayer';
|
import { LinkRenderLayer } from 'browser/renderer/LinkRenderLayer';
|
||||||
import { Disposable } from 'common/Lifecycle';
|
import { Disposable } from 'common/Lifecycle';
|
||||||
import { IColorSet, ILinkifier, ILinkifier2 } from 'browser/Types';
|
import { IColorSet, ILinkifier, ILinkifier2 } from 'browser/Types';
|
||||||
import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services';
|
import { ICharSizeService } from 'browser/services/Services';
|
||||||
import { IBufferService, IOptionsService, ICoreService, IInstantiationService } from 'common/services/Services';
|
import { IBufferService, IOptionsService, IInstantiationService } from 'common/services/Services';
|
||||||
import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache';
|
import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache';
|
||||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||||
|
import { IDecorationOptions, IDecoration } from 'xterm';
|
||||||
|
|
||||||
let nextRendererId = 1;
|
let nextRendererId = 1;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2022 The xterm.js authors. All rights reserved.
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { IDecorationService, IRenderService } from 'browser/services/Services';
|
||||||
|
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||||
|
import { Disposable } from 'common/Lifecycle';
|
||||||
|
import { IBufferService, IInstantiationService } from 'common/services/Services';
|
||||||
|
import { IDecorationOptions, IDecoration, IMarker } from 'xterm';
|
||||||
|
|
||||||
|
export class DecorationService extends Disposable implements IDecorationService {
|
||||||
|
|
||||||
|
private readonly _decorations: Decoration[] = [];
|
||||||
|
private _container: HTMLElement | undefined;
|
||||||
|
private _screenElement: HTMLElement | undefined;
|
||||||
|
private _renderService: IRenderService | undefined;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@IBufferService private readonly _bufferService: IBufferService,
|
||||||
|
@IInstantiationService private readonly _instantiationService: IInstantiationService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public attachToDom(screenElement: HTMLElement, renderService: IRenderService): void {
|
||||||
|
this._renderService = renderService;
|
||||||
|
this._screenElement = screenElement;
|
||||||
|
this._container = document.createElement('div');
|
||||||
|
this._container.classList.add('xterm-decoration-container');
|
||||||
|
screenElement.appendChild(this._container);
|
||||||
|
this.refresh();
|
||||||
|
this.register(this._renderService.onRenderedBufferChange(() => this.refresh()));
|
||||||
|
this.register(this._renderService.onDimensionsChange(() => this.refresh(true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {
|
||||||
|
if (decorationOptions.marker.isDisposed || !this._container) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const decoration = this._instantiationService.createInstance(Decoration, decorationOptions, this._container);
|
||||||
|
this._decorations.push(decoration);
|
||||||
|
decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1));
|
||||||
|
return decoration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public refresh(recreate?: boolean): void {
|
||||||
|
if (!this._bufferService || !this._renderService) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const decoration of this._decorations) {
|
||||||
|
decoration.render(this._renderService, recreate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
for (const decoration of this._decorations) {
|
||||||
|
decoration.dispose();
|
||||||
|
}
|
||||||
|
if (this._container) {
|
||||||
|
this._screenElement?.removeChild(this._container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class Decoration extends Disposable implements IDecoration {
|
||||||
|
private static _nextId = 1;
|
||||||
|
private readonly _marker: IMarker;
|
||||||
|
private _element: HTMLElement | undefined;
|
||||||
|
private readonly _id: number = Decoration._nextId++;
|
||||||
|
public isDisposed: boolean = false;
|
||||||
|
|
||||||
|
public get element(): HTMLElement | undefined { return this._element; }
|
||||||
|
public get marker(): IMarker { return this._marker; }
|
||||||
|
|
||||||
|
private _onDispose = new EventEmitter<void>();
|
||||||
|
public get onDispose(): IEvent<void> { return this._onDispose.event; }
|
||||||
|
|
||||||
|
private _onRender = new EventEmitter<HTMLElement>();
|
||||||
|
public get onRender(): IEvent<HTMLElement> { return this._onRender.event; }
|
||||||
|
|
||||||
|
public x: number;
|
||||||
|
public anchor: 'left' | 'right';
|
||||||
|
public width: number;
|
||||||
|
public height: number;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
options: IDecorationOptions,
|
||||||
|
private readonly _container: HTMLElement,
|
||||||
|
@IBufferService private readonly _bufferService: IBufferService
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.x = options.x ?? 0;
|
||||||
|
this._marker = options.marker;
|
||||||
|
this.anchor = options.anchor || 'left';
|
||||||
|
this.width = options.width || 1;
|
||||||
|
this.height = options.height || 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(renderService: IRenderService, recreate?: boolean): void {
|
||||||
|
if (!this._element || recreate) {
|
||||||
|
this._createElement(renderService, recreate);
|
||||||
|
}
|
||||||
|
if (this._container && this._element && !this._container.contains(this._element)) {
|
||||||
|
this._container.append(this._element);
|
||||||
|
}
|
||||||
|
this._refreshStyle(renderService);
|
||||||
|
this._onRender.fire(this._element!);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createElement(renderService: IRenderService, recreate?: boolean): void {
|
||||||
|
if (recreate && this._element) {
|
||||||
|
this._container.removeChild(this._element);
|
||||||
|
}
|
||||||
|
this._element = document.createElement('div');
|
||||||
|
this._element.classList.add('xterm-decoration');
|
||||||
|
this._element.style.width = `${this.width * renderService.dimensions.scaledCellWidth}px`;
|
||||||
|
this._element.style.height = `${this.height * renderService.dimensions.scaledCellHeight}px`;
|
||||||
|
this._element.style.top = `${(this.marker.line - this._bufferService.buffers.active.ydisp) * renderService.dimensions.scaledCellHeight}px`;
|
||||||
|
|
||||||
|
if (this.x && this.x > this._bufferService.cols) {
|
||||||
|
this._element!.style.display = 'none';
|
||||||
|
}
|
||||||
|
if (this.anchor === 'right') {
|
||||||
|
this._element.style.right = this.x ? `${this.x * renderService.dimensions.scaledCellWidth}px` : '';
|
||||||
|
} else {
|
||||||
|
this._element.style.left = this.x ? `${this.x * renderService.dimensions.scaledCellWidth}px` : '';
|
||||||
|
}
|
||||||
|
this.register({
|
||||||
|
dispose: () => {
|
||||||
|
if (this.isDisposed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._container.removeChild(this._element!);
|
||||||
|
this.isDisposed = true;
|
||||||
|
this._marker.dispose();
|
||||||
|
// Emit before super.dispose such that dispose listeners get a change to react
|
||||||
|
this._onDispose.fire();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _refreshStyle(renderService: IRenderService): void {
|
||||||
|
const line = this.marker.line - this._bufferService.buffers.active.ydisp;
|
||||||
|
if (line < 0 || line > this._bufferService.rows) {
|
||||||
|
// outside of viewport
|
||||||
|
this._element!.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
this._element!.style.top = `${line * renderService.dimensions.scaledCellHeight}px`;
|
||||||
|
this._element!.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@ import { IColorSet } from 'browser/Types';
|
|||||||
import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
|
import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
|
||||||
import { createDecorator } from 'common/services/ServiceRegistry';
|
import { createDecorator } from 'common/services/ServiceRegistry';
|
||||||
import { IDisposable } from 'common/Types';
|
import { IDisposable } from 'common/Types';
|
||||||
|
import { IDecorationOptions, IDecoration } from 'xterm';
|
||||||
|
import { IBufferService } from 'common/services/Services';
|
||||||
|
|
||||||
export const ICharSizeService = createDecorator<ICharSizeService>('CharSizeService');
|
export const ICharSizeService = createDecorator<ICharSizeService>('CharSizeService');
|
||||||
export interface ICharSizeService {
|
export interface ICharSizeService {
|
||||||
@@ -113,3 +115,11 @@ export interface ICharacterJoinerService {
|
|||||||
deregister(joinerId: number): boolean;
|
deregister(joinerId: number): boolean;
|
||||||
getJoinedCharacters(row: number): [number, number][];
|
getJoinedCharacters(row: number): [number, number][];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const IDecorationService = createDecorator<IDecorationService>('DecorationService');
|
||||||
|
export interface IDecorationService extends IDisposable {
|
||||||
|
registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined;
|
||||||
|
refresh(): void;
|
||||||
|
attachToDom(screenElement: HTMLElement, renderService: IRenderService, bufferService: IBufferService): void;
|
||||||
|
}
|
||||||
|
|||||||
@@ -731,6 +731,52 @@ describe('API Integration Tests', function(): void {
|
|||||||
await pollFor(page, `window.term._core._renderService.dimensions.actualCellWidth > 0`, true);
|
await pollFor(page, `window.term._core._renderService.dimensions.actualCellWidth > 0`, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('registerDecoration', () => {
|
||||||
|
it('should register decorations and render them', async () => {
|
||||||
|
await openTerminal(page);
|
||||||
|
await writeSync(page, '\\n\\n\\n\\n');
|
||||||
|
await writeSync(page, '\\n\\n\\n\\n');
|
||||||
|
await writeSync(page, '\\n\\n\\n\\n');
|
||||||
|
await page.evaluate(`window.marker1 = window.term.addMarker(1)`);
|
||||||
|
await page.evaluate(`window.marker2 = window.term.addMarker(2)`);
|
||||||
|
await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`);
|
||||||
|
await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`);
|
||||||
|
await page.evaluate(`window.term.resize(10, 5)`);
|
||||||
|
assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 2);
|
||||||
|
});
|
||||||
|
it('on resize should dispose of the old decoration and create a new one', async () => {
|
||||||
|
await openTerminal(page);
|
||||||
|
await writeSync(page, '\\n\\n\\n\\n');
|
||||||
|
await writeSync(page, '\\n\\n\\n\\n');
|
||||||
|
await page.evaluate(`window.marker = window.term.addMarker(1)`);
|
||||||
|
await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker })`);
|
||||||
|
await page.evaluate(`window.term.resize(10, 5)`);
|
||||||
|
assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 1);
|
||||||
|
});
|
||||||
|
it('should return undefined when the marker has already been disposed of', async () => {
|
||||||
|
await openTerminal(page);
|
||||||
|
await writeSync(page, '\\n\\n\\n\\n');
|
||||||
|
await writeSync(page, '\\n\\n\\n\\n');
|
||||||
|
await page.evaluate(`window.marker = window.term.addMarker(1)`);
|
||||||
|
await page.evaluate(`window.marker.dispose()`);
|
||||||
|
assert.equal(await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker });`), undefined);
|
||||||
|
});
|
||||||
|
it('should throw when a negative x offset is provided', async () => {
|
||||||
|
await openTerminal(page);
|
||||||
|
await writeSync(page, '\\n\\n\\n\\n');
|
||||||
|
await writeSync(page, '\\n\\n\\n\\n');
|
||||||
|
await page.evaluate(`window.marker = window.term.addMarker(1)`);
|
||||||
|
await page.evaluate(`
|
||||||
|
try {
|
||||||
|
window.decoration = window.term.registerDecoration({ marker: window.marker, x: -2 });
|
||||||
|
} catch (e) {
|
||||||
|
window.throwMessage = e.message;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
await pollFor(page, 'window.throwMessage', 'This API only accepts positive integers');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('registerLinkProvider', () => {
|
describe('registerLinkProvider', () => {
|
||||||
it('should fire provideLinks when hovering cells', async () => {
|
it('should fire provideLinks when hovering cells', async () => {
|
||||||
await openTerminal(page, { rendererType: 'dom' });
|
await openTerminal(page, { rendererType: 'dom' });
|
||||||
|
|||||||
Vendored
+85
-9
@@ -380,29 +380,96 @@ declare module 'xterm' {
|
|||||||
* is trimmed and lines are added or removed. This is a single line that may
|
* is trimmed and lines are added or removed. This is a single line that may
|
||||||
* be part of a larger wrapped line.
|
* be part of a larger wrapped line.
|
||||||
*/
|
*/
|
||||||
export interface IMarker extends IDisposable {
|
export interface IMarker extends IDisposableWithEvent {
|
||||||
/**
|
/**
|
||||||
* A unique identifier for this marker.
|
* A unique identifier for this marker.
|
||||||
*/
|
*/
|
||||||
readonly id: number;
|
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
|
* The actual line index in the buffer at this point in time. This is set to
|
||||||
* -1 if the marker has been disposed.
|
* -1 if the marker has been disposed.
|
||||||
*/
|
*/
|
||||||
readonly line: number;
|
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
|
* Event listener to get notified when this gets disposed.
|
||||||
* might happen for a marker, that got invalidated by scrolling out or removal of
|
|
||||||
* a line from the buffer.
|
|
||||||
*/
|
*/
|
||||||
onDispose: IEvent<void>;
|
onDispose: IEvent<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<HTMLElement>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The HTMLElement that gets created after the
|
||||||
|
* first _onRender call, or undefined if accessed before
|
||||||
|
* that.
|
||||||
|
*/
|
||||||
|
readonly element: HTMLElement | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options provided when registering a decoration
|
||||||
|
* containing a @param marker, @param anchor,
|
||||||
|
* @param x offset from the anchor, @param width in cells
|
||||||
|
* and @param height in cells.
|
||||||
|
*/
|
||||||
|
export interface IDecorationOptions {
|
||||||
|
/**
|
||||||
|
* The line in the terminal where
|
||||||
|
* 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -870,6 +937,15 @@ declare module 'xterm' {
|
|||||||
*/
|
*/
|
||||||
addMarker(cursorYOffset: number): IMarker | undefined;
|
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 alt buffer is active or the marker has already been disposed of.
|
||||||
|
* @throws when options include a negative x offset.
|
||||||
|
*/
|
||||||
|
registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets whether the terminal has an active selection.
|
* Gets whether the terminal has an active selection.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user