Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add Decorations #3630

Merged
merged 63 commits into from
Feb 10, 2022
Merged
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
c5810e6
Take 1 of api
meganrogge Jan 27, 2022
ebdd07b
get button to work
meganrogge Jan 29, 2022
00ce851
better button
meganrogge Jan 30, 2022
f9ac540
polish
meganrogge Jan 30, 2022
d7854bc
remove unused imports
meganrogge Jan 30, 2022
9b4801f
more polish
meganrogge Jan 30, 2022
c5a0711
clean up
meganrogge Jan 31, 2022
fc62451
more cleanup
meganrogge Jan 31, 2022
7d77268
improve docs
meganrogge Jan 31, 2022
5ccb41b
re add height and width
meganrogge Feb 1, 2022
49085bb
use decorations service instead of decorations render layer
meganrogge Feb 3, 2022
ad59db9
try
meganrogge Feb 3, 2022
912c33c
get it to wrk
meganrogge Feb 3, 2022
b2f541d
clean up
meganrogge Feb 3, 2022
42d4038
use css class for statics
meganrogge Feb 3, 2022
3745496
Merge branch 'xtermjs:master' into master
meganrogge Feb 4, 2022
8f7db4b
fix buffers not clearing
meganrogge Feb 4, 2022
ac6877a
clean up
meganrogge Feb 7, 2022
b226eb2
throw for negative x
meganrogge Feb 7, 2022
8bf3cd1
add to test
meganrogge Feb 7, 2022
8b540c5
set initial top value
meganrogge Feb 7, 2022
cae9f44
remove mention of buffer
meganrogge Feb 8, 2022
19b9d0e
cleanup
meganrogge Feb 8, 2022
10d2e66
Merge branch 'master' into merogge/buffer-marker-decoration
meganrogge Feb 8, 2022
3299315
remove elt from screen on dispose
meganrogge Feb 8, 2022
094763e
Revert changes to webglRenderer
meganrogge Feb 8, 2022
275ae6b
DecorationsService -> DecorationService
meganrogge Feb 8, 2022
01b66b7
revert formatting changes
meganrogge Feb 8, 2022
3e5ecbf
more formatting
meganrogge Feb 8, 2022
723e915
more decorationsService -> singular
meganrogge Feb 8, 2022
571bf9e
fix bouncing on re render
meganrogge Feb 8, 2022
d8234af
move listener into terminal.ts
meganrogge Feb 8, 2022
58c2ba5
remove empty line
meganrogge Feb 8, 2022
a972237
add decoration test to demo
meganrogge Feb 8, 2022
5fd7216
add api tests
meganrogge Feb 8, 2022
88bd33f
bring jumpyness back
meganrogge Feb 8, 2022
ad9818f
Update typings/xterm.d.ts
meganrogge Feb 9, 2022
5360682
Update src/browser/services/DecorationService.ts
meganrogge Feb 9, 2022
f2991a5
cleanup
meganrogge Feb 9, 2022
a9b496f
Update src/browser/services/DecorationService.ts
meganrogge Feb 9, 2022
cfe306f
break things
meganrogge Feb 9, 2022
216849b
Merge branch 'merogge/buffer-marker-decoration' of https://github.com…
meganrogge Feb 9, 2022
ae958c2
fix the problem
meganrogge Feb 9, 2022
fb4b19a
polish
meganrogge Feb 9, 2022
591f32d
Update src/browser/Terminal.ts
meganrogge Feb 9, 2022
db9bdd4
fix some things
meganrogge Feb 9, 2022
a2721d4
Merge branch 'merogge/buffer-marker-decoration' of https://github.com…
meganrogge Feb 9, 2022
8e75cec
change how rendering happens
meganrogge Feb 9, 2022
313cb2f
add a container
meganrogge Feb 9, 2022
95c9fe1
save options in constructor
meganrogge Feb 9, 2022
3e20458
refresh on dimensions changed
meganrogge Feb 9, 2022
e2a402d
add tests
meganrogge Feb 9, 2022
8767564
hide if invalid x
meganrogge Feb 9, 2022
93bfc18
fix test
meganrogge Feb 9, 2022
e5d4e96
fix test
meganrogge Feb 9, 2022
6a79017
on dispose, remove container
meganrogge Feb 9, 2022
c7df6e3
inject bufferService
meganrogge Feb 9, 2022
bf5fed0
fix test
meganrogge Feb 9, 2022
7424649
get term to scroll so it refreshes
meganrogge Feb 9, 2022
b3b9f3c
add poll for
meganrogge Feb 9, 2022
881bc4f
different approach
meganrogge Feb 9, 2022
14b71b6
fix test
meganrogge Feb 10, 2022
9ed9520
try to fix tests
meganrogge Feb 10, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions css/xterm.css
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,8 @@
.xterm-strikethrough {
text-decoration: line-through;
}

