Compare commits

...
62 Commits
Author SHA1 Message Date
Paris Kasidiaris 68906b5def Bump version to 2.0.0
Signed-off-by: Paris Kasidiaris <paris@sourcelair.com>
2016-10-03 12:37:00 +00:00
Paris Kasidiaris dddaa16573 Merge pull request #298 from sourcelair/fix-release-scripts
Fix release scripts
2016-10-03 15:33:20 +03:00
Paris Kasidiaris d581559dd4 Fix release scripts 2016-10-03 15:32:14 +03:00
Paris Kasidiaris 3e77db8149 Merge pull request #269 from Tyriar/set_package_main
Set package.json main to build/xterm.js
2016-10-03 14:11:34 +03:00
Paris Kasidiaris 9115846017 Merge pull request #293 from sourcelair/issue-#202-fix-paste
Allow right-click paste (again)
2016-10-01 10:39:46 +03:00
Paris Kasidiaris ab7d63a8c8 Merge pull request #295 from sourcelair/update-copyright-text
Update copyright text in all files
2016-09-30 14:06:34 +03:00
Paris Kasidiaris a7acee726d Update copyright text in all files
sourceLair Limited → SourceLair Private Company
2016-09-30 13:52:55 +03:00
Paris Kasidiaris a6f047331f Add forgotten line to keep copied text 😅 2016-09-29 10:59:24 +03:00
Paris Kasidiaris a224acb50b Document new Clipboard module 2016-09-29 07:51:31 +00:00
Paris Kasidiaris 42a1e4ef9b Move clipboard into its own module 2016-09-29 07:51:07 +00:00
Paris Kasidiaris dff867b8a7 Fix right-click paste
Fix #202
2016-09-29 10:50:21 +03:00
Daniel Imms 8bf8147028 Merge pull request #289 from Tyriar/151_allow_wheel_in_application_mode
Allow wheel events in application mode
2016-09-25 04:44:53 -07:00
Daniel Imms ccf6d8f87d Merge pull request #287 from Tyriar/286_fix_scroll_in_app_mode
Sync scroll bar instead of disabling on enter app mode
2016-09-25 04:44:44 -07:00
Daniel Imms ac817ccc66 Merge pull request #288 from Tyriar/278_sync_scrollbar_after_reset
Sync scroll bar after reset
2016-09-25 02:44:56 -07:00
Daniel Imms 37c07eaaf9 Allow wheel events in application mode
This change allows wheel events in application mode which fixes mouse wheel
scrolling in oh-my-zsh and powershell for Linux (among others). Along with #287
a functional scroll bar will also be usable in those shells.

