Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68906b5def | ||
|
|
dddaa16573 | ||
|
|
d581559dd4 | ||
|
|
3e77db8149 | ||
|
|
9115846017 | ||
|
|
ab7d63a8c8 | ||
|
|
a7acee726d | ||
|
|
a6f047331f | ||
|
|
a224acb50b | ||
|
|
42a1e4ef9b | ||
|
|
dff867b8a7 | ||
|
|
8bf8147028 | ||
|
|
ccf6d8f87d | ||
|
|
ac817ccc66 | ||
|
|
37c07eaaf9 | ||
|
|
71e20801f0 | ||
|
|
bae1bd6a42 | ||
|
|
c8b194931f | ||
|
|
c7a4881552 | ||
|
|
26fd963fbd | ||
|
|
122a34b40e | ||
|
|
33abd69e85 | ||
|
|
76a686beb2 | ||
|
|
53e8ac9b0c | ||
|
|
4b459fe0fd | ||
|
|
be4c8341ba | ||
|
|
a33bc027f1 | ||
|
|
3c9d8b0d84 | ||
|
|
26fc53990b | ||
|
|
852dac4d62 | ||
|
|
76719413f9 | ||
|
|
fca673d653 | ||
|
|
446c3958df | ||
|
|
59e72b8b07 | ||
|
|
15e56bd809 | ||
|
|
ab5cc0ad05 | ||
|
|
b89bce17ad | ||
|
|
7fcbddac04 | ||
|
|
4e1bbee67e | ||
|
|
ecfa44b0e8 | ||
|
|
db76868c26 | ||
|
|
b4c7989566 | ||
|
|
7b38b9ed57 | ||
|
|
c2fccc0350 | ||
|
|
b56ad5e8fe | ||
|
|
abfcf27962 | ||
|
|
6a04614549 | ||
|
|
89f981836f | ||
|
|
33d4d400f0 | ||
|
|
749ed6372a | ||
|
|
81fccee906 | ||
|
|
4caac78241 | ||
|
|
7ff03bb475 | ||
|
|
28c3a2026c | ||
|
|
a9c85fe825 | ||
|
|
c46e799dd0 | ||
|
|
ed1a31d1cf | ||
|
|
7f7156bafa | ||
|
|
62ce64f97d | ||
|
|
04a226f813 | ||
|
|
68b012ca06 | ||
|
|
220828f7db | ||
|
|
7df7f8f598 | ||
|
|
05aff199b2 | ||
|
|
fc22fc39e2 |
@@ -10,3 +10,4 @@ Makefile.gyp
|
|||||||
example/*.log
|
example/*.log
|
||||||
docs/
|
docs/
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
|
/.idea/
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ Alessandro Nadalin <alessandro.nadalin@gmail.com>
|
|||||||
Alexander Olsson <noseglid@gmail.com>
|
Alexander Olsson <noseglid@gmail.com>
|
||||||
Antonis Kalipetis <akalipetis@sourcelair.com>
|
Antonis Kalipetis <akalipetis@sourcelair.com>
|
||||||
Anton Skshidlevsky <meefik@gmail.com>
|
Anton Skshidlevsky <meefik@gmail.com>
|
||||||
|
Anton Yurovskykh <anton.yurovskykh@gmail.com>
|
||||||
Austin Robertson <austinrobertson@gmail.com>
|
Austin Robertson <austinrobertson@gmail.com>
|
||||||
ayapi <colors.aya@gmail.com>
|
ayapi <colors.aya@gmail.com>
|
||||||
Benjamin Fischer <benjamin.fischer@rwth-aachen.de>
|
Benjamin Fischer <benjamin.fischer@rwth-aachen.de>
|
||||||
@@ -13,6 +14,7 @@ Daniel Imms <daimms@microsoft.com>
|
|||||||
Daniel Risacher <drisacher@gmail.com>
|
Daniel Risacher <drisacher@gmail.com>
|
||||||
Dan Kaplun <dbkaplun@twitch.tv>
|
Dan Kaplun <dbkaplun@twitch.tv>
|
||||||
Darin Morrison <freebroccolo@users.noreply.github.com>
|
Darin Morrison <freebroccolo@users.noreply.github.com>
|
||||||
|
imoses <ido@twiggle.com>
|
||||||
Jean Bruenn <himself@jeanbruenn.info>
|
Jean Bruenn <himself@jeanbruenn.info>
|
||||||
Jörg Breitbart <jerch@rockborn.de>
|
Jörg Breitbart <jerch@rockborn.de>
|
||||||
Paris Kasidiaris <pariskasidiaris@gmail.com>
|
Paris Kasidiaris <pariskasidiaris@gmail.com>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2014, sourceLair Limited (https://github.com/sourcelair/)
|
Copyright (c) 2014-2016, SourceLair Private Company (https://www.sourcelair.com)
|
||||||
Copyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/)
|
Copyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/)
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ It enables applications to provide fully featured terminals to their users and c
|
|||||||
Xterm.js is used in several world-class applications to provide great terminal experiences.
|
Xterm.js is used in several world-class applications to provide great terminal experiences.
|
||||||
|
|
||||||
- [**SourceLair**](https://www.sourcelair.com/): In-browser IDE that provides its users with fully-featured Linux terminals based on xterm.js
|
- [**SourceLair**](https://www.sourcelair.com/): In-browser IDE that provides its users with fully-featured Linux terminals based on xterm.js
|
||||||
- [**Microsoft Visual Studio Code**](http://code.visualstudio.com/): Modern, versatile, powerful and open source code editor that provides integrated terminal based on xterm.js
|
- [**Microsoft Visual Studio Code**](http://code.visualstudio.com/): Modern, versatile and powerful open source code editor that provides an integrated terminal based on xterm.js
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ npm install
|
|||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
Then open http://0.0.0.0:3000 in a web browser (use http://127.0.0.1:3000 is running under Windows).
|
Then open http://0.0.0.0:3000 in a web browser (use http://127.0.0.1:3000 if running under Windows).
|
||||||
|
|
||||||
## Addons
|
## Addons
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ Addons are JavaScript modules that attach functions to the `Terminal` prototype
|
|||||||
To use an addon, just include the JavaScript file after xterm.js and before the `Terminal` object has been instantiated. The function should then be exposed on the `Terminal` object:
|
To use an addon, just 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
|
```html
|
||||||
<script src="node_modules/src/xterm.js"></script>
|
<script src="node_modules/dist/xterm.js"></script>
|
||||||
<script src="node_modules/addons/fit/fit.js"></script>
|
<script src="node_modules/addons/fit/fit.js"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
/*
|
/*
|
||||||
* Plain browser environment
|
* Plain browser environment
|
||||||
*/
|
*/
|
||||||
attach(this.Xterm);
|
attach(window.Terminal);
|
||||||
}
|
}
|
||||||
})(function (Xterm) {
|
})(function (Xterm) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@
|
|||||||
/*
|
/*
|
||||||
* Plain browser environment
|
* Plain browser environment
|
||||||
*/
|
*/
|
||||||
fit(this.Xterm);
|
fit(window.Terminal);
|
||||||
}
|
}
|
||||||
})(function (Xterm) {
|
})(function (Xterm) {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
/*
|
/*
|
||||||
* Plain browser environment
|
* Plain browser environment
|
||||||
*/
|
*/
|
||||||
fullscreen(this.Xterm);
|
fullscreen(window.Terminal);
|
||||||
}
|
}
|
||||||
})(function (Xterm) {
|
})(function (Xterm) {
|
||||||
var exports = {};
|
var exports = {};
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
/*
|
/*
|
||||||
* Plain browser environment
|
* Plain browser environment
|
||||||
*/
|
*/
|
||||||
linkify(this.Xterm);
|
linkify(window.Terminal);
|
||||||
}
|
}
|
||||||
})(function (Xterm) {
|
})(function (Xterm) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#! /usr/bin/env node
|
||||||
|
|
||||||
|
var child_process = require('child_process');
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
|
var buildDir = process.env.BUILD_DIR || 'build';
|
||||||
|
|
||||||
|
if (!fs.existsSync(buildDir)){
|
||||||
|
fs.mkdirSync(buildDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add `node_modules/.bin` to PATH
|
||||||
|
process.env.PATH = process.cwd() + '/node_modules/.bin:' + process.env.PATH;
|
||||||
|
|
||||||
|
console.log('Building xterm.js into ' + buildDir);
|
||||||
|
|
||||||
|
// Build ES2015 modules into ES5 form, then concatenate them,
|
||||||
|
// then remove unused require calls and save in output file with source map.
|
||||||
|
console.log(' - Building ' + buildDir + '/xterm.js...');
|
||||||
|
|
||||||
|
var jsBuildCmd = 'browserify src/xterm.js -s Terminal -t [ babelify --presets [ es2015 ] ] --debug | ';
|
||||||
|
jsBuildCmd += 'derequire | exorcist ' + buildDir + '/xterm.js.map > ' + buildDir + '/xterm.js';
|
||||||
|
|
||||||
|
var jsBuildProcess = child_process.execSync(jsBuildCmd);
|
||||||
|
|
||||||
|
if (jsBuildProcess.status) {
|
||||||
|
console.log(jsBuildProcess.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(' OK.');
|
||||||
|
|
||||||
|
// Copy CSS into $BUILD_DIR
|
||||||
|
console.log(' - Building ' + buildDir + '/xterm.css...');
|
||||||
|
|
||||||
|
fs.createReadStream('src/xterm.css').pipe(
|
||||||
|
fs.createWriteStream(buildDir + '/xterm.css')
|
||||||
|
);
|
||||||
|
console.log(' OK.');
|
||||||
+10
-3
@@ -3,6 +3,8 @@
|
|||||||
# Usage: ./bin/prepare-release x.y.z
|
# Usage: ./bin/prepare-release x.y.z
|
||||||
# x.y.z should be semver (e.g. 1.0.0)
|
# x.y.z should be semver (e.g. 1.0.0)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
NEW_VERSION=$1
|
NEW_VERSION=$1
|
||||||
CURRENT_PACKAGE_JSON_VERSION=$(cat package.json \
|
CURRENT_PACKAGE_JSON_VERSION=$(cat package.json \
|
||||||
| grep version \
|
| grep version \
|
||||||
@@ -17,11 +19,16 @@ CURRENT_BOWER_JSON_VERSION=$(cat bower.json \
|
|||||||
| sed 's/[",]//g' \
|
| sed 's/[",]//g' \
|
||||||
| tr -d '[[:space:]]')
|
| tr -d '[[:space:]]')
|
||||||
|
|
||||||
# Update version in package.json and bower.json
|
# Build xterm.js into `dist`
|
||||||
sed -i "s/\"version\": \"$CURRENT_PACKAGE_JSON_VERSION\"/\"version\": \"$NEW_VERSION\"/g" package.json
|
export BUILD_DIR=dist
|
||||||
sed -i "s/\"version\": \"$CURRENT_BOWER_JSON_VERSION\"/\"version\": \"$NEW_VERSION\"/g" bower.json
|
./bin/build
|
||||||
|
|
||||||
# Update AUTHORS file
|
# Update AUTHORS file
|
||||||
sh bin/generate-authors
|
sh bin/generate-authors
|
||||||
|
|
||||||
|
# Update version in package.json and bower.json
|
||||||
|
sed -i "s/\"version\": \"$CURRENT_PACKAGE_JSON_VERSION\"/\"version\": \"$NEW_VERSION\"/g" package.json
|
||||||
|
sed -i "s/\"version\": \"$CURRENT_BOWER_JSON_VERSION\"/\"version\": \"$NEW_VERSION\"/g" bower.json
|
||||||
|
|
||||||
git commit -S -s -a -m "Bump version to $NEW_VERSION"
|
git commit -S -s -a -m "Bump version to $NEW_VERSION"
|
||||||
|
git tag $NEW_VERSION
|
||||||
|
|||||||
Executable
+22
@@ -0,0 +1,22 @@
|
|||||||
|
#! /usr/bin/env sh
|
||||||
|
|
||||||
|
# Usage: ./bin/release x.y.z
|
||||||
|
# x.y.z should be semver (e.g. 1.0.0)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ -z "$1" ];
|
||||||
|
then
|
||||||
|
echo "No version supplied. Please a version argument\n"
|
||||||
|
echo "Usage: $0 VERSION\n"
|
||||||
|
echo "Example: $0 1.0.0"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
NEW_VERSION=$1
|
||||||
|
|
||||||
|
./bin/prepare-release $NEW_VERSION
|
||||||
|
|
||||||
|
git push && \
|
||||||
|
git push --tags && \
|
||||||
|
npm publish
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "xterm.js",
|
"name": "xterm.js",
|
||||||
"version": "1.1.2",
|
"version": "2.0.0",
|
||||||
"ignore": ["demo", "test", ".gitignore"]
|
"ignore": ["demo", "test", ".gitignore"]
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ var pty = require('pty.js');
|
|||||||
var terminals = {},
|
var terminals = {},
|
||||||
logs = {};
|
logs = {};
|
||||||
|
|
||||||
app.use('/src', express.static(__dirname + '/../src'));
|
app.use('/build', express.static(__dirname + '/../build'));
|
||||||
app.use('/addons', express.static(__dirname + '/../addons'));
|
app.use('/addons', express.static(__dirname + '/../addons'));
|
||||||
|
|
||||||
app.get('/', function(req, res){
|
app.get('/', function(req, res){
|
||||||
|
|||||||
+2
-2
@@ -2,11 +2,11 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>xterm.js demo</title>
|
<title>xterm.js demo</title>
|
||||||
<link rel="stylesheet" href="../src/xterm.css" />
|
<link rel="stylesheet" href="../build/xterm.css" />
|
||||||
<link rel="stylesheet" href="../addons/fullscreen/fullscreen.css" />
|
<link rel="stylesheet" href="../addons/fullscreen/fullscreen.css" />
|
||||||
<link rel="stylesheet" href="style.css" />
|
<link rel="stylesheet" href="style.css" />
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/1.0.0/fetch.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/1.0.0/fetch.min.js"></script>
|
||||||
<script src="../src/xterm.js" ></script>
|
<script src="../build/xterm.js" ></script>
|
||||||
<script src="../addons/attach/attach.js" ></script>
|
<script src="../addons/attach/attach.js" ></script>
|
||||||
<script src="../addons/fit/fit.js" ></script>
|
<script src="../addons/fit/fit.js" ></script>
|
||||||
<script src="../addons/fullscreen/fullscreen.js" ></script>
|
<script src="../addons/fullscreen/fullscreen.js" ></script>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"source": {
|
"source": {
|
||||||
"include": [
|
"include": [
|
||||||
"src/xterm.js",
|
"src/xterm.js",
|
||||||
|
"src/handlers/Clipboard.js",
|
||||||
"addons/attach/attach.js",
|
"addons/attach/attach.js",
|
||||||
"addons/fit/fit.js",
|
"addons/fit/fit.js",
|
||||||
"addons/fullscreen/fullscreen.js",
|
"addons/fullscreen/fullscreen.js",
|
||||||
|
|||||||
+13
-5
@@ -1,28 +1,36 @@
|
|||||||
{
|
{
|
||||||
"name": "xterm",
|
"name": "xterm",
|
||||||
"version": "1.1.2",
|
"version": "2.0.0",
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"demo",
|
"demo",
|
||||||
"test",
|
"test",
|
||||||
".gitignore"
|
".gitignore"
|
||||||
],
|
],
|
||||||
"main": "src/xterm.js",
|
"main": "dist/xterm.js",
|
||||||
"repository": "https://github.com/sourcelair/xterm.js",
|
"repository": "https://github.com/sourcelair/xterm.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"babel-core": "6.14.0",
|
||||||
|
"babel-preset-es2015": "6.14.0",
|
||||||
|
"babelify": "^7.3.0",
|
||||||
|
"browserify": "^13.1.0",
|
||||||
"chai": "3.5.0",
|
"chai": "3.5.0",
|
||||||
|
"derequire": "^2.0.3",
|
||||||
"docdash": "0.4.0",
|
"docdash": "0.4.0",
|
||||||
|
"exorcist": "^0.4.0",
|
||||||
"express": "4.13.4",
|
"express": "4.13.4",
|
||||||
"express-ws": "2.0.0-rc.1",
|
"express-ws": "2.0.0-rc.1",
|
||||||
"glob": "^7.0.5",
|
"glob": "^7.0.5",
|
||||||
"jsdoc": "3.4.0",
|
"jsdoc": "3.4.0",
|
||||||
"mocha": "2.5.3",
|
"mocha": "2.5.3",
|
||||||
|
"nodemon": "1.10.2",
|
||||||
"pty.js": "0.3.1",
|
"pty.js": "0.3.1",
|
||||||
"sleep": "^3.0.1"
|
"sleep": "^3.0.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node demo/app",
|
"start": "nodemon --watch src --watch addons --exec bash -c './bin/build && node demo/app'",
|
||||||
"test": "mocha --recursive",
|
"test": "mocha --recursive --compilers js:babel-core/register",
|
||||||
"build:docs": "node_modules/.bin/jsdoc -c jsdoc.json"
|
"build:docs": "jsdoc -c jsdoc.json",
|
||||||
|
"build": "./bin/build"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,201 @@
|
|||||||
|
/**
|
||||||
|
* xterm.js: xterm, in the browser
|
||||||
|
* Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates the logic for handling compositionstart, compositionupdate and compositionend
|
||||||
|
* events, displaying the in-progress composition to the UI and forwarding the final composition
|
||||||
|
* to the handler.
|
||||||
|
* @param {HTMLTextAreaElement} textarea The textarea that xterm uses for input.
|
||||||
|
* @param {HTMLElement} compositionView The element to display the in-progress composition in.
|
||||||
|
* @param {Terminal} terminal The Terminal to forward the finished composition to.
|
||||||
|
*/
|
||||||
|
function CompositionHelper(textarea, compositionView, terminal) {
|
||||||
|
this.textarea = textarea;
|
||||||
|
this.compositionView = compositionView;
|
||||||
|
this.terminal = terminal;
|
||||||
|
|
||||||
|
// Whether input composition is currently happening, eg. via a mobile keyboard, speech input
|
||||||
|
// or IME. This variable determines whether the compositionText should be displayed on the UI.
|
||||||
|
this.isComposing = false;
|
||||||
|
|
||||||
|
// The input currently being composed, eg. via a mobile keyboard, speech input or IME.
|
||||||
|
this.compositionText = null;
|
||||||
|
|
||||||
|
// The position within the input textarea's value of the current composition.
|
||||||
|
this.compositionPosition = { start: null, end: null };
|
||||||
|
|
||||||
|
// Whether a composition is in the process of being sent, setting this to false will cancel
|
||||||
|
// any in-progress composition.
|
||||||
|
this.isSendingComposition = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the compositionstart event, activating the composition view.
|
||||||
|
*/
|
||||||
|
CompositionHelper.prototype.compositionstart = function() {
|
||||||
|
this.isComposing = true;
|
||||||
|
this.compositionPosition.start = this.textarea.value.length;
|
||||||
|
this.compositionView.textContent = '';
|
||||||
|
this.compositionView.classList.add('active');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the compositionupdate event, updating the composition view.
|
||||||
|
* @param {CompositionEvent} ev The event.
|
||||||
|
*/
|
||||||
|
CompositionHelper.prototype.compositionupdate = function(ev) {
|
||||||
|
this.compositionView.textContent = ev.data;
|
||||||
|
this.updateCompositionElements();
|
||||||
|
var self = this;
|
||||||
|
setTimeout(function() {
|
||||||
|
self.compositionPosition.end = self.textarea.value.length;
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the compositionend event, hiding the composition view and sending the composition to
|
||||||
|
* the handler.
|
||||||
|
*/
|
||||||
|
CompositionHelper.prototype.compositionend = function() {
|
||||||
|
this.finalizeComposition(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the keydown event, routing any necessary events to the CompositionHelper functions.
|
||||||
|
* @return Whether the Terminal should continue processing the keydown event.
|
||||||
|
*/
|
||||||
|
CompositionHelper.prototype.keydown = function(ev) {
|
||||||
|
if (this.isComposing || this.isSendingComposition) {
|
||||||
|
if (ev.keyCode === 229) {
|
||||||
|
// Continue composing if the keyCode is the "composition character"
|
||||||
|
return false;
|
||||||
|
} else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {
|
||||||
|
// Continue composing if the keyCode is a modifier key
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
// Finish composition immediately. This is mainly here for the case where enter is
|
||||||
|
// pressed and the handler needs to be triggered before the command is executed.
|
||||||
|
this.finalizeComposition(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ev.keyCode === 229) {
|
||||||
|
// If the "composition character" is used but gets to this point it means a non-composition
|
||||||
|
// character (eg. numbers and punctuation) was pressed when the IME was active.
|
||||||
|
this.handleAnyTextareaChanges();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finalizes the composition, resuming regular input actions. This is called when a composition
|
||||||
|
* is ending.
|
||||||
|
* @param {boolean} waitForPropogation Whether to wait for events to propogate before sending
|
||||||
|
* the input. This should be false if a non-composition keystroke is entered before the
|
||||||
|
* compositionend event is triggered, such as enter, so that the composition is send before
|
||||||
|
* the command is executed.
|
||||||
|
*/
|
||||||
|
CompositionHelper.prototype.finalizeComposition = function(waitForPropogation) {
|
||||||
|
this.compositionView.classList.remove('active');
|
||||||
|
this.isComposing = false;
|
||||||
|
this.clearTextareaPosition();
|
||||||
|
|
||||||
|
if (!waitForPropogation) {
|
||||||
|
// Cancel any delayed composition send requests and send the input immediately.
|
||||||
|
this.isSendingComposition = false;
|
||||||
|
var input = this.textarea.value.substring(this.compositionPosition.start, this.compositionPosition.end);
|
||||||
|
this.terminal.handler(input);
|
||||||
|
} else {
|
||||||
|
// Make a deep copy of the composition position here as a new compositionstart event may
|
||||||
|
// fire before the setTimeout executes.
|
||||||
|
var currentCompositionPosition = {
|
||||||
|
start: this.compositionPosition.start,
|
||||||
|
end: this.compositionPosition.end,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since composition* events happen before the changes take place in the textarea on most
|
||||||
|
// browsers, use a setTimeout with 0ms time to allow the native compositionend event to
|
||||||
|
// complete. This ensures the correct character is retrieved, this solution was used
|
||||||
|
// because:
|
||||||
|
// - The compositionend event's data property is unreliable, at least on Chromium
|
||||||
|
// - The last compositionupdate event's data property does not always accurately describe
|
||||||
|
// the character, a counter example being Korean where an ending consonsant can move to
|
||||||
|
// the following character if the following input is a vowel.
|
||||||
|
var self = this;
|
||||||
|
this.isSendingComposition = true;
|
||||||
|
setTimeout(function () {
|
||||||
|
// Ensure that the input has not already been sent
|
||||||
|
if (self.isSendingComposition) {
|
||||||
|
self.isSendingComposition = false;
|
||||||
|
var input;
|
||||||
|
if (self.isComposing) {
|
||||||
|
// Use the end position to get the string if a new composition has started.
|
||||||
|
input = self.textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end);
|
||||||
|
} else {
|
||||||
|
// Don't use the end position here in order to pick up any characters after the
|
||||||
|
// composition has finished, for example when typing a non-composition character
|
||||||
|
// (eg. 2) after a composition character.
|
||||||
|
input = self.textarea.value.substring(currentCompositionPosition.start);
|
||||||
|
}
|
||||||
|
self.terminal.handler(input);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply any changes made to the textarea after the current event chain is allowed to complete.
|
||||||
|
* This should be called when not currently composing but a keydown event with the "composition
|
||||||
|
* character" (229) is triggered, in order to allow non-composition text to be entered when an
|
||||||
|
* IME is active.
|
||||||
|
*/
|
||||||
|
CompositionHelper.prototype.handleAnyTextareaChanges = function() {
|
||||||
|
var oldValue = this.textarea.value;
|
||||||
|
var self = this;
|
||||||
|
setTimeout(function() {
|
||||||
|
// Ignore if a composition has started since the timeout
|
||||||
|
if (!self.isComposing) {
|
||||||
|
var newValue = self.textarea.value;
|
||||||
|
var diff = newValue.replace(oldValue, '');
|
||||||
|
if (diff.length > 0) {
|
||||||
|
self.terminal.handler(diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Positions the composition view on top of the cursor and the textarea just below it (so the
|
||||||
|
* IME helper dialog is positioned correctly).
|
||||||
|
*/
|
||||||
|
CompositionHelper.prototype.updateCompositionElements = function(dontRecurse) {
|
||||||
|
if (!this.isComposing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var cursor = this.terminal.element.querySelector('.terminal-cursor');
|
||||||
|
if (cursor) {
|
||||||
|
this.compositionView.style.left = cursor.offsetLeft + 'px';
|
||||||
|
this.compositionView.style.top = cursor.offsetTop + 'px';
|
||||||
|
var compositionViewBounds = this.compositionView.getBoundingClientRect();
|
||||||
|
this.textarea.style.left = cursor.offsetLeft + compositionViewBounds.width + 'px';
|
||||||
|
this.textarea.style.top = (cursor.offsetTop + cursor.offsetHeight) + 'px';
|
||||||
|
}
|
||||||
|
if (!dontRecurse) {
|
||||||
|
setTimeout(this.updateCompositionElements.bind(this, true), 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the textarea's position so that the cursor does not blink on IE.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
CompositionHelper.prototype.clearTextareaPosition = function() {
|
||||||
|
this.textarea.style.left = '';
|
||||||
|
this.textarea.style.top = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
export { CompositionHelper };
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* xterm.js: xterm, in the browser
|
||||||
|
* Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License)
|
||||||
|
*/
|
||||||
|
|
||||||
|
function EventEmitter() {
|
||||||
|
this._events = this._events || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
EventEmitter.prototype.addListener = function(type, listener) {
|
||||||
|
this._events[type] = this._events[type] || [];
|
||||||
|
this._events[type].push(listener);
|
||||||
|
};
|
||||||
|
|
||||||
|
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
|
||||||
|
|
||||||
|
EventEmitter.prototype.removeListener = function(type, listener) {
|
||||||
|
if (!this._events[type]) return;
|
||||||
|
|
||||||
|
var obj = this._events[type]
|
||||||
|
, i = obj.length;
|
||||||
|
|
||||||
|
while (i--) {
|
||||||
|
if (obj[i] === listener || obj[i].listener === listener) {
|
||||||
|
obj.splice(i, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
|
||||||
|
|
||||||
|
EventEmitter.prototype.removeAllListeners = function(type) {
|
||||||
|
if (this._events[type]) delete this._events[type];
|
||||||
|
};
|
||||||
|
|
||||||
|
EventEmitter.prototype.once = function(type, listener) {
|
||||||
|
var self = this;
|
||||||
|
function on() {
|
||||||
|
var args = Array.prototype.slice.call(arguments);
|
||||||
|
this.removeListener(type, on);
|
||||||
|
return listener.apply(this, args);
|
||||||
|
}
|
||||||
|
on.listener = listener;
|
||||||
|
return this.on(type, on);
|
||||||
|
};
|
||||||
|
|
||||||
|
EventEmitter.prototype.emit = function(type) {
|
||||||
|
if (!this._events[type]) return;
|
||||||
|
|
||||||
|
var args = Array.prototype.slice.call(arguments, 1)
|
||||||
|
, obj = this._events[type]
|
||||||
|
, l = obj.length
|
||||||
|
, i = 0;
|
||||||
|
|
||||||
|
for (; i < l; i++) {
|
||||||
|
obj[i].apply(this, args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
EventEmitter.prototype.listeners = function(type) {
|
||||||
|
return this._events[type] = this._events[type] || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
export { EventEmitter };
|
||||||
+114
@@ -0,0 +1,114 @@
|
|||||||
|
/**
|
||||||
|
* xterm.js: xterm, in the browser
|
||||||
|
* Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the viewport of a terminal, the visible area within the larger buffer of output.
|
||||||
|
* Logic for the virtual scroll bar is included in this object.
|
||||||
|
* @param {Terminal} terminal The Terminal object.
|
||||||
|
* @param {HTMLElement} viewportElement The DOM element acting as the viewport
|
||||||
|
* @param {HTMLElement} charMeasureElement A DOM element used to measure the character size of
|
||||||
|
* the terminal.
|
||||||
|
*/
|
||||||
|
function Viewport(terminal, viewportElement, scrollArea, charMeasureElement) {
|
||||||
|
this.terminal = terminal;
|
||||||
|
this.viewportElement = viewportElement;
|
||||||
|
this.scrollArea = scrollArea;
|
||||||
|
this.charMeasureElement = charMeasureElement;
|
||||||
|
this.currentRowHeight = 0;
|
||||||
|
this.lastRecordedBufferLength = 0;
|
||||||
|
this.lastRecordedViewportHeight = 0;
|
||||||
|
|
||||||
|
this.terminal.on('scroll', this.syncScrollArea.bind(this));
|
||||||
|
this.terminal.on('resize', this.syncScrollArea.bind(this));
|
||||||
|
this.viewportElement.addEventListener('scroll', this.onScroll.bind(this));
|
||||||
|
|
||||||
|
this.syncScrollArea();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes row height, setting line-height, viewport height and scroll area height if
|
||||||
|
* necessary.
|
||||||
|
* @param {number|undefined} charSize A character size measurement bounding rect object, if it
|
||||||
|
* doesn't exist it will be created.
|
||||||
|
*/
|
||||||
|
Viewport.prototype.refresh = function(charSize) {
|
||||||
|
var size = charSize || this.charMeasureElement.getBoundingClientRect();
|
||||||
|
if (size.height > 0) {
|
||||||
|
var rowHeightChanged = size.height !== this.currentRowHeight;
|
||||||
|
if (rowHeightChanged) {
|
||||||
|
this.currentRowHeight = size.height;
|
||||||
|
this.viewportElement.style.lineHeight = size.height + 'px';
|
||||||
|
this.terminal.rowContainer.style.lineHeight = size.height + 'px';
|
||||||
|
}
|
||||||
|
var viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows;
|
||||||
|
if (rowHeightChanged || viewportHeightChanged) {
|
||||||
|
this.lastRecordedViewportHeight = this.terminal.rows;
|
||||||
|
this.viewportElement.style.height = size.height * this.terminal.rows + 'px';
|
||||||
|
}
|
||||||
|
this.scrollArea.style.height = (size.height * this.lastRecordedBufferLength) + 'px';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates dimensions and synchronizes the scroll area if necessary.
|
||||||
|
*/
|
||||||
|
Viewport.prototype.syncScrollArea = function() {
|
||||||
|
if (this.lastRecordedBufferLength !== this.terminal.lines.length) {
|
||||||
|
// If buffer height changed
|
||||||
|
this.lastRecordedBufferLength = this.terminal.lines.length;
|
||||||
|
this.refresh();
|
||||||
|
} else if (this.lastRecordedViewportHeight !== this.terminal.rows) {
|
||||||
|
// If viewport height changed
|
||||||
|
this.refresh();
|
||||||
|
} else {
|
||||||
|
// If size has changed, refresh viewport
|
||||||
|
var size = this.charMeasureElement.getBoundingClientRect();
|
||||||
|
if (size.height !== this.currentRowHeight) {
|
||||||
|
this.refresh(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync scrollTop
|
||||||
|
var scrollTop = this.terminal.ydisp * this.currentRowHeight;
|
||||||
|
if (this.viewportElement.scrollTop !== scrollTop) {
|
||||||
|
this.viewportElement.scrollTop = scrollTop;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles scroll events on the viewport, calculating the new viewport and requesting the
|
||||||
|
* terminal to scroll to it.
|
||||||
|
* @param {Event} ev The scroll event.
|
||||||
|
*/
|
||||||
|
Viewport.prototype.onScroll = function(ev) {
|
||||||
|
var newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);
|
||||||
|
var diff = newRow - this.terminal.ydisp;
|
||||||
|
this.terminal.scrollDisp(diff, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles mouse wheel events by adjusting the viewport's scrollTop and delegating the actual
|
||||||
|
* scrolling to `onScroll`, this event needs to be attached manually by the consumer of
|
||||||
|
* `Viewport`.
|
||||||
|
* @param {WheelEvent} ev The mouse wheel event.
|
||||||
|
*/
|
||||||
|
Viewport.prototype.onWheel = function(ev) {
|
||||||
|
if (ev.deltaY === 0) {
|
||||||
|
// Do nothing if it's not a vertical scroll event
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Fallback to WheelEvent.DOM_DELTA_PIXEL
|
||||||
|
var multiplier = 1;
|
||||||
|
if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {
|
||||||
|
multiplier = this.currentRowHeight;
|
||||||
|
} else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
|
||||||
|
multiplier = this.currentRowHeight * this.terminal.rows;
|
||||||
|
}
|
||||||
|
this.viewportElement.scrollTop += ev.deltaY * multiplier;
|
||||||
|
// Prevent the page from scrolling when the terminal scrolls
|
||||||
|
ev.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Viewport };
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
/**
|
||||||
|
* xterm.js: xterm, in the browser
|
||||||
|
* Copyright (c) 2016, SourceLair Private Company <www.sourcelair.com> (MIT License)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clipboard handler module. This module contains methods for handling all
|
||||||
|
* clipboard-related events appropriately in the terminal.
|
||||||
|
* @module xterm/handlers/Clipboard
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares text copied from terminal selection, to be saved in the clipboard by:
|
||||||
|
* 1. stripping all trailing white spaces
|
||||||
|
* 2. converting all non-breaking spaces to regular spaces
|
||||||
|
* @param {string} text The copied text that needs processing for storing in clipboard
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function prepareTextForClipboard(text) {
|
||||||
|
var space = String.fromCharCode(32),
|
||||||
|
nonBreakingSpace = String.fromCharCode(160),
|
||||||
|
allNonBreakingSpaces = new RegExp(nonBreakingSpace, 'g'),
|
||||||
|
processedText = text.split('\n').map(function (line) {
|
||||||
|
// Strip all trailing white spaces and convert all non-breaking spaces
|
||||||
|
// to regular spaces.
|
||||||
|
var processedLine = line.replace(/\s+$/g, '').replace(allNonBreakingSpaces, space);
|
||||||
|
|
||||||
|
return processedLine;
|
||||||
|
}).join('\n');
|
||||||
|
|
||||||
|
return processedText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds copy functionality to the given terminal.
|
||||||
|
* @param {ClipboardEvent} ev The original copy event to be handled
|
||||||
|
*/
|
||||||
|
function copyHandler (ev) {
|
||||||
|
var copiedText = window.getSelection().toString(),
|
||||||
|
text = prepareTextForClipboard(copiedText);
|
||||||
|
|
||||||
|
ev.clipboardData.setData('text/plain', text);
|
||||||
|
ev.preventDefault(); // Prevent or the original text will be copied.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect the clipboard's data to the terminal's input handler.
|
||||||
|
* @param {ClipboardEvent} ev The original paste event to be handled
|
||||||
|
* @param {Terminal} term The terminal on which to apply the handled paste event
|
||||||
|
*/
|
||||||
|
function pasteHandler(ev, term) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
if (ev.clipboardData) {
|
||||||
|
var text = ev.clipboardData.getData('text/plain');
|
||||||
|
term.handler(text);
|
||||||
|
term.textarea.value = '';
|
||||||
|
return term.cancel(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind to right-click event and allow right-click copy and paste.
|
||||||
|
*
|
||||||
|
* **Logic**
|
||||||
|
* If text is selected and right-click happens on selected text, then
|
||||||
|
* do nothing to allow seamless copying.
|
||||||
|
* If no text is selected or right-click is outside of the selection
|
||||||
|
* area, then bring the terminal's input below the cursor, in order to
|
||||||
|
* trigger the event on the textarea and allow-right click paste, without
|
||||||
|
* caring about disappearing selection.
|
||||||
|
* @param {ClipboardEvent} ev The original paste event to be handled
|
||||||
|
* @param {Terminal} term The terminal on which to apply the handled paste event
|
||||||
|
*/
|
||||||
|
function rightClickHandler(ev, term) {
|
||||||
|
var s = document.getSelection(),
|
||||||
|
sText = prepareTextForClipboard(s.toString()),
|
||||||
|
r = s.getRangeAt(0);
|
||||||
|
|
||||||
|
var x = ev.clientX,
|
||||||
|
y = ev.clientY;
|
||||||
|
|
||||||
|
var cr = r.getClientRects(),
|
||||||
|
clickIsOnSelection = false,
|
||||||
|
i, rect;
|
||||||
|
|
||||||
|
for (i=0; i<cr.length; i++) {
|
||||||
|
rect = cr[i];
|
||||||
|
clickIsOnSelection = (
|
||||||
|
(x > rect.left) && (x < rect.right) &&
|
||||||
|
(y > rect.top) && (y < rect.bottom)
|
||||||
|
);
|
||||||
|
// If we clicked on selection and selection is not a single space,
|
||||||
|
// then mark the right click as copy-only. We check for the single
|
||||||
|
// space selection, as this can happen when clicking on an
|
||||||
|
// and there is not much pointing in copying a single space.
|
||||||
|
// Single space is char
|
||||||
|
if (clickIsOnSelection && (sText !== ' ')) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bring textarea at the cursor position
|
||||||
|
if (!clickIsOnSelection) {
|
||||||
|
term.textarea.style.position = 'fixed';
|
||||||
|
term.textarea.style.width = '10px';
|
||||||
|
term.textarea.style.height = '10px';
|
||||||
|
term.textarea.style.left = x + 'px';
|
||||||
|
term.textarea.style.top = y + 'px';
|
||||||
|
term.textarea.style.zIndex = 1000;
|
||||||
|
term.textarea.focus();
|
||||||
|
|
||||||
|
// Reset the terminal textarea's styling
|
||||||
|
setTimeout(function () {
|
||||||
|
term.textarea.style.position = null;
|
||||||
|
term.textarea.style.width = null;
|
||||||
|
term.textarea.style.height = null;
|
||||||
|
term.textarea.style.left = null;
|
||||||
|
term.textarea.style.top = null;
|
||||||
|
term.textarea.style.zIndex = null;
|
||||||
|
}, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
prepareTextForClipboard, copyHandler, pasteHandler, rightClickHandler
|
||||||
|
};
|
||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* xterm.js: xterm, in the browser
|
* xterm.js: xterm, in the browser
|
||||||
* Copyright (c) 2014, sourceLair Limited (www.sourcelair.com (MIT License)
|
* Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License)
|
||||||
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
||||||
* https://github.com/chjj/term.js
|
* https://github.com/chjj/term.js
|
||||||
*
|
*
|
||||||
@@ -54,13 +54,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.terminal .xterm-helper-textarea {
|
.terminal .xterm-helper-textarea {
|
||||||
position: absolute;
|
|
||||||
/*
|
/*
|
||||||
* HACK: to fix IE's blinking cursor
|
* HACK: to fix IE's blinking cursor
|
||||||
* Move textarea out of the screen to the far left, so that the cursor is not visible.
|
* Move textarea out of the screen to the far left, so that the cursor is not visible.
|
||||||
*/
|
*/
|
||||||
left: -9999em;
|
position: absolute;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
left: -9999em;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
z-index: -10;
|
z-index: -10;
|
||||||
|
|||||||
+192
-536
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* xterm.js: xterm, in the browser
|
* xterm.js: xterm, in the browser
|
||||||
* Copyright (c) 2014, sourceLair Limited (www.sourcelair.com (MIT License)
|
* Copyright (c) 2014-2014, SourceLair Private Company <www.sourcelair.com> (MIT License)
|
||||||
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
||||||
* https://github.com/chjj/term.js
|
* https://github.com/chjj/term.js
|
||||||
*
|
*
|
||||||
@@ -31,25 +31,11 @@
|
|||||||
* other features.
|
* other features.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function (xterm) {
|
import { CompositionHelper } from './CompositionHelper.js';
|
||||||
if (typeof exports === 'object' && typeof module === 'object') {
|
import { EventEmitter } from './EventEmitter.js';
|
||||||
/*
|
import { Viewport } from './Viewport.js';
|
||||||
* CommonJS environment
|
import { rightClickHandler, pasteHandler, copyHandler } from './handlers/Clipboard.js';
|
||||||
*/
|
|
||||||
module.exports = xterm.call(this);
|
|
||||||
} else if (typeof define == 'function') {
|
|
||||||
/*
|
|
||||||
* Require.js is available
|
|
||||||
*/
|
|
||||||
define([], xterm.bind(window));
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
* Plain browser environment
|
|
||||||
*/
|
|
||||||
this.Xterm = xterm.call(this);
|
|
||||||
this.Terminal = this.Xterm; /* Backwards compatibility with term.js */
|
|
||||||
}
|
|
||||||
})(function() {
|
|
||||||
/**
|
/**
|
||||||
* Terminal Emulation References:
|
* Terminal Emulation References:
|
||||||
* http://vt100.net/
|
* http://vt100.net/
|
||||||
@@ -61,401 +47,8 @@
|
|||||||
* http://linux.die.net/man/7/urxvt
|
* http://linux.die.net/man/7/urxvt
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
// Let it work inside Node.js for automated testing purposes.
|
||||||
|
var document = (typeof window != 'undefined') ? window.document : null;
|
||||||
/**
|
|
||||||
* Shared
|
|
||||||
*/
|
|
||||||
|
|
||||||
var window = this, document = this.document;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* EventEmitter
|
|
||||||
*/
|
|
||||||
|
|
||||||
function EventEmitter() {
|
|
||||||
this._events = this._events || {};
|
|
||||||
}
|
|
||||||
|
|
||||||
EventEmitter.prototype.addListener = function(type, listener) {
|
|
||||||
this._events[type] = this._events[type] || [];
|
|
||||||
this._events[type].push(listener);
|
|
||||||
};
|
|
||||||
|
|
||||||
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
|
|
||||||
|
|
||||||
EventEmitter.prototype.removeListener = function(type, listener) {
|
|
||||||
if (!this._events[type]) return;
|
|
||||||
|
|
||||||
var obj = this._events[type]
|
|
||||||
, i = obj.length;
|
|
||||||
|
|
||||||
while (i--) {
|
|
||||||
if (obj[i] === listener || obj[i].listener === listener) {
|
|
||||||
obj.splice(i, 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
|
|
||||||
|
|
||||||
EventEmitter.prototype.removeAllListeners = function(type) {
|
|
||||||
if (this._events[type]) delete this._events[type];
|
|
||||||
};
|
|
||||||
|
|
||||||
EventEmitter.prototype.once = function(type, listener) {
|
|
||||||
var self = this;
|
|
||||||
function on() {
|
|
||||||
var args = Array.prototype.slice.call(arguments);
|
|
||||||
this.removeListener(type, on);
|
|
||||||
return listener.apply(this, args);
|
|
||||||
}
|
|
||||||
on.listener = listener;
|
|
||||||
return this.on(type, on);
|
|
||||||
};
|
|
||||||
|
|
||||||
EventEmitter.prototype.emit = function(type) {
|
|
||||||
if (!this._events[type]) return;
|
|
||||||
|
|
||||||
var args = Array.prototype.slice.call(arguments, 1)
|
|
||||||
, obj = this._events[type]
|
|
||||||
, l = obj.length
|
|
||||||
, i = 0;
|
|
||||||
|
|
||||||
for (; i < l; i++) {
|
|
||||||
obj[i].apply(this, args);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
EventEmitter.prototype.listeners = function(type) {
|
|
||||||
return this._events[type] = this._events[type] || [];
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encapsulates the logic for handling compositionstart, compositionupdate and compositionend
|
|
||||||
* events, displaying the in-progress composition to the UI and forwarding the final composition
|
|
||||||
* to the handler.
|
|
||||||
* @param {HTMLTextAreaElement} textarea The textarea that xterm uses for input.
|
|
||||||
* @param {HTMLElement} compositionView The element to display the in-progress composition in.
|
|
||||||
* @param {Terminal} terminal The Terminal to forward the finished composition to.
|
|
||||||
*/
|
|
||||||
function CompositionHelper(textarea, compositionView, terminal) {
|
|
||||||
this.textarea = textarea;
|
|
||||||
this.compositionView = compositionView;
|
|
||||||
this.terminal = terminal;
|
|
||||||
|
|
||||||
// Whether input composition is currently happening, eg. via a mobile keyboard, speech input
|
|
||||||
// or IME. This variable determines whether the compositionText should be displayed on the UI.
|
|
||||||
this.isComposing = false;
|
|
||||||
|
|
||||||
// The input currently being composed, eg. via a mobile keyboard, speech input or IME.
|
|
||||||
this.compositionText = null;
|
|
||||||
|
|
||||||
// The position within the input textarea's value of the current composition.
|
|
||||||
this.compositionPosition = { start: null, end: null };
|
|
||||||
|
|
||||||
// Whether a composition is in the process of being sent, setting this to false will cancel
|
|
||||||
// any in-progress composition.
|
|
||||||
this.isSendingComposition = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the compositionstart event, activating the composition view.
|
|
||||||
*/
|
|
||||||
CompositionHelper.prototype.compositionstart = function() {
|
|
||||||
this.isComposing = true;
|
|
||||||
this.compositionPosition.start = this.textarea.value.length;
|
|
||||||
this.compositionView.textContent = '';
|
|
||||||
this.compositionView.classList.add('active');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the compositionupdate event, updating the composition view.
|
|
||||||
* @param {CompositionEvent} ev The event.
|
|
||||||
*/
|
|
||||||
CompositionHelper.prototype.compositionupdate = function(ev) {
|
|
||||||
this.compositionView.textContent = ev.data;
|
|
||||||
this.updateCompositionElements();
|
|
||||||
var self = this;
|
|
||||||
setTimeout(function() {
|
|
||||||
self.compositionPosition.end = self.textarea.value.length;
|
|
||||||
}, 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the compositionend event, hiding the composition view and sending the composition to
|
|
||||||
* the handler.
|
|
||||||
*/
|
|
||||||
CompositionHelper.prototype.compositionend = function() {
|
|
||||||
this.finalizeComposition(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the keydown event, routing any necessary events to the CompositionHelper functions.
|
|
||||||
* @return Whether the Terminal should continue processing the keydown event.
|
|
||||||
*/
|
|
||||||
CompositionHelper.prototype.keydown = function(ev) {
|
|
||||||
if (this.isComposing || this.isSendingComposition) {
|
|
||||||
if (ev.keyCode === 229) {
|
|
||||||
// Continue composing if the keyCode is the "composition character"
|
|
||||||
return false;
|
|
||||||
} else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {
|
|
||||||
// Continue composing if the keyCode is a modifier key
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
// Finish composition immediately. This is mainly here for the case where enter is
|
|
||||||
// pressed and the handler needs to be triggered before the command is executed.
|
|
||||||
this.finalizeComposition(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ev.keyCode === 229) {
|
|
||||||
// If the "composition character" is used but gets to this point it means a non-composition
|
|
||||||
// character (eg. numbers and punctuation) was pressed when the IME was active.
|
|
||||||
this.handleAnyTextareaChanges();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finalizes the composition, resuming regular input actions. This is called when a composition
|
|
||||||
* is ending.
|
|
||||||
* @param {boolean} waitForPropogation Whether to wait for events to propogate before sending
|
|
||||||
* the input. This should be false if a non-composition keystroke is entered before the
|
|
||||||
* compositionend event is triggered, such as enter, so that the composition is send before
|
|
||||||
* the command is executed.
|
|
||||||
*/
|
|
||||||
CompositionHelper.prototype.finalizeComposition = function(waitForPropogation) {
|
|
||||||
this.compositionView.classList.remove('active');
|
|
||||||
this.isComposing = false;
|
|
||||||
this.clearTextareaPosition();
|
|
||||||
|
|
||||||
if (!waitForPropogation) {
|
|
||||||
// Cancel any delayed composition send requests and send the input immediately.
|
|
||||||
this.isSendingComposition = false;
|
|
||||||
var input = this.textarea.value.substring(this.compositionPosition.start, this.compositionPosition.end);
|
|
||||||
this.terminal.handler(input);
|
|
||||||
} else {
|
|
||||||
// Make a deep copy of the composition position here as a new compositionstart event may
|
|
||||||
// fire before the setTimeout executes.
|
|
||||||
var currentCompositionPosition = {
|
|
||||||
start: this.compositionPosition.start,
|
|
||||||
end: this.compositionPosition.end,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since composition* events happen before the changes take place in the textarea on most
|
|
||||||
// browsers, use a setTimeout with 0ms time to allow the native compositionend event to
|
|
||||||
// complete. This ensures the correct character is retrieved, this solution was used
|
|
||||||
// because:
|
|
||||||
// - The compositionend event's data property is unreliable, at least on Chromium
|
|
||||||
// - The last compositionupdate event's data property does not always accurately describe
|
|
||||||
// the character, a counter example being Korean where an ending consonsant can move to
|
|
||||||
// the following character if the following input is a vowel.
|
|
||||||
var self = this;
|
|
||||||
this.isSendingComposition = true;
|
|
||||||
setTimeout(function () {
|
|
||||||
// Ensure that the input has not already been sent
|
|
||||||
if (self.isSendingComposition) {
|
|
||||||
self.isSendingComposition = false;
|
|
||||||
var input;
|
|
||||||
if (self.isComposing) {
|
|
||||||
// Use the end position to get the string if a new composition has started.
|
|
||||||
input = self.textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end);
|
|
||||||
} else {
|
|
||||||
// Don't use the end position here in order to pick up any characters after the
|
|
||||||
// composition has finished, for example when typing a non-composition character
|
|
||||||
// (eg. 2) after a composition character.
|
|
||||||
input = self.textarea.value.substring(currentCompositionPosition.start);
|
|
||||||
}
|
|
||||||
self.terminal.handler(input);
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply any changes made to the textarea after the current event chain is allowed to complete.
|
|
||||||
* This should be called when not currently composing but a keydown event with the "composition
|
|
||||||
* character" (229) is triggered, in order to allow non-composition text to be entered when an
|
|
||||||
* IME is active.
|
|
||||||
*/
|
|
||||||
CompositionHelper.prototype.handleAnyTextareaChanges = function() {
|
|
||||||
var oldValue = this.textarea.value;
|
|
||||||
var self = this;
|
|
||||||
setTimeout(function() {
|
|
||||||
// Ignore if a composition has started since the timeout
|
|
||||||
if (!self.isComposing) {
|
|
||||||
var newValue = self.textarea.value;
|
|
||||||
var diff = newValue.replace(oldValue, '');
|
|
||||||
if (diff.length > 0) {
|
|
||||||
self.terminal.handler(diff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Positions the composition view on top of the cursor and the textarea just below it (so the
|
|
||||||
* IME helper dialog is positioned correctly).
|
|
||||||
*/
|
|
||||||
CompositionHelper.prototype.updateCompositionElements = function(dontRecurse) {
|
|
||||||
if (!this.isComposing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var cursor = this.terminal.element.querySelector('.terminal-cursor');
|
|
||||||
if (cursor) {
|
|
||||||
this.compositionView.style.left = cursor.offsetLeft + 'px';
|
|
||||||
this.compositionView.style.top = cursor.offsetTop + 'px';
|
|
||||||
var compositionViewBounds = this.compositionView.getBoundingClientRect();
|
|
||||||
this.textarea.style.left = cursor.offsetLeft + compositionViewBounds.width + 'px';
|
|
||||||
this.textarea.style.top = (cursor.offsetTop + cursor.offsetHeight) + 'px';
|
|
||||||
}
|
|
||||||
if (!dontRecurse) {
|
|
||||||
setTimeout(this.updateCompositionElements.bind(this, true), 0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the textarea's position so that the cursor does not blink on IE.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
CompositionHelper.prototype.clearTextareaPosition = function() {
|
|
||||||
this.textarea.style.left = '';
|
|
||||||
this.textarea.style.top = '';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the viewport of a terminal, the visible area within the larger buffer of output.
|
|
||||||
* Logic for the virtual scroll bar is included in this object.
|
|
||||||
* @param {Terminal} terminal The Terminal object.
|
|
||||||
* @param {HTMLElement} viewportElement The DOM element acting as the viewport
|
|
||||||
* @param {HTMLElement} charMeasureElement A DOM element used to measure the character size of
|
|
||||||
* the terminal.
|
|
||||||
*/
|
|
||||||
function Viewport(terminal, viewportElement, scrollArea, charMeasureElement) {
|
|
||||||
this.terminal = terminal;
|
|
||||||
this.viewportElement = viewportElement;
|
|
||||||
this.scrollArea = scrollArea;
|
|
||||||
this.charMeasureElement = charMeasureElement;
|
|
||||||
this.currentRowHeight = 0;
|
|
||||||
this.lastRecordedBufferLength = 0;
|
|
||||||
this.lastRecordedViewportHeight = 0;
|
|
||||||
|
|
||||||
this.terminal.on('scroll', this.syncScrollArea.bind(this));
|
|
||||||
this.terminal.on('resize', this.syncScrollArea.bind(this));
|
|
||||||
this.viewportElement.addEventListener('scroll', this.onScroll.bind(this));
|
|
||||||
|
|
||||||
this.syncScrollArea();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refreshes row height, setting line-height, viewport height and scroll area height if
|
|
||||||
* necessary.
|
|
||||||
* @param {number|undefined} charSize A character size measurement bounding rect object, if it
|
|
||||||
* doesn't exist it will be created.
|
|
||||||
*/
|
|
||||||
Viewport.prototype.refresh = function(charSize) {
|
|
||||||
var size = charSize || this.charMeasureElement.getBoundingClientRect();
|
|
||||||
if (size.height > 0) {
|
|
||||||
var rowHeightChanged = size.height !== this.currentRowHeight;
|
|
||||||
if (rowHeightChanged) {
|
|
||||||
this.currentRowHeight = size.height;
|
|
||||||
this.viewportElement.style.lineHeight = size.height + 'px';
|
|
||||||
this.terminal.rowContainer.style.lineHeight = size.height + 'px';
|
|
||||||
}
|
|
||||||
var viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows;
|
|
||||||
if (rowHeightChanged || viewportHeightChanged) {
|
|
||||||
this.lastRecordedViewportHeight = this.terminal.rows;
|
|
||||||
this.viewportElement.style.height = size.height * this.terminal.rows + 'px';
|
|
||||||
}
|
|
||||||
this.scrollArea.style.height = (size.height * this.lastRecordedBufferLength) + 'px';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates dimensions and synchronizes the scroll area if necessary.
|
|
||||||
*/
|
|
||||||
Viewport.prototype.syncScrollArea = function() {
|
|
||||||
if (this.isApplicationMode) {
|
|
||||||
// Fix scroll bar in application mode
|
|
||||||
this.lastRecordedBufferLength = this.terminal.rows;
|
|
||||||
this.refresh();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.lastRecordedBufferLength !== this.terminal.lines.length) {
|
|
||||||
// If buffer height changed
|
|
||||||
this.lastRecordedBufferLength = this.terminal.lines.length;
|
|
||||||
this.refresh();
|
|
||||||
} else if (this.lastRecordedViewportHeight !== this.terminal.rows) {
|
|
||||||
// If viewport height changed
|
|
||||||
this.refresh();
|
|
||||||
} else {
|
|
||||||
// If size has changed, refresh viewport
|
|
||||||
var size = this.charMeasureElement.getBoundingClientRect();
|
|
||||||
if (size.height !== this.currentRowHeight) {
|
|
||||||
this.refresh(size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sync scrollTop
|
|
||||||
var scrollTop = this.terminal.ydisp * this.currentRowHeight;
|
|
||||||
if (this.viewportElement.scrollTop !== scrollTop) {
|
|
||||||
this.viewportElement.scrollTop = scrollTop;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the application mode of the viewport.
|
|
||||||
* @param {boolean} isApplicationMode Sets whether the terminal is in application mode. true
|
|
||||||
* for application mode (DECKPAM) and false for normal mode (DECKPNM).
|
|
||||||
*/
|
|
||||||
Viewport.prototype.setApplicationMode = function(isApplicationMode) {
|
|
||||||
this.isApplicationMode = isApplicationMode;
|
|
||||||
this.syncScrollArea();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles scroll events on the viewport, calculating the new viewport and requesting the
|
|
||||||
* terminal to scroll to it.
|
|
||||||
* @param {Event} ev The scroll event.
|
|
||||||
*/
|
|
||||||
Viewport.prototype.onScroll = function(ev) {
|
|
||||||
if (this.isApplicationMode) {
|
|
||||||
// Scrolling via the scroll bar is disabled during application mode
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);
|
|
||||||
var diff = newRow - this.terminal.ydisp;
|
|
||||||
this.terminal.scrollDisp(diff, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles mouse wheel events by adjusting the viewport's scrollTop and delegating the actual
|
|
||||||
* scrolling to `onScroll`, this event needs to be attached manually by the consumer of
|
|
||||||
* `Viewport`.
|
|
||||||
* @param {WheelEvent} ev The mouse wheel event.
|
|
||||||
*/
|
|
||||||
Viewport.prototype.onWheel = function(ev) {
|
|
||||||
if (ev.deltaY === 0) {
|
|
||||||
// Do nothing if it's not a vertical scroll event
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Fallback to WheelEvent.DOM_DELTA_PIXEL
|
|
||||||
var multiplier = 1;
|
|
||||||
if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {
|
|
||||||
multiplier = this.currentRowHeight;
|
|
||||||
} else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
|
|
||||||
multiplier = this.currentRowHeight * this.terminal.rows;
|
|
||||||
}
|
|
||||||
this.viewportElement.scrollTop += ev.deltaY * multiplier;
|
|
||||||
// Prevent the page from scrolling when the terminal scrolls
|
|
||||||
ev.preventDefault();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* States
|
* States
|
||||||
@@ -526,8 +119,9 @@
|
|||||||
|
|
||||||
// this.context = options.context || window;
|
// this.context = options.context || window;
|
||||||
// this.document = options.document || document;
|
// this.document = options.document || document;
|
||||||
this.parent = options.body || options.parent
|
this.parent = options.body || options.parent || (
|
||||||
|| (document ? document.getElementsByTagName('body')[0] : null);
|
document ? document.getElementsByTagName('body')[0] : null
|
||||||
|
);
|
||||||
|
|
||||||
this.cols = options.cols || options.geometry[0];
|
this.cols = options.cols || options.geometry[0];
|
||||||
this.rows = options.rows || options.geometry[1];
|
this.rows = options.rows || options.geometry[1];
|
||||||
@@ -764,6 +358,35 @@
|
|||||||
return this.textarea.focus();
|
return this.textarea.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves an option's value from the terminal.
|
||||||
|
* @param {string} key The option key.
|
||||||
|
*/
|
||||||
|
Terminal.prototype.getOption = function(key, value) {
|
||||||
|
if (!(key in Terminal.defaults)) {
|
||||||
|
throw new Error('No option with key "' + key + '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof this.options[key] !== 'undefined') {
|
||||||
|
return this.options[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this[key];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an option on the terminal.
|
||||||
|
* @param {string} key The option key.
|
||||||
|
* @param {string} value The option value.
|
||||||
|
*/
|
||||||
|
Terminal.prototype.setOption = function(key, value) {
|
||||||
|
if (!(key in Terminal.defaults)) {
|
||||||
|
throw new Error('No option with key "' + key + '"');
|
||||||
|
}
|
||||||
|
this[key] = value;
|
||||||
|
this.options[key] = value;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Binds the desired focus behavior on a given terminal object.
|
* Binds the desired focus behavior on a given terminal object.
|
||||||
*
|
*
|
||||||
@@ -809,52 +432,20 @@
|
|||||||
* Initialize default behavior
|
* Initialize default behavior
|
||||||
*/
|
*/
|
||||||
Terminal.prototype.initGlobal = function() {
|
Terminal.prototype.initGlobal = function() {
|
||||||
Terminal.bindPaste(this);
|
var term = this;
|
||||||
|
|
||||||
Terminal.bindKeys(this);
|
Terminal.bindKeys(this);
|
||||||
Terminal.bindCopy(this);
|
|
||||||
Terminal.bindFocus(this);
|
Terminal.bindFocus(this);
|
||||||
Terminal.bindBlur(this);
|
Terminal.bindBlur(this);
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
// Bind clipboard functionality
|
||||||
* Bind to paste event and allow both keyboard and right-click pasting, without having the
|
on(this.element, 'copy', copyHandler);
|
||||||
* contentEditable value set to true.
|
on(this.textarea, 'paste', function (ev) {
|
||||||
*/
|
pasteHandler.call(this, ev, term);
|
||||||
Terminal.bindPaste = function(term) {
|
});
|
||||||
on([term.textarea, term.element], 'paste', function(ev) {
|
on(this.element, 'contextmenu', function (ev) {
|
||||||
ev.stopPropagation();
|
rightClickHandler.call(this, ev, term);
|
||||||
if (ev.clipboardData) {
|
|
||||||
var text = ev.clipboardData.getData('text/plain');
|
|
||||||
term.handler(text);
|
|
||||||
term.textarea.value = '';
|
|
||||||
return term.cancel(ev);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepares text copied from terminal selection, to be saved in the clipboard by:
|
|
||||||
* 1. stripping all trailing white spaces
|
|
||||||
* 2. converting all non-breaking spaces to regular spaces
|
|
||||||
* @param {string} text The copied text that needs processing for storing in clipboard
|
|
||||||
* @returns {string}
|
|
||||||
* @static
|
|
||||||
*/
|
|
||||||
Terminal.prepareCopiedTextForClipboard = function (text) {
|
|
||||||
var space = String.fromCharCode(32),
|
|
||||||
nonBreakingSpace = String.fromCharCode(160),
|
|
||||||
allNonBreakingSpaces = new RegExp(nonBreakingSpace, 'g'),
|
|
||||||
processedText = text.split('\n').map(function (line) {
|
|
||||||
/**
|
|
||||||
* Strip all trailing white spaces and convert all non-breaking spaces to regular
|
|
||||||
* spaces.
|
|
||||||
*/
|
|
||||||
var processedLine = line.replace(/\s+$/g, '').replace(allNonBreakingSpaces, space);
|
|
||||||
|
|
||||||
return processedLine;
|
|
||||||
}).join('\n');
|
|
||||||
|
|
||||||
return processedText;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -893,16 +484,6 @@
|
|||||||
term.on('refresh', term.compositionHelper.updateCompositionElements.bind(term.compositionHelper));
|
term.on('refresh', term.compositionHelper.updateCompositionElements.bind(term.compositionHelper));
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Binds copy functionality to the given terminal.
|
|
||||||
* @static
|
|
||||||
*/
|
|
||||||
Terminal.bindCopy = function(term) {
|
|
||||||
on(term.element, 'copy', function(ev) {
|
|
||||||
return; // temporary
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert the given row to the terminal or produce a new one
|
* Insert the given row to the terminal or produce a new one
|
||||||
@@ -934,25 +515,19 @@
|
|||||||
throw new Error('Terminal requires a parent element.');
|
throw new Error('Terminal requires a parent element.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Grab global elements
|
||||||
* Grab global elements
|
|
||||||
*/
|
|
||||||
this.context = this.parent.ownerDocument.defaultView;
|
this.context = this.parent.ownerDocument.defaultView;
|
||||||
this.document = this.parent.ownerDocument;
|
this.document = this.parent.ownerDocument;
|
||||||
this.body = this.document.getElementsByTagName('body')[0];
|
this.body = this.document.getElementsByTagName('body')[0];
|
||||||
|
|
||||||
/*
|
// Parse User-Agent
|
||||||
* Parse User-Agent
|
|
||||||
*/
|
|
||||||
if (this.context.navigator && this.context.navigator.userAgent) {
|
if (this.context.navigator && this.context.navigator.userAgent) {
|
||||||
this.isMSIE = !!~this.context.navigator.userAgent.indexOf('MSIE');
|
this.isMSIE = !!~this.context.navigator.userAgent.indexOf('MSIE');
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Find the users platform. We use this to interpret the meta key
|
||||||
* Find the users platform. We use this to interpret the meta key
|
// and ISO third level shifts.
|
||||||
* and ISO third level shifts.
|
// http://stackoverflow.com/q/19877924/577598
|
||||||
* http://stackoverflow.com/questions/19877924/what-is-the-list-of-possible-values-for-navigator-platform-as-of-today
|
|
||||||
*/
|
|
||||||
if (this.context.navigator && this.context.navigator.platform) {
|
if (this.context.navigator && this.context.navigator.platform) {
|
||||||
this.isMac = contains(
|
this.isMac = contains(
|
||||||
this.context.navigator.platform,
|
this.context.navigator.platform,
|
||||||
@@ -966,9 +541,7 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
//Create main element container
|
||||||
* Create main element container
|
|
||||||
*/
|
|
||||||
this.element = this.document.createElement('div');
|
this.element = this.document.createElement('div');
|
||||||
this.element.classList.add('terminal');
|
this.element.classList.add('terminal');
|
||||||
this.element.classList.add('xterm');
|
this.element.classList.add('xterm');
|
||||||
@@ -984,19 +557,15 @@
|
|||||||
this.viewportScrollArea.classList.add('xterm-scroll-area');
|
this.viewportScrollArea.classList.add('xterm-scroll-area');
|
||||||
this.viewportElement.appendChild(this.viewportScrollArea);
|
this.viewportElement.appendChild(this.viewportScrollArea);
|
||||||
|
|
||||||
/*
|
// Create the container that will hold the lines of the terminal and then
|
||||||
* Create the container that will hold the lines of the terminal and then
|
// produce the lines the lines.
|
||||||
* produce the lines the lines.
|
|
||||||
*/
|
|
||||||
this.rowContainer = document.createElement('div');
|
this.rowContainer = document.createElement('div');
|
||||||
this.rowContainer.classList.add('xterm-rows');
|
this.rowContainer.classList.add('xterm-rows');
|
||||||
this.element.appendChild(this.rowContainer);
|
this.element.appendChild(this.rowContainer);
|
||||||
this.children = [];
|
this.children = [];
|
||||||
|
|
||||||
/*
|
// Create the container that will hold helpers like the textarea for
|
||||||
* Create the container that will hold helpers like the textarea for
|
// capturing DOM Events. Then produce the helpers.
|
||||||
* capturing DOM Events. Then produce the helpers.
|
|
||||||
*/
|
|
||||||
this.helperContainer = document.createElement('div');
|
this.helperContainer = document.createElement('div');
|
||||||
this.helperContainer.classList.add('xterm-helpers');
|
this.helperContainer.classList.add('xterm-helpers');
|
||||||
// TODO: This should probably be inserted once it's filled to prevent an additional layout
|
// TODO: This should probably be inserted once it's filled to prevent an additional layout
|
||||||
@@ -1042,7 +611,7 @@
|
|||||||
// Ensure there is a Terminal.focus.
|
// Ensure there is a Terminal.focus.
|
||||||
this.focus();
|
this.focus();
|
||||||
|
|
||||||
on(this.element, 'mouseup', function() {
|
on(this.element, 'click', function() {
|
||||||
var selection = document.getSelection(),
|
var selection = document.getSelection(),
|
||||||
collapsed = selection.isCollapsed,
|
collapsed = selection.isCollapsed,
|
||||||
isRange = typeof collapsed == 'boolean' ? !collapsed : selection.type == 'Range';
|
isRange = typeof collapsed == 'boolean' ? !collapsed : selection.type == 'Range';
|
||||||
@@ -1407,7 +976,6 @@
|
|||||||
// the shell for example
|
// the shell for example
|
||||||
on(el, 'wheel', function(ev) {
|
on(el, 'wheel', function(ev) {
|
||||||
if (self.mouseEvents) return;
|
if (self.mouseEvents) return;
|
||||||
if (self.applicationKeypad) return;
|
|
||||||
self.viewport.onWheel(ev);
|
self.viewport.onWheel(ev);
|
||||||
return self.cancel(ev);
|
return self.cancel(ev);
|
||||||
});
|
});
|
||||||
@@ -1579,9 +1147,7 @@
|
|||||||
classNames.push('xterm-blink');
|
classNames.push('xterm-blink');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// If inverse flag is on, then swap the foreground and background variables.
|
||||||
* If inverse flag is on, then swap the foreground and background variables.
|
|
||||||
*/
|
|
||||||
if (flags & Terminal.flags.INVERSE) {
|
if (flags & Terminal.flags.INVERSE) {
|
||||||
/* One-line variable swap in JavaScript: http://stackoverflow.com/a/16201730 */
|
/* One-line variable swap in JavaScript: http://stackoverflow.com/a/16201730 */
|
||||||
bg = [fg, fg = bg][0];
|
bg = [fg, fg = bg][0];
|
||||||
@@ -2084,7 +1650,7 @@
|
|||||||
case '=':
|
case '=':
|
||||||
this.log('Serial port requested application keypad.');
|
this.log('Serial port requested application keypad.');
|
||||||
this.applicationKeypad = true;
|
this.applicationKeypad = true;
|
||||||
this.viewport.setApplicationMode(true);
|
this.viewport.syncScrollArea();
|
||||||
this.state = normal;
|
this.state = normal;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -2092,7 +1658,7 @@
|
|||||||
case '>':
|
case '>':
|
||||||
this.log('Switching back to normal keypad.');
|
this.log('Switching back to normal keypad.');
|
||||||
this.applicationKeypad = false;
|
this.applicationKeypad = false;
|
||||||
this.viewport.setApplicationMode(false);
|
this.viewport.syncScrollArea();
|
||||||
this.state = normal;
|
this.state = normal;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -2835,7 +2401,7 @@
|
|||||||
|
|
||||||
if (result.scrollDisp) {
|
if (result.scrollDisp) {
|
||||||
this.scrollDisp(result.scrollDisp);
|
this.scrollDisp(result.scrollDisp);
|
||||||
return this.cancel(ev);
|
return this.cancel(ev, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isThirdLevelShift(this, ev)) {
|
if (isThirdLevelShift(this, ev)) {
|
||||||
@@ -2878,16 +2444,16 @@
|
|||||||
};
|
};
|
||||||
var modifiers = ev.shiftKey << 0 | ev.altKey << 1 | ev.ctrlKey << 2 | ev.metaKey << 3;
|
var modifiers = ev.shiftKey << 0 | ev.altKey << 1 | ev.ctrlKey << 2 | ev.metaKey << 3;
|
||||||
switch (ev.keyCode) {
|
switch (ev.keyCode) {
|
||||||
// backspace
|
|
||||||
case 8:
|
case 8:
|
||||||
|
// backspace
|
||||||
if (ev.shiftKey) {
|
if (ev.shiftKey) {
|
||||||
result.key = '\x08'; // ^H
|
result.key = '\x08'; // ^H
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
result.key = '\x7f'; // ^?
|
result.key = '\x7f'; // ^?
|
||||||
break;
|
break;
|
||||||
// tab
|
|
||||||
case 9:
|
case 9:
|
||||||
|
// tab
|
||||||
if (ev.shiftKey) {
|
if (ev.shiftKey) {
|
||||||
result.key = '\x1b[Z';
|
result.key = '\x1b[Z';
|
||||||
break;
|
break;
|
||||||
@@ -2895,18 +2461,18 @@
|
|||||||
result.key = '\t';
|
result.key = '\t';
|
||||||
result.cancel = true;
|
result.cancel = true;
|
||||||
break;
|
break;
|
||||||
// return/enter
|
|
||||||
case 13:
|
case 13:
|
||||||
|
// return/enter
|
||||||
result.key = '\r';
|
result.key = '\r';
|
||||||
result.cancel = true;
|
result.cancel = true;
|
||||||
break;
|
break;
|
||||||
// escape
|
|
||||||
case 27:
|
case 27:
|
||||||
|
// escape
|
||||||
result.key = '\x1b';
|
result.key = '\x1b';
|
||||||
result.cancel = true;
|
result.cancel = true;
|
||||||
break;
|
break;
|
||||||
// left-arrow
|
|
||||||
case 37:
|
case 37:
|
||||||
|
// left-arrow
|
||||||
if (modifiers) {
|
if (modifiers) {
|
||||||
result.key = '\x1b[1;' + (modifiers + 1) + 'D';
|
result.key = '\x1b[1;' + (modifiers + 1) + 'D';
|
||||||
// HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards
|
// HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards
|
||||||
@@ -2920,8 +2486,8 @@
|
|||||||
result.key = '\x1b[D';
|
result.key = '\x1b[D';
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
// right-arrow
|
|
||||||
case 39:
|
case 39:
|
||||||
|
// right-arrow
|
||||||
if (modifiers) {
|
if (modifiers) {
|
||||||
result.key = '\x1b[1;' + (modifiers + 1) + 'C';
|
result.key = '\x1b[1;' + (modifiers + 1) + 'C';
|
||||||
// HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward
|
// HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward
|
||||||
@@ -2935,8 +2501,8 @@
|
|||||||
result.key = '\x1b[C';
|
result.key = '\x1b[C';
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
// up-arrow
|
|
||||||
case 38:
|
case 38:
|
||||||
|
// up-arrow
|
||||||
if (modifiers) {
|
if (modifiers) {
|
||||||
result.key = '\x1b[1;' + (modifiers + 1) + 'A';
|
result.key = '\x1b[1;' + (modifiers + 1) + 'A';
|
||||||
// HACK: Make Alt + up-arrow behave like Ctrl + up-arrow
|
// HACK: Make Alt + up-arrow behave like Ctrl + up-arrow
|
||||||
@@ -2950,8 +2516,8 @@
|
|||||||
result.key = '\x1b[A';
|
result.key = '\x1b[A';
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
// down-arrow
|
|
||||||
case 40:
|
case 40:
|
||||||
|
// down-arrow
|
||||||
if (modifiers) {
|
if (modifiers) {
|
||||||
result.key = '\x1b[1;' + (modifiers + 1) + 'B';
|
result.key = '\x1b[1;' + (modifiers + 1) + 'B';
|
||||||
// HACK: Make Alt + down-arrow behave like Ctrl + down-arrow
|
// HACK: Make Alt + down-arrow behave like Ctrl + down-arrow
|
||||||
@@ -2965,18 +2531,24 @@
|
|||||||
result.key = '\x1b[B';
|
result.key = '\x1b[B';
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
// insert
|
|
||||||
case 45:
|
case 45:
|
||||||
|
// insert
|
||||||
if (!ev.shiftKey && !ev.ctrlKey) {
|
if (!ev.shiftKey && !ev.ctrlKey) {
|
||||||
// <Ctrl> or <Shift> + <Insert> are used to
|
// <Ctrl> or <Shift> + <Insert> are used to
|
||||||
// copy-paste on some systems.
|
// copy-paste on some systems.
|
||||||
result.key = '\x1b[2~';
|
result.key = '\x1b[2~';
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 46:
|
||||||
// delete
|
// delete
|
||||||
case 46: result.key = '\x1b[3~'; break;
|
if (modifiers) {
|
||||||
// home
|
result.key = '\x1b[3;' + (modifiers + 1) + '~';
|
||||||
|
} else {
|
||||||
|
result.key = '\x1b[3~';
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 36:
|
case 36:
|
||||||
|
// home
|
||||||
if (modifiers)
|
if (modifiers)
|
||||||
result.key = '\x1b[1;' + (modifiers + 1) + 'H';
|
result.key = '\x1b[1;' + (modifiers + 1) + 'H';
|
||||||
else if (this.applicationCursor)
|
else if (this.applicationCursor)
|
||||||
@@ -2984,8 +2556,8 @@
|
|||||||
else
|
else
|
||||||
result.key = '\x1b[H';
|
result.key = '\x1b[H';
|
||||||
break;
|
break;
|
||||||
// end
|
|
||||||
case 35:
|
case 35:
|
||||||
|
// end
|
||||||
if (modifiers)
|
if (modifiers)
|
||||||
result.key = '\x1b[1;' + (modifiers + 1) + 'F';
|
result.key = '\x1b[1;' + (modifiers + 1) + 'F';
|
||||||
else if (this.applicationCursor)
|
else if (this.applicationCursor)
|
||||||
@@ -2993,35 +2565,107 @@
|
|||||||
else
|
else
|
||||||
result.key = '\x1b[F';
|
result.key = '\x1b[F';
|
||||||
break;
|
break;
|
||||||
// page up
|
|
||||||
case 33:
|
case 33:
|
||||||
|
// page up
|
||||||
if (ev.shiftKey) {
|
if (ev.shiftKey) {
|
||||||
result.scrollDisp = -(this.rows - 1);
|
result.scrollDisp = -(this.rows - 1);
|
||||||
} else {
|
} else {
|
||||||
result.key = '\x1b[5~';
|
result.key = '\x1b[5~';
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
// page down
|
|
||||||
case 34:
|
case 34:
|
||||||
|
// page down
|
||||||
if (ev.shiftKey) {
|
if (ev.shiftKey) {
|
||||||
result.scrollDisp = this.rows - 1;
|
result.scrollDisp = this.rows - 1;
|
||||||
} else {
|
} else {
|
||||||
result.key = '\x1b[6~';
|
result.key = '\x1b[6~';
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 112:
|
||||||
// F1-F12
|
// F1-F12
|
||||||
case 112: result.key = '\x1bOP'; break;
|
if (modifiers) {
|
||||||
case 113: result.key = '\x1bOQ'; break;
|
result.key = '\x1b[1;' + (modifiers + 1) + 'P';
|
||||||
case 114: result.key = '\x1bOR'; break;
|
} else {
|
||||||
case 115: result.key = '\x1bOS'; break;
|
result.key = '\x1bOP';
|
||||||
case 116: result.key = '\x1b[15~'; break;
|
}
|
||||||
case 117: result.key = '\x1b[17~'; break;
|
break;
|
||||||
case 118: result.key = '\x1b[18~'; break;
|
case 113:
|
||||||
case 119: result.key = '\x1b[19~'; break;
|
if (modifiers) {
|
||||||
case 120: result.key = '\x1b[20~'; break;
|
result.key = '\x1b[1;' + (modifiers + 1) + 'Q';
|
||||||
case 121: result.key = '\x1b[21~'; break;
|
} else {
|
||||||
case 122: result.key = '\x1b[23~'; break;
|
result.key = '\x1bOQ';
|
||||||
case 123: result.key = '\x1b[24~'; break;
|
}
|
||||||
|
break;
|
||||||
|
case 114:
|
||||||
|
if (modifiers) {
|
||||||
|
result.key = '\x1b[1;' + (modifiers + 1) + 'R';
|
||||||
|
} else {
|
||||||
|
result.key = '\x1bOR';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 115:
|
||||||
|
if (modifiers) {
|
||||||
|
result.key = '\x1b[1;' + (modifiers + 1) + 'S';
|
||||||
|
} else {
|
||||||
|
result.key = '\x1bOS';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 116:
|
||||||
|
if (modifiers) {
|
||||||
|
result.key = '\x1b[15;' + (modifiers + 1) + '~';
|
||||||
|
} else {
|
||||||
|
result.key = '\x1b[15~';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 117:
|
||||||
|
if (modifiers) {
|
||||||
|
result.key = '\x1b[17;' + (modifiers + 1) + '~';
|
||||||
|
} else {
|
||||||
|
result.key = '\x1b[17~';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 118:
|
||||||
|
if (modifiers) {
|
||||||
|
result.key = '\x1b[18;' + (modifiers + 1) + '~';
|
||||||
|
} else {
|
||||||
|
result.key = '\x1b[18~';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 119:
|
||||||
|
if (modifiers) {
|
||||||
|
result.key = '\x1b[19;' + (modifiers + 1) + '~';
|
||||||
|
} else {
|
||||||
|
result.key = '\x1b[19~';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 120:
|
||||||
|
if (modifiers) {
|
||||||
|
result.key = '\x1b[20;' + (modifiers + 1) + '~';
|
||||||
|
} else {
|
||||||
|
result.key = '\x1b[20~';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 121:
|
||||||
|
if (modifiers) {
|
||||||
|
result.key = '\x1b[21;' + (modifiers + 1) + '~';
|
||||||
|
} else {
|
||||||
|
result.key = '\x1b[21~';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 122:
|
||||||
|
if (modifiers) {
|
||||||
|
result.key = '\x1b[23;' + (modifiers + 1) + '~';
|
||||||
|
} else {
|
||||||
|
result.key = '\x1b[23~';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 123:
|
||||||
|
if (modifiers) {
|
||||||
|
result.key = '\x1b[24;' + (modifiers + 1) + '~';
|
||||||
|
} else {
|
||||||
|
result.key = '\x1b[24~';
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// a-z and space
|
// a-z and space
|
||||||
if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
|
if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
|
||||||
@@ -3257,9 +2901,7 @@
|
|||||||
}
|
}
|
||||||
this.rows = y;
|
this.rows = y;
|
||||||
|
|
||||||
/*
|
// Make sure that the cursor stays on screen
|
||||||
* Make sure that the cursor stays on screen
|
|
||||||
*/
|
|
||||||
if (this.y >= y) {
|
if (this.y >= y) {
|
||||||
this.y = y - 1;
|
this.y = y - 1;
|
||||||
}
|
}
|
||||||
@@ -3386,6 +3028,24 @@
|
|||||||
this.updateRange(y);
|
this.updateRange(y);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the entire buffer, making the prompt line the new first line.
|
||||||
|
*/
|
||||||
|
Terminal.prototype.clear = function() {
|
||||||
|
if (this.ybase === 0 && this.y === 0) {
|
||||||
|
// Don't clear if it's already clear
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.lines = [this.lines[this.ybase + this.y]];
|
||||||
|
this.ydisp = 0;
|
||||||
|
this.ybase = 0;
|
||||||
|
this.y = 0;
|
||||||
|
for (var i = 1; i < this.rows; i++) {
|
||||||
|
this.lines.push(this.blankLine());
|
||||||
|
}
|
||||||
|
this.refresh(0, this.rows - 1);
|
||||||
|
this.emit('scroll', this.ydisp);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erase all content in the given line
|
* Erase all content in the given line
|
||||||
@@ -3505,6 +3165,7 @@
|
|||||||
Terminal.call(this, this.options);
|
Terminal.call(this, this.options);
|
||||||
this.customKeydownHandler = customKeydownHandler;
|
this.customKeydownHandler = customKeydownHandler;
|
||||||
this.refresh(0, this.rows - 1);
|
this.refresh(0, this.rows - 1);
|
||||||
|
this.viewport.syncScrollArea();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -4362,7 +4023,7 @@
|
|||||||
case 66:
|
case 66:
|
||||||
this.log('Serial port requested application keypad.');
|
this.log('Serial port requested application keypad.');
|
||||||
this.applicationKeypad = true;
|
this.applicationKeypad = true;
|
||||||
this.viewport.setApplicationMode(true);
|
this.viewport.syncScrollArea();
|
||||||
break;
|
break;
|
||||||
case 9: // X10 Mouse
|
case 9: // X10 Mouse
|
||||||
// no release, no motion, no wheel, no modifiers.
|
// no release, no motion, no wheel, no modifiers.
|
||||||
@@ -4561,8 +4222,8 @@
|
|||||||
break;
|
break;
|
||||||
case 66:
|
case 66:
|
||||||
this.log('Switching back to normal keypad.');
|
this.log('Switching back to normal keypad.');
|
||||||
this.viewport.setApplicationMode(false);
|
|
||||||
this.applicationKeypad = false;
|
this.applicationKeypad = false;
|
||||||
|
this.viewport.syncScrollArea();
|
||||||
break;
|
break;
|
||||||
case 9: // X10 Mouse
|
case 9: // X10 Mouse
|
||||||
case 1000: // vt200 mouse
|
case 1000: // vt200 mouse
|
||||||
@@ -4849,6 +4510,7 @@
|
|||||||
this.originMode = false;
|
this.originMode = false;
|
||||||
this.wraparoundMode = false; // autowrap
|
this.wraparoundMode = false; // autowrap
|
||||||
this.applicationKeypad = false; // ?
|
this.applicationKeypad = false; // ?
|
||||||
|
this.viewport.syncScrollArea();
|
||||||
this.applicationCursor = false;
|
this.applicationCursor = false;
|
||||||
this.scrollTop = 0;
|
this.scrollTop = 0;
|
||||||
this.scrollBottom = this.rows - 1;
|
this.scrollBottom = this.rows - 1;
|
||||||
@@ -5232,10 +4894,6 @@
|
|||||||
return w1 !== w2;
|
return w1 !== w2;
|
||||||
}
|
}
|
||||||
|
|
||||||
var String = this.String;
|
|
||||||
var setTimeout = this.setTimeout;
|
|
||||||
var setInterval = this.setInterval;
|
|
||||||
|
|
||||||
function indexOf(obj, el) {
|
function indexOf(obj, el) {
|
||||||
var i = obj.length;
|
var i = obj.length;
|
||||||
while (i--) {
|
while (i--) {
|
||||||
@@ -5444,6 +5102,4 @@
|
|||||||
Terminal.off = off;
|
Terminal.off = off;
|
||||||
Terminal.cancel = cancel;
|
Terminal.cancel = cancel;
|
||||||
|
|
||||||
|
module.exports = Terminal;
|
||||||
return Terminal;
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
var assert = require('chai').assert;
|
||||||
|
var Terminal = require('../src/xterm');
|
||||||
|
var Clipboard = require('../src/handlers/Clipboard');
|
||||||
|
|
||||||
|
|
||||||
|
describe('evaluateCopiedTextProcessing', function () {
|
||||||
|
it('should strip trailing whitespaces and replace nbsps with spaces', function () {
|
||||||
|
var nonBreakingSpace = String.fromCharCode(160),
|
||||||
|
copiedText = 'echo' + nonBreakingSpace + 'hello' + nonBreakingSpace,
|
||||||
|
processedText = Clipboard.prepareTextForClipboard(copiedText);
|
||||||
|
|
||||||
|
// No trailing spaces
|
||||||
|
assert.equal(processedText.match(/\s+$/), null);
|
||||||
|
|
||||||
|
// No non-breaking space
|
||||||
|
assert.equal(processedText.indexOf(nonBreakingSpace), -1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -15,7 +15,7 @@ describe('CompositionHelper', function () {
|
|||||||
remove: function () {},
|
remove: function () {},
|
||||||
},
|
},
|
||||||
getBoundingClientRect: function () {
|
getBoundingClientRect: function () {
|
||||||
return { width: 0 }
|
return { width: 0 };
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
left: 0,
|
left: 0,
|
||||||
|
|||||||
+131
-19
@@ -8,6 +8,84 @@ describe('xterm.js', function() {
|
|||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
xterm = new Terminal();
|
xterm = new Terminal();
|
||||||
xterm.refresh = function(){};
|
xterm.refresh = function(){};
|
||||||
|
xterm.viewport = {
|
||||||
|
syncScrollArea: function(){}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getOption', function() {
|
||||||
|
it('should retrieve the option correctly', function() {
|
||||||
|
// In the `options` namespace.
|
||||||
|
xterm.options.cursorBlink = true;
|
||||||
|
assert.equal(xterm.getOption('cursorBlink'), true);
|
||||||
|
|
||||||
|
// On the Terminal instance
|
||||||
|
delete xterm.options.cursorBlink;
|
||||||
|
xterm.cursorBlink = false;
|
||||||
|
assert.equal(xterm.getOption('cursorBlink'), false);
|
||||||
|
});
|
||||||
|
it('should throw when retrieving a non-existant option', function() {
|
||||||
|
assert.throws(xterm.getOption.bind(xterm, 'fake', true));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setOption', function() {
|
||||||
|
it('should set the option correctly', function() {
|
||||||
|
xterm.setOption('cursorBlink', true);
|
||||||
|
assert.equal(xterm.cursorBlink, true);
|
||||||
|
assert.equal(xterm.options.cursorBlink, true);
|
||||||
|
xterm.setOption('cursorBlink', false);
|
||||||
|
assert.equal(xterm.cursorBlink, false);
|
||||||
|
assert.equal(xterm.options.cursorBlink, false);
|
||||||
|
});
|
||||||
|
it('should throw when setting a non-existant option', function() {
|
||||||
|
assert.throws(xterm.setOption.bind(xterm, 'fake', true));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('clear', function() {
|
||||||
|
it('should clear a buffer equal to rows', function() {
|
||||||
|
var promptLine = xterm.lines[xterm.ybase + xterm.y];
|
||||||
|
xterm.clear();
|
||||||
|
assert.equal(xterm.y, 0);
|
||||||
|
assert.equal(xterm.ybase, 0);
|
||||||
|
assert.equal(xterm.ydisp, 0);
|
||||||
|
assert.equal(xterm.lines.length, xterm.rows);
|
||||||
|
assert.deepEqual(xterm.lines[0], promptLine);
|
||||||
|
for (var i = 1; i < xterm.rows; i++) {
|
||||||
|
assert.deepEqual(xterm.lines[0], xterm.blankLine());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('should clear a buffer larger than rows', function() {
|
||||||
|
// Fill the buffer with dummy rows
|
||||||
|
for (var i = 0; i < xterm.rows * 2; i++) {
|
||||||
|
xterm.write('test\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
var promptLine = xterm.lines[xterm.ybase + xterm.y];
|
||||||
|
xterm.clear();
|
||||||
|
assert.equal(xterm.y, 0);
|
||||||
|
assert.equal(xterm.ybase, 0);
|
||||||
|
assert.equal(xterm.ydisp, 0);
|
||||||
|
assert.equal(xterm.lines.length, xterm.rows);
|
||||||
|
assert.deepEqual(xterm.lines[0], promptLine);
|
||||||
|
for (var i = 1; i < xterm.rows; i++) {
|
||||||
|
assert.deepEqual(xterm.lines[i], xterm.blankLine());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('should not break the prompt when cleared twice', function() {
|
||||||
|
var promptLine = xterm.lines[xterm.ybase + xterm.y];
|
||||||
|
xterm.clear();
|
||||||
|
xterm.clear();
|
||||||
|
assert.equal(xterm.y, 0);
|
||||||
|
assert.equal(xterm.ybase, 0);
|
||||||
|
assert.equal(xterm.ydisp, 0);
|
||||||
|
assert.equal(xterm.lines.length, xterm.rows);
|
||||||
|
assert.deepEqual(xterm.lines[0], promptLine);
|
||||||
|
for (var i = 1; i < xterm.rows; i++) {
|
||||||
|
assert.deepEqual(xterm.lines[i], xterm.blankLine());
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('evaluateKeyEscapeSequence', function() {
|
describe('evaluateKeyEscapeSequence', function() {
|
||||||
@@ -49,6 +127,15 @@ describe('xterm.js', function() {
|
|||||||
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 122 }).key, '\x1b[23~'); // CSI 2 3 ~
|
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 122 }).key, '\x1b[23~'); // CSI 2 3 ~
|
||||||
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 123 }).key, '\x1b[24~'); // CSI 2 4 ~
|
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 123 }).key, '\x1b[24~'); // CSI 2 4 ~
|
||||||
});
|
});
|
||||||
|
it('should return \\x1b[3;5~ for ctrl+delete', function() {
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 46 }).key, '\x1b[3;5~');
|
||||||
|
});
|
||||||
|
it('should return \\x1b[3;2~ for shift+delete', function() {
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 46 }).key, '\x1b[3;2~');
|
||||||
|
});
|
||||||
|
it('should return \\x1b[3;3~ for alt+delete', function() {
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 46 }).key, '\x1b[3;3~');
|
||||||
|
});
|
||||||
it('should return \\x1b[5D for ctrl+left', function() {
|
it('should return \\x1b[5D for ctrl+left', function() {
|
||||||
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 37 }).key, '\x1b[1;5D'); // CSI 5 D
|
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 37 }).key, '\x1b[1;5D'); // CSI 5 D
|
||||||
});
|
});
|
||||||
@@ -75,6 +162,45 @@ describe('xterm.js', function() {
|
|||||||
it('should return \\x1b[5B for alt+down', function() {
|
it('should return \\x1b[5B for alt+down', function() {
|
||||||
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B
|
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B
|
||||||
});
|
});
|
||||||
|
it('should return the correct escape sequence for modified F1-F12 keys', function() {
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 112 }).key, '\x1b[1;2P');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 113 }).key, '\x1b[1;2Q');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 114 }).key, '\x1b[1;2R');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 115 }).key, '\x1b[1;2S');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 116 }).key, '\x1b[15;2~');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 117 }).key, '\x1b[17;2~');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 118 }).key, '\x1b[18;2~');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 119 }).key, '\x1b[19;2~');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 120 }).key, '\x1b[20;2~');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 121 }).key, '\x1b[21;2~');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 122 }).key, '\x1b[23;2~');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 123 }).key, '\x1b[24;2~');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 112 }).key, '\x1b[1;3P');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 113 }).key, '\x1b[1;3Q');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 114 }).key, '\x1b[1;3R');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 115 }).key, '\x1b[1;3S');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 116 }).key, '\x1b[15;3~');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 117 }).key, '\x1b[17;3~');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 118 }).key, '\x1b[18;3~');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 119 }).key, '\x1b[19;3~');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 120 }).key, '\x1b[20;3~');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 121 }).key, '\x1b[21;3~');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 122 }).key, '\x1b[23;3~');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 123 }).key, '\x1b[24;3~');
|
||||||
|
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 112 }).key, '\x1b[1;5P');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 113 }).key, '\x1b[1;5Q');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 114 }).key, '\x1b[1;5R');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 115 }).key, '\x1b[1;5S');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 116 }).key, '\x1b[15;5~');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 117 }).key, '\x1b[17;5~');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 118 }).key, '\x1b[18;5~');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 119 }).key, '\x1b[19;5~');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 120 }).key, '\x1b[20;5~');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 121 }).key, '\x1b[21;5~');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 122 }).key, '\x1b[23;5~');
|
||||||
|
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 123 }).key, '\x1b[24;5~');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('attachCustomEventHandler', function () {
|
describe('attachCustomEventHandler', function () {
|
||||||
@@ -119,20 +245,6 @@ describe('xterm.js', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('evaluateCopiedTextProcessing', function () {
|
|
||||||
it('should strip trailing whitespaces and replace nbsps with spaces', function () {
|
|
||||||
var nonBreakingSpace = String.fromCharCode(160),
|
|
||||||
copiedText = 'echo' + nonBreakingSpace + 'hello' + nonBreakingSpace,
|
|
||||||
processedText = Terminal.prepareCopiedTextForClipboard(copiedText);
|
|
||||||
|
|
||||||
// No trailing spaces
|
|
||||||
assert.equal(processedText.match(/\s+$/), null);
|
|
||||||
|
|
||||||
// No non-breaking space
|
|
||||||
assert.equal(processedText.indexOf(nonBreakingSpace), -1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Third level shift', function() {
|
describe('Third level shift', function() {
|
||||||
var evKeyDown = {
|
var evKeyDown = {
|
||||||
preventDefault: function() {},
|
preventDefault: function() {},
|
||||||
@@ -271,7 +383,7 @@ describe('xterm.js', function() {
|
|||||||
|
|
||||||
describe('unicode - surrogates', function() {
|
describe('unicode - surrogates', function() {
|
||||||
it('2 characters per cell', function () {
|
it('2 characters per cell', function () {
|
||||||
var high = '\uD800';
|
var high = String.fromCharCode(0xD800);
|
||||||
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
||||||
xterm.write(high + String.fromCharCode(i));
|
xterm.write(high + String.fromCharCode(i));
|
||||||
var tchar = xterm.lines[0][0];
|
var tchar = xterm.lines[0][0];
|
||||||
@@ -283,7 +395,7 @@ describe('xterm.js', function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('2 characters at last cell', function() {
|
it('2 characters at last cell', function() {
|
||||||
var high = '\uD800';
|
var high = String.fromCharCode(0xD800);
|
||||||
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
||||||
xterm.x = xterm.cols - 1;
|
xterm.x = xterm.cols - 1;
|
||||||
xterm.write(high + String.fromCharCode(i));
|
xterm.write(high + String.fromCharCode(i));
|
||||||
@@ -294,7 +406,7 @@ describe('xterm.js', function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('2 characters per cell over line end with autowrap', function() {
|
it('2 characters per cell over line end with autowrap', function() {
|
||||||
var high = '\uD800';
|
var high = String.fromCharCode(0xD800);
|
||||||
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
||||||
xterm.x = xterm.cols - 1;
|
xterm.x = xterm.cols - 1;
|
||||||
xterm.wraparoundMode = true;
|
xterm.wraparoundMode = true;
|
||||||
@@ -307,7 +419,7 @@ describe('xterm.js', function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('2 characters per cell over line end without autowrap', function() {
|
it('2 characters per cell over line end without autowrap', function() {
|
||||||
var high = '\uD800';
|
var high = String.fromCharCode(0xD800);
|
||||||
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
||||||
xterm.x = xterm.cols - 1;
|
xterm.x = xterm.cols - 1;
|
||||||
xterm.wraparoundMode = false;
|
xterm.wraparoundMode = false;
|
||||||
@@ -319,7 +431,7 @@ describe('xterm.js', function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('splitted surrogates', function() {
|
it('splitted surrogates', function() {
|
||||||
var high = '\uD800';
|
var high = String.fromCharCode(0xD800);
|
||||||
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
for (var i=0xDC00; i<=0xDCFF; ++i) {
|
||||||
xterm.write(high);
|
xterm.write(high);
|
||||||
xterm.write(String.fromCharCode(i));
|
xterm.write(String.fromCharCode(i));
|
||||||
|
|||||||
+1
-17
@@ -6,6 +6,7 @@ describe('Viewport', function () {
|
|||||||
var viewportElement;
|
var viewportElement;
|
||||||
var charMeasureElement;
|
var charMeasureElement;
|
||||||
var viewport;
|
var viewport;
|
||||||
|
var scrollAreaElement;
|
||||||
|
|
||||||
var CHARACTER_HEIGHT = 10;
|
var CHARACTER_HEIGHT = 10;
|
||||||
|
|
||||||
@@ -45,23 +46,6 @@ describe('Viewport', function () {
|
|||||||
it('should define Viewport.prototype.onWheel', function () {
|
it('should define Viewport.prototype.onWheel', function () {
|
||||||
assert.isDefined(Terminal.Viewport.prototype.onWheel);
|
assert.isDefined(Terminal.Viewport.prototype.onWheel);
|
||||||
});
|
});
|
||||||
it('should define Viewport.prototype.setApplicationMode', function () {
|
|
||||||
assert.isDefined(Terminal.Viewport.prototype.setApplicationMode);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('setApplicationMode', function () {
|
|
||||||
it('should restrict the scroll area to the viewport', function () {
|
|
||||||
terminal.lines.push('');
|
|
||||||
terminal.lines.push('');
|
|
||||||
terminal.rows = 1;
|
|
||||||
viewport.syncScrollArea();
|
|
||||||
assert.equal(scrollAreaElement.style.height, 2 * CHARACTER_HEIGHT + 'px');
|
|
||||||
viewport.setApplicationMode(true);
|
|
||||||
assert.equal(scrollAreaElement.style.height, CHARACTER_HEIGHT + 'px');
|
|
||||||
viewport.setApplicationMode(false);
|
|
||||||
assert.equal(scrollAreaElement.style.height, 2 * CHARACTER_HEIGHT + 'px');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('refresh', function () {
|
describe('refresh', function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user