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] 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: {