*.target.gyp.mk
*.node
example/*.log
-docs/_build
+docs/
npm-debug.log
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 + ')(',
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
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>',
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.
+++ /dev/null
-{
- "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"
- }
-}
\ No newline at end of file
<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>
-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);
term.write(data);
});
}
-
-socket.onopen = runRealTerminal;
-socket.onclose = runFakeTerminal;
-socket.onerror = runFakeTerminal;
--- /dev/null
+{
+ "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
+ }
+}
"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"
}
}
* 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;
*/
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;
this.tabs;
this.setupStops();
+ this.debounceRefresh();
}
inherits(Terminal, EventEmitter);
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
*/
});
/**
- * Focused Terminal
+ * Focus the terminal.
+ *
+ * @public
*/
Terminal.prototype.focus = function() {
if (document.activeElement === this.element) {
};
- /*
- * 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;
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
};
/**
- * Destroy Terminal
+ * Destroys the terminal.
+ *
+ * @public
*/
-
Terminal.prototype.destroy = function() {
this.readable = false;
this.writable = false;
* 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);
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++) {
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;
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
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;
--- /dev/null
+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');
+ });
+ });
+ });
+});
--- /dev/null
+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');
+ });
+});
xterm.handler = function() {};
xterm.showCursor = function() {};
xterm.clearSelection = function() {};
- })
+ });
describe('On Mac OS', function() {
beforeEach(function() {