Merge remote-tracking branch 'upstream/master' into 361_circular_list_scrollback

This commit is contained in:
Daniel Imms
2016-12-12 15:05:19 -08:00
39 changed files with 5346 additions and 1981 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.js]
[*.{j,t}s]
max_line_length = 100
[*.css]
+1 -1
View File
@@ -11,4 +11,4 @@ docs/
npm-debug.log
/.idea/
.env
dist/*
build/
+2
View File
@@ -24,6 +24,8 @@ Mikko Karvonen <mikko.karvonen@arm.com>
Paris Kasidiaris <pariskasidiaris@gmail.com>
Paris Kasidiaris <paris@sourcelair.com>
runarberg <runar@greenqloud.com>
Shuanglei Tao <tsl0922@gmail.com>
Steven Silvester <steven.silvester@ieee.org>
Thanasis Daglis <thanasis@sourcelair.com>
Tine Jozelj <tine.jozelj@outlook.com>
YuviPanda <yuvipanda@gmail.com>
+3 -3
View File
@@ -1,6 +1,6 @@
# xterm.js
![xterm.js build status](https://api.travis-ci.org/sourcelair/xterm.js.svg) [![Gitter](https://badges.gitter.im/sourcelair/xterm.js.svg)](https://gitter.im/sourcelair/xterm.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![xterm.js build status](https://api.travis-ci.org/sourcelair/xterm.js.svg)](https://travis-ci.org/sourcelair/xterm.js) [![Gitter](https://badges.gitter.im/sourcelair/xterm.js.svg)](https://gitter.im/sourcelair/xterm.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
Xterm.js is a terminal front-end component written in JavaScript that works in the browser.
@@ -54,13 +54,13 @@ Then open http://0.0.0.0:3000 in a web browser (use http://127.0.0.1:3000 if run
## 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).
Addons are JavaScript modules that attach functions to the `Terminal` prototype to extend its functionality. There are a handful available in the main repository in the `dist/addons` directory, you can even write your own (though they may break when the internals of xterm.js change across versions).
To use an addon, just include the JavaScript file after xterm.js and before the `Terminal` object has been instantiated. The function should then be exposed on the `Terminal` object:
```html
<script src="node_modules/dist/xterm.js"></script>
<script src="node_modules/addons/fit/fit.js"></script>
<script src="node_modules/dist/addons/fit/fit.js"></script>
```
```js
+23 -11
View File
@@ -1,23 +1,35 @@
#! /usr/bin/env bash
set -e
# $BUILD_DIR should default to "build"
BUILD_DIR=${BUILD_DIR:=build}
# Create the build directory
mkdir -p $BUILD_DIR
# Clean out/* to prevent confusion if files were deleted in src/
rm -rf out/*
# Build all TypeScript files (including tests) to out/
tsc
# Concat all xterm.js files into a single file and output as a UMD to dist/xterm.js
browserify ./out/xterm.js --standalone Terminal --debug --outfile ./dist/xterm.js
cat ./dist/xterm.js | exorcist ./dist/xterm.js.map -b ./dist > ./dist/xterm.temp.js
rm ./dist/xterm.js
mv ./dist/xterm.temp.js ./dist/xterm.js
# Concat all xterm.js files into a single file and output as a UMD to $BUILD_DIR/xterm.js
browserify ./out/xterm.js --standalone Terminal --debug --outfile ./$BUILD_DIR/xterm.js
cat ./$BUILD_DIR/xterm.js | exorcist ./$BUILD_DIR/xterm.js.map -b ./$BUILD_DIR > ./$BUILD_DIR/xterm.temp.js
rm ./$BUILD_DIR/xterm.js
mv ./$BUILD_DIR/xterm.temp.js ./$BUILD_DIR/xterm.js
# Resolve the chain of sourcemaps so that ./dist/xterm.js.map points at ./src
sorcery -i dist/xterm.js
# Resolve the chain of sourcemaps so that ./$BUILD_DIR/xterm.js.map points at ./src
sorcery -i $BUILD_DIR/xterm.js
# Copy all CSS files from src/ to dist/
# Copy all CSS files from src/ to $BUILD_DIR/
cd src
find . -name '*.css' | cpio -pdm ../dist
find . -name '*.css' | cpio -pdm ../$BUILD_DIR
cd ..
# Copy addons from out/ to dist/
# Copy addons from out/ to $BUILD_DIR/
cd out/addons
find . -name '*.js' | cpio -pdm ../../dist/addons
find . -name '*.js' | cpio -pdm ../../$BUILD_DIR/addons
cd ../..
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm.js",
"version": "2.1.0",
"version": "2.2.1",
"ignore": ["demo", "test", ".gitignore"],
"main": [
"dist/xterm.js",
+1 -1
View File
@@ -7,7 +7,7 @@ var pty = require('pty.js');
var terminals = {},
logs = {};
app.use('/dist', express.static(__dirname + '/../dist'));
app.use('/build', express.static(__dirname + '/../build'));
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
+6 -6
View File
@@ -2,14 +2,14 @@
<html>
<head>
<title>xterm.js demo</title>
<link rel="stylesheet" href="/dist/xterm.css" />
<link rel="stylesheet" href="/dist/addons/fullscreen/fullscreen.css" />
<link rel="stylesheet" href="/build/xterm.css" />
<link rel="stylesheet" href="/build/addons/fullscreen/fullscreen.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="/dist/xterm.js" ></script>
<script src="/dist/addons/attach/attach.js" ></script>
<script src="/dist/addons/fit/fit.js" ></script>
<script src="/dist/addons/fullscreen/fullscreen.js" ></script>
<script src="/build/xterm.js" ></script>
<script src="/build/addons/attach/attach.js" ></script>
<script src="/build/addons/fit/fit.js" ></script>
<script src="/build/addons/fullscreen/fullscreen.js" ></script>
</head>
<body>
<h1>xterm.js: xterm, in the browser</h1>
+114
View File
@@ -0,0 +1,114 @@
/**
* Implements the attach method, that attaches the terminal to a WebSocket stream.
* @module xterm/addons/attach/attach
* @license MIT
*/
(function (attach) {
if (typeof exports === 'object' && typeof module === 'object') {
/*
* CommonJS environment
*/
module.exports = attach(require('../../xterm'));
}
else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../xterm'], attach);
}
else {
/*
* Plain browser environment
*/
attach(window.Terminal);
}
})(function (Xterm) {
'use strict';
var exports = {};
/**
* Attaches the given terminal to the given socket.
*
* @param {Xterm} term - The terminal to be attached to the given socket.
* @param {WebSocket} socket - The socket to attach the current terminal.
* @param {boolean} bidirectional - Whether the terminal should send data
* to the socket as well.
* @param {boolean} buffered - Whether the rendering of incoming data
* should happen instantly or at a maximum
* frequency of 1 rendering per 10ms.
*/
exports.attach = function (term, socket, bidirectional, buffered) {
bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional;
term.socket = socket;
term._flushBuffer = function () {
term.write(term._attachSocketBuffer);
term._attachSocketBuffer = null;
clearTimeout(term._attachSocketBufferTimer);
term._attachSocketBufferTimer = null;
};
term._pushToBuffer = function (data) {
if (term._attachSocketBuffer) {
term._attachSocketBuffer += data;
}
else {
term._attachSocketBuffer = data;
setTimeout(term._flushBuffer, 10);
}
};
term._getMessage = function (ev) {
if (buffered) {
term._pushToBuffer(ev.data);
}
else {
term.write(ev.data);
}
};
term._sendData = function (data) {
socket.send(data);
};
socket.addEventListener('message', term._getMessage);
if (bidirectional) {
term.on('data', term._sendData);
}
socket.addEventListener('close', term.detach.bind(term, socket));
socket.addEventListener('error', term.detach.bind(term, socket));
};
/**
* Detaches the given terminal from the given socket
*
* @param {Xterm} term - The terminal to be detached from the given socket.
* @param {WebSocket} socket - The socket from which to detach the current
* terminal.
*/
exports.detach = function (term, socket) {
term.off('data', term._sendData);
socket = (typeof socket == 'undefined') ? term.socket : socket;
if (socket) {
socket.removeEventListener('message', term._getMessage);
}
delete term.socket;
};
/**
* Attaches the current terminal to the given socket
*
* @param {WebSocket} socket - The socket to attach the current terminal.
* @param {boolean} bidirectional - Whether the terminal should send data
* to the socket as well.
* @param {boolean} buffered - Whether the rendering of incoming data
* should happen instantly or at a maximum
* frequency of 1 rendering per 10ms.
*/
Xterm.prototype.attach = function (socket, bidirectional, buffered) {
return exports.attach(this, socket, bidirectional, buffered);
};
/**
* Detaches the current terminal from the given socket.
*
* @param {WebSocket} socket - The socket from which to detach the current
* terminal.
*/
Xterm.prototype.detach = function (socket) {
return exports.detach(this, socket);
};
return exports;
});
//# sourceMappingURL=attach.js.map
+59
View File
@@ -0,0 +1,59 @@
/**
* Fit terminal columns and rows to the dimensions of its DOM element.
*
* ## Approach
* - Rows: Truncate the division of the terminal parent element height by the terminal row height.
*
* - Columns: Truncate the division of the terminal parent element width by the terminal character
* width (apply display: inline at the terminal row and truncate its width with the current
* number of columns).
* @module xterm/addons/fit/fit
* @license MIT
*/
(function (fit) {
if (typeof exports === 'object' && typeof module === 'object') {
/*
* CommonJS environment
*/
module.exports = fit(require('../../xterm'));
}
else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../xterm'], fit);
}
else {
/*
* Plain browser environment
*/
fit(window.Terminal);
}
})(function (Xterm) {
var exports = {};
exports.proposeGeometry = function (term) {
var parentElementStyle = window.getComputedStyle(term.element.parentElement), parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')), parentElementWidth = parseInt(parentElementStyle.getPropertyValue('width')), elementStyle = window.getComputedStyle(term.element), elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom')), elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left')), availableHeight = parentElementHeight - elementPaddingVer, availableWidth = parentElementWidth - elementPaddingHor, container = term.rowContainer, subjectRow = term.rowContainer.firstElementChild, contentBuffer = subjectRow.innerHTML, characterHeight, rows, characterWidth, cols, geometry;
subjectRow.style.display = 'inline';
subjectRow.innerHTML = 'W'; // Common character for measuring width, although on monospace
characterWidth = subjectRow.getBoundingClientRect().width;
subjectRow.style.display = ''; // Revert style before calculating height, since they differ.
characterHeight = parseInt(subjectRow.offsetHeight);
subjectRow.innerHTML = contentBuffer;
rows = parseInt(availableHeight / characterHeight);
cols = parseInt(availableWidth / characterWidth) - 1;
geometry = { cols: cols, rows: rows };
return geometry;
};
exports.fit = function (term) {
var geometry = exports.proposeGeometry(term);
term.resize(geometry.cols, geometry.rows);
};
Xterm.prototype.proposeGeometry = function () {
return exports.proposeGeometry(this);
};
Xterm.prototype.fit = function () {
return exports.fit(this);
};
return exports;
});
//# sourceMappingURL=fit.js.map
+10
View File
@@ -0,0 +1,10 @@
.xterm.fullscreen {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: auto;
height: auto;
z-index: 255;
}
+50
View File
@@ -0,0 +1,50 @@
/**
* Fullscreen addon for xterm.js
* @module xterm/addons/fullscreen/fullscreen
* @license MIT
*/
(function (fullscreen) {
if (typeof exports === 'object' && typeof module === 'object') {
/*
* CommonJS environment
*/
module.exports = fullscreen(require('../../xterm'));
}
else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../xterm'], fullscreen);
}
else {
/*
* Plain browser environment
*/
fullscreen(window.Terminal);
}
})(function (Xterm) {
var exports = {};
/**
* Toggle the given terminal's fullscreen mode.
* @param {Xterm} term - The terminal to toggle full screen mode
* @param {boolean} fullscreen - Toggle fullscreen on (true) or off (false)
*/
exports.toggleFullScreen = function (term, fullscreen) {
var fn;
if (typeof fullscreen == 'undefined') {
fn = (term.element.classList.contains('fullscreen')) ? 'remove' : 'add';
}
else if (!fullscreen) {
fn = 'remove';
}
else {
fn = 'add';
}
term.element.classList[fn]('fullscreen');
};
Xterm.prototype.toggleFullscreen = function (fullscreen) {
exports.toggleFullScreen(this, fullscreen);
};
return exports;
});
//# sourceMappingURL=fullscreen.js.map
+165
View File
@@ -0,0 +1,165 @@
/**
* Methods for turning URL subscrings in the terminal's content into links (`a` DOM elements).
* @module xterm/addons/linkify/linkify
* @license MIT
*/
(function (linkify) {
if (typeof exports === 'object' && typeof module === 'object') {
/*
* CommonJS environment
*/
module.exports = linkify(require('../../xterm'));
}
else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../xterm'], linkify);
}
else {
/*
* Plain browser environment
*/
linkify(window.Terminal);
}
})(function (Xterm) {
'use strict';
var exports = {}, protocolClause = '(https?:\\/\\/)', domainCharacterSet = '[\\da-z\\.-]+', negatedDomainCharacterSet = '[^\\da-z\\.-]+', domainBodyClause = '(' + domainCharacterSet + ')', tldClause = '([a-z\\.]{2,6})', ipClause = '((\\d{1,3}\\.){3}\\d{1,3})', portClause = '(:\\d{1,5})', hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + ')' + portClause + '?', pathClause = '(\\/[\\/\\w\\.-]*)*', negatedPathCharacterSet = '[^\\/\\w\\.-]+', bodyClause = hostClause + pathClause, start = '(?:^|' + negatedDomainCharacterSet + ')(', end = ')($|' + negatedPathCharacterSet + ')', lenientUrlClause = start + protocolClause + '?' + bodyClause + end, strictUrlClause = start + protocolClause + bodyClause + end, lenientUrlRegex = new RegExp(lenientUrlClause), strictUrlRegex = new RegExp(strictUrlClause);
/**
* Converts all valid URLs found in the given terminal line into
* hyperlinks. The terminal line can be either the HTML element itself
* or the index of the termina line in the children of the terminal
* rows container.
*
* @param {Xterm} terminal - The terminal that owns the given line.
* @param {number|HTMLDivElement} line - The terminal line that should get
* "linkified".
* @param {boolean} lenient - The regex type that will be used to identify links. If lenient is
* false, the regex requires a protocol clause. Defaults to true.
* @param {string} target - Sets target="" attribute with value provided to links.
* Default doesn't set target attribute
* @emits linkify
* @emits linkify:line
*/
exports.linkifyTerminalLine = function (terminal, line, lenient, target) {
if (typeof line == 'number') {
line = terminal.rowContainer.children[line];
}
else if (!(line instanceof HTMLDivElement)) {
var message = 'The "line" argument should be either a number';
message += ' or an HTMLDivElement';
throw new TypeError(message);
}
if (typeof target === 'undefined') {
target = '';
}
else {
target = 'target="' + target + '"';
}
var buffer = document.createElement('span'), nodes = line.childNodes;
for (var j = 0; j < nodes.length; j++) {
var node = nodes[j], match;
/**
* Since we cannot access the TextNode's HTML representation
* from the instance itself, we assign its data as textContent
* to a dummy buffer span, in order to retrieve the TextNode's
* HTML representation from the buffer's innerHTML.
*/
buffer.textContent = node.data;
var nodeHTML = buffer.innerHTML;
/**
* Apply function only on TextNodes
*/
if (node.nodeType != node.TEXT_NODE) {
continue;
}
var url = exports.findLinkMatch(node.data, lenient);
if (!url) {
continue;
}
var startsWithProtocol = new RegExp('^' + protocolClause), urlHasProtocol = url.match(startsWithProtocol), href = (urlHasProtocol) ? url : 'http://' + url, link = '<a href="' + href + '" ' + target + '>' + url + '</a>', newHTML = nodeHTML.replace(url, link);
line.innerHTML = line.innerHTML.replace(nodeHTML, newHTML);
}
/**
* This event gets emitted when conversion of all URL susbtrings
* to HTML anchor elements (links) has finished, for a specific
* line of the current Xterm instance.
*
* @event linkify:line
*/
terminal.emit('linkify:line', line);
};
/**
* Finds a link within a block of text.
*
* @param {string} text - The text to search .
* @param {boolean} lenient - Whether to use the lenient search.
* @return {string} A URL.
*/
exports.findLinkMatch = function (text, lenient) {
var match = text.match(lenient ? lenientUrlRegex : strictUrlRegex);
if (!match || match.length === 0) {
return null;
}
return match[1];
};
/**
* Converts all valid URLs found in the terminal view into hyperlinks.
*
* @param {Xterm} terminal - The terminal that should get "linkified".
* @param {boolean} lenient - The regex type that will be used to identify links. If lenient is
* false, the regex requires a protocol clause. Defaults to true.
* @param {string} target - Sets target="" attribute with value provided to links.
* Default doesn't set target attribute
* @emits linkify
* @emits linkify:line
*/
exports.linkify = function (terminal, lenient, target) {
var rows = terminal.rowContainer.children;
lenient = (typeof lenient == "boolean") ? lenient : true;
for (var i = 0; i < rows.length; i++) {
var line = rows[i];
exports.linkifyTerminalLine(terminal, line, lenient, target);
}
/**
* This event gets emitted when conversion of all URL substrings to
* HTML anchor elements (links) has finished for the current Xterm
* instance's view.
*
* @event linkify
*/
terminal.emit('linkify');
};
/**
* Extend Xterm prototype.
*/
/**
* Converts all valid URLs found in the current terminal linte into
* hyperlinks.
*
* @memberof Xterm
* @param {number|HTMLDivElement} line - The terminal line that should get
* "linkified".
* @param {boolean} lenient - The regex type that will be used to identify links. If lenient is
* false, the regex requires a protocol clause. Defaults to true.
* @param {string} target - Sets target="" attribute with value provided to links.
* Default doesn't set target attribute
*/
Xterm.prototype.linkifyTerminalLine = function (line, lenient, target) {
return exports.linkifyTerminalLine(this, line, lenient, target);
};
/**
* Converts all valid URLs found in the current terminal into hyperlinks.
*
* @memberof Xterm
* @param {boolean} lenient - The regex type that will be used to identify links. If lenient is
* false, the regex requires a protocol clause. Defaults to true.
* @param {string} target - Sets target="" attribute with value provided to links.
* Default doesn't set target attribute
*/
Xterm.prototype.linkify = function (lenient, target) {
return exports.linkify(this, lenient, target);
};
return exports;
});
//# sourceMappingURL=linkify.js.map
+122
View File
@@ -0,0 +1,122 @@
/**
* This module provides methods for attaching a terminal to a terminado WebSocket stream.
*
* @module xterm/addons/terminado/terminado
* @license MIT
*/
(function (attach) {
if (typeof exports === 'object' && typeof module === 'object') {
/*
* CommonJS environment
*/
module.exports = attach(require('../../xterm'));
}
else if (typeof define == 'function') {
/*
* Require.js is available
*/
define(['../../xterm'], attach);
}
else {
/*
* Plain browser environment
*/
attach(window.Terminal);
}
})(function (Xterm) {
'use strict';
var exports = {};
/**
* Attaches the given terminal to the given socket.
*
* @param {Xterm} term - The terminal to be attached to the given socket.
* @param {WebSocket} socket - The socket to attach the current terminal.
* @param {boolean} bidirectional - Whether the terminal should send data
* to the socket as well.
* @param {boolean} buffered - Whether the rendering of incoming data
* should happen instantly or at a maximum
* frequency of 1 rendering per 10ms.
*/
exports.terminadoAttach = function (term, socket, bidirectional, buffered) {
bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional;
term.socket = socket;
term._flushBuffer = function () {
term.write(term._attachSocketBuffer);
term._attachSocketBuffer = null;
clearTimeout(term._attachSocketBufferTimer);
term._attachSocketBufferTimer = null;
};
term._pushToBuffer = function (data) {
if (term._attachSocketBuffer) {
term._attachSocketBuffer += data;
}
else {
term._attachSocketBuffer = data;
setTimeout(term._flushBuffer, 10);
}
};
term._getMessage = function (ev) {
var data = JSON.parse(ev.data);
if (data[0] == "stdout") {
if (buffered) {
term._pushToBuffer(data[1]);
}
else {
term.write(data[1]);
}
}
};
term._sendData = function (data) {
socket.send(JSON.stringify(['stdin', data]));
};
term._setSize = function (size) {
socket.send(JSON.stringify(['set_size', size.rows, size.cols]));
};
socket.addEventListener('message', term._getMessage);
if (bidirectional) {
term.on('data', term._sendData);
}
term.on('resize', term._setSize);
socket.addEventListener('close', term.terminadoDetach.bind(term, socket));
socket.addEventListener('error', term.terminadoDetach.bind(term, socket));
};
/**
* Detaches the given terminal from the given socket
*
* @param {Xterm} term - The terminal to be detached from the given socket.
* @param {WebSocket} socket - The socket from which to detach the current
* terminal.
*/
exports.terminadoDetach = function (term, socket) {
term.off('data', term._sendData);
socket = (typeof socket == 'undefined') ? term.socket : socket;
if (socket) {
socket.removeEventListener('message', term._getMessage);
}
delete term.socket;
};
/**
* Attaches the current terminal to the given socket
*
* @param {WebSocket} socket - The socket to attach the current terminal.
* @param {boolean} bidirectional - Whether the terminal should send data
* to the socket as well.
* @param {boolean} buffered - Whether the rendering of incoming data
* should happen instantly or at a maximum
* frequency of 1 rendering per 10ms.
*/
Xterm.prototype.terminadoAttach = function (socket, bidirectional, buffered) {
return exports.terminadoAttach(this, socket, bidirectional, buffered);
};
/**
* Detaches the current terminal from the given socket.
*
* @param {WebSocket} socket - The socket from which to detach the current
* terminal.
*/
Xterm.prototype.terminadoDetach = function (socket) {
return exports.terminadoDetach(this, socket);
};
return exports;
});
//# sourceMappingURL=terminado.js.map
+1 -1
View File
@@ -61,7 +61,7 @@
position: absolute;
opacity: 0;
left: -9999em;
top: -9999em;
top: 0;
width: 0;
height: 0;
z-index: -10;
+4373 -1451
View File
File diff suppressed because it is too large Load Diff
+1 -27
View File
File diff suppressed because one or more lines are too long
+6 -7
View File
@@ -1,13 +1,12 @@
{
"source": {
"include": [
"src/xterm.js",
"src/handlers/Clipboard.js",
"addons/attach/attach.js",
"addons/fit/fit.js",
"addons/fullscreen/fullscreen.js",
"addons/linkify/linkify.js"
]
"src/"
],
"exclude": [
"src/test/"
],
"excludePattern": "src/.+\\.test\\.(js|ts)$"
},
"opts": {
"readme": "README.md",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "xterm",
"version": "2.1.0",
"version": "2.2.1",
"ignore": [
"demo",
"test",
@@ -20,7 +20,7 @@
"express": "4.13.4",
"express-ws": "2.0.0-rc.1",
"glob": "^7.0.5",
"jsdoc": "3.4.0",
"jsdoc": "3.4.3",
"mocha": "2.5.3",
"nodemon": "1.10.2",
"pty.js": "0.3.1",
-213
View File
@@ -1,213 +0,0 @@
/**
* 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) {
// Take .xterm-rows offsetTop into account as well in case it's positioned absolutely within
// the .xterm element.
var xtermRows = this.terminal.element.querySelector('.xterm-rows');
var cursorTop = xtermRows.offsetTop + cursor.offsetTop;
this.compositionView.style.left = cursor.offsetLeft + 'px';
this.compositionView.style.top = cursorTop + 'px';
this.compositionView.style.height = cursor.offsetHeight + 'px';
this.compositionView.style.lineHeight = cursor.offsetHeight + 'px';
// Sync the textarea to the exact position of the composition view so the IME knows where the
// text is.
var compositionViewBounds = this.compositionView.getBoundingClientRect();
this.textarea.style.left = cursor.offsetLeft + 'px';
this.textarea.style.top = cursorTop + 'px';
this.textarea.style.width = compositionViewBounds.width + 'px';
this.textarea.style.height = compositionViewBounds.height + 'px';
this.textarea.style.lineHeight = compositionViewBounds.height + '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 };
@@ -1,20 +1,20 @@
var assert = require('chai').assert;
var Terminal = require('../xterm');
import { assert } from 'chai';
import { CompositionHelper } from './CompositionHelper';
describe('CompositionHelper', function () {
describe('CompositionHelper', () => {
var terminal;
var compositionHelper;
var compositionView;
var textarea;
var handledText;
beforeEach(function () {
beforeEach(() => {
compositionView = {
classList: {
add: function () {},
remove: function () {},
add: () => {},
remove: () => {},
},
getBoundingClientRect: function () {
getBoundingClientRect: () => {
return { width: 0 };
},
style: {
@@ -32,7 +32,7 @@ describe('CompositionHelper', function () {
};
terminal = {
element: {
querySelector: function () {
querySelector: () => {
return { offsetLeft: 0, offsetTop: 0 };
}
},
@@ -41,53 +41,26 @@ describe('CompositionHelper', function () {
}
};
handledText = '';
compositionHelper = new Terminal.CompositionHelper(textarea, compositionView, terminal);
compositionHelper = new CompositionHelper(textarea, compositionView, terminal);
});
describe('Public API', function () {
it('should define CompositionHelper.prototype.compositionstart', function () {
assert.isDefined(Terminal.CompositionHelper.prototype.compositionstart);
});
it('should define CompositionHelper.prototype.compositionupdate', function () {
assert.isDefined(Terminal.CompositionHelper.prototype.compositionupdate);
});
it('should define CompositionHelper.prototype.compositionend', function () {
assert.isDefined(Terminal.CompositionHelper.prototype.compositionend);
});
it('should define CompositionHelper.prototype.finalizeComposition', function () {
assert.isDefined(Terminal.CompositionHelper.prototype.finalizeComposition);
});
it('should define CompositionHelper.prototype.handleAnyTextareaChanges', function () {
assert.isDefined(Terminal.CompositionHelper.prototype.handleAnyTextareaChanges);
});
it('should define CompositionHelper.prototype.updateCompositionElements', function () {
assert.isDefined(Terminal.CompositionHelper.prototype.updateCompositionElements);
});
it('should define CompositionHelper.isComposing', function () {
assert.isDefined(compositionHelper.isComposing);
});
it('should define CompositionHelper.isSendingComposition', function () {
assert.isDefined(compositionHelper.isSendingComposition);
});
});
describe('Input', function () {
describe('Input', () => {
it('Should insert simple characters', function (done) {
// First character 'ㅇ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
assert.equal(handledText, 'ㅇ');
// Second character 'ㅇ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇㅇ';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
assert.equal(handledText, 'ㅇㅇ');
done();
}, 0);
@@ -101,29 +74,29 @@ describe('CompositionHelper', function () {
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '아';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate({ data: '앙' });
textarea.value = '앙';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
assert.equal(handledText, '앙');
// Second character '앙'
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = '앙ㅇ';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '앙아';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate({ data: '앙' });
textarea.value = '앙앙';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
assert.equal(handledText, '앙앙');
done();
}, 0);
@@ -141,21 +114,21 @@ describe('CompositionHelper', function () {
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '아';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
// Start second character '아' in first character
compositionHelper.compositionupdate({ data: '앙' });
textarea.value = '앙';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '아아'
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
assert.equal(handledText, '아아');
done();
}, 0);
@@ -170,16 +143,16 @@ describe('CompositionHelper', function () {
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'd' });
textarea.value = 'd';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate({ data: 'だ' });
textarea.value = 'だ';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
// Second character 'あ'
compositionHelper.compositionupdate({ data: 'だあ' });
textarea.value = 'だあ';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
assert.equal(handledText, 'だあ');
done();
}, 0);
@@ -193,20 +166,20 @@ describe('CompositionHelper', function () {
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'd' });
textarea.value = 'd';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate({ data: 'だ' });
textarea.value = 'だ';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
// Second character 'ー'
compositionHelper.compositionupdate({ data: 'だー' });
textarea.value = 'だー';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
// Convert to katakana 'ダー'
compositionHelper.compositionupdate({ data: 'ダー' });
textarea.value = 'ダー';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
assert.equal(handledText, 'ダー');
done();
}, 0);
@@ -221,20 +194,20 @@ describe('CompositionHelper', function () {
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'い' });
textarea.value = 'い';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
// Second character 'ま'
compositionHelper.compositionupdate({ data: 'いm' });
textarea.value = 'いm';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate({ data: 'いま' });
textarea.value = 'いま';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
// Convert to kanji '今'
compositionHelper.compositionupdate({ data: '今' });
textarea.value = '今';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
assert.equal(handledText, '今');
done();
}, 0);
@@ -249,11 +222,11 @@ describe('CompositionHelper', function () {
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
// Second character '1' (a non-composition character)
textarea.value = 'ㅇ1';
setTimeout(function() { // wait for any textarea updates
setTimeout(() => { // wait for any textarea updates
assert.equal(handledText, 'ㅇ1');
done();
}, 0);
+232
View File
@@ -0,0 +1,232 @@
/**
* @license MIT
*/
import { ITerminal } from './Interfaces';
interface IPosition {
start: number;
end: number;
}
/**
* 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.
*/
export class CompositionHelper {
/**
* 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.
*/
private isComposing: boolean;
/**
* The position within the input textarea's value of the current composition.
*/
private compositionPosition: IPosition;;
/**
* Whether a composition is in the process of being sent, setting this to false will cancel any
* in-progress composition.
*/
private isSendingComposition: boolean;
/**
* Creates a new CompositionHelper.
* @param textarea The textarea that xterm uses for input.
* @param compositionView The element to display the in-progress composition in.
* @param terminal The Terminal to forward the finished composition to.
*/
constructor(
private textarea: HTMLTextAreaElement,
private compositionView: HTMLElement,
private terminal: ITerminal
) {
this.isComposing = false;
this.isSendingComposition = false;
this.compositionPosition = { start: null, end: null };
}
/**
* Handles the compositionstart event, activating the composition view.
*/
public compositionstart() {
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.
*/
public compositionupdate(ev: CompositionEvent) {
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.
*/
public compositionend() {
this.finalizeComposition(true);
}
/**
* Handles the keydown event, routing any necessary events to the CompositionHelper functions.
* @param ev The keydown event.
* @return Whether the Terminal should continue processing the keydown event.
*/
public keydown(ev: KeyboardEvent) {
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 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.
*/
private finalizeComposition(waitForPropogation: boolean) {
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.
*/
private handleAnyTextareaChanges() {
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).
* @param dontRecurse Whether to use setTimeout to recursively trigger another update, this is
* necessary as the IME events across browsers are not consistently triggered.
*/
public updateCompositionElements(dontRecurse?: boolean) {
if (!this.isComposing) {
return;
}
var cursor = <HTMLElement>this.terminal.element.querySelector('.terminal-cursor');
if (cursor) {
// Take .xterm-rows offsetTop into account as well in case it's positioned absolutely within
// the .xterm element.
var xtermRows = <HTMLElement>this.terminal.element.querySelector('.xterm-rows');
var cursorTop = xtermRows.offsetTop + cursor.offsetTop;
this.compositionView.style.left = cursor.offsetLeft + 'px';
this.compositionView.style.top = cursorTop + 'px';
this.compositionView.style.height = cursor.offsetHeight + 'px';
this.compositionView.style.lineHeight = cursor.offsetHeight + 'px';
// Sync the textarea to the exact position of the composition view so the IME knows where the
// text is.
var compositionViewBounds = this.compositionView.getBoundingClientRect();
this.textarea.style.left = cursor.offsetLeft + 'px';
this.textarea.style.top = cursorTop + 'px';
this.textarea.style.width = compositionViewBounds.width + 'px';
this.textarea.style.height = compositionViewBounds.height + 'px';
this.textarea.style.lineHeight = compositionViewBounds.height + '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
*/
private clearTextareaPosition() {
this.textarea.style.left = '';
this.textarea.style.top = '';
};
}
+1 -2
View File
@@ -1,6 +1,5 @@
/**
* xterm.js: xterm, in the browser
* Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License)
* @license MIT
*/
function EventEmitter() {
+22 -2
View File
@@ -1,14 +1,34 @@
/**
* xterm.js: xterm, in the browser
* Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License)
* @license MIT
*/
export interface IBrowser {
isNode: boolean;
userAgent: string;
platform: string;
isFirefox: boolean;
isMSIE: boolean;
isMac: boolean;
isIpad: boolean;
isIphone: boolean;
isMSWindows: boolean;
}
export interface ITerminal {
element: HTMLElement;
rowContainer: HTMLElement;
textarea: HTMLTextAreaElement;
ydisp: number;
lines: string[];
rows: number;
browser: IBrowser;
/**
* Emit the 'data' event and populate the given data.
* @param data The data to populate in the event.
*/
handler(data: string);
on(event: string, callback: () => void);
scrollDisp(disp: number, suppressScrollEvent: boolean);
cancel(ev: Event, force?: boolean);
}
+8 -5
View File
@@ -1,6 +1,5 @@
/**
* xterm.js: xterm, in the browser
* Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License)
* @license MIT
*/
import { ITerminal } from './Interfaces';
@@ -8,15 +7,19 @@ import { ITerminal } from './Interfaces';
/**
* 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 viewportElement The DOM element acting as the viewport.
* @param scrollArea The DOM element acting as the scroll area.
* @param charMeasureElement A DOM element used to measure the character size of. the terminal.
*/
export class Viewport {
private currentRowHeight: number;
private lastRecordedBufferLength: number;
private lastRecordedViewportHeight: number;
/**
* Creates a new Viewport.
* @param terminal The terminal this viewport belongs to.
* @param viewportElement The DOM element acting as the viewport.
* @param scrollArea The DOM element acting as the scroll area.
* @param charMeasureElement A DOM element used to measure the character size of. the terminal.
*/
constructor(
private terminal: ITerminal,
private viewportElement: HTMLElement,
+4 -12
View File
@@ -1,9 +1,7 @@
/*
* Implements the attach method, that
* attaches the terminal to a WebSocket stream.
*
* The bidirectional argument indicates, whether the terminal should
* send data to the socket as well and is true, by default.
/**
* Implements the attach method, that attaches the terminal to a WebSocket stream.
* @module xterm/addons/attach/attach
* @license MIT
*/
(function (attach) {
@@ -26,12 +24,6 @@
})(function (Xterm) {
'use strict';
/**
* This module provides methods for attaching a terminal to a WebSocket
* stream.
*
* @module xterm/addons/attach/attach
*/
var exports = {};
/**
+12 -17
View File
@@ -1,16 +1,16 @@
/*
* Fit terminal columns and rows to the dimensions of its
* DOM element.
/**
* Fit terminal columns and rows to the dimensions of its DOM element.
*
* Approach:
* - Rows: Truncate the division of the terminal parent element height
* by the terminal row height
* ## Approach
* - Rows: Truncate the division of the terminal parent element height by the terminal row height.
*
* - Columns: Truncate the division of the terminal parent element width by
* the terminal character width (apply display: inline at the
* terminal row and truncate its width with the current number
* of columns)
* - Columns: Truncate the division of the terminal parent element width by the terminal character
* width (apply display: inline at the terminal row and truncate its width with the current
* number of columns).
* @module xterm/addons/fit/fit
* @license MIT
*/
(function (fit) {
if (typeof exports === 'object' && typeof module === 'object') {
/*
@@ -29,17 +29,12 @@
fit(window.Terminal);
}
})(function (Xterm) {
/**
* This module provides methods for fitting a terminal's size to a parent container.
*
* @module xterm/addons/fit/fit
*/
var exports = {};
exports.proposeGeometry = function (term) {
var parentElementStyle = window.getComputedStyle(term.element.parentElement),
parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')),
parentElementWidth = parseInt(parentElementStyle.getPropertyValue('width')),
parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17),
elementStyle = window.getComputedStyle(term.element),
elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom')),
elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left')),
@@ -62,7 +57,7 @@
subjectRow.innerHTML = contentBuffer;
rows = parseInt(availableHeight / characterHeight);
cols = parseInt(availableWidth / characterWidth) - 1;
cols = parseInt(availableWidth / characterWidth);
geometry = {cols: cols, rows: rows};
return geometry;
+8 -10
View File
@@ -1,14 +1,7 @@
/*
/**
* Fullscreen addon for xterm.js
*
* Implements the toggleFullscreen function.
*
* If the `fullscreen` argument has been supplied, then
* if it is true, the fullscreen mode gets turned on,
* if it is false or null, the fullscreen mode gets turned off.
*
* If the `fullscreen` argument has not been supplied, the
* fullscreen mode is being toggled.
* @module xterm/addons/fullscreen/fullscreen
* @license MIT
*/
(function (fullscreen) {
if (typeof exports === 'object' && typeof module === 'object') {
@@ -30,6 +23,11 @@
})(function (Xterm) {
var exports = {};
/**
* Toggle the given terminal's fullscreen mode.
* @param {Xterm} term - The terminal to toggle full screen mode
* @param {boolean} fullscreen - Toggle fullscreen on (true) or off (false)
*/
exports.toggleFullScreen = function (term, fullscreen) {
var fn;
+6 -6
View File
@@ -1,3 +1,9 @@
/**
* Methods for turning URL subscrings in the terminal's content into links (`a` DOM elements).
* @module xterm/addons/linkify/linkify
* @license MIT
*/
(function (linkify) {
if (typeof exports === 'object' && typeof module === 'object') {
/*
@@ -18,12 +24,6 @@
})(function (Xterm) {
'use strict';
/**
* This module provides methods for convertings valid URL substrings
* into HTML anchor elements (links), inside a terminal view.
*
* @module xterm/addons/linkify/linkify
*/
var exports = {},
protocolClause = '(https?:\\/\\/)',
domainCharacterSet = '[\\da-z\\.-]+',
+4 -11
View File
@@ -1,9 +1,8 @@
/*
* Implements the attach method that
* attaches the terminal to a Terminado WebSocket stream.
/**
* This module provides methods for attaching a terminal to a terminado WebSocket stream.
*
* The bidirectional argument indicates, whether the terminal should
* send data to the socket as well and is true, by default.
* @module xterm/addons/terminado/terminado
* @license MIT
*/
(function (attach) {
@@ -26,12 +25,6 @@
})(function (Xterm) {
'use strict';
/**
* This module provides methods for attaching a terminal to a WebSocket
* stream.
*
* @module xterm/addons/attach/attach
*/
var exports = {};
/**
@@ -1,6 +1,6 @@
var assert = require('chai').assert;
var Terminal = require('../xterm');
var Clipboard = require('../handlers/Clipboard');
import { assert } from 'chai';
import * as Terminal from '../xterm';
import * as Clipboard from './Clipboard';
describe('evaluateCopiedTextProcessing', function () {
@@ -1,13 +1,27 @@
/**
* xterm.js: xterm, in the browser
* Copyright (c) 2016, SourceLair Private Company <www.sourcelair.com> (MIT License)
* Clipboard handler module: exports methods for handling all clipboard-related events in the
* terminal.
* @module xterm/handlers/Clipboard
* @license MIT
*/
/**
* Clipboard handler module. This module contains methods for handling all
* clipboard-related events appropriately in the terminal.
* @module xterm/handlers/Clipboard
*/
import { ITerminal } from '../Interfaces';
// We are extending the ClipboardEvent interface, since TypeScript has not declared the
// clientX and clientY properties that we use.
interface IClipboardEvent extends ClipboardEvent {
clientX: number;
clientY: number;
}
interface IWindow extends Window {
clipboardData?: {
getData(format: string): string;
setData(format: string, data: string);
};
}
declare var window: IWindow;
/**
* Prepares text copied from terminal selection, to be saved in the clipboard by:
@@ -16,14 +30,14 @@
* @param {string} text The copied text that needs processing for storing in clipboard
* @returns {string}
*/
function prepareTextForClipboard(text) {
var space = String.fromCharCode(32),
export function prepareTextForClipboard(text: string): string {
let 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);
let processedLine = line.replace(/\s+$/g, '').replace(allNonBreakingSpaces, space);
return processedLine;
}).join('\n');
@@ -35,8 +49,10 @@ function prepareTextForClipboard(text) {
* Binds copy functionality to the given terminal.
* @param {ClipboardEvent} ev The original copy event to be handled
*/
function copyHandler(ev, term) {
var copiedText = window.getSelection().toString(),
export function copyHandler(ev: IClipboardEvent, term: ITerminal) {
// We cast `window` to `any` type, because TypeScript has not declared the `clipboardData`
// property that we use below for Internet Explorer.
let copiedText = window.getSelection().toString(),
text = prepareTextForClipboard(copiedText);
if (term.browser.isMSIE) {
@@ -53,10 +69,12 @@ function copyHandler(ev, term) {
* @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) {
export function pasteHandler(ev: IClipboardEvent, term: ITerminal) {
ev.stopPropagation();
var dispatchPaste = function(text) {
let text: string;
let dispatchPaste = function(text) {
term.handler(text);
term.textarea.value = '';
return term.cancel(ev);
@@ -64,12 +82,12 @@ function pasteHandler(ev, term) {
if (term.browser.isMSIE) {
if (window.clipboardData) {
var text = window.clipboardData.getData('Text');
text = window.clipboardData.getData('Text');
dispatchPaste(text);
}
} else {
if (ev.clipboardData) {
var text = ev.clipboardData.getData('text/plain');
text = ev.clipboardData.getData('text/plain');
dispatchPaste(text);
}
}
@@ -88,16 +106,16 @@ function pasteHandler(ev, term) {
* @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(),
export function rightClickHandler(ev: IClipboardEvent, term: ITerminal) {
let s = document.getSelection(),
selectedText = prepareTextForClipboard(s.toString()),
clickIsOnSelection = false;
clickIsOnSelection = false,
x = ev.clientX,
y = ev.clientY;
if (s.rangeCount) {
var r = s.getRangeAt(0),
let r = s.getRangeAt(0),
cr = r.getClientRects(),
x = ev.clientX,
y = ev.clientY,
i, rect;
for (i=0; i<cr.length; i++) {
@@ -127,7 +145,7 @@ function rightClickHandler(ev, term) {
term.textarea.style.height = '20px';
term.textarea.style.left = (x - 10) + 'px';
term.textarea.style.top = (y - 10) + 'px';
term.textarea.style.zIndex = 1000;
term.textarea.style.zIndex = '1000';
term.textarea.focus();
// Reset the terminal textarea's styling
@@ -141,7 +159,3 @@ function rightClickHandler(ev, term) {
}, 4);
}
}
export {
prepareTextForClipboard, copyHandler, pasteHandler, rightClickHandler
};
+2 -1
View File
@@ -189,7 +189,7 @@ describe('xterm.js', function() {
it('should scroll down, when a key is pressed and terminal is scrolled up', function () {
// Override evaluateKeyEscapeSequence to return cancel code
xterm.evaluateKeyEscapeSequence = function() {
return { cancel: true };
return { key: 'a' };
};
var event = {
type: 'keydown',
@@ -200,6 +200,7 @@ describe('xterm.js', function() {
xterm.ydisp = 0;
xterm.ybase = 40;
assert.notEqual(xterm.ydisp, xterm.ybase);
xterm.keyDown(event);
// Ensure that now the terminal is scrolled to bottom
+2 -7
View File
@@ -1,12 +1,7 @@
/**
* xterm.js: xterm, in the browser
* Copyright (c) 2016, SourceLair Private Company <www.sourcelair.com> (MIT License)
*/
/**
* Browser utilities module. This module contains attributes and methods to help with
* identifying the current browser and platform.
* Attributes and methods to help with identifying the current browser and platform.
* @module xterm/utils/Browser
* @license MIT
*/
import { contains } from './Generic.js';
+2 -7
View File
@@ -1,12 +1,7 @@
/**
* xterm.js: xterm, in the browser
* Copyright (c) 2016, SourceLair Private Company <www.sourcelair.com> (MIT License)
*/
/**
* Generic utilities module. This module contains generic methods that can be helpful at
* different parts of the code base.
* Generic utilities module with methods that can be helpful at different parts of the code base.
* @module xterm/utils/Generic
* @license MIT
*/
/**
-21
View File
@@ -1,21 +0,0 @@
import { assert } from 'chai';
import * as Keyboard from './Keyboard';
describe('Keyboard', () => {
describe('isModifierOnlyKeyboardEvent', () => {
it('should return true when only modifier keys are used', () => {
// Note that KeyboardEvent.keyCode is deprecated but we're using it to improve browser
// compatibility. This helper returns the `any` type because KeyboardEvent doesn't exist under
// NodeJS.
function createEvent(keyCode: number): any {
return { keyCode };
}
assert.isTrue(Keyboard.isModifierOnlyKeyboardEvent(createEvent(16)));
assert.isTrue(Keyboard.isModifierOnlyKeyboardEvent(createEvent(17)));
assert.isTrue(Keyboard.isModifierOnlyKeyboardEvent(createEvent(18)));
assert.isTrue(Keyboard.isModifierOnlyKeyboardEvent(createEvent(91)));
assert.isFalse(Keyboard.isModifierOnlyKeyboardEvent(createEvent(19)));
assert.isFalse(Keyboard.isModifierOnlyKeyboardEvent(createEvent(90)));
});
});
});
-22
View File
@@ -1,22 +0,0 @@
/**
* xterm.js: xterm, in the browser
* Copyright (c) 2016, SourceLair Private Company <www.sourcelair.com> (MIT License)
*/
/**
* Keyboard utilities module. This module contains utilities for dealing with keyboard interaction.
* @module xterm/utils/Keyboard
*/
/**
* Gets whether a KeyboardEvent is made up entirely of modifier keys.
*
* @param event The event to check.
* @return Whether the KeyboardEvent is made up entirely of modifier keys.
*/
export function isModifierOnlyKeyboardEvent(event: KeyboardEvent): boolean {
return event.keyCode === 16 || // Shift
event.keyCode === 17 || // Control
event.keyCode === 18 || // Alt
event.keyCode === 91; // Meta
}
+1 -1
View File
@@ -61,7 +61,7 @@
position: absolute;
opacity: 0;
left: -9999em;
top: -9999em;
top: 0;
width: 0;
height: 0;
z-index: -10;
+14 -32
View File
@@ -1,27 +1,5 @@
/**
* xterm.js: xterm, in the browser
* Copyright (c) 2014-2014, SourceLair Private Company <www.sourcelair.com> (MIT License)
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
* https://github.com/chjj/term.js
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Originally forked from (with the author's permission):
* Fabrice Bellard's javascript vt100 for jslinux:
* http://bellard.org/jslinux/
@@ -29,6 +7,7 @@
* The original design remains. The terminal itself
* has been extended to include xterm CSI codes, among
* other features.
* @license MIT
*/
import { CompositionHelper } from './CompositionHelper.js';
@@ -807,7 +786,7 @@ Terminal.prototype.bindMouse = function() {
pos.x -= 32;
pos.y -= 32;
self.send('\x1b[<'
+ ((button & 3) === 3 ? button & ~3 : button)
+ (((button & 3) === 3 ? button & ~3 : button) - 32)
+ ';'
+ pos.x
+ ';'
@@ -2430,12 +2409,10 @@ Terminal.prototype.keyDown = function(ev) {
return false;
}
// Scroll down to prompt, whenever the user presses a key.
if (!Keyboard.isModifierOnlyKeyboardEvent(ev) && this.ybase !== this.ydisp) {
this.scrollToBottom();
}
if (!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)) {
if (this.ybase !== this.ydisp) {
this.scrollToBottom();
}
return false;
}
@@ -2724,10 +2701,13 @@ Terminal.prototype.evaluateKeyEscapeSequence = function(ev) {
// delete
result.key = String.fromCharCode(127);
} else if (ev.keyCode === 219) {
// ^[ - escape
// ^[ - Control Sequence Introducer (CSI)
result.key = String.fromCharCode(27);
} else if (ev.keyCode === 220) {
// ^\ - String Terminator (ST)
result.key = String.fromCharCode(28);
} else if (ev.keyCode === 221) {
// ^] - group sep
// ^] - Operating System Command (OSC)
result.key = String.fromCharCode(29);
}
} else if (!this.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) {
@@ -3148,6 +3128,10 @@ Terminal.prototype.is = function(term) {
* @param {string} data The data to populate in the event.
*/
Terminal.prototype.handler = function(data) {
// Input is being sent to the terminal, the terminal should focus the prompt.
if (this.ybase !== this.ydisp) {
this.scrollToBottom();
}
this.emit('data', data);
};
@@ -5130,8 +5114,6 @@ var wcwidth = (function(opts) {
*/
Terminal.EventEmitter = EventEmitter;
Terminal.CompositionHelper = CompositionHelper;
Terminal.Viewport = Viewport;
Terminal.inherits = inherits;
/**