From cca31f34043d41280164c52ac286b20f03d1600d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 10 Jan 2025 03:21:36 +0100 Subject: [PATCH 01/16] initial attempt --- addons/addon-progress/src/ProgressAddon.ts | 25 ++++--- addons/addon-progress/src/tsconfig.json | 6 ++ .../typings/addon-progress.d.ts | 7 +- addons/addon-progress/webpack.config.js | 3 +- demo/client.ts | 6 +- src/browser/public/Terminal.ts | 19 +++++- src/browser/tsconfig.json | 6 +- src/shared/shared.ts | 44 ++++++++++++ src/shared/tsconfig.json | 24 +++++++ typings/xterm.d.ts | 67 +++++++++++++++++++ webpack.config.js | 1 + 11 files changed, 188 insertions(+), 20 deletions(-) create mode 100644 src/shared/shared.ts create mode 100644 src/shared/tsconfig.json diff --git a/addons/addon-progress/src/ProgressAddon.ts b/addons/addon-progress/src/ProgressAddon.ts index 175d7af0ab..e48dcb050f 100644 --- a/addons/addon-progress/src/ProgressAddon.ts +++ b/addons/addon-progress/src/ProgressAddon.ts @@ -3,9 +3,14 @@ * @license MIT */ -import type { Terminal, ITerminalAddon, IDisposable } from '@xterm/xterm'; +import { Terminal, ITerminalAddon, IDisposable, EmitterCtorType, IEmitter, IEvent } from '@xterm/xterm'; import type { ProgressAddon as IProgressApi, IProgressState } from '@xterm/addon-progress'; -import type { Emitter, Event } from 'vs/base/common/event'; + +// to use impl parts: +// in 3rd party addons +//import { EmitterAddon } from '@xterm/xterm'; +// in xtermjs repo addons +import { EmitterAddon } from 'shared/shared'; const enum ProgressType { @@ -33,13 +38,18 @@ function toInt(s: string): number { } -export class ProgressAddon implements ITerminalAddon, IProgressApi { +export class ProgressAddon extends EmitterAddon 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; - public onChange!: Event; + private _onChange: IEmitter; + public onChange: IEvent; + + constructor(protected readonly emitterCtor: EmitterCtorType) { + super(emitterCtor); + this._onChange = new this.emitterCtor(); + this.onChange = this._onChange.event; + } public dispose(): void { this._seqHandler?.dispose(); @@ -81,9 +91,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 { diff --git a/addons/addon-progress/src/tsconfig.json b/addons/addon-progress/src/tsconfig.json index ffc7b01a58..cb24712e6a 100644 --- a/addons/addon-progress/src/tsconfig.json +++ b/addons/addon-progress/src/tsconfig.json @@ -22,6 +22,9 @@ "vs/*": [ "../../../src/vs/*" ], + "shared/*": [ + "../../../src/shared/*" + ], "@xterm/addon-progress": [ "../typings/addon-progress.d.ts" ] @@ -37,6 +40,9 @@ }, { "path": "../../../src/vs" + }, + { + "path": "../../../src/shared" } ] } diff --git a/addons/addon-progress/typings/addon-progress.d.ts b/addons/addon-progress/typings/addon-progress.d.ts index c9431dad22..fe64dc458b 100644 --- a/addons/addon-progress/typings/addon-progress.d.ts +++ b/addons/addon-progress/typings/addon-progress.d.ts @@ -3,19 +3,20 @@ * @license MIT */ -import { Terminal, ITerminalAddon, IDisposable, IEvent } from '@xterm/xterm'; +import { Terminal, ITerminalAddon, IDisposable, IEvent, EmitterCtorType } from '@xterm/xterm'; +import { EmitterAddon } from 'shared/shared'; declare module '@xterm/addon-progress' { /** * An xterm.js addon that provides an interface for ConEmu's progress * sequence. */ - export class ProgressAddon implements ITerminalAddon, IDisposable { + export class ProgressAddon extends EmitterAddon implements ITerminalAddon, IDisposable { /** * Creates a new progress addon */ - constructor(); + constructor(emitterCtor: EmitterCtorType); /** * Activates the addon diff --git a/addons/addon-progress/webpack.config.js b/addons/addon-progress/webpack.config.js index 1bf29bfd2c..f2e96cfd25 100644 --- a/addons/addon-progress/webpack.config.js +++ b/addons/addon-progress/webpack.config.js @@ -27,7 +27,8 @@ module.exports = { alias: { common: path.resolve('../../out/common'), browser: path.resolve('../../out/browser'), - vs: path.resolve('../../out/vs') + vs: path.resolve('../../out/vs'), + shared: path.resolve('../../out/shared') } }, output: { diff --git a/demo/client.ts b/demo/client.ts index f5903aa4d7..64e40e59b9 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -16,7 +16,7 @@ if ('WebAssembly' in window) { ImageAddon = imageAddon.ImageAddon; } -import { Terminal, ITerminalOptions, type IDisposable, type ITheme } from '@xterm/xterm'; +import { Terminal, ITerminalOptions, type IDisposable, type ITheme, emitterCtor } from '@xterm/xterm'; import { AttachAddon } from '@xterm/addon-attach'; import { ClipboardAddon } from '@xterm/addon-clipboard'; import { FitAddon } from '@xterm/addon-fit'; @@ -279,7 +279,7 @@ function createTerminal(): void { addons.serialize.instance = new SerializeAddon(); addons.fit.instance = new FitAddon(); addons.image.instance = new ImageAddon(); - addons.progress.instance = new ProgressAddon(); + addons.progress.instance = new ProgressAddon(emitterCtor); addons.unicodeGraphemes.instance = new UnicodeGraphemesAddon(); addons.clipboard.instance = new ClipboardAddon(); try { // try to start with webgl renderer (might throw on older safari/webkit) @@ -660,7 +660,7 @@ function initAddons(term: Terminal): void { } if (checkbox.checked) { // HACK: Manually remove addons that cannot be changes - addon.instance = new (addon as IDemoAddon>).ctor(); + //addon.instance = new (addon as IDemoAddon>).ctor(); try { term.loadAddon(addon.instance); if (name === 'webgl') { diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 3b4309437f..f00ca9249b 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -6,14 +6,14 @@ import * as Strings from 'browser/LocalizableStrings'; import { CoreBrowserTerminal as TerminalCore } from 'browser/CoreBrowserTerminal'; import { IBufferRange, ITerminal } from 'browser/Types'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, toDisposable as toDisposableOrig } from 'vs/base/common/lifecycle'; import { ITerminalOptions } from 'common/Types'; import { AddonManager } from 'common/public/AddonManager'; import { BufferNamespaceApi } from 'common/public/BufferNamespaceApi'; import { ParserApi } from 'common/public/ParserApi'; import { UnicodeApi } from 'common/public/UnicodeApi'; import { IBufferNamespace as IBufferNamespaceApi, IDecoration, IDecorationOptions, IDisposable, ILinkProvider, ILocalizableStrings, IMarker, IModes, IParser, ITerminalAddon, Terminal as ITerminalApi, ITerminalInitOnlyOptions, IUnicodeHandling } from '@xterm/xterm'; -import type { Event } from 'vs/base/common/event'; +import { Emitter, type Event } from 'vs/base/common/event'; /** * The set of options that only have an effect when set in the Terminal constructor. @@ -272,3 +272,18 @@ export class Terminal extends Disposable implements ITerminalApi { } } } + + +/** + * Expose often needed vs/* parts in addons. + * Exposed statically on the xterm package, + * so they can be used on addon ctors already. + */ +export { + DisposableStore as disposableStoreCtor, + toDisposable, + Emitter as emitterCtor, + DisposableAddon, + EmitterAddon, + DisposableEmitterAddon +} from 'shared/shared'; diff --git a/src/browser/tsconfig.json b/src/browser/tsconfig.json index 38854e260f..e1b73cf642 100644 --- a/src/browser/tsconfig.json +++ b/src/browser/tsconfig.json @@ -13,7 +13,8 @@ "baseUrl": "..", "paths": { "common/*": [ "./common/*" ], - "vs/*": [ "./vs/*" ] + "vs/*": [ "./vs/*" ], + "shared/*": [ "./shared/*" ], } }, "include": [ @@ -22,6 +23,7 @@ ], "references": [ { "path": "../common" }, - { "path": "../vs" } + { "path": "../vs" }, + { "path": "../shared" }, ] } diff --git a/src/shared/shared.ts b/src/shared/shared.ts new file mode 100644 index 0000000000..ea80b32611 --- /dev/null +++ b/src/shared/shared.ts @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2024 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IDisposable, IDisposableStore, DisposableStoreCtorType, EmitterCtorType } from '@xterm/xterm'; + +export { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +export { Emitter } from 'vs/base/common/event'; + +export class DisposableAddon implements IDisposable { + protected readonly _store: IDisposableStore; + + constructor(storeCtor: DisposableStoreCtorType) { + this._store = new storeCtor(); + } + + dispose(): void { + this._store.dispose(); + } +} + + +export class EmitterAddon { + constructor( + protected readonly emitterCtor: EmitterCtorType + ) {} +} + + +export class DisposableEmitterAddon implements IDisposable { + protected readonly _store: IDisposableStore; + + constructor( + readonly storeCtor: DisposableStoreCtorType, + protected readonly emitterCtor: EmitterCtorType + ) { + this._store = new storeCtor(); + } + + dispose(): void { + this._store.dispose(); + } +} diff --git a/src/shared/tsconfig.json b/src/shared/tsconfig.json new file mode 100644 index 0000000000..e9b3673ab2 --- /dev/null +++ b/src/shared/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../tsconfig-library-base", + "compilerOptions": { + "lib": [ + "es2015", + "es2016.Array.Include" + ], + "outDir": "../../out", + "types": [ + "../../node_modules/@types/mocha" + ], + "baseUrl": "..", + "paths": { + "vs/*": [ "./vs/*" ] + } + }, + "include": [ + "./**/*", + "../../typings/xterm.d.ts" + ], + "references": [ + { "path": "../vs" } + ] +} diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 15a0332789..40cb008af7 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -1956,4 +1956,71 @@ declare module '@xterm/xterm' { */ readonly wraparoundMode: boolean; } + + + /** + * Get Emitter constructor. + */ + export const emitterCtor: EmitterCtorType; + + /** + * Get DisposableStore contructor. + */ + export const disposableStoreCtor: DisposableStoreCtorType; + + /** + * Turn a function into a Disposable. + */ + export const toDisposable: (fn: () => void) => IDisposable; + + + export interface IEmitter { + dispose(): void; + event: IEvent; + fire(event: T): void; + hasListeners(): boolean; + } + + interface IDisposableStore extends IDisposable { + /** + * `true` if this object has been disposed of. + */ + isDisposed: boolean; + /** + * Dispose of all registered disposables but do not mark this object as disposed. + */ + clear(): void; + /** + * Add a new {@link IDisposable disposable} to the collection. + */ + add(o: T): T; + /** + * Deletes a disposable from store and disposes of it. This will not throw or warn and proceed to dispose the + * disposable even when the disposable is not part in the store. + */ + delete(o: T): void; + /** + * Deletes the value from the store, but does not dispose it. + */ + deleteAndLeak(o: T): void; + } + + export type EmitterCtorType = { new(): IEmitter }; + export type DisposableStoreCtorType = { new(): IDisposableStore; } + + export class DisposableAddon implements IDisposable { + protected readonly _store: IDisposableStore; + constructor(storeCtor: DisposableStoreCtorType); + dispose(): void; + } + export class EmitterAddon { + protected readonly emitterCtor: EmitterCtorType; + constructor(emitterCtor: EmitterCtorType); + } + export class DisposableEmitterAddon implements IDisposable { + protected readonly _store: IDisposableStore; + protected readonly emitterCtor: EmitterCtorType; + constructor(storeCtor: DisposableStoreCtorType, emitterCtor: EmitterCtorType); + dispose(): void; + } } diff --git a/webpack.config.js b/webpack.config.js index 123e31dfbb..37b07d1bbe 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -35,6 +35,7 @@ const config = { common: path.resolve('./out/common'), browser: path.resolve('./out/browser'), vs: path.resolve('./out/vs'), + shared: path.resolve('./out/shared') } }, output: { From fa565a180eb961d13592bc6140385d81a9db8c27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 10 Jan 2025 03:51:29 +0100 Subject: [PATCH 02/16] make linter happy --- .eslintrc.json | 1 + addons/addon-progress/src/ProgressAddon.ts | 10 ++++++---- addons/addon-progress/typings/addon-progress.d.ts | 2 +- src/browser/public/Terminal.ts | 4 ++-- src/shared/shared.ts | 8 ++++---- typings/xterm.d.ts | 12 ++++++------ 6 files changed, 20 insertions(+), 17 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index acd85af60a..399ef0bc77 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -11,6 +11,7 @@ "src/browser/tsconfig.json", "src/common/tsconfig.json", "src/headless/tsconfig.json", + "src/shared/tsconfig.json", "src/vs/tsconfig.json", "test/benchmark/tsconfig.json", "test/playwright/tsconfig.json", diff --git a/addons/addon-progress/src/ProgressAddon.ts b/addons/addon-progress/src/ProgressAddon.ts index e48dcb050f..5eec7380b5 100644 --- a/addons/addon-progress/src/ProgressAddon.ts +++ b/addons/addon-progress/src/ProgressAddon.ts @@ -7,8 +7,10 @@ import { Terminal, ITerminalAddon, IDisposable, EmitterCtorType, IEmitter, IEven import type { ProgressAddon as IProgressApi, IProgressState } from '@xterm/addon-progress'; // to use impl parts: + // in 3rd party addons -//import { EmitterAddon } from '@xterm/xterm'; +// import { EmitterAddon } from '@xterm/xterm'; + // in xtermjs repo addons import { EmitterAddon } from 'shared/shared'; @@ -45,9 +47,9 @@ export class ProgressAddon extends EmitterAddon implements ITerminalAddon, IProg private _onChange: IEmitter; public onChange: IEvent; - constructor(protected readonly emitterCtor: EmitterCtorType) { - super(emitterCtor); - this._onChange = new this.emitterCtor(); + constructor(protected readonly _emitterCtor: EmitterCtorType) { + super(_emitterCtor); + this._onChange = new this._emitterCtor(); this.onChange = this._onChange.event; } diff --git a/addons/addon-progress/typings/addon-progress.d.ts b/addons/addon-progress/typings/addon-progress.d.ts index fe64dc458b..ea89b9a795 100644 --- a/addons/addon-progress/typings/addon-progress.d.ts +++ b/addons/addon-progress/typings/addon-progress.d.ts @@ -16,7 +16,7 @@ declare module '@xterm/addon-progress' { /** * Creates a new progress addon */ - constructor(emitterCtor: EmitterCtorType); + constructor(_emitterCtor: EmitterCtorType); /** * Activates the addon diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index f00ca9249b..c57bf68556 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -6,14 +6,14 @@ import * as Strings from 'browser/LocalizableStrings'; import { CoreBrowserTerminal as TerminalCore } from 'browser/CoreBrowserTerminal'; import { IBufferRange, ITerminal } from 'browser/Types'; -import { Disposable, DisposableStore, toDisposable as toDisposableOrig } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { ITerminalOptions } from 'common/Types'; import { AddonManager } from 'common/public/AddonManager'; import { BufferNamespaceApi } from 'common/public/BufferNamespaceApi'; import { ParserApi } from 'common/public/ParserApi'; import { UnicodeApi } from 'common/public/UnicodeApi'; import { IBufferNamespace as IBufferNamespaceApi, IDecoration, IDecorationOptions, IDisposable, ILinkProvider, ILocalizableStrings, IMarker, IModes, IParser, ITerminalAddon, Terminal as ITerminalApi, ITerminalInitOnlyOptions, IUnicodeHandling } from '@xterm/xterm'; -import { Emitter, type Event } from 'vs/base/common/event'; +import { type Event } from 'vs/base/common/event'; /** * The set of options that only have an effect when set in the Terminal constructor. diff --git a/src/shared/shared.ts b/src/shared/shared.ts index ea80b32611..f6ee95d2e7 100644 --- a/src/shared/shared.ts +++ b/src/shared/shared.ts @@ -15,7 +15,7 @@ export class DisposableAddon implements IDisposable { this._store = new storeCtor(); } - dispose(): void { + public dispose(): void { this._store.dispose(); } } @@ -23,7 +23,7 @@ export class DisposableAddon implements IDisposable { export class EmitterAddon { constructor( - protected readonly emitterCtor: EmitterCtorType + protected readonly _emitterCtor: EmitterCtorType ) {} } @@ -33,12 +33,12 @@ export class DisposableEmitterAddon implements IDisposable { constructor( readonly storeCtor: DisposableStoreCtorType, - protected readonly emitterCtor: EmitterCtorType + protected readonly _emitterCtor: EmitterCtorType ) { this._store = new storeCtor(); } - dispose(): void { + public dispose(): void { this._store.dispose(); } } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 40cb008af7..dd6e009f73 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -2011,16 +2011,16 @@ declare module '@xterm/xterm' { export class DisposableAddon implements IDisposable { protected readonly _store: IDisposableStore; constructor(storeCtor: DisposableStoreCtorType); - dispose(): void; + public dispose(): void; } export class EmitterAddon { - protected readonly emitterCtor: EmitterCtorType; - constructor(emitterCtor: EmitterCtorType); + protected readonly _emitterCtor: EmitterCtorType; + constructor(_emitterCtor: EmitterCtorType); } export class DisposableEmitterAddon implements IDisposable { protected readonly _store: IDisposableStore; - protected readonly emitterCtor: EmitterCtorType; - constructor(storeCtor: DisposableStoreCtorType, emitterCtor: EmitterCtorType); - dispose(): void; + protected readonly _emitterCtor: EmitterCtorType; + constructor(storeCtor: DisposableStoreCtorType, _emitterCtor: EmitterCtorType); + public dispose(): void; } } From 0eaf97a1be9d7bfd6b2e76bfb837a034cae65786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 10 Jan 2025 03:56:04 +0100 Subject: [PATCH 03/16] make linter happy --- src/shared/shared.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/shared.ts b/src/shared/shared.ts index f6ee95d2e7..71e9937344 100644 --- a/src/shared/shared.ts +++ b/src/shared/shared.ts @@ -32,7 +32,7 @@ export class DisposableEmitterAddon implements IDisposable { protected readonly _store: IDisposableStore; constructor( - readonly storeCtor: DisposableStoreCtorType, + protected readonly storeCtor: DisposableStoreCtorType, protected readonly _emitterCtor: EmitterCtorType ) { this._store = new storeCtor(); From 5bf815311ffebb4271824c9a53803501164116ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 10 Jan 2025 03:58:41 +0100 Subject: [PATCH 04/16] make linter happy --- src/shared/shared.ts | 8 ++++---- typings/xterm.d.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/shared/shared.ts b/src/shared/shared.ts index 71e9937344..6a317f7941 100644 --- a/src/shared/shared.ts +++ b/src/shared/shared.ts @@ -11,8 +11,8 @@ export { Emitter } from 'vs/base/common/event'; export class DisposableAddon implements IDisposable { protected readonly _store: IDisposableStore; - constructor(storeCtor: DisposableStoreCtorType) { - this._store = new storeCtor(); + constructor(protected readonly _storeCtor: DisposableStoreCtorType) { + this._store = new _storeCtor(); } public dispose(): void { @@ -32,10 +32,10 @@ export class DisposableEmitterAddon implements IDisposable { protected readonly _store: IDisposableStore; constructor( - protected readonly storeCtor: DisposableStoreCtorType, + protected readonly _storeCtor: DisposableStoreCtorType, protected readonly _emitterCtor: EmitterCtorType ) { - this._store = new storeCtor(); + this._store = new _storeCtor(); } public dispose(): void { diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index dd6e009f73..68ae85b464 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -2010,7 +2010,7 @@ declare module '@xterm/xterm' { export class DisposableAddon implements IDisposable { protected readonly _store: IDisposableStore; - constructor(storeCtor: DisposableStoreCtorType); + constructor(_storeCtor: DisposableStoreCtorType); public dispose(): void; } export class EmitterAddon { @@ -2020,7 +2020,7 @@ declare module '@xterm/xterm' { export class DisposableEmitterAddon implements IDisposable { protected readonly _store: IDisposableStore; protected readonly _emitterCtor: EmitterCtorType; - constructor(storeCtor: DisposableStoreCtorType, _emitterCtor: EmitterCtorType); + constructor(_storeCtor: DisposableStoreCtorType, _emitterCtor: EmitterCtorType); public dispose(): void; } } From da44482f89f423b9966d28032442850397feab2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 10 Jan 2025 04:01:46 +0100 Subject: [PATCH 05/16] make linter happy --- typings/xterm.d.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 68ae85b464..9473afd172 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -1987,7 +1987,8 @@ declare module '@xterm/xterm' { */ isDisposed: boolean; /** - * Dispose of all registered disposables but do not mark this object as disposed. + * Dispose of all registered disposables but do not mark this object + * as disposed. */ clear(): void; /** @@ -1995,7 +1996,8 @@ declare module '@xterm/xterm' { */ add(o: T): T; /** - * Deletes a disposable from store and disposes of it. This will not throw or warn and proceed to dispose the + * Deletes a disposable from store and disposes of it. + * This will not throw or warn and proceed to dispose the * disposable even when the disposable is not part in the store. */ delete(o: T): void; @@ -2005,7 +2007,7 @@ declare module '@xterm/xterm' { deleteAndLeak(o: T): void; } - export type EmitterCtorType = { new(): IEmitter }; + export type EmitterCtorType = { new(): IEmitter; } export type DisposableStoreCtorType = { new(): IDisposableStore; } export class DisposableAddon implements IDisposable { From 0c2323274e5ae50b884d4c6a0b2c606031e6e57a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 10 Jan 2025 04:07:07 +0100 Subject: [PATCH 06/16] make linter happy --- typings/xterm.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 9473afd172..0fe72116f4 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -2007,8 +2007,8 @@ declare module '@xterm/xterm' { deleteAndLeak(o: T): void; } - export type EmitterCtorType = { new(): IEmitter; } - export type DisposableStoreCtorType = { new(): IDisposableStore; } + export type EmitterCtorType = new() => IEmitter + export type DisposableStoreCtorType = new() => IDisposableStore export class DisposableAddon implements IDisposable { protected readonly _store: IDisposableStore; From e1ffd155b5110940ac5041c8d78fe07960a4179d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 10 Jan 2025 04:08:38 +0100 Subject: [PATCH 07/16] make linter happy --- typings/xterm.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 0fe72116f4..d3a24ad821 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -2007,8 +2007,8 @@ declare module '@xterm/xterm' { deleteAndLeak(o: T): void; } - export type EmitterCtorType = new() => IEmitter - export type DisposableStoreCtorType = new() => IDisposableStore + export type EmitterCtorType = new() => IEmitter; + export type DisposableStoreCtorType = new() => IDisposableStore; export class DisposableAddon implements IDisposable { protected readonly _store: IDisposableStore; From 2a7ac40b7301b00ff9bcba7788d9b7cc31e77b48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 10 Jan 2025 04:24:39 +0100 Subject: [PATCH 08/16] add test stub --- src/shared/shared.test.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/shared/shared.test.ts diff --git a/src/shared/shared.test.ts b/src/shared/shared.test.ts new file mode 100644 index 0000000000..e69de29bb2 From 22fb93d0a4578f1b4a8dbd0b23fed3a81840ef58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 10 Jan 2025 04:31:39 +0100 Subject: [PATCH 09/16] fix esbuild --- bin/esbuild.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/esbuild.mjs b/bin/esbuild.mjs index 1727defe54..d05b9194ff 100644 --- a/bin/esbuild.mjs +++ b/bin/esbuild.mjs @@ -176,6 +176,7 @@ if (config.addon) { 'src/browser/**/*.ts', 'src/common/**/*.ts', 'src/headless/**/*.ts', + 'src/shared/**/*.ts', 'src/vs/base/**/*.ts', 'src/vs/patches/**/*.ts' ], From 14259bdb20412345f4fb2c686954eb5852ae5f49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 10 Jan 2025 04:42:39 +0100 Subject: [PATCH 10/16] fix integration test --- addons/addon-progress/test/ProgressAddon.test.ts | 2 +- demo/client.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/addons/addon-progress/test/ProgressAddon.test.ts b/addons/addon-progress/test/ProgressAddon.test.ts index 792c0445de..ce4177a981 100644 --- a/addons/addon-progress/test/ProgressAddon.test.ts +++ b/addons/addon-progress/test/ProgressAddon.test.ts @@ -23,7 +23,7 @@ test.describe('ProgressAddon', () => { window.progressStack = []; window.term.reset(); window.progressAddon?.dispose(); - window.progressAddon = new ProgressAddon(); + window.progressAddon = new ProgressAddon(emitterCtor); window.term.loadAddon(window.progressAddon); window.progressAddon.onChange(progress => window.progressStack.push(progress)); `); diff --git a/demo/client.ts b/demo/client.ts index 64e40e59b9..613475aa2c 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -44,6 +44,8 @@ export interface IWindowWithTerminal extends Window { Unicode11Addon?: typeof Unicode11Addon; // eslint-disable-line @typescript-eslint/naming-convention UnicodeGraphemesAddon?: typeof UnicodeGraphemesAddon; // eslint-disable-line @typescript-eslint/naming-convention LigaturesAddon?: typeof LigaturesAddon; // eslint-disable-line @typescript-eslint/naming-convention + + emitterCtor?: typeof emitterCtor; } declare let window: IWindowWithTerminal; @@ -226,6 +228,8 @@ if (document.location.pathname === '/test') { window.LigaturesAddon = LigaturesAddon; window.WebLinksAddon = WebLinksAddon; window.WebglAddon = WebglAddon; + + window.emitterCtor = emitterCtor; } else { createTerminal(); document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler); From 41d1ca1907316f1472f5ae27e5d7f7a398e542bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 10 Jan 2025 04:58:50 +0100 Subject: [PATCH 11/16] remove vs imports from shared --- src/browser/public/Terminal.ts | 5 ++--- src/shared/shared.ts | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index c57bf68556..e15db2df80 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -280,10 +280,9 @@ export class Terminal extends Disposable implements ITerminalApi { * so they can be used on addon ctors already. */ export { - DisposableStore as disposableStoreCtor, - toDisposable, - Emitter as emitterCtor, DisposableAddon, EmitterAddon, DisposableEmitterAddon } from 'shared/shared'; +export { DisposableStore as disposableStoreCtor, toDisposable } from 'vs/base/common/lifecycle'; +export { Emitter as emitterCtor } from 'vs/base/common/event'; diff --git a/src/shared/shared.ts b/src/shared/shared.ts index 6a317f7941..7d9e3b7a04 100644 --- a/src/shared/shared.ts +++ b/src/shared/shared.ts @@ -5,8 +5,6 @@ import { IDisposable, IDisposableStore, DisposableStoreCtorType, EmitterCtorType } from '@xterm/xterm'; -export { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; -export { Emitter } from 'vs/base/common/event'; export class DisposableAddon implements IDisposable { protected readonly _store: IDisposableStore; From 543fefa7441194bfa3e8235072acb7333c10834a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 11 Jan 2025 21:02:47 +0100 Subject: [PATCH 12/16] next approach --- .eslintrc.json | 1 - addons/addon-progress/src/ProgressAddon.ts | 17 ++--- addons/addon-progress/src/tsconfig.json | 6 -- .../addon-progress/test/ProgressAddon.test.ts | 2 +- .../typings/addon-progress.d.ts | 8 +-- addons/addon-progress/webpack.config.js | 1 - bin/esbuild.mjs | 1 - demo/client.ts | 12 ++-- src/browser/public/Terminal.ts | 32 +++++---- src/browser/tsconfig.json | 6 +- src/common/shared/AddonDisposable.ts | 19 ++++++ src/headless/public/Terminal.ts | 19 +++++- src/shared/shared.test.ts | 0 src/shared/shared.ts | 42 ------------ src/shared/tsconfig.json | 24 ------- typings/xterm-headless.d.ts | 27 ++++++++ typings/xterm.d.ts | 66 ++++--------------- webpack.config.js | 1 - 18 files changed, 106 insertions(+), 178 deletions(-) create mode 100644 src/common/shared/AddonDisposable.ts delete mode 100644 src/shared/shared.test.ts delete mode 100644 src/shared/shared.ts delete mode 100644 src/shared/tsconfig.json diff --git a/.eslintrc.json b/.eslintrc.json index 399ef0bc77..acd85af60a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -11,7 +11,6 @@ "src/browser/tsconfig.json", "src/common/tsconfig.json", "src/headless/tsconfig.json", - "src/shared/tsconfig.json", "src/vs/tsconfig.json", "test/benchmark/tsconfig.json", "test/playwright/tsconfig.json", diff --git a/addons/addon-progress/src/ProgressAddon.ts b/addons/addon-progress/src/ProgressAddon.ts index 5eec7380b5..a600844412 100644 --- a/addons/addon-progress/src/ProgressAddon.ts +++ b/addons/addon-progress/src/ProgressAddon.ts @@ -3,17 +3,9 @@ * @license MIT */ -import { Terminal, ITerminalAddon, IDisposable, EmitterCtorType, IEmitter, IEvent } from '@xterm/xterm'; +import { Terminal, ITerminalAddon, IDisposable, IEmitter, IEvent, ISharedExports } from '@xterm/xterm'; import type { ProgressAddon as IProgressApi, IProgressState } from '@xterm/addon-progress'; -// to use impl parts: - -// in 3rd party addons -// import { EmitterAddon } from '@xterm/xterm'; - -// in xtermjs repo addons -import { EmitterAddon } from 'shared/shared'; - const enum ProgressType { REMOVE = 0, @@ -40,16 +32,15 @@ function toInt(s: string): number { } -export class ProgressAddon extends EmitterAddon implements ITerminalAddon, IProgressApi { +export class ProgressAddon implements ITerminalAddon, IProgressApi { private _seqHandler: IDisposable | undefined; private _st: ProgressType = ProgressType.REMOVE; private _pr = 0; private _onChange: IEmitter; public onChange: IEvent; - constructor(protected readonly _emitterCtor: EmitterCtorType) { - super(_emitterCtor); - this._onChange = new this._emitterCtor(); + constructor(sharedExports: ISharedExports) { + this._onChange = new sharedExports.Emitter(); this.onChange = this._onChange.event; } diff --git a/addons/addon-progress/src/tsconfig.json b/addons/addon-progress/src/tsconfig.json index cb24712e6a..ffc7b01a58 100644 --- a/addons/addon-progress/src/tsconfig.json +++ b/addons/addon-progress/src/tsconfig.json @@ -22,9 +22,6 @@ "vs/*": [ "../../../src/vs/*" ], - "shared/*": [ - "../../../src/shared/*" - ], "@xterm/addon-progress": [ "../typings/addon-progress.d.ts" ] @@ -40,9 +37,6 @@ }, { "path": "../../../src/vs" - }, - { - "path": "../../../src/shared" } ] } diff --git a/addons/addon-progress/test/ProgressAddon.test.ts b/addons/addon-progress/test/ProgressAddon.test.ts index ce4177a981..e532148805 100644 --- a/addons/addon-progress/test/ProgressAddon.test.ts +++ b/addons/addon-progress/test/ProgressAddon.test.ts @@ -23,7 +23,7 @@ test.describe('ProgressAddon', () => { window.progressStack = []; window.term.reset(); window.progressAddon?.dispose(); - window.progressAddon = new ProgressAddon(emitterCtor); + window.progressAddon = new ProgressAddon(sharedExports); window.term.loadAddon(window.progressAddon); window.progressAddon.onChange(progress => window.progressStack.push(progress)); `); diff --git a/addons/addon-progress/typings/addon-progress.d.ts b/addons/addon-progress/typings/addon-progress.d.ts index ea89b9a795..d72f29bbde 100644 --- a/addons/addon-progress/typings/addon-progress.d.ts +++ b/addons/addon-progress/typings/addon-progress.d.ts @@ -3,20 +3,20 @@ * @license MIT */ -import { Terminal, ITerminalAddon, IDisposable, IEvent, EmitterCtorType } from '@xterm/xterm'; -import { EmitterAddon } from 'shared/shared'; +import { Terminal, ITerminalAddon, IDisposable, IEvent, ISharedExports } from '@xterm/xterm'; + declare module '@xterm/addon-progress' { /** * An xterm.js addon that provides an interface for ConEmu's progress * sequence. */ - export class ProgressAddon extends EmitterAddon implements ITerminalAddon, IDisposable { + export class ProgressAddon implements ITerminalAddon, IDisposable { /** * Creates a new progress addon */ - constructor(_emitterCtor: EmitterCtorType); + constructor(sharedExports: ISharedExports); /** * Activates the addon diff --git a/addons/addon-progress/webpack.config.js b/addons/addon-progress/webpack.config.js index f2e96cfd25..5ee2531dab 100644 --- a/addons/addon-progress/webpack.config.js +++ b/addons/addon-progress/webpack.config.js @@ -28,7 +28,6 @@ module.exports = { common: path.resolve('../../out/common'), browser: path.resolve('../../out/browser'), vs: path.resolve('../../out/vs'), - shared: path.resolve('../../out/shared') } }, output: { diff --git a/bin/esbuild.mjs b/bin/esbuild.mjs index d05b9194ff..1727defe54 100644 --- a/bin/esbuild.mjs +++ b/bin/esbuild.mjs @@ -176,7 +176,6 @@ if (config.addon) { 'src/browser/**/*.ts', 'src/common/**/*.ts', 'src/headless/**/*.ts', - 'src/shared/**/*.ts', 'src/vs/base/**/*.ts', 'src/vs/patches/**/*.ts' ], diff --git a/demo/client.ts b/demo/client.ts index 613475aa2c..6a87b8f5fc 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -16,7 +16,7 @@ if ('WebAssembly' in window) { ImageAddon = imageAddon.ImageAddon; } -import { Terminal, ITerminalOptions, type IDisposable, type ITheme, emitterCtor } from '@xterm/xterm'; +import { Terminal, ITerminalOptions, type IDisposable, type ITheme, sharedExports, ISharedExports } from '@xterm/xterm'; import { AttachAddon } from '@xterm/addon-attach'; import { ClipboardAddon } from '@xterm/addon-clipboard'; import { FitAddon } from '@xterm/addon-fit'; @@ -32,6 +32,7 @@ import { UnicodeGraphemesAddon } from '@xterm/addon-unicode-graphemes'; export interface IWindowWithTerminal extends Window { term: typeof Terminal; Terminal: typeof Terminal; + sharedExports: ISharedExports; AttachAddon?: typeof AttachAddon; // eslint-disable-line @typescript-eslint/naming-convention ClipboardAddon?: typeof ClipboardAddon; // eslint-disable-line @typescript-eslint/naming-convention FitAddon?: typeof FitAddon; // eslint-disable-line @typescript-eslint/naming-convention @@ -44,8 +45,6 @@ export interface IWindowWithTerminal extends Window { Unicode11Addon?: typeof Unicode11Addon; // eslint-disable-line @typescript-eslint/naming-convention UnicodeGraphemesAddon?: typeof UnicodeGraphemesAddon; // eslint-disable-line @typescript-eslint/naming-convention LigaturesAddon?: typeof LigaturesAddon; // eslint-disable-line @typescript-eslint/naming-convention - - emitterCtor?: typeof emitterCtor; } declare let window: IWindowWithTerminal; @@ -216,6 +215,7 @@ const createNewWindowButtonHandler: () => void = () => { if (document.location.pathname === '/test') { window.Terminal = Terminal; + window.sharedExports = sharedExports; window.AttachAddon = AttachAddon; window.ClipboardAddon = ClipboardAddon; window.FitAddon = FitAddon; @@ -228,8 +228,6 @@ if (document.location.pathname === '/test') { window.LigaturesAddon = LigaturesAddon; window.WebLinksAddon = WebLinksAddon; window.WebglAddon = WebglAddon; - - window.emitterCtor = emitterCtor; } else { createTerminal(); document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler); @@ -283,7 +281,8 @@ function createTerminal(): void { addons.serialize.instance = new SerializeAddon(); addons.fit.instance = new FitAddon(); addons.image.instance = new ImageAddon(); - addons.progress.instance = new ProgressAddon(emitterCtor); + //addons.progress.instance = new ProgressAddon(Terminal as unknown as IXtermSharedImports); + addons.progress.instance = new ProgressAddon(sharedExports); addons.unicodeGraphemes.instance = new UnicodeGraphemesAddon(); addons.clipboard.instance = new ClipboardAddon(); try { // try to start with webgl renderer (might throw on older safari/webkit) @@ -664,6 +663,7 @@ function initAddons(term: Terminal): void { } if (checkbox.checked) { // HACK: Manually remove addons that cannot be changes + // FIXME: re-enable this once done with the sharedExports //addon.instance = new (addon as IDemoAddon>).ctor(); try { term.loadAddon(addon.instance); diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index e15db2df80..d09ada4213 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -6,14 +6,26 @@ import * as Strings from 'browser/LocalizableStrings'; import { CoreBrowserTerminal as TerminalCore } from 'browser/CoreBrowserTerminal'; import { IBufferRange, ITerminal } from 'browser/Types'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { ITerminalOptions } from 'common/Types'; import { AddonManager } from 'common/public/AddonManager'; import { BufferNamespaceApi } from 'common/public/BufferNamespaceApi'; import { ParserApi } from 'common/public/ParserApi'; import { UnicodeApi } from 'common/public/UnicodeApi'; -import { IBufferNamespace as IBufferNamespaceApi, IDecoration, IDecorationOptions, IDisposable, ILinkProvider, ILocalizableStrings, IMarker, IModes, IParser, ITerminalAddon, Terminal as ITerminalApi, ITerminalInitOnlyOptions, IUnicodeHandling } from '@xterm/xterm'; -import { type Event } from 'vs/base/common/event'; +import { IBufferNamespace as IBufferNamespaceApi, IDecoration, IDecorationOptions, IDisposable, ILinkProvider, ILocalizableStrings, IMarker, IModes, IParser, ITerminalAddon, Terminal as ITerminalApi, ITerminalInitOnlyOptions, IUnicodeHandling, ISharedExports } from '@xterm/xterm'; +import { type Event, Emitter } from 'vs/base/common/event'; + + +/** + * EXPERIMENTAL: + * Expose certain building blocks on the module to be used at runtime in addons. + */ +export const sharedExports: ISharedExports = { + DisposableStore, + Emitter, + toDisposable +}; + /** * The set of options that only have an effect when set in the Terminal constructor. @@ -272,17 +284,3 @@ export class Terminal extends Disposable implements ITerminalApi { } } } - - -/** - * Expose often needed vs/* parts in addons. - * Exposed statically on the xterm package, - * so they can be used on addon ctors already. - */ -export { - DisposableAddon, - EmitterAddon, - DisposableEmitterAddon -} from 'shared/shared'; -export { DisposableStore as disposableStoreCtor, toDisposable } from 'vs/base/common/lifecycle'; -export { Emitter as emitterCtor } from 'vs/base/common/event'; diff --git a/src/browser/tsconfig.json b/src/browser/tsconfig.json index e1b73cf642..38854e260f 100644 --- a/src/browser/tsconfig.json +++ b/src/browser/tsconfig.json @@ -13,8 +13,7 @@ "baseUrl": "..", "paths": { "common/*": [ "./common/*" ], - "vs/*": [ "./vs/*" ], - "shared/*": [ "./shared/*" ], + "vs/*": [ "./vs/*" ] } }, "include": [ @@ -23,7 +22,6 @@ ], "references": [ { "path": "../common" }, - { "path": "../vs" }, - { "path": "../shared" }, + { "path": "../vs" } ] } diff --git a/src/common/shared/AddonDisposable.ts b/src/common/shared/AddonDisposable.ts new file mode 100644 index 0000000000..36f2d4f5f3 --- /dev/null +++ b/src/common/shared/AddonDisposable.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2024 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IDisposable, IDisposableStore, ISharedExports } from '@xterm/xterm'; + + +export class AddonDisposable implements IDisposable { + protected readonly _store: IDisposableStore; + + constructor(sharedExports: ISharedExports) { + this._store = new sharedExports.DisposableStore(); + } + + public dispose(): void { + this._store.dispose(); + } +} diff --git a/src/headless/public/Terminal.ts b/src/headless/public/Terminal.ts index 738570c9e7..6ab7f2f290 100644 --- a/src/headless/public/Terminal.ts +++ b/src/headless/public/Terminal.ts @@ -6,12 +6,25 @@ import { BufferNamespaceApi } from 'common/public/BufferNamespaceApi'; import { ParserApi } from 'common/public/ParserApi'; import { UnicodeApi } from 'common/public/UnicodeApi'; -import { IBufferNamespace as IBufferNamespaceApi, IMarker, IModes, IParser, ITerminalAddon, ITerminalInitOnlyOptions, IUnicodeHandling, Terminal as ITerminalApi } from '@xterm/headless'; +import { IBufferNamespace as IBufferNamespaceApi, IMarker, IModes, IParser, ITerminalAddon, ITerminalInitOnlyOptions, IUnicodeHandling, Terminal as ITerminalApi, ISharedExports } from '@xterm/headless'; import { Terminal as TerminalCore } from 'headless/Terminal'; import { AddonManager } from 'common/public/AddonManager'; import { ITerminalOptions } from 'common/Types'; -import { Disposable } from 'vs/base/common/lifecycle'; -import type { Event } from 'vs/base/common/event'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { type Event, Emitter } from 'vs/base/common/event'; + + +/** + * EXPERIMENTAL: + * Expose certain building blocks on the module to be used at runtime in addons. + */ +export const sharedExports: ISharedExports = { + DisposableStore, + Emitter, + toDisposable +}; + + /** * The set of options that only have an effect when set in the Terminal constructor. */ diff --git a/src/shared/shared.test.ts b/src/shared/shared.test.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/shared/shared.ts b/src/shared/shared.ts deleted file mode 100644 index 7d9e3b7a04..0000000000 --- a/src/shared/shared.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) 2024 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IDisposable, IDisposableStore, DisposableStoreCtorType, EmitterCtorType } from '@xterm/xterm'; - - -export class DisposableAddon implements IDisposable { - protected readonly _store: IDisposableStore; - - constructor(protected readonly _storeCtor: DisposableStoreCtorType) { - this._store = new _storeCtor(); - } - - public dispose(): void { - this._store.dispose(); - } -} - - -export class EmitterAddon { - constructor( - protected readonly _emitterCtor: EmitterCtorType - ) {} -} - - -export class DisposableEmitterAddon implements IDisposable { - protected readonly _store: IDisposableStore; - - constructor( - protected readonly _storeCtor: DisposableStoreCtorType, - protected readonly _emitterCtor: EmitterCtorType - ) { - this._store = new _storeCtor(); - } - - public dispose(): void { - this._store.dispose(); - } -} diff --git a/src/shared/tsconfig.json b/src/shared/tsconfig.json deleted file mode 100644 index e9b3673ab2..0000000000 --- a/src/shared/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "extends": "../tsconfig-library-base", - "compilerOptions": { - "lib": [ - "es2015", - "es2016.Array.Include" - ], - "outDir": "../../out", - "types": [ - "../../node_modules/@types/mocha" - ], - "baseUrl": "..", - "paths": { - "vs/*": [ "./vs/*" ] - } - }, - "include": [ - "./**/*", - "../../typings/xterm.d.ts" - ], - "references": [ - { "path": "../vs" } - ] -} diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index 1085db9b43..e696713e21 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -1358,4 +1358,31 @@ declare module '@xterm/headless' { */ readonly wraparoundMode: boolean; } + + /** + * EXPERIMENTAL: + * Module exposure of certain building blocks to be used at runtime in addons. + */ + export interface ISharedExports { + readonly DisposableStore: new() => IDisposableStore; + readonly Emitter: new() => IEmitter; + readonly toDisposable: (fn: () => void) => IDisposable; + } + + export const sharedExports: ISharedExports; + + // FIXME: @Tyriar - plz have a look at the following interfaces and + // to what degree those should be exposed or get stripped down + export interface IEmitter extends IDisposable { + event: IEvent; + fire(event: T): void; + hasListeners(): boolean; + } + export interface IDisposableStore extends IDisposable { + isDisposed: boolean; + clear(): void; + add(o: T): T; + delete(o: T): void; + deleteAndLeak(o: T): void; + } } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index d3a24ad821..1c18c9f730 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -1957,72 +1957,30 @@ declare module '@xterm/xterm' { readonly wraparoundMode: boolean; } - - /** - * Get Emitter constructor. - */ - export const emitterCtor: EmitterCtorType; - - /** - * Get DisposableStore contructor. - */ - export const disposableStoreCtor: DisposableStoreCtorType; - /** - * Turn a function into a Disposable. + * EXPERIMENTAL: + * Module exposure of certain building blocks to be used at runtime in addons. */ - export const toDisposable: (fn: () => void) => IDisposable; + export interface ISharedExports { + readonly DisposableStore: new() => IDisposableStore; + readonly Emitter: new() => IEmitter; + readonly toDisposable: (fn: () => void) => IDisposable; + } + export const sharedExports: ISharedExports; - export interface IEmitter { - dispose(): void; + // FIXME: @Tyriar - plz have a look at the following interfaces and + // to what degree those should be exposed or get stripped down + export interface IEmitter extends IDisposable { event: IEvent; fire(event: T): void; hasListeners(): boolean; } - - interface IDisposableStore extends IDisposable { - /** - * `true` if this object has been disposed of. - */ + export interface IDisposableStore extends IDisposable { isDisposed: boolean; - /** - * Dispose of all registered disposables but do not mark this object - * as disposed. - */ clear(): void; - /** - * Add a new {@link IDisposable disposable} to the collection. - */ add(o: T): T; - /** - * Deletes a disposable from store and disposes of it. - * This will not throw or warn and proceed to dispose the - * disposable even when the disposable is not part in the store. - */ delete(o: T): void; - /** - * Deletes the value from the store, but does not dispose it. - */ deleteAndLeak(o: T): void; } - - export type EmitterCtorType = new() => IEmitter; - export type DisposableStoreCtorType = new() => IDisposableStore; - - export class DisposableAddon implements IDisposable { - protected readonly _store: IDisposableStore; - constructor(_storeCtor: DisposableStoreCtorType); - public dispose(): void; - } - export class EmitterAddon { - protected readonly _emitterCtor: EmitterCtorType; - constructor(_emitterCtor: EmitterCtorType); - } - export class DisposableEmitterAddon implements IDisposable { - protected readonly _store: IDisposableStore; - protected readonly _emitterCtor: EmitterCtorType; - constructor(_storeCtor: DisposableStoreCtorType, _emitterCtor: EmitterCtorType); - public dispose(): void; - } } diff --git a/webpack.config.js b/webpack.config.js index 37b07d1bbe..123e31dfbb 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -35,7 +35,6 @@ const config = { common: path.resolve('./out/common'), browser: path.resolve('./out/browser'), vs: path.resolve('./out/vs'), - shared: path.resolve('./out/shared') } }, output: { From 893d89ab29a7b999ca72c35c1b28603b27a3f85a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 11 Jan 2025 21:18:15 +0100 Subject: [PATCH 13/16] cleanup --- addons/addon-image/src/ImageAddon.ts | 6 +-- addons/addon-image/src/ImageRenderer.ts | 15 +++---- addons/addon-image/test/ImageAddon.test.ts | 7 ++-- addons/addon-image/typings/addon-image.d.ts | 4 +- addons/addon-progress/webpack.config.js | 2 +- demo/client.ts | 7 ++-- src/browser/public/Terminal.ts | 3 +- src/common/shared/AddonDisposable.ts | 7 ++++ typings/xterm-headless.d.ts | 44 ++++++++++----------- typings/xterm.d.ts | 6 +++ 10 files changed, 58 insertions(+), 43 deletions(-) diff --git a/addons/addon-image/src/ImageAddon.ts b/addons/addon-image/src/ImageAddon.ts index 167e93a791..2712a1d10e 100644 --- a/addons/addon-image/src/ImageAddon.ts +++ b/addons/addon-image/src/ImageAddon.ts @@ -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 = new Map(); - constructor(opts?: Partial) { + constructor(private _sharedExports: ISharedExports, opts?: Partial) { 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 diff --git a/addons/addon-image/src/ImageRenderer.ts b/addons/addon-image/src/ImageRenderer.ts index e1790f4783..3dedb42453 100644 --- a/addons/addon-image/src/ImageRenderer.ts +++ b/addons/addon-image/src/ImageRenderer.ts @@ -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; 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; diff --git a/addons/addon-image/test/ImageAddon.test.ts b/addons/addon-image/test/ImageAddon.test.ts index 25d47c8242..bdba47c8ad 100644 --- a/addons/addon-image/test/ImageAddon.test.ts +++ b/addons/addon-image/test/ImageAddon.test.ts @@ -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): any; + new(sharedExports: ISharedExports, options?: Partial): 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); diff --git a/addons/addon-image/typings/addon-image.d.ts b/addons/addon-image/typings/addon-image.d.ts index 48ba488cfa..942b58c4f3 100644 --- a/addons/addon-image/typings/addon-image.d.ts +++ b/addons/addon-image/typings/addon-image.d.ts @@ -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; diff --git a/addons/addon-progress/webpack.config.js b/addons/addon-progress/webpack.config.js index 5ee2531dab..1bf29bfd2c 100644 --- a/addons/addon-progress/webpack.config.js +++ b/addons/addon-progress/webpack.config.js @@ -27,7 +27,7 @@ module.exports = { alias: { common: path.resolve('../../out/common'), browser: path.resolve('../../out/browser'), - vs: path.resolve('../../out/vs'), + vs: path.resolve('../../out/vs') } }, output: { diff --git a/demo/client.ts b/demo/client.ts index 6a87b8f5fc..7e03034937 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -280,8 +280,7 @@ function createTerminal(): void { addons.search.instance = new SearchAddon(); addons.serialize.instance = new SerializeAddon(); addons.fit.instance = new FitAddon(); - addons.image.instance = new ImageAddon(); - //addons.progress.instance = new ProgressAddon(Terminal as unknown as IXtermSharedImports); + addons.image.instance = new ImageAddon(sharedExports); addons.progress.instance = new ProgressAddon(sharedExports); addons.unicodeGraphemes.instance = new UnicodeGraphemesAddon(); addons.clipboard.instance = new ClipboardAddon(); @@ -640,8 +639,8 @@ function initAddons(term: Terminal): void { if (checkbox.checked) { const ctorOptionsJson = document.querySelector('#image-options').value; addon.instance = ctorOptionsJson - ? new addons[name].ctor(JSON.parse(ctorOptionsJson)) - : new addons[name].ctor(); + ? new addons[name].ctor(sharedExports, JSON.parse(ctorOptionsJson)) + : new addons[name].ctor(sharedExports); term.loadAddon(addon.instance); } else { addon.instance!.dispose(); diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index d09ada4213..2f895fb44b 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -6,7 +6,7 @@ import * as Strings from 'browser/LocalizableStrings'; import { CoreBrowserTerminal as TerminalCore } from 'browser/CoreBrowserTerminal'; import { IBufferRange, ITerminal } from 'browser/Types'; -import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ITerminalOptions } from 'common/Types'; import { AddonManager } from 'common/public/AddonManager'; import { BufferNamespaceApi } from 'common/public/BufferNamespaceApi'; @@ -22,6 +22,7 @@ import { type Event, Emitter } from 'vs/base/common/event'; */ export const sharedExports: ISharedExports = { DisposableStore, + MutableDisposable, Emitter, toDisposable }; diff --git a/src/common/shared/AddonDisposable.ts b/src/common/shared/AddonDisposable.ts index 36f2d4f5f3..f41d526d7e 100644 --- a/src/common/shared/AddonDisposable.ts +++ b/src/common/shared/AddonDisposable.ts @@ -13,6 +13,13 @@ export class AddonDisposable implements IDisposable { this._store = new sharedExports.DisposableStore(); } + protected _register(o: T): T { + if ((o as unknown as IDisposable) === this) { + throw new Error('Cannot register a disposable on itself!'); + } + return this._store.add(o); + } + public dispose(): void { this._store.dispose(); } diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index e696713e21..2c7b8b88bf 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -1363,26 +1363,26 @@ declare module '@xterm/headless' { * EXPERIMENTAL: * Module exposure of certain building blocks to be used at runtime in addons. */ - export interface ISharedExports { - readonly DisposableStore: new() => IDisposableStore; - readonly Emitter: new() => IEmitter; - readonly toDisposable: (fn: () => void) => IDisposable; - } - - export const sharedExports: ISharedExports; - - // FIXME: @Tyriar - plz have a look at the following interfaces and - // to what degree those should be exposed or get stripped down - export interface IEmitter extends IDisposable { - event: IEvent; - fire(event: T): void; - hasListeners(): boolean; - } - export interface IDisposableStore extends IDisposable { - isDisposed: boolean; - clear(): void; - add(o: T): T; - delete(o: T): void; - deleteAndLeak(o: T): void; - } + export interface ISharedExports { + readonly DisposableStore: new() => IDisposableStore; + readonly Emitter: new() => IEmitter; + readonly toDisposable: (fn: () => void) => IDisposable; + } + + export const sharedExports: ISharedExports; + + // FIXME: @Tyriar - plz have a look at the following interfaces and + // to what degree those should be exposed or get stripped down + export interface IEmitter extends IDisposable { + event: IEvent; + fire(event: T): void; + hasListeners(): boolean; + } + export interface IDisposableStore extends IDisposable { + isDisposed: boolean; + clear(): void; + add(o: T): T; + delete(o: T): void; + deleteAndLeak(o: T): void; + } } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 1c18c9f730..d67cf385ba 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -1963,6 +1963,7 @@ declare module '@xterm/xterm' { */ export interface ISharedExports { readonly DisposableStore: new() => IDisposableStore; + readonly MutableDisposable: new() => IMutableDisposable; readonly Emitter: new() => IEmitter; readonly toDisposable: (fn: () => void) => IDisposable; } @@ -1983,4 +1984,9 @@ declare module '@xterm/xterm' { delete(o: T): void; deleteAndLeak(o: T): void; } + export interface IMutableDisposable extends IDisposable { + value: T | undefined; + clear(): void; + dispose(): void; + } } From b03934c20832b1a5f3e4b521d71f47bdba9930a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 11 Jan 2025 23:30:56 +0100 Subject: [PATCH 14/16] apply to search addon --- addons/addon-search/src/SearchAddon.ts | 49 +++++++++++++------ addons/addon-search/test/SearchAddon.test.ts | 2 +- addons/addon-search/typings/addon-search.d.ts | 4 +- demo/client.ts | 2 +- 4 files changed, 37 insertions(+), 20 deletions(-) diff --git a/addons/addon-search/src/SearchAddon.ts b/addons/addon-search/src/SearchAddon.ts index 77cf9b16a9..dd12b1822e 100644 --- a/addons/addon-search/src/SearchAddon.ts +++ b/addons/addon-search/src/SearchAddon.ts @@ -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 = new Set(); private _highlightDecorations: IHighlight[] = []; - private _selectedDecoration: MutableDisposable = this._register(new MutableDisposable()); + private _selectedDecoration: IMutableDisposable; 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; + + 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) { + super(_sharedExports); + this._onDidChangeResults = this._register(new _sharedExports.Emitter<{ resultIndex: number, resultCount: number }>()); + this.onDidChangeResults = this._onDidChangeResults.event; - constructor(options?: Partial) { - 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; } diff --git a/addons/addon-search/test/SearchAddon.test.ts b/addons/addon-search/test/SearchAddon.test.ts index 0045cf1c21..6055a200bc 100644 --- a/addons/addon-search/test/SearchAddon.test.ts +++ b/addons/addon-search/test/SearchAddon.test.ts @@ -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); `); }); diff --git a/addons/addon-search/typings/addon-search.d.ts b/addons/addon-search/typings/addon-search.d.ts index 282004a2ae..afaa0c663f 100644 --- a/addons/addon-search/typings/addon-search.d.ts +++ b/addons/addon-search/typings/addon-search.d.ts @@ -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); + constructor(sharedExports: ISharedExports, options?: Partial); /** * Activates the addon diff --git a/demo/client.ts b/demo/client.ts index 7e03034937..5802c9355a 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -277,7 +277,7 @@ function createTerminal(): void { // Load addons const typedTerm = term as Terminal; - addons.search.instance = new SearchAddon(); + addons.search.instance = new SearchAddon(sharedExports); addons.serialize.instance = new SerializeAddon(); addons.fit.instance = new FitAddon(); addons.image.instance = new ImageAddon(sharedExports); From a96e4f85651c9948e0c4ca38cab53ae3e40b61ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 12 Jan 2025 00:54:31 +0100 Subject: [PATCH 15/16] apply to webgl addon --- addons/addon-webgl/src/CharAtlasCache.ts | 5 +- addons/addon-webgl/src/DevicePixelObserver.ts | 6 +- addons/addon-webgl/src/GlyphRenderer.ts | 19 +-- addons/addon-webgl/src/RectangleRenderer.ts | 17 +-- addons/addon-webgl/src/TextureAtlas.ts | 16 ++- addons/addon-webgl/src/Types.ts | 8 +- addons/addon-webgl/src/WebglAddon.ts | 47 ++++--- addons/addon-webgl/src/WebglRenderer.ts | 123 +++++++++++++----- addons/addon-webgl/src/WebglUtils.ts | 5 + .../src/renderLayer/BaseRenderLayer.ts | 14 +- .../src/renderLayer/LinkRenderLayer.ts | 5 +- addons/addon-webgl/test/WebglRenderer.test.ts | 4 +- addons/addon-webgl/typings/addon-webgl.d.ts | 4 +- demo/client.ts | 4 +- 14 files changed, 183 insertions(+), 94 deletions(-) diff --git a/addons/addon-webgl/src/CharAtlasCache.ts b/addons/addon-webgl/src/CharAtlasCache.ts index ea02581b8d..c416fa2e00 100644 --- a/addons/addon-webgl/src/CharAtlasCache.ts +++ b/addons/addon-webgl/src/CharAtlasCache.ts @@ -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, 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] }; diff --git a/addons/addon-webgl/src/DevicePixelObserver.ts b/addons/addon-webgl/src/DevicePixelObserver.ts index 33d733ef57..5842283747 100644 --- a/addons/addon-webgl/src/DevicePixelObserver.ts +++ b/addons/addon-webgl/src/DevicePixelObserver.ts @@ -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()); } diff --git a/addons/addon-webgl/src/GlyphRenderer.ts b/addons/addon-webgl/src/GlyphRenderer.ts index 10c9ad7222..1237e28e28 100644 --- a/addons/addon-webgl/src/GlyphRenderer.ts +++ b/addons/addon-webgl/src/GlyphRenderer.ts @@ -5,12 +5,12 @@ import { TextureAtlas } from './TextureAtlas'; import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { NULL_CELL_CODE } from 'common/buffer/Constants'; -import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { Terminal } from '@xterm/xterm'; +import { ISharedExports, Terminal } from '@xterm/xterm'; import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject, type IRasterizedGlyph, type ITextureAtlas } from './Types'; import { createProgram, GLTexture, PROJECTION_MATRIX } from './WebglUtils'; import type { IOptionsService } from 'common/services/Services'; import { allowRescaling, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; +import { AddonDisposable } from 'common/shared/AddonDisposable'; interface IVertices { attributes: Float32Array; @@ -88,7 +88,7 @@ let $glyph: IRasterizedGlyph | undefined = undefined; let $leftCellPadding = 0; let $clippedPixels = 0; -export class GlyphRenderer extends Disposable { +export class GlyphRenderer extends AddonDisposable { private readonly _program: WebGLProgram; private readonly _vertexArrayObject: IWebGLVertexArrayObject; private readonly _projectionLocation: WebGLUniformLocation; @@ -109,12 +109,13 @@ export class GlyphRenderer extends Disposable { }; constructor( + private _sharedExports: ISharedExports, private readonly _terminal: Terminal, private readonly _gl: IWebGL2RenderingContext, private _dimensions: IRenderDimensions, private readonly _optionsService: IOptionsService ) { - super(); + super(_sharedExports); const gl = this._gl; @@ -126,7 +127,7 @@ export class GlyphRenderer extends Disposable { } this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, createFragmentShaderSource(TextureAtlas.maxAtlasPages))); - this._register(toDisposable(() => gl.deleteProgram(this._program))); + this._register(_sharedExports.toDisposable(() => gl.deleteProgram(this._program))); // Uniform locations this._projectionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_projection')); @@ -140,7 +141,7 @@ export class GlyphRenderer extends Disposable { // Setup a_unitquad, this defines the 4 vertices of a rectangle const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]); const unitQuadVerticesBuffer = gl.createBuffer(); - this._register(toDisposable(() => gl.deleteBuffer(unitQuadVerticesBuffer))); + this._register(_sharedExports.toDisposable(() => gl.deleteBuffer(unitQuadVerticesBuffer))); gl.bindBuffer(gl.ARRAY_BUFFER, unitQuadVerticesBuffer); gl.bufferData(gl.ARRAY_BUFFER, unitQuadVertices, gl.STATIC_DRAW); gl.enableVertexAttribArray(VertexAttribLocations.UNIT_QUAD); @@ -151,13 +152,13 @@ export class GlyphRenderer extends Disposable { // triangle strip const unitQuadElementIndices = new Uint8Array([0, 1, 2, 3]); const elementIndicesBuffer = gl.createBuffer(); - this._register(toDisposable(() => gl.deleteBuffer(elementIndicesBuffer))); + this._register(_sharedExports.toDisposable(() => gl.deleteBuffer(elementIndicesBuffer))); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementIndicesBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, unitQuadElementIndices, gl.STATIC_DRAW); // Setup attributes this._attributesBuffer = throwIfFalsy(gl.createBuffer()); - this._register(toDisposable(() => gl.deleteBuffer(this._attributesBuffer))); + this._register(_sharedExports.toDisposable(() => gl.deleteBuffer(this._attributesBuffer))); gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); gl.enableVertexAttribArray(VertexAttribLocations.OFFSET); gl.vertexAttribPointer(VertexAttribLocations.OFFSET, 2, gl.FLOAT, false, BYTES_PER_CELL, 0); @@ -192,7 +193,7 @@ export class GlyphRenderer extends Disposable { this._atlasTextures = []; for (let i = 0; i < TextureAtlas.maxAtlasPages; i++) { const glTexture = new GLTexture(throwIfFalsy(gl.createTexture())); - this._register(toDisposable(() => gl.deleteTexture(glTexture.texture))); + this._register(_sharedExports.toDisposable(() => gl.deleteTexture(glTexture.texture))); gl.activeTexture(gl.TEXTURE0 + i); gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); diff --git a/addons/addon-webgl/src/RectangleRenderer.ts b/addons/addon-webgl/src/RectangleRenderer.ts index 8ace9076e9..46da70222b 100644 --- a/addons/addon-webgl/src/RectangleRenderer.ts +++ b/addons/addon-webgl/src/RectangleRenderer.ts @@ -7,13 +7,13 @@ import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { IThemeService } from 'browser/services/Services'; import { ReadonlyColorSet } from 'browser/Types'; import { Attributes, FgFlags } from 'common/buffer/Constants'; -import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IColor } from 'common/Types'; -import { Terminal } from '@xterm/xterm'; +import { ISharedExports, Terminal } from '@xterm/xterm'; import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject } from './Types'; import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; +import { AddonDisposable } from 'common/shared/AddonDisposable'; const enum VertexAttribLocations { POSITION = 0, @@ -73,7 +73,7 @@ let $g = 0; let $b = 0; let $a = 0; -export class RectangleRenderer extends Disposable { +export class RectangleRenderer extends AddonDisposable { private _program: WebGLProgram; private _vertexArrayObject: IWebGLVertexArrayObject; @@ -86,17 +86,18 @@ export class RectangleRenderer extends Disposable { private _verticesCursor: Vertices = new Vertices(); constructor( + private _sharedExports: ISharedExports, private _terminal: Terminal, private _gl: IWebGL2RenderingContext, private _dimensions: IRenderDimensions, private readonly _themeService: IThemeService ) { - super(); + super(_sharedExports); const gl = this._gl; this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource)); - this._register(toDisposable(() => gl.deleteProgram(this._program))); + this._register(_sharedExports.toDisposable(() => gl.deleteProgram(this._program))); // Uniform locations this._projectionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_projection')); @@ -108,7 +109,7 @@ export class RectangleRenderer extends Disposable { // Setup a_unitquad, this defines the 4 vertices of a rectangle const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]); const unitQuadVerticesBuffer = gl.createBuffer(); - this._register(toDisposable(() => gl.deleteBuffer(unitQuadVerticesBuffer))); + this._register(_sharedExports.toDisposable(() => gl.deleteBuffer(unitQuadVerticesBuffer))); gl.bindBuffer(gl.ARRAY_BUFFER, unitQuadVerticesBuffer); gl.bufferData(gl.ARRAY_BUFFER, unitQuadVertices, gl.STATIC_DRAW); gl.enableVertexAttribArray(VertexAttribLocations.UNIT_QUAD); @@ -119,13 +120,13 @@ export class RectangleRenderer extends Disposable { // triangle strip const unitQuadElementIndices = new Uint8Array([0, 1, 2, 3]); const elementIndicesBuffer = gl.createBuffer(); - this._register(toDisposable(() => gl.deleteBuffer(elementIndicesBuffer))); + this._register(_sharedExports.toDisposable(() => gl.deleteBuffer(elementIndicesBuffer))); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementIndicesBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, unitQuadElementIndices, gl.STATIC_DRAW); // Setup attributes this._attributesBuffer = throwIfFalsy(gl.createBuffer()); - this._register(toDisposable(() => gl.deleteBuffer(this._attributesBuffer))); + this._register(_sharedExports.toDisposable(() => gl.deleteBuffer(this._attributesBuffer))); gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); gl.enableVertexAttribArray(VertexAttribLocations.POSITION); gl.vertexAttribPointer(VertexAttribLocations.POSITION, 2, gl.FLOAT, false, BYTES_PER_RECTANGLE, 0); diff --git a/addons/addon-webgl/src/TextureAtlas.ts b/addons/addon-webgl/src/TextureAtlas.ts index a7d1138ee0..0bcaa1688e 100644 --- a/addons/addon-webgl/src/TextureAtlas.ts +++ b/addons/addon-webgl/src/TextureAtlas.ts @@ -15,7 +15,7 @@ import { IColor } from 'common/Types'; import { AttributeData } from 'common/buffer/AttributeData'; import { Attributes, DEFAULT_COLOR, DEFAULT_EXT, UnderlineStyle } from 'common/buffer/Constants'; import { IUnicodeService } from 'common/services/Services'; -import { Emitter } from 'vs/base/common/event'; +import { ISharedExports, IEmitter, IEvent } from '@xterm/xterm'; /** * A shared object which is used to draw nothing for a particular cell. @@ -80,16 +80,22 @@ export class TextureAtlas implements ITextureAtlas { public static maxAtlasPages: number | undefined; public static maxTextureSize: number | undefined; - private readonly _onAddTextureAtlasCanvas = new Emitter(); - public readonly onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event; - private readonly _onRemoveTextureAtlasCanvas = new Emitter(); - public readonly onRemoveTextureAtlasCanvas = this._onRemoveTextureAtlasCanvas.event; + private readonly _onAddTextureAtlasCanvas: IEmitter; + public readonly onAddTextureAtlasCanvas: IEvent; + private readonly _onRemoveTextureAtlasCanvas: IEmitter; + public readonly onRemoveTextureAtlasCanvas: IEvent; constructor( + _sharedExports: ISharedExports, private readonly _document: Document, private readonly _config: ICharAtlasConfig, private readonly _unicodeService: IUnicodeService ) { + this._onAddTextureAtlasCanvas = new _sharedExports.Emitter(); + this.onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event; + this._onRemoveTextureAtlasCanvas = new _sharedExports.Emitter(); + this.onRemoveTextureAtlasCanvas = this._onRemoveTextureAtlasCanvas.event; + this._createNewPage(); this._tmpCanvas = createCanvas( _document, diff --git a/addons/addon-webgl/src/Types.ts b/addons/addon-webgl/src/Types.ts index e030e579ed..faf249f9b5 100644 --- a/addons/addon-webgl/src/Types.ts +++ b/addons/addon-webgl/src/Types.ts @@ -3,11 +3,11 @@ * @license MIT */ -import { FontWeight } from '@xterm/xterm'; +import { FontWeight, IEvent } from '@xterm/xterm'; import { IColorSet } from 'browser/Types'; import { ISelectionRenderModel } from 'browser/renderer/shared/Types'; import { CursorInactiveStyle, CursorStyle, type IDisposable } from 'common/Types'; -import type { Event } from 'vs/base/common/event'; + export interface IRenderModel { cells: Uint32Array; @@ -58,8 +58,8 @@ export interface ICharAtlasConfig { export interface ITextureAtlas extends IDisposable { readonly pages: { canvas: HTMLCanvasElement, version: number }[]; - onAddTextureAtlasCanvas: Event; - onRemoveTextureAtlasCanvas: Event; + onAddTextureAtlasCanvas: IEvent; + onRemoveTextureAtlasCanvas: IEvent; /** * Warm up the texture atlas, adding common glyphs to avoid slowing early frame. diff --git a/addons/addon-webgl/src/WebglAddon.ts b/addons/addon-webgl/src/WebglAddon.ts index 49a796de9a..65b1e7596d 100644 --- a/addons/addon-webgl/src/WebglAddon.ts +++ b/addons/addon-webgl/src/WebglAddon.ts @@ -3,32 +3,33 @@ * @license MIT */ -import type { ITerminalAddon, Terminal } from '@xterm/xterm'; +import type { ISharedExports, ITerminalAddon, Terminal, IEmitter, IEvent, IDisposable } from '@xterm/xterm'; import type { WebglAddon as IWebglApi } from '@xterm/addon-webgl'; import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services'; import { ITerminal } from 'browser/Types'; -import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { getSafariVersion, isSafari } from 'common/Platform'; import { ICoreService, IDecorationService, ILogService, IOptionsService } from 'common/services/Services'; import { IWebGL2RenderingContext } from './Types'; import { WebglRenderer } from './WebglRenderer'; import { setTraceLogger } from 'common/services/LogService'; -import { Emitter, Event } from 'vs/base/common/event'; +import { AddonDisposable } from 'common/shared/AddonDisposable'; +import { forwardEvent } from './WebglUtils'; -export class WebglAddon extends Disposable implements ITerminalAddon , IWebglApi { +export class WebglAddon extends AddonDisposable implements ITerminalAddon , IWebglApi { private _terminal?: Terminal; private _renderer?: WebglRenderer; - private readonly _onChangeTextureAtlas = this._register(new Emitter()); - public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; - private readonly _onAddTextureAtlasCanvas = this._register(new Emitter()); - public readonly onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event; - private readonly _onRemoveTextureAtlasCanvas = this._register(new Emitter()); - public readonly onRemoveTextureAtlasCanvas = this._onRemoveTextureAtlasCanvas.event; - private readonly _onContextLoss = this._register(new Emitter()); - public readonly onContextLoss = this._onContextLoss.event; + private readonly _onChangeTextureAtlas: IEmitter; + public readonly onChangeTextureAtlas: IEvent; + private readonly _onAddTextureAtlasCanvas: IEmitter; + public readonly onAddTextureAtlasCanvas: IEvent; + private readonly _onRemoveTextureAtlasCanvas: IEmitter; + public readonly onRemoveTextureAtlasCanvas: IEvent; + private readonly _onContextLoss: IEmitter; + public readonly onContextLoss: IEvent; constructor( + private _sharedExports: ISharedExports, private _preserveDrawingBuffer?: boolean ) { if (isSafari && getSafariVersion() < 16) { @@ -43,7 +44,16 @@ export class WebglAddon extends Disposable implements ITerminalAddon , IWebglApi throw new Error('Webgl2 is only supported on Safari 16 and above'); } } - super(); + super(_sharedExports); + + this._onChangeTextureAtlas = this._register(new _sharedExports.Emitter()); + this.onChangeTextureAtlas = this._onChangeTextureAtlas.event; + this._onAddTextureAtlasCanvas = this._register(new _sharedExports.Emitter()); + this.onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event; + this._onRemoveTextureAtlasCanvas = this._register(new _sharedExports.Emitter()); + this.onRemoveTextureAtlasCanvas = this._onRemoveTextureAtlasCanvas.event; + this._onContextLoss = this._register(new _sharedExports.Emitter()); + this.onContextLoss = this._onContextLoss.event; } public activate(terminal: Terminal): void { @@ -71,6 +81,7 @@ export class WebglAddon extends Disposable implements ITerminalAddon , IWebglApi setTraceLogger(logService); this._renderer = this._register(new WebglRenderer( + this._sharedExports, terminal, characterJoinerService, charSizeService, @@ -81,13 +92,13 @@ export class WebglAddon extends Disposable implements ITerminalAddon , IWebglApi themeService, this._preserveDrawingBuffer )); - this._register(Event.forward(this._renderer.onContextLoss, this._onContextLoss)); - this._register(Event.forward(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas)); - this._register(Event.forward(this._renderer.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas)); - this._register(Event.forward(this._renderer.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas)); + this._register(forwardEvent(this._renderer.onContextLoss, this._onContextLoss)); + this._register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas)); + this._register(forwardEvent(this._renderer.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas)); + this._register(forwardEvent(this._renderer.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas)); renderService.setRenderer(this._renderer); - this._register(toDisposable(() => { + this._register(this._sharedExports.toDisposable(() => { if ((this._terminal as any)._core._store._isDisposed) { return; } diff --git a/addons/addon-webgl/src/WebglRenderer.ts b/addons/addon-webgl/src/WebglRenderer.ts index 398594d942..288570027c 100644 --- a/addons/addon-webgl/src/WebglRenderer.ts +++ b/addons/addon-webgl/src/WebglRenderer.ts @@ -15,26 +15,25 @@ import { AttributeData } from 'common/buffer/AttributeData'; import { CellData } from 'common/buffer/CellData'; import { Attributes, Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { Terminal } from '@xterm/xterm'; +import { ISharedExports, Terminal, IMutableDisposable, IDisposable, IEmitter, IEvent } from '@xterm/xterm'; import { GlyphRenderer } from './GlyphRenderer'; import { RectangleRenderer } from './RectangleRenderer'; import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_EXT_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL, RenderModel } from './RenderModel'; import { IWebGL2RenderingContext, type ITextureAtlas } from './Types'; import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; import { IRenderLayer } from './renderLayer/Types'; -import { Emitter, Event } from 'vs/base/common/event'; -import { addDisposableListener } from 'vs/base/browser/dom'; -import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils'; +import { AddonDisposable } from 'common/shared/AddonDisposable'; +import { forwardEvent } from './WebglUtils'; -export class WebglRenderer extends Disposable implements IRenderer { +export class WebglRenderer extends AddonDisposable implements IRenderer { private _renderLayers: IRenderLayer[]; - private _cursorBlinkStateManager: MutableDisposable = new MutableDisposable(); - private _charAtlasDisposable = this._register(new MutableDisposable()); + private _cursorBlinkStateManager: IMutableDisposable; + private _charAtlasDisposable: IMutableDisposable; private _charAtlas: ITextureAtlas | undefined; private _devicePixelRatio: number; private _deviceMaxTextureSize: number; - private _observerDisposable = this._register(new MutableDisposable()); + private _observerDisposable: IMutableDisposable; private _model: RenderModel = new RenderModel(); private _workCell: ICellData = new CellData(); @@ -43,8 +42,8 @@ export class WebglRenderer extends Disposable implements IRenderer { private _canvas: HTMLCanvasElement; private _gl: IWebGL2RenderingContext; - private _rectangleRenderer: MutableDisposable = this._register(new MutableDisposable()); - private _glyphRenderer: MutableDisposable = this._register(new MutableDisposable()); + private _rectangleRenderer: IMutableDisposable; + private _glyphRenderer: IMutableDisposable; public readonly dimensions: IRenderDimensions; @@ -52,18 +51,19 @@ export class WebglRenderer extends Disposable implements IRenderer { private _isAttached: boolean; private _contextRestorationTimeout: number | undefined; - private readonly _onChangeTextureAtlas = this._register(new Emitter()); - public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; - private readonly _onAddTextureAtlasCanvas = this._register(new Emitter()); - public readonly onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event; - private readonly _onRemoveTextureAtlasCanvas = this._register(new Emitter()); - public readonly onRemoveTextureAtlasCanvas = this._onRemoveTextureAtlasCanvas.event; - private readonly _onRequestRedraw = this._register(new Emitter()); - public readonly onRequestRedraw = this._onRequestRedraw.event; - private readonly _onContextLoss = this._register(new Emitter()); - public readonly onContextLoss = this._onContextLoss.event; + private readonly _onChangeTextureAtlas: IEmitter; + public readonly onChangeTextureAtlas: IEvent; + private readonly _onAddTextureAtlasCanvas: IEmitter; + public readonly onAddTextureAtlasCanvas: IEvent; + private readonly _onRemoveTextureAtlasCanvas: IEmitter; + public readonly onRemoveTextureAtlasCanvas: IEvent; + private readonly _onRequestRedraw: IEmitter; + public readonly onRequestRedraw: IEvent; + private readonly _onContextLoss: IEmitter; + public readonly onContextLoss: IEvent; constructor( + private _sharedExports: ISharedExports, private _terminal: Terminal, private readonly _characterJoinerService: ICharacterJoinerService, private readonly _charSizeService: ICharSizeService, @@ -74,7 +74,24 @@ export class WebglRenderer extends Disposable implements IRenderer { private readonly _themeService: IThemeService, preserveDrawingBuffer?: boolean ) { - super(); + super(_sharedExports); + + this._cursorBlinkStateManager = new _sharedExports.MutableDisposable(); + this._charAtlasDisposable = this._register(new _sharedExports.MutableDisposable()); + this._observerDisposable = this._register(new _sharedExports.MutableDisposable()); + this._rectangleRenderer = this._register(new _sharedExports.MutableDisposable()); + this._glyphRenderer = this._register(new _sharedExports.MutableDisposable()); + + this._onChangeTextureAtlas = this._register(new _sharedExports.Emitter()); + this.onChangeTextureAtlas = this._onChangeTextureAtlas.event; + this._onAddTextureAtlasCanvas = this._register(new _sharedExports.Emitter()); + this.onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event; + this._onRemoveTextureAtlasCanvas = this._register(new _sharedExports.Emitter()); + this.onRemoveTextureAtlasCanvas = this._onRemoveTextureAtlasCanvas.event; + this._onRequestRedraw = this._register(new _sharedExports.Emitter()); + this.onRequestRedraw = this._onRequestRedraw.event; + this._onContextLoss = this._register(new _sharedExports.Emitter()); + this.onContextLoss = this._onContextLoss.event; this._register(this._themeService.onChangeColors(() => this._handleColorChange())); @@ -83,7 +100,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._core = (this._terminal as any)._core; this._renderLayers = [ - new LinkRenderLayer(this._core.screenElement!, 2, this._terminal, this._core.linkifier!, this._coreBrowserService, _optionsService, this._themeService) + new LinkRenderLayer(_sharedExports, this._core.screenElement!, 2, this._terminal, this._core.linkifier!, this._coreBrowserService, _optionsService, this._themeService) ]; this.dimensions = createRenderDimensions(); this._devicePixelRatio = this._coreBrowserService.dpr; @@ -128,9 +145,9 @@ export class WebglRenderer extends Disposable implements IRenderer { this._requestRedrawViewport(); })); - this._observerDisposable.value = observeDevicePixelDimensions(this._canvas, this._coreBrowserService.window, (w, h) => this._setCanvasDevicePixelDimensions(w, h)); + this._observerDisposable.value = observeDevicePixelDimensions(this._sharedExports, this._canvas, this._coreBrowserService.window, (w, h) => this._setCanvasDevicePixelDimensions(w, h)); this._register(this._coreBrowserService.onWindowChange(w => { - this._observerDisposable.value = observeDevicePixelDimensions(this._canvas, w, (w, h) => this._setCanvasDevicePixelDimensions(w, h)); + this._observerDisposable.value = observeDevicePixelDimensions(this._sharedExports, this._canvas, w, (w, h) => this._setCanvasDevicePixelDimensions(w, h)); })); this._core.screenElement!.appendChild(this._canvas); @@ -139,7 +156,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._isAttached = this._coreBrowserService.window.document.body.contains(this._core.screenElement!); - this._register(toDisposable(() => { + this._register(_sharedExports.toDisposable(() => { for (const l of this._renderLayers) { l.dispose(); } @@ -248,8 +265,8 @@ export class WebglRenderer extends Disposable implements IRenderer { * Initializes members dependent on WebGL context state. */ private _initializeWebGLState(): [RectangleRenderer, GlyphRenderer] { - this._rectangleRenderer.value = new RectangleRenderer(this._terminal, this._gl, this.dimensions, this._themeService); - this._glyphRenderer.value = new GlyphRenderer(this._terminal, this._gl, this.dimensions, this._optionsService); + this._rectangleRenderer.value = new RectangleRenderer(this._sharedExports, this._terminal, this._gl, this.dimensions, this._themeService); + this._glyphRenderer.value = new GlyphRenderer(this._sharedExports, this._terminal, this._gl, this.dimensions, this._optionsService); // Update dimensions and acquire char atlas this.handleCharSizeChanged(); @@ -268,6 +285,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } const atlas = acquireTextureAtlas( + this._sharedExports, this._terminal, this._optionsService.rawOptions, this._themeService.colors, @@ -280,10 +298,14 @@ export class WebglRenderer extends Disposable implements IRenderer { ); if (this._charAtlas !== atlas) { this._onChangeTextureAtlas.fire(atlas.pages[0].canvas); - this._charAtlasDisposable.value = combinedDisposable( - Event.forward(atlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas), - Event.forward(atlas.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas) - ); + const disposables = [ + forwardEvent(atlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas), + forwardEvent(atlas.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas) + ]; + this._charAtlasDisposable.value = this._sharedExports.toDisposable(() => { + for (let i = 0; i < disposables.length; ++i) disposables[i].dispose(); + disposables.length = 0; + }); } this._charAtlas = atlas; this._charAtlas.warmUp(); @@ -675,3 +697,42 @@ export class JoinedCellData extends AttributeData implements ICellData { function clamp(value: number, max: number, min: number = 0): number { return Math.max(Math.min(value, max), min); } + + + + +/** borrowed from vs */ +class DomListener implements IDisposable { + private _handler: (e: any) => void; + private _node: EventTarget; + private readonly _type: string; + private readonly _options: boolean | AddEventListenerOptions; + + constructor(node: EventTarget, type: string, handler: (e: any) => void, options?: boolean | AddEventListenerOptions) { + this._node = node; + this._type = type; + this._handler = handler; + this._options = (options || false); + this._node.addEventListener(this._type, this._handler, this._options); + } + + dispose(): void { + if (!this._handler) { + // Already disposed + return; + } + + this._node.removeEventListener(this._type, this._handler, this._options); + + // Prevent leakers from holding on to the dom or handler func + this._node = null!; + this._handler = null!; + } +} + +export function addDisposableListener(node: EventTarget, type: K, handler: (event: GlobalEventHandlersEventMap[K]) => void, useCapture?: boolean): IDisposable; +export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable; +export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, options: AddEventListenerOptions): IDisposable; +export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCaptureOrOptions?: boolean | AddEventListenerOptions): IDisposable { + return new DomListener(node, type, handler, useCaptureOrOptions); +} diff --git a/addons/addon-webgl/src/WebglUtils.ts b/addons/addon-webgl/src/WebglUtils.ts index da765c8020..f721f9414a 100644 --- a/addons/addon-webgl/src/WebglUtils.ts +++ b/addons/addon-webgl/src/WebglUtils.ts @@ -3,6 +3,7 @@ * @license MIT */ +import { IDisposable, IEmitter, IEvent } from '@xterm/xterm'; import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; /** @@ -61,3 +62,7 @@ export class GLTexture { this.version = -1; } } + +export function forwardEvent(from: IEvent, to: IEmitter): IDisposable { + return from(e => to.fire(e)); +} diff --git a/addons/addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/addon-webgl/src/renderLayer/BaseRenderLayer.ts index 85b170e76c..35cb746551 100644 --- a/addons/addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -7,16 +7,17 @@ import { ReadonlyColorSet } from 'browser/Types'; import { acquireTextureAtlas } from '../CharAtlasCache'; import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; -import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { CellData } from 'common/buffer/CellData'; import { IOptionsService } from 'common/services/Services'; -import { Terminal } from '@xterm/xterm'; +import { ISharedExports, Terminal } from '@xterm/xterm'; import { IRenderLayer } from './Types'; import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { TEXT_BASELINE } from '../Constants'; import type { ITextureAtlas } from '../Types'; +import { AddonDisposable } from 'common/shared/AddonDisposable'; -export abstract class BaseRenderLayer extends Disposable implements IRenderLayer { + +export abstract class BaseRenderLayer extends AddonDisposable implements IRenderLayer { private _canvas: HTMLCanvasElement; protected _ctx!: CanvasRenderingContext2D; private _deviceCharWidth: number = 0; @@ -29,6 +30,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer protected _charAtlas: ITextureAtlas | undefined; constructor( + private _sharedExports: ISharedExports, terminal: Terminal, private _container: HTMLElement, id: string, @@ -38,7 +40,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer protected readonly _optionsService: IOptionsService, protected readonly _themeService: IThemeService ) { - super(); + super(_sharedExports); this._canvas = this._coreBrowserService.mainDocument.createElement('canvas'); this._canvas.classList.add(`xterm-${id}-layer`); this._canvas.style.zIndex = zIndex.toString(); @@ -48,7 +50,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer this._refreshCharAtlas(terminal, e); this.reset(terminal); })); - this._register(toDisposable(() => { + this._register(_sharedExports.toDisposable(() => { this._canvas.remove(); })); } @@ -96,7 +98,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer return; } - this._charAtlas = acquireTextureAtlas(terminal, this._optionsService.rawOptions, colorSet, this._deviceCellWidth, this._deviceCellHeight, this._deviceCharWidth, this._deviceCharHeight, this._coreBrowserService.dpr, 2048); + this._charAtlas = acquireTextureAtlas(this._sharedExports, terminal, this._optionsService.rawOptions, colorSet, this._deviceCellWidth, this._deviceCellHeight, this._deviceCharWidth, this._deviceCharHeight, this._coreBrowserService.dpr, 2048); this._charAtlas.warmUp(); } diff --git a/addons/addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/addon-webgl/src/renderLayer/LinkRenderLayer.ts index faceac44fa..3a97f85756 100644 --- a/addons/addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -9,13 +9,14 @@ import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { ILinkifier2, ILinkifierEvent } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; -import { Terminal } from '@xterm/xterm'; +import { ISharedExports, Terminal } from '@xterm/xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; export class LinkRenderLayer extends BaseRenderLayer { private _state: ILinkifierEvent | undefined; constructor( + sharedExports: ISharedExports, container: HTMLElement, zIndex: number, terminal: Terminal, @@ -24,7 +25,7 @@ export class LinkRenderLayer extends BaseRenderLayer { optionsService: IOptionsService, themeService: IThemeService ) { - super(terminal, container, 'link', zIndex, true, coreBrowserService, optionsService, themeService); + super(sharedExports, terminal, container, 'link', zIndex, true, coreBrowserService, optionsService, themeService); this._register(linkifier2.onShowLinkUnderline(e => this._handleShowLinkUnderline(e))); this._register(linkifier2.onHideLinkUnderline(e => this._handleHideLinkUnderline(e))); diff --git a/addons/addon-webgl/test/WebglRenderer.test.ts b/addons/addon-webgl/test/WebglRenderer.test.ts index af61992ced..f414c697e1 100644 --- a/addons/addon-webgl/test/WebglRenderer.test.ts +++ b/addons/addon-webgl/test/WebglRenderer.test.ts @@ -15,7 +15,7 @@ test.beforeAll(async ({ browser }) => { await openTerminal(ctx); ctxWrapper.value = ctx; await ctx.page.evaluate(` - window.addon = new window.WebglAddon(true); + window.addon = new window.WebglAddon(sharedExports, true); window.term.loadAddon(window.addon); `); }); @@ -31,7 +31,7 @@ test.describe('WebGL Renderer Integration Tests', async () => { injectSharedRendererTests(ctxWrapper); injectSharedRendererTestsStandalone(ctxWrapper, async () => { await ctx.page.evaluate(` - window.addon = new window.WebglAddon(true); + window.addon = new window.WebglAddon(sharedExports, true); window.term.loadAddon(window.addon); `); }); diff --git a/addons/addon-webgl/typings/addon-webgl.d.ts b/addons/addon-webgl/typings/addon-webgl.d.ts index 1d7e1510c7..d9940af30b 100644 --- a/addons/addon-webgl/typings/addon-webgl.d.ts +++ b/addons/addon-webgl/typings/addon-webgl.d.ts @@ -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-webgl' { /** @@ -32,7 +32,7 @@ declare module '@xterm/addon-webgl' { */ public readonly onRemoveTextureAtlasCanvas: IEvent; - constructor(preserveDrawingBuffer?: boolean); + constructor(sharedExports: ISharedExports, preserveDrawingBuffer?: boolean); /** * Activates the addon. diff --git a/demo/client.ts b/demo/client.ts index 5802c9355a..9edcf0c152 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -285,7 +285,7 @@ function createTerminal(): void { addons.unicodeGraphemes.instance = new UnicodeGraphemesAddon(); addons.clipboard.instance = new ClipboardAddon(); try { // try to start with webgl renderer (might throw on older safari/webkit) - addons.webgl.instance = new WebglAddon(); + addons.webgl.instance = new WebglAddon(sharedExports); } catch (e) { console.warn(e); } @@ -696,7 +696,7 @@ function initAddons(term: Terminal): void { if (addons.webgl.instance) { preDisposeWebgl(); addons.webgl.instance.dispose(); - addons.webgl.instance = new addons.webgl.ctor(); + addons.webgl.instance = new addons.webgl.ctor(sharedExports); term.loadAddon(addons.webgl.instance); postInitWebgl(); } From 883483ba9ecbde2ad3997bef3871a943fbd41dfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 12 Jan 2025 01:01:10 +0100 Subject: [PATCH 16/16] make linter happy --- addons/addon-webgl/src/WebglAddon.ts | 2 +- addons/addon-webgl/src/WebglRenderer.ts | 52 ++++++++++++------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/addons/addon-webgl/src/WebglAddon.ts b/addons/addon-webgl/src/WebglAddon.ts index 65b1e7596d..13e748a41d 100644 --- a/addons/addon-webgl/src/WebglAddon.ts +++ b/addons/addon-webgl/src/WebglAddon.ts @@ -3,7 +3,7 @@ * @license MIT */ -import type { ISharedExports, ITerminalAddon, Terminal, IEmitter, IEvent, IDisposable } from '@xterm/xterm'; +import type { ISharedExports, ITerminalAddon, Terminal, IEmitter, IEvent } from '@xterm/xterm'; import type { WebglAddon as IWebglApi } from '@xterm/addon-webgl'; import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services'; import { ITerminal } from 'browser/Types'; diff --git a/addons/addon-webgl/src/WebglRenderer.ts b/addons/addon-webgl/src/WebglRenderer.ts index 288570027c..6c73635a53 100644 --- a/addons/addon-webgl/src/WebglRenderer.ts +++ b/addons/addon-webgl/src/WebglRenderer.ts @@ -703,36 +703,36 @@ function clamp(value: number, max: number, min: number = 0): number { /** borrowed from vs */ class DomListener implements IDisposable { - private _handler: (e: any) => void; - private _node: EventTarget; - private readonly _type: string; - private readonly _options: boolean | AddEventListenerOptions; - - constructor(node: EventTarget, type: string, handler: (e: any) => void, options?: boolean | AddEventListenerOptions) { - this._node = node; - this._type = type; - this._handler = handler; - this._options = (options || false); - this._node.addEventListener(this._type, this._handler, this._options); - } - - dispose(): void { - if (!this._handler) { - // Already disposed - return; - } - - this._node.removeEventListener(this._type, this._handler, this._options); - - // Prevent leakers from holding on to the dom or handler func - this._node = null!; - this._handler = null!; - } + private _handler: (e: any) => void; + private _node: EventTarget; + private readonly _type: string; + private readonly _options: boolean | AddEventListenerOptions; + + constructor(node: EventTarget, type: string, handler: (e: any) => void, options?: boolean | AddEventListenerOptions) { + this._node = node; + this._type = type; + this._handler = handler; + this._options = (options || false); + this._node.addEventListener(this._type, this._handler, this._options); + } + + public dispose(): void { + if (!this._handler) { + // Already disposed + return; + } + + this._node.removeEventListener(this._type, this._handler, this._options); + + // Prevent leakers from holding on to the dom or handler func + this._node = null!; + this._handler = null!; + } } export function addDisposableListener(node: EventTarget, type: K, handler: (event: GlobalEventHandlersEventMap[K]) => void, useCapture?: boolean): IDisposable; export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable; export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, options: AddEventListenerOptions): IDisposable; export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCaptureOrOptions?: boolean | AddEventListenerOptions): IDisposable { - return new DomListener(node, type, handler, useCaptureOrOptions); + return new DomListener(node, type, handler, useCaptureOrOptions); }