diff --git a/demo/client.ts b/demo/client.ts index ae628baa..40679477 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -8,11 +8,12 @@ /// import { Terminal } from '../lib/public/Terminal'; -import * as attach from '../lib/addons/attach/attach'; +import { AttachAddon } from 'xterm-addon-attach'; +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 * as winptyCompat from '../lib/addons/winptyCompat/winptyCompat'; import { ISearchOptions } from '../lib/addons/search/Interfaces'; @@ -25,15 +26,14 @@ export interface IWindowWithTerminal extends Window { } declare let window: IWindowWithTerminal; -Terminal.applyAddon(attach); Terminal.applyAddon(fit); Terminal.applyAddon(fullscreen); Terminal.applyAddon(search); -// Terminal.applyAddon(webLinks); Terminal.applyAddon(winptyCompat); let term; +let attachAddon: AttachAddon; let protocol; let socketURL; let socket; @@ -84,7 +84,12 @@ function createTerminal(): void { terminalContainer.removeChild(terminalContainer.children[0]); } term = new Terminal({}); - (term as TerminalType).loadAddon(webLinks.WebLinksAddon).init(); + + // Load addons + const typedTerm = term as TerminalType; + typedTerm.loadAddon(WebLinksAddon).init(); + attachAddon = typedTerm.loadAddon(AttachAddon); + window.term = term; // Expose `term` to window for debugging purposes term.on('resize', (size: { cols: number, rows: number }) => { if (!pid) { @@ -144,7 +149,7 @@ function createTerminal(): void { } function runRealTerminal(): void { - term.attach(socket); + attachAddon.attach(socket); term._initialized = true; } diff --git a/demo/start.js b/demo/start.js index 78f1ff1d..1796dc17 100644 --- a/demo/start.js +++ b/demo/start.js @@ -26,7 +26,8 @@ const clientConfig = { { test: /\.js$/, use: ["source-map-loader"], - enforce: "pre" + enforce: "pre", + exclude: /node_modules/ } ] }, diff --git a/package.json b/package.json index 5ed3a4b8..ab101fca 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,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", "zmodem.js": "^0.1.5" }, "scripts": { @@ -58,12 +60,12 @@ "test-coverage": "nyc -x gulpfile.js -x '**/*test*' npm run mocha", "mocha": "gulp test", "tsc": "tsc", - "prebuild": "concurrently --kill-others-on-fail --names \"lib,attach,fit,fullscreen,search,terminado,webLinks,winptyCompat,zmodem,css\" \"tsc\" \"tsc -p ./src/addons/attach\" \"tsc -p ./src/addons/fit\" \"tsc -p ./src/addons/fullscreen\" \"tsc -p ./src/addons/search\" \"tsc -p ./src/addons/terminado\" \"tsc -p ./src/addons/webLinks\" \"tsc -p ./src/addons/winptyCompat\" \"tsc -p ./src/addons/zmodem\" \"gulp css\"", + "prebuild": "concurrently --kill-others-on-fail --names \"lib,fit,fullscreen,search,terminado,winptyCompat,zmodem,css\" \"tsc\" \"tsc -p ./src/addons/fit\" \"tsc -p ./src/addons/fullscreen\" \"tsc -p ./src/addons/search\" \"tsc -p ./src/addons/terminado\" \"tsc -p ./src/addons/winptyCompat\" \"tsc -p ./src/addons/zmodem\" \"gulp css\"", "build": "gulp build", "prepublish": "npm run build", "coveralls": "nyc report --reporter=text-lcov | coveralls", "watch": "concurrently --kill-others-on-fail --names \"lib,css\" \"tsc -w\" \"gulp watch-css\"", - "watch-addons": "concurrently --kill-others-on-fail --names \"attach,fit,fullscreen,search,terminado,webLinks,winptyCompat,zmodem\" \"tsc -w -p ./src/addons/attach\" \"tsc -w -p ./src/addons/fit\" \"tsc -w -p ./src/addons/fullscreen\" \"tsc -w -p ./src/addons/search\" \"tsc -w -p ./src/addons/terminado\" \"tsc -w -p ./src/addons/webLinks\" \"tsc -w -p ./src/addons/winptyCompat\" \"tsc -w -p ./src/addons/zmodem\"", + "watch-addons": "concurrently --kill-others-on-fail --names \"fit,fullscreen,search,terminado,winptyCompat,zmodem\" \"tsc -w -p ./src/addons/fit\" \"tsc -w -p ./src/addons/fullscreen\" \"tsc -w -p ./src/addons/search\" \"tsc -w -p ./src/addons/terminado\" \"tsc -w -p ./src/addons/winptyCompat\" \"tsc -w -p ./src/addons/zmodem\"", "layering": "concurrently --kill-others-on-fail --names \"common,core\" \"tsc -p ./src/common\" \"tsc -p ./src/core\"" } } diff --git a/src/addons/attach/Interfaces.ts b/src/addons/attach/Interfaces.ts deleted file mode 100644 index ab5846f5..00000000 --- a/src/addons/attach/Interfaces.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) 2018 The xterm.js authors. All rights reserved. - * @license MIT - * - * Implements the attach method, that attaches the terminal to a WebSocket stream. - */ - -import { Terminal, IDisposable } from 'xterm'; - -export interface IAttachAddonTerminal extends Terminal { - _core: { - register(d: T): void; - }; - - __socket?: WebSocket; - __attachSocketBuffer?: string; - - __getMessage?(ev: MessageEvent): void; - __flushBuffer?(): void; - __pushToBuffer?(data: string): void; - __sendData?(data: string): void; -} diff --git a/src/addons/attach/attach.test.ts b/src/addons/attach/attach.test.ts deleted file mode 100644 index e280b656..00000000 --- a/src/addons/attach/attach.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) 2014 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; - -import * as attach from './attach'; - -class MockTerminal {} - -describe('attach addon', () => { - describe('apply', () => { - it('should do register the `attach` and `detach` methods', () => { - attach.apply(MockTerminal); - assert.equal(typeof (MockTerminal).prototype.attach, 'function'); - assert.equal(typeof (MockTerminal).prototype.detach, 'function'); - }); - }); -}); diff --git a/src/addons/attach/attach.ts b/src/addons/attach/attach.ts deleted file mode 100644 index f121e2e2..00000000 --- a/src/addons/attach/attach.ts +++ /dev/null @@ -1,155 +0,0 @@ -/** - * Copyright (c) 2014 The xterm.js authors. All rights reserved. - * @license MIT - * - * Implements the attach method, that attaches the terminal to a WebSocket stream. - */ - -import { Terminal, IDisposable } from 'xterm'; -import { IAttachAddonTerminal } from './Interfaces'; - -/** - * Attaches the given terminal to the given socket. - * - * @param term The terminal to be attached to the given socket. - * @param socket The socket to attach the current terminal. - * @param bidirectional Whether the terminal should send data to the socket as well. - * @param buffered Whether the rendering of incoming data should happen instantly or at a maximum - * frequency of 1 rendering per 10ms. - */ -export function attach(term: Terminal, socket: WebSocket, bidirectional: boolean, buffered: boolean): void { - const addonTerminal = term; - bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional; - addonTerminal.__socket = socket; - - addonTerminal.__flushBuffer = () => { - addonTerminal.write(addonTerminal.__attachSocketBuffer); - addonTerminal.__attachSocketBuffer = null; - }; - - addonTerminal.__pushToBuffer = (data: string) => { - if (addonTerminal.__attachSocketBuffer) { - addonTerminal.__attachSocketBuffer += data; - } else { - addonTerminal.__attachSocketBuffer = data; - setTimeout(addonTerminal.__flushBuffer, 10); - } - }; - - // TODO: This should be typed but there seem to be issues importing the type - let myTextDecoder: any; - - addonTerminal.__getMessage = function(ev: MessageEvent): void { - let str: string; - - if (typeof ev.data === 'object') { - if (!myTextDecoder) { - myTextDecoder = new TextDecoder(); - } - if (ev.data instanceof ArrayBuffer) { - str = myTextDecoder.decode(ev.data); - displayData(str); - } else { - const fileReader = new FileReader(); - - fileReader.addEventListener('load', () => { - str = myTextDecoder.decode(fileReader.result); - displayData(str); - }); - fileReader.readAsArrayBuffer(ev.data); - } - } else if (typeof ev.data === 'string') { - displayData(ev.data); - } else { - throw Error(`Cannot handle "${typeof ev.data}" websocket message.`); - } - }; - - /** - * Push data to buffer or write it in the terminal. - * This is used as a callback for FileReader.onload. - * - * @param str String decoded by FileReader. - * @param data The data of the EventMessage. - */ - function displayData(str?: string, data?: string): void { - if (buffered) { - addonTerminal.__pushToBuffer(str || data); - } else { - addonTerminal.write(str || data); - } - } - - addonTerminal.__sendData = (data: string) => { - if (socket.readyState !== 1) { - return; - } - socket.send(data); - }; - - addonTerminal._core.register(addSocketListener(socket, 'message', addonTerminal.__getMessage)); - - if (bidirectional) { - addonTerminal._core.register(addonTerminal.addDisposableListener('data', addonTerminal.__sendData)); - } - - addonTerminal._core.register(addSocketListener(socket, 'close', () => detach(addonTerminal, socket))); - addonTerminal._core.register(addSocketListener(socket, 'error', () => detach(addonTerminal, socket))); -} - -function addSocketListener(socket: WebSocket, type: string, handler: (this: WebSocket, ev: Event) => any): IDisposable { - socket.addEventListener(type, handler); - return { - dispose: () => { - if (!handler) { - // Already disposed - return; - } - socket.removeEventListener(type, handler); - handler = null; - } - }; -} - -/** - * Detaches the given terminal from the given socket - * - * @param term The terminal to be detached from the given socket. - * @param socket The socket from which to detach the current terminal. - */ -export function detach(term: Terminal, socket: WebSocket): void { - const addonTerminal = term; - addonTerminal.off('data', addonTerminal.__sendData); - - socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket; - - if (socket) { - socket.removeEventListener('message', addonTerminal.__getMessage); - } - - delete addonTerminal.__socket; -} - - -export function apply(terminalConstructor: typeof Terminal): void { - /** - * Attaches the current terminal to the given socket - * - * @param socket The socket to attach the current terminal. - * @param bidirectional Whether the terminal should send data to the socket as well. - * @param buffered Whether the rendering of incoming data should happen instantly or at a maximum - * frequency of 1 rendering per 10ms. - */ - (terminalConstructor.prototype).attach = function (socket: WebSocket, bidirectional: boolean, buffered: boolean): void { - attach(this, socket, bidirectional, buffered); - }; - - /** - * Detaches the current terminal from the given socket. - * - * @param socket The socket from which to detach the current terminal. - */ - (terminalConstructor.prototype).detach = function (socket: WebSocket): void { - detach(this, socket); - }; -} diff --git a/src/addons/attach/index.html b/src/addons/attach/index.html deleted file mode 100644 index b6f853be..00000000 --- a/src/addons/attach/index.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - -
- -

