diff --git a/gulpfile.js b/gulpfile.js index c1c3a7898..0c3fe0f39 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 5f84d8a2f..7ae64fbc2 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 712d911d4..000000000 --- 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 000000000..9bf0e0707 --- /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 e554a0f3a..0142c35e7 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 c82039abf..000000000 --- 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 000000000..159cc987e --- /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 000000000..a372d0bb8 --- /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 000000000..949f8b60c --- /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 1474d2db6..000000000 --- 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 000000000..c77ea75c9 --- /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 000000000..7211b5182 --- /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 000000000..b04144c13 --- /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 000000000..552e9332b --- /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 4cf08227b..000000000 --- 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 000000000..fe059898b --- /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 000000000..50bec2e80 --- /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 000000000..775b20d57 --- /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 000000000..2e9fc1644 --- /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 000000000..21e54e567 --- /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 7474df2e6..c9e9a53a3 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 000000000..218130ab5 --- /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 000000000..9ded72e57 --- /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 1bab2976b..000000000 --- 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 000000000..3cb283162 --- /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 000000000..3a79d1c89 --- /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 +}