Merge branch 'master' into pr/parser-escape-state

This commit is contained in:
Daniel Imms
2018-01-05 11:02:14 -08:00
committed by GitHub
51 changed files with 1025 additions and 854 deletions
+3
View File
@@ -23,3 +23,6 @@ fixtures/typings-test/*.js
dist/
src/utils/TestUtils.ts
src/xterm.js
# Keep the demo builds out of Git
demo/dist/
+18
View File
@@ -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
View File
@@ -1,4 +1,4 @@
FROM node:6.9
FROM node:6
MAINTAINER Paris Kasidiaris <paris@sourcelair.com>
# Set the working directory
@@ -6,7 +6,7 @@ WORKDIR /usr/src/app
# Set an entrypoint, to automatically install node modules
ENTRYPOINT ["/bin/bash", "-c", "if [[ ! -d node_modules ]]; then npm install; fi; exec \"${@:0}\";"]
CMD ["npm", "run", "dev"]
CMD ["npm", "run", "start"]
# First, install dependencies to improve layer caching
COPY package.json /usr/src/app/
-1
View File
@@ -1 +0,0 @@
web: npm run dev
+22 -34
View File
@@ -1,6 +1,6 @@
# [![xterm.js logo](logo-full.png)](https://xtermjs.org)
[![xterm.js build status](https://api.travis-ci.org/sourcelair/xterm.js.svg)](https://travis-ci.org/sourcelair/xterm.js) [![Coverage Status](https://coveralls.io/repos/github/sourcelair/xterm.js/badge.svg)](https://coveralls.io/github/sourcelair/xterm.js) [![Gitter](https://badges.gitter.im/sourcelair/xterm.js.svg)](https://gitter.im/sourcelair/xterm.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![jsDelivr Hits](https://data.jsdelivr.com/v1/package/npm/xterm/badge?style=rounded)](https://www.jsdelivr.com/package/npm/xterm)
[![xterm.js build status](https://api.travis-ci.org/xtermjs/xterm.js.svg)](https://travis-ci.org/xtermjs/xterm.js) [![Coverage Status](https://coveralls.io/repos/github/sourcelair/xterm.js/badge.svg)](https://coveralls.io/github/sourcelair/xterm.js) [![Gitter](https://badges.gitter.im/sourcelair/xterm.js.svg)](https://gitter.im/sourcelair/xterm.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![jsDelivr Hits](https://data.jsdelivr.com/v1/package/npm/xterm/badge?style=rounded)](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
View File
@@ -17,8 +17,12 @@ app.get('/style.css', function(req, res){
res.sendFile(__dirname + '/style.css');
});
app.get('/main.js', function(req, res){
res.sendFile(__dirname + '/main.js');
app.get('/dist/bundle.js', function(req, res){
res.sendFile(__dirname + '/dist/bundle.js');
});
app.get('/dist/bundle.js.map', function(req, res){
res.sendFile(__dirname + '/dist/bundle.js.map');
});
app.post('/terminals', function (req, res) {
+1 -7
View File
@@ -7,12 +7,6 @@
<link rel="stylesheet" href="style.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-promise/4.1.1/es6-promise.auto.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/1.0.0/fetch.min.js"></script>
<script src="/build/xterm.js" ></script>
<script src="/build/addons/attach/attach.js" ></script>
<script src="/build/addons/fit/fit.js" ></script>
<script src="/build/addons/fullscreen/fullscreen.js" ></script>
<script src="/build/addons/search/search.js" ></script>
<script src="/build/addons/winptyCompat/winptyCompat.js" ></script>
</head>
<body>
<h1>xterm.js: xterm, in the browser</h1>
@@ -71,6 +65,6 @@
</div>
</div>
<p><strong>Attention:</strong> The demo is a barebones implementation and is designed for xterm.js evaluation purposes only. Exposing the demo to the public as is would introduce security risks for the host.</p>
<script src="main.js" defer ></script>
<script src="dist/bundle.js" defer ></script>
</body>
</html>
+23 -6
View File
@@ -1,3 +1,18 @@
import * as Terminal from '../build/xterm';
import * as attach from '../build/addons/attach/attach';
import * as fit from '../build/addons/fit/fit';
import * as fullscreen from '../build/addons/fullscreen/fullscreen';
import * as search from '../build/addons/search/search';
import * as winptyCompat from '../build/addons/winptyCompat/winptyCompat';
Terminal.applyAddon(attach);
Terminal.applyAddon(fit);
Terminal.applyAddon(fullscreen);
Terminal.applyAddon(search);
Terminal.applyAddon(winptyCompat);
var term,
protocol,
socketURL,
@@ -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;
-9
View File
@@ -1,9 +0,0 @@
version: '2'
services:
web:
build: ./
volumes:
- ./:/usr/src/app
ports:
- 3000:3000
+9
View File
@@ -0,0 +1,9 @@
version: "3"
services:
web:
build: .
volumes:
- ./:/usr/src/app
ports:
- ${XTERMJS_PORT:3000}:3000
+10 -10
View File
@@ -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,7 @@ namespace methods_core {
// no arg
t.on('blur', () => {});
t.on('focus', () => {});
t.on('lineFeed', () => {});
t.on('linefeed', () => {});
t.on('selection', () => {});
// args
t.on('data', () => {});
@@ -89,7 +89,7 @@ namespace methods_core {
// no arg
t.off('blur', () => {});
t.off('focus', () => {});
t.off('lineFeed', () => {});
t.off('linefeed', () => {});
t.off('selection', () => {});
// args
t.off('data', () => {});
@@ -170,9 +170,9 @@ namespace methods_core {
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
View File
@@ -16,6 +16,7 @@ const source = require('vinyl-source-stream');
const sourcemaps = require('gulp-sourcemaps');
const ts = require('gulp-typescript');
const util = require('gulp-util');
const webpack = require('webpack-stream');
let buildDir = process.env.BUILD_DIR || 'build';
let tsProject = ts.createProject('tsconfig.json');
@@ -44,30 +45,31 @@ gulp.task('tsc', function () {
tsResult.dts.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest(outDir))
);
fs.emptyDirSync(`${outDir}/addons/search`);
fs.emptyDirSync(`${outDir}/addons/winptyCompat`);
let addons = ['attach', 'fit', 'fullscreen', 'search', 'terminado', 'winptyCompat', 'zmodem'];
let addonStreams = addons.map(function(addon) {
fs.emptyDirSync(`${outDir}/addons/${addon}`);
let tsProjectSearchAddon = ts.createProject('./src/addons/search/tsconfig.json');
let tsResultSearchAddon = tsProjectSearchAddon.src().pipe(sourcemaps.init()).pipe(tsProjectSearchAddon());
let tscSearchAddon = tsResultSearchAddon.js.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest(`${outDir}/addons/search`));
let tsProjectAddon = ts.createProject(`./src/addons/${addon}/tsconfig.json`);
let tsResultAddon = tsProjectAddon.src().pipe(sourcemaps.init()).pipe(tsProjectAddon());
let tscAddon = tsResultAddon.js
.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''}))
.pipe(gulp.dest(`${outDir}/addons/${addon}`));
let tsProjectWinptyCompatAddon = ts.createProject('./src/addons/winptyCompat/tsconfig.json');
let tsResultWinptyCompatAddon = tsProjectWinptyCompatAddon.src().pipe(sourcemaps.init()).pipe(tsProjectWinptyCompatAddon());
let tscWinptyCompatAddon = tsResultWinptyCompatAddon.js.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest(`${outDir}/addons/winptyCompat`));
return tscAddon;
});
// Copy all addons from ${srcDir}/ to ${outDir}/
let copyAddons = gulp.src([
`${srcDir}/addons/**/*`,
`!${srcDir}/addons/search`,
`!${srcDir}/addons/search/**`,
`!${srcDir}/addons/winptyCompat`,
`!${srcDir}/addons/winptyCompat/**`
`${srcDir}/addons/**/**`
]).pipe(gulp.dest(`${outDir}/addons`));
// Copy stylesheets from ${srcDir}/ to ${outDir}/
let copyStylesheets = gulp.src(`${srcDir}/**/*.css`).pipe(gulp.dest(outDir));
return merge(tsc, tscSearchAddon, tscWinptyCompatAddon, copyAddons, copyStylesheets);
// Join all streams into a single array
let streams = [tsc].concat(addonStreams).concat([copyAddons, copyStylesheets]);
return merge.apply(this, streams);
});
/**
@@ -136,10 +138,7 @@ gulp.task('browserify-addons', ['tsc'], function() {
// Copy all add-ons from outDir to buildDir
let copyAddons = gulp.src([
// Copy JS addons
`${outDir}/addons/**/*`,
// Exclude TS addons from copy as they are being built via browserify
`!${outDir}/addons/search`,
`!${outDir}/addons/search/**`,
`${outDir}/addons/**/*`
]).pipe(gulp.dest(`${buildDir}/addons`));
return merge(searchBundle, winptyCompatBundle, copyAddons);
@@ -195,6 +194,12 @@ gulp.task('sorcery-addons', ['browserify-addons'], function () {
chain.writeSync();
});
gulp.task('webpack', ['build'], function() {
return gulp.src('demo/main.js')
.pipe(webpack(require('./webpack.config.js')))
.pipe(gulp.dest('demo/dist/'));
});
/**
* Submit coverage results to coveralls.io
*/
+122 -23
View File
@@ -1,6 +1,6 @@
{
"name": "xterm",
"version": "2.8.1",
"version": "2.9.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -44,6 +44,12 @@
"integrity": "sha512-7F3/P6MkTPA0QxOstRqfcnoReCUy5V/QG92cyBoZSPnqdX44L8TtNELSVfN56gAttm3YWj9cEi8FRIPVq0WmeQ==",
"dev": true
},
"@types/text-encoding": {
"version": "0.0.32",
"resolved": "https://registry.npmjs.org/@types/text-encoding/-/text-encoding-0.0.32.tgz",
"integrity": "sha512-kQ79aFmYcD/DR3QKo6wXyvNrKi7PunY0KYTBUhHjHl0SXwWuLRl9Leh73YtnsSJMBi9qSiK+fxWhdJNQPbhc9A==",
"dev": true
},
"@types/tough-cookie": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.0.tgz",
@@ -555,12 +561,29 @@
"integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=",
"dev": true
},
"clone-buffer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz",
"integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=",
"dev": true
},
"clone-stats": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz",
"integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=",
"dev": true
},
"cloneable-readable": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.0.0.tgz",
"integrity": "sha1-pikNQT8hemEjL5XkWP84QYz7ARc=",
"dev": true,
"requires": {
"inherits": "2.0.3",
"process-nextick-args": "1.0.7",
"through2": "2.0.3"
}
},
"combine-source-map": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.7.2.tgz",
@@ -620,6 +643,15 @@
}
}
},
"concat-with-sourcemaps": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.0.4.tgz",
"integrity": "sha1-9Vs74q60dgGxCi1SWcz7cP0vHdY=",
"dev": true,
"requires": {
"source-map": "0.5.6"
}
},
"console-browserify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
@@ -662,6 +694,16 @@
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true
},
"crc-32": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.1.1.tgz",
"integrity": "sha1-XXOdXkxuNSrYME1zIj1IP+Va240=",
"dev": true,
"requires": {
"exit-on-epipe": "1.0.1",
"printj": "1.1.0"
}
},
"create-ecdh": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz",
@@ -1021,6 +1063,12 @@
}
}
},
"exit-on-epipe": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz",
"integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==",
"dev": true
},
"express": {
"version": "4.13.4",
"resolved": "https://registry.npmjs.org/express/-/express-4.13.4.tgz",
@@ -4190,6 +4238,51 @@
}
}
},
"gulp-concat": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz",
"integrity": "sha1-Yz0WyV2IUEYorQJmVmPO5aR5M1M=",
"dev": true,
"requires": {
"concat-with-sourcemaps": "1.0.4",
"through2": "2.0.3",
"vinyl": "2.1.0"
},
"dependencies": {
"clone": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz",
"integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=",
"dev": true
},
"clone-stats": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz",
"integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=",
"dev": true
},
"replace-ext": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz",
"integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=",
"dev": true
},
"vinyl": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.1.0.tgz",
"integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=",
"dev": true,
"requires": {
"clone": "2.1.1",
"clone-buffer": "1.0.0",
"clone-stats": "1.0.0",
"cloneable-readable": "1.0.0",
"remove-trailing-separator": "1.0.2",
"replace-ext": "1.0.0"
}
}
}
},
"gulp-coveralls": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/gulp-coveralls/-/gulp-coveralls-0.1.4.tgz",
@@ -9519,28 +9612,19 @@
"duplexer2": "0.0.2"
}
},
"nan": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz",
"integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=",
"dev": true
},
"node-pty": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-0.4.1.tgz",
"integrity": "sha1-qAs1/le2TwVasZsteqYE2DYVXAQ=",
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-0.7.3.tgz",
"integrity": "sha1-S7NqJKYu6fMzLi4jENZkLZ2u0fc=",
"dev": true,
"requires": {
"extend": "1.2.1",
"nan": "2.2.1"
},
"dependencies": {
"extend": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz",
"integrity": "sha1-oPX9bPyDpf5J72mNYOyKYk3UV2w=",
"dev": true
},
"nan": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.2.1.tgz",
"integrity": "sha1-1oaT9rNLtB1mvGizpPne/HnXFJs=",
"dev": true
}
"nan": "2.8.0"
}
},
"nodemon": {
@@ -10899,6 +10983,12 @@
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
"dev": true
},
"printj": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/printj/-/printj-1.1.0.tgz",
"integrity": "sha512-NbiNBOQ0GioHyeD3ni8wZB7ZmfU7mxIrqhWR5XSreX3rUVvk5UOwpzxOnWqrLdCtoBbdQ40sEwC+nXxxjlUo0A==",
"dev": true
},
"process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@@ -12271,9 +12361,9 @@
"dev": true
},
"typescript": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.2.2.tgz",
"integrity": "sha1-YGAiUIR5tV/6NotY/uljoD39eww=",
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.4.2.tgz",
"integrity": "sha1-+DlfhdRZJ2BnyYiqQYN6j4KHCEQ=",
"dev": true
},
"umd": {
@@ -12547,6 +12637,15 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
"dev": true
},
"zmodem.js": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/zmodem.js/-/zmodem.js-0.1.6.tgz",
"integrity": "sha1-QNeLS0uYQiBo5CB5II/wgFcLrdk=",
"dev": true,
"requires": {
"crc-32": "1.1.1"
}
}
}
}
+10 -6
View File
@@ -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",
@@ -68,20 +70,22 @@
"typescript": "~2.4.0",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"webpack": "^3.10.0",
"webpack-stream": "^4.0.0",
"zmodem.js": "^0.1.5"
},
"scripts": {
"prestart": "npm run build",
"prestart": "gulp webpack",
"start": "node demo/app",
"prestart-zmodem": "npm run build",
"start-zmodem": "node build/addons/zmodem/demo/app",
"dev": "nodemon -e js,ts,css --watch src --watch demo --exec npm start",
"lint": "tslint src/*.ts src/**/*.ts",
"test": "gulp test",
"build:docs": "jsdoc -c jsdoc.json",
"build": "gulp build",
"prepublish": "npm run build",
"coveralls": "gulp coveralls"
"coveralls": "gulp coveralls",
"webpack": "gulp webpack"
},
"dependencies": {}
}
+2 -2
View File
@@ -134,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');
}
/**
+5 -1
View File
@@ -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;
+3 -3
View File
@@ -255,7 +255,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
public selectAll(): void {
this._model.isSelectAllActive = true;
this.refresh();
this.emit('selection');
this._terminal.emit('selection');
}
/**
@@ -528,7 +528,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];
}
@@ -544,7 +544,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
this._removeMouseDownListeners();
if (this.hasSelection)
this.emit('selection');
this._terminal.emit('selection');
}
/**
+5 -4
View File
@@ -3,8 +3,9 @@
* @license MIT
*/
import { assert, expect } from 'chai';
import { assert, expect } from 'chai';
import { Terminal } from './Terminal';
import * as attach from './addons/attach/attach';
import { MockViewport, MockCompositionHelper, MockRenderer } from './utils/TestUtils.test';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer';
@@ -42,9 +43,9 @@ describe('term.js addons', () => {
};
});
it('should load addons with Terminal.loadAddon', () => {
Terminal.loadAddon('attach');
// Test that addon was loaded successfully, adding attach to Terminal's
it('should apply addons with Terminal.applyAddon', () => {
Terminal.applyAddon(attach);
// Test that addon was applied successfully, adding attach to Terminal's
// prototype.
assert.equal(typeof (<any>Terminal).prototype.attach, 'function');
});
+29 -32
View File
@@ -47,12 +47,6 @@ import { MouseZoneManager } from './input/MouseZoneManager';
import { initialize as initializeCharAtlas } from './renderer/CharAtlas';
import { IRenderer } from './renderer/Interfaces';
// Declares required for loadAddon
declare var exports: any;
declare var module: any;
declare var define: any;
declare var require: any;
// Let it work inside Node.js for automated testing purposes.
const document = (typeof window !== 'undefined') ? window.document : null;
@@ -322,7 +316,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 +580,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 +601,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 +620,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 +672,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// Listen for mouse events and translate
// them into terminal mouse protocols.
this.bindMouse();
}
/**
@@ -687,22 +687,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 +1449,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;
@@ -2127,6 +2119,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()) {
-141
View File
@@ -1,141 +0,0 @@
/**
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
* @license MIT
*
* Implements the attach method, that attaches the terminal to a WebSocket stream.
*/
(function (attach) {
if (typeof exports === 'object' && typeof module === 'object') {
/*
* CommonJS environment
*/
module.exports = attach(require('../../Terminal').Terminal);
} else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../xterm'], attach);
} else {
/*
* Plain browser environment
*/
attach(window.Terminal);
}
})(function (Terminal) {
'use strict';
var exports = {};
/**
* Attaches the given terminal to the given socket.
*
* @param {Terminal} term - The terminal to be attached to the given socket.
* @param {WebSocket} socket - The socket to attach the current terminal.
* @param {boolean} bidirectional - Whether the terminal should send data
* to the socket as well.
* @param {boolean} buffered - Whether the rendering of incoming data
* should happen instantly or at a maximum
* frequency of 1 rendering per 10ms.
*/
exports.attach = function (term, socket, bidirectional, buffered) {
bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional;
term.socket = socket;
term._flushBuffer = function () {
term.write(term._attachSocketBuffer);
term._attachSocketBuffer = null;
};
term._pushToBuffer = function (data) {
if (term._attachSocketBuffer) {
term._attachSocketBuffer += data;
} else {
term._attachSocketBuffer = data;
setTimeout(term._flushBuffer, 10);
}
};
var myTextDecoder;
term._getMessage = function (ev) {
var str;
if (typeof ev.data === "object") {
if (ev.data instanceof ArrayBuffer) {
if (!myTextDecoder) {
myTextDecoder = new TextDecoder();
}
str = myTextDecoder.decode( ev.data );
}
else {
throw "TODO: handle Blob?";
}
}
if (buffered) {
term._pushToBuffer(str || ev.data);
} else {
term.write(str || ev.data);
}
};
term._sendData = function (data) {
socket.send(data);
};
socket.addEventListener('message', term._getMessage);
if (bidirectional) {
term.on('data', term._sendData);
}
socket.addEventListener('close', term.detach.bind(term, socket));
socket.addEventListener('error', term.detach.bind(term, socket));
};
/**
* Detaches the given terminal from the given socket
*
* @param {Terminal} term - The terminal to be detached from the given socket.
* @param {WebSocket} socket - The socket from which to detach the current
* terminal.
*/
exports.detach = function (term, socket) {
term.off('data', term._sendData);
socket = (typeof socket == 'undefined') ? term.socket : socket;
if (socket) {
socket.removeEventListener('message', term._getMessage);
}
delete term.socket;
};
/**
* Attaches the current terminal to the given socket
*
* @param {WebSocket} socket - The socket to attach the current terminal.
* @param {boolean} bidirectional - Whether the terminal should send data
* to the socket as well.
* @param {boolean} buffered - Whether the rendering of incoming data
* should happen instantly or at a maximum
* frequency of 1 rendering per 10ms.
*/
Terminal.prototype.attach = function (socket, bidirectional, buffered) {
return exports.attach(this, socket, bidirectional, buffered);
};
/**
* Detaches the current terminal from the given socket.
*
* @param {WebSocket} socket - The socket from which to detach the current
* terminal.
*/
Terminal.prototype.detach = function (socket) {
return exports.detach(this, socket);
};
return exports;
});
+20
View File
@@ -0,0 +1,20 @@
/**
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert, expect } from 'chai';
import * as attach from './attach'
class MockTerminal {}
describe('attach addon', () => {
describe('apply', () => {
it('should do register the `attach` and `detach` methods', () => {
attach.apply(MockTerminal);
assert.equal(typeof (<any>MockTerminal).prototype.attach, 'function');
assert.equal(typeof (<any>MockTerminal).prototype.detach, 'function');
});
});
});
+122
View File
@@ -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);
};
}
+10
View File
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"rootDir": ".",
"outDir": "../../../lib/addons/attach/",
"sourceMap": true,
"removeComments": true
}
}
-81
View File
@@ -1,81 +0,0 @@
/**
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
* @license MIT
*
* Fit terminal columns and rows to the dimensions of its DOM element.
*
* ## Approach
*
* Rows: Truncate the division of the terminal parent element height by the
* terminal row height.
* Columns: Truncate the division of the terminal parent element width by the
* terminal character width (apply display: inline at the terminal
* row and truncate its width with the current number of columns).
*/
(function (fit) {
if (typeof exports === 'object' && typeof module === 'object') {
/*
* CommonJS environment
*/
module.exports = fit(require('../../Terminal').Terminal);
} else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../xterm'], fit);
} else {
/*
* Plain browser environment
*/
fit(window.Terminal);
}
})(function (Terminal) {
var exports = {};
exports.proposeGeometry = function (term) {
if (!term.element.parentElement) {
return null;
}
var parentElementStyle = window.getComputedStyle(term.element.parentElement);
var parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
var parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17);
var elementStyle = window.getComputedStyle(term.element);
var elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom'));
var elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left'));
var availableHeight = parentElementHeight - elementPaddingVer;
var availableWidth = parentElementWidth - elementPaddingHor;
var geometry = {
cols: Math.floor(availableWidth / term.charMeasure.width),
rows: Math.floor(availableHeight / Math.floor(term.charMeasure.height * term.getOption('lineHeight')))
};
return geometry;
};
exports.fit = function (term) {
// Wrap fit in a setTimeout as charMeasure needs time to get initialized
// after calling Terminal.open
setTimeout(function () {
var geometry = exports.proposeGeometry(term);
if (geometry) {
// Force a full render
if (term.rows !== geometry.rows || term.cols !== geometry.cols) {
term.renderer.clear();
term.resize(geometry.cols, geometry.rows);
}
}
}, 0);
};
Terminal.prototype.proposeGeometry = function () {
return exports.proposeGeometry(this);
};
Terminal.prototype.fit = function () {
return exports.fit(this);
};
return exports;
});
+20
View File
@@ -0,0 +1,20 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert, expect } from 'chai';
import * as fit from './fit'
class MockTerminal {}
describe('fit addon', () => {
describe('apply', () => {
it('should do register the `proposeGeometry` and `fit` methods', () => {
fit.apply(MockTerminal);
assert.equal(typeof (<any>MockTerminal).prototype.proposeGeometry, 'function');
assert.equal(typeof (<any>MockTerminal).prototype.fit, 'function');
});
});
});
+55
View File
@@ -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);
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"rootDir": ".",
"outDir": "../../../lib/addons/fit/",
"sourceMap": true,
"removeComments": true
}
}
-50
View File
@@ -1,50 +0,0 @@
/**
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
* @license MIT
*/
(function (fullscreen) {
if (typeof exports === 'object' && typeof module === 'object') {
/*
* CommonJS environment
*/
module.exports = fullscreen(require('../../Terminal').Terminal);
} else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../xterm'], fullscreen);
} else {
/*
* Plain browser environment
*/
fullscreen(window.Terminal);
}
})(function (Terminal) {
var exports = {};
/**
* Toggle the given terminal's fullscreen mode.
* @param {Terminal} term - The terminal to toggle full screen mode
* @param {boolean} fullscreen - Toggle fullscreen on (true) or off (false)
*/
exports.toggleFullScreen = function (term, fullscreen) {
var fn;
if (typeof fullscreen == 'undefined') {
fn = (term.element.classList.contains('fullscreen')) ? 'remove' : 'add';
} else if (!fullscreen) {
fn = 'remove';
} else {
fn = 'add';
}
term.element.classList[fn]('fullscreen');
};
Terminal.prototype.toggleFullscreen = function (fullscreen) {
exports.toggleFullScreen(this, fullscreen);
};
return exports;
});
+19
View File
@@ -0,0 +1,19 @@
/**
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert, expect } from 'chai';
import * as fullscreen from './fullscreen'
class MockTerminal {}
describe('fullscreen addon', () => {
describe('apply', () => {
it('should do register the `toggleFullscreen` method', () => {
fullscreen.apply(MockTerminal);
assert.equal(typeof (<any>MockTerminal).prototype.toggleFullScreen, 'function');
});
});
});
+29
View File
@@ -0,0 +1,29 @@
/**
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
* @license MIT
*/
/**
* Toggle the given terminal's fullscreen mode.
* @param {Terminal} term - The terminal to toggle full screen mode
* @param {boolean} fullscreen - Toggle fullscreen on (true) or off (false)
*/
export function toggleFullScreen(term, fullscreen) {
var fn;
if (typeof fullscreen == 'undefined') {
fn = (term.element.classList.contains('fullscreen')) ? 'remove' : 'add';
} else if (!fullscreen) {
fn = 'remove';
} else {
fn = 'add';
}
term.element.classList[fn]('fullscreen');
};
export function apply(terminalConstructor) {
terminalConstructor.prototype.toggleFullScreen = function (fullscreen) {
return toggleFullScreen(this, fullscreen);
};
}
+10
View File
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"rootDir": ".",
"outDir": "../../../lib/addons/fullscreen/",
"sourceMap": true,
"removeComments": true
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"name": "xterm.search",
"main": "search.js",
"private": true
}
+33 -47
View File
@@ -5,53 +5,39 @@
import { SearchHelper } from './SearchHelper';
declare var exports: any;
declare var module: any;
declare var define: any;
declare var require: any;
declare var window: any;
(function (addon) {
if (typeof window !== 'undefined' && 'Terminal' in window) {
/**
* Plain browser environment
*/
addon(window.Terminal);
} else if (typeof exports === 'object' && typeof module === 'object') {
/**
* CommonJS environment
*/
module.exports = addon(require('../../Terminal').Terminal);
} else if (typeof define === 'function') {
/**
* Require.js is available
*/
define(['../../xterm'], addon);
/**
* Find the next instance of the term, then scroll to and select it. If it
* doesn't exist, do nothing.
* @param term Tne search term.
* @return Whether a result was found.
*/
export function findNext(terminal: any, term: string): boolean {
if (!terminal._searchHelper) {
terminal.searchHelper = new SearchHelper(terminal);
}
})((Terminal: any) => {
/**
* Find the next instance of the term, then scroll to and select it. If it
* doesn't exist, do nothing.
* @param term Tne search term.
* @return Whether a result was found.
*/
Terminal.prototype.findNext = function(term: string): boolean {
if (!this._searchHelper) {
this.searchHelper = new SearchHelper(this);
}
return (<SearchHelper>this.searchHelper).findNext(term);
};
return (<SearchHelper>terminal.searchHelper).findNext(term);
};
/**
* Find the previous instance of the term, then scroll to and select it. If it
* doesn't exist, do nothing.
* @param term Tne search term.
* @return Whether a result was found.
*/
Terminal.prototype.findPrevious = function(term: string): boolean {
if (!this._searchHelper) {
this.searchHelper = new SearchHelper(this);
}
return (<SearchHelper>this.searchHelper).findPrevious(term);
};
});
/**
* Find the previous instance of the term, then scroll to and select it. If it
* doesn't exist, do nothing.
* @param term Tne search term.
* @return Whether a result was found.
*/
export function findPrevious(terminal: any, term: string): boolean {
if (!terminal._searchHelper) {
terminal.searchHelper = new SearchHelper(terminal);
}
return (<SearchHelper>terminal.searchHelper).findPrevious(term);
};
export function apply(terminalConstructor) {
terminalConstructor.prototype.findNext = function(term) {
return findNext(this, term);
}
terminalConstructor.prototype.findPrevious = function(term) {
return findPrevious(this, term);
}
}
-134
View File
@@ -1,134 +0,0 @@
/**
* Copyright (c) 2016 The xterm.js authors. All rights reserved.
* @license MIT
*
* This module provides methods for attaching a terminal to a terminado
* WebSocket stream.
*/
(function (attach) {
if (typeof exports === 'object' && typeof module === 'object') {
/*
* CommonJS environment
*/
module.exports = attach(require('../../Terminal').Terminal);
} else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../xterm'], attach);
} else {
/*
* Plain browser environment
*/
attach(window.Terminal);
}
})(function (Terminal) {
'use strict';
var exports = {};
/**
* Attaches the given terminal to the given socket.
*
* @param {Terminal} term - The terminal to be attached to the given socket.
* @param {WebSocket} socket - The socket to attach the current terminal.
* @param {boolean} bidirectional - Whether the terminal should send data
* to the socket as well.
* @param {boolean} buffered - Whether the rendering of incoming data
* should happen instantly or at a maximum
* frequency of 1 rendering per 10ms.
*/
exports.terminadoAttach = function (term, socket, bidirectional, buffered) {
bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional;
term.socket = socket;
term._flushBuffer = function () {
term.write(term._attachSocketBuffer);
term._attachSocketBuffer = null;
};
term._pushToBuffer = function (data) {
if (term._attachSocketBuffer) {
term._attachSocketBuffer += data;
} else {
term._attachSocketBuffer = data;
setTimeout(term._flushBuffer, 10);
}
};
term._getMessage = function (ev) {
var data = JSON.parse(ev.data)
if( data[0] == "stdout" ) {
if (buffered) {
term._pushToBuffer(data[1]);
} else {
term.write(data[1]);
}
}
};
term._sendData = function (data) {
socket.send(JSON.stringify(['stdin', data]));
};
term._setSize = function (size) {
socket.send(JSON.stringify(['set_size', size.rows, size.cols]));
};
socket.addEventListener('message', term._getMessage);
if (bidirectional) {
term.on('data', term._sendData);
}
term.on('resize', term._setSize);
socket.addEventListener('close', term.terminadoDetach.bind(term, socket));
socket.addEventListener('error', term.terminadoDetach.bind(term, socket));
};
/**
* Detaches the given terminal from the given socket
*
* @param {Xterm} term - The terminal to be detached from the given socket.
* @param {WebSocket} socket - The socket from which to detach the current
* terminal.
*/
exports.terminadoDetach = function (term, socket) {
term.off('data', term._sendData);
socket = (typeof socket == 'undefined') ? term.socket : socket;
if (socket) {
socket.removeEventListener('message', term._getMessage);
}
delete term.socket;
};
/**
* Attaches the current terminal to the given socket
*
* @param {WebSocket} socket - The socket to attach the current terminal.
* @param {boolean} bidirectional - Whether the terminal should send data
* to the socket as well.
* @param {boolean} buffered - Whether the rendering of incoming data
* should happen instantly or at a maximum
* frequency of 1 rendering per 10ms.
*/
Terminal.prototype.terminadoAttach = function (socket, bidirectional, buffered) {
return exports.terminadoAttach(this, socket, bidirectional, buffered);
};
/**
* Detaches the current terminal from the given socket.
*
* @param {WebSocket} socket - The socket from which to detach the current
* terminal.
*/
Terminal.prototype.terminadoDetach = function (socket) {
return exports.terminadoDetach(this, socket);
};
return exports;
});
+20
View File
@@ -0,0 +1,20 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert, expect } from 'chai';
import * as terminado from './terminado'
class MockTerminal {}
describe('terminado addon', () => {
describe('apply', () => {
it('should do register the `terminadoAttach` and `terminadoDetach` methods', () => {
terminado.apply(MockTerminal);
assert.equal(typeof (<any>MockTerminal).prototype.terminadoAttach, 'function');
assert.equal(typeof (<any>MockTerminal).prototype.terminadoDetach, 'function');
});
});
});
+111
View File
@@ -0,0 +1,111 @@
/**
* Copyright (c) 2016 The xterm.js authors. All rights reserved.
* @license MIT
*
* This module provides methods for attaching a terminal to a terminado
* WebSocket stream.
*/
/**
* Attaches the given terminal to the given socket.
*
* @param {Terminal} term - The terminal to be attached to the given socket.
* @param {WebSocket} socket - The socket to attach the current terminal.
* @param {boolean} bidirectional - Whether the terminal should send data
* to the socket as well.
* @param {boolean} buffered - Whether the rendering of incoming data
* should happen instantly or at a maximum
* frequency of 1 rendering per 10ms.
*/
export function terminadoAttach(term, socket, bidirectional, buffered) {
bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional;
term.socket = socket;
term._flushBuffer = function() {
term.write(term._attachSocketBuffer);
term._attachSocketBuffer = null;
};
term._pushToBuffer = function(data) {
if (term._attachSocketBuffer) {
term._attachSocketBuffer += data;
} else {
term._attachSocketBuffer = data;
setTimeout(term._flushBuffer, 10);
}
};
term._getMessage = function(ev) {
var data = JSON.parse(ev.data)
if( data[0] == "stdout" ) {
if (buffered) {
term._pushToBuffer(data[1]);
} else {
term.write(data[1]);
}
}
};
term._sendData = function(data) {
socket.send(JSON.stringify(['stdin', data]));
};
term._setSize = function(size) {
socket.send(JSON.stringify(['set_size', size.rows, size.cols]));
};
socket.addEventListener('message', term._getMessage);
if (bidirectional) {
term.on('data', term._sendData);
}
term.on('resize', term._setSize);
socket.addEventListener('close', term.terminadoDetach.bind(term, socket));
socket.addEventListener('error', term.terminadoDetach.bind(term, socket));
};
/**
* Detaches the given terminal from the given socket
*
* @param {Xterm} term - The terminal to be detached from the given socket.
* @param {WebSocket} socket - The socket from which to detach the current
* terminal.
*/
export function terminadoDetach(term, socket) {
term.off('data', term._sendData);
socket = (typeof socket == 'undefined') ? term.socket : socket;
if (socket) {
socket.removeEventListener('message', term._getMessage);
}
delete term.socket;
};
export function apply(terminalConstructor) {
/**
* Attaches the current terminal to the given socket
*
* @param {WebSocket} socket - The socket to attach the current terminal.
* @param {boolean} bidirectional - Whether the terminal should send data
* to the socket as well.
* @param {boolean} buffered - Whether the rendering of incoming data
* should happen instantly or at a maximum
* frequency of 1 rendering per 10ms.
*/
terminalConstructor.prototype.terminadoAttach = function(socket, bidirectional, buffered) {
return terminadoAttach(this, socket, bidirectional, buffered);
};
/**
* Detaches the current terminal from the given socket.
*
* @param {WebSocket} socket - The socket from which to detach the current
* terminal.
*/
terminalConstructor.prototype.terminadoDetach = function(socket) {
return terminadoDetach(this, socket);
};
}
+10
View File
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"rootDir": ".",
"outDir": "../../../lib/addons/terminado/",
"sourceMap": true,
"removeComments": true
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"name": "xterm.winptyCompat",
"main": "winptyCompat.js",
"private": true
}
@@ -0,0 +1,19 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert, expect } from 'chai';
import * as winptyCompat from './winptyCompat'
class MockTerminal {}
describe('winptyCompat addon', () => {
describe('apply', () => {
it('should do register the `winptyCompatInit` method', () => {
winptyCompat.apply(MockTerminal);
assert.equal(typeof (<any>MockTerminal).prototype.winptyCompatInit, 'function');
});
});
});
+12 -27
View File
@@ -3,28 +3,7 @@
* @license MIT
*/
declare var exports: any;
declare var define: any;
(function (addon) {
if (typeof window !== 'undefined' && 'Terminal' in window) {
/**
* Plain browser environment
*/
addon((<any>window).Terminal);
} else if (typeof exports === 'object' && typeof module === 'object') {
/**
* CommonJS environment
*/
module.exports = addon(require('../../Terminal').Terminal);
} else if (typeof define === 'function') {
/**
* Require.js is available
*/
define(['../../xterm'], addon);
}
})((Terminal: any) => {
Terminal.prototype.winptyCompatInit = function(): void {
export function winptyCompatInit(terminal): void {
// Don't do anything when the platform is not Windows
const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
if (!isWindows) {
@@ -41,13 +20,19 @@ declare var define: any;
// space. This is certainly not without its problems, but generally on
// Windows when text reaches the end of the terminal it's likely going to be
// wrapped.
this.on('lineFeed', () => {
const line = this.buffer.lines.get(this.buffer.ybase + this.buffer.y - 1);
const lastChar = line[this.cols - 1];
terminal.on('linefeed', () => {
const line = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y - 1);
const lastChar = line[terminal.cols - 1];
if (lastChar[3] !== 32 /* ' ' */) {
const nextLine = this.buffer.lines.get(this.buffer.ybase + this.buffer.y);
const nextLine = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y);
(<any>nextLine).isWrapped = true;
}
});
}
export function apply(terminalConstructor) {
terminalConstructor.prototype.winptyCompatInit = function(): void {
winptyCompatInit(this);
};
});
}
+5
View File
@@ -0,0 +1,5 @@
{
"name": "xterm.zmodem",
"main": "zmodem.js",
"private": true
}
+10
View File
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"rootDir": ".",
"outDir": "../../../lib/addons/zmodem/",
"sourceMap": true,
"removeComments": true
}
}
-108
View File
@@ -1,108 +0,0 @@
/**
*
* Allow xterm.js to handle ZMODEM uploads and downloads.
*
* This addon is a wrapper around zmodem.js. It adds the following to the
* Terminal class:
*
* - function `zmodemAttach(<WebSocket>, <Object>)` - creates a Zmodem.Sentry
* on the passed WebSocket object. The Object passed is optional and
* can contain:
* - noTerminalWriteOutsideSession: Suppress writes from the Sentry
* object to the Terminal while there is no active Session. This
* is necessary for compatibility with, for example, the
* `attach.js` addon.
*
* - event `zmodemDetect` - fired on Zmodem.Sentry’s `on_detect` callback.
* Passes the zmodem.js Detection object.
*
* - event `zmodemRetract` - fired on Zmodem.Sentry’s `on_retract` callback.
*
* You’ll need to provide logic to handle uploads and downloads.
* See zmodem.js’s documentation for more details.
*
* **IMPORTANT:** After you confirm() a zmodem.js Detection, if you have
* used the `attach` or `terminado` addons, you’ll need to suspend their
* operation for the duration of the ZMODEM session. (The demo does this
* via `detach()` and a re-`attach()`.)
*/
(function (addon) {
if (typeof exports === 'object' && typeof module === 'object') {
/*
* CommonJS environment
*/
module.exports = addon(require('../../Terminal').Terminal);
} else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../xterm'], addon);
} else {
/*
* Plain browser environment
*/
addon(window.Terminal);
}
})(function _zmodemAddon(Terminal) {
Object.assign(
Terminal.prototype,
{
zmodemAttach: function zmodemAttach(ws, opts) {
var term = this;
if (!opts) opts = {};
var senderFunc = function _ws_sender_func(octets) {
ws.send( new Uint8Array(octets) );
};
var zsentry;
function _shouldWrite() {
return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession;
}
zsentry = new Zmodem.Sentry( {
to_terminal: function _to_terminal(octets) {
if (_shouldWrite()) {
term.write(
String.fromCharCode.apply(String, octets)
);
}
},
sender: senderFunc,
on_retract: function _on_retract() {
term.emit("zmodemRetract");
},
on_detect: function _on_detect(detection) {
term.emit("zmodemDetect", detection);
},
} );
function handleWSMessage(evt) {
//In testing with xterm.js’s demo the first message was
//always text even if the rest were binary. While that
//may be specific to xterm.js’s demo, ultimately we
//should reject anything that isn’t binary.
if (typeof evt.data === "string") {
if (_shouldWrite()) {
term.write(evt.data);
}
}
else {
zsentry.consume(evt.data);
}
}
ws.binaryType = "arraybuffer";
ws.addEventListener("message", handleWSMessage);
},
zmodemBrowser: Zmodem.Browser,
}
);
});
+20
View File
@@ -0,0 +1,20 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert, expect } from 'chai';
import * as zmodem from './zmodem'
class MockTerminal {}
describe('zmodem addon', () => {
describe('apply', () => {
it('should do register the `zmodemAttach` method and `zmodemBrowser` attribute', () => {
zmodem.apply(MockTerminal);
assert.equal(typeof (<any>MockTerminal).prototype.zmodemAttach, 'function');
assert.equal(typeof (<any>MockTerminal).prototype.zmodemBrowser, 'object');
});
});
});
+90
View File
@@ -0,0 +1,90 @@
/**
*
* Allow xterm.js to handle ZMODEM uploads and downloads.
*
* This addon is a wrapper around zmodem.js. It adds the following to the
* Terminal class:
*
* - function `zmodemAttach(<WebSocket>, <Object>)` - creates a Zmodem.Sentry
* on the passed WebSocket object. The Object passed is optional and
* can contain:
* - noTerminalWriteOutsideSession: Suppress writes from the Sentry
* object to the Terminal while there is no active Session. This
* is necessary for compatibility with, for example, the
* `attach.js` addon.
*
* - event `zmodemDetect` - fired on Zmodem.Sentry’s `on_detect` callback.
* Passes the zmodem.js Detection object.
*
* - event `zmodemRetract` - fired on Zmodem.Sentry’s `on_retract` callback.
*
* You’ll need to provide logic to handle uploads and downloads.
* See zmodem.js’s documentation for more details.
*
* **IMPORTANT:** After you confirm() a zmodem.js Detection, if you have
* used the `attach` or `terminado` addons, you’ll need to suspend their
* operation for the duration of the ZMODEM session. (The demo does this
* via `detach()` and a re-`attach()`.)
*/
let Zmodem;
export function zmodemAttach(term, ws, opts) {
if (!opts) opts = {};
var senderFunc = function _ws_sender_func(octets) {
ws.send(new Uint8Array(octets));
};
var zsentry;
function _shouldWrite() {
return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession;
}
zsentry = new Zmodem.Sentry( {
to_terminal: function _to_terminal(octets) {
if (_shouldWrite()) {
term.write(
String.fromCharCode.apply(String, octets)
);
}
},
sender: senderFunc,
on_retract: function _on_retract() {
term.emit('zmodemRetract');
},
on_detect: function _on_detect(detection) {
term.emit('zmodemDetect', detection);
},
} );
function handleWSMessage(evt) {
//In testing with xterm.js’s demo the first message was
//always text even if the rest were binary. While that
//may be specific to xterm.js’s demo, ultimately we
//should reject anything that isn’t binary.
if (typeof evt.data === 'string') {
if (_shouldWrite()) {
term.write(evt.data);
}
}
else {
zsentry.consume(evt.data);
}
}
ws.binaryType = 'arraybuffer';
ws.addEventListener('message', handleWSMessage);
}
export function apply(terminalConstructor) {
Zmodem = (typeof window == 'object') ? (<any>window).ZModem : {Browser: null}; // Nullify browser for tests
terminalConstructor.prototype.zmodemAttach = zmodemAttach.bind(this, this);
terminalConstructor.prototype.zmodemBrowser = Zmodem.Browser
}
+65 -53
View File
@@ -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;
}
}
+5 -28
View File
@@ -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 => {
+9 -18
View File
@@ -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');
}
}
}
+1
View File
@@ -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 {
+7 -7
View File
@@ -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' | 'selection', 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' | 'selection' | '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.
@@ -500,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;
}
}
+10
View File
@@ -0,0 +1,10 @@
const path = require('path');
module.exports = {
entry: './demo/main.js',
output: {
path: path.resolve(__dirname, 'demo/dist'),
filename: 'bundle.js'
},
devtool: 'source-map'
};