mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge remote-tracking branch 'upstream/master' into 119_fix_cursor_blink
This commit is contained in:
+1
-1
@@ -8,5 +8,5 @@ Makefile.gyp
|
||||
*.target.gyp.mk
|
||||
*.node
|
||||
example/*.log
|
||||
docs/_build
|
||||
docs/
|
||||
npm-debug.log
|
||||
|
||||
+21
-15
@@ -30,8 +30,10 @@
|
||||
negatedDomainCharacterSet = '[^\\da-z\\.-]+',
|
||||
domainBodyClause = '(' + domainCharacterSet + ')',
|
||||
tldClause = '([a-z\\.]{2,6})',
|
||||
hostClause = domainBodyClause + '\\.' + tldClause,
|
||||
pathClause = '([\\/\\w\\.-]*)*\\/?',
|
||||
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 + ')(',
|
||||
@@ -41,7 +43,6 @@
|
||||
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
|
||||
@@ -90,22 +91,13 @@
|
||||
continue;
|
||||
}
|
||||
|
||||
var url = exports.findLinkMatch(node.data, lenient);
|
||||
|
||||
if (lenient) {
|
||||
match = node.data.match(lenientUrlRegex);
|
||||
} else {
|
||||
match = node.data.match(strictUrlRegex);
|
||||
}
|
||||
|
||||
/**
|
||||
* If no URL was found in the current text, return.
|
||||
*/
|
||||
if (!match) {
|
||||
if (!url) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var url = match[1],
|
||||
startsWithProtocol = new RegExp('^' + protocolClause),
|
||||
var startsWithProtocol = new RegExp('^' + protocolClause),
|
||||
urlHasProtocol = url.match(startsWithProtocol),
|
||||
href = (urlHasProtocol) ? url : 'http://' + url,
|
||||
link = '<a href="' + href + '" >' + url + '</a>',
|
||||
@@ -124,6 +116,20 @@
|
||||
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.
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"source": {
|
||||
"include": [
|
||||
"src/xterm.js",
|
||||
"addons/attach/attach.js",
|
||||
"addons/fit/fit.js",
|
||||
"addons/fullscreen/fullscreen.js",
|
||||
"addons/linkify/linkify.js"
|
||||
]
|
||||
},
|
||||
"opts": {
|
||||
"readme": "README.md"
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -9,12 +9,16 @@
|
||||
<script src="../addons/attach/attach.js" ></script>
|
||||
<script src="../addons/fit/fit.js" ></script>
|
||||
<script src="../addons/fullscreen/fullscreen.js" ></script>
|
||||
<script src="main.js" defer ></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>
|
||||
xterm.js: xterm, in the browser
|
||||
</h1>
|
||||
<div id="terminal-container"></div>
|
||||
<div>
|
||||
<h2>Options</h2>
|
||||
<label><input type="checkbox" id="option-cursor-blink"> cursorBlink</label>
|
||||
</div>
|
||||
<script src="main.js" defer ></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+32
-11
@@ -1,11 +1,36 @@
|
||||
var terminalContainer = document.getElementById('terminal-container'),
|
||||
term = new Terminal(),
|
||||
protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://',
|
||||
socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/bash',
|
||||
socket = new WebSocket(socketURL);
|
||||
var term,
|
||||
protocol,
|
||||
socketURL,
|
||||
socket;
|
||||
|
||||
var terminalContainer = document.getElementById('terminal-container');
|
||||
var optionElements = {
|
||||
cursorBlink: document.querySelector('#option-cursor-blink')
|
||||
};
|
||||
|
||||
optionElements.cursorBlink.addEventListener('change', createTerminal);
|
||||
|
||||
createTerminal();
|
||||
|
||||
function createTerminal() {
|
||||
while (terminalContainer.children.length) {
|
||||
terminalContainer.removeChild(terminalContainer.children[0]);
|
||||
}
|
||||
term = new Terminal({
|
||||
cursorBlink: optionElements.cursorBlink.checked
|
||||
});
|
||||
protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
|
||||
socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/bash';
|
||||
socket = new WebSocket(socketURL);
|
||||
|
||||
term.open(terminalContainer);
|
||||
term.fit();
|
||||
|
||||
socket.onopen = runRealTerminal;
|
||||
socket.onclose = runFakeTerminal;
|
||||
socket.onerror = runFakeTerminal;
|
||||
}
|
||||
|
||||
term.open(terminalContainer);
|
||||
term.fit();
|
||||
|
||||
function runRealTerminal() {
|
||||
term.attach(socket);
|
||||
@@ -54,7 +79,3 @@ function runFakeTerminal() {
|
||||
term.write(data);
|
||||
});
|
||||
}
|
||||
|
||||
socket.onopen = runRealTerminal;
|
||||
socket.onclose = runFakeTerminal;
|
||||
socket.onerror = runFakeTerminal;
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"source": {
|
||||
"include": [
|
||||
"src/xterm.js",
|
||||
"addons/attach/attach.js",
|
||||
"addons/fit/fit.js",
|
||||
"addons/fullscreen/fullscreen.js",
|
||||
"addons/linkify/linkify.js"
|
||||
]
|
||||
},
|
||||
"opts": {
|
||||
"readme": "README.md",
|
||||
"template": "node_modules/docdash",
|
||||
"encoding": "utf8",
|
||||
"destination": "docs/",
|
||||
"recurse": true,
|
||||
"verbose": true
|
||||
},
|
||||
"templates": {
|
||||
"cleverLinks": false,
|
||||
"monospaceLinks": false
|
||||
}
|
||||
}
|
||||
+5
-2
@@ -10,10 +10,13 @@
|
||||
"express-ws": "2.0.0-rc.1",
|
||||
"pty.js": "0.3.0",
|
||||
"mocha": "2.5.3",
|
||||
"chai": "3.5.0"
|
||||
"chai": "3.5.0",
|
||||
"jsdoc": "3.4.0",
|
||||
"docdash": "0.4.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "bash bin/server",
|
||||
"test": "bash bin/test"
|
||||
"test": "bash bin/test --recursive",
|
||||
"build:docs": "node_modules/.bin/jsdoc -c jsdoc.json"
|
||||
}
|
||||
}
|
||||
|
||||
+112
-7
@@ -142,6 +142,14 @@
|
||||
* Terminal
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a new `Terminal` object.
|
||||
*
|
||||
* @param {object} options An object containing a set of options, the available options are:
|
||||
* - cursorBlink (boolean): Whether the terminal cursor blinks
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
function Terminal(options) {
|
||||
var self = this;
|
||||
|
||||
@@ -222,6 +230,16 @@
|
||||
*/
|
||||
this.y = 0;
|
||||
|
||||
/**
|
||||
* Used to debounce the refresh function
|
||||
*/
|
||||
this.isRefreshing = false;
|
||||
|
||||
/**
|
||||
* Whether there is a full terminal refresh queued
|
||||
*/
|
||||
this.queuedRefresh = false;
|
||||
|
||||
this.cursorState = 0;
|
||||
this.cursorHidden = false;
|
||||
this.convertEol;
|
||||
@@ -289,6 +307,7 @@
|
||||
|
||||
this.tabs;
|
||||
this.setupStops();
|
||||
this.debounceRefresh();
|
||||
}
|
||||
|
||||
inherits(Terminal, EventEmitter);
|
||||
@@ -299,6 +318,26 @@
|
||||
return (this.defAttr & ~0x1ff) | (this.curAttr & 0x1ff);
|
||||
};
|
||||
|
||||
/**
|
||||
* Allow refresh to execute only approximately 30 times a second. For commands that pass a
|
||||
* significant amount of output to the write function, this prevents the terminal from maxing
|
||||
* out the CPU and making the UI unresponsive. While commands can still run beyond what they do
|
||||
* on the terminal, it is far better with a debounce in place as every single terminal
|
||||
* manipulation does not need to be constructed in the DOM.
|
||||
*
|
||||
* A side-effect of this is that it makes ^C to interrupt a process seem more responsive.
|
||||
*/
|
||||
Terminal.prototype.debounceRefresh = function () {
|
||||
var self = this;
|
||||
window.setInterval(function () {
|
||||
self.isRefreshing = false;
|
||||
if (self.queuedRefresh) {
|
||||
// Do a full refresh in case multiple refreshes were requested.
|
||||
self.refresh(0, self.rows - 1);
|
||||
}
|
||||
}, 34);
|
||||
};
|
||||
|
||||
/**
|
||||
* Colors
|
||||
*/
|
||||
@@ -408,7 +447,9 @@
|
||||
});
|
||||
|
||||
/**
|
||||
* Focused Terminal
|
||||
* Focus the terminal.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
Terminal.prototype.focus = function() {
|
||||
if (document.activeElement === this.element) {
|
||||
@@ -636,8 +677,12 @@
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Open Terminal in the DOM
|
||||
/**
|
||||
* Opens the terminal within an element.
|
||||
*
|
||||
* @param {HTMLElement} parent The element to create the terminal within.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
Terminal.prototype.open = function(parent) {
|
||||
var self=this, i=0, div;
|
||||
@@ -745,6 +790,26 @@
|
||||
this.emit('open');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to load an add-on using CommonJS or RequireJS (whichever is available).
|
||||
* @param {string} addon The name of the addon to load
|
||||
* @static
|
||||
*/
|
||||
Terminal.loadAddon = function(addon, callback) {
|
||||
if (typeof exports === 'object' && typeof module === 'object') {
|
||||
// CommonJS
|
||||
return require(__dirname + '/../addons/' + addon);
|
||||
} else if (typeof define == 'function') {
|
||||
// RequireJS
|
||||
return require(['../addons/' + addon + '/' + addon], callback);
|
||||
} else {
|
||||
console.error('Cannot load a module without a CommonJS or RequireJS environment.');
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// XTerm mouse events
|
||||
// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
|
||||
// To better understand these
|
||||
@@ -1084,9 +1149,10 @@
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy Terminal
|
||||
* Destroys the terminal.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
|
||||
Terminal.prototype.destroy = function() {
|
||||
this.readable = false;
|
||||
this.writable = false;
|
||||
@@ -1124,9 +1190,24 @@
|
||||
* Next 14 bits: a mask for misc. flags:
|
||||
* 1=bold, 2=underline, 4=blink, 8=inverse, 16=invisible
|
||||
*/
|
||||
|
||||
/**
|
||||
* Refreshes terminal content within two rows (inclusive).
|
||||
*
|
||||
* @param {number} start The row to start from (between 0 and terminal's height terminal - 1)
|
||||
* @param {number} end The row to end at (between fromRow and terminal's height terminal - 1)
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
Terminal.prototype.refresh = function(start, end) {
|
||||
var x, y, i, line, out, ch, width, data, attr, bg, fg, flags, row, parent, focused = document.activeElement;
|
||||
|
||||
if (this.isRefreshing) {
|
||||
this.queuedRefresh = true;
|
||||
return;
|
||||
}
|
||||
this.isRefreshing = true;
|
||||
|
||||
if (end - start >= this.rows / 2) {
|
||||
parent = this.element.parentNode;
|
||||
if (parent) parent.removeChild(this.element);
|
||||
@@ -1135,9 +1216,9 @@
|
||||
width = this.cols;
|
||||
y = start;
|
||||
|
||||
if (end >= this.lines.length) {
|
||||
if (end >= this.rows.length) {
|
||||
this.log('`end` is too large. Most likely a bad CSR.');
|
||||
end = this.lines.length - 1;
|
||||
end = this.rows.length - 1;
|
||||
}
|
||||
|
||||
for (; y <= end; y++) {
|
||||
@@ -1360,6 +1441,13 @@
|
||||
this.refresh(0, this.rows - 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes text to the terminal.
|
||||
*
|
||||
* @param {string} text The text to write to the terminal.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
Terminal.prototype.write = function(data) {
|
||||
var l = data.length, i = 0, j, cs, ch;
|
||||
|
||||
@@ -2649,6 +2737,14 @@
|
||||
this.context.console.error.apply(this.context.console, args);
|
||||
};
|
||||
|
||||
/**
|
||||
* Resizes the terminal.
|
||||
*
|
||||
* @param {number} x The number of columns to resize to.
|
||||
* @param {number} y The number of rows to resize to.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
Terminal.prototype.resize = function(x, y) {
|
||||
var line
|
||||
, el
|
||||
@@ -4571,6 +4667,15 @@
|
||||
|
||||
Terminal.EventEmitter = EventEmitter;
|
||||
Terminal.inherits = inherits;
|
||||
|
||||
/**
|
||||
* Adds an event listener to the terminal.
|
||||
*
|
||||
* @param {string} event The name of the event. TODO: Document all event types
|
||||
* @param {function} callback The function to call when the event is triggered.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
Terminal.on = on;
|
||||
Terminal.off = off;
|
||||
Terminal.cancel = cancel;
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
var assert = require('chai').assert;
|
||||
var Terminal = require('../../src/xterm');
|
||||
var linkify = require('../../addons/linkify/linkify');
|
||||
|
||||
describe('linkify addon', function () {
|
||||
var xterm;
|
||||
|
||||
describe('API', function () {
|
||||
it('should define Terminal.prototype.linkify', function () {
|
||||
assert.isDefined(Terminal.prototype.linkify);
|
||||
});
|
||||
it('should define Terminal.prototype.linkifyTerminalLine', function () {
|
||||
assert.isDefined(Terminal.prototype.linkifyTerminalLine);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findUrlMatchOnLine', function () {
|
||||
describe('strict regex', function () {
|
||||
it('should match when the entire text is a match', function () {
|
||||
assert.equal(linkify.findLinkMatch('http://github.com', false), 'http://github.com');
|
||||
assert.equal(linkify.findLinkMatch('http://127.0.0.1', false), 'http://127.0.0.1');
|
||||
});
|
||||
it('should match simple domains', function () {
|
||||
assert.equal(linkify.findLinkMatch('foo http://github.com bar', false), 'http://github.com');
|
||||
assert.equal(linkify.findLinkMatch('foo http://www.github.com bar', false), 'http://www.github.com');
|
||||
assert.equal(linkify.findLinkMatch('foo https://github.com bar', false), 'https://github.com');
|
||||
assert.equal(linkify.findLinkMatch('foo https://www.github.com bar', false), 'https://www.github.com');
|
||||
});
|
||||
it('should match web addresses with alpha paths', function () {
|
||||
assert.equal(linkify.findLinkMatch('foo http://github.com/a/b/c bar', false), 'http://github.com/a/b/c');
|
||||
assert.equal(linkify.findLinkMatch('foo http://www.github.com/a/b/c bar', false), 'http://www.github.com/a/b/c');
|
||||
});
|
||||
it('should not include whitespace surrounding a match', function () {
|
||||
assert.equal(linkify.findLinkMatch(' http://github.com', false), 'http://github.com');
|
||||
assert.equal(linkify.findLinkMatch('http://github.com ', false), 'http://github.com');
|
||||
assert.equal(linkify.findLinkMatch(' http://github.com ', false), 'http://github.com');
|
||||
});
|
||||
it('should match IP addresses', function () {
|
||||
assert.equal(linkify.findLinkMatch('foo http://127.0.0.1 bar', false), 'http://127.0.0.1');
|
||||
assert.equal(linkify.findLinkMatch('foo https://127.0.0.1 bar', false), 'https://127.0.0.1');
|
||||
});
|
||||
it('should match ports on both domains and IP addresses', function () {
|
||||
assert.equal(linkify.findLinkMatch('foo http://127.0.0.1:8080 bar', false), 'http://127.0.0.1:8080');
|
||||
assert.equal(linkify.findLinkMatch('foo http://www.github.com:8080 bar', false), 'http://www.github.com:8080');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
var assert = require('chai').assert;
|
||||
var Terminal = require('../../src/xterm');
|
||||
|
||||
describe('xterm.js addons', function() {
|
||||
it('should load addons with Terminal.loadAddon', function () {
|
||||
Terminal.loadAddon('attach');
|
||||
// Test that function was loaded successfully
|
||||
assert.equal(typeof Terminal.prototype.attach, 'function');
|
||||
});
|
||||
});
|
||||
+1
-1
@@ -79,7 +79,7 @@ describe('xterm.js', function() {
|
||||
xterm.handler = function() {};
|
||||
xterm.showCursor = function() {};
|
||||
xterm.clearSelection = function() {};
|
||||
})
|
||||
});
|
||||
|
||||
describe('On Mac OS', function() {
|
||||
beforeEach(function() {
|
||||
|
||||
Reference in New Issue
Block a user