.xterm-screen .xterm-decoration-container .xterm-decoration {
z-index: 6;
position: absolute;
}
7 changes: 7 additions & 0 deletions demo/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ if (document.location.pathname === '/test') {
document.getElementById('serialize').addEventListener('click', serializeButtonHandler);
document.getElementById('custom-glyph').addEventListener('click', writeCustomGlyphHandler);
document.getElementById('load-test').addEventListener('click', loadTest);
document.getElementById('add-decoration').addEventListener('click', addDecoration);
}

function createTerminal(): void {
Expand Down Expand Up @@ -525,3 +526,9 @@ function loadTest() {
term._core._onData.fire('\x03');
});
}

function addDecoration() {
const marker = term.addMarker(1);
const decoration = term.registerDecoration({ marker });
decoration.element.style.backgroundColor = 'red';
}
1 change: 1 addition & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ <h3>Test</h3>
<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="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>
Expand Down
12 changes: 10 additions & 2 deletions src/browser/Terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ 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, IDecorationOptions, 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';
import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { ColorManager } from 'browser/ColorManager';
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 { IBuffer } from 'common/buffer/Types';
import { MouseService } from 'browser/services/MouseService';
Expand All @@ -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 { DecorationService } from 'browser/services/DecorationService';

// Let it work inside Node.js for automated testing purposes.
const document: Document = (typeof window !== 'undefined') ? window.document : null as any;
Expand Down Expand Up @@ -108,6 +109,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
public linkifier: ILinkifier;
public linkifier2: ILinkifier2;
public viewport: IViewport | undefined;
public decorationService: IDecorationService;
private _compositionHelper: ICompositionHelper | undefined;
private _mouseZoneManager: IMouseZoneManager | undefined;
private _accessibilityManager: AccessibilityManager | undefined;
Expand Down Expand Up @@ -157,6 +159,7 @@ export class Terminal extends CoreTerminal implements ITerminal {

this.linkifier = this._instantiationService.createInstance(Linkifier);
this.linkifier2 = this.register(this._instantiationService.createInstance(Linkifier2));
this.decorationService = this.register(this._instantiationService.createInstance(DecorationService));

// Setup InputHandler listeners
this.register(this._inputHandler.onRequestBell(() => this.bell()));
Expand Down Expand Up @@ -574,6 +577,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.linkifier.attachToDom(this.element, this._mouseZoneManager);
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.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e)));

Expand Down Expand Up @@ -998,6 +1002,10 @@ export class Terminal extends CoreTerminal implements ITerminal {
return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset);
}

