diff --git a/gulpfile.js b/gulpfile.js index 13a3ddac..c1c3a789 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -44,9 +44,16 @@ gulp.task('tsc', function () { tsResult.dts.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest(outDir)) ); + fs.emptyDirSync(`${outDir}/addons/attach`); fs.emptyDirSync(`${outDir}/addons/search`); fs.emptyDirSync(`${outDir}/addons/winptyCompat`); + let tsProjectAttachAddon = ts.createProject('./src/addons/attach/tsconfig.json'); + let tsResultAttachAddon = tsProjectAttachAddon.src().pipe(sourcemaps.init()).pipe(tsProjectAttachAddon()); + let tscAttachAddon = tsResultAttachAddon.js + .pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})) + .pipe(gulp.dest(`${outDir}/addons/attach`)); + let tsProjectSearchAddon = ts.createProject('./src/addons/search/tsconfig.json'); let tsResultSearchAddon = tsProjectSearchAddon.src().pipe(sourcemaps.init()).pipe(tsProjectSearchAddon()); let tscSearchAddon = tsResultSearchAddon.js.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest(`${outDir}/addons/search`)); @@ -58,6 +65,8 @@ gulp.task('tsc', function () { // Copy all addons from ${srcDir}/ to ${outDir}/ let copyAddons = gulp.src([ `${srcDir}/addons/**/*`, + `!${srcDir}/addons/attach`, + `!${srcDir}/addons/attach/**`, `!${srcDir}/addons/search`, `!${srcDir}/addons/search/**`, `!${srcDir}/addons/winptyCompat`, diff --git a/package-lock.json b/package-lock.json index 75d2afca..c83a4d9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "xterm", - "version": "2.8.1", + "version": "2.9.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -44,6 +44,12 @@ "integrity": "sha512-7F3/P6MkTPA0QxOstRqfcnoReCUy5V/QG92cyBoZSPnqdX44L8TtNELSVfN56gAttm3YWj9cEi8FRIPVq0WmeQ==", "dev": true }, + "@types/text-encoding": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/text-encoding/-/text-encoding-0.0.32.tgz", + "integrity": "sha512-kQ79aFmYcD/DR3QKo6wXyvNrKi7PunY0KYTBUhHjHl0SXwWuLRl9Leh73YtnsSJMBi9qSiK+fxWhdJNQPbhc9A==", + "dev": true + }, "@types/tough-cookie": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.0.tgz", @@ -555,12 +561,29 @@ "integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=", "dev": true }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true + }, "clone-stats": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", "dev": true }, + "cloneable-readable": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.0.0.tgz", + "integrity": "sha1-pikNQT8hemEjL5XkWP84QYz7ARc=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "process-nextick-args": "1.0.7", + "through2": "2.0.3" + } + }, "combine-source-map": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.7.2.tgz", @@ -620,6 +643,15 @@ } } }, + "concat-with-sourcemaps": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.0.4.tgz", + "integrity": "sha1-9Vs74q60dgGxCi1SWcz7cP0vHdY=", + "dev": true, + "requires": { + "source-map": "0.5.6" + } + }, "console-browserify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", @@ -662,6 +694,16 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, + "crc-32": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.1.1.tgz", + "integrity": "sha1-XXOdXkxuNSrYME1zIj1IP+Va240=", + "dev": true, + "requires": { + "exit-on-epipe": "1.0.1", + "printj": "1.1.0" + } + }, "create-ecdh": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz", @@ -1021,6 +1063,12 @@ } } }, + "exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", + "dev": true + }, "express": { "version": "4.13.4", "resolved": "https://registry.npmjs.org/express/-/express-4.13.4.tgz", @@ -4190,6 +4238,51 @@ } } }, + "gulp-concat": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz", + "integrity": "sha1-Yz0WyV2IUEYorQJmVmPO5aR5M1M=", + "dev": true, + "requires": { + "concat-with-sourcemaps": "1.0.4", + "through2": "2.0.3", + "vinyl": "2.1.0" + }, + "dependencies": { + "clone": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, + "vinyl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.1.0.tgz", + "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", + "dev": true, + "requires": { + "clone": "2.1.1", + "clone-buffer": "1.0.0", + "clone-stats": "1.0.0", + "cloneable-readable": "1.0.0", + "remove-trailing-separator": "1.0.2", + "replace-ext": "1.0.0" + } + } + } + }, "gulp-coveralls": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/gulp-coveralls/-/gulp-coveralls-0.1.4.tgz", @@ -9519,28 +9612,19 @@ "duplexer2": "0.0.2" } }, + "nan": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", + "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=", + "dev": true + }, "node-pty": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-0.4.1.tgz", - "integrity": "sha1-qAs1/le2TwVasZsteqYE2DYVXAQ=", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-0.7.3.tgz", + "integrity": "sha1-S7NqJKYu6fMzLi4jENZkLZ2u0fc=", "dev": true, "requires": { - "extend": "1.2.1", - "nan": "2.2.1" - }, - "dependencies": { - "extend": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz", - "integrity": "sha1-oPX9bPyDpf5J72mNYOyKYk3UV2w=", - "dev": true - }, - "nan": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.2.1.tgz", - "integrity": "sha1-1oaT9rNLtB1mvGizpPne/HnXFJs=", - "dev": true - } + "nan": "2.8.0" } }, "nodemon": { @@ -10899,6 +10983,12 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, + "printj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.0.tgz", + "integrity": "sha512-NbiNBOQ0GioHyeD3ni8wZB7ZmfU7mxIrqhWR5XSreX3rUVvk5UOwpzxOnWqrLdCtoBbdQ40sEwC+nXxxjlUo0A==", + "dev": true + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -12271,9 +12361,9 @@ "dev": true }, "typescript": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.2.2.tgz", - "integrity": "sha1-YGAiUIR5tV/6NotY/uljoD39eww=", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.4.2.tgz", + "integrity": "sha1-+DlfhdRZJ2BnyYiqQYN6j4KHCEQ=", "dev": true }, "umd": { @@ -12547,6 +12637,15 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true + }, + "zmodem.js": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/zmodem.js/-/zmodem.js-0.1.6.tgz", + "integrity": "sha1-QNeLS0uYQiBo5CB5II/wgFcLrdk=", + "dev": true, + "requires": { + "crc-32": "1.1.1" + } } } } diff --git a/package.json b/package.json index 6cdebd90..747d6d0a 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@types/jsdom": "^11.0.1", "@types/mocha": "^2.2.33", "@types/node": "^6.0.41", + "@types/text-encoding": "0.0.32", "browserify": "^13.3.0", "chai": "3.5.0", "docdash": "0.4.0", diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index e9acd537..9cb0b75c 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -3,8 +3,9 @@ * @license MIT */ - import { assert, expect } from 'chai'; +import { assert, expect } from 'chai'; import { Terminal } from './Terminal'; +import * as attach from './addons/attach/attach'; import { MockViewport, MockCompositionHelper, MockRenderer } from './utils/TestUtils.test'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; @@ -42,9 +43,9 @@ describe('term.js addons', () => { }; }); - it('should load addons with Terminal.loadAddon', () => { - Terminal.loadAddon('attach'); - // Test that addon was loaded successfully, adding attach to Terminal's + it('should apply addons with Terminal.applyAddon', () => { + Terminal.applyAddon(attach); + // Test that addon was applied successfully, adding attach to Terminal's // prototype. assert.equal(typeof (Terminal).prototype.attach, 'function'); }); diff --git a/src/Terminal.ts b/src/Terminal.ts index 9e6312a5..5f84d8a2 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -47,12 +47,6 @@ import { MouseZoneManager } from './input/MouseZoneManager'; import { initialize as initializeCharAtlas } from './renderer/CharAtlas'; import { IRenderer } from './renderer/Interfaces'; -// Declares required for loadAddon -declare var exports: any; -declare var module: any; -declare var define: any; -declare var require: any; - // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -687,22 +681,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } /** - * Attempts to load an add-on using CommonJS or RequireJS (whichever is available). - * @param {string} addon The name of the addon to load - * @static + * Apply the provided addon on the `Terminal` class. + * @param addon The addon to apply. */ - public static loadAddon(addon: string, callback?: Function): boolean | any { - // TODO: Improve return type and documentation - if (typeof exports === 'object' && typeof module === 'object') { - // CommonJS - return require('./addons/' + addon + '/' + addon); - } else if (typeof define === 'function') { - // RequireJS - return (require)(['./addons/' + addon + '/' + addon], callback); - } else { - console.error('Cannot load a module without a CommonJS or RequireJS environment.'); - return false; - } + public static applyAddon(addon: any): void { + addon.default(Terminal); } /** diff --git a/src/addons/attach/attach.ts b/src/addons/attach/attach.ts new file mode 100644 index 00000000..e554a0f3 --- /dev/null +++ b/src/addons/attach/attach.ts @@ -0,0 +1,119 @@ +/** + * Copyright (c) 2014 The xterm.js authors. All rights reserved. + * @license MIT + * + * Implements the attach method, that attaches the terminal to a WebSocket stream. + */ + +/** + * Attaches the given terminal to the given socket. + * + * @param {Terminal} term - The terminal to be attached to the given socket. + * @param {WebSocket} socket - The socket to attach the current terminal. + * @param {boolean} bidirectional - Whether the terminal should send data + * to the socket as well. + * @param {boolean} buffered - Whether the rendering of incoming data + * should happen instantly or at a maximum + * frequency of 1 rendering per 10ms. + */ +function attach (term, socket, bidirectional, buffered) { + bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional; + term.socket = socket; + + term._flushBuffer = function () { + term.write(term._attachSocketBuffer); + term._attachSocketBuffer = null; + }; + + term._pushToBuffer = function (data) { + if (term._attachSocketBuffer) { + term._attachSocketBuffer += data; + } else { + term._attachSocketBuffer = data; + setTimeout(term._flushBuffer, 10); + } + }; + + var myTextDecoder; + + term._getMessage = function (ev) { + var str; + if (typeof ev.data === "object") { + if (ev.data instanceof ArrayBuffer) { + if (!myTextDecoder) { + myTextDecoder = new TextDecoder(); + } + + str = myTextDecoder.decode( ev.data ); + } + else { + throw "TODO: handle Blob?"; + } + } + + if (buffered) { + term._pushToBuffer(str || ev.data); + } else { + term.write(str || ev.data); + } + }; + + term._sendData = function (data) { + socket.send(data); + }; + + socket.addEventListener('message', term._getMessage); + + if (bidirectional) { + term.on('data', term._sendData); + } + + socket.addEventListener('close', term.detach.bind(term, socket)); + socket.addEventListener('error', term.detach.bind(term, socket)); +}; + +/** + * Detaches the given terminal from the given socket + * + * @param {Terminal} term - The terminal to be detached from the given socket. + * @param {WebSocket} socket - The socket from which to detach the current + * terminal. + */ +function detach (term, socket) { + term.off('data', term._sendData); + + socket = (typeof socket == 'undefined') ? term.socket : socket; + + if (socket) { + socket.removeEventListener('message', term._getMessage); + } + + delete term.socket; +}; + + +export default function applyOn(terminalConstructor) { + /** + * Attaches the current terminal to the given socket + * + * @param {WebSocket} socket - The socket to attach the current terminal. + * @param {boolean} bidirectional - Whether the terminal should send data + * to the socket as well. + * @param {boolean} 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, bidirectional, buffered) { + return exports.attach(this, socket, bidirectional, buffered); + }; + + /** + * Detaches the current terminal from the given socket. + * + * @param {WebSocket} socket - The socket from which to detach the current + * terminal. + */ + terminalConstructor.prototype.detach = function (socket) { + return exports.detach(this, socket); + }; +} diff --git a/src/addons/attach/tsconfig.json b/src/addons/attach/tsconfig.json new file mode 100644 index 00000000..97722324 --- /dev/null +++ b/src/addons/attach/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "rootDir": ".", + "outDir": "../../../lib/addons/attach/", + "sourceMap": true, + "removeComments": true + } +} diff --git a/tsconfig.json b/tsconfig.json index 38bfd2fa..45bdda17 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,8 @@ "rootDir": "src", "outDir": "lib", "sourceMap": true, - "removeComments": true + "removeComments": true, + "allowJs": true }, "include": [ "src/**/*"