Skip to content

Expose key helpers on the API for addons #5292

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

Draft
wants to merge 16 commits into
base: master
Choose a base branch
from
6 changes: 3 additions & 3 deletions addons/addon-image/src/ImageAddon.ts
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
* @license MIT
*/

import type { ITerminalAddon, IDisposable } from '@xterm/xterm';
import type { ITerminalAddon, IDisposable, ISharedExports } from '@xterm/xterm';
import type { ImageAddon as IImageApi } from '@xterm/addon-image';
import { IIPHandler } from './IIPHandler';
import { ImageRenderer } from './ImageRenderer';
@@ -57,7 +57,7 @@ export class ImageAddon implements ITerminalAddon , IImageApi {
private _terminal: ITerminalExt | undefined;
private _handlers: Map<String, IResetHandler> = new Map();

constructor(opts?: Partial<IImageAddonOptions>) {
constructor(private _sharedExports: ISharedExports, opts?: Partial<IImageAddonOptions>) {
this._opts = Object.assign({}, DEFAULT_OPTIONS, opts);
this._defaultOpts = Object.assign({}, DEFAULT_OPTIONS, opts);
}
@@ -80,7 +80,7 @@ export class ImageAddon implements ITerminalAddon , IImageApi {
this._terminal = terminal;

// internal data structures
this._renderer = new ImageRenderer(terminal);
this._renderer = new ImageRenderer(this._sharedExports, terminal);
this._storage = new ImageStorage(terminal, this._renderer, this._opts);

// enable size reports
15 changes: 8 additions & 7 deletions addons/addon-image/src/ImageRenderer.ts
Original file line number Diff line number Diff line change
@@ -4,9 +4,9 @@
*/

import { toRGBA8888 } from 'sixel/lib/Colors';
import { IDisposable } from '@xterm/xterm';
import { IDisposable, IMutableDisposable, ISharedExports } from '@xterm/xterm';
import { ICellSize, ITerminalExt, IImageSpec, IRenderDimensions, IRenderService } from './Types';
import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { AddonDisposable } from 'common/shared/AddonDisposable';

const PLACEHOLDER_LENGTH = 4096;
const PLACEHOLDER_HEIGHT = 24;
@@ -17,12 +17,12 @@ const PLACEHOLDER_HEIGHT = 24;
* - add canvas layer to DOM (browser only for now)
* - draw image tiles onRender
*/
export class ImageRenderer extends Disposable implements IDisposable {
export class ImageRenderer extends AddonDisposable implements IDisposable {
public canvas: HTMLCanvasElement | undefined;
private _ctx: CanvasRenderingContext2D | null | undefined;
private _placeholder: HTMLCanvasElement | undefined;
private _placeholderBitmap: ImageBitmap | undefined;
private _optionsRefresh = this._register(new MutableDisposable());
private _optionsRefresh: IMutableDisposable<IDisposable>;
private _oldOpen: ((parent: HTMLElement) => void) | undefined;
private _renderService: IRenderService | undefined;
private _oldSetRenderer: ((renderer: any) => void) | undefined;
@@ -67,8 +67,8 @@ export class ImageRenderer extends Disposable implements IDisposable {
}


constructor(private _terminal: ITerminalExt) {
super();
constructor(sharedExports: ISharedExports, private _terminal: ITerminalExt) {
super(sharedExports);
this._oldOpen = this._terminal._core.open;
this._terminal._core.open = (parent: HTMLElement): void => {
this._oldOpen?.call(this._terminal._core, parent);
@@ -78,13 +78,14 @@ export class ImageRenderer extends Disposable implements IDisposable {
this._open();
}
// hack to spot fontSize changes
this._optionsRefresh = new sharedExports.MutableDisposable();
this._optionsRefresh.value = this._terminal._core.optionsService.onOptionChange(option => {
if (option === 'fontSize') {
this.rescaleCanvas();
this._renderService?.refreshRows(0, this._terminal.rows);
}
});
this._register(toDisposable(() => {
this._register(sharedExports.toDisposable(() => {
this.removeLayerFromDom();
if (this._terminal._core && this._oldOpen) {
this._terminal._core.open = this._oldOpen;
7 changes: 4 additions & 3 deletions addons/addon-image/test/ImageAddon.test.ts
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ import { readFileSync } from 'fs';
import { FINALIZER, introducer, sixelEncode } from 'sixel';
import { ITestContext, createTestContext, openTerminal, pollFor, timeout } from '../../../test/playwright/TestUtils';
import { deepStrictEqual, ok, strictEqual } from 'assert';
import { ISharedExports } from '@xterm/xterm';

/**
* Plugin ctor options.
@@ -27,7 +28,7 @@ export interface IImageAddonOptions {

// eslint-disable-next-line
declare const ImageAddon: {
new(options?: Partial<IImageAddonOptions>): any;
new(sharedExports: ISharedExports, options?: Partial<IImageAddonOptions>): any;
};

interface ITestData {
@@ -91,7 +92,7 @@ test.describe('ImageAddon', () => {
await ctx.page.evaluate(`
window.term.reset()
window.imageAddon?.dispose();
window.imageAddon = new ImageAddon({ sixelPaletteLimit: 512 });
window.imageAddon = new ImageAddon(sharedExports, { sixelPaletteLimit: 512 });
window.term.loadAddon(window.imageAddon);
`);
});
@@ -152,7 +153,7 @@ test.describe('ImageAddon', () => {
iipSizeLimit: 1000
};
await ctx.page.evaluate(opts => {
(window as any).imageAddonCustom = new ImageAddon(opts.opts);
(window as any).imageAddonCustom = new ImageAddon((window as any).sharedExports, opts.opts);
(window as any).term.loadAddon((window as any).imageAddonCustom);
}, { opts: customSettings });
deepStrictEqual(await ctx.page.evaluate(`window.imageAddonCustom._opts`), customSettings);
4 changes: 2 additions & 2 deletions addons/addon-image/typings/addon-image.d.ts
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
* @license MIT
*/

import { Terminal, ITerminalAddon } from '@xterm/xterm';
import { Terminal, ITerminalAddon, ISharedExports } from '@xterm/xterm';

declare module '@xterm/addon-image' {
export interface IImageAddonOptions {
@@ -79,7 +79,7 @@ declare module '@xterm/addon-image' {
}

export class ImageAddon implements ITerminalAddon {
constructor(options?: IImageAddonOptions);
constructor(sharedExports: ISharedExports, options?: IImageAddonOptions);
public activate(terminal: Terminal): void;
public dispose(): void;

16 changes: 8 additions & 8 deletions addons/addon-progress/src/ProgressAddon.ts
Original file line number Diff line number Diff line change
@@ -3,9 +3,8 @@
* @license MIT
*/

import type { Terminal, ITerminalAddon, IDisposable } from '@xterm/xterm';
import { Terminal, ITerminalAddon, IDisposable, IEmitter, IEvent, ISharedExports } from '@xterm/xterm';
import type { ProgressAddon as IProgressApi, IProgressState } from '@xterm/addon-progress';
import type { Emitter, Event } from 'vs/base/common/event';


const enum ProgressType {
@@ -37,9 +36,13 @@ export class ProgressAddon implements ITerminalAddon, IProgressApi {
private _seqHandler: IDisposable | undefined;
private _st: ProgressType = ProgressType.REMOVE;
private _pr = 0;
// HACK: This uses ! to align with the API, this should be fixed when 5283 is resolved
private _onChange!: Emitter<IProgressState>;
public onChange!: Event<IProgressState>;
private _onChange: IEmitter<IProgressState>;
public onChange: IEvent<IProgressState>;

constructor(sharedExports: ISharedExports) {
this._onChange = new sharedExports.Emitter<IProgressState>();
this.onChange = this._onChange.event;
}

public dispose(): void {
this._seqHandler?.dispose();
@@ -81,9 +84,6 @@ export class ProgressAddon implements ITerminalAddon, IProgressApi {
}
return true;
});
// FIXME: borrow emitter ctor from xterm, to be changed once #5283 is resolved
this._onChange = new (terminal as any)._core._onData.constructor();
this.onChange = this._onChange!.event;
}

public get progress(): IProgressState {
2 changes: 1 addition & 1 deletion addons/addon-progress/test/ProgressAddon.test.ts
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ test.describe('ProgressAddon', () => {
window.progressStack = [];
window.term.reset();
window.progressAddon?.dispose();
window.progressAddon = new ProgressAddon();
window.progressAddon = new ProgressAddon(sharedExports);
window.term.loadAddon(window.progressAddon);
window.progressAddon.onChange(progress => window.progressStack.push(progress));
`);
5 changes: 3 additions & 2 deletions addons/addon-progress/typings/addon-progress.d.ts
Original file line number Diff line number Diff line change
@@ -3,7 +3,8 @@
* @license MIT
*/

import { Terminal, ITerminalAddon, IDisposable, IEvent } from '@xterm/xterm';
import { Terminal, ITerminalAddon, IDisposable, IEvent, ISharedExports } from '@xterm/xterm';


declare module '@xterm/addon-progress' {
/**
@@ -15,7 +16,7 @@ declare module '@xterm/addon-progress' {
/**
* Creates a new progress addon
*/
constructor();
constructor(sharedExports: ISharedExports);

/**
* Activates the addon
49 changes: 33 additions & 16 deletions addons/addon-search/src/SearchAddon.ts
Original file line number Diff line number Diff line change
@@ -3,10 +3,10 @@
* @license MIT
*/

import type { Terminal, IDisposable, ITerminalAddon, IDecoration } from '@xterm/xterm';
import type { Terminal, IDisposable, ITerminalAddon, IDecoration, ISharedExports, IEmitter, IEvent, IMutableDisposable } from '@xterm/xterm';
import type { SearchAddon as ISearchApi } from '@xterm/addon-search';
import { Emitter } from 'vs/base/common/event';
import { combinedDisposable, Disposable, dispose, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { AddonDisposable } from 'common/shared/AddonDisposable';


export interface ISearchOptions {
regex?: boolean;
@@ -62,12 +62,12 @@ const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\\;:"\',./<>?';
const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs
const DEFAULT_HIGHLIGHT_LIMIT = 1000;

export class SearchAddon extends Disposable implements ITerminalAddon , ISearchApi {
export class SearchAddon extends AddonDisposable implements ITerminalAddon , ISearchApi {
private _terminal: Terminal | undefined;
private _cachedSearchTerm: string | undefined;
private _highlightedLines: Set<number> = new Set();
private _highlightDecorations: IHighlight[] = [];
private _selectedDecoration: MutableDisposable<IHighlight> = this._register(new MutableDisposable());
private _selectedDecoration: IMutableDisposable<IHighlight>;
private _highlightLimit: number;
private _lastSearchOptions: ISearchOptions | undefined;
private _highlightTimeout: number | undefined;
@@ -78,13 +78,18 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
*/
private _linesCache: LineCacheEntry[] | undefined;
private _linesCacheTimeoutId = 0;
private _linesCacheDisposables = new MutableDisposable();
private _linesCacheDisposables: IMutableDisposable<IDisposable>;

private readonly _onDidChangeResults: IEmitter<{ resultIndex: number, resultCount: number }>;
public readonly onDidChangeResults: IEvent<{ resultIndex: number, resultCount: number }>;

private readonly _onDidChangeResults = this._register(new Emitter<{ resultIndex: number, resultCount: number }>());
public readonly onDidChangeResults = this._onDidChangeResults.event;
constructor(private _sharedExports: ISharedExports, options?: Partial<ISearchAddonOptions>) {
super(_sharedExports);
this._onDidChangeResults = this._register(new _sharedExports.Emitter<{ resultIndex: number, resultCount: number }>());
this.onDidChangeResults = this._onDidChangeResults.event;

constructor(options?: Partial<ISearchAddonOptions>) {
super();
this._selectedDecoration = this._register(new _sharedExports.MutableDisposable());
this._linesCacheDisposables = new _sharedExports.MutableDisposable();

this._highlightLimit = options?.highlightLimit ?? DEFAULT_HIGHLIGHT_LIMIT;
}
@@ -93,7 +98,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
this._terminal = terminal;
this._register(this._terminal.onWriteParsed(() => this._updateMatches()));
this._register(this._terminal.onResize(() => this._updateMatches()));
this._register(toDisposable(() => this.clearDecorations()));
this._register(this._sharedExports.toDisposable(() => this.clearDecorations()));
}

private _updateMatches(): void {
@@ -111,7 +116,9 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA

public clearDecorations(retainCachedSearchTerm?: boolean): void {
this._selectedDecoration.clear();
dispose(this._highlightDecorations);
for (let i = 0; i < this._highlightDecorations.length; ++i) {
this._highlightDecorations[i].dispose();
}
this._highlightDecorations = [];
this._highlightedLines.clear();
if (!retainCachedSearchTerm) {
@@ -426,11 +433,15 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
const terminal = this._terminal!;
if (!this._linesCache) {
this._linesCache = new Array(terminal.buffer.active.length);
this._linesCacheDisposables.value = combinedDisposable(
const disposables = [
terminal.onLineFeed(() => this._destroyLinesCache()),
terminal.onCursorMove(() => this._destroyLinesCache()),
terminal.onResize(() => this._destroyLinesCache())
);
];
this._linesCacheDisposables.value = this._sharedExports.toDisposable(() => {
for (let i = 0; i < disposables.length; ++i) disposables[i].dispose();
disposables.length = 0;
});
}

window.clearTimeout(this._linesCacheTimeoutId);
@@ -682,7 +693,10 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
const disposables: IDisposable[] = [];
disposables.push(marker);
disposables.push(decoration.onRender((e) => this._applyStyles(e, options.activeMatchBorder, true)));
disposables.push(decoration.onDispose(() => dispose(disposables)));
disposables.push(decoration.onDispose(() => {
for (let i = 0; i < disposables.length; ++i) disposables[i].dispose();
disposables.length = 0;
}));
this._selectedDecoration.value = { decoration, match: result, dispose() { decoration.dispose(); } };
}
}
@@ -744,7 +758,10 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
const disposables: IDisposable[] = [];
disposables.push(marker);
disposables.push(findResultDecoration.onRender((e) => this._applyStyles(e, options.matchBorder, false)));
disposables.push(findResultDecoration.onDispose(() => dispose(disposables)));
disposables.push(findResultDecoration.onDispose(() => {
for (let i = 0; i < disposables.length; ++i) disposables[i].dispose();
disposables.length = 0;
}));
}
return findResultDecoration;
}
2 changes: 1 addition & 1 deletion addons/addon-search/test/SearchAddon.test.ts
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ test.describe('Search Tests', () => {
await ctx.page.evaluate(`
window.term.reset()
window.search?.dispose();
window.search = new SearchAddon();
window.search = new SearchAddon(sharedExports);
window.term.loadAddon(window.search);
`);
});
4 changes: 2 additions & 2 deletions addons/addon-search/typings/addon-search.d.ts
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
* @license MIT
*/

import { Terminal, ITerminalAddon, IEvent } from '@xterm/xterm';
import { Terminal, ITerminalAddon, IEvent, ISharedExports } from '@xterm/xterm';

declare module '@xterm/addon-search' {
/**
@@ -95,7 +95,7 @@ declare module '@xterm/addon-search' {
* Creates a new search addon.
* @param options Options for the search addon.
*/
constructor(options?: Partial<ISearchAddonOptions>);
constructor(sharedExports: ISharedExports, options?: Partial<ISearchAddonOptions>);

/**
* Activates the addon
5 changes: 3 additions & 2 deletions addons/addon-webgl/src/CharAtlasCache.ts
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
*/

import { TextureAtlas } from './TextureAtlas';
import { ITerminalOptions, Terminal } from '@xterm/xterm';
import { ISharedExports, ITerminalOptions, Terminal } from '@xterm/xterm';
import { ITerminal, ReadonlyColorSet } from 'browser/Types';
import { ICharAtlasConfig, ITextureAtlas } from './Types';
import { generateConfig, configEquals } from './CharAtlasUtils';
@@ -24,6 +24,7 @@ const charAtlasCache: ITextureAtlasCacheEntry[] = [];
* one that is in use by another terminal.
*/
export function acquireTextureAtlas(
sharedExports: ISharedExports,
terminal: Terminal,
options: Required<ITerminalOptions>,
colors: ReadonlyColorSet,
@@ -67,7 +68,7 @@ export function acquireTextureAtlas(

const core: ITerminal = (terminal as any)._core;
const newEntry: ITextureAtlasCacheEntry = {
atlas: new TextureAtlas(document, newConfig, core.unicodeService),
atlas: new TextureAtlas(sharedExports, document, newConfig, core.unicodeService),
config: newConfig,
ownedBy: [terminal]
};
6 changes: 3 additions & 3 deletions addons/addon-webgl/src/DevicePixelObserver.ts
Original file line number Diff line number Diff line change
@@ -3,9 +3,9 @@
* @license MIT
*/

import { toDisposable, IDisposable } from 'vs/base/common/lifecycle';
import { ISharedExports, IDisposable } from '@xterm/xterm';

export function observeDevicePixelDimensions(element: HTMLElement, parentWindow: Window & typeof globalThis, callback: (deviceWidth: number, deviceHeight: number) => void): IDisposable {
export function observeDevicePixelDimensions(sharedExports: ISharedExports, element: HTMLElement, parentWindow: Window & typeof globalThis, callback: (deviceWidth: number, deviceHeight: number) => void): IDisposable {
// Observe any resizes to the element and extract the actual pixel size of the element if the
// devicePixelContentBoxSize API is supported. This allows correcting rounding errors when
// converting between CSS pixels and device pixels which causes blurry rendering when device
@@ -36,5 +36,5 @@ export function observeDevicePixelDimensions(element: HTMLElement, parentWindow:
observer.disconnect();
observer = undefined;
}
return toDisposable(() => observer?.disconnect());
return sharedExports.toDisposable(() => observer?.disconnect());
}
Loading
Oops, something went wrong.
Loading
Oops, something went wrong.