diff --git a/src/term.js b/src/term.js index f542dd0..36ae17a 100644 --- a/src/term.js +++ b/src/term.js @@ -30,3208 +30,3280 @@ * other features. */ -;(function() { +;(function () { + + /** + * Terminal Emulation References: + * http://vt100.net/ + * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt + * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + * http://invisible-island.net/vttest/ + * http://www.inwap.com/pdp10/ansicode.txt + * http://linux.die.net/man/4/console_codes + * http://linux.die.net/man/7/urxvt + */ + + 'use strict'; + + /** + * Shared + */ + + var window = this + , document = this.document; + + /** + * EventEmitter + */ + + function EventEmitter() { + this._events = this._events || {}; + } -/** - * Terminal Emulation References: - * http://vt100.net/ - * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt - * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html - * http://invisible-island.net/vttest/ - * http://www.inwap.com/pdp10/ansicode.txt - * http://linux.die.net/man/4/console_codes - * http://linux.die.net/man/7/urxvt - */ + EventEmitter.prototype.addListener = function (type, listener) { + this._events[type] = this._events[type] || []; + this._events[type].push(listener); + }; -'use strict'; + EventEmitter.prototype.on = EventEmitter.prototype.addListener; -/** - * Shared - */ + EventEmitter.prototype.removeListener = function (type, listener) { + if (!this._events[type]) return; -var window = this - , document = this.document; + var obj = this._events[type] + , i = obj.length; -/** - * EventEmitter - */ + while (i--) { + if (obj[i] === listener || obj[i].listener === listener) { + obj.splice(i, 1); + return; + } + } + }; -function EventEmitter() { - this._events = this._events || {}; -} + EventEmitter.prototype.off = EventEmitter.prototype.removeListener; -EventEmitter.prototype.addListener = function(type, listener) { - this._events[type] = this._events[type] || []; - this._events[type].push(listener); -}; + EventEmitter.prototype.removeAllListeners = function (type) { + if (this._events[type]) delete this._events[type]; + }; -EventEmitter.prototype.on = EventEmitter.prototype.addListener; + EventEmitter.prototype.once = function (type, listener) { + function on() { + var args = Array.prototype.slice.call(arguments); + this.removeListener(type, on); + return listener.apply(this, args); + } -EventEmitter.prototype.removeListener = function(type, listener) { - if (!this._events[type]) return; + on.listener = listener; + return this.on(type, on); + }; - var obj = this._events[type] - , i = obj.length; + EventEmitter.prototype.emit = function (type) { + if (!this._events[type]) return; - while (i--) { - if (obj[i] === listener || obj[i].listener === listener) { - obj.splice(i, 1); - return; - } - } -}; - -EventEmitter.prototype.off = EventEmitter.prototype.removeListener; - -EventEmitter.prototype.removeAllListeners = function(type) { - if (this._events[type]) delete this._events[type]; -}; - -EventEmitter.prototype.once = function(type, listener) { - function on() { - var args = Array.prototype.slice.call(arguments); - this.removeListener(type, on); - return listener.apply(this, args); - } - on.listener = listener; - return this.on(type, on); -}; - -EventEmitter.prototype.emit = function(type) { - if (!this._events[type]) return; - - var args = Array.prototype.slice.call(arguments, 1) - , obj = this._events[type] - , l = obj.length - , i = 0; - - for (; i < l; i++) { - obj[i].apply(this, args); - } -}; - -EventEmitter.prototype.listeners = function(type) { - return this._events[type] = this._events[type] || []; -}; + var args = Array.prototype.slice.call(arguments, 1) + , obj = this._events[type] + , l = obj.length + , i = 0; -/** - * Stream - */ + for (; i < l; i++) { + obj[i].apply(this, args); + } + }; -function Stream() { - EventEmitter.call(this); -} - -inherits(Stream, EventEmitter); - -Stream.prototype.pipe = function(dest, options) { - var src = this - , ondata - , onerror - , onend; - - function unbind() { - src.removeListener('data', ondata); - src.removeListener('error', onerror); - src.removeListener('end', onend); - dest.removeListener('error', onerror); - dest.removeListener('close', unbind); - } - - src.on('data', ondata = function(data) { - dest.write(data); - }); - - src.on('error', onerror = function(err) { - unbind(); - if (!this.listeners('error').length) { - throw err; - } - }); + EventEmitter.prototype.listeners = function (type) { + return this._events[type] = this._events[type] || []; + }; - src.on('end', onend = function() { - dest.end(); - unbind(); - }); + /** + * Stream + */ - dest.on('error', onerror); - dest.on('close', unbind); + function Stream() { + EventEmitter.call(this); + } - dest.emit('pipe', src); + inherits(Stream, EventEmitter); - return dest; -}; + Stream.prototype.pipe = function (dest, options) { + var src = this + , ondata + , onerror + , onend; -/** - * States - */ + function unbind() { + src.removeListener('data', ondata); + src.removeListener('error', onerror); + src.removeListener('end', onend); + dest.removeListener('error', onerror); + dest.removeListener('close', unbind); + } -var normal = 0 - , escaped = 1 - , csi = 2 - , osc = 3 - , charset = 4 - , dcs = 5 - , ignore = 6 - , UDK = { type: 'udk' }; + src.on('data', ondata = function (data) { + dest.write(data); + }); -/** - * Terminal - */ + src.on('error', onerror = function (err) { + unbind(); + if (!this.listeners('error').length) { + throw err; + } + }); -function Terminal(options) { - var self = this; + src.on('end', onend = function () { + dest.end(); + unbind(); + }); - if (!(this instanceof Terminal)) { - return new Terminal(arguments[0], arguments[1], arguments[2]); - } + dest.on('error', onerror); + dest.on('close', unbind); - Stream.call(this); + dest.emit('pipe', src); - if (typeof options === 'number') { - options = { - cols: arguments[0], - rows: arguments[1], - handler: arguments[2] + return dest; }; - } - options = options || {}; + /** + * States + */ - each(keys(Terminal.defaults), function(key) { - if (options[key] == null) { - options[key] = Terminal.options[key]; - // Legacy: - if (Terminal[key] !== Terminal.defaults[key]) { - options[key] = Terminal[key]; - } - } - self[key] = options[key]; - }); - - if (options.colors.length === 8) { - options.colors = options.colors.concat(Terminal._colors.slice(8)); - } else if (options.colors.length === 16) { - options.colors = options.colors.concat(Terminal._colors.slice(16)); - } else if (options.colors.length === 10) { - options.colors = options.colors.slice(0, -2).concat( - Terminal._colors.slice(8, -2), options.colors.slice(-2)); - } else if (options.colors.length === 18) { - options.colors = options.colors.slice(0, -2).concat( - Terminal._colors.slice(16, -2), options.colors.slice(-2)); - } - this.colors = options.colors; - - this.options = options; - - // this.context = options.context || window; - // this.document = options.document || document; - this.parent = options.body || options.parent - || (document ? document.getElementsByTagName('body')[0] : null); - - this.cols = options.cols || options.geometry[0]; - this.rows = options.rows || options.geometry[1]; - - // Act as though we are a node TTY stream: - this.setRawMode; - this.isTTY = true; - this.isRaw = true; - this.columns = this.cols; - this.rows = this.rows; - - if (options.handler) { - this.on('data', options.handler); - } - - this.ybase = 0; - this.ydisp = 0; - this.x = 0; - this.y = 0; - this.cursorState = 0; - this.cursorHidden = false; - this.convertEol; - this.state = 0; - this.queue = ''; - this.scrollTop = 0; - this.scrollBottom = this.rows - 1; - - // modes - this.applicationKeypad = false; - this.applicationCursor = false; - this.originMode = false; - this.insertMode = false; - this.wraparoundMode = false; - this.normal = null; - - // select modes - this.prefixMode = false; - this.selectMode = false; - this.visualMode = false; - this.searchMode = false; - this.searchDown; - this.entry = ''; - this.entryPrefix = 'Search: '; - this._real; - this._selected; - this._textarea; - - // charset - this.charset = null; - this.gcharset = null; - this.glevel = 0; - this.charsets = [null]; - - // mouse properties - this.decLocator; - this.x10Mouse; - this.vt200Mouse; - this.vt300Mouse; - this.normalMouse; - this.mouseEvents; - this.sendFocus; - this.utfMouse; - this.sgrMouse; - this.urxvtMouse; - - // misc - this.element; - this.children; - this.refreshStart; - this.refreshEnd; - this.savedX; - this.savedY; - this.savedCols; - - // stream - this.readable = true; - this.writable = true; - - this.defAttr = (0 << 18) | (257 << 9) | (256 << 0); - this.curAttr = this.defAttr; - - this.params = []; - this.currentParam = 0; - this.prefix = ''; - this.postfix = ''; - - this.lines = []; - var i = this.rows; - while (i--) { - this.lines.push(this.blankLine()); - } - - this.tabs; - this.setupStops(); -} - -inherits(Terminal, Stream); + var normal = 0 + , escaped = 1 + , csi = 2 + , osc = 3 + , charset = 4 + , dcs = 5 + , ignore = 6 + , UDK = {type: 'udk'}; -/** - * Colors - */ + /** + * Terminal + */ -// Colors 0-15 -Terminal.tangoColors = [ - // dark: - '#2e3436', - '#cc0000', - '#4e9a06', - '#c4a000', - '#3465a4', - '#75507b', - '#06989a', - '#d3d7cf', - // bright: - '#555753', - '#ef2929', - '#8ae234', - '#fce94f', - '#729fcf', - '#ad7fa8', - '#34e2e2', - '#eeeeec' -]; - -Terminal.xtermColors = [ - // dark: - '#000000', // black - '#cd0000', // red3 - '#00cd00', // green3 - '#cdcd00', // yellow3 - '#0000ee', // blue2 - '#cd00cd', // magenta3 - '#00cdcd', // cyan3 - '#e5e5e5', // gray90 - // bright: - '#7f7f7f', // gray50 - '#ff0000', // red - '#00ff00', // green - '#ffff00', // yellow - '#5c5cff', // rgb:5c/5c/ff - '#ff00ff', // magenta - '#00ffff', // cyan - '#ffffff' // white -]; + function Terminal(options) { + var self = this; -// Colors 0-15 + 16-255 -// Much thanks to TooTallNate for writing this. -Terminal.colors = (function() { - var colors = Terminal.tangoColors.slice() - , r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff] - , i; - - // 16-231 - i = 0; - for (; i < 216; i++) { - out(r[(i / 36) % 6 | 0], r[(i / 6) % 6 | 0], r[i % 6]); - } - - // 232-255 (grey) - i = 0; - for (; i < 24; i++) { - r = 8 + i * 10; - out(r, r, r); - } - - function out(r, g, b) { - colors.push('#' + hex(r) + hex(g) + hex(b)); - } - - function hex(c) { - c = c.toString(16); - return c.length < 2 ? '0' + c : c; - } - - return colors; -})(); + if (!(this instanceof Terminal)) { + return new Terminal(arguments[0], arguments[1], arguments[2]); + } -// Default BG/FG -Terminal.colors[256] = '#000000'; -Terminal.colors[257] = '#f0f0f0'; + Stream.call(this); -Terminal._colors = Terminal.colors.slice(); + if (typeof options === 'number') { + options = { + cols: arguments[0], + rows: arguments[1], + handler: arguments[2] + }; + } -Terminal.vcolors = (function() { - var out = [] - , colors = Terminal.colors - , i = 0 - , color; + options = options || {}; - for (; i < 256; i++) { - color = parseInt(colors[i].substring(1), 16); - out.push([ - (color >> 16) & 0xff, - (color >> 8) & 0xff, - color & 0xff - ]); - } + each(keys(Terminal.defaults), function (key) { + if (options[key] == null) { + options[key] = Terminal.options[key]; + // Legacy: + if (Terminal[key] !== Terminal.defaults[key]) { + options[key] = Terminal[key]; + } + } + self[key] = options[key]; + }); + + if (options.colors.length === 8) { + options.colors = options.colors.concat(Terminal._colors.slice(8)); + } else if (options.colors.length === 16) { + options.colors = options.colors.concat(Terminal._colors.slice(16)); + } else if (options.colors.length === 10) { + options.colors = options.colors.slice(0, -2).concat( + Terminal._colors.slice(8, -2), options.colors.slice(-2)); + } else if (options.colors.length === 18) { + options.colors = options.colors.slice(0, -2).concat( + Terminal._colors.slice(16, -2), options.colors.slice(-2)); + } + this.colors = options.colors; - return out; -})(); + this.options = options; -/** - * Options - */ + // this.context = options.context || window; + // this.document = options.document || document; + this.parent = options.body || options.parent + || (document ? document.getElementsByTagName('body')[0] : null); -Terminal.defaults = { - colors: Terminal.colors, - convertEol: false, - termName: 'xterm', - geometry: [80, 24], - cursorBlink: true, - visualBell: false, - popOnBell: false, - scrollback: 1000, - screenKeys: false, - debug: false, - useStyle: false - // programFeatures: false, - // focusKeys: false, -}; - -Terminal.options = {}; - -each(keys(Terminal.defaults), function(key) { - Terminal[key] = Terminal.defaults[key]; - Terminal.options[key] = Terminal.defaults[key]; -}); + this.cols = options.cols || options.geometry[0]; + this.rows = options.rows || options.geometry[1]; -/** - * Focused Terminal - */ + // Act as though we are a node TTY stream: + this.setRawMode; + this.isTTY = true; + this.isRaw = true; + this.columns = this.cols; + this.rows = this.rows; -Terminal.focus = null; + if (options.handler) { + this.on('data', options.handler); + } -Terminal.prototype.focus = function() { - if (Terminal.focus === this) return; + this.ybase = 0; + this.ydisp = 0; + this.x = 0; + this.y = 0; + this.cursorState = 0; + this.cursorHidden = false; + this.convertEol; + this.state = 0; + this.queue = ''; + this.scrollTop = 0; + this.scrollBottom = this.rows - 1; - if (Terminal.focus) { - Terminal.focus.blur(); - } + // modes + this.applicationKeypad = false; + this.applicationCursor = false; + this.originMode = false; + this.insertMode = false; + this.wraparoundMode = false; + this.normal = null; + + // select modes + this.prefixMode = false; + this.selectMode = false; + this.visualMode = false; + this.searchMode = false; + this.searchDown; + this.entry = ''; + this.entryPrefix = 'Search: '; + this._real; + this._selected; + this._textarea; + + // charset + this.charset = null; + this.gcharset = null; + this.glevel = 0; + this.charsets = [null]; + + // mouse properties + this.decLocator; + this.x10Mouse; + this.vt200Mouse; + this.vt300Mouse; + this.normalMouse; + this.mouseEvents; + this.sendFocus; + this.utfMouse; + this.sgrMouse; + this.urxvtMouse; + + // misc + this.element; + this.children; + this.refreshStart; + this.refreshEnd; + this.savedX; + this.savedY; + this.savedCols; + + // stream + this.readable = true; + this.writable = true; + + this.defAttr = (0 << 18) | (257 << 9) | (256 << 0); + this.curAttr = this.defAttr; + + this.params = []; + this.currentParam = 0; + this.prefix = ''; + this.postfix = ''; - if (this.sendFocus) this.send('\x1b[I'); - this.showCursor(); + this.lines = []; + var i = this.rows; + while (i--) { + this.lines.push(this.blankLine()); + } - // try { - // this.element.focus(); - // } catch (e) { - // ; - // } + this.tabs; + this.setupStops(); + } - // this.emit('focus'); + inherits(Terminal, Stream); - Terminal.focus = this; -}; + /** + * Colors + */ -Terminal.prototype.blur = function() { - if (Terminal.focus !== this) return; +// Colors 0-15 + Terminal.tangoColors = [ + // dark: + '#2e3436', + '#cc0000', + '#4e9a06', + '#c4a000', + '#3465a4', + '#75507b', + '#06989a', + '#d3d7cf', + // bright: + '#555753', + '#ef2929', + '#8ae234', + '#fce94f', + '#729fcf', + '#ad7fa8', + '#34e2e2', + '#eeeeec' + ]; - this.cursorState = 0; - this.refresh(this.y, this.y); - if (this.sendFocus) this.send('\x1b[O'); + Terminal.xtermColors = [ + // dark: + '#000000', // black + '#cd0000', // red3 + '#00cd00', // green3 + '#cdcd00', // yellow3 + '#0000ee', // blue2 + '#cd00cd', // magenta3 + '#00cdcd', // cyan3 + '#e5e5e5', // gray90 + // bright: + '#7f7f7f', // gray50 + '#ff0000', // red + '#00ff00', // green + '#ffff00', // yellow + '#5c5cff', // rgb:5c/5c/ff + '#ff00ff', // magenta + '#00ffff', // cyan + '#ffffff' // white + ]; - // try { - // this.element.blur(); - // } catch (e) { - // ; - // } +// Colors 0-15 + 16-255 +// Much thanks to TooTallNate for writing this. + Terminal.colors = (function () { + var colors = Terminal.tangoColors.slice() + , r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff] + , i; + + // 16-231 + i = 0; + for (; i < 216; i++) { + out(r[(i / 36) % 6 | 0], r[(i / 6) % 6 | 0], r[i % 6]); + } - // this.emit('blur'); + // 232-255 (grey) + i = 0; + for (; i < 24; i++) { + r = 8 + i * 10; + out(r, r, r); + } - Terminal.focus = null; -}; + function out(r, g, b) { + colors.push('#' + hex(r) + hex(g) + hex(b)); + } -/** - * Initialize global behavior - */ + function hex(c) { + c = c.toString(16); + return c.length < 2 ? '0' + c : c; + } -Terminal.prototype.initGlobal = function() { - var document = this.document; + return colors; + })(); - Terminal._boundDocs = Terminal._boundDocs || []; - if (~indexOf(Terminal._boundDocs, document)) { - return; - } - Terminal._boundDocs.push(document); +// Default BG/FG + Terminal.colors[256] = '#000000'; + Terminal.colors[257] = '#f0f0f0'; + + Terminal._colors = Terminal.colors.slice(); + + Terminal.vcolors = (function () { + var out = [] + , colors = Terminal.colors + , i = 0 + , color; + + for (; i < 256; i++) { + color = parseInt(colors[i].substring(1), 16); + out.push([ + (color >> 16) & 0xff, + (color >> 8) & 0xff, + color & 0xff + ]); + } - Terminal.bindPaste(document); + return out; + })(); + + /** + * Options + */ + + Terminal.defaults = { + colors: Terminal.colors, + convertEol: false, + termName: 'xterm', + geometry: [80, 24], + cursorBlink: true, + visualBell: false, + popOnBell: false, + scrollback: 1000, + screenKeys: false, + debug: false, + useStyle: false + // programFeatures: false, + // focusKeys: false, + }; - Terminal.bindKeys(document); + Terminal.options = {}; - Terminal.bindCopy(document); + each(keys(Terminal.defaults), function (key) { + Terminal[key] = Terminal.defaults[key]; + Terminal.options[key] = Terminal.defaults[key]; + }); - if (this.isMobile) { - this.fixMobile(document); - } + /** + * Focused Terminal + */ - if (this.useStyle) { - Terminal.insertStyle(document, this.colors[256], this.colors[257]); - } -}; + Terminal.focus = null; -/** - * Bind to paste event - */ + Terminal.prototype.focus = function () { + if (Terminal.focus === this) return; -Terminal.bindPaste = function(document) { - // This seems to work well for ctrl-V and middle-click, - // even without the contentEditable workaround. - var window = document.defaultView; - on(window, 'paste', function(ev) { - var term = Terminal.focus; - if (!term) return; - if (ev.clipboardData) { - term.send(ev.clipboardData.getData('text/plain')); - } else if (term.context.clipboardData) { - term.send(term.context.clipboardData.getData('Text')); - } - // Not necessary. Do it anyway for good measure. - term.element.contentEditable = 'inherit'; - return cancel(ev); - }); -}; + if (Terminal.focus) { + Terminal.focus.blur(); + } -/** - * Global Events for key handling - */ + if (this.sendFocus) this.send('\x1b[I'); + this.showCursor(); -Terminal.bindKeys = function(document) { - // We should only need to check `target === body` below, - // but we can check everything for good measure. - on(document, 'keydown', function(ev) { - if (!Terminal.focus) return; - var target = ev.target || ev.srcElement; - if (!target) return; - if (target === Terminal.focus.element - || target === Terminal.focus.context - || target === Terminal.focus.document - || target === Terminal.focus.body - || target === Terminal._textarea - || target === Terminal.focus.parent) { - return Terminal.focus.keyDown(ev); - } - }, true); - - on(document, 'keypress', function(ev) { - if (!Terminal.focus) return; - var target = ev.target || ev.srcElement; - if (!target) return; - if (target === Terminal.focus.element - || target === Terminal.focus.context - || target === Terminal.focus.document - || target === Terminal.focus.body - || target === Terminal._textarea - || target === Terminal.focus.parent) { - return Terminal.focus.keyPress(ev); - } - }, true); + // try { + // this.element.focus(); + // } catch (e) { + // ; + // } - // If we click somewhere other than a - // terminal, unfocus the terminal. - on(document, 'mousedown', function(ev) { - if (!Terminal.focus) return; + // this.emit('focus'); - var el = ev.target || ev.srcElement; - if (!el) return; + Terminal.focus = this; + }; - do { - if (el === Terminal.focus.element) return; - } while (el = el.parentNode); + Terminal.prototype.blur = function () { + if (Terminal.focus !== this) return; - Terminal.focus.blur(); - }); -}; + this.cursorState = 0; + this.refresh(this.y, this.y); + if (this.sendFocus) this.send('\x1b[O'); -/** - * Copy Selection w/ Ctrl-C (Select Mode) - */ + // try { + // this.element.blur(); + // } catch (e) { + // ; + // } -Terminal.bindCopy = function(document) { - var window = document.defaultView; - - // if (!('onbeforecopy' in document)) { - // // Copies to *only* the clipboard. - // on(window, 'copy', function fn(ev) { - // var term = Terminal.focus; - // if (!term) return; - // if (!term._selected) return; - // var text = term.grabText( - // term._selected.x1, term._selected.x2, - // term._selected.y1, term._selected.y2); - // term.emit('copy', text); - // ev.clipboardData.setData('text/plain', text); - // }); - // return; - // } - - // Copies to primary selection *and* clipboard. - // NOTE: This may work better on capture phase, - // or using the `beforecopy` event. - on(window, 'copy', function(ev) { - var term = Terminal.focus; - if (!term) return; - if (!term._selected) return; - var textarea = term.getCopyTextarea(); - var text = term.grabText( - term._selected.x1, term._selected.x2, - term._selected.y1, term._selected.y2); - term.emit('copy', text); - textarea.focus(); - textarea.textContent = text; - textarea.value = text; - textarea.setSelectionRange(0, text.length); - setTimeout(function() { - term.element.focus(); - term.focus(); - }, 1); - }); -}; + // this.emit('blur'); -/** - * Fix Mobile - */ + Terminal.focus = null; + }; -Terminal.prototype.fixMobile = function(document) { - var self = this; - - var textarea = document.createElement('textarea'); - textarea.style.position = 'absolute'; - textarea.style.left = '-32000px'; - textarea.style.top = '-32000px'; - textarea.style.width = '0px'; - textarea.style.height = '0px'; - textarea.style.opacity = '0'; - textarea.style.backgroundColor = 'transparent'; - textarea.style.borderStyle = 'none'; - textarea.style.outlineStyle = 'none'; - textarea.autocapitalize = 'none'; - textarea.autocorrect = 'off'; - - document.getElementsByTagName('body')[0].appendChild(textarea); - - Terminal._textarea = textarea; - - setTimeout(function() { - textarea.focus(); - }, 1000); - - if (this.isAndroid) { - on(textarea, 'change', function() { - var value = textarea.textContent || textarea.value; - textarea.value = ''; - textarea.textContent = ''; - self.send(value + '\r'); - }); - } -}; + /** + * Initialize global behavior + */ -/** - * Insert a default style - */ + Terminal.prototype.initGlobal = function () { + var document = this.document; -Terminal.insertStyle = function(document, bg, fg) { - var style = document.getElementById('term-style'); - if (style) return; - - var head = document.getElementsByTagName('head')[0]; - if (!head) return; - - var style = document.createElement('style'); - style.id = 'term-style'; - - // textContent doesn't work well with IE for