Merge branch 'master' into feature-778
This commit is contained in:
@@ -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/
|
||||
|
||||
@@ -19,45 +19,63 @@ Bill Church <billchurch@users.noreply.github.com>
|
||||
Bob Reid <bobreid@Bobs-MacBook-Pro.local>
|
||||
bottleofwater <nison.mael+bottleofwater@gmail.com>
|
||||
Brian Mock <brian@mockbrian.com>
|
||||
Bruno Ribeito <b.m.fernandes.ribeiro@gmail.com>
|
||||
Carson Anderson <carson@betterservers.com>
|
||||
CHaBou <chabup@delean.fr>
|
||||
Christian Budde Christensen <budde377@gmail.com>
|
||||
Christof Marti <chrmarti@microsoft.com>
|
||||
Christopher Jeffrey <chjjeffrey@gmail.com>
|
||||
coderaiser <mnemonic.enemy@gmail.com>
|
||||
Dan Brown <jdanbrown@gmail.com>
|
||||
Daniel Griffen <daniel@griffen.io>
|
||||
Daniel Imms <daimms@microsoft.com>
|
||||
Daniel Risacher <drisacher@gmail.com>
|
||||
Dan Kaplun <dbkaplun@twitch.tv>
|
||||
Darin Morrison <freebroccolo@users.noreply.github.com>
|
||||
dcylabs <dcylabs@gmail.com>
|
||||
Dominik Csapak <d.csapak@proxmox.com>
|
||||
Edgar Andrés Margffoy Tuay <andfoy@gmail.com>
|
||||
Elliot Saba <staticfloat@gmail.com>
|
||||
Exile <exile@chamber.ee>
|
||||
Felipe Gasper <felipe@felipegasper.com>
|
||||
ficristo <ficristo.work@gmail.com>
|
||||
Gary Ritchie <gary@rstudio.com>
|
||||
hiro-su <h.sugipon@gmail.com>
|
||||
Ian Lewis <ianlewis@google.com>
|
||||
imoses <ido@twiggle.com>
|
||||
InDieTasten <indietasten@gmail.com>
|
||||
irokas <akasidiari@inf.uth.gr>
|
||||
Jakob Gillich <jakob@gillich.me>
|
||||
Jean Bruenn <himself@jeanbruenn.info>
|
||||
Jeremy Danyow <jedanyow@microsoft.com>
|
||||
Joao Moreno <jomo@microsoft.com>
|
||||
Joao Moreno <mail@joaomoreno.com>
|
||||
Johannes Zellner <johannes@nebulon.de>
|
||||
Jon Masters <jon.masters@sky.com>
|
||||
Jörg Breitbart <jerch@rockborn.de>
|
||||
Justin Mecham <justin@mecham.me>
|
||||
Kirill Merkushev <lanwen@yandex.ru>
|
||||
Luca <LucaT1@users.noreply.github.com>
|
||||
Lucian Buzzo <lucian.buzzo@gmail.com>
|
||||
Lukas Drgon <lukas.drgon@gmail.com>
|
||||
Maël Nison <nison.mael@gmail.com>
|
||||
Marc Dumais <marc.dumais@ericsson.com>
|
||||
Martin Chloride <i@martincl2.me>
|
||||
Martin Koppehel <martin.koppehel@st.ovgu.de>
|
||||
Martin Wang <jiahaow@ca.ibm.com>
|
||||
Matt Bierner <matb@microsoft.com>
|
||||
Michael Irwin <mikesir87@gmail.com>
|
||||
Mikko Karvonen <mikko.karvonen@arm.com>
|
||||
mofux <t.zilz@mofux.org>
|
||||
Nicolas Ramz <nicolas.ramz@gmail.com>
|
||||
npezza93 <npezza93@gmail.com>
|
||||
Oleksandr Andriienko <oandriie@redhat.com>
|
||||
Paris Kasidiaris <pariskasidiaris@gmail.com>
|
||||
Paris Kasidiaris <paris@sourcelair.com>
|
||||
Peter Baumgarten <me@peterbaumgarten.com>
|
||||
Rick Baker <rick@ricktbaker.com>
|
||||
runarberg <runar@greenqloud.com>
|
||||
Samuel Williams <samuel.williams@oriontransfer.co.nz>
|
||||
Saswat Das <saswatds@users.noreply.github.com>
|
||||
Saul Costa <saul@codevolve.com>
|
||||
Shuanglei Tao <tsl0922@gmail.com>
|
||||
|
||||
+2
-2
@@ -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 +0,0 @@
|
||||
web: npm run dev
|
||||
@@ -1,6 +1,6 @@
|
||||
# [](https://xtermjs.org)
|
||||
# [](https://xtermjs.org)
|
||||
|
||||
[](https://travis-ci.org/sourcelair/xterm.js) [](https://coveralls.io/github/sourcelair/xterm.js) [](https://gitter.im/sourcelair/xterm.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](https://www.jsdelivr.com/package/npm/xterm)
|
||||
[](https://travis-ci.org/xtermjs/xterm.js) [](https://coveralls.io/github/sourcelair/xterm.js) [](https://gitter.im/sourcelair/xterm.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](https://www.jsdelivr.com/package/npm/xterm)
|
||||
|
||||
Xterm.js is a terminal front-end component written in JavaScript that works in the browser.
|
||||
|
||||
@@ -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:
|
||||
@@ -131,6 +114,11 @@ Xterm.js is used in several world-class applications to provide great terminal e
|
||||
computational environment for Jupyter, supporting interactive data science and scientific computing across all programming languages.
|
||||
- [**Theia**](https://github.com/theia-ide/theia): Theia is a cloud & desktop IDE framework implemented in TypeScript.
|
||||
- [**Opshell**](https://github.com/ricktbaker/opshell) Ops Helper tool to make life easier working with AWS instances across multiple organizations.
|
||||
- [**Proxmox VE**](https://www.proxmox.com/en/proxmox-ve): Proxmox VE is a complete open-source platform for enterprise virtualization. It uses xterm.js for container terminals and the host shell.
|
||||
- [**Script Runner**](https://github.com/ioquatix/script-runner): Run scripts (or a shell) in Atom.
|
||||
- [**Whack Whack Terminal**](https://github.com/Microsoft/WhackWhackTerminal): Terminal emulator for Visual Studio 2017.
|
||||
- [**VTerm**](https://github.com/vterm/vterm): Extensible terminal emulator based on Electron and React.
|
||||
- [**electerm**](http://electerm.html5beta.com): electerm is a terminal/ssh/sftp client(mac, win, linux) based on electron/node-pty/xterm.
|
||||
|
||||
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it in our list.
|
||||
|
||||
|
||||
+6
-2
@@ -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
@@ -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>
|
||||
|
||||
+23
-6
@@ -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,
|
||||
@@ -24,8 +39,8 @@ function setTerminalSize() {
|
||||
var rows = parseInt(rowsElement.value, 10);
|
||||
var viewportElement = document.querySelector('.xterm-viewport');
|
||||
var scrollBarWidth = viewportElement.offsetWidth - viewportElement.clientWidth;
|
||||
var width = (cols * term.charMeasure.width + 20 /*room for scrollbar*/).toString() + 'px';
|
||||
var height = (rows * term.charMeasure.height).toString() + 'px';
|
||||
var width = (cols * term.renderer.dimensions.actualCellWidth + 20 /*room for scrollbar*/).toString() + 'px';
|
||||
var height = (rows * term.renderer.dimensions.actualCellHeight).toString() + 'px';
|
||||
|
||||
terminalContainer.style.width = width;
|
||||
terminalContainer.style.height = height;
|
||||
@@ -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 () {
|
||||
@@ -103,9 +120,9 @@ function createTerminal() {
|
||||
|
||||
fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, {method: 'POST'}).then(function (res) {
|
||||
|
||||
res.text().then(function (pid) {
|
||||
window.pid = pid;
|
||||
socketURL += pid;
|
||||
res.text().then(function (processId) {
|
||||
pid = processId;
|
||||
socketURL += processId;
|
||||
socket = new WebSocket(socketURL);
|
||||
socket.onopen = runRealTerminal;
|
||||
socket.onclose = runFakeTerminal;
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
web:
|
||||
build: ./
|
||||
volumes:
|
||||
- ./:/usr/src/app
|
||||
ports:
|
||||
- 3000:3000
|
||||
@@ -0,0 +1,9 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
volumes:
|
||||
- ./:/usr/src/app
|
||||
ports:
|
||||
- ${XTERMJS_PORT:3000}:3000
|
||||
@@ -36,12 +36,12 @@ namespace properties {
|
||||
|
||||
namespace static_methods {
|
||||
{
|
||||
Terminal.loadAddon('attach');
|
||||
Terminal.loadAddon('fit');
|
||||
Terminal.loadAddon('fullscreen');
|
||||
Terminal.loadAddon('search');
|
||||
Terminal.loadAddon('terminado');
|
||||
Terminal.loadAddon('winptyCompat');
|
||||
Terminal.applyAddon({});
|
||||
Terminal.applyAddon({});
|
||||
Terminal.applyAddon({});
|
||||
Terminal.applyAddon({});
|
||||
Terminal.applyAddon({});
|
||||
Terminal.applyAddon({});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,8 @@ namespace methods_core {
|
||||
// no arg
|
||||
t.on('blur', () => {});
|
||||
t.on('focus', () => {});
|
||||
t.on('lineFeed', () => {});
|
||||
t.on('linefeed', () => {});
|
||||
t.on('selection', () => {});
|
||||
// args
|
||||
t.on('data', () => {});
|
||||
t.on('data', (data: string) => console.log(data));
|
||||
@@ -88,7 +89,8 @@ namespace methods_core {
|
||||
// no arg
|
||||
t.off('blur', () => {});
|
||||
t.off('focus', () => {});
|
||||
t.off('lineFeed', () => {});
|
||||
t.off('linefeed', () => {});
|
||||
t.off('selection', () => {});
|
||||
// args
|
||||
t.off('data', () => {});
|
||||
t.off('data', (data: string) => console.log(data));
|
||||
@@ -137,7 +139,6 @@ namespace methods_core {
|
||||
const r14: number = t.getOption('rows');
|
||||
const r15: number = t.getOption('tabStopWidth');
|
||||
const r16: number = t.getOption('scrollback');
|
||||
const r17: [number, number] = t.getOption('geometry');
|
||||
const r18: (data: string) => void = t.getOption('handler');
|
||||
const r19: string = t.getOption('bellSound');
|
||||
const r20: string = t.getOption('bellStyle');
|
||||
@@ -167,13 +168,12 @@ namespace methods_core {
|
||||
t.setOption('rows', 1);
|
||||
t.setOption('tabStopWidth', 1);
|
||||
t.setOption('scrollback', 1);
|
||||
t.setOption('geometry', [1, 1]);
|
||||
t.setOption('handler', (data: string) => console.log(data));
|
||||
t.setOption('bellSound', 'foo');
|
||||
t.setOption('bellStyle', 'none');
|
||||
t.setOption('bellStyle', 'visual');
|
||||
// t.setOption('bellStyle', 'visual');
|
||||
t.setOption('bellStyle', 'sound');
|
||||
t.setOption('bellStyle', 'both');
|
||||
// t.setOption('bellStyle', 'both');
|
||||
t.setOption('fontSize', 1);
|
||||
t.setOption('lineHeight', 1);
|
||||
t.setOption('fontFamily', 'foo');
|
||||
|
||||
+23
-18
@@ -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
|
||||
*/
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Generated
+122
-23
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-8
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "xterm",
|
||||
"description": "Full xterm terminal, in your browser",
|
||||
"version": "2.9.1",
|
||||
"version": "3.0.0",
|
||||
"ignore": [
|
||||
"demo",
|
||||
"test",
|
||||
@@ -9,7 +9,7 @@
|
||||
],
|
||||
"main": "lib/Terminal.js",
|
||||
"types": "typings/xterm.d.ts",
|
||||
"repository": "https://github.com/sourcelair/xterm.js",
|
||||
"repository": "https://github.com/xtermjs/xterm.js",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"*.js",
|
||||
@@ -35,13 +35,15 @@
|
||||
"src/*.ts",
|
||||
"src/**/*.js",
|
||||
"src/**/*.js.map",
|
||||
"src/**/*.ts"
|
||||
"src/**/*.ts",
|
||||
"typings/*.d.ts"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/chai": "^3.4.34",
|
||||
"@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",
|
||||
@@ -51,6 +53,7 @@
|
||||
"glob": "^7.0.5",
|
||||
"gulp": "3.9.1",
|
||||
"gulp-cli": "^1.2.2",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-coveralls": "^0.1.4",
|
||||
"gulp-istanbul": "^1.1.1",
|
||||
"gulp-mocha": "^3.0.1",
|
||||
@@ -66,17 +69,23 @@
|
||||
"tslint": "^4.0.2",
|
||||
"typescript": "~2.4.0",
|
||||
"vinyl-buffer": "^1.0.0",
|
||||
"vinyl-source-stream": "^1.1.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",
|
||||
"dev": "nodemon -e js,ts,css --watch src --watch demo --exec npm start",
|
||||
"prestart-zmodem": "npm run build",
|
||||
"start-zmodem": "node build/addons/zmodem/demo/app",
|
||||
"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": {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* Copyright (c) 2016 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: number) => number {
|
||||
// extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c
|
||||
// combining characters
|
||||
const COMBINING_BMP = [
|
||||
[0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],
|
||||
[0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],
|
||||
[0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],
|
||||
[0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],
|
||||
[0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],
|
||||
[0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],
|
||||
[0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],
|
||||
[0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],
|
||||
[0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],
|
||||
[0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],
|
||||
[0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],
|
||||
[0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],
|
||||
[0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],
|
||||
[0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],
|
||||
[0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],
|
||||
[0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],
|
||||
[0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],
|
||||
[0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],
|
||||
[0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],
|
||||
[0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],
|
||||
[0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],
|
||||
[0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],
|
||||
[0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],
|
||||
[0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],
|
||||
[0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],
|
||||
[0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],
|
||||
[0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],
|
||||
[0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],
|
||||
[0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],
|
||||
[0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],
|
||||
[0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],
|
||||
[0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],
|
||||
[0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],
|
||||
[0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],
|
||||
[0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],
|
||||
[0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],
|
||||
[0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],
|
||||
[0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],
|
||||
[0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],
|
||||
[0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],
|
||||
[0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],
|
||||
[0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],
|
||||
[0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB],
|
||||
];
|
||||
const COMBINING_HIGH = [
|
||||
[0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],
|
||||
[0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],
|
||||
[0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],
|
||||
[0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],
|
||||
[0xE0100, 0xE01EF]
|
||||
];
|
||||
// binary search
|
||||
function bisearch(ucs: number, data: number[][]): boolean {
|
||||
let min = 0;
|
||||
let max = data.length - 1;
|
||||
let mid;
|
||||
if (ucs < data[0][0] || ucs > data[max][1])
|
||||
return false;
|
||||
while (max >= min) {
|
||||
mid = (min + max) >> 1;
|
||||
if (ucs > data[mid][1])
|
||||
min = mid + 1;
|
||||
else if (ucs < data[mid][0])
|
||||
max = mid - 1;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function wcwidthBMP(ucs: number): number {
|
||||
// test for 8-bit control characters
|
||||
if (ucs === 0)
|
||||
return opts.nul;
|
||||
if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
|
||||
return opts.control;
|
||||
// binary search in table of non-spacing characters
|
||||
if (bisearch(ucs, COMBINING_BMP))
|
||||
return 0;
|
||||
// if we arrive here, ucs is not a combining or C0/C1 control character
|
||||
if (isWideBMP(ucs)) {
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
function isWideBMP(ucs: number): boolean {
|
||||
return (
|
||||
ucs >= 0x1100 && (
|
||||
ucs <= 0x115f || // Hangul Jamo init. consonants
|
||||
ucs === 0x2329 ||
|
||||
ucs === 0x232a ||
|
||||
(ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || // CJK..Yi
|
||||
(ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables
|
||||
(ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compat Ideographs
|
||||
(ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms
|
||||
(ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms
|
||||
(ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms
|
||||
(ucs >= 0xffe0 && ucs <= 0xffe6)));
|
||||
}
|
||||
function wcwidthHigh(ucs: number): 0 | 1 | 2 {
|
||||
if (bisearch(ucs, COMBINING_HIGH))
|
||||
return 0;
|
||||
if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) {
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
const control = opts.control | 0;
|
||||
let table: number[] | Uint32Array = null;
|
||||
function init_table(): number[] | Uint32Array {
|
||||
// lookup table for BMP
|
||||
const CODEPOINTS = 65536; // BMP holds 65536 codepoints
|
||||
const BITWIDTH = 2; // a codepoint can have a width of 0, 1 or 2
|
||||
const ITEMSIZE = 32; // using uint32_t
|
||||
const CONTAINERSIZE = CODEPOINTS * BITWIDTH / ITEMSIZE;
|
||||
const CODEPOINTS_PER_ITEM = ITEMSIZE / BITWIDTH;
|
||||
table = (typeof Uint32Array === 'undefined')
|
||||
? new Array(CONTAINERSIZE)
|
||||
: new Uint32Array(CONTAINERSIZE);
|
||||
for (let i = 0; i < CONTAINERSIZE; ++i) {
|
||||
let num = 0;
|
||||
let pos = CODEPOINTS_PER_ITEM;
|
||||
while (pos--)
|
||||
num = (num << 2) | wcwidthBMP(CODEPOINTS_PER_ITEM * i + pos);
|
||||
table[i] = num;
|
||||
}
|
||||
return table;
|
||||
}
|
||||
// get width from lookup table
|
||||
// position in container : num / CODEPOINTS_PER_ITEM
|
||||
// ==> n = table[Math.floor(num / 16)]
|
||||
// ==> n = table[num >> 4]
|
||||
// 16 codepoints per number: FFEEDDCCBBAA99887766554433221100
|
||||
// position in number : (num % CODEPOINTS_PER_ITEM) * BITWIDTH
|
||||
// ==> m = (n % 16) * 2
|
||||
// ==> m = (num & 15) << 1
|
||||
// right shift to position m
|
||||
// ==> n = n >> m e.g. m=12 000000000000FFEEDDCCBBAA99887766
|
||||
// we are only interested in 2 LSBs, cut off higher bits
|
||||
// ==> n = n & 3 e.g. 000000000000000000000000000000XX
|
||||
return function (num: number): number {
|
||||
num = num | 0; // get asm.js like optimization under V8
|
||||
if (num < 32)
|
||||
return control | 0;
|
||||
if (num < 127)
|
||||
return 1;
|
||||
let t = table || init_table();
|
||||
if (num < 65536)
|
||||
return t[num >> 4] >> ((num & 15) << 1) & 3;
|
||||
// do a full search for high codepoints
|
||||
return wcwidthHigh(num);
|
||||
};
|
||||
})({nul: 0, control: 0}); // configurable options
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { InputHandler } from './InputHandler';
|
||||
import { wcwidth } from './InputHandler';
|
||||
import { MockInputHandlingTerminal } from './utils/TestUtils.test';
|
||||
|
||||
describe('InputHandler', () => {
|
||||
@@ -66,4 +65,18 @@ describe('InputHandler', () => {
|
||||
assert.equal(terminal.options['cursorBlink'], false);
|
||||
});
|
||||
});
|
||||
describe('setMode', () => {
|
||||
it('should toggle Terminal.bracketedPasteMode', () => {
|
||||
let terminal = new MockInputHandlingTerminal();
|
||||
terminal.prefix = '?';
|
||||
terminal.bracketedPasteMode = false;
|
||||
let inputHandler = new InputHandler(terminal);
|
||||
// Set bracketed paste mode
|
||||
inputHandler.setMode([2004]);
|
||||
assert.equal(terminal.bracketedPasteMode, true);
|
||||
// Reset bracketed paste mode
|
||||
inputHandler.resetMode([2004]);
|
||||
assert.equal(terminal.bracketedPasteMode, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+9
-159
@@ -10,6 +10,7 @@ import { DEFAULT_CHARSET } from './Charsets';
|
||||
import { CharData } from './Types';
|
||||
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer';
|
||||
import { FLAGS } from './renderer/Types';
|
||||
import { wcwidth } from './CharWidth';
|
||||
|
||||
/**
|
||||
* The terminal's standard implementation of IInputHandler, this handles all
|
||||
@@ -133,9 +134,9 @@ export class InputHandler implements IInputHandler {
|
||||
/**
|
||||
* This event is emitted whenever the terminal outputs a LF or NL.
|
||||
*
|
||||
* @event lineFeed
|
||||
* @event linefeed
|
||||
*/
|
||||
this._terminal.emit('lineFeed');
|
||||
this._terminal.emit('linefeed');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -931,6 +932,9 @@ export class InputHandler implements IInputHandler {
|
||||
this._terminal.viewport.syncScrollArea();
|
||||
this._terminal.showCursor();
|
||||
break;
|
||||
case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)
|
||||
this._terminal.bracketedPasteMode = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1101,6 +1105,9 @@ export class InputHandler implements IInputHandler {
|
||||
this._terminal.viewport.syncScrollArea();
|
||||
this._terminal.showCursor();
|
||||
break;
|
||||
case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)
|
||||
this._terminal.bracketedPasteMode = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1446,160 +1453,3 @@ export class InputHandler implements IInputHandler {
|
||||
this._terminal.buffer.y = this._terminal.buffer.savedY || 0;
|
||||
}
|
||||
}
|
||||
|
||||
export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: number) => number {
|
||||
// extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c
|
||||
// combining characters
|
||||
const COMBINING_BMP = [
|
||||
[0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],
|
||||
[0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],
|
||||
[0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],
|
||||
[0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],
|
||||
[0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],
|
||||
[0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],
|
||||
[0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],
|
||||
[0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],
|
||||
[0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],
|
||||
[0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],
|
||||
[0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],
|
||||
[0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],
|
||||
[0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],
|
||||
[0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],
|
||||
[0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],
|
||||
[0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],
|
||||
[0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],
|
||||
[0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],
|
||||
[0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],
|
||||
[0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],
|
||||
[0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],
|
||||
[0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],
|
||||
[0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],
|
||||
[0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],
|
||||
[0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],
|
||||
[0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],
|
||||
[0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],
|
||||
[0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],
|
||||
[0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],
|
||||
[0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],
|
||||
[0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],
|
||||
[0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],
|
||||
[0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],
|
||||
[0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],
|
||||
[0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],
|
||||
[0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],
|
||||
[0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],
|
||||
[0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],
|
||||
[0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],
|
||||
[0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],
|
||||
[0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],
|
||||
[0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],
|
||||
[0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB],
|
||||
];
|
||||
const COMBINING_HIGH = [
|
||||
[0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],
|
||||
[0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],
|
||||
[0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],
|
||||
[0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],
|
||||
[0xE0100, 0xE01EF]
|
||||
];
|
||||
// binary search
|
||||
function bisearch(ucs: number, data: number[][]): boolean {
|
||||
let min = 0;
|
||||
let max = data.length - 1;
|
||||
let mid;
|
||||
if (ucs < data[0][0] || ucs > data[max][1])
|
||||
return false;
|
||||
while (max >= min) {
|
||||
mid = (min + max) >> 1;
|
||||
if (ucs > data[mid][1])
|
||||
min = mid + 1;
|
||||
else if (ucs < data[mid][0])
|
||||
max = mid - 1;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function wcwidthBMP(ucs: number): number {
|
||||
// test for 8-bit control characters
|
||||
if (ucs === 0)
|
||||
return opts.nul;
|
||||
if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
|
||||
return opts.control;
|
||||
// binary search in table of non-spacing characters
|
||||
if (bisearch(ucs, COMBINING_BMP))
|
||||
return 0;
|
||||
// if we arrive here, ucs is not a combining or C0/C1 control character
|
||||
if (isWideBMP(ucs)) {
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
function isWideBMP(ucs: number): boolean {
|
||||
return (
|
||||
ucs >= 0x1100 && (
|
||||
ucs <= 0x115f || // Hangul Jamo init. consonants
|
||||
ucs === 0x2329 ||
|
||||
ucs === 0x232a ||
|
||||
(ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || // CJK..Yi
|
||||
(ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables
|
||||
(ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compat Ideographs
|
||||
(ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms
|
||||
(ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms
|
||||
(ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms
|
||||
(ucs >= 0xffe0 && ucs <= 0xffe6)));
|
||||
}
|
||||
function wcwidthHigh(ucs: number): 0 | 1 | 2 {
|
||||
if (bisearch(ucs, COMBINING_HIGH))
|
||||
return 0;
|
||||
if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) {
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
const control = opts.control | 0;
|
||||
let table: number[] | Uint32Array = null;
|
||||
function init_table(): number[] | Uint32Array {
|
||||
// lookup table for BMP
|
||||
const CODEPOINTS = 65536; // BMP holds 65536 codepoints
|
||||
const BITWIDTH = 2; // a codepoint can have a width of 0, 1 or 2
|
||||
const ITEMSIZE = 32; // using uint32_t
|
||||
const CONTAINERSIZE = CODEPOINTS * BITWIDTH / ITEMSIZE;
|
||||
const CODEPOINTS_PER_ITEM = ITEMSIZE / BITWIDTH;
|
||||
table = (typeof Uint32Array === 'undefined')
|
||||
? new Array(CONTAINERSIZE)
|
||||
: new Uint32Array(CONTAINERSIZE);
|
||||
for (let i = 0; i < CONTAINERSIZE; ++i) {
|
||||
let num = 0;
|
||||
let pos = CODEPOINTS_PER_ITEM;
|
||||
while (pos--)
|
||||
num = (num << 2) | wcwidthBMP(CODEPOINTS_PER_ITEM * i + pos);
|
||||
table[i] = num;
|
||||
}
|
||||
return table;
|
||||
}
|
||||
// get width from lookup table
|
||||
// position in container : num / CODEPOINTS_PER_ITEM
|
||||
// ==> n = table[Math.floor(num / 16)]
|
||||
// ==> n = table[num >> 4]
|
||||
// 16 codepoints per number: FFEEDDCCBBAA99887766554433221100
|
||||
// position in number : (num % CODEPOINTS_PER_ITEM) * BITWIDTH
|
||||
// ==> m = (n % 16) * 2
|
||||
// ==> m = (num & 15) << 1
|
||||
// right shift to position m
|
||||
// ==> n = n >> m e.g. m=12 000000000000FFEEDDCCBBAA99887766
|
||||
// we are only interested in 2 LSBs, cut off higher bits
|
||||
// ==> n = n & 3 e.g. 000000000000000000000000000000XX
|
||||
return function (num: number): number {
|
||||
num = num | 0; // get asm.js like optimization under V8
|
||||
if (num < 32)
|
||||
return control | 0;
|
||||
if (num < 127)
|
||||
return 1;
|
||||
let t = table || init_table();
|
||||
if (num < 65536)
|
||||
return t[num >> 4] >> ((num & 15) << 1) & 3;
|
||||
// do a full search for high codepoints
|
||||
return wcwidthHigh(num);
|
||||
};
|
||||
})({nul: 0, control: 0}); // configurable options
|
||||
|
||||
+2
-1
@@ -48,6 +48,7 @@ export interface ITerminal extends ILinkifierAccessor, IBufferAccessor, IElement
|
||||
buffers: IBufferSet;
|
||||
isFocused: boolean;
|
||||
mouseHelper: IMouseHelper;
|
||||
bracketedPasteMode: boolean;
|
||||
|
||||
/**
|
||||
* Emit the 'data' event and populate the given data.
|
||||
@@ -82,6 +83,7 @@ export interface IInputHandlingTerminal extends IEventEmitter {
|
||||
originMode: boolean;
|
||||
insertMode: boolean;
|
||||
wraparoundMode: boolean;
|
||||
bracketedPasteMode: boolean;
|
||||
defAttr: number;
|
||||
curAttr: number;
|
||||
prefix: string;
|
||||
@@ -138,7 +140,6 @@ export interface ITerminalOptions {
|
||||
enableBold?: boolean;
|
||||
fontSize?: number;
|
||||
fontFamily?: string;
|
||||
geometry?: [number, number];
|
||||
handler?: (data: string) => void;
|
||||
letterSpacing?: number;
|
||||
lineHeight?: number;
|
||||
|
||||
@@ -286,36 +286,43 @@ export class Parser {
|
||||
// Single Shift Select of G2 Character Set
|
||||
// ( SS2 is 0x8e). This affects next character only.
|
||||
case 'N':
|
||||
this._state = ParserState.NORMAL;
|
||||
break;
|
||||
// ESC O
|
||||
// Single Shift Select of G3 Character Set
|
||||
// ( SS3 is 0x8f). This affects next character only.
|
||||
case 'O':
|
||||
this._state = ParserState.NORMAL;
|
||||
break;
|
||||
// ESC n
|
||||
// Invoke the G2 Character Set as GL (LS2).
|
||||
case 'n':
|
||||
this._terminal.setgLevel(2);
|
||||
this._state = ParserState.NORMAL;
|
||||
break;
|
||||
// ESC o
|
||||
// Invoke the G3 Character Set as GL (LS3).
|
||||
case 'o':
|
||||
this._terminal.setgLevel(3);
|
||||
this._state = ParserState.NORMAL;
|
||||
break;
|
||||
// ESC |
|
||||
// Invoke the G3 Character Set as GR (LS3R).
|
||||
case '|':
|
||||
this._terminal.setgLevel(3);
|
||||
this._state = ParserState.NORMAL;
|
||||
break;
|
||||
// ESC }
|
||||
// Invoke the G2 Character Set as GR (LS2R).
|
||||
case '}':
|
||||
this._terminal.setgLevel(2);
|
||||
this._state = ParserState.NORMAL;
|
||||
break;
|
||||
// ESC ~
|
||||
// Invoke the G1 Character Set as GR (LS1R).
|
||||
case '~':
|
||||
this._terminal.setgLevel(1);
|
||||
this._state = ParserState.NORMAL;
|
||||
break;
|
||||
|
||||
// ESC 7 Save Cursor (DECSC).
|
||||
|
||||
@@ -14,6 +14,10 @@ import { BufferSet } from './BufferSet';
|
||||
import { MockTerminal } from './utils/TestUtils.test';
|
||||
import { LineData, CharData } from './Types';
|
||||
|
||||
class TestMockTerminal extends MockTerminal {
|
||||
emit(event: string, data: any): void {}
|
||||
}
|
||||
|
||||
class TestSelectionManager extends SelectionManager {
|
||||
constructor(
|
||||
terminal: ITerminal,
|
||||
@@ -48,7 +52,7 @@ describe('SelectionManager', () => {
|
||||
dom = new jsdom.JSDOM('');
|
||||
window = dom.window;
|
||||
document = window.document;
|
||||
terminal = new MockTerminal();
|
||||
terminal = new TestMockTerminal();
|
||||
terminal.cols = 80;
|
||||
terminal.rows = 2;
|
||||
terminal.options.scrollback = 100;
|
||||
|
||||
@@ -288,6 +288,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
||||
public selectAll(): void {
|
||||
this._model.isSelectAllActive = true;
|
||||
this.refresh();
|
||||
this._terminal.emit('selection');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -560,7 +561,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
||||
this._terminal.scrollLines(this._dragScrollAmount, false);
|
||||
// Re-evaluate selection
|
||||
if (this._dragScrollAmount > 0) {
|
||||
this._model.selectionEnd = [this._terminal.cols - 1, this._terminal.buffer.ydisp + this._terminal.rows];
|
||||
this._model.selectionEnd = [this._terminal.cols - 1, Math.min(this._terminal.buffer.ydisp + this._terminal.rows, this._terminal.buffer.lines.length - 1)];
|
||||
} else {
|
||||
this._model.selectionEnd = [0, this._terminal.buffer.ydisp];
|
||||
}
|
||||
@@ -574,6 +575,9 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
||||
*/
|
||||
private _onMouseUp(event: MouseEvent): void {
|
||||
this._removeMouseDownListeners();
|
||||
|
||||
if (this.hasSelection)
|
||||
this._terminal.emit('selection');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
+35
-39
@@ -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;
|
||||
|
||||
@@ -70,9 +64,10 @@ const WRITE_BUFFER_PAUSE_THRESHOLD = 5;
|
||||
const WRITE_BATCH_SIZE = 300;
|
||||
|
||||
const DEFAULT_OPTIONS: ITerminalOptions = {
|
||||
cols: 80,
|
||||
rows: 24,
|
||||
convertEol: false,
|
||||
termName: 'xterm',
|
||||
geometry: [80, 24],
|
||||
cursorBlink: false,
|
||||
cursorStyle: 'block',
|
||||
bellSound: BellSound,
|
||||
@@ -133,6 +128,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
public originMode: boolean;
|
||||
public insertMode: boolean;
|
||||
public wraparoundMode: boolean; // defaults: xterm - true, vt100 - false
|
||||
public bracketedPasteMode: boolean;
|
||||
|
||||
// charset
|
||||
// The current charset
|
||||
@@ -206,7 +202,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
|
||||
public cols: number;
|
||||
public rows: number;
|
||||
public geometry: [/*cols*/number, /*rows*/number];
|
||||
|
||||
/**
|
||||
* Creates a new `Terminal` object.
|
||||
@@ -242,9 +237,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
// TODO: WHy not document.body?
|
||||
this.parent = document ? document.body : null;
|
||||
|
||||
this.cols = this.options.cols || this.options.geometry[0];
|
||||
this.rows = this.options.rows || this.options.geometry[1];
|
||||
this.geometry = [this.cols, this.rows];
|
||||
this.cols = this.options.cols;
|
||||
this.rows = this.options.rows;
|
||||
|
||||
if (this.options.handler) {
|
||||
this.on('data', this.options.handler);
|
||||
@@ -261,6 +255,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
this.originMode = false;
|
||||
this.insertMode = false;
|
||||
this.wraparoundMode = true; // defaults: xterm - true, vt100 - false
|
||||
this.bracketedPasteMode = false;
|
||||
|
||||
// charset
|
||||
this.charset = null;
|
||||
@@ -322,7 +317,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
* Focus the terminal. Delegates focus handling to the terminal's DOM element.
|
||||
*/
|
||||
public focus(): void {
|
||||
this.textarea.focus();
|
||||
if (this.textarea) {
|
||||
this.textarea.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public get isFocused(): boolean {
|
||||
@@ -584,19 +581,19 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
this.element = this.document.createElement('div');
|
||||
this.element.classList.add('terminal');
|
||||
this.element.classList.add('xterm');
|
||||
|
||||
this.element.setAttribute('tabindex', '0');
|
||||
this.parent.appendChild(this.element);
|
||||
|
||||
// Performance: Use a document fragment to build the terminal
|
||||
// viewport and helper elements detached from the DOM
|
||||
const fragment = document.createDocumentFragment();
|
||||
this.viewportElement = document.createElement('div');
|
||||
this.viewportElement.classList.add('xterm-viewport');
|
||||
this.element.appendChild(this.viewportElement);
|
||||
fragment.appendChild(this.viewportElement);
|
||||
this.viewportScrollArea = document.createElement('div');
|
||||
this.viewportScrollArea.classList.add('xterm-scroll-area');
|
||||
this.viewportElement.appendChild(this.viewportScrollArea);
|
||||
|
||||
// preload audio
|
||||
this.syncBellSound();
|
||||
|
||||
this._mouseZoneManager = new MouseZoneManager(this);
|
||||
this.on('scroll', () => this._mouseZoneManager.clearAll());
|
||||
this.linkifier.attachToDom(this._mouseZoneManager);
|
||||
@@ -605,8 +602,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
// capturing DOM Events. Then produce the helpers.
|
||||
this.helperContainer = document.createElement('div');
|
||||
this.helperContainer.classList.add('xterm-helpers');
|
||||
// TODO: This should probably be inserted once it's filled to prevent an additional layout
|
||||
this.element.appendChild(this.helperContainer);
|
||||
fragment.appendChild(this.helperContainer);
|
||||
|
||||
this.textarea = document.createElement('textarea');
|
||||
this.textarea.classList.add('xterm-helper-textarea');
|
||||
this.textarea.setAttribute('autocorrect', 'off');
|
||||
@@ -624,11 +621,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
|
||||
this.charSizeStyleElement = document.createElement('style');
|
||||
this.helperContainer.appendChild(this.charSizeStyleElement);
|
||||
|
||||
this.parent.appendChild(this.element);
|
||||
|
||||
this.charMeasure = new CharMeasure(document, this.helperContainer);
|
||||
|
||||
// Preload audio, this relied on helperContainer
|
||||
this.syncBellSound();
|
||||
|
||||
// Performance: Add viewport and helper elements from the fragment
|
||||
this.element.appendChild(fragment);
|
||||
|
||||
this.renderer = new Renderer(this, this.options.theme);
|
||||
this.options.theme = null;
|
||||
this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure);
|
||||
@@ -673,6 +673,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
// Listen for mouse events and translate
|
||||
// them into terminal mouse protocols.
|
||||
this.bindMouse();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -687,22 +688,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1460,6 +1450,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
if (ev.shiftKey) {
|
||||
result.key = C0.BS; // ^H
|
||||
break;
|
||||
} else if (ev.altKey) {
|
||||
result.key = C0.ESC + C0.DEL; // \e ^?
|
||||
break;
|
||||
}
|
||||
result.key = C0.DEL; // ^?
|
||||
break;
|
||||
@@ -1866,8 +1859,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
this.charMeasure.measure(this.options);
|
||||
|
||||
this.refresh(0, this.rows - 1);
|
||||
|
||||
this.geometry = [this.cols, this.rows];
|
||||
this.emit('resize', {cols: x, rows: y});
|
||||
}
|
||||
|
||||
@@ -2129,6 +2120,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
}
|
||||
|
||||
private syncBellSound(): void {
|
||||
// Don't update anything if the terminal has not been opened yet
|
||||
if (!this.element) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.soundBell() && this.bellAudioElement) {
|
||||
this.bellAudioElement.setAttribute('src', this.options.bellSound);
|
||||
} else if (this.soundBell()) {
|
||||
|
||||
@@ -1,125 +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);
|
||||
}
|
||||
};
|
||||
|
||||
term._getMessage = function (ev) {
|
||||
if (buffered) {
|
||||
term._pushToBuffer(ev.data);
|
||||
} else {
|
||||
term.write(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;
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 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) {
|
||||
if (socket.readyState !== 1) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"rootDir": ".",
|
||||
"outDir": "../../../lib/addons/attach/",
|
||||
"sourceMap": true,
|
||||
"removeComments": true
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 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.renderer.dimensions.actualCellWidth),
|
||||
rows: Math.floor(availableHeight / term.renderer.dimensions.actualCellHeight)
|
||||
};
|
||||
|
||||
return geometry;
|
||||
};
|
||||
|
||||
export function fit(term) {
|
||||
var geometry = 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function apply(terminalConstructor) {
|
||||
terminalConstructor.prototype.proposeGeometry = function() {
|
||||
return proposeGeometry(this);
|
||||
}
|
||||
|
||||
terminalConstructor.prototype.fit = function() {
|
||||
return fit(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"rootDir": ".",
|
||||
"outDir": "../../../lib/addons/fit/",
|
||||
"sourceMap": true,
|
||||
"removeComments": true
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"rootDir": ".",
|
||||
"outDir": "../../../lib/addons/fullscreen/",
|
||||
"sourceMap": true,
|
||||
"removeComments": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "xterm.search",
|
||||
"main": "search.js",
|
||||
"private": true
|
||||
}
|
||||
+33
-47
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"rootDir": ".",
|
||||
"outDir": "../../../lib/addons/terminado/",
|
||||
"sourceMap": true,
|
||||
"removeComments": true
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
var expressWs = require('express-ws')(app);
|
||||
var os = require('os');
|
||||
var pty = require('node-pty');
|
||||
|
||||
var terminals = {},
|
||||
logs = {};
|
||||
|
||||
app.use('/build', express.static(__dirname + '/../../../../build'));
|
||||
app.use('/demo', express.static(__dirname + '/../../../../demo'));
|
||||
app.use('/zmodemjs', express.static(__dirname + '/../../../../node_modules/zmodem.js/dist'));
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.sendFile(__dirname + '/index.html');
|
||||
});
|
||||
|
||||
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.post('/terminals', function (req, res) {
|
||||
var cols = parseInt(req.query.cols),
|
||||
rows = parseInt(req.query.rows),
|
||||
term = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : 'bash', [], {
|
||||
encoding: null,
|
||||
name: 'xterm-color',
|
||||
cols: cols || 80,
|
||||
rows: rows || 24,
|
||||
cwd: process.env.PWD,
|
||||
env: process.env
|
||||
});
|
||||
|
||||
console.log('Created terminal with PID: ' + term.pid);
|
||||
terminals[term.pid] = term;
|
||||
logs[term.pid] = '';
|
||||
term.on('data', function(data) {
|
||||
logs[term.pid] += data;
|
||||
});
|
||||
res.send(term.pid.toString());
|
||||
res.end();
|
||||
});
|
||||
|
||||
app.post('/terminals/:pid/size', function (req, res) {
|
||||
var pid = parseInt(req.params.pid),
|
||||
cols = parseInt(req.query.cols),
|
||||
rows = parseInt(req.query.rows),
|
||||
term = terminals[pid];
|
||||
|
||||
term.resize(cols, rows);
|
||||
console.log('Resized terminal ' + pid + ' to ' + cols + ' cols and ' + rows + ' rows.');
|
||||
res.end();
|
||||
});
|
||||
|
||||
app.ws('/terminals/:pid', function (ws, req) {
|
||||
var term = terminals[parseInt(req.params.pid)];
|
||||
console.log('Connected to terminal ' + term.pid);
|
||||
ws.send(logs[term.pid]);
|
||||
|
||||
term.on('data', function(data) {
|
||||
try {
|
||||
ws.send(data);
|
||||
} catch (ex) {
|
||||
// The WebSocket is not open, ignore
|
||||
}
|
||||
});
|
||||
ws.on('message', function(msg) {
|
||||
term.write(msg);
|
||||
});
|
||||
ws.on('close', function () {
|
||||
term.kill();
|
||||
console.log('Closed terminal ' + term.pid);
|
||||
// Clean things up
|
||||
delete terminals[term.pid];
|
||||
delete logs[term.pid];
|
||||
});
|
||||
});
|
||||
|
||||
var port = process.env.PORT || 3000,
|
||||
host = os.platform() === 'win32' ? '127.0.0.1' : '0.0.0.0';
|
||||
|
||||
console.log('App listening to http://' + host + ':' + port);
|
||||
app.listen(port, host);
|
||||
@@ -0,0 +1,128 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>xterm.js demo</title>
|
||||
<link rel="stylesheet" href="/build/xterm.css" />
|
||||
<link rel="stylesheet" href="/build/addons/fullscreen/fullscreen.css" />
|
||||
<link rel="stylesheet" href="/demo/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="/zmodemjs/zmodem.js"></script>
|
||||
<script src="/build/addons/zmodem/zmodem.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>
|
||||
</head>
|
||||
<body>
|
||||
<h1>xterm.js: xterm, in the browser</h1>
|
||||
|
||||
<div id="terminal-container"></div>
|
||||
|
||||
<div id="zmodem_controls">
|
||||
<form id="zm_start" style="display: none" action="javascript:void(0)">
|
||||
ZMODEM detected: Start ZMODEM session?
|
||||
<label><input id="zmstart_yes" name="zmstart" type=radio checked value="1"> Yes</label>
|
||||
|
||||
<label><input name="zmstart" type=radio value=""> No</label>
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
|
||||
<form id="zm_offer" style="display: none" action="javascript:void(0)">
|
||||
<p>ZMODEM File offered!</p>
|
||||
|
||||
<label><input id="zmaccept_yes" name="zmaccept" type=radio checked value="1"> Accept</label>
|
||||
|
||||
<label><input name="zmaccept" type=radio value=""> Skip</label>
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
|
||||
<div id="zm_file" style="display: none">
|
||||
<div>Name: <span id="name"></span></div>
|
||||
<div>Size: <span id="size"></span></div>
|
||||
<div>Last modified: <span id="mtime"></span></div>
|
||||
<div>Mode: <span id="mode"></span></div>
|
||||
<br>
|
||||
<div>Conversion: <span id="zfile_conversion"></span></div>
|
||||
<div>Management: <span id="zfile_management"></span></div>
|
||||
<div>Transport: <span id="zfile_transport"></span></div>
|
||||
<div>Sparse? <span id="zfile_sparse"></span></div>
|
||||
<br>
|
||||
<div>Files remaining in batch: <span id="files_remaining"></span></div>
|
||||
<div>Bytes remaining in batch: <span id="bytes_remaining"></span></div>
|
||||
</div>
|
||||
|
||||
<form id="zm_progress" style="display: none" action="javascript:void(0)">
|
||||
<div><span id="percent_received"></span>% (<span id="bytes_received"></span> bytes) received</div>
|
||||
<button id="zm_progress_skipper" type="button" onclick="skip_current_file();">Skip File</button>
|
||||
</form>
|
||||
|
||||
<form id="zm_choose" style="display: none" action="javascript:void(0)">
|
||||
<label>Choose file(s): <input id="zm_files" type="file" multiple></label>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Actions</h2>
|
||||
<p>
|
||||
<label>Find next <input id="find-next"/></label>
|
||||
<label>Find previous <input id="find-previous"/></label>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h2>Options</h2>
|
||||
<p>
|
||||
<label><input type="checkbox" id="option-cursor-blink"> cursorBlink</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" checked id="zmodem-auto"> Accept all ZMODEM prompts<sup>*</sup></label>
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
cursorStyle
|
||||
<select id="option-cursor-style">
|
||||
<option value="block">block</option>
|
||||
<option value="underline">underline</option>
|
||||
<option value="bar">bar</option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
bellStyle
|
||||
<select id="option-bell-style">
|
||||
<option value="">none</option>
|
||||
<option value="sound">sound</option>
|
||||
<option value="visual">visual</option>
|
||||
<option value="both">both</option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>scrollback <input type="number" id="option-scrollback" value="1000" /></label>
|
||||
</p>
|
||||
<p>
|
||||
<label>tabStopWidth <input type="number" id="option-tabstopwidth" value="8" /></label>
|
||||
</p>
|
||||
<div>
|
||||
<h3>Size</h3>
|
||||
<div>
|
||||
<div style="display: inline-block; margin-right: 16px;">
|
||||
<label for="cols">Columns</label>
|
||||
<input type="number" id="cols" />
|
||||
</div>
|
||||
<div style="display: inline-block; margin-right: 16px;">
|
||||
<label for="rows">Rows</label>
|
||||
<input type="number" id="rows" />
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
<p><sup>*</sup> ZMODEM file transfers are supported via an addon. To try it out, install <a href="https://ohse.de/uwe/software/lrzsz.html"><code>lrzsz</code></a> onto the remote peer, then run <code>rz</code> to send from your browser or <code>sz <file></code> to send from the remote peer.</p>
|
||||
<script src="main.js" defer ></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,383 @@
|
||||
"use strict";
|
||||
|
||||
var term,
|
||||
protocol,
|
||||
socketURL,
|
||||
socket,
|
||||
pid;
|
||||
|
||||
var terminalContainer = document.getElementById('terminal-container'),
|
||||
actionElements = {
|
||||
findNext: document.querySelector('#find-next'),
|
||||
findPrevious: document.querySelector('#find-previous')
|
||||
},
|
||||
optionElements = {
|
||||
cursorBlink: document.querySelector('#option-cursor-blink'),
|
||||
cursorStyle: document.querySelector('#option-cursor-style'),
|
||||
scrollback: document.querySelector('#option-scrollback'),
|
||||
tabstopwidth: document.querySelector('#option-tabstopwidth'),
|
||||
bellStyle: document.querySelector('#option-bell-style')
|
||||
},
|
||||
colsElement = document.getElementById('cols'),
|
||||
rowsElement = document.getElementById('rows');
|
||||
|
||||
function setTerminalSize() {
|
||||
var cols = parseInt(colsElement.value, 10);
|
||||
var rows = parseInt(rowsElement.value, 10);
|
||||
var viewportElement = document.querySelector('.xterm-viewport');
|
||||
var scrollBarWidth = viewportElement.offsetWidth - viewportElement.clientWidth;
|
||||
var width = (cols * term.charMeasure.width + 20 /*room for scrollbar*/).toString() + 'px';
|
||||
var height = (rows * term.charMeasure.height).toString() + 'px';
|
||||
|
||||
terminalContainer.style.width = width;
|
||||
terminalContainer.style.height = height;
|
||||
term.resize(cols, rows);
|
||||
}
|
||||
|
||||
colsElement.addEventListener('change', setTerminalSize);
|
||||
rowsElement.addEventListener('change', setTerminalSize);
|
||||
|
||||
actionElements.findNext.addEventListener('keypress', function (e) {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
term.findNext(actionElements.findNext.value);
|
||||
}
|
||||
});
|
||||
actionElements.findPrevious.addEventListener('keypress', function (e) {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
term.findPrevious(actionElements.findPrevious.value);
|
||||
}
|
||||
});
|
||||
|
||||
optionElements.cursorBlink.addEventListener('change', function () {
|
||||
term.setOption('cursorBlink', optionElements.cursorBlink.checked);
|
||||
});
|
||||
optionElements.cursorStyle.addEventListener('change', function () {
|
||||
term.setOption('cursorStyle', optionElements.cursorStyle.value);
|
||||
});
|
||||
optionElements.bellStyle.addEventListener('change', function () {
|
||||
term.setOption('bellStyle', optionElements.bellStyle.value);
|
||||
});
|
||||
optionElements.scrollback.addEventListener('change', function () {
|
||||
term.setOption('scrollback', parseInt(optionElements.scrollback.value, 10));
|
||||
});
|
||||
optionElements.tabstopwidth.addEventListener('change', function () {
|
||||
term.setOption('tabStopWidth', parseInt(optionElements.tabstopwidth.value, 10));
|
||||
});
|
||||
|
||||
createTerminal();
|
||||
|
||||
function createTerminal() {
|
||||
// Clean terminal
|
||||
while (terminalContainer.children.length) {
|
||||
terminalContainer.removeChild(terminalContainer.children[0]);
|
||||
}
|
||||
term = new Terminal({
|
||||
cursorBlink: optionElements.cursorBlink.checked,
|
||||
scrollback: parseInt(optionElements.scrollback.value, 10),
|
||||
tabStopWidth: parseInt(optionElements.tabstopwidth.value, 10)
|
||||
});
|
||||
term.on('resize', function (size) {
|
||||
if (!pid) {
|
||||
return;
|
||||
}
|
||||
var cols = size.cols,
|
||||
rows = size.rows,
|
||||
url = '/terminals/' + pid + '/size?cols=' + cols + '&rows=' + rows;
|
||||
|
||||
fetch(url, {method: 'POST'});
|
||||
});
|
||||
protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
|
||||
socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/';
|
||||
|
||||
term.open(terminalContainer);
|
||||
term.fit();
|
||||
|
||||
// fit is called within a setTimeout, cols and rows need this.
|
||||
setTimeout(function () {
|
||||
colsElement.value = term.cols;
|
||||
rowsElement.value = term.rows;
|
||||
|
||||
// Set terminal size again to set the specific dimensions on the demo
|
||||
setTerminalSize();
|
||||
|
||||
fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, {method: 'POST'}).then(function (res) {
|
||||
|
||||
res.text().then(function (pid) {
|
||||
window.pid = pid;
|
||||
socketURL += pid;
|
||||
socket = new WebSocket(socketURL);
|
||||
socket.onopen = runRealTerminal;
|
||||
socket.onclose = runFakeTerminal;
|
||||
socket.onerror = runFakeTerminal;
|
||||
|
||||
term.zmodemAttach(socket, {
|
||||
noTerminalWriteOutsideSession: true,
|
||||
} );
|
||||
|
||||
term.on("zmodemRetract", () => {
|
||||
start_form.style.display = "none";
|
||||
start_form.onsubmit = null;
|
||||
});
|
||||
|
||||
term.on("zmodemDetect", (detection) => {
|
||||
function do_zmodem() {
|
||||
term.detach();
|
||||
let zsession = detection.confirm();
|
||||
|
||||
var promise;
|
||||
|
||||
if (zsession.type === "receive") {
|
||||
promise = _handle_receive_session(zsession);
|
||||
}
|
||||
else {
|
||||
promise = _handle_send_session(zsession);
|
||||
}
|
||||
|
||||
promise.catch( console.error.bind(console) ).then( () => {
|
||||
term.attach(socket);
|
||||
} );
|
||||
}
|
||||
|
||||
if (_auto_zmodem()) {
|
||||
do_zmodem();
|
||||
}
|
||||
else {
|
||||
start_form.style.display = "";
|
||||
start_form.onsubmit = function(e) {
|
||||
start_form.style.display = "none";
|
||||
|
||||
if (document.getElementById("zmstart_yes").checked) {
|
||||
do_zmodem();
|
||||
}
|
||||
else {
|
||||
detection.deny();
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// UI STUFF
|
||||
|
||||
function _show_file_info(xfer) {
|
||||
var file_info = xfer.get_details();
|
||||
|
||||
document.getElementById("name").textContent = file_info.name;
|
||||
document.getElementById("size").textContent = file_info.size;
|
||||
document.getElementById("mtime").textContent = file_info.mtime;
|
||||
document.getElementById("files_remaining").textContent = file_info.files_remaining;
|
||||
document.getElementById("bytes_remaining").textContent = file_info.bytes_remaining;
|
||||
|
||||
document.getElementById("mode").textContent = "0" + file_info.mode.toString(8);
|
||||
|
||||
var xfer_opts = xfer.get_options();
|
||||
["conversion", "management", "transport", "sparse"].forEach( (lbl) => {
|
||||
document.getElementById(`zfile_${lbl}`).textContent = xfer_opts[lbl];
|
||||
} );
|
||||
|
||||
document.getElementById("zm_file").style.display = "";
|
||||
}
|
||||
function _hide_file_info() {
|
||||
document.getElementById("zm_file").style.display = "none";
|
||||
}
|
||||
|
||||
function _save_to_disk(xfer, buffer) {
|
||||
return Zmodem.Browser.save_to_disk(buffer, xfer.get_details().name);
|
||||
}
|
||||
|
||||
var skipper_button = document.getElementById("zm_progress_skipper");
|
||||
var skipper_button_orig_text = skipper_button.textContent;
|
||||
|
||||
function _show_progress() {
|
||||
skipper_button.disabled = false;
|
||||
skipper_button.textContent = skipper_button_orig_text;
|
||||
|
||||
document.getElementById("bytes_received").textContent = 0;
|
||||
document.getElementById("percent_received").textContent = 0;
|
||||
|
||||
document.getElementById("zm_progress").style.display = "";
|
||||
}
|
||||
|
||||
function _update_progress(xfer) {
|
||||
var total_in = xfer.get_offset();
|
||||
|
||||
document.getElementById("bytes_received").textContent = total_in;
|
||||
|
||||
var percent_received = 100 * total_in / xfer.get_details().size;
|
||||
document.getElementById("percent_received").textContent = percent_received.toFixed(2);
|
||||
}
|
||||
|
||||
function _hide_progress() {
|
||||
document.getElementById("zm_progress").style.display = "none";
|
||||
}
|
||||
|
||||
var start_form = document.getElementById("zm_start");
|
||||
|
||||
function _auto_zmodem() {
|
||||
return document.getElementById("zmodem-auto").checked;
|
||||
}
|
||||
|
||||
// END UI STUFF
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
function _handle_receive_session(zsession) {
|
||||
zsession.on("offer", function(xfer) {
|
||||
current_receive_xfer = xfer;
|
||||
|
||||
_show_file_info(xfer);
|
||||
|
||||
var offer_form = document.getElementById("zm_offer");
|
||||
|
||||
function on_form_submit() {
|
||||
offer_form.style.display = "none";
|
||||
|
||||
//START
|
||||
//if (offer_form.zmaccept.value) {
|
||||
if (_auto_zmodem() || document.getElementById("zmaccept_yes").checked) {
|
||||
_show_progress();
|
||||
|
||||
var FILE_BUFFER = [];
|
||||
xfer.on("input", (payload) => {
|
||||
_update_progress(xfer);
|
||||
FILE_BUFFER.push( new Uint8Array(payload) );
|
||||
});
|
||||
xfer.accept().then(
|
||||
() => {
|
||||
_save_to_disk(xfer, FILE_BUFFER);
|
||||
},
|
||||
console.error.bind(console)
|
||||
);
|
||||
}
|
||||
else {
|
||||
xfer.skip();
|
||||
}
|
||||
//END
|
||||
}
|
||||
|
||||
if (_auto_zmodem()) {
|
||||
on_form_submit();
|
||||
}
|
||||
else {
|
||||
offer_form.onsubmit = on_form_submit;
|
||||
offer_form.style.display = "";
|
||||
}
|
||||
} );
|
||||
|
||||
var promise = new Promise( (res) => {
|
||||
zsession.on("session_end", () => {
|
||||
_hide_file_info();
|
||||
_hide_progress();
|
||||
res();
|
||||
} );
|
||||
} );
|
||||
|
||||
zsession.start();
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
function _handle_send_session(zsession) {
|
||||
var choose_form = document.getElementById("zm_choose");
|
||||
choose_form.style.display = "";
|
||||
|
||||
var file_el = document.getElementById("zm_files");
|
||||
|
||||
var promise = new Promise( (res) => {
|
||||
file_el.onchange = function(e) {
|
||||
choose_form.style.display = "none";
|
||||
|
||||
var files_obj = file_el.files;
|
||||
|
||||
Zmodem.Browser.send_files(
|
||||
zsession,
|
||||
files_obj,
|
||||
{
|
||||
on_offer_response(obj, xfer) {
|
||||
if (xfer) _show_progress();
|
||||
//console.log("offer", xfer ? "accepted" : "skipped");
|
||||
},
|
||||
on_progress(obj, xfer) {
|
||||
_update_progress(xfer);
|
||||
},
|
||||
on_file_complete(obj) {
|
||||
//console.log("COMPLETE", obj);
|
||||
_hide_progress();
|
||||
},
|
||||
}
|
||||
).then(_hide_progress).then(
|
||||
zsession.close.bind(zsession),
|
||||
console.error.bind(console)
|
||||
).then( () => {
|
||||
_hide_file_info();
|
||||
_hide_progress();
|
||||
res();
|
||||
} );
|
||||
};
|
||||
} );
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
//This is here to allow canceling of an in-progress ZMODEM transfer.
|
||||
var current_receive_xfer;
|
||||
|
||||
//Called from HTML directly.
|
||||
function skip_current_file() {
|
||||
current_receive_xfer.skip();
|
||||
|
||||
skipper_button.disabled = true;
|
||||
skipper_button.textContent = "Waiting for server to acknowledge skip …";
|
||||
}
|
||||
|
||||
function runRealTerminal() {
|
||||
term.attach(socket);
|
||||
|
||||
term._initialized = true;
|
||||
}
|
||||
|
||||
function runFakeTerminal() {
|
||||
if (term._initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
term._initialized = true;
|
||||
|
||||
var shellprompt = '$ ';
|
||||
|
||||
term.prompt = function () {
|
||||
term.write('\r\n' + shellprompt);
|
||||
};
|
||||
|
||||
term.writeln('Welcome to xterm.js');
|
||||
term.writeln('This is a local terminal emulation, without a real terminal in the back-end.');
|
||||
term.writeln('Type some keys and commands to play around.');
|
||||
term.writeln('');
|
||||
term.prompt();
|
||||
|
||||
term.on('key', function (key, ev) {
|
||||
var printable = (
|
||||
!ev.altKey && !ev.altGraphKey && !ev.ctrlKey && !ev.metaKey
|
||||
);
|
||||
|
||||
if (ev.keyCode == 13) {
|
||||
term.prompt();
|
||||
} else if (ev.keyCode == 8) {
|
||||
// Do not delete the prompt
|
||||
if (term.x > 2) {
|
||||
term.write('\b \b');
|
||||
}
|
||||
} else if (printable) {
|
||||
term.write(key);
|
||||
}
|
||||
});
|
||||
|
||||
term.on('paste', function (data, ev) {
|
||||
term.write(data);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "xterm.zmodem",
|
||||
"main": "zmodem.js",
|
||||
"private": true
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"rootDir": ".",
|
||||
"outDir": "../../../lib/addons/zmodem/",
|
||||
"sourceMap": true,
|
||||
"removeComments": true
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
}
|
||||
@@ -16,4 +16,12 @@ describe('evaluatePastedTextProcessing', () => {
|
||||
assert.equal(processedText, 'foo\r\nbar\r\n');
|
||||
assert.equal(windowsProcessedText, 'foo\rbar\r');
|
||||
});
|
||||
it('should bracket pasted text in bracketedPasteMode', () => {
|
||||
const pastedText = 'foo bar';
|
||||
const unbracketedText = Clipboard.bracketTextForPaste(pastedText, false);
|
||||
const bracketedText = Clipboard.bracketTextForPaste(pastedText, true);
|
||||
|
||||
assert.equal(unbracketedText, 'foo bar');
|
||||
assert.equal(bracketedText, '\x1b[200~foo bar\x1b[201~');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,6 +25,17 @@ export function prepareTextForTerminal(text: string, isMSWindows: boolean): stri
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bracket text for paste, if necessary, as per https://cirw.in/blog/bracketed-paste
|
||||
* @param text The pasted text to bracket
|
||||
*/
|
||||
export function bracketTextForPaste(text: string, bracketedPasteMode: boolean): string {
|
||||
if (bracketedPasteMode) {
|
||||
return '\x1b[200~' + text + '\x1b[201~';
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds copy functionality to the given terminal.
|
||||
* @param {ClipboardEvent} ev The original copy event to be handled
|
||||
@@ -52,6 +63,7 @@ export function pasteHandler(ev: ClipboardEvent, term: ITerminal): void {
|
||||
|
||||
let dispatchPaste = function(text: string): void {
|
||||
text = prepareTextForTerminal(text, term.browser.isMSWindows);
|
||||
text = bracketTextForPaste(text, term.bracketedPasteMode);
|
||||
term.handler(text);
|
||||
term.textarea.value = '';
|
||||
term.emit('paste', text);
|
||||
|
||||
+65
-53
@@ -52,6 +52,7 @@ export class Renderer extends EventEmitter implements IRenderer {
|
||||
actualCellHeight: null
|
||||
};
|
||||
this._devicePixelRatio = window.devicePixelRatio;
|
||||
this._updateDimensions();
|
||||
}
|
||||
|
||||
public onWindowResize(devicePixelRatio: number): void {
|
||||
@@ -78,59 +79,8 @@ export class Renderer extends EventEmitter implements IRenderer {
|
||||
}
|
||||
|
||||
public onResize(cols: number, rows: number, didCharSizeChange: boolean): void {
|
||||
if (!this._terminal.charMeasure.width || !this._terminal.charMeasure.height) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate the scaled character width. Width is floored as it must be
|
||||
// drawn to an integer grid in order for the CharAtlas "stamps" to not be
|
||||
// blurry. When text is drawn to the grid not using the CharAtlas, it is
|
||||
// clipped to ensure there is no overlap with the next cell.
|
||||
this.dimensions.scaledCharWidth = Math.floor(this._terminal.charMeasure.width * window.devicePixelRatio);
|
||||
|
||||
// Calculate the scaled character height. Height is ceiled in case
|
||||
// devicePixelRatio is a floating point number in order to ensure there is
|
||||
// enough space to draw the character to the cell.
|
||||
this.dimensions.scaledCharHeight = Math.ceil(this._terminal.charMeasure.height * window.devicePixelRatio);
|
||||
|
||||
// Calculate the scaled cell height, if lineHeight is not 1 then the value
|
||||
// will be floored because since lineHeight can never be lower then 1, there
|
||||
// is a guarentee that the scaled line height will always be larger than
|
||||
// scaled char height.
|
||||
this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight);
|
||||
|
||||
// Calculate the y coordinate within a cell that text should draw from in
|
||||
// order to draw in the center of a cell.
|
||||
this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2);
|
||||
|
||||
// Calculate the scaled cell width, taking the letterSpacing into account.
|
||||
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing);
|
||||
|
||||
// Calculate the x coordinate with a cell that text should draw from in
|
||||
// order to draw in the center of a cell.
|
||||
this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing / 2);
|
||||
|
||||
// Recalculate the canvas dimensions; scaled* define the actual number of
|
||||
// pixel in the canvas
|
||||
this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight;
|
||||
this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth;
|
||||
|
||||
// The the size of the canvas on the page. It's very important that this
|
||||
// rounds to nearest integer and not ceils as browsers often set
|
||||
// window.devicePixelRatio as something like 1.100000023841858, when it's
|
||||
// actually 1.1. Ceiling causes blurriness as the backing canvas image is 1
|
||||
// pixel too large for the canvas element size.
|
||||
this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio);
|
||||
this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio);
|
||||
|
||||
// Get the _actual_ dimensions of an individual cell. This needs to be
|
||||
// derived from the canvasWidth/Height calculated above which takes into
|
||||
// account window.devicePixelRatio. CharMeasure.width/height by itself is
|
||||
// insufficient when the page is not at 100% zoom level as CharMeasure is
|
||||
// measured in CSS pixels, but the actual char size on the canvas can
|
||||
// differ.
|
||||
this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows;
|
||||
this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols;
|
||||
// Update character and canvas dimensions
|
||||
this._updateDimensions();
|
||||
|
||||
// Resize all render layers
|
||||
this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions, didCharSizeChange));
|
||||
@@ -218,4 +168,66 @@ export class Renderer extends EventEmitter implements IRenderer {
|
||||
this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end));
|
||||
this._terminal.emit('refresh', {start, end});
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculates the character and canvas dimensions.
|
||||
*/
|
||||
private _updateDimensions(): void {
|
||||
// Perform a new measure if the CharMeasure dimensions are not yet available
|
||||
if (!this._terminal.charMeasure.width || !this._terminal.charMeasure.height) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate the scaled character width. Width is floored as it must be
|
||||
// drawn to an integer grid in order for the CharAtlas "stamps" to not be
|
||||
// blurry. When text is drawn to the grid not using the CharAtlas, it is
|
||||
// clipped to ensure there is no overlap with the next cell.
|
||||
this.dimensions.scaledCharWidth = Math.floor(this._terminal.charMeasure.width * window.devicePixelRatio);
|
||||
|
||||
// Calculate the scaled character height. Height is ceiled in case
|
||||
// devicePixelRatio is a floating point number in order to ensure there is
|
||||
// enough space to draw the character to the cell.
|
||||
this.dimensions.scaledCharHeight = Math.ceil(this._terminal.charMeasure.height * window.devicePixelRatio);
|
||||
|
||||
// Calculate the scaled cell height, if lineHeight is not 1 then the value
|
||||
// will be floored because since lineHeight can never be lower then 1, there
|
||||
// is a guarentee that the scaled line height will always be larger than
|
||||
// scaled char height.
|
||||
this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight);
|
||||
|
||||
// Calculate the y coordinate within a cell that text should draw from in
|
||||
// order to draw in the center of a cell.
|
||||
this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2);
|
||||
|
||||
// Calculate the scaled cell width, taking the letterSpacing into account.
|
||||
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing);
|
||||
|
||||
// Calculate the x coordinate with a cell that text should draw from in
|
||||
// order to draw in the center of a cell.
|
||||
this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing / 2);
|
||||
|
||||
// Recalculate the canvas dimensions; scaled* define the actual number of
|
||||
// pixel in the canvas
|
||||
this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight;
|
||||
this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth;
|
||||
|
||||
// The the size of the canvas on the page. It's very important that this
|
||||
// rounds to nearest integer and not ceils as browsers often set
|
||||
// window.devicePixelRatio as something like 1.100000023841858, when it's
|
||||
// actually 1.1. Ceiling causes blurriness as the backing canvas image is 1
|
||||
// pixel too large for the canvas element size.
|
||||
this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio);
|
||||
this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio);
|
||||
|
||||
// Get the _actual_ dimensions of an individual cell. This needs to be
|
||||
// derived from the canvasWidth/Height calculated above which takes into
|
||||
// account window.devicePixelRatio. CharMeasure.width/height by itself is
|
||||
// insufficient when the page is not at 100% zoom level as CharMeasure is
|
||||
// measured in CSS pixels, but the actual char size on the canvas can
|
||||
// differ.
|
||||
this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows;
|
||||
this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,41 +25,18 @@ describe('CharMeasure', () => {
|
||||
});
|
||||
|
||||
describe('measure', () => {
|
||||
it('should set _measureElement on first call', () => {
|
||||
charMeasure.measure({});
|
||||
assert.isDefined((<any>charMeasure)._measureElement, 'CharMeasure.measure should have created _measureElement');
|
||||
it('should have _measureElement', () => {
|
||||
assert.isDefined((<any>charMeasure)._measureElement, 'new CharMeasure() should have created _measureElement');
|
||||
});
|
||||
|
||||
it('should be performed async on first call', done => {
|
||||
assert.equal(charMeasure.width, null);
|
||||
charMeasure.measure({});
|
||||
it('should be performed sync', () => {
|
||||
// Mock getBoundingClientRect since jsdom doesn't have a layout engine
|
||||
(<any>charMeasure)._measureElement.getBoundingClientRect = () => {
|
||||
return { width: 1, height: 1 };
|
||||
};
|
||||
assert.equal(charMeasure.width, null);
|
||||
setTimeout(() => {
|
||||
assert.equal(charMeasure.width, 1);
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should be performed sync on successive calls', done => {
|
||||
charMeasure.measure({});
|
||||
// Mock getBoundingClientRect since jsdom doesn't have a layout engine
|
||||
(<any>charMeasure)._measureElement.getBoundingClientRect = () => {
|
||||
return { width: 1, height: 1 };
|
||||
};
|
||||
setTimeout(() => {
|
||||
const firstWidth = charMeasure.width;
|
||||
// Mock getBoundingClientRect since jsdom doesn't have a layout engine
|
||||
(<any>charMeasure)._measureElement.getBoundingClientRect = () => {
|
||||
return { width: 2, height: 2 };
|
||||
};
|
||||
charMeasure.measure({});
|
||||
assert.equal(charMeasure.width, firstWidth * 2);
|
||||
done();
|
||||
}, 0);
|
||||
assert.equal(charMeasure.height, 1);
|
||||
assert.equal(charMeasure.width, 1);
|
||||
});
|
||||
|
||||
it('should NOT do a measure when the parent is hidden', done => {
|
||||
|
||||
@@ -22,6 +22,14 @@ export class CharMeasure extends EventEmitter implements ICharMeasure {
|
||||
super();
|
||||
this._document = document;
|
||||
this._parentElement = parentElement;
|
||||
this._measureElement = this._document.createElement('span');
|
||||
this._measureElement.style.position = 'absolute';
|
||||
this._measureElement.style.top = '0';
|
||||
this._measureElement.style.left = '-9999em';
|
||||
this._measureElement.style.lineHeight = 'normal';
|
||||
this._measureElement.textContent = 'W';
|
||||
this._measureElement.setAttribute('aria-hidden', 'true');
|
||||
this._parentElement.appendChild(this._measureElement);
|
||||
}
|
||||
|
||||
public get width(): number {
|
||||
@@ -33,24 +41,6 @@ export class CharMeasure extends EventEmitter implements ICharMeasure {
|
||||
}
|
||||
|
||||
public measure(options: ITerminalOptions): void {
|
||||
if (!this._measureElement) {
|
||||
this._measureElement = this._document.createElement('span');
|
||||
this._measureElement.style.position = 'absolute';
|
||||
this._measureElement.style.top = '0';
|
||||
this._measureElement.style.left = '-9999em';
|
||||
this._measureElement.style.lineHeight = 'normal';
|
||||
this._measureElement.textContent = 'W';
|
||||
this._measureElement.setAttribute('aria-hidden', 'true');
|
||||
this._parentElement.appendChild(this._measureElement);
|
||||
// Perform _doMeasure async if the element was just attached as sometimes
|
||||
// getBoundingClientRect does not return accurate values without this.
|
||||
setTimeout(() => this._doMeasure(options), 0);
|
||||
} else {
|
||||
this._doMeasure(options);
|
||||
}
|
||||
}
|
||||
|
||||
private _doMeasure(options: ITerminalOptions): void {
|
||||
this._measureElement.style.fontFamily = options.fontFamily;
|
||||
this._measureElement.style.fontSize = `${options.fontSize}px`;
|
||||
const geometry = this._measureElement.getBoundingClientRect();
|
||||
@@ -65,4 +55,5 @@ export class CharMeasure extends EventEmitter implements ICharMeasure {
|
||||
this.emit('charsizechanged');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import * as Browser from './Browser';
|
||||
import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from '../renderer/Interfaces';
|
||||
|
||||
export class MockTerminal implements ITerminal {
|
||||
bracketedPasteMode: boolean;
|
||||
mouseHelper: IMouseHelper;
|
||||
renderer: IRenderer;
|
||||
linkifier: ILinkifier;
|
||||
@@ -94,6 +95,7 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal {
|
||||
originMode: boolean;
|
||||
insertMode: boolean;
|
||||
wraparoundMode: boolean;
|
||||
bracketedPasteMode: boolean;
|
||||
defAttr: number;
|
||||
curAttr: number;
|
||||
prefix: string;
|
||||
|
||||
@@ -95,6 +95,7 @@
|
||||
/* On OS X this is required in order for the scroll bar to appear fully opaque */
|
||||
background-color: #000;
|
||||
overflow-y: scroll;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.xterm canvas {
|
||||
|
||||
Vendored
+8
-19
@@ -20,7 +20,7 @@ declare module 'xterm' {
|
||||
/**
|
||||
* The type of the bell notification the terminal will use.
|
||||
*/
|
||||
bellStyle?: 'none' | 'visual' | 'sound' | 'both';
|
||||
bellStyle?: 'none' /*| 'visual'*/ | 'sound' /*| 'both'*/;
|
||||
|
||||
/**
|
||||
* The number of columns in the terminal.
|
||||
@@ -218,7 +218,7 @@ declare module 'xterm' {
|
||||
* @param type The type of the event.
|
||||
* @param listener The listener.
|
||||
*/
|
||||
on(type: 'blur' | 'focus' | 'lineFeed', listener: () => void): void;
|
||||
on(type: 'blur' | 'focus' | 'linefeed' | 'selection', listener: () => void): void;
|
||||
/**
|
||||
* Registers an event listener.
|
||||
* @param type The type of the event.
|
||||
@@ -273,7 +273,7 @@ declare module 'xterm' {
|
||||
* @param type The type of the event.
|
||||
* @param listener The listener.
|
||||
*/
|
||||
off(type: 'blur' | 'focus' | 'lineFeed' | 'data' | 'key' | 'keypress' | 'keydown' | 'refresh' | 'resize' | 'scroll' | 'title' | string, listener: (...args: any[]) => void): void;
|
||||
off(type: 'blur' | 'focus' | 'linefeed' | 'selection' | 'data' | 'key' | 'keypress' | 'keydown' | 'refresh' | 'resize' | 'scroll' | 'title' | string, listener: (...args: any[]) => void): void;
|
||||
|
||||
/**
|
||||
* Resizes the terminal.
|
||||
@@ -420,11 +420,6 @@ declare module 'xterm' {
|
||||
* @param key The option key.
|
||||
*/
|
||||
getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number;
|
||||
/**
|
||||
* Retrieves an option's value from the terminal.
|
||||
* @param key The option key.
|
||||
*/
|
||||
getOption(key: 'geometry'): [number, number];
|
||||
/**
|
||||
* Retrieves an option's value from the terminal.
|
||||
* @param key The option key.
|
||||
@@ -471,13 +466,7 @@ declare module 'xterm' {
|
||||
* @param key The option key.
|
||||
* @param value The option value.
|
||||
*/
|
||||
setOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback', value: number): void;
|
||||
/**
|
||||
* Sets an option on the terminal.
|
||||
* @param key The option key.
|
||||
* @param value The option value.
|
||||
*/
|
||||
setOption(key: 'geometry', value: [number, number]): void;
|
||||
setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void;
|
||||
/**
|
||||
* Sets an option on the terminal.
|
||||
* @param key The option key.
|
||||
@@ -511,10 +500,10 @@ declare module 'xterm' {
|
||||
reset(): void
|
||||
|
||||
/**
|
||||
* Loads an addon, attaching it to the Terminal prototype and making it
|
||||
* available to all newly created Terminals.
|
||||
* @param addon The addon to load.
|
||||
* Applies an addon to the Terminal prototype, making it available to all
|
||||
* newly created Terminals.
|
||||
* @param addon The addon to apply.
|
||||
*/
|
||||
static loadAddon(addon: 'attach' | 'fit' | 'fullscreen' | 'search' | 'terminado' | 'winptyCompat'): void;
|
||||
static applyAddon(addon: any): void;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
};
|
||||
Reference in New Issue
Block a user