Merge remote-tracking branch 'upstream/master' into linkify_ip_addr

This commit is contained in:
Daniel Imms
2016-06-10 18:08:39 -07:00
13 changed files with 520 additions and 295 deletions
+13
View File
@@ -0,0 +1,13 @@
language: node_js
node_js:
- 4
env:
- CXX=g++-4.8
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
notifications:
email: false
+31 -1
View File
@@ -1,5 +1,7 @@
# xterm.js
![xterm.js build status](https://api.travis-ci.org/sourcelair/xterm.js.svg)
Xterm.js is a full xterm clone, written in JavaScript.
It is used at [SourceLair](https://www.sourcelair.com/home) to help people develop their applications in their browsers.
@@ -8,7 +10,35 @@ Xterm.js supplies a modular, event-based interface that lets developers build ad
![xterm.js screenshot](xtermjs.png)
### Contribution and License Agreement
## Demo
To launch the demo simply run:
```
npm install
npm start
```
Then open http://0.0.0.0:3000 in a web browser.
## Addons
Addons are JavaScript modules that attach functions to the `Terminal` prototype to extend its functionality. There are a handful available in the main repository in the `addons` directory, you can even write your own (though they may break when the internals of xterm.js change across versions).
To use an addon, just include the JavaScript file after xterm.js and before the `Terminal` object has been instantiated. The function should then be exposed on the `Terminal` object:
```html
<script src="node_modules/src/xterm.js"></script>
<script src="node_modules/addons/linkify/linkify.js"></script>
```
```js
var xterm = new Terminal();
// init code...
xterm.linkify();
```
## Contribution and License Agreement
If you contribute code to this project, you are implicitly allowing your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work.
Executable
+5
View File
@@ -0,0 +1,5 @@
#! /bin/bash
#
# Testing script for xterm.js
node_modules/.bin/mocha $@
+6 -3
View File
@@ -29,9 +29,12 @@ app.ws('/bash', function(ws, req) {
cwd: process.env.PWD,
env: process.env
});
term.on('data', function(data) {
ws.send(data);
try {
ws.send(data);
} catch (ex) {
// The WebSocket is not open, ignore
}
});
ws.on('message', function(msg) {
term.write(msg);
@@ -45,5 +48,5 @@ app.ws('/bash', function(ws, req) {
var port = process.env.PORT || 3000,
host = '0.0.0.0';
console.log('App listening to ' + host + ':' + port);
console.log('App listening to http://' + host + ':' + port);
app.listen(port, host);
+1 -1
View File
@@ -21,6 +21,6 @@ h1 {
padding: 2px;
}
#terminal-container .terminal .terminal-cursor {
#terminal-container .terminal:focus .terminal-cursor {
background-color: #fafafa;
}
+5 -2
View File
@@ -8,9 +8,12 @@
"devDependencies": {
"express": "4.13.4",
"express-ws": "2.0.0-rc.1",
"pty.js": "0.3.0"
"pty.js": "0.3.0",
"mocha": "2.5.3",
"chai": "3.5.0"
},
"scripts": {
"start": "bash bin/server"
"start": "bash bin/server",
"test": "bash bin/test"
}
}
+7 -9
View File
@@ -51,6 +51,12 @@
color: #000;
}
.terminal:not(:focus) .terminal-cursor {
outline: 1px solid #fff;
outline-offset: -1px;
background-color: transparent;
}
/*
* Determine default colors for xterm.js
*/
@@ -2123,13 +2129,5 @@
* in order to allow child elements to adjust.
*/
.terminal .xterm-rows > div {
line-height: 1.3;
}
/**
* All styled spans inside terminal lines should be inline-blocks,
* in orde to achieve better height adjustment.
*/
.terminal .xterm-rows span {
display: inline-block;
line-height: normal;
}
+259 -138
View File
@@ -202,10 +202,26 @@
this.on('data', options.handler);
}
/**
* The scroll position of the y cursor, ie. ybase + y = the y position within the entire
*/
this.ybase = 0;
/**
* The scroll position of the viewport
*/
this.ydisp = 0;
/**
* The cursor's x position after ybase
*/
this.x = 0;
/**
* The cursor's y position after ybase
*/
this.y = 0;
this.cursorState = 0;
this.cursorHidden = false;
this.convertEol;
@@ -261,6 +277,10 @@
this.prefix = '';
this.postfix = '';
/**
* An array of all lines in the entire buffer, including the prompt. The lines are array of
* characters which are 2-length arrays where [0] is an attribute and [1] is the character.
*/
this.lines = [];
var i = this.rows;
while (i--) {
@@ -401,7 +421,6 @@
this.showCursor();
this.element.focus();
this.emit('focus', {terminal: this});
};
Terminal.prototype.blur = function() {
@@ -417,7 +436,6 @@
this.send('\x1b[O');
}
Terminal.focus = null;
this.emit('blur', {terminal: this});
};
/**
@@ -501,6 +519,11 @@
term.leaseContentEditable();
}
}
if (!term.isMac && ev.keyCode == 45 && ev.shiftKey && !ev.ctrlKey) {
// Shift + Insert pastes on windows and many linuxes
term.leaseContentEditable();
}
});
/**
@@ -526,17 +549,41 @@
}, true);
};
/**
* 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;
};
/**
* Bind copy event. Stript trailing whitespaces from selection.
* Binds copy functionality to the given terminal.
* @static
*/
Terminal.bindCopy = function(term) {
on(term.element, 'copy', function(ev) {
var selectedText = window.getSelection().toString(),
copiedText = selectedText.split('\n').map(function (element) {
return element.replace(/\s+$/g, '');
}).join('\n');
ev.clipboardData.setData('text/plain', copiedText);
var copiedText = window.getSelection().toString(),
text = Terminal.prepareCopiedTextForClipboard(copiedText);
ev.clipboardData.setData('text/plain', text);
ev.preventDefault();
});
};
@@ -612,12 +659,27 @@
* Parse User-Agent
*/
if (this.context.navigator && this.context.navigator.userAgent) {
this.isMac = !!~this.context.navigator.userAgent.indexOf('Mac');
this.isIpad = !!~this.context.navigator.userAgent.indexOf('iPad');
this.isIphone = !!~this.context.navigator.userAgent.indexOf('iPhone');
this.isMSIE = !!~this.context.navigator.userAgent.indexOf('MSIE');
}
/*
* Find the users platform. We use this to interpret the meta key
* and ISO third level shifts.
* 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) {
this.isMac = contains(
this.context.navigator.platform,
['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K']
);
this.isIpad = this.context.navigator.platform === 'iPad';
this.isIphone = this.context.navigator.platform === 'iPhone';
this.isMSWindows = contains(
this.context.navigator.platform,
['Windows', 'Win16', 'Win32', 'WinCE']
);
}
/*
* Create main element container
*/
@@ -627,6 +689,12 @@
this.element.classList.add('xterm-theme-' + this.theme);
this.element.setAttribute('tabindex', 0);
this.element.spellcheck = 'false';
this.element.onfocus = function() {
self.emit('focus', {terminal: this});
};
this.element.onblur = function() {
self.emit('blur', {terminal: this});
};
/*
* Create the container that will hold the lines of the terminal and then
@@ -1036,11 +1104,11 @@
* Flags used to render terminal text properly
*/
Terminal.flags = {
BOLD: 0b00001,
UNDERLINE: 0b00010,
BLINK: 0b00100,
INVERSE: 0b01000,
INVISIBLE: 0b10000
BOLD: 1,
UNDERLINE: 2,
BLINK: 4,
INVERSE: 8,
INVISIBLE: 16
}
/*
@@ -1078,9 +1146,8 @@
line = this.lines[row];
out = '';
if (y === this.y
if (this.y === y - (this.ybase - this.ydisp)
&& this.cursorState
&& (this.ydisp === this.ybase)
&& !this.cursorHidden) {
x = this.x;
} else {
@@ -2277,219 +2344,226 @@
// Key Resources:
// https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent
Terminal.prototype.keyDown = function(ev) {
var self = this, key;
var self = this;
var result = this.evaluateKeyEscapeSequence(ev);
if (result.scrollDisp) {
this.scrollDisp(result.scrollDisp);
return this.cancel(ev);
}
if (isThirdLevelShift(this, ev)) {
return true;
}
if (result.cancel ) {
// The event is canceled at the end already, is this necessary?
this.cancel(ev, true);
}
if (!result.key) {
return true;
}
this.emit('keydown', ev);
this.emit('key', result.key, ev);
this.showCursor();
this.handler(result.key);
return this.cancel(ev, true);
};
/**
* Returns an object that determines how a KeyboardEvent should be handled. The key of the
* returned value is the new key code to pass to the PTY.
*
* Reference: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
*/
Terminal.prototype.evaluateKeyEscapeSequence = function(ev) {
var result = {
// Whether to cancel event propogation (NOTE: this may not be needed since the event is
// canceled at the end of keyDown
cancel: false,
// The new key even to emit
key: undefined,
// The number of characters to scroll, if this is defined it will cancel the event
scrollDisp: undefined
};
switch (ev.keyCode) {
// backspace
case 8:
if (ev.shiftKey) {
key = '\x08'; // ^H
result.key = '\x08'; // ^H
break;
}
key = '\x7f'; // ^?
result.key = '\x7f'; // ^?
break;
// tab
case 9:
if (ev.shiftKey) {
key = '\x1b[Z';
result.key = '\x1b[Z';
break;
}
key = '\t';
this.cancel(ev, true);
result.key = '\t';
result.cancel = true;
break;
// return/enter
case 13:
key = '\r';
this.cancel(ev, true);
result.key = '\r';
result.cancel = true;
break;
// escape
case 27:
key = '\x1b';
this.cancel(ev, true);
result.key = '\x1b';
result.cancel = true;
break;
// left-arrow
case 37:
if (ev.altKey) {
this.cancel(ev, true);
key = '\x1bb' // Jump a word back
break;
} else if (this.applicationCursor) {
key = '\x1bOD'; // SS3 as ^[O for 7-bit
result.key = '\x1bb' // Jump a word back
result.cancel = true;
break;
}
key = '\x1b[D';
if (ev.ctrlKey) {
result.key = '\x1b[5D'; // Jump a word back
break;
}
if (this.applicationCursor) {
result.key = '\x1bOD'; // SS3 as ^[O for 7-bit
break;
}
result.key = '\x1b[D';
break;
// right-arrow
case 39:
if (ev.altKey) {
this.cancel(ev, true);
key = '\x1bf' // Jump a word forward
break;
} else if (this.applicationCursor) {
key = '\x1bOC';
result.key = '\x1bf' // Jump a word forward
result.cancel = true;
break;
}
key = '\x1b[C';
if (ev.ctrlKey) {
result.key = '\x1b[5C'; // Jump a word forward
break;
}
if (this.applicationCursor) {
result.key = '\x1bOC';
break;
}
result.key = '\x1b[C';
break;
// up-arrow
case 38:
if (this.applicationCursor) {
key = '\x1bOA';
result.key = '\x1bOA';
break;
}
if (ev.ctrlKey) {
this.scrollDisp(-1);
return this.cancel(ev);
result.scrollDisp = -1;
} else {
key = '\x1b[A';
result.key = '\x1b[A';
}
break;
// down-arrow
case 40:
if (this.applicationCursor) {
key = '\x1bOB';
result.key = '\x1bOB';
break;
}
if (ev.ctrlKey) {
this.scrollDisp(1);
return this.cancel(ev);
result.scrollDisp = 1;
} else {
key = '\x1b[B';
result.key = '\x1b[B';
}
break;
// delete
case 46:
key = '\x1b[3~';
break;
// insert
case 45:
key = '\x1b[2~';
if (!ev.shiftKey && !ev.ctrlKey) {
// <Ctrl> or <Shift> + <Insert> are used to
// copy-paste on some systems.
result.key = '\x1b[2~';
}
break;
// delete
case 46: result.key = '\x1b[3~'; break;
// home
case 36:
if (this.applicationKeypad) {
key = '\x1bOH';
result.key = '\x1bOH';
break;
}
key = '\x1bOH';
result.key = '\x1bOH';
break;
// end
case 35:
if (this.applicationKeypad) {
key = '\x1bOF';
result.key = '\x1bOF';
break;
}
key = '\x1bOF';
result.key = '\x1bOF';
break;
// page up
case 33:
if (ev.shiftKey) {
this.scrollDisp(-(this.rows - 1));
return this.cancel(ev);
result.scrollDisp = -(this.rows - 1);
} else {
key = '\x1b[5~';
result.key = '\x1b[5~';
}
break;
// page down
case 34:
if (ev.shiftKey) {
this.scrollDisp(this.rows - 1);
return this.cancel(ev);
result.scrollDisp = this.rows - 1;
} else {
key = '\x1b[6~';
result.key = '\x1b[6~';
}
break;
// F1
case 112:
key = '\x1bOP';
break;
// F2
case 113:
key = '\x1bOQ';
break;
// F3
case 114:
key = '\x1bOR';
break;
// F4
case 115:
key = '\x1bOS';
break;
// F5
case 116:
key = '\x1b[15~';
break;
// F6
case 117:
key = '\x1b[17~';
break;
// F7
case 118:
key = '\x1b[18~';
break;
// F8
case 119:
key = '\x1b[19~';
break;
// F9
case 120:
key = '\x1b[20~';
break;
// F10
case 121:
key = '\x1b[21~';
break;
// F11
case 122:
key = '\x1b[23~';
break;
// F12
case 123:
key = '\x1b[24~';
break;
// F1-F12
case 112: result.key = '\x1bOP'; break;
case 113: result.key = '\x1bOQ'; break;
case 114: result.key = '\x1bOR'; break;
case 115: result.key = '\x1bOS'; break;
case 116: result.key = '\x1b[15~'; break;
case 117: result.key = '\x1b[17~'; break;
case 118: result.key = '\x1b[18~'; break;
case 119: result.key = '\x1b[19~'; break;
case 120: result.key = '\x1b[20~'; break;
case 121: result.key = '\x1b[21~'; break;
case 122: result.key = '\x1b[23~'; break;
case 123: result.key = '\x1b[24~'; break;
default:
// a-z and space
if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
if (ev.keyCode >= 65 && ev.keyCode <= 90) {
key = String.fromCharCode(ev.keyCode - 64);
result.key = String.fromCharCode(ev.keyCode - 64);
} else if (ev.keyCode === 32) {
// NUL
key = String.fromCharCode(0);
result.key = String.fromCharCode(0);
} else if (ev.keyCode >= 51 && ev.keyCode <= 55) {
// escape, file sep, group sep, record sep, unit sep
key = String.fromCharCode(ev.keyCode - 51 + 27);
result.key = String.fromCharCode(ev.keyCode - 51 + 27);
} else if (ev.keyCode === 56) {
// delete
key = String.fromCharCode(127);
result.key = String.fromCharCode(127);
} else if (ev.keyCode === 219) {
// ^[ - escape
key = String.fromCharCode(27);
result.key = String.fromCharCode(27);
} else if (ev.keyCode === 221) {
// ^] - group sep
key = String.fromCharCode(29);
result.key = String.fromCharCode(29);
}
} else if ((!this.isMac && ev.altKey) || (this.isMac && ev.metaKey)) {
} else if (!this.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) {
// On Mac this is a third level shift. Use <Esc> instead.
if (ev.keyCode >= 65 && ev.keyCode <= 90) {
key = '\x1b' + String.fromCharCode(ev.keyCode + 32);
result.key = '\x1b' + String.fromCharCode(ev.keyCode + 32);
} else if (ev.keyCode === 192) {
key = '\x1b`';
result.key = '\x1b`';
} else if (ev.keyCode >= 48 && ev.keyCode <= 57) {
key = '\x1b' + (ev.keyCode - 48);
result.key = '\x1b' + (ev.keyCode - 48);
}
}
break;
}
if (!key || (this.isMac && ev.metaKey)) {
return true;
}
this.emit('keydown', ev);
this.emit('key', key, ev);
this.showCursor();
this.handler(key);
return this.cancel(ev, true);
return result;
};
Terminal.prototype.setgLevel = function(g) {
@@ -2517,7 +2591,9 @@
return false;
}
if (!key || ev.ctrlKey || ev.altKey || ev.metaKey) {
if (!key || (
(ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this, ev)
)) {
return false;
}
@@ -2583,7 +2659,12 @@
, el
, i
, j
, ch;
, ch
, addToY;
if (x === this.cols && y === this.rows) {
return;
}
if (x < 1) x = 1;
if (y < 1) y = 1;
@@ -2598,7 +2679,7 @@
this.lines[i].push(ch);
}
}
} else if (j > x) {
} else { // (j > x)
i = this.lines.length;
while (i--) {
while (this.lines[i].length > x) {
@@ -2611,20 +2692,39 @@
// resize rows
j = this.rows;
addToY = 0;
if (j < y) {
el = this.element;
while (j++ < y) {
// y is rows, not this.y
if (this.lines.length < y + this.ybase) {
this.lines.push(this.blankLine());
if (this.ybase > 0 && this.lines.length <= this.ybase + this.y + addToY + 1) {
// There is room above the buffer and there are no empty elements below the line,
// scroll up
this.ybase--;
this.ydisp--;
addToY++
} else {
// Add a blank line if there is no buffer left at the top to scroll to, or if there
// are blank lines after the cursor
this.lines.push(this.blankLine());
}
}
if (this.children.length < y) {
this.insertRow();
}
}
} else if (j > y) {
} else { // (j > y)
while (j-- > y) {
if (this.lines.length > y + this.ybase) {
this.lines.shift();
if (this.lines.length > this.ybase + this.y + 1) {
// The line is a blank line below the cursor, remove it
this.lines.pop();
} else {
// The line is the cursor, scroll down
this.ybase++;
this.ydisp++;
}
}
if (this.children.length > y) {
el = this.children.shift();
@@ -2641,6 +2741,9 @@
if (this.y >= y) {
this.y = y - 1;
}
if (addToY) {
this.y += addToY;
}
if (this.x >= x) {
this.x = x - 1;
@@ -4317,6 +4420,15 @@
* Helpers
*/
function contains(el, arr) {
for (var i = 0; i < arr.length; i += 1) {
if (el === arr[i]) {
return true;
}
}
return false;
}
function on(el, type, handler, capture) {
if (!Array.isArray(el)) {
el = [el];
@@ -4373,6 +4485,15 @@
return -1;
}
function isThirdLevelShift(term, ev) {
var thirdLevelKey =
(term.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||
(term.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);
// Don't invoke for arrows, pageDown, home, backspace, etc.
return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47);
}
function isWide(ch) {
if (ch <= '\uff00') return false;
return (ch >= '\uff01' && ch <= '\uffbe')
-49
View File
@@ -1,49 +0,0 @@
/**
* term.js
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
*/
var element = {
createElement: function() { return element; },
appendChild: function() {},
removeChild: function() {},
addEventListener: function() {},
removeEventListener: function() {},
setAttribute: function() {},
style: {}
};
global.window = global;
window.navigator = { userAgent: '' };
window.document = element;
window.document.body = element;
element.ownerDocument = window.document;
window.document.defaultView = window;
var Terminal = require('../src/term');
Terminal.cursorBlink = false;
var data = require('./data').data;
var term = new Terminal({
cols: 250,
rows: 100
});
term.open(element);
var time = new Date;
var t = 10;
while (t--) {
var l = data.length
, i = 0;
for (; i < l; i++) {
term.write(data[i]);
}
}
console.log('Completed: %d.', new Date - time);
console.log('Average (?): 13.5k (for ~2.7k writes).');
-10
View File
@@ -1,10 +0,0 @@
167a168,170
> var stream = fs.createWriteStream(__dirname + '/../test/data.js');
> stream.write('this.data = [\n');
>
169a173
> stream.write(' ' + JSON.stringify(data) + ',\n');
182a187,189
>
> stream.write('];\n');
> stream.end();
-49
View File
@@ -1,49 +0,0 @@
<!doctype html>
<title>term.js test</title>
<style>
h1 {
margin-bottom: 20px;
font: 20px/1.5 sans-serif;
}
</style>
<h1>term.js test</h1>
<script src="term.js"></script>
<script src="data.js"></script>
<script>
;(function() {
var time = new Date
, l = data.length * 0.9 | 0
, i = 0;
Terminal.cursorBlink = false;
var term = new Terminal({
cols: 80,
rows: 30,
useStyle: true
});
window.onload = function() {
// Add terminal.
term.open(document.body);
// Run benchmark.
(function write() {
if (i >= l) return next();
term.write(data[i]);
i++;
setTimeout(write, 1);
})();
// Results.
function next() {
term.write('\x1b[2J');
term.reset();
term.refresh(0, term.rows - 1);
term.writeln('Completed in ' + (new Date - time) + '.');
term.writeln('Writes: ' + l + '.');
term.writeln('Average (?): 28.5k (for ~2.5k writes).');
}
};
}).call(this);
</script>
-33
View File
@@ -1,33 +0,0 @@
/**
* term.js
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
*/
var http = require('http')
, path = require('path')
, fs = require('fs');
var express = require('express')
, term = require('../');
var app = express()
, server = http.createServer(app);
app.use(function(req, res, next) {
var setHeader = res.setHeader;
res.setHeader = function(name) {
switch (name) {
case 'Cache-Control':
case 'Last-Modified':
case 'ETag':
return;
}
return setHeader.apply(res, arguments);
};
next();
});
app.use(express.static(__dirname));
app.use(term.middleware());
server.listen(8080);
+193
View File
@@ -0,0 +1,193 @@
var assert = require('chai').assert;
var Terminal = require('../src/xterm');
describe('xterm.js', function() {
var xterm;
beforeEach(function () {
xterm = new Terminal();
});
describe('evaluateKeyEscapeSequence', function() {
it('should return the correct escape sequence for unmodified keys', function() {
// Backspace
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 8 }).key, '\x7f'); // ^?
// Tab
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 9 }).key, '\t');
// Return/enter
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 13 }).key, '\r'); // CR
// Escape
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 27 }).key, '\x1b');
// Page up, page down
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 33 }).key, '\x1b[5~'); // CSI 5 ~
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 34 }).key, '\x1b[6~'); // CSI 6 ~
// End, Home
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 35 }).key, '\x1bOF'); // SS3 F
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 36 }).key, '\x1bOH'); // SS3 H
// Left, up, right, down arrows
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 37 }).key, '\x1b[D'); // CSI D
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 38 }).key, '\x1b[A'); // CSI A
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 39 }).key, '\x1b[C'); // CSI C
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 40 }).key, '\x1b[B'); // CSI B
// Insert
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 45 }).key, '\x1b[2~'); // CSI 2 ~
// Delete
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 46 }).key, '\x1b[3~'); // CSI 3 ~
// F1-F12
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 112 }).key, '\x1bOP'); // SS3 P
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 113 }).key, '\x1bOQ'); // SS3 Q
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 114 }).key, '\x1bOR'); // SS3 R
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 115 }).key, '\x1bOS'); // SS3 S
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 116 }).key, '\x1b[15~'); // CSI 1 5 ~
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 117 }).key, '\x1b[17~'); // CSI 1 7 ~
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 118 }).key, '\x1b[18~'); // CSI 1 8 ~
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 119 }).key, '\x1b[19~'); // CSI 1 9 ~
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 120 }).key, '\x1b[20~'); // CSI 2 0 ~
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 121 }).key, '\x1b[21~'); // CSI 2 1 ~
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 122 }).key, '\x1b[23~'); // CSI 2 3 ~
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 123 }).key, '\x1b[24~'); // CSI 2 4 ~
});
it('should return \\x1b[5D for ctrl+left', function() {
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 37 }).key, '\x1b[5D'); // CSI 5 D
});
it('should return \\x1b[5C for ctrl+right', function() {
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 39 }).key, '\x1b[5C'); // CSI 5 C
});
});
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() {
var ev = {
preventDefault: function() {},
stopPropagation: function() {}
};
beforeEach(function() {
xterm.handler = function() {};
xterm.showCursor = function() {};
xterm.clearSelection = function() {};
})
describe('On Mac OS', function() {
beforeEach(function() {
xterm.isMac = true;
});
it('should not interfere with the alt key on keyDown', function() {
assert.equal(
xterm.keyDown(Object.assign({}, ev, { altKey: true, keyCode: 81 })),
true
);
assert.equal(
xterm.keyDown(Object.assign({}, ev, { altKey: true, keyCode: 192 })),
true
);
});
it('should interefere with the alt + arrow keys', function() {
assert.equal(
xterm.keyDown(Object.assign({}, ev, { altKey: true, keyCode: 37 })),
false
);
assert.equal(
xterm.keyDown(Object.assign({}, ev, { altKey: true, keyCode: 39 })),
false
);
});
it('should emit key with alt + key on keyPress', function(done) {
var keys = ['@', '@', '\\', '\\', '|', '|'];
xterm.on('keypress', function(key) {
if (key) {
var index = keys.indexOf(key);
assert(index !== -1, "Emitted wrong key: " + key);
keys.splice(index, 1);
}
if (keys.length === 0) done();
});
xterm.keyPress(Object.assign({}, ev, { altKey: true, keyCode: 64 })); // @
// Firefox
xterm.keyPress(Object.assign({}, ev, { altKey: true, charCode: 64, keyCode: 0 }));
xterm.keyPress(Object.assign({}, ev, { altKey: true, keyCode: 92 })); // \
xterm.keyPress(Object.assign({}, ev, { altKey: true, charCode: 92, keyCode: 0 }));
xterm.keyPress(Object.assign({}, ev, { altKey: true, keyCode: 124 })); // |
xterm.keyPress(Object.assign({}, ev, { altKey: true, charCode: 124, keyCode: 0 }));
});
});
describe('On MS Windows', function() {
beforeEach(function() {
xterm.isMSWindows = true;
});
it('should not interfere with the alt + ctrl key on keyDown', function() {
assert.equal(
xterm.keyDown(Object.assign({}, ev, { altKey: true, ctrlKey: true, keyCode: 81 })),
true
);
assert.equal(
xterm.keyDown(Object.assign({}, ev, { altKey: true, ctrlKey: true, keyCode: 192 })),
true
);
});
it('should interefere with the alt + ctrl + arrow keys', function() {
assert.equal(
xterm.keyDown(Object.assign({}, ev, { altKey: true, ctrlKey: true, keyCode: 37 })),
false
);
assert.equal(
xterm.keyDown(Object.assign({}, ev, { altKey: true, ctrlKey: true, keyCode: 39 })),
false
);
});
it('should emit key with alt + ctrl + key on keyPress', function(done) {
var keys = ['@', '@', '\\', '\\', '|', '|'];
xterm.on('keypress', function(key) {
if (key) {
var index = keys.indexOf(key);
assert(index !== -1, "Emitted wrong key: " + key);
keys.splice(index, 1);
}
if (keys.length === 0) done();
});
xterm.keyPress(
Object.assign({}, ev, { altKey: true, ctrlKey: true, keyCode: 64 })
); // @
xterm.keyPress(
Object.assign({}, ev, { altKey: true, ctrlKey: true, charCode: 64, keyCode: 0 })
);
xterm.keyPress(
Object.assign({}, ev, { altKey: true, ctrlKey: true, keyCode: 92 })
); // \
xterm.keyPress(
Object.assign({}, ev, { altKey: true, ctrlKey: true, charCode: 92, keyCode: 0 })
);
xterm.keyPress(
Object.assign({}, ev, { altKey: true, ctrlKey: true, keyCode: 124 })
); // |
xterm.keyPress(
Object.assign({}, ev, { altKey: true, ctrlKey: true, charCode: 124, keyCode: 0 })
);
});
});
});
});