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"