From 4c4684295e1e1ad3cfaa1d7bd7b3bbb541201d34 Mon Sep 17 00:00:00 2001 From: Paris Kasidiaris Date: Wed, 15 Nov 2017 19:39:56 +0200 Subject: [PATCH 01/13] First PoC of `.applyAddon` --- gulpfile.js | 9 ++ package-lock.json | 145 +++++++++++++++++++++++++++----- package.json | 1 + src/Terminal.test.ts | 9 +- src/Terminal.ts | 25 +----- src/addons/attach/attach.ts | 119 ++++++++++++++++++++++++++ src/addons/attach/tsconfig.json | 10 +++ tsconfig.json | 3 +- 8 files changed, 272 insertions(+), 49 deletions(-) create mode 100644 src/addons/attach/attach.ts create mode 100644 src/addons/attach/tsconfig.json 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/**/*" From 2547858efbf7ea834c1ad303b3d2447fa41249ed Mon Sep 17 00:00:00 2001 From: Paris Kasidiaris Date: Wed, 15 Nov 2017 19:48:10 +0200 Subject: [PATCH 02/13] Remove "debugging" `allowJs` --- tsconfig.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 45bdda17..38bfd2fa 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,8 +5,7 @@ "rootDir": "src", "outDir": "lib", "sourceMap": true, - "removeComments": true, - "allowJs": true + "removeComments": true }, "include": [ "src/**/*" From 510de739963e500031c1de7b92935312060cc312 Mon Sep 17 00:00:00 2001 From: Paris Kasidiaris Date: Fri, 17 Nov 2017 13:32:41 +0200 Subject: [PATCH 03/13] Port addons to new `applyAddon` API and TypeScript --- gulpfile.js | 38 ++--- src/Terminal.ts | 2 +- src/addons/attach/attach.js | 141 ------------------- src/addons/attach/attach.test.ts | 20 +++ src/addons/attach/attach.ts | 18 +-- src/addons/fit/fit.js | 81 ----------- src/addons/fit/fit.test.ts | 20 +++ src/addons/fit/fit.ts | 60 ++++++++ src/addons/fit/tsconfig.json | 10 ++ src/addons/fullscreen/fullscreen.js | 50 ------- src/addons/fullscreen/fullscreen.test.ts | 19 +++ src/addons/fullscreen/fullscreen.ts | 29 ++++ src/addons/fullscreen/tsconfig.json | 10 ++ src/addons/search/package.json | 5 + src/addons/terminado/terminado.js | 134 ------------------ src/addons/terminado/terminado.test.ts | 20 +++ src/addons/terminado/terminado.ts | 111 +++++++++++++++ src/addons/terminado/tsconfig.json | 10 ++ src/addons/winptyCompat/package.json | 5 + src/addons/winptyCompat/winptyCompat.test.ts | 19 +++ src/addons/winptyCompat/winptyCompat.ts | 26 +--- src/addons/zmodem/package.json | 5 + src/addons/zmodem/tsconfig.json | 10 ++ src/addons/zmodem/zmodem.js | 108 -------------- src/addons/zmodem/zmodem.test.ts | 20 +++ src/addons/zmodem/zmodem.ts | 89 ++++++++++++ 26 files changed, 490 insertions(+), 570 deletions(-) delete mode 100644 src/addons/attach/attach.js create mode 100644 src/addons/attach/attach.test.ts delete mode 100644 src/addons/fit/fit.js create mode 100644 src/addons/fit/fit.test.ts create mode 100644 src/addons/fit/fit.ts create mode 100644 src/addons/fit/tsconfig.json delete mode 100644 src/addons/fullscreen/fullscreen.js create mode 100644 src/addons/fullscreen/fullscreen.test.ts create mode 100644 src/addons/fullscreen/fullscreen.ts create mode 100644 src/addons/fullscreen/tsconfig.json create mode 100644 src/addons/search/package.json delete mode 100644 src/addons/terminado/terminado.js create mode 100644 src/addons/terminado/terminado.test.ts create mode 100644 src/addons/terminado/terminado.ts create mode 100644 src/addons/terminado/tsconfig.json create mode 100644 src/addons/winptyCompat/package.json create mode 100644 src/addons/winptyCompat/winptyCompat.test.ts create mode 100644 src/addons/zmodem/package.json create mode 100644 src/addons/zmodem/tsconfig.json delete mode 100644 src/addons/zmodem/zmodem.js create mode 100644 src/addons/zmodem/zmodem.test.ts create mode 100644 src/addons/zmodem/zmodem.ts diff --git a/gulpfile.js b/gulpfile.js index c1c3a789..0c3fe0f3 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -44,39 +44,31 @@ 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 addons = ['attach', 'fit', 'fullscreen', 'search', 'terminado', 'winptyCompat', 'zmodem']; + let addonStreams = addons.map(function(addon) { + fs.emptyDirSync(`${outDir}/addons/${addon}`); - 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 tsProjectAddon = ts.createProject(`./src/addons/${addon}/tsconfig.json`); + let tsResultAddon = tsProjectAddon.src().pipe(sourcemaps.init()).pipe(tsProjectAddon()); + let tscAddon = tsResultAddon.js + .pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})) + .pipe(gulp.dest(`${outDir}/addons/${addon}`)); - 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`)); - - let tsProjectWinptyCompatAddon = ts.createProject('./src/addons/winptyCompat/tsconfig.json'); - let tsResultWinptyCompatAddon = tsProjectWinptyCompatAddon.src().pipe(sourcemaps.init()).pipe(tsProjectWinptyCompatAddon()); - let tscWinptyCompatAddon = tsResultWinptyCompatAddon.js.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest(`${outDir}/addons/winptyCompat`)); + return tscAddon; + }); // 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`, - `!${srcDir}/addons/winptyCompat/**` + `${srcDir}/addons/**/**` ]).pipe(gulp.dest(`${outDir}/addons`)); // Copy stylesheets from ${srcDir}/ to ${outDir}/ let copyStylesheets = gulp.src(`${srcDir}/**/*.css`).pipe(gulp.dest(outDir)); - return merge(tsc, tscSearchAddon, tscWinptyCompatAddon, copyAddons, copyStylesheets); + // Join all streams into a single array + let streams = [tsc].concat(addonStreams).concat([copyAddons, copyStylesheets]); + + return merge.apply(this, streams); }); /** diff --git a/src/Terminal.ts b/src/Terminal.ts index 5f84d8a2..7ae64fbc 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -685,7 +685,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param addon The addon to apply. */ public static applyAddon(addon: any): void { - addon.default(Terminal); + addon.apply(Terminal); } /** diff --git a/src/addons/attach/attach.js b/src/addons/attach/attach.js deleted file mode 100644 index 712d911d..00000000 --- a/src/addons/attach/attach.js +++ /dev/null @@ -1,141 +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. - */ - -(function (attach) { - if (typeof exports === 'object' && typeof module === 'object') { - /* - * CommonJS environment - */ - module.exports = attach(require('../../Terminal').Terminal); - } else if (typeof define == 'function') { - /* - * Require.js is available - */ - define(['../../xterm'], attach); - } else { - /* - * Plain browser environment - */ - attach(window.Terminal); - } -})(function (Terminal) { - 'use strict'; - - var exports = {}; - - /** - * 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. - */ - exports.attach = function (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. - */ - exports.detach = function (term, socket) { - term.off('data', term._sendData); - - socket = (typeof socket == 'undefined') ? term.socket : socket; - - if (socket) { - socket.removeEventListener('message', term._getMessage); - } - - delete term.socket; - }; - - /** - * 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. - */ - Terminal.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. - */ - Terminal.prototype.detach = function (socket) { - return exports.detach(this, socket); - }; - - return exports; -}); diff --git a/src/addons/attach/attach.test.ts b/src/addons/attach/attach.test.ts new file mode 100644 index 00000000..9bf0e070 --- /dev/null +++ b/src/addons/attach/attach.test.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2014 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert, expect } 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 index e554a0f3..0142c35e 100644 --- a/src/addons/attach/attach.ts +++ b/src/addons/attach/attach.ts @@ -16,16 +16,16 @@ * should happen instantly or at a maximum * frequency of 1 rendering per 10ms. */ -function attach (term, socket, bidirectional, buffered) { +function attach(term, socket, bidirectional, buffered) { bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional; term.socket = socket; - term._flushBuffer = function () { + term._flushBuffer = function() { term.write(term._attachSocketBuffer); term._attachSocketBuffer = null; }; - term._pushToBuffer = function (data) { + term._pushToBuffer = function(data) { if (term._attachSocketBuffer) { term._attachSocketBuffer += data; } else { @@ -36,7 +36,7 @@ function attach (term, socket, bidirectional, buffered) { var myTextDecoder; - term._getMessage = function (ev) { + term._getMessage = function(ev) { var str; if (typeof ev.data === "object") { if (ev.data instanceof ArrayBuffer) { @@ -58,7 +58,7 @@ function attach (term, socket, bidirectional, buffered) { } }; - term._sendData = function (data) { + term._sendData = function(data) { socket.send(data); }; @@ -79,7 +79,7 @@ function attach (term, socket, bidirectional, buffered) { * @param {WebSocket} socket - The socket from which to detach the current * terminal. */ -function detach (term, socket) { +function detach(term, socket) { term.off('data', term._sendData); socket = (typeof socket == 'undefined') ? term.socket : socket; @@ -92,7 +92,7 @@ function detach (term, socket) { }; -export default function applyOn(terminalConstructor) { +export function apply(terminalConstructor) { /** * Attaches the current terminal to the given socket * @@ -103,7 +103,7 @@ export default function applyOn(terminalConstructor) { * should happen instantly or at a maximum * frequency of 1 rendering per 10ms. */ - terminalConstructor.prototype.attach = function (socket, bidirectional, buffered) { + terminalConstructor.prototype.attach = function(socket, bidirectional, buffered) { return exports.attach(this, socket, bidirectional, buffered); }; @@ -113,7 +113,7 @@ export default function applyOn(terminalConstructor) { * @param {WebSocket} socket - The socket from which to detach the current * terminal. */ - terminalConstructor.prototype.detach = function (socket) { + terminalConstructor.prototype.detach = function(socket) { return exports.detach(this, socket); }; } diff --git a/src/addons/fit/fit.js b/src/addons/fit/fit.js deleted file mode 100644 index c82039ab..00000000 --- a/src/addons/fit/fit.js +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright (c) 2014 The xterm.js authors. All rights reserved. - * @license MIT - * - * Fit terminal columns and rows to the dimensions of its DOM element. - * - * ## Approach - * - * Rows: Truncate the division of the terminal parent element height by the - * terminal row height. - * Columns: Truncate the division of the terminal parent element width by the - * terminal character width (apply display: inline at the terminal - * row and truncate its width with the current number of columns). - */ - -(function (fit) { - if (typeof exports === 'object' && typeof module === 'object') { - /* - * CommonJS environment - */ - module.exports = fit(require('../../Terminal').Terminal); - } else if (typeof define == 'function') { - /* - * Require.js is available - */ - define(['../../xterm'], fit); - } else { - /* - * Plain browser environment - */ - fit(window.Terminal); - } -})(function (Terminal) { - var exports = {}; - - exports.proposeGeometry = function (term) { - if (!term.element.parentElement) { - return null; - } - var parentElementStyle = window.getComputedStyle(term.element.parentElement); - var parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')); - var parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17); - var elementStyle = window.getComputedStyle(term.element); - var elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom')); - var elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left')); - var availableHeight = parentElementHeight - elementPaddingVer; - var availableWidth = parentElementWidth - elementPaddingHor; - var geometry = { - cols: Math.floor(availableWidth / term.charMeasure.width), - rows: Math.floor(availableHeight / Math.floor(term.charMeasure.height * term.getOption('lineHeight'))) - }; - - return geometry; - }; - - exports.fit = function (term) { - // Wrap fit in a setTimeout as charMeasure needs time to get initialized - // after calling Terminal.open - setTimeout(function () { - var geometry = exports.proposeGeometry(term); - - if (geometry) { - // Force a full render - if (term.rows !== geometry.rows || term.cols !== geometry.cols) { - term.renderer.clear(); - term.resize(geometry.cols, geometry.rows); - } - } - }, 0); - }; - - Terminal.prototype.proposeGeometry = function () { - return exports.proposeGeometry(this); - }; - - Terminal.prototype.fit = function () { - return exports.fit(this); - }; - - return exports; -}); diff --git a/src/addons/fit/fit.test.ts b/src/addons/fit/fit.test.ts new file mode 100644 index 00000000..159cc987 --- /dev/null +++ b/src/addons/fit/fit.test.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert, expect } from 'chai'; + +import * as fit from './fit' + +class MockTerminal {} + +describe('fit addon', () => { + describe('apply', () => { + it('should do register the `proposeGeometry` and `fit` methods', () => { + fit.apply(MockTerminal); + assert.equal(typeof (MockTerminal).prototype.proposeGeometry, 'function'); + assert.equal(typeof (MockTerminal).prototype.fit, 'function'); + }); + }); +}); diff --git a/src/addons/fit/fit.ts b/src/addons/fit/fit.ts new file mode 100644 index 00000000..a372d0bb --- /dev/null +++ b/src/addons/fit/fit.ts @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2014 The xterm.js authors. All rights reserved. + * @license MIT + * + * Fit terminal columns and rows to the dimensions of its DOM element. + * + * ## Approach + * + * Rows: Truncate the division of the terminal parent element height by the + * terminal row height. + * Columns: Truncate the division of the terminal parent element width by the + * terminal character width (apply display: inline at the terminal + * row and truncate its width with the current number of columns). + */ + +function proposeGeometry(term) { + if (!term.element.parentElement) { + return null; + } + var parentElementStyle = window.getComputedStyle(term.element.parentElement); + var parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')); + var parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17); + var elementStyle = window.getComputedStyle(term.element); + var elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom')); + var elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left')); + var availableHeight = parentElementHeight - elementPaddingVer; + var availableWidth = parentElementWidth - elementPaddingHor; + var geometry = { + cols: Math.floor(availableWidth / term.charMeasure.width), + rows: Math.floor(availableHeight / Math.floor(term.charMeasure.height * term.getOption('lineHeight'))) + }; + + return geometry; +}; + +function fit(term) { + // Wrap fit in a setTimeout as charMeasure needs time to get initialized + // after calling Terminal.open + setTimeout(function () { + var geometry = exports.proposeGeometry(term); + + if (geometry) { + // Force a full render + if (term.rows !== geometry.rows || term.cols !== geometry.cols) { + term.renderer.clear(); + term.resize(geometry.cols, geometry.rows); + } + } + }, 0); +}; + +export function apply(terminalConstructor) { + terminalConstructor.prototype.proposeGeometry = function () { + return proposeGeometry(this); + }; + + terminalConstructor.prototype.fit = function () { + return fit(this); + }; +} diff --git a/src/addons/fit/tsconfig.json b/src/addons/fit/tsconfig.json new file mode 100644 index 00000000..949f8b60 --- /dev/null +++ b/src/addons/fit/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "rootDir": ".", + "outDir": "../../../lib/addons/fit/", + "sourceMap": true, + "removeComments": true + } +} diff --git a/src/addons/fullscreen/fullscreen.js b/src/addons/fullscreen/fullscreen.js deleted file mode 100644 index 1474d2db..00000000 --- a/src/addons/fullscreen/fullscreen.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (c) 2014 The xterm.js authors. All rights reserved. - * @license MIT - */ - -(function (fullscreen) { - if (typeof exports === 'object' && typeof module === 'object') { - /* - * CommonJS environment - */ - module.exports = fullscreen(require('../../Terminal').Terminal); - } else if (typeof define == 'function') { - /* - * Require.js is available - */ - define(['../../xterm'], fullscreen); - } else { - /* - * Plain browser environment - */ - fullscreen(window.Terminal); - } -})(function (Terminal) { - var exports = {}; - - /** - * Toggle the given terminal's fullscreen mode. - * @param {Terminal} term - The terminal to toggle full screen mode - * @param {boolean} fullscreen - Toggle fullscreen on (true) or off (false) - */ - exports.toggleFullScreen = function (term, fullscreen) { - var fn; - - if (typeof fullscreen == 'undefined') { - fn = (term.element.classList.contains('fullscreen')) ? 'remove' : 'add'; - } else if (!fullscreen) { - fn = 'remove'; - } else { - fn = 'add'; - } - - term.element.classList[fn]('fullscreen'); - }; - - Terminal.prototype.toggleFullscreen = function (fullscreen) { - exports.toggleFullScreen(this, fullscreen); - }; - - return exports; -}); diff --git a/src/addons/fullscreen/fullscreen.test.ts b/src/addons/fullscreen/fullscreen.test.ts new file mode 100644 index 00000000..c77ea75c --- /dev/null +++ b/src/addons/fullscreen/fullscreen.test.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2014 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert, expect } from 'chai'; + +import * as fullscreen from './fullscreen' + +class MockTerminal {} + +describe('fullscreen addon', () => { + describe('apply', () => { + it('should do register the `toggleFullscreen` method', () => { + fullscreen.apply(MockTerminal); + assert.equal(typeof (MockTerminal).prototype.toggleFullScreen, 'function'); + }); + }); +}); diff --git a/src/addons/fullscreen/fullscreen.ts b/src/addons/fullscreen/fullscreen.ts new file mode 100644 index 00000000..7211b518 --- /dev/null +++ b/src/addons/fullscreen/fullscreen.ts @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2014 The xterm.js authors. All rights reserved. + * @license MIT + */ + +/** + * Toggle the given terminal's fullscreen mode. + * @param {Terminal} term - The terminal to toggle full screen mode + * @param {boolean} fullscreen - Toggle fullscreen on (true) or off (false) + */ +function toggleFullScreen(term, fullscreen) { + var fn; + + if (typeof fullscreen == 'undefined') { + fn = (term.element.classList.contains('fullscreen')) ? 'remove' : 'add'; + } else if (!fullscreen) { + fn = 'remove'; + } else { + fn = 'add'; + } + + term.element.classList[fn]('fullscreen'); +}; + +export function apply(terminalConstructor) { + terminalConstructor.prototype.toggleFullScreen = function (fullscreen) { + return toggleFullScreen(this, fullscreen); + }; +} diff --git a/src/addons/fullscreen/tsconfig.json b/src/addons/fullscreen/tsconfig.json new file mode 100644 index 00000000..b04144c1 --- /dev/null +++ b/src/addons/fullscreen/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "rootDir": ".", + "outDir": "../../../lib/addons/fullscreen/", + "sourceMap": true, + "removeComments": true + } +} diff --git a/src/addons/search/package.json b/src/addons/search/package.json new file mode 100644 index 00000000..552e9332 --- /dev/null +++ b/src/addons/search/package.json @@ -0,0 +1,5 @@ +{ + "name": "xterm.search", + "main": "search.js", + "private": true +} diff --git a/src/addons/terminado/terminado.js b/src/addons/terminado/terminado.js deleted file mode 100644 index 4cf08227..00000000 --- a/src/addons/terminado/terminado.js +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Copyright (c) 2016 The xterm.js authors. All rights reserved. - * @license MIT - * - * This module provides methods for attaching a terminal to a terminado - * WebSocket stream. - */ - -(function (attach) { - if (typeof exports === 'object' && typeof module === 'object') { - /* - * CommonJS environment - */ - module.exports = attach(require('../../Terminal').Terminal); - } else if (typeof define == 'function') { - /* - * Require.js is available - */ - define(['../../xterm'], attach); - } else { - /* - * Plain browser environment - */ - attach(window.Terminal); - } -})(function (Terminal) { - 'use strict'; - - var exports = {}; - - /** - * 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. - */ - exports.terminadoAttach = function (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); - } - }; - - term._getMessage = function (ev) { - var data = JSON.parse(ev.data) - if( data[0] == "stdout" ) { - if (buffered) { - term._pushToBuffer(data[1]); - } else { - term.write(data[1]); - } - } - }; - - term._sendData = function (data) { - socket.send(JSON.stringify(['stdin', data])); - }; - - term._setSize = function (size) { - socket.send(JSON.stringify(['set_size', size.rows, size.cols])); - }; - - socket.addEventListener('message', term._getMessage); - - if (bidirectional) { - term.on('data', term._sendData); - } - term.on('resize', term._setSize); - - socket.addEventListener('close', term.terminadoDetach.bind(term, socket)); - socket.addEventListener('error', term.terminadoDetach.bind(term, socket)); - }; - - /** - * Detaches the given terminal from the given socket - * - * @param {Xterm} term - The terminal to be detached from the given socket. - * @param {WebSocket} socket - The socket from which to detach the current - * terminal. - */ - exports.terminadoDetach = function (term, socket) { - term.off('data', term._sendData); - - socket = (typeof socket == 'undefined') ? term.socket : socket; - - if (socket) { - socket.removeEventListener('message', term._getMessage); - } - - delete term.socket; - }; - - /** - * 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. - */ - Terminal.prototype.terminadoAttach = function (socket, bidirectional, buffered) { - return exports.terminadoAttach(this, socket, bidirectional, buffered); - }; - - /** - * Detaches the current terminal from the given socket. - * - * @param {WebSocket} socket - The socket from which to detach the current - * terminal. - */ - Terminal.prototype.terminadoDetach = function (socket) { - return exports.terminadoDetach(this, socket); - }; - - return exports; -}); diff --git a/src/addons/terminado/terminado.test.ts b/src/addons/terminado/terminado.test.ts new file mode 100644 index 00000000..fe059898 --- /dev/null +++ b/src/addons/terminado/terminado.test.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert, expect } from 'chai'; + +import * as terminado from './terminado' + +class MockTerminal {} + +describe('terminado addon', () => { + describe('apply', () => { + it('should do register the `terminadoAttach` and `terminadoDetach` methods', () => { + terminado.apply(MockTerminal); + assert.equal(typeof (MockTerminal).prototype.terminadoAttach, 'function'); + assert.equal(typeof (MockTerminal).prototype.terminadoDetach, 'function'); + }); + }); +}); diff --git a/src/addons/terminado/terminado.ts b/src/addons/terminado/terminado.ts new file mode 100644 index 00000000..50bec2e8 --- /dev/null +++ b/src/addons/terminado/terminado.ts @@ -0,0 +1,111 @@ +/** + * Copyright (c) 2016 The xterm.js authors. All rights reserved. + * @license MIT + * + * This module provides methods for attaching a terminal to a terminado + * 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 terminadoAttach(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); + } + }; + + term._getMessage = function(ev) { + var data = JSON.parse(ev.data) + if( data[0] == "stdout" ) { + if (buffered) { + term._pushToBuffer(data[1]); + } else { + term.write(data[1]); + } + } + }; + + term._sendData = function(data) { + socket.send(JSON.stringify(['stdin', data])); + }; + + term._setSize = function(size) { + socket.send(JSON.stringify(['set_size', size.rows, size.cols])); + }; + + socket.addEventListener('message', term._getMessage); + + if (bidirectional) { + term.on('data', term._sendData); + } + term.on('resize', term._setSize); + + socket.addEventListener('close', term.terminadoDetach.bind(term, socket)); + socket.addEventListener('error', term.terminadoDetach.bind(term, socket)); +}; + +/** + * Detaches the given terminal from the given socket + * + * @param {Xterm} term - The terminal to be detached from the given socket. + * @param {WebSocket} socket - The socket from which to detach the current + * terminal. + */ +function terminadoDetach(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 function apply(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.terminadoAttach = function(socket, bidirectional, buffered) { + return terminadoAttach(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.terminadoDetach = function(socket) { + return terminadoDetach(this, socket); + }; +} diff --git a/src/addons/terminado/tsconfig.json b/src/addons/terminado/tsconfig.json new file mode 100644 index 00000000..775b20d5 --- /dev/null +++ b/src/addons/terminado/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "rootDir": ".", + "outDir": "../../../lib/addons/terminado/", + "sourceMap": true, + "removeComments": true + } +} diff --git a/src/addons/winptyCompat/package.json b/src/addons/winptyCompat/package.json new file mode 100644 index 00000000..2e9fc164 --- /dev/null +++ b/src/addons/winptyCompat/package.json @@ -0,0 +1,5 @@ +{ + "name": "xterm.winptyCompat", + "main": "winptyCompat.js", + "private": true +} diff --git a/src/addons/winptyCompat/winptyCompat.test.ts b/src/addons/winptyCompat/winptyCompat.test.ts new file mode 100644 index 00000000..21e54e56 --- /dev/null +++ b/src/addons/winptyCompat/winptyCompat.test.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert, expect } from 'chai'; + +import * as winptyCompat from './winptyCompat' + +class MockTerminal {} + +describe('winptyCompat addon', () => { + describe('apply', () => { + it('should do register the `winptyCompatInit` method', () => { + winptyCompat.apply(MockTerminal); + assert.equal(typeof (MockTerminal).prototype.winptyCompatInit, 'function'); + }); + }); +}); diff --git a/src/addons/winptyCompat/winptyCompat.ts b/src/addons/winptyCompat/winptyCompat.ts index 7474df2e..c9e9a53a 100644 --- a/src/addons/winptyCompat/winptyCompat.ts +++ b/src/addons/winptyCompat/winptyCompat.ts @@ -3,28 +3,8 @@ * @license MIT */ -declare var exports: any; -declare var define: any; - -(function (addon) { - if (typeof window !== 'undefined' && 'Terminal' in window) { - /** - * Plain browser environment - */ - addon((window).Terminal); - } else if (typeof exports === 'object' && typeof module === 'object') { - /** - * CommonJS environment - */ - module.exports = addon(require('../../Terminal').Terminal); - } else if (typeof define === 'function') { - /** - * Require.js is available - */ - define(['../../xterm'], addon); - } -})((Terminal: any) => { - Terminal.prototype.winptyCompatInit = function(): void { +export function apply(terminalConstructor) { + terminalConstructor.prototype.winptyCompatInit = function(): void { // Don't do anything when the platform is not Windows const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0; if (!isWindows) { @@ -50,4 +30,4 @@ declare var define: any; } }); }; -}); +} diff --git a/src/addons/zmodem/package.json b/src/addons/zmodem/package.json new file mode 100644 index 00000000..218130ab --- /dev/null +++ b/src/addons/zmodem/package.json @@ -0,0 +1,5 @@ +{ + "name": "xterm.zmodem", + "main": "zmodem.js", + "private": true +} diff --git a/src/addons/zmodem/tsconfig.json b/src/addons/zmodem/tsconfig.json new file mode 100644 index 00000000..9ded72e5 --- /dev/null +++ b/src/addons/zmodem/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "rootDir": ".", + "outDir": "../../../lib/addons/zmodem/", + "sourceMap": true, + "removeComments": true + } +} diff --git a/src/addons/zmodem/zmodem.js b/src/addons/zmodem/zmodem.js deleted file mode 100644 index 1bab2976..00000000 --- a/src/addons/zmodem/zmodem.js +++ /dev/null @@ -1,108 +0,0 @@ -/** - * - * Allow xterm.js to handle ZMODEM uploads and downloads. - * - * This addon is a wrapper around zmodem.js. It adds the following to the - * Terminal class: - * - * - function `zmodemAttach(, )` - creates a Zmodem.Sentry - * on the passed WebSocket object. The Object passed is optional and - * can contain: - * - noTerminalWriteOutsideSession: Suppress writes from the Sentry - * object to the Terminal while there is no active Session. This - * is necessary for compatibility with, for example, the - * `attach.js` addon. - * - * - event `zmodemDetect` - fired on Zmodem.Sentry’s `on_detect` callback. - * Passes the zmodem.js Detection object. - * - * - event `zmodemRetract` - fired on Zmodem.Sentry’s `on_retract` callback. - * - * You’ll need to provide logic to handle uploads and downloads. - * See zmodem.js’s documentation for more details. - * - * **IMPORTANT:** After you confirm() a zmodem.js Detection, if you have - * used the `attach` or `terminado` addons, you’ll need to suspend their - * operation for the duration of the ZMODEM session. (The demo does this - * via `detach()` and a re-`attach()`.) - */ -(function (addon) { - if (typeof exports === 'object' && typeof module === 'object') { - /* - * CommonJS environment - */ - module.exports = addon(require('../../Terminal').Terminal); - } else if (typeof define == 'function') { - /* - * Require.js is available - */ - define(['../../xterm'], addon); - } else { - /* - * Plain browser environment - */ - addon(window.Terminal); - } -})(function _zmodemAddon(Terminal) { - Object.assign( - Terminal.prototype, - { - zmodemAttach: function zmodemAttach(ws, opts) { - var term = this; - - if (!opts) opts = {}; - - var senderFunc = function _ws_sender_func(octets) { - ws.send( new Uint8Array(octets) ); - }; - - var zsentry; - - function _shouldWrite() { - return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession; - } - - zsentry = new Zmodem.Sentry( { - to_terminal: function _to_terminal(octets) { - if (_shouldWrite()) { - term.write( - String.fromCharCode.apply(String, octets) - ); - } - }, - - sender: senderFunc, - - on_retract: function _on_retract() { - term.emit("zmodemRetract"); - }, - - on_detect: function _on_detect(detection) { - term.emit("zmodemDetect", detection); - }, - } ); - - function handleWSMessage(evt) { - - //In testing with xterm.js’s demo the first message was - //always text even if the rest were binary. While that - //may be specific to xterm.js’s demo, ultimately we - //should reject anything that isn’t binary. - if (typeof evt.data === "string") { - if (_shouldWrite()) { - term.write(evt.data); - } - } - else { - zsentry.consume(evt.data); - } - } - - ws.binaryType = "arraybuffer"; - ws.addEventListener("message", handleWSMessage); - }, - - zmodemBrowser: Zmodem.Browser, - } - ); -}); diff --git a/src/addons/zmodem/zmodem.test.ts b/src/addons/zmodem/zmodem.test.ts new file mode 100644 index 00000000..3cb28316 --- /dev/null +++ b/src/addons/zmodem/zmodem.test.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert, expect } from 'chai'; + +import * as zmodem from './zmodem' + +class MockTerminal {} + +describe('zmodem addon', () => { + describe('apply', () => { + it('should do register the `zmodemAttach` method and `zmodemBrowser` attribute', () => { + zmodem.apply(MockTerminal); + assert.equal(typeof (MockTerminal).prototype.zmodemAttach, 'function'); + assert.equal(typeof (MockTerminal).prototype.zmodemBrowser, 'object'); + }); + }); +}); diff --git a/src/addons/zmodem/zmodem.ts b/src/addons/zmodem/zmodem.ts new file mode 100644 index 00000000..3a79d1c8 --- /dev/null +++ b/src/addons/zmodem/zmodem.ts @@ -0,0 +1,89 @@ +/** + * + * Allow xterm.js to handle ZMODEM uploads and downloads. + * + * This addon is a wrapper around zmodem.js. It adds the following to the + * Terminal class: + * + * - function `zmodemAttach(, )` - creates a Zmodem.Sentry + * on the passed WebSocket object. The Object passed is optional and + * can contain: + * - noTerminalWriteOutsideSession: Suppress writes from the Sentry + * object to the Terminal while there is no active Session. This + * is necessary for compatibility with, for example, the + * `attach.js` addon. + * + * - event `zmodemDetect` - fired on Zmodem.Sentry’s `on_detect` callback. + * Passes the zmodem.js Detection object. + * + * - event `zmodemRetract` - fired on Zmodem.Sentry’s `on_retract` callback. + * + * You’ll need to provide logic to handle uploads and downloads. + * See zmodem.js’s documentation for more details. + * + * **IMPORTANT:** After you confirm() a zmodem.js Detection, if you have + * used the `attach` or `terminado` addons, you’ll need to suspend their + * operation for the duration of the ZMODEM session. (The demo does this + * via `detach()` and a re-`attach()`.) + */ + +export function apply(terminalConstructor) { + let Zmodem = (typeof window == 'object') ? (window).ZModem : {Browser: null}; // Nullify browser for tests + + terminalConstructor.prototype.zmodemAttach = function(ws, opts) { + var term = this; + + if (!opts) opts = {}; + + var senderFunc = function _ws_sender_func(octets) { + ws.send( new Uint8Array(octets) ); + }; + + var zsentry; + + function _shouldWrite() { + return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession; + } + + zsentry = new Zmodem.Sentry( { + to_terminal: function _to_terminal(octets) { + if (_shouldWrite()) { + term.write( + String.fromCharCode.apply(String, octets) + ); + } + }, + + sender: senderFunc, + + on_retract: function _on_retract() { + term.emit('zmodemRetract'); + }, + + on_detect: function _on_detect(detection) { + term.emit('zmodemDetect', detection); + }, + } ); + + function handleWSMessage(evt) { + + //In testing with xterm.js’s demo the first message was + //always text even if the rest were binary. While that + //may be specific to xterm.js’s demo, ultimately we + //should reject anything that isn’t binary. + if (typeof evt.data === 'string') { + if (_shouldWrite()) { + term.write(evt.data); + } + } + else { + zsentry.consume(evt.data); + } + } + + ws.binaryType = 'arraybuffer'; + ws.addEventListener('message', handleWSMessage); + } + + terminalConstructor.prototype.zmodemBrowser = Zmodem.Browser +} From 31cc0c30c17ee9a8f18acf5d1477f86360b9445a Mon Sep 17 00:00:00 2001 From: Paris Kasidiaris Date: Fri, 17 Nov 2017 13:39:52 +0200 Subject: [PATCH 04/13] Update README.md --- README.md | 52 ++++++++++++++++++++-------------------------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 4d7596ab..2bf3027b 100644 --- a/README.md +++ b/README.md @@ -49,11 +49,21 @@ To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to t Finally instantiate the `Terminal` object and then call the `open` function with the DOM object of the `div`. +### Importing + +The proposed way to load xterm.js is via the ES6 module syntax. + +```javascript +import { Terminal } from 'xterm'; +``` + +*Note: There are currently no typings for addons so you will need to upcast if using TypeScript, eg. `(xterm).fit()`.* + ### Addons -Addons are JavaScript modules that attach functions to the `Terminal` prototype to extend its functionality. There are a handful available in the main repository in the `dist/addons` directory, you can even write your own (though they may break when the internals of xterm.js change across versions). +Addons are JavaScript modules that extend the `Terminal` prototype with new methods and attributes to provide additional functionality. There are a handful available in the main repository in the `src/addons` directory and you can even write your own, by using xterm.js' public API. -To use an addon, just include the JavaScript file after xterm.js and before the `Terminal` object has been instantiated. The function should then be exposed on the `Terminal` object: +To use an addon, just import the JavaScript module and pass it to `Terminal`'s `applyAddon` method: ```html @@ -61,38 +71,16 @@ To use an addon, just include the JavaScript file after xterm.js and before the ``` ```js -// Instantiate the terminal and call fit -var xterm = new Terminal(); -xterm.fit(); +import { Terminal } from xterm; +import * as fit from 'xterm/addons/fit/fit'; + + +Terminal.applyAddon(fit); + +var xterm = new Terminal(); // Instantiate the terminal +xterm.fit(); // Use the `fit` method, provided by the `fit` addon ``` -### Importing - -If the environment allows it, you can import xterm.js like so: - -```ts -// CommonJS -var Terminal = require('xterm').Terminal; - -// ES6 / TypeScript -import { Terminal } from 'xterm'; -``` - -Importing addons in this environment can be done using a `Terminal.loadAddon` call: - -```ts -import { Terminal } from 'xterm'; - -// Notice it's called statically on the type, not an object -Terminal.loadAddon('fit'); - -// Instantiate the terminal and call fit -var xterm = new Terminal(); -xterm.fit(); -``` - -*Note: There are currently no typings for addons so you will need to upcast if using TypeScript, eg. `(xterm).fit()`* - ## Browser Support Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Here is a list of the versions we aim to support: From 8d54735321ce3e1e9143a835f299c10d303eccfb Mon Sep 17 00:00:00 2001 From: Paris Kasidiaris Date: Fri, 17 Nov 2017 13:46:17 +0200 Subject: [PATCH 05/13] Fix README.md --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 2bf3027b..13b1f11c 100644 --- a/README.md +++ b/README.md @@ -65,12 +65,7 @@ Addons are JavaScript modules that extend the `Terminal` prototype with new meth To use an addon, just import the JavaScript module and pass it to `Terminal`'s `applyAddon` method: -```html - - -``` - -```js +```javascript import { Terminal } from xterm; import * as fit from 'xterm/addons/fit/fit'; From 052dc15d55b3b918755620acd42a4bfdba2ef992 Mon Sep 17 00:00:00 2001 From: Paris Kasidiaris Date: Fri, 17 Nov 2017 13:52:42 +0200 Subject: [PATCH 06/13] Update README once more --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 13b1f11c..813f43c8 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ To use an addon, just import the JavaScript module and pass it to `Terminal`'s ` ```javascript import { Terminal } from xterm; -import * as fit from 'xterm/addons/fit/fit'; +import * as fit from 'xterm/lib/addons/fit/fit'; Terminal.applyAddon(fit); From d81e304ce7428494b7ffb4f7d8aea60b813fecb2 Mon Sep 17 00:00:00 2001 From: Paris Kasidiaris Date: Tue, 21 Nov 2017 12:53:43 +0200 Subject: [PATCH 07/13] Export static methods wherever possible --- src/addons/attach/attach.ts | 4 +- src/addons/fit/fit.ts | 4 +- src/addons/fullscreen/fullscreen.ts | 2 +- src/addons/search/search.ts | 81 +++++++++++-------------- src/addons/terminado/terminado.ts | 4 +- src/addons/winptyCompat/winptyCompat.ts | 16 +++-- 6 files changed, 51 insertions(+), 60 deletions(-) diff --git a/src/addons/attach/attach.ts b/src/addons/attach/attach.ts index 0142c35e..4827e070 100644 --- a/src/addons/attach/attach.ts +++ b/src/addons/attach/attach.ts @@ -16,7 +16,7 @@ * should happen instantly or at a maximum * frequency of 1 rendering per 10ms. */ -function attach(term, socket, bidirectional, buffered) { +export function attach(term, socket, bidirectional, buffered) { bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional; term.socket = socket; @@ -79,7 +79,7 @@ function attach(term, socket, bidirectional, buffered) { * @param {WebSocket} socket - The socket from which to detach the current * terminal. */ -function detach(term, socket) { +export function detach(term, socket) { term.off('data', term._sendData); socket = (typeof socket == 'undefined') ? term.socket : socket; diff --git a/src/addons/fit/fit.ts b/src/addons/fit/fit.ts index a372d0bb..ebd0177a 100644 --- a/src/addons/fit/fit.ts +++ b/src/addons/fit/fit.ts @@ -13,7 +13,7 @@ * row and truncate its width with the current number of columns). */ -function proposeGeometry(term) { +export function proposeGeometry(term) { if (!term.element.parentElement) { return null; } @@ -33,7 +33,7 @@ function proposeGeometry(term) { return geometry; }; -function fit(term) { +export function fit(term) { // Wrap fit in a setTimeout as charMeasure needs time to get initialized // after calling Terminal.open setTimeout(function () { diff --git a/src/addons/fullscreen/fullscreen.ts b/src/addons/fullscreen/fullscreen.ts index 7211b518..31ab5189 100644 --- a/src/addons/fullscreen/fullscreen.ts +++ b/src/addons/fullscreen/fullscreen.ts @@ -8,7 +8,7 @@ * @param {Terminal} term - The terminal to toggle full screen mode * @param {boolean} fullscreen - Toggle fullscreen on (true) or off (false) */ -function toggleFullScreen(term, fullscreen) { +export function toggleFullScreen(term, fullscreen) { var fn; if (typeof fullscreen == 'undefined') { diff --git a/src/addons/search/search.ts b/src/addons/search/search.ts index 69c5737b..aae0e392 100644 --- a/src/addons/search/search.ts +++ b/src/addons/search/search.ts @@ -4,54 +4,41 @@ */ import { SearchHelper } from './SearchHelper'; +import { ITerminal } from '../../Interfaces'; -declare var exports: any; -declare var module: any; -declare var define: any; -declare var require: any; -declare var window: any; -(function (addon) { - if (typeof window !== 'undefined' && 'Terminal' in window) { - /** - * Plain browser environment - */ - addon(window.Terminal); - } else if (typeof exports === 'object' && typeof module === 'object') { - /** - * CommonJS environment - */ - module.exports = addon(require('../../Terminal').Terminal); - } else if (typeof define === 'function') { - /** - * Require.js is available - */ - define(['../../xterm'], addon); +/** + * Find the next instance of the term, then scroll to and select it. If it + * doesn't exist, do nothing. + * @param term Tne search term. + * @return Whether a result was found. + */ +function findNext(terminal: ITerminal, term: string): boolean { + if (!terminal._searchHelper) { + terminal.searchHelper = new SearchHelper(terminal); } -})((Terminal: any) => { - /** - * Find the next instance of the term, then scroll to and select it. If it - * doesn't exist, do nothing. - * @param term Tne search term. - * @return Whether a result was found. - */ - Terminal.prototype.findNext = function(term: string): boolean { - if (!this._searchHelper) { - this.searchHelper = new SearchHelper(this); - } - return (this.searchHelper).findNext(term); - }; + return (terminal.searchHelper).findNext(term); +}; - /** - * Find the previous instance of the term, then scroll to and select it. If it - * doesn't exist, do nothing. - * @param term Tne search term. - * @return Whether a result was found. - */ - Terminal.prototype.findPrevious = function(term: string): boolean { - if (!this._searchHelper) { - this.searchHelper = new SearchHelper(this); - } - return (this.searchHelper).findPrevious(term); - }; -}); +/** + * Find the previous instance of the term, then scroll to and select it. If it + * doesn't exist, do nothing. + * @param term Tne search term. + * @return Whether a result was found. + */ +Terminal.prototype.findPrevious = function(terminal: ITerminal, term: string): boolean { + if (!terminal._searchHelper) { + terminal.searchHelper = new SearchHelper(terminal); + } + return (terminal.searchHelper).findPrevious(term); +}; + +export function apply(terminalConstructor) { + terminalConstructor.prototype.findNext = function(term) { + return findNext(this, term); + } + + terminalConstructor.prototype.findPrevious = function(term) { + return findPrevious(this, term); + } +} diff --git a/src/addons/terminado/terminado.ts b/src/addons/terminado/terminado.ts index 50bec2e8..9a7c7ad6 100644 --- a/src/addons/terminado/terminado.ts +++ b/src/addons/terminado/terminado.ts @@ -17,7 +17,7 @@ * should happen instantly or at a maximum * frequency of 1 rendering per 10ms. */ -function terminadoAttach(term, socket, bidirectional, buffered) { +export function terminadoAttach(term, socket, bidirectional, buffered) { bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional; term.socket = socket; @@ -72,7 +72,7 @@ function terminadoAttach(term, socket, bidirectional, buffered) { * @param {WebSocket} socket - The socket from which to detach the current * terminal. */ -function terminadoDetach(term, socket) { +export function terminadoDetach(term, socket) { term.off('data', term._sendData); socket = (typeof socket == 'undefined') ? term.socket : socket; diff --git a/src/addons/winptyCompat/winptyCompat.ts b/src/addons/winptyCompat/winptyCompat.ts index c9e9a53a..9dc46e46 100644 --- a/src/addons/winptyCompat/winptyCompat.ts +++ b/src/addons/winptyCompat/winptyCompat.ts @@ -3,8 +3,7 @@ * @license MIT */ -export function apply(terminalConstructor) { - terminalConstructor.prototype.winptyCompatInit = function(): void { +export function winptyCompatInit(terminal): void { // Don't do anything when the platform is not Windows const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0; if (!isWindows) { @@ -21,13 +20,18 @@ export function apply(terminalConstructor) { // space. This is certainly not without its problems, but generally on // Windows when text reaches the end of the terminal it's likely going to be // wrapped. - this.on('lineFeed', () => { - const line = this.buffer.lines.get(this.buffer.ybase + this.buffer.y - 1); - const lastChar = line[this.cols - 1]; + terminal.on('lineFeed', () => { + const line = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y - 1); + const lastChar = line[terminal.cols - 1]; + if (lastChar[3] !== 32 /* ' ' */) { - const nextLine = this.buffer.lines.get(this.buffer.ybase + this.buffer.y); + const nextLine = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y); (nextLine).isWrapped = true; } }); + +export function apply(terminalConstructor) { + terminalConstructor.prototype.winptyCompatInit = function(): void { + winptyCompatInit(this); }; } From 6fec430e320912fea45618893f932f42b58fba87 Mon Sep 17 00:00:00 2001 From: Paris Kasidiaris Date: Wed, 22 Nov 2017 16:22:47 +0200 Subject: [PATCH 08/13] Fix tests --- src/addons/search/search.ts | 5 ++--- src/addons/winptyCompat/winptyCompat.ts | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/addons/search/search.ts b/src/addons/search/search.ts index aae0e392..c9814802 100644 --- a/src/addons/search/search.ts +++ b/src/addons/search/search.ts @@ -4,7 +4,6 @@ */ import { SearchHelper } from './SearchHelper'; -import { ITerminal } from '../../Interfaces'; /** @@ -13,7 +12,7 @@ import { ITerminal } from '../../Interfaces'; * @param term Tne search term. * @return Whether a result was found. */ -function findNext(terminal: ITerminal, term: string): boolean { +function findNext(terminal: any, term: string): boolean { if (!terminal._searchHelper) { terminal.searchHelper = new SearchHelper(terminal); } @@ -26,7 +25,7 @@ function findNext(terminal: ITerminal, term: string): boolean { * @param term Tne search term. * @return Whether a result was found. */ -Terminal.prototype.findPrevious = function(terminal: ITerminal, term: string): boolean { +function findPrevious(terminal: any, term: string): boolean { if (!terminal._searchHelper) { terminal.searchHelper = new SearchHelper(terminal); } diff --git a/src/addons/winptyCompat/winptyCompat.ts b/src/addons/winptyCompat/winptyCompat.ts index 9dc46e46..c84a04a6 100644 --- a/src/addons/winptyCompat/winptyCompat.ts +++ b/src/addons/winptyCompat/winptyCompat.ts @@ -29,6 +29,7 @@ export function winptyCompatInit(terminal): void { (nextLine).isWrapped = true; } }); +} export function apply(terminalConstructor) { terminalConstructor.prototype.winptyCompatInit = function(): void { From e39d10e12fe487b33a36d39ddc1d7bada93c9411 Mon Sep 17 00:00:00 2001 From: Paris Kasidiaris Date: Fri, 8 Dec 2017 13:12:11 +0200 Subject: [PATCH 09/13] Fix exports --- src/addons/attach/attach.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/addons/attach/attach.ts b/src/addons/attach/attach.ts index 4827e070..aade7291 100644 --- a/src/addons/attach/attach.ts +++ b/src/addons/attach/attach.ts @@ -104,7 +104,7 @@ export function apply(terminalConstructor) { * frequency of 1 rendering per 10ms. */ terminalConstructor.prototype.attach = function(socket, bidirectional, buffered) { - return exports.attach(this, socket, bidirectional, buffered); + return attach(this, socket, bidirectional, buffered); }; /** @@ -114,6 +114,6 @@ export function apply(terminalConstructor) { * terminal. */ terminalConstructor.prototype.detach = function(socket) { - return exports.detach(this, socket); + return detach(this, socket); }; } From 8830820821075bb89c81812e5127d64b5de3b8b3 Mon Sep 17 00:00:00 2001 From: Paris Kasidiaris Date: Tue, 12 Dec 2017 10:45:46 +0200 Subject: [PATCH 10/13] Update demo to use Webpack --- .gitignore | 3 +++ Dockerfile | 4 ++-- Procfile.dev | 1 - demo/app.js | 8 ++++++-- demo/index.html | 8 +------- demo/main.js | 15 +++++++++++++++ docker-compose.yaml | 9 --------- docker-compose.yml | 9 +++++++++ gulpfile.js | 12 ++++++++---- package.json | 8 +++++--- src/addons/search/search.ts | 4 ++-- webpack.config.js | 10 ++++++++++ 12 files changed, 61 insertions(+), 30 deletions(-) delete mode 100644 Procfile.dev delete mode 100644 docker-compose.yaml create mode 100644 docker-compose.yml create mode 100644 webpack.config.js diff --git a/.gitignore b/.gitignore index 8e28c797..bf0c3d8c 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ fixtures/typings-test/*.js dist/ src/utils/TestUtils.ts src/xterm.js + +# Keep the demo builds out of Git +demo/dist/ diff --git a/Dockerfile b/Dockerfile index 1f0db1f9..22556f43 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:6.9 +FROM node:6 MAINTAINER Paris Kasidiaris # Set the working directory @@ -6,7 +6,7 @@ WORKDIR /usr/src/app # Set an entrypoint, to automatically install node modules ENTRYPOINT ["/bin/bash", "-c", "if [[ ! -d node_modules ]]; then npm install; fi; exec \"${@:0}\";"] -CMD ["npm", "run", "dev"] +CMD ["npm", "run", "start"] # First, install dependencies to improve layer caching COPY package.json /usr/src/app/ diff --git a/Procfile.dev b/Procfile.dev deleted file mode 100644 index 8d2c5445..00000000 --- a/Procfile.dev +++ /dev/null @@ -1 +0,0 @@ -web: npm run dev diff --git a/demo/app.js b/demo/app.js index 8637dad8..7604e9cd 100644 --- a/demo/app.js +++ b/demo/app.js @@ -17,8 +17,12 @@ app.get('/style.css', function(req, res){ res.sendFile(__dirname + '/style.css'); }); -app.get('/main.js', function(req, res){ - res.sendFile(__dirname + '/main.js'); +app.get('/dist/bundle.js', function(req, res){ + res.sendFile(__dirname + '/dist/bundle.js'); +}); + +app.get('/dist/bundle.js.map', function(req, res){ + res.sendFile(__dirname + '/dist/bundle.js.map'); }); app.post('/terminals', function (req, res) { diff --git a/demo/index.html b/demo/index.html index 8d900903..93399f9c 100644 --- a/demo/index.html +++ b/demo/index.html @@ -7,12 +7,6 @@ - - - - - -

