diff --git a/demo/client.ts b/demo/client.ts index 3d55bc00..f4a23f52 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -88,8 +88,9 @@ function createTerminal(): void { // Load addons const typedTerm = term as TerminalType; - typedTerm.loadAddon(WebLinksAddon).init(); - attachAddon = typedTerm.loadAddon(AttachAddon); + typedTerm.loadAddon(new WebLinksAddon()); + attachAddon = new AttachAddon(); + typedTerm.loadAddon(attachAddon); window.term = term; // Expose `term` to window for debugging purposes term.on('resize', (size: { cols: number, rows: number }) => { diff --git a/demo/start.js b/demo/start.js index 2e2186dc..7e13e790 100644 --- a/demo/start.js +++ b/demo/start.js @@ -31,6 +31,7 @@ const clientConfig = { ] }, resolve: { + modules: [path.resolve(__dirname, '..'), 'node_modules'], extensions: [ '.tsx', '.ts', '.js' ] }, output: { diff --git a/package.json b/package.json index 438e3e1b..b1f8e8f4 100644 --- a/package.json +++ b/package.json @@ -42,8 +42,8 @@ "vinyl-source-stream": "^1.1.0", "webpack": "^4.17.1", "webpack-cli": "^3.1.0", - "xterm-addon-attach": "0.1.0-beta4", - "xterm-addon-web-links": "0.1.0-beta3", + "xterm-addon-attach": "0.1.0-beta7", + "xterm-addon-web-links": "0.1.0-beta6", "zmodem.js": "^0.1.5" }, "scripts": { diff --git a/src/Terminal.ts b/src/Terminal.ts index df8f5d90..7a29a282 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -44,7 +44,7 @@ import { DEFAULT_BELL_SOUND, SoundManager } from './SoundManager'; import { MouseZoneManager } from './ui/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ScreenDprMonitor } from './ui/ScreenDprMonitor'; -import { ITheme, IMarker, IDisposable, ITerminalAddon, ITerminalAddonConstructor } from 'xterm'; +import { ITheme, IMarker, IDisposable, ITerminalAddon } from 'xterm'; import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; import { IKeyboardEvent } from './common/Types'; @@ -1925,16 +1925,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // this.options.bellStyle === 'both'; } - public loadAddon(addonConstructor: ITerminalAddonConstructor): T { - return this._addonManager.loadAddon(this, addonConstructor); - } - - public disposeAddon(addonConstructor: ITerminalAddonConstructor): void { - this._addonManager.disposeAddon(addonConstructor); - } - - public getAddon(addonConstructor: ITerminalAddonConstructor): T { - return this._addonManager.getAddon(addonConstructor); + public loadAddon(addon: ITerminalAddon): void { + return this._addonManager.loadAddon(this, addon); } } diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 6f2de10f..77889271 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ITerminalAddonConstructor } from 'xterm'; +import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon } from 'xterm'; import { ITerminal } from '../Types'; import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../Strings'; @@ -154,14 +154,8 @@ export class Terminal implements ITerminalApi { public static applyAddon(addon: any): void { addon.apply(Terminal); } - public loadAddon(addonConstructor: ITerminalAddonConstructor): T { - return this._core.loadAddon(addonConstructor); - } - public getAddon(addonConstructor: ITerminalAddonConstructor): T { - return this._core.getAddon(addonConstructor); - } - public disposeAddon(addonConstructor: ITerminalAddonConstructor): void { - this._core.disposeAddon(addonConstructor); + public loadAddon(addon: ITerminalAddon): void { + return this._core.loadAddon(addon); } public static get strings(): ILocalizableStrings { return Strings; diff --git a/src/ui/AddonManager.test.ts b/src/ui/AddonManager.test.ts index ac83917c..8198ce31 100644 --- a/src/ui/AddonManager.test.ts +++ b/src/ui/AddonManager.test.ts @@ -24,84 +24,27 @@ describe('AddonManager', () => { it('should call addon constructor', () => { let called = false; class Addon implements ITerminalAddon { - constructor(terminal: any) { + activate(terminal: any): void { assert.equal(terminal, 'foo', 'The first constructor arg should be Terminal'); called = true; } dispose(): void { } } - manager.loadAddon('foo' as any, Addon); + manager.loadAddon('foo' as any, new Addon()); assert.equal(called, true); }); }); - describe('getAddon', () => { - it('should fetch registered addons', () => { - class BaseAddon implements ITerminalAddon { - constructor() { } - dispose(): void { } - } - class Addon1 extends BaseAddon { } - class Addon2 extends BaseAddon { } - class Addon3 extends BaseAddon { } - const addon1 = manager.loadAddon(null, Addon1); - assert.equal(manager.getAddon(Addon1), addon1); - assert.equal(manager.addons.length, 1); - const addon2 = manager.loadAddon(null, Addon2); - assert.equal(manager.getAddon(Addon1), addon1); - assert.equal(manager.getAddon(Addon2), addon2); - assert.equal(manager.addons.length, 2); - const addon3 = manager.loadAddon(null, Addon3); - assert.equal(manager.getAddon(Addon1), addon1); - assert.equal(manager.getAddon(Addon2), addon2); - assert.equal(manager.getAddon(Addon3), addon3); - assert.equal(manager.addons.length, 3); - }); - }); - - describe('disposeAddon', () => { - it('should dispose the loaded addon and remove it from the loaded list', () => { - let called = 0; - class BaseAddon implements ITerminalAddon { - constructor() { } - dispose(): void { - called++; - } - } - class Addon1 extends BaseAddon { } - class Addon2 extends BaseAddon { } - class Addon3 extends BaseAddon { } - manager.loadAddon(null, Addon1); - manager.loadAddon(null, Addon2); - manager.loadAddon(null, Addon3); - assert.equal(manager.addons.length, 3); - manager.disposeAddon(Addon1); - assert.equal(called, 1); - assert.equal(manager.addons.length, 2); - manager.disposeAddon(Addon2); - assert.equal(called, 2); - assert.equal(manager.addons.length, 1); - manager.disposeAddon(Addon3); - assert.equal(called, 3); - assert.equal(manager.addons.length, 0); - }); - }); - describe('dispose', () => { it('should dispose all loaded addons', () => { let called = 0; - class BaseAddon implements ITerminalAddon { - constructor() { } - dispose(): void { - called++; - } + class Addon implements ITerminalAddon { + activate(): void {} + dispose(): void { called++; } } - class Addon1 extends BaseAddon { } - class Addon2 extends BaseAddon { } - class Addon3 extends BaseAddon { } - manager.loadAddon(null, Addon1); - manager.loadAddon(null, Addon2); - manager.loadAddon(null, Addon3); + manager.loadAddon(null, new Addon()); + manager.loadAddon(null, new Addon()); + manager.loadAddon(null, new Addon()); assert.equal(manager.addons.length, 3); manager.dispose(); assert.equal(called, 3); diff --git a/src/ui/AddonManager.ts b/src/ui/AddonManager.ts index b55bd6a8..34e7e5dd 100644 --- a/src/ui/AddonManager.ts +++ b/src/ui/AddonManager.ts @@ -3,12 +3,12 @@ * @license MIT */ -import { ITerminalAddon, ITerminalAddonConstructor, IDisposable, Terminal } from 'xterm'; +import { ITerminalAddon, IDisposable, Terminal } from 'xterm'; export interface ILoadedAddon { - ctor: ITerminalAddonConstructor; instance: ITerminalAddon; dispose: () => void; + isDisposed: boolean; } export class AddonManager implements IDisposable { @@ -23,38 +23,25 @@ export class AddonManager implements IDisposable { } } - public loadAddon(terminal: Terminal, addonConstructor: ITerminalAddonConstructor): T { - const instance = new addonConstructor(terminal); + public loadAddon(terminal: Terminal, instance: ITerminalAddon): void { const loadedAddon: ILoadedAddon = { - ctor: addonConstructor, instance, - dispose: instance.dispose + dispose: instance.dispose, + isDisposed: false }; this._addons.push(loadedAddon); instance.dispose = () => this._wrappedAddonDispose(loadedAddon); - return instance; - } - - public disposeAddon(addonConstructor: ITerminalAddonConstructor): void { - const match = this._addons.find(value => value.ctor === addonConstructor); - if (!match) { - throw new Error('Could not dispose an addon that has not been loaded'); - } - match.instance.dispose(); - } - - public getAddon(addonConstructor: ITerminalAddonConstructor): T { - const match = this._addons.find(value => value.ctor === addonConstructor); - if (!match) { - return undefined; - } - return match.instance as T; + instance.activate(terminal); } private _wrappedAddonDispose(loadedAddon: ILoadedAddon): void { + if (loadedAddon.isDisposed) { + // Do nothing if already disposed + return; + } let index = -1; for (let i = 0; i < this._addons.length; i++) { - if (this._addons[i].ctor === loadedAddon.ctor) { + if (this._addons[i] === loadedAddon) { index = i; break; } @@ -63,6 +50,7 @@ export class AddonManager implements IDisposable { throw new Error('Could not dispose an addon that has not been loaded'); } loadedAddon.dispose(); + loadedAddon.isDisposed = true; this._addons.splice(index, 1); } } diff --git a/src/ui/TestUtils.test.ts b/src/ui/TestUtils.test.ts index ec3aa60e..4a3a24e3 100644 --- a/src/ui/TestUtils.test.ts +++ b/src/ui/TestUtils.test.ts @@ -8,7 +8,7 @@ import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuff import { ICircularList, XtermListener } from '../common/Types'; import { Buffer } from '../Buffer'; import * as Browser from '../common/Platform'; -import { ITheme, IDisposable, IMarker, ITerminalAddon, ITerminalAddonConstructor } from 'xterm'; +import { ITheme, IDisposable, IMarker, ITerminalAddon } from 'xterm'; import { Terminal } from '../Terminal'; import { AttributeData } from '../BufferLine'; @@ -20,13 +20,7 @@ export class TestTerminal extends Terminal { } export class MockTerminal implements ITerminal { - loadAddon(addonConstructor: ITerminalAddonConstructor): T { - throw new Error('Method not implemented.'); - } - disposeAddon(addonConstructor: ITerminalAddonConstructor): void { - throw new Error('Method not implemented.'); - } - getAddon(addonConstructor: ITerminalAddonConstructor): T { + loadAddon(addon: ITerminalAddon): void { throw new Error('Method not implemented.'); } markers: IMarker[]; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index fddd8982..830ed298 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -772,22 +772,18 @@ declare module 'xterm' { */ static applyAddon(addon: any): void; - loadAddon(addonConstructor: ITerminalAddonConstructor): T; - disposeAddon(addonConstructor: ITerminalAddonConstructor): void; - getAddon(addonConstructor: ITerminalAddonConstructor): T; - } - - export interface ITerminalAddonConstructor { - new(terminal: Terminal): T; + /** + * Loads an addon into this instance of xterm.js. + * @param addon The addon to load. + */ + loadAddon(addon: ITerminalAddon): void; } export interface ITerminalAddon { /** - * This property declares all addon dependencies that must be intialized - * before this addon can be constructed. For addons with no dependencies - * just don't include this property. + * This is called when the addon is activated within xterm.js. */ - // readonly DEPENDENCIES?: ITerminalAddonConstructor[]; + activate(terminal: Terminal): void; /** * This function includes anything that needs to happen to clean up when diff --git a/yarn.lock b/yarn.lock index 1fa1739c..4ca95193 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7122,15 +7122,15 @@ xregexp@4.0.0: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= -xterm-addon-attach@0.1.0-beta4: - version "0.1.0-beta4" - resolved "https://registry.yarnpkg.com/xterm-addon-attach/-/xterm-addon-attach-0.1.0-beta4.tgz#546010f66533f22bfad7605345e44ed95d90a1de" - integrity sha512-HwxNoNS1Fxoo6+MPZJ+5+sMTQHrZEcptL4qstHlaERqxL7ei/lvKMpSRfIo1eRNqxL4vHzYkNWJO7QLATmdalA== +xterm-addon-attach@0.1.0-beta7: + version "0.1.0-beta7" + resolved "https://registry.yarnpkg.com/xterm-addon-attach/-/xterm-addon-attach-0.1.0-beta7.tgz#787f6cce709611ee08ab731b95a62fa1c0bce6a9" + integrity sha512-nQr6LcYtpZcyDoHyL/BDIPJcTgL7qlHR/rvm8lSizQysGVT0pSzr5M7SjY3kQHw33U3hTer3c6oZzwjfj4ohOw== -xterm-addon-web-links@0.1.0-beta3: - version "0.1.0-beta3" - resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.1.0-beta3.tgz#bd2d45d399340bd1b5bbf44850a0be9c91a1451e" - integrity sha512-nkgwAYZXS97zL650MTl6RnA/iXYAD0yOVf/28+ZTlLQZJVYH2DP22rhOK0aqO+tWX91WUrvkcqxFCY965fomaQ== +xterm-addon-web-links@0.1.0-beta6: + version "0.1.0-beta6" + resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.1.0-beta6.tgz#9b4e862be8928ef455a667745bea479665db6c6b" + integrity sha512-tkVU5wCfBFjXwfOvcbMHoLoMDANztkwSREiKyu2R059kEF+sP67Z33HzxVCXUWFuCmutcx40xR2O0BK68gXZlg== y18n@^3.2.0, y18n@^3.2.1: version "3.2.1"