Merge pull request #1130 from xtermjs/applyaddon

New addon system: `applyAddon`
This commit is contained in:
Paris Kasidiaris
2017-12-18 21:44:00 +02:00
committed by GitHub
41 changed files with 862 additions and 712 deletions
+3
View File
@@ -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/
+2 -2
View File
@@ -1,4 +1,4 @@
FROM node:6.9
FROM node:6
MAINTAINER Paris Kasidiaris <paris@sourcelair.com>
# 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/
-1
View File
@@ -1 +0,0 @@
web: npm run dev
+16 -33
View File
@@ -49,50 +49,33 @@ 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`.
### 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).
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:
```html
<script src="node_modules/xterm/dist/xterm.js"></script>
<script src="node_modules/xterm/dist/addons/fit/fit.js"></script>
```
```js
// Instantiate the terminal and call fit
var xterm = new Terminal();
xterm.fit();
```
### Importing
If the environment allows it, you can import xterm.js like so:
The proposed way to load xterm.js is via the ES6 module syntax.
```ts
// CommonJS
var Terminal = require('xterm').Terminal;
// ES6 / TypeScript
```javascript
import { Terminal } from 'xterm';
```
Importing addons in this environment can be done using a `Terminal.loadAddon` call:
*Note: There are currently no typings for addons so you will need to upcast if using TypeScript, eg. `(<any>xterm).fit()`.*
```ts
import { Terminal } from 'xterm';
### Addons
// Notice it's called statically on the type, not an object
Terminal.loadAddon('fit');
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.
// Instantiate the terminal and call fit
var xterm = new Terminal();
xterm.fit();
To use an addon, just import the JavaScript module and pass it to `Terminal`'s `applyAddon` method:
```javascript
import { Terminal } from xterm;
import * as fit from 'xterm/lib/addons/fit/fit';
Terminal.applyAddon(fit);
var xterm = new Terminal(); // Instantiate the terminal
xterm.fit(); // Use the `fit` method, provided by the `fit` addon
```
*Note: There are currently no typings for addons so you will need to upcast if using TypeScript, eg. `(<any>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:
+6 -2
View File
@@ -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) {
+1 -7
View File
@@ -7,12 +7,6 @@
<link rel="stylesheet" href="style.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-promise/4.1.1/es6-promise.auto.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/1.0.0/fetch.min.js"></script>
<script src="/build/xterm.js" ></script>
<script src="/build/addons/attach/attach.js" ></script>
<script src="/build/addons/fit/fit.js" ></script>
<script src="/build/addons/fullscreen/fullscreen.js" ></script>
<script src="/build/addons/search/search.js" ></script>
<script src="/build/addons/winptyCompat/winptyCompat.js" ></script>
</head>
<body>
<h1>xterm.js: xterm, in the browser</h1>
@@ -71,6 +65,6 @@
</div>
</div>
<p><strong>Attention:</strong> 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.</p>
<script src="main.js" defer ></script>
<script src="dist/bundle.js" defer ></script>
</body>
</html>
+18 -1
View File
@@ -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,
@@ -76,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;
@@ -90,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 () {
-9
View File
@@ -1,9 +0,0 @@
version: '2'
services:
web:
build: ./
volumes:
- ./:/usr/src/app
ports:
- 3000:3000
+9
View File
@@ -0,0 +1,9 @@
version: "3"
services:
web:
build: .
volumes:
- ./:/usr/src/app
ports:
- ${XTERMJS_PORT:3000}:3000
+23 -18
View File
@@ -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');
@@ -44,30 +45,31 @@ gulp.task('tsc', function () {
tsResult.dts.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest(outDir))
);
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 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 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 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/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);
});
/**
@@ -136,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);
@@ -195,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(require('./webpack.config.js')))
.pipe(gulp.dest('demo/dist/'));
});
/**
* Submit coverage results to coveralls.io
*/
+122 -23
View File
@@ -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"
}
}
}
}
+6 -3
View File
@@ -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",
@@ -68,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": "gulp webpack"
},
"dependencies": {}
}
+5 -4
View File
@@ -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 (<any>Terminal).prototype.attach, 'function');
});
+4 -21
View File
@@ -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 (<any>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.apply(Terminal);
}
/**
-141
View File
@@ -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;
});
+20
View File
@@ -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 (<any>MockTerminal).prototype.attach, 'function');
assert.equal(typeof (<any>MockTerminal).prototype.detach, 'function');
});
});
});
+119
View File
@@ -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.
*/
export 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.
*/
export 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 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.attach = function(socket, bidirectional, buffered) {
return 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 detach(this, socket);
};
}
+10
View File
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"rootDir": ".",
"outDir": "../../../lib/addons/attach/",
"sourceMap": true,
"removeComments": true
}
}
-81
View File
@@ -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;
});
+20
View File
@@ -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 (<any>MockTerminal).prototype.proposeGeometry, 'function');
assert.equal(typeof (<any>MockTerminal).prototype.fit, 'function');
});
});
});
+60
View File
@@ -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).
*/
export 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;
};
export 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);
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"rootDir": ".",
"outDir": "../../../lib/addons/fit/",
"sourceMap": true,
"removeComments": true
}
}
-50
View File
@@ -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;
});
+19
View File
@@ -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 (<any>MockTerminal).prototype.toggleFullScreen, 'function');
});
});
});
+29
View File
@@ -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)
*/
export 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);
};
}
+10
View File
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"rootDir": ".",
"outDir": "../../../lib/addons/fullscreen/",
"sourceMap": true,
"removeComments": true
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"name": "xterm.search",
"main": "search.js",
"private": true
}
+33 -47
View File
@@ -5,53 +5,39 @@
import { SearchHelper } from './SearchHelper';
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.
*/
export function findNext(terminal: any, 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 (<SearchHelper>this.searchHelper).findNext(term);
};
return (<SearchHelper>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 (<SearchHelper>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.
*/
export function findPrevious(terminal: any, term: string): boolean {
if (!terminal._searchHelper) {
terminal.searchHelper = new SearchHelper(terminal);
}
return (<SearchHelper>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);
}
}
-134
View File
@@ -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;
});
+20
View File
@@ -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 (<any>MockTerminal).prototype.terminadoAttach, 'function');
assert.equal(typeof (<any>MockTerminal).prototype.terminadoDetach, 'function');
});
});
});
+111
View File
@@ -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.
*/
export 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.
*/
export 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);
};
}
+10
View File
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"rootDir": ".",
"outDir": "../../../lib/addons/terminado/",
"sourceMap": true,
"removeComments": true
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"name": "xterm.winptyCompat",
"main": "winptyCompat.js",
"private": true
}
@@ -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 (<any>MockTerminal).prototype.winptyCompatInit, 'function');
});
});
});
+12 -27
View File
@@ -3,28 +3,7 @@
* @license MIT
*/
declare var exports: any;
declare var define: any;
(function (addon) {
if (typeof window !== 'undefined' && 'Terminal' in window) {
/**
* Plain browser environment
*/
addon((<any>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 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) {
@@ -41,13 +20,19 @@ declare var define: any;
// 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);
(<any>nextLine).isWrapped = true;
}
});
}
export function apply(terminalConstructor) {
terminalConstructor.prototype.winptyCompatInit = function(): void {
winptyCompatInit(this);
};
});
}
+5
View File
@@ -0,0 +1,5 @@
{
"name": "xterm.zmodem",
"main": "zmodem.js",
"private": true
}
+10
View File
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"rootDir": ".",
"outDir": "../../../lib/addons/zmodem/",
"sourceMap": true,
"removeComments": true
}
}
-108
View File
@@ -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(<WebSocket>, <Object>)` - 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,
}
);
});
+20
View File
@@ -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 (<any>MockTerminal).prototype.zmodemAttach, 'function');
assert.equal(typeof (<any>MockTerminal).prototype.zmodemBrowser, 'object');
});
});
});
+90
View File
@@ -0,0 +1,90 @@
/**
*
* 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(<WebSocket>, <Object>)` - 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()`.)
*/
let Zmodem;
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) {
Zmodem = (typeof window == 'object') ? (<any>window).ZModem : {Browser: null}; // Nullify browser for tests
terminalConstructor.prototype.zmodemAttach = zmodemAttach.bind(this, this);
terminalConstructor.prototype.zmodemBrowser = Zmodem.Browser
}
+10
View File
@@ -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'
};