xterm.js: xterm, in the browser

@@ -71,6 +65,6 @@

Attention: The demo is a barebones implementation and is designed for xterm.js evaluation purposes only. Exposing the demo to the public as is would introduce security risks for the host.

- + diff --git a/demo/main.js b/demo/main.js index 9de682bf..58d79e19 100644 --- a/demo/main.js +++ b/demo/main.js @@ -1,3 +1,18 @@ +import * as Terminal from '../build/xterm'; +import * as attach from '../build/addons/attach/attach'; +import * as fit from '../build/addons/fit/fit'; +import * as fullscreen from '../build/addons/fullscreen/fullscreen'; +import * as search from '../build/addons/search/search'; +import * as winptyCompat from '../build/addons/winptyCompat/winptyCompat'; + + +Terminal.applyAddon(attach); +Terminal.applyAddon(fit); +Terminal.applyAddon(fullscreen); +Terminal.applyAddon(search); +Terminal.applyAddon(winptyCompat); + + var term, protocol, socketURL, diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index 9579dcf5..00000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,9 +0,0 @@ -version: '2' - -services: - web: - build: ./ - volumes: - - ./:/usr/src/app - ports: - - 3000:3000 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..03e88fbc --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,9 @@ +version: "3" + +services: + web: + build: . + volumes: + - ./:/usr/src/app + ports: + - ${XTERMJS_PORT:3000}:3000 diff --git a/gulpfile.js b/gulpfile.js index 0c3fe0f3..907529c4 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -16,6 +16,7 @@ const source = require('vinyl-source-stream'); const sourcemaps = require('gulp-sourcemaps'); const ts = require('gulp-typescript'); const util = require('gulp-util'); +const webpack = require('webpack-stream'); let buildDir = process.env.BUILD_DIR || 'build'; let tsProject = ts.createProject('tsconfig.json'); @@ -137,10 +138,7 @@ gulp.task('browserify-addons', ['tsc'], function() { // Copy all add-ons from outDir to buildDir let copyAddons = gulp.src([ // Copy JS addons - `${outDir}/addons/**/*`, - // Exclude TS addons from copy as they are being built via browserify - `!${outDir}/addons/search`, - `!${outDir}/addons/search/**`, + `${outDir}/addons/**/*` ]).pipe(gulp.dest(`${buildDir}/addons`)); return merge(searchBundle, winptyCompatBundle, copyAddons); @@ -196,6 +194,12 @@ gulp.task('sorcery-addons', ['browserify-addons'], function () { chain.writeSync(); }); +gulp.task('webpack', ['build'], function() { + return gulp.src('demo/main.js') + .pipe(webpack()) + .pipe(gulp.dest('demo/dist/')); +}); + /** * Submit coverage results to coveralls.io */ diff --git a/package.json b/package.json index 747d6d0a..22f60b75 100644 --- a/package.json +++ b/package.json @@ -69,20 +69,22 @@ "typescript": "~2.4.0", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0", + "webpack": "^3.10.0", + "webpack-stream": "^4.0.0", "zmodem.js": "^0.1.5" }, "scripts": { - "prestart": "npm run build", + "prestart": "gulp webpack", "start": "node demo/app", "prestart-zmodem": "npm run build", "start-zmodem": "node build/addons/zmodem/demo/app", - "dev": "nodemon -e js,ts,css --watch src --watch demo --exec npm start", "lint": "tslint src/*.ts src/**/*.ts", "test": "gulp test", "build:docs": "jsdoc -c jsdoc.json", "build": "gulp build", "prepublish": "npm run build", - "coveralls": "gulp coveralls" + "coveralls": "gulp coveralls", + "webpack": "webpack" }, "dependencies": {} } diff --git a/src/addons/search/search.ts b/src/addons/search/search.ts index c9814802..480fea7f 100644 --- a/src/addons/search/search.ts +++ b/src/addons/search/search.ts @@ -12,7 +12,7 @@ import { SearchHelper } from './SearchHelper'; * @param term Tne search term. * @return Whether a result was found. */ -function findNext(terminal: any, term: string): boolean { +export function findNext(terminal: any, term: string): boolean { if (!terminal._searchHelper) { terminal.searchHelper = new SearchHelper(terminal); } @@ -25,7 +25,7 @@ function findNext(terminal: any, term: string): boolean { * @param term Tne search term. * @return Whether a result was found. */ -function findPrevious(terminal: any, term: string): boolean { +export function findPrevious(terminal: any, term: string): boolean { if (!terminal._searchHelper) { terminal.searchHelper = new SearchHelper(terminal); } diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 00000000..0c39626e --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,10 @@ +const path = require('path'); + +module.exports = { + entry: './demo/main.js', + output: { + path: path.resolve(__dirname, 'demo/dist'), + filename: 'bundle.js' + }, + devtool: 'source-map' +}; From c9b672e0c6b621b6f794de5af18977143d33ffab Mon Sep 17 00:00:00 2001 From: Paris Kasidiaris Date: Tue, 12 Dec 2017 10:47:58 +0200 Subject: [PATCH 11/13] Simplify fit addon --- src/addons/fit/fit.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/addons/fit/fit.ts b/src/addons/fit/fit.ts index ebd0177a..1f906ee3 100644 --- a/src/addons/fit/fit.ts +++ b/src/addons/fit/fit.ts @@ -50,11 +50,6 @@ export function fit(term) { }; export function apply(terminalConstructor) { - terminalConstructor.prototype.proposeGeometry = function () { - return proposeGeometry(this); - }; - - terminalConstructor.prototype.fit = function () { - return fit(this); - }; + terminalConstructor.prototype.proposeGeometry = proposeGeometry.bind(this, this); + terminalConstructor.prototype.fit = fit.bind(this, this); } From 80450450e91abff3deb9426ce3d408ef4ad2331b Mon Sep 17 00:00:00 2001 From: Paris Kasidiaris Date: Tue, 12 Dec 2017 10:50:18 +0200 Subject: [PATCH 12/13] Export zmodemAttach --- src/addons/zmodem/zmodem.ts | 109 ++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 55 deletions(-) diff --git a/src/addons/zmodem/zmodem.ts b/src/addons/zmodem/zmodem.ts index 3a79d1c8..68ce691c 100644 --- a/src/addons/zmodem/zmodem.ts +++ b/src/addons/zmodem/zmodem.ts @@ -27,63 +27,62 @@ * via `detach()` and a re-`attach()`.) */ +export function zmodemAttach(term, ws, opts) { + if (!opts) opts = {}; + + var senderFunc = function _ws_sender_func(octets) { + ws.send(new Uint8Array(octets)); + }; + + var zsentry; + + function _shouldWrite() { + return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession; + } + + zsentry = new Zmodem.Sentry( { + to_terminal: function _to_terminal(octets) { + if (_shouldWrite()) { + term.write( + String.fromCharCode.apply(String, octets) + ); + } + }, + + sender: senderFunc, + + on_retract: function _on_retract() { + term.emit('zmodemRetract'); + }, + + on_detect: function _on_detect(detection) { + term.emit('zmodemDetect', detection); + }, + } ); + + function handleWSMessage(evt) { + + //In testing with xterm.js’s demo the first message was + //always text even if the rest were binary. While that + //may be specific to xterm.js’s demo, ultimately we + //should reject anything that isn’t binary. + if (typeof evt.data === 'string') { + if (_shouldWrite()) { + term.write(evt.data); + } + } + else { + zsentry.consume(evt.data); + } + } + + ws.binaryType = 'arraybuffer'; + ws.addEventListener('message', handleWSMessage); +} + export function apply(terminalConstructor) { let Zmodem = (typeof window == 'object') ? (window).ZModem : {Browser: null}; // Nullify browser for tests - terminalConstructor.prototype.zmodemAttach = function(ws, opts) { - var term = this; - - if (!opts) opts = {}; - - var senderFunc = function _ws_sender_func(octets) { - ws.send( new Uint8Array(octets) ); - }; - - var zsentry; - - function _shouldWrite() { - return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession; - } - - zsentry = new Zmodem.Sentry( { - to_terminal: function _to_terminal(octets) { - if (_shouldWrite()) { - term.write( - String.fromCharCode.apply(String, octets) - ); - } - }, - - sender: senderFunc, - - on_retract: function _on_retract() { - term.emit('zmodemRetract'); - }, - - on_detect: function _on_detect(detection) { - term.emit('zmodemDetect', detection); - }, - } ); - - function handleWSMessage(evt) { - - //In testing with xterm.js’s demo the first message was - //always text even if the rest were binary. While that - //may be specific to xterm.js’s demo, ultimately we - //should reject anything that isn’t binary. - if (typeof evt.data === 'string') { - if (_shouldWrite()) { - term.write(evt.data); - } - } - else { - zsentry.consume(evt.data); - } - } - - ws.binaryType = 'arraybuffer'; - ws.addEventListener('message', handleWSMessage); - } - + terminalConstructor.prototype.zmodemAttach = zmodemAttach.bind(this, this); terminalConstructor.prototype.zmodemBrowser = Zmodem.Browser } From da604ff69c6a17910c89f4895811d88b6122d828 Mon Sep 17 00:00:00 2001 From: Paris Kasidiaris Date: Sat, 16 Dec 2017 11:29:12 +0200 Subject: [PATCH 13/13] Fix `webpack` task in Gulp and `fit` add-on --- demo/main.js | 4 +++- gulpfile.js | 2 +- package.json | 2 +- src/addons/fit/fit.ts | 9 +++++++-- src/addons/zmodem/zmodem.ts | 4 +++- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/demo/main.js b/demo/main.js index 58d79e19..9fa5964d 100644 --- a/demo/main.js +++ b/demo/main.js @@ -91,6 +91,7 @@ function createTerminal() { scrollback: parseInt(optionElements.scrollback.value, 10), tabStopWidth: parseInt(optionElements.tabstopwidth.value, 10) }); + window.term = term; // Expose `term` to window for debugging purposes term.on('resize', function (size) { if (!pid) { return; @@ -105,8 +106,9 @@ function createTerminal() { socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/'; term.open(terminalContainer); - term.fit(); term.winptyCompatInit(); + term.fit(); + term.focus(); // fit is called within a setTimeout, cols and rows need this. setTimeout(function () { diff --git a/gulpfile.js b/gulpfile.js index 907529c4..49d67ed6 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -196,7 +196,7 @@ gulp.task('sorcery-addons', ['browserify-addons'], function () { gulp.task('webpack', ['build'], function() { return gulp.src('demo/main.js') - .pipe(webpack()) + .pipe(webpack(require('./webpack.config.js'))) .pipe(gulp.dest('demo/dist/')); }); diff --git a/package.json b/package.json index 22f60b75..e4bb08c1 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "build": "gulp build", "prepublish": "npm run build", "coveralls": "gulp coveralls", - "webpack": "webpack" + "webpack": "gulp webpack" }, "dependencies": {} } diff --git a/src/addons/fit/fit.ts b/src/addons/fit/fit.ts index 1f906ee3..14addfdf 100644 --- a/src/addons/fit/fit.ts +++ b/src/addons/fit/fit.ts @@ -50,6 +50,11 @@ export function fit(term) { }; export function apply(terminalConstructor) { - terminalConstructor.prototype.proposeGeometry = proposeGeometry.bind(this, this); - terminalConstructor.prototype.fit = fit.bind(this, this); + terminalConstructor.prototype.proposeGeometry = function() { + return proposeGeometry(this); + } + + terminalConstructor.prototype.fit = function() { + return fit(this); + } } diff --git a/src/addons/zmodem/zmodem.ts b/src/addons/zmodem/zmodem.ts index 68ce691c..57bdde3c 100644 --- a/src/addons/zmodem/zmodem.ts +++ b/src/addons/zmodem/zmodem.ts @@ -27,6 +27,8 @@ * via `detach()` and a re-`attach()`.) */ +let Zmodem; + export function zmodemAttach(term, ws, opts) { if (!opts) opts = {}; @@ -81,7 +83,7 @@ export function zmodemAttach(term, ws, opts) { } export function apply(terminalConstructor) { - let Zmodem = (typeof window == 'object') ? (window).ZModem : {Browser: null}; // Nullify browser for tests + Zmodem = (typeof window == 'object') ? (window).ZModem : {Browser: null}; // Nullify browser for tests terminalConstructor.prototype.zmodemAttach = zmodemAttach.bind(this, this); terminalConstructor.prototype.zmodemBrowser = Zmodem.Browser