- xterm.js: socket attach -

-

- Attach the terminal to a WebSocket terminal stream with ease. Perfect for attaching to your - Docker containers. -

-

- Socket information -

-
- - -
-
- -
- - - \ No newline at end of file diff --git a/src/addons/attach/package.json b/src/addons/attach/package.json deleted file mode 100644 index 9e45068b..00000000 --- a/src/addons/attach/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "xterm.attach", - "main": "attach.js", - "private": true -} diff --git a/src/addons/attach/tsconfig.json b/src/addons/attach/tsconfig.json deleted file mode 100644 index 359fbd24..00000000 --- a/src/addons/attach/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "es5", - "lib": [ - "dom", - "es6", - ], - "rootDir": ".", - "outDir": "../../../lib/addons/attach/", - "sourceMap": true, - "removeComments": true, - "declaration": true, - "preserveWatchOutput": true - }, - "include": [ - "**/*.ts", - "../../../typings/xterm.d.ts" - ] -} diff --git a/src/addons/webLinks/package.json b/src/addons/webLinks/package.json deleted file mode 100644 index f200cab4..00000000 --- a/src/addons/webLinks/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "xterm.weblinks", - "main": "weblinks.js", - "private": true -} diff --git a/src/addons/webLinks/tsconfig.json b/src/addons/webLinks/tsconfig.json deleted file mode 100644 index 18105aa2..00000000 --- a/src/addons/webLinks/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "es5", - "lib": [ - "dom", - "es5", - ], - "rootDir": ".", - "outDir": "../../../lib/addons/webLinks/", - "sourceMap": true, - "removeComments": true, - "declaration": true, - "preserveWatchOutput": true, - "types": [ - "../../node_modules/@types/mocha" - ] - }, - "include": [ - "**/*.ts", - "../../../typings/xterm.d.ts" - ] -} diff --git a/src/addons/webLinks/webLinks.test.ts b/src/addons/webLinks/webLinks.test.ts deleted file mode 100644 index 1e8a4ae7..00000000 --- a/src/addons/webLinks/webLinks.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; - -import * as webLinks from './webLinks'; - -class MockTerminal { - public regex: RegExp; - public handler: (event: MouseEvent, uri: string) => void; - public options?: any; - - public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: any): number { - this.regex = regex; - this.handler = handler; - this.options = options; - return 0; - } -} - -describe('webLinks addon', () => { - describe('apply', () => { - it('should do register the `webLinksInit` method', () => { - webLinks.apply(MockTerminal); - assert.equal(typeof (MockTerminal).prototype.webLinksInit, 'function'); - }); - }); - - it('should allow ~ character in URI path', () => { - const term = new MockTerminal(); - webLinks.webLinksInit(term); - - const row = ' http://foo.com/a~b#c~d?e~f '; - - const match = row.match(term.regex); - const uri = match[term.options.matchIndex]; - - assert.equal(uri, 'http://foo.com/a~b#c~d?e~f'); - }); - - it('should allow : character in URI path', () => { - const term = new MockTerminal(); - webLinks.webLinksInit(term); - - const row = ' http://foo.com/colon:test '; - - const match = row.match(term.regex); - const uri = match[term.options.matchIndex]; - - assert.equal(uri, 'http://foo.com/colon:test'); - }); - - it('should not allow : character at the end of a URI path', () => { - const term = new MockTerminal(); - webLinks.webLinksInit(term); - - const row = ' http://foo.com/colon:test: '; - - const match = row.match(term.regex); - const uri = match[term.options.matchIndex]; - - assert.equal(uri, 'http://foo.com/colon:test'); - }); - - it('should not allow " character at the end of a URI enclosed with ""', () => { - const term = new MockTerminal(); - webLinks.webLinksInit(term); - - const row = '"http://foo.com/"'; - - const match = row.match(term.regex); - const uri = match[term.options.matchIndex]; - - assert.equal(uri, 'http://foo.com/'); - }); - - it('should not allow \' character at the end of a URI enclosed with \'\'', () => { - const term = new MockTerminal(); - webLinks.webLinksInit(term); - - const row = '\'http://foo.com/\''; - - const match = row.match(term.regex); - const uri = match[term.options.matchIndex]; - - assert.equal(uri, 'http://foo.com/'); - }); -}); diff --git a/src/addons/webLinks/webLinks.ts b/src/addons/webLinks/webLinks.ts deleted file mode 100644 index 6fc6b25d..00000000 --- a/src/addons/webLinks/webLinks.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { Terminal, ILinkMatcherOptions, ITerminalAddon } from 'xterm'; - -const protocolClause = '(https?:\\/\\/)'; -const domainCharacterSet = '[\\da-z\\.-]+'; -const negatedDomainCharacterSet = '[^\\da-z\\.-]+'; -const domainBodyClause = '(' + domainCharacterSet + ')'; -const tldClause = '([a-z\\.]{2,6})'; -const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})'; -const localHostClause = '(localhost)'; -const portClause = '(:\\d{1,5})'; -const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?'; -const pathClause = '(\\/[\\/\\w\\.\\-%~:]*)*([^:"\'\\s])'; -const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*'; -const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?'; -const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?'; -const negatedPathCharacterSet = '[^\\/\\w\\.\\-%]+'; -const bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause; -const start = '(?:^|' + negatedDomainCharacterSet + ')('; -const end = ')($|' + negatedPathCharacterSet + ')'; -const strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end); - -function handleLink(event: MouseEvent, uri: string): void { - window.open(uri, '_blank'); -} - -/** - * Initialize the web links addon, registering the link matcher. - * @param term The terminal to use web links within. - * @param handler A custom handler to use. - * @param options Custom options to use, matchIndex will always be ignored. - */ -export function webLinksInit(term: Terminal, handler: (event: MouseEvent, uri: string) => void = handleLink, options: ILinkMatcherOptions = {}): void { - // TODO: Remove this - options.matchIndex = 1; - term.registerLinkMatcher(strictUrlRegex, handler, options); -} - -export function apply(terminalConstructor: typeof Terminal): void { - // TODO: Remove this - (terminalConstructor.prototype).webLinksInit = function (handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): void { - webLinksInit(this, handler, options); - }; -} - -export class WebLinksAddon implements ITerminalAddon { - private _linkMatcherId: number; - - constructor(private _terminal: Terminal) { - } - - public init(handler: (event: MouseEvent, uri: string) => void = handleLink, options: ILinkMatcherOptions = {}): void { - options.matchIndex = 1; - this._linkMatcherId = this._terminal.registerLinkMatcher(strictUrlRegex, handler, options); - } - - public dispose(): void { - this._terminal.deregisterLinkMatcher(this._linkMatcherId); - } -} diff --git a/yarn.lock b/yarn.lock index 5555321d..e345f6d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7201,6 +7201,16 @@ 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-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== + y18n@^3.2.0, y18n@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"