public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {
return this.decorationService!.registerDecoration(decorationOptions);
meganrogge marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Gets whether the terminal has an active selection.
*/
Expand Down
11 changes: 10 additions & 1 deletion src/browser/TestUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* @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 { ICharacterJoinerService, ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types';
Expand Down Expand Up @@ -102,6 +102,9 @@ export class MockTerminal implements ITerminal {
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
throw new Error('Method not implemented.');
}
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {
throw new Error('Method not implemented.');
}
public hasSelection(): boolean {
throw new Error('Method not implemented.');
}
Expand Down Expand Up @@ -283,6 +286,9 @@ export class MockRenderer implements IRenderer {
public setColors(colors: IColorSet): void {
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 onCharSizeChanged(): void { }
public onBlur(): void { }
Expand Down Expand Up @@ -422,6 +428,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 {
Expand Down
5 changes: 3 additions & 2 deletions src/browser/Types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
* @license MIT
*/

import { IDisposable, IMarker, ISelectionPosition } from 'xterm';
import { IDecorationOptions, IDecoration, IDisposable, IMarker, ISelectionPosition } from 'xterm';
import { IEvent } from 'common/EventEmitter';
import { ICoreTerminal, CharData, ITerminalOptions } from 'common/Types';
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';

export interface ITerminal extends IPublicTerminal, ICoreTerminal {
Expand Down Expand Up @@ -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: IDecorationOptions): IDecoration | undefined;
hasSelection(): boolean;
getSelection(): string;
getSelectionPosition(): ISelectionPosition | undefined;
Expand Down
13 changes: 12 additions & 1 deletion src/browser/public/Terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, IDecorationOptions, IDecoration } from 'xterm';
import { ITerminal } from 'browser/Types';
import { Terminal as TerminalCore } from 'browser/Terminal';
import * as Strings from 'browser/LocalizableStrings';
Expand Down Expand Up @@ -171,6 +171,11 @@ export class Terminal implements ITerminalApi {
this._verifyIntegers(cursorYOffset);
return this._core.addMarker(cursorYOffset);
}
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {
this._checkProposedApi();
this._verifyPositiveInteger(decorationOptions.x);
return this._core.registerDecoration(decorationOptions);
meganrogge marked this conversation as resolved.
Show resolved Hide resolved
}
public addMarker(cursorYOffset: number): IMarker | undefined {
return this.registerMarker(cursorYOffset);
}
Expand Down Expand Up @@ -281,4 +286,10 @@ export class Terminal implements ITerminalApi {
}
}
}

private _verifyPositiveInteger(value?: number): void {
if (value && (value === Infinity || isNaN(value) || value % 1 !== 0 || value < 0)) {
throw new Error('This API only accepts positive integers');
}
}
}
5 changes: 3 additions & 2 deletions src/browser/renderer/Renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { IDecorationOptions, IDecoration } from 'xterm';

let nextRendererId = 1;

Expand Down
145 changes: 145 additions & 0 deletions src/browser/services/DecorationService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/**
* 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 } 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 _renderService: IRenderService | undefined;
private _bufferService: IBufferService | undefined;

constructor(
) {
super();
}
meganrogge marked this conversation as resolved.
Show resolved Hide resolved

public attachToDom(screenElement: HTMLElement, renderService: IRenderService, bufferService: IBufferService): void {
this._renderService = renderService;
this._bufferService = bufferService;
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 = new Decoration(decorationOptions, this._container);
this._decorations.push(decoration);
decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1));
return decoration;
meganrogge marked this conversation as resolved.
Show resolved Hide resolved
}

public refresh(recreate?: boolean): void {
if (!this._bufferService || !this._renderService) {
return;
}
for (const decoration of this._decorations) {
decoration.render(this._bufferService, this._renderService, recreate);
}
}

public dispose(): void {
for (const decoration of this._decorations) {
decoration.dispose();
}
}
}

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
) {
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(bufferService: IBufferService, renderService: IRenderService, recreate?: boolean): void {
if (!this._element || recreate) {
this._createElement(bufferService, renderService, recreate);
}
if (this._container && this._element && !this._container.contains(this._element)) {
this._container.append(this._element);
}
this._refreshStyle(bufferService, renderService);
this._onRender.fire(this._element!);
}

private _createElement(bufferService: IBufferService, renderService: IRenderService, recreate?: boolean): void {
if (recreate) {
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 - bufferService.buffers.active.ydisp) * renderService.dimensions.scaledCellHeight}px`;

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(bufferService: IBufferService, renderService: IRenderService): void {
const line = this.marker.line - bufferService.buffers.active.ydisp;
if (line < 0 || line > 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';
}
}
}
10 changes: 10 additions & 0 deletions src/browser/services/Services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ 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 { IDecorationOptions, IDecoration } from 'xterm';
import { IBufferService } from 'common/services/Services';

export const ICharSizeService = createDecorator<ICharSizeService>('CharSizeService');
export interface ICharSizeService {
Expand Down Expand Up @@ -113,3 +115,11 @@ export interface ICharacterJoinerService {
deregister(joinerId: number): boolean;
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;
}
Loading