Fixes #151
2016-09-25 02:42:46 -07:00
Daniel Imms 71e20801f0 Fix tests 2016-09-25 02:07:53 -07:00
Daniel Imms bae1bd6a42 Fix tests 2016-09-25 02:02:05 -07:00
Daniel Imms c8b194931f Sync scroll bar after reset
Fixes #278
2016-09-25 01:56:03 -07:00
Daniel Imms c7a4881552 Sync scroll bar instead of disabling on enter app mode
Fixes #286
2016-09-25 01:50:49 -07:00
Paris Kasidiaris 26fd963fbd Merge pull request #283 from iMoses/master
Workaround a known bug in Babel JS
2016-09-22 14:46:02 +01:00
imoses 122a34b40e Merge branch 'master' of https://github.com/sourcelair/xterm.js 2016-09-22 16:28:20 +03:00
imoses 33abd69e85 Tests were failing locally due to a babel js bug - https://github.com/babel/babel/issues/3826
Fixed the tests to work around the problem and added /.idea/ to .gitignore for the benefit of contributors who uses intellij
2016-09-22 16:26:03 +03:00
Paris Kasidiaris 76a686beb2 Merge pull request #282 from sourcelair/get-option
Add `getOption` API
2016-09-21 17:14:39 +01:00
Paris Kasidiaris 53e8ac9b0c Stricter check for option existence 2016-09-21 19:09:55 +03:00
Paris Kasidiaris 4b459fe0fd Implement term.getOption 2016-09-21 13:56:03 +03:00
Daniel Imms be4c8341ba Merge pull request #275 from Tyriar/move_comments_inside_case
Move key handler comments inside case blocks
2016-09-18 13:49:52 -07:00
Daniel Imms a33bc027f1 Merge pull request #274 from Tyriar/205_cancel_shift_pg_up_down_event
Cancel keydown event on shift+page up/down
2016-09-18 13:49:38 -07:00
Daniel Imms 3c9d8b0d84 Merge pull request #276 from Tyriar/173_clear_api
Expose API to clear the terminal
2016-09-17 04:11:20 -07:00
Daniel Imms 26fc53990b Fix clear function when buffer is <= rows 2016-09-17 04:07:36 -07:00
Daniel Imms 852dac4d62 Don't clear prompt on clear if it already is 2016-09-17 03:59:42 -07:00
Daniel Imms 76719413f9 Expose API to clear the terminal
Fixes #173
2016-09-17 02:39:22 -07:00
Daniel Imms fca673d653 Move key handler comments inside case blocks
Improves readability
2016-09-17 01:11:43 -07:00
Daniel Imms 446c3958df Cancel keydown even on shift+page up/down
Fixes #205
2016-09-17 01:09:08 -07:00
Daniel Imms 59e72b8b07 Merge pull request #272 from Tyriar/271_setOption
Add setOption API
2016-09-16 02:17:28 -07:00
Daniel Imms 15e56bd809 Add @param to setOption 2016-09-16 02:03:31 -07:00
Daniel Imms ab5cc0ad05 Add setOption API
Fixes #271
2016-09-16 01:46:02 -07:00
Daniel Imms b89bce17ad Merge pull request #268 from Tyriar/267_fix_mouseup_focus
Fix focus on mouseup when mousedown was in another element
2016-09-16 01:23:57 -07:00
Daniel Imms 7fcbddac04 Set package.json main to build/xterm.js
build/xterm.js should be used when require is used when installed via npm.
2016-09-15 16:25:22 -07:00
Daniel Imms 4e1bbee67e Fix focus on mouseup when mousedown was in another element
Fixes #267
2016-09-15 13:50:39 -07:00
Paris Kasidiaris ecfa44b0e8 Merge pull request #263 from sourcelair/fix-indentation
Fix indentation of `src/xterm.js`
2016-09-05 13:18:49 +03:00
Paris Kasidiaris db76868c26 Fix indentation of src/xterm.js 2016-09-04 16:47:21 +03:00
Paris Kasidiaris b4c7989566 Merge pull request #252 from ayapi/modifier-keys
add modifier+delete, modifier+F1-F12 keys
2016-09-04 16:23:29 +03:00
Paris Kasidiaris 7b38b9ed57 Merge pull request #261 from yurovant/patch-1
Update README.md
2016-09-02 11:54:25 +03:00
Anton Yurovskykh c2fccc0350 Update README.md
Fix typo: is => if
2016-09-02 09:28:57 +03:00
Daniel Imms b56ad5e8fe Merge pull request #243 from Tyriar/242_enable_scroll_on_soft_reset
Disable application mode in viewport on soft reset
2016-08-31 10:40:54 -07:00
Daniel Imms abfcf27962 Merge pull request #256 from Tyriar/254_compositionhelper_module
Pull CompositionHelper into a module
2016-08-31 10:34:14 -07:00
Daniel Imms 6a04614549 Merge remote-tracking branch 'upstream/master' into 254_compositionhelper_module 2016-08-29 16:50:35 -07:00
Daniel Imms 89f981836f Merge pull request #255 from Tyriar/253_viewport_module
Pull Viewport into a module
2016-08-29 16:49:03 -07:00
Paris Kasidiaris 33d4d400f0 Merge pull request #257 from Tyriar/improve_copyright_line
Improve copyright line
2016-08-30 02:38:41 +03:00
Daniel Imms 749ed6372a Improve copyright line 2016-08-29 16:21:00 -07:00
Daniel Imms 81fccee906 Add copyright line 2016-08-29 16:20:11 -07:00
Daniel Imms 4caac78241 Improve copyright line 2016-08-29 16:19:27 -07:00
Daniel Imms 7ff03bb475 Pull Viewport into a module
Fixes #253
2016-08-29 16:17:59 -07:00
Daniel Imms 28c3a2026c Pull CompositionHelper into a module
Fixes #254
2016-08-29 16:15:28 -07:00
Paris Kasidiaris a9c85fe825 Merge pull request #251 from sourcelair/issue-#158-es2015
Introduce Babel as the build system, convert src/xterm.js to ES2015 and export EventEmitter
2016-08-30 01:59:17 +03:00
Paris Kasidiaris c46e799dd0 Update README.md to include xterm.js from new directory 2016-08-29 22:57:19 +03:00
Paris Kasidiaris ed1a31d1cf Make src/xterm.js ES2015 and export EventEmitter into its own file
Fix #158
2016-08-29 19:51:40 +00:00
ayapi 7f7156bafa add modifier+F1-F12 keys 2016-08-27 01:13:01 +09:00
ayapi 62ce64f97d add modifier+delete keys 2016-08-27 01:03:35 +09:00
Paris Kasidiaris 04a226f813 Merge pull request #249 from sourcelair/Tyriar-patch-3
Improve grammar
2016-08-26 03:40:16 +03:00
Daniel Imms 68b012ca06 Improve grammar 2016-08-25 14:46:20 -07:00
Daniel Imms fc22fc39e2 Disable application mode in viewport on soft reset
Fixes #242
2016-08-23 09:11:02 -07:00
28 changed files with 5610 additions and 5273 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"presets": ["es2015"]
}
+1
View File
@@ -10,3 +10,4 @@ Makefile.gyp
example/*.log example/*.log
docs/ docs/
npm-debug.log npm-debug.log
/.idea/
+2
View File
@@ -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 -1
View File
@@ -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
+3 -3
View File
@@ -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>
``` ```
+1 -1
View File
@@ -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
View File
@@ -26,7 +26,7 @@
/* /*
* Plain browser environment * Plain browser environment
*/ */
fit(this.Xterm); fit(window.Terminal);
} }
})(function (Xterm) { })(function (Xterm) {
/** /**
+1 -1
View File
@@ -25,7 +25,7 @@
/* /*
* Plain browser environment * Plain browser environment
*/ */
fullscreen(this.Xterm); fullscreen(window.Terminal);
} }
})(function (Xterm) { })(function (Xterm) {
var exports = {}; var exports = {};
+1 -1
View File
@@ -13,7 +13,7 @@
/* /*
* Plain browser environment * Plain browser environment
*/ */
linkify(this.Xterm); linkify(window.Terminal);
} }
})(function (Xterm) { })(function (Xterm) {
'use strict'; 'use strict';
Executable
+38
View File
@@ -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.');
+9 -3
View File
@@ -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,12 +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 git tag $NEW_VERSION
+2
View File
@@ -3,6 +3,8 @@
# Usage: ./bin/release x.y.z # Usage: ./bin/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
if [ -z "$1" ]; if [ -z "$1" ];
then then
echo "No version supplied. Please a version argument\n" echo "No version supplied. Please a version argument\n"
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"name": "xterm.js", "name": "xterm.js",
"version": "1.1.3", "version": "2.0.0",
"ignore": ["demo", "test", ".gitignore"] "ignore": ["demo", "test", ".gitignore"]
} }
+1 -1
View File
@@ -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
View File
@@ -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>
+1
View File
@@ -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
View File
@@ -1,28 +1,36 @@
{ {
"name": "xterm", "name": "xterm",
"version": "1.1.3", "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"
} }
} }
+201
View File
@@ -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 };
+65
View File
@@ -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
View File
@@ -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 };
+126
View File
@@ -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 &nbsp;
// 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
View File
@@ -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
View File
@@ -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;
});
+18
View File
@@ -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);
});
});
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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 () {