diff --git a/demo/client.ts b/demo/client.ts index f6a70f0c..f91c535f 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -8,12 +8,11 @@ /// import { Terminal } from '../lib/public/Terminal'; -import * as attach from '../lib/addons/attach/attach'; +import { AttachAddon } from 'xterm-addon-attach'; +import { SearchAddon, ISearchOptions } from 'xterm-addon-search'; +import { WebLinksAddon } from 'xterm-addon-web-links'; + import * as fit from '../lib/addons/fit/fit'; -import * as fullscreen from '../lib/addons/fullscreen/fullscreen'; -import * as search from '../lib/addons/search/search'; -import * as webLinks from '../lib/addons/webLinks/webLinks'; -import { ISearchOptions } from '../lib/addons/search/Interfaces'; // Pulling in the module's types relies on the above, it's looks a // little weird here as we're importing "this" module @@ -25,14 +24,11 @@ export interface IWindowWithTerminal extends Window { } declare let window: IWindowWithTerminal; -Terminal.applyAddon(attach); Terminal.applyAddon(fit); -Terminal.applyAddon(fullscreen); -Terminal.applyAddon(search); -Terminal.applyAddon(webLinks); - let term; +let attachAddon: AttachAddon; +let searchAddon: SearchAddon; let protocol; let socketURL; let socket; @@ -85,10 +81,20 @@ function createTerminal(): void { while (terminalContainer.children.length) { terminalContainer.removeChild(terminalContainer.children[0]); } + const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0; term = new Terminal({ windowsMode: isWindows } as ITerminalOptions); + + // Load addons + const typedTerm = term as TerminalType; + typedTerm.loadAddon(new WebLinksAddon()); + attachAddon = new AttachAddon(); + typedTerm.loadAddon(attachAddon); + searchAddon = new SearchAddon(); + typedTerm.loadAddon(searchAddon); + window.term = term; // Expose `term` to window for debugging purposes term.onResize((size: { cols: number, rows: number }) => { if (!pid) { @@ -104,8 +110,6 @@ function createTerminal(): void { socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/'; term.open(terminalContainer); - - term.webLinksInit(); term.fit(); term.focus(); @@ -114,12 +118,12 @@ function createTerminal(): void { addDomListener(actionElements.findNext, 'keyup', (e) => { const searchOptions = getSearchOptions(); searchOptions.incremental = e.key !== `Enter`; - term.findNext(actionElements.findNext.value, searchOptions); + searchAddon.findNext(actionElements.findNext.value, searchOptions); }); addDomListener(actionElements.findPrevious, 'keyup', (e) => { if (e.key === `Enter`) { - term.findPrevious(actionElements.findPrevious.value, getSearchOptions()); + searchAddon.findPrevious(actionElements.findPrevious.value, getSearchOptions()); } }); @@ -149,7 +153,7 @@ function createTerminal(): void { } function runRealTerminal(): void { - term.attach(socket); + attachAddon.attach(socket); term._initialized = true; } diff --git a/demo/index.html b/demo/index.html index 370a51ed..a7ab0f0c 100644 --- a/demo/index.html +++ b/demo/index.html @@ -3,7 +3,6 @@ xterm.js demo - @@ -16,7 +15,7 @@

- +

diff --git a/demo/start.js b/demo/start.js index 278c572f..7e13e790 100644 --- a/demo/start.js +++ b/demo/start.js @@ -25,11 +25,13 @@ const clientConfig = { { test: /\.js$/, use: ["source-map-loader"], - enforce: "pre" + enforce: "pre", + exclude: /node_modules/ } ] }, resolve: { + modules: [path.resolve(__dirname, '..'), 'node_modules'], extensions: [ '.tsx', '.ts', '.js' ] }, output: { diff --git a/package.json b/package.json index 63fd07ca..cafa43b1 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,9 @@ "vinyl-source-stream": "^1.1.0", "webpack": "^4.17.1", "webpack-cli": "^3.1.0", + "xterm-addon-attach": "0.1.0-beta7", + "xterm-addon-search": "0.1.0-beta4", + "xterm-addon-web-links": "0.1.0-beta6", "zmodem.js": "^0.1.5" }, "scripts": { diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 9b5fa9c8..5d54e6f2 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -16,7 +16,7 @@ class TestTerminal extends Terminal { public keyPress(ev: any): boolean { return this._keyPress(ev); } } -describe('xterm.js', () => { +describe('Terminal', () => { let term: TestTerminal; const termOptions = { cols: INIT_COLS, diff --git a/src/Types.ts b/src/Types.ts index f57385f0..716daa5d 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -221,55 +221,56 @@ export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAcc showCursor(): void; } +// Portions of the public API that are required by the internal Terminal export interface IPublicTerminal extends IDisposable, IEventEmitter { - textarea: HTMLTextAreaElement; - rows: number; - cols: number; - buffer: IBuffer; - markers: IMarker[]; - onCursorMove: IEvent; - onData: IEvent; - onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>; - onLineFeed: IEvent; - onScroll: IEvent; - onSelectionChange: IEvent; - onRender: IEvent<{ start: number, end: number }>; - onResize: IEvent<{ cols: number, rows: number }>; - onTitleChange: IEvent; - blur(): void; - focus(): void; - resize(columns: number, rows: number): void; - writeln(data: string): void; - open(parent: HTMLElement): void; - attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void; - addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable; - addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; - registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number; - deregisterLinkMatcher(matcherId: number): void; - registerCharacterJoiner(handler: (text: string) => [number, number][]): number; - deregisterCharacterJoiner(joinerId: number): void; - addMarker(cursorYOffset: number): IMarker; - hasSelection(): boolean; - getSelection(): string; - getSelectionPosition(): ISelectionPosition | undefined; - clearSelection(): void; - select(column: number, row: number, length: number): void; - selectAll(): void; - selectLines(start: number, end: number): void; - dispose(): void; - destroy(): void; - scrollLines(amount: number): void; - scrollPages(pageCount: number): void; - scrollToTop(): void; - scrollToBottom(): void; - scrollToLine(line: number): void; - clear(): void; - write(data: string): void; - writeUtf8(data: Uint8Array): void; - getOption(key: string): any; - setOption(key: string, value: any): void; - refresh(start: number, end: number): void; - reset(): void; + textarea: HTMLTextAreaElement; + rows: number; + cols: number; + buffer: IBuffer; + markers: IMarker[]; + onCursorMove: IEvent; + onData: IEvent; + onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>; + onLineFeed: IEvent; + onScroll: IEvent; + onSelectionChange: IEvent; + onRender: IEvent<{ start: number, end: number }>; + onResize: IEvent<{ cols: number, rows: number }>; + onTitleChange: IEvent; + blur(): void; + focus(): void; + resize(columns: number, rows: number): void; + writeln(data: string): void; + open(parent: HTMLElement): void; + attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void; + addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable; + addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; + registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number; + deregisterLinkMatcher(matcherId: number): void; + registerCharacterJoiner(handler: (text: string) => [number, number][]): number; + deregisterCharacterJoiner(joinerId: number): void; + addMarker(cursorYOffset: number): IMarker; + hasSelection(): boolean; + getSelection(): string; + getSelectionPosition(): ISelectionPosition | undefined; + clearSelection(): void; + select(column: number, row: number, length: number): void; + selectAll(): void; + selectLines(start: number, end: number): void; + dispose(): void; + destroy(): void; + scrollLines(amount: number): void; + scrollPages(pageCount: number): void; + scrollToTop(): void; + scrollToBottom(): void; + scrollToLine(line: number): void; + clear(): void; + write(data: string): void; + writeUtf8(data: Uint8Array): void; + getOption(key: string): any; + setOption(key: string, value: any): void; + refresh(start: number, end: number): void; + reset(): void; } export interface IBufferAccessor { diff --git a/src/addons/search/Interfaces.ts b/src/addons/search/Interfaces.ts index 27e3c4b3..872537ea 100644 --- a/src/addons/search/Interfaces.ts +++ b/src/addons/search/Interfaces.ts @@ -5,14 +5,8 @@ import { Terminal } from 'xterm'; -// TODO: Don't rely on this private API -export interface ITerminalCore { - buffer: any; -} - export interface ISearchAddonTerminal extends Terminal { __searchHelper?: ISearchHelper; - _core: ITerminalCore; } export interface ISearchHelper { diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index c42167fa..e23b14a1 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -44,7 +44,7 @@ export class SearchHelper implements ISearchHelper { } let startCol: number = 0; - let startRow = this._terminal._core.buffer.ydisp; + let startRow = this._terminal.buffer.viewportY; if (this._terminal.hasSelection()) { // Start from the selection end if there is a selection @@ -62,7 +62,7 @@ export class SearchHelper implements ISearchHelper { let cumulativeCols = startCol; // If startRow is wrapped row, scan for unwrapped row above. // So we can start matching on wrapped line from long unwrapped line. - while (this._terminal._core.buffer.lines.get(findingRow).isWrapped) { + while (this._terminal.buffer.getLine(findingRow).isWrapped) { findingRow--; cumulativeCols += this._terminal.cols; } @@ -73,7 +73,7 @@ export class SearchHelper implements ISearchHelper { // Search from startRow + 1 to end if (!result) { - for (let y = startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) { + for (let y = startRow + 1; y < this._terminal.buffer.baseY + this._terminal.rows; y++) { // If the current line is wrapped line, increase index of column to ignore the previous scan // Otherwise, reset beginning column index to zero with set new unwrapped line index @@ -115,7 +115,7 @@ export class SearchHelper implements ISearchHelper { } const isReverseSearch = true; - let startRow = this._terminal._core.buffer.ydisp + this._terminal.rows - 1; + let startRow = this._terminal.buffer.viewportY + this._terminal.rows - 1; let startCol = this._terminal.cols; if (this._terminal.hasSelection()) { @@ -135,7 +135,7 @@ export class SearchHelper implements ISearchHelper { // If the line is wrapped line, increase number of columns that is needed to be scanned // Se we can scan on wrapped line from unwrapped line let cumulativeCols = this._terminal.cols; - if (this._terminal._core.buffer.lines.get(startRow).isWrapped) { + if (this._terminal.buffer.getLine(startRow).isWrapped) { cumulativeCols += startCol; } for (let y = startRow - 1; y >= 0; y--) { @@ -145,7 +145,7 @@ export class SearchHelper implements ISearchHelper { } // If the current line is wrapped line, increase scanning range, // preparing for scanning on unwrapped line - if (this._terminal._core.buffer.lines.get(y).isWrapped) { + if (this._terminal.buffer.getLine(y).isWrapped) { cumulativeCols += this._terminal.cols; } else { cumulativeCols = this._terminal.cols; @@ -156,14 +156,14 @@ export class SearchHelper implements ISearchHelper { // Search from the bottom to startRow (search the whole startRow again in // case startCol > 0) if (!result) { - const searchFrom = this._terminal._core.buffer.ybase + this._terminal.rows - 1; + const searchFrom = this._terminal.buffer.baseY + this._terminal.rows - 1; let cumulativeCols = this._terminal.cols; for (let y = searchFrom; y >= startRow; y--) { result = this._findInLine(term, y, cumulativeCols, searchOptions, isReverseSearch); if (result) { break; } - if (this._terminal._core.buffer.lines.get(y).isWrapped) { + if (this._terminal.buffer.getLine(y).isWrapped) { cumulativeCols += this._terminal.cols; } else { cumulativeCols = this._terminal.cols; @@ -180,7 +180,7 @@ export class SearchHelper implements ISearchHelper { */ private _initLinesCache(): void { if (!this._linesCache) { - this._linesCache = new Array(this._terminal._core.buffer.length); + this._linesCache = new Array(this._terminal.buffer.length); this._cursorMoveListener = this._terminal.onCursorMove(() => this._destroyLinesCache()); this._resizeListener = this._terminal.onResize(() => this._destroyLinesCache()); } @@ -230,7 +230,7 @@ export class SearchHelper implements ISearchHelper { protected _findInLine(term: string, row: number, col: number, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult { // Ignore wrapped lines, only consider on unwrapped line (first row of command string). - if (this._terminal._core.buffer.lines.get(row).isWrapped) { + if (this._terminal.buffer.getLine(row).isWrapped) { return; } let stringLine = this._linesCache ? this._linesCache[row] : void 0; @@ -282,18 +282,18 @@ export class SearchHelper implements ISearchHelper { return; } - const line = this._terminal._core.buffer.lines.get(row); + const line = this._terminal.buffer.getLine(row); for (let i = 0; i < resultIndex; i++) { - const charData = line.get(i); + const cell = line.getCell(i); // Adjust the searchIndex to normalize emoji into single chars - const char = charData[1/*CHAR_DATA_CHAR_INDEX*/]; + const char = cell.char; if (char.length > 1) { resultIndex -= char.length - 1; } // Adjust the searchIndex for empty characters following wide unicode // chars (eg. CJK) - const charWidth = charData[2/*CHAR_DATA_WIDTH_INDEX*/]; + const charWidth = cell.width; if (charWidth === 0) { resultIndex++; } @@ -318,9 +318,9 @@ export class SearchHelper implements ISearchHelper { let lineWrapsToNext: boolean; do { - const nextLine = this._terminal._core.buffer.lines.get(lineIndex + 1); + const nextLine = this._terminal.buffer.getLine(lineIndex + 1); lineWrapsToNext = nextLine ? nextLine.isWrapped : false; - lineString += this._terminal._core.buffer.translateBufferLineToString(lineIndex, !lineWrapsToNext && trimRight).substring(0, this._terminal.cols); + lineString += this._terminal.buffer.getLine(lineIndex).translateToString(!lineWrapsToNext && trimRight).substring(0, this._terminal.cols); lineIndex++; } while (lineWrapsToNext); @@ -338,7 +338,7 @@ export class SearchHelper implements ISearchHelper { return false; } this._terminal.select(result.col, result.row, result.term.length); - this._terminal.scrollLines(result.row - this._terminal._core.buffer.ydisp); + this._terminal.scrollLines(result.row - this._terminal.buffer.viewportY); return true; } } diff --git a/src/addons/search/search.test.ts b/src/addons/search/search.test.ts index 6551fa8b..aee88f50 100644 --- a/src/addons/search/search.test.ts +++ b/src/addons/search/search.test.ts @@ -23,11 +23,56 @@ class MockTerminal { get core(): any { return this._core; } + get buffer(): IBuffer { + // TODO: This is a hacky workaround until we use puppeteer for addon tests + const buffer = this._core.buffer; + return { + cursorY: buffer.y, + cursorX: buffer.x, + viewportY: buffer.ydisp, + baseY: buffer.ybase, + length: buffer.length, + getLine(y: number): IBufferLine { + return { + isWrapped: buffer.lines.get(y) ? buffer.lines.get(y).isWrapped : false, + getCell(x: number): IBufferCell { + return { + char: buffer.lines.get(y).get(x)[1/*CHAR_DATA_CHAR_INDEX*/], + width: buffer.lines.get(y).get(x)[2/*CHAR_DATA_WIDTH_INDEX*/] + }; + }, + translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string { + return buffer.translateBufferLineToString(y, trimRight); + } + }; + } + }; + } pushWriteData(): void { this._core._innerWrite(); } } +interface IBuffer { + readonly cursorY: number; + readonly cursorX: number; + readonly viewportY: number; + readonly baseY: number; + readonly length: number; + getLine(y: number): IBufferLine | undefined; +} + +interface IBufferLine { + readonly isWrapped: boolean; + getCell(x: number): IBufferCell; + translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string; +} + +interface IBufferCell { + readonly char: string; + readonly width: number; +} + class TestSearchHelper extends SearchHelper { public findInLine(term: string, rowNumber: number, searchOptions?: ISearchOptions): ISearchResult { return this._findInLine(term, rowNumber, 0, searchOptions); diff --git a/src/public/AddonManager.test.ts b/src/public/AddonManager.test.ts new file mode 100644 index 00000000..8198ce31 --- /dev/null +++ b/src/public/AddonManager.test.ts @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { AddonManager, ILoadedAddon } from './AddonManager'; +import { ITerminalAddon } from 'xterm'; + +class TestAddonManager extends AddonManager { + public get addons(): ILoadedAddon[] { + return this._addons; + } +} + +describe('AddonManager', () => { + let manager: TestAddonManager; + + beforeEach(() => { + manager = new TestAddonManager(); + }); + + describe('loadAddon', () => { + it('should call addon constructor', () => { + let called = false; + class Addon implements ITerminalAddon { + activate(terminal: any): void { + assert.equal(terminal, 'foo', 'The first constructor arg should be Terminal'); + called = true; + } + dispose(): void { } + } + manager.loadAddon('foo' as any, new Addon()); + assert.equal(called, true); + }); + }); + + describe('dispose', () => { + it('should dispose all loaded addons', () => { + let called = 0; + class Addon implements ITerminalAddon { + activate(): void {} + dispose(): void { called++; } + } + 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); + assert.equal(manager.addons.length, 0); + }); + }); +}); diff --git a/src/public/AddonManager.ts b/src/public/AddonManager.ts new file mode 100644 index 00000000..b5506514 --- /dev/null +++ b/src/public/AddonManager.ts @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ITerminalAddon, IDisposable, Terminal } from 'xterm'; + +export interface ILoadedAddon { + instance: ITerminalAddon; + dispose: () => void; + isDisposed: boolean; +} + +export class AddonManager implements IDisposable { + protected _addons: ILoadedAddon[] = []; + + constructor() { + } + + public dispose(): void { + for (let i = this._addons.length - 1; i >= 0; i--) { + this._addons[i].instance.dispose(); + } + } + + public loadAddon(terminal: Terminal, instance: ITerminalAddon): void { + const loadedAddon: ILoadedAddon = { + instance, + dispose: instance.dispose, + isDisposed: false + }; + this._addons.push(loadedAddon); + instance.dispose = () => this._wrappedAddonDispose(loadedAddon); + 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] === loadedAddon) { + index = i; + break; + } + } + if (index === -1) { + throw new Error('Could not dispose an addon that has not been loaded'); + } + loadedAddon.isDisposed = true; + loadedAddon.dispose(); + this._addons.splice(index, 1); + } +} diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 1a4a5108..2ad5313e 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -135,6 +135,52 @@ describe('API Integration Tests', () => { assert.equal(await page.evaluate(`document.activeElement.className`), ''); }); + describe('loadAddon', () => { + it('constructor', async function(): Promise { + this.timeout(10000); + await openTerminal({ cols: 5 }); + await page.evaluate(` + window.cols = 0; + window.term.loadAddon({ + activate: (t) => window.cols = t.cols, + dispose: () => {} + }); + `); + assert.equal(await page.evaluate(`window.cols`), 5); + }); + + it('dispose (addon)', async function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + window.disposeCalled = false + window.addon = { + activate: () => {}, + dispose: () => window.disposeCalled = true + }; + window.term.loadAddon(window.addon); + `); + assert.equal(await page.evaluate(`window.disposeCalled`), false); + await page.evaluate(`window.addon.dispose()`); + assert.equal(await page.evaluate(`window.disposeCalled`), true); + }); + + it('dispose (terminal)', async function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + window.disposeCalled = false + window.term.loadAddon({ + activate: () => {}, + dispose: () => window.disposeCalled = true + }); + `); + assert.equal(await page.evaluate(`window.disposeCalled`), false); + await page.evaluate(`window.term.dispose()`); + assert.equal(await page.evaluate(`window.disposeCalled`), true); + }); + }); + describe('Events', () => { it('onCursorMove', async function(): Promise { this.timeout(10000); diff --git a/src/public/Terminal.test.ts b/src/public/Terminal.test.ts index 06c8f1d5..6bad5b04 100644 --- a/src/public/Terminal.test.ts +++ b/src/public/Terminal.test.ts @@ -7,7 +7,7 @@ import { assert } from 'chai'; import { Terminal } from './Terminal'; import * as attach from '../addons/attach/attach'; -describe('Terminal', () => { + describe('Terminal', () => { it('should apply addons with Terminal.applyAddon', () => { Terminal.applyAddon(attach); // Test that addon was applied successfully, adding attach to Terminal's diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 4d48bf79..f1874a3f 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -3,18 +3,21 @@ * @license MIT */ -import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, ISelectionPosition } from 'xterm'; +import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm'; import { ITerminal, IBuffer } from '../Types'; import { IBufferLine } from '../core/Types'; import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../Strings'; import { IEvent } from '../common/EventEmitter2'; +import { AddonManager } from './AddonManager'; export class Terminal implements ITerminalApi { private _core: ITerminal; + private _addonManager: AddonManager; constructor(options?: ITerminalOptions) { this._core = new TerminalCore(options); + this._addonManager = new AddonManager(); } public get onCursorMove(): IEvent { return this._core.onCursorMove; } @@ -115,6 +118,7 @@ export class Terminal implements ITerminalApi { this._core.selectLines(start, end); } public dispose(): void { + this._addonManager.dispose(); this._core.dispose(); } public destroy(): void { @@ -176,6 +180,9 @@ export class Terminal implements ITerminalApi { public static applyAddon(addon: any): void { addon.apply(Terminal); } + public loadAddon(addon: ITerminalAddon): void { + return this._addonManager.loadAddon(this, addon); + } public static get strings(): ILocalizableStrings { return Strings; } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 9afac941..097fd2d2 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -881,8 +881,25 @@ declare module 'xterm' { * Applies an addon to the Terminal prototype, making it available to all * newly created Terminals. * @param addon The addon to apply. + * @deprecated Use the new loadAddon API/addon format. */ static applyAddon(addon: any): void; + + /** + * (EXPERIMENTAL) Loads an addon into this instance of xterm.js. + * @param addon The addon to load. + */ + loadAddon(addon: ITerminalAddon): void; + } + + /** + * An addon that can provide additional functionality to the terminal. + */ + export interface ITerminalAddon extends IDisposable { + /** + * (EXPERIMENTAL) This is called when the addon is activated within xterm.js. + */ + activate(terminal: Terminal): void; } /** diff --git a/yarn.lock b/yarn.lock index 87ca4a18..3db8e9a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7236,6 +7236,21 @@ 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-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-search@0.1.0-beta4: + version "0.1.0-beta4" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.1.0-beta4.tgz#c73fe058c87f07eaae31baaa92976e927438a396" + integrity sha512-tJgZ1VTRd/DOFUhSFZzybRF8SR1LCEXRYkw/mHzGV5Ba3zhqVdSkN/0J9sjOpX6u21buee2OmTiCMZxq80zfJg== + +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" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"