/** * term.js - an xterm emulator * 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/ * Copyright (c) 2011 Fabrice Bellard * The original design remains. The terminal itself * has been extended to include xterm CSI codes, among * other features. */ ;(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 || {}; } EventEmitter.prototype.addListener = function(type, listener) { this._events[type] = this._events[type] || []; this._events[type].push(listener); }; EventEmitter.prototype.on = EventEmitter.prototype.addListener; EventEmitter.prototype.removeListener = function(type, listener) { if (!this._events[type]) return; var obj = this._events[type] , i = obj.length; 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] || []; }; /** * Stream */ 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; } }); src.on('end', onend = function() { dest.end(); unbind(); }); dest.on('error', onerror); dest.on('close', unbind); dest.emit('pipe', src); return dest; }; /** * States */ var normal = 0 , escaped = 1 , csi = 2 , osc = 3 , charset = 4 , dcs = 5 , ignore = 6 , UDK = { type: 'udk' }; /** * Terminal */ function Terminal(options) { var self = this; if (!(this instanceof Terminal)) { return new Terminal(arguments[0], arguments[1], arguments[2]); } Stream.call(this); if (typeof options === 'number') { options = { cols: arguments[0], rows: arguments[1], handler: arguments[2] }; } options = options || {}; 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); /** * Colors */ // 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 ]; // 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; })(); // 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 ]); } 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.options = {}; each(keys(Terminal.defaults), function(key) { Terminal[key] = Terminal.defaults[key]; Terminal.options[key] = Terminal.defaults[key]; }); /** * Focused Terminal */ Terminal.focus = null; Terminal.prototype.focus = function() { if (this._textarea) { this._textarea.focus(); } if (Terminal.focus === this) return; if (Terminal.focus) { Terminal.focus.blur(); } if (this.sendFocus) this.send('\x1b[I'); this.showCursor(); // try { // this.element.focus(); // } catch (e) { // ; // } // this.emit('focus'); Terminal.focus = this; }; Terminal.prototype.blur = function() { if (Terminal.focus !== this) return; this.cursorState = 0; this.refresh(this.y, this.y); if (this.sendFocus) this.send('\x1b[O'); // try { // this.element.blur(); // } catch (e) { // ; // } // this.emit('blur'); Terminal.focus = null; }; /** * Initialize global behavior */ Terminal.prototype.initGlobal = function() { var document = this.document; Terminal._boundDocs = Terminal._boundDocs || []; if (~indexOf(Terminal._boundDocs, document)) { return; } Terminal._boundDocs.push(document); Terminal.bindPaste(document); Terminal.bindKeys(document); Terminal.bindCopy(document); if (this.useStyle) { Terminal.insertStyle(document, this.colors[256], this.colors[257]); } }; /** * Bind to paste event */ 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 (term._textarea) 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); }); }; /** * Global Events for key handling */ 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.focus._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.focus._textarea || target === Terminal.focus.parent) { return Terminal.focus.keyPress(ev); } }, true); // If we click somewhere other than a // applications, unfocus the applications. on(document, 'mousedown', function(ev) { if (!Terminal.focus) return; var el = ev.target || ev.srcElement; if (!el) return; if (!el.parentNode) return; if (!el.parentNode.parentNode) return; do { if (el === Terminal.focus.element) return; } while (el = el.parentNode); Terminal.focus.blur(); }); }; /** * Copy Selection w/ Ctrl-C (Select Mode) */ 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); }); }; /** * Fix Mobile */ Terminal.prototype.getTextarea = 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 = '100em'; textarea.style.height = '2em'; textarea.style.padding = '0'; textarea.style.opacity = '0'; textarea.style.color = 'inherit'; textarea.style.font = 'inherit'; textarea.style.textIndent = '-1em'; /* Hide text cursor on IE */ textarea.style.backgroundColor = 'transparent'; textarea.style.borderStyle = 'none'; textarea.style.outlineStyle = 'none'; textarea.autocapitalize = 'none'; textarea.autocorrect = 'off'; var onInputTimestamp; var onInput = function(ev){ if(ev.timeStamp && ev.timeStamp === onInputTimestamp){ return; } onInputTimestamp = ev.timeStamp; var value = textarea.textContent || textarea.value; if (typeof self.select.startPos !== 'undefined'){ self.select = {}; self.clearSelectedText(); self.refresh(0, self.rows - 1); } if (!self.compositionStatus) { textarea.value = ''; textarea.textContent = ''; self.send(value); } }; on(textarea, 'compositionstart', function() { textarea.style.opacity = "1.0"; textarea.style.textIndent = "0"; self.compositionStatus = true; }); on(textarea, 'compositionend', function(ev) { textarea.style.opacity = "0.0"; textarea.style.textIndent = "-1em"; self.compositionStatus = false; setTimeout(function(){ onInput(ev); // for IE that does not trigger 'input' after the IME composition. }, 1); }); on(textarea, 'keydown', function(){ var value = textarea.textContent || textarea.value; }); on(textarea, 'input', onInput); if (Terminal.isAndroid) { on(textarea, 'change', function() { var value = textarea.textContent || textarea.value; textarea.value = ''; textarea.textContent = ''; self.send(value + '\r'); }); } return textarea; }; /** * Insert a default style */ 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 <style> elements. style.innerHTML = '' + '.applications {\n' + ' float: left;\n' + ' border: ' + bg + ' solid 5px;\n' + ' font-family: "DejaVu Sans Mono", "Liberation Mono", monospace;\n' + ' font-size: 11px;\n' + ' color: ' + fg + ';\n' + ' background: ' + bg + ';\n' + '}\n' + '\n' + '.applications-cursor {\n' + ' color: ' + bg + ';\n' + ' background: ' + fg + ';\n' + '}\n'; // var out = ''; // each(Terminal.colors, function(color, i) { // if (i === 256) { // out += '\n.term-bg-color-default { background-color: ' + color + '; }'; // } // if (i === 257) { // out += '\n.term-fg-color-default { color: ' + color + '; }'; // } // out += '\n.term-bg-color-' + i + ' { background-color: ' + color + '; }'; // out += '\n.term-fg-color-' + i + ' { color: ' + color + '; }'; // }); // style.innerHTML += out + '\n'; head.insertBefore(style, head.firstChild); }; /** * Open Terminal */ Terminal.prototype.open = function(parent) { var self = this , i = 0 , div; this.parent = parent || this.parent; if (!this.parent) { throw new Error('Terminal requires a parent element.'); } // Grab global elements. this.context = this.parent.ownerDocument.defaultView; this.document = this.parent.ownerDocument; this.body = this.document.getElementsByTagName('body')[0]; // Parse user-agent strings. if (this.context.navigator && this.context.navigator.userAgent) { this.isMac = !!~this.context.navigator.userAgent.indexOf('Mac'); this.isIpad = !!~this.context.navigator.userAgent.indexOf('iPad'); this.isIphone = !!~this.context.navigator.userAgent.indexOf('iPhone'); this.isAndroid = !!~this.context.navigator.userAgent.indexOf('Android'); this.isMobile = this.isIpad || this.isIphone || this.isAndroid; this.isMSIE = !!~this.context.navigator.userAgent.indexOf('MSIE'); } // Create our main applications element. this.element = this.document.createElement('div'); this.element.className = 'terminal'; this.element.style.outline = 'none'; this.element.setAttribute('tabindex', 0); this.element.setAttribute('spellcheck', 'false'); this.element.style.backgroundColor = this.colors[256]; this.element.style.color = this.colors[257]; // Create the lines for our applications. this.children = []; for (; i < this.rows; i++) { div = this.document.createElement('div'); this.element.appendChild(div); this.children.push(div); } this._textarea = this.getTextarea(this.document); this.element.appendChild(this._textarea); this.parent.appendChild(this.element); this.select = {}; // Draw the screen. this.refresh(0, this.rows - 1); var updateSelect = function(){ var startPos = self.select.startPos; var endPos = self.select.endPos; if(endPos.y < startPos.y || (startPos.y == endPos.y && endPos.x < startPos.x)){ var tmp = startPos; startPos = endPos; endPos = tmp; } if (self.select.clicks === 2){ var j = i; var isMark = function(ch){ var code = ch.charCodeAt(0); return (code <= 0x2f) || (0x3a <= code && code <= 0x40) || (0x5b <= code && code < 0x60) || (0x7b <= code && code <= 0x7f); } while (startPos.x > 0 && !isMark(self.lines[startPos.y][startPos.x-1][1])){ startPos.x--; } while (endPos.x < self.cols && !isMark(self.lines[endPos.y][endPos.x][1])){ endPos.x++; } }else if(self.select.clicks === 3){ startPos.x = 0; endPos.y ++; endPos.x = 0; } if (startPos.x === endPos.x && startPos.y === endPos.y){ self.clearSelectedText(); }else{ var x2 = endPos.x; var y2 = endPos.y; x2 --; if(x2<0){ y2--; x2 = self.cols - 1; } self.selectText(startPos.x, x2, startPos.y, y2); } }; var copySelectToTextarea = function (){ var textarea = self._textarea; if (textarea) { if (self.select.startPos.x === self.select.endPos.x && self.select.startPos.y === self.select.endPos.y){ textarea.value = ""; textarea.select(); return; } var x2 = self.select.endPos.x; var y2 = self.select.endPos.y; x2 --; if(x2<0){ y2--; x2 = self.cols - 1; } var value = self.grabText(self.select.startPos.x, x2, self.select.startPos.y, y2); textarea.value = value; textarea.select(); } }; on(this.element, 'mousedown', function(ev) { if(ev.button === 2){ var r = self.element.getBoundingClientRect(); var x = ev.pageX - r.left + self.element.offsetLeft; var y = ev.pageY - r.top + self.element.offsetTop; self._textarea.style.left = x + 'px'; self._textarea.style.top = y + 'px'; return; } if (ev.button != 0){ return; } if (navigator.userAgent.indexOf("Trident")){ /* IE does not hold click number as "detail" property. */ if (self.select.timer){ self.select.clicks ++; clearTimeout(self.select.timer); self.select.timer = null; }else{ self.select.clicks = 1; } self.select.timer = setTimeout(function(){ self.select.timer = null; }, 600); }else{ self.select.clicks = ev.detail; } if (! ev.shiftKey){ self.clearSelectedText(); self.select.startPos = self.getCoords(ev); self.select.startPos.y += self.ydisp; } self.select.endPos = self.getCoords(ev); self.select.endPos.y += self.ydisp; updateSelect(); copySelectToTextarea(); self.refresh(0, self.rows - 1); self.select.selecting = true; }); on(this.element, 'mousemove', function(ev) { if(self.select.selecting){ self.select.endPos = self.getCoords(ev); self.select.endPos.y += self.ydisp; updateSelect(); self.refresh(0, self.rows - 1); } }); on(document, 'mouseup', function(ev) { if(ev.button === 2){ var r = self.element.getBoundingClientRect(); var x = ev.pageX - r.left + self.element.offsetLeft; var y = ev.pageY - r.top + self.element.offsetTop; self._textarea.style.left = x - 1 + 'px'; self._textarea.style.top = y - 1 + 'px'; return; } if(self.select.selecting){ self.select.selecting = false; copySelectToTextarea(); } }); if (!('useEvents' in this.options) || this.options.useEvents) { // Initialize global actions that // need to be taken on the document. this.initGlobal(); } if (!('useFocus' in this.options) || this.options.useFocus) { // Ensure there is a Terminal.focus. this.focus(); // Start blinking the cursor. this.startBlink(); // Bind to DOM events related // to focus and paste behavior. on(this.element, 'focus', function() { self.focus(); }); // This causes slightly funky behavior. // on(this.element, 'blur', function() { // self.blur(); // }); on(this.element, 'mousedown', function() { self.focus(); }); // Clickable paste workaround, using contentEditable. // This probably shouldn't work, // ... but it does. Firefox's paste // event seems to only work for textareas? on(this.element, 'mousedown', function(ev) { var button = ev.button != null ? +ev.button : ev.which != null ? ev.which - 1 : null; // Does IE9 do this? if (self.isMSIE) { button = button === 1 ? 0 : button === 4 ? 1 : button; } if (button !== 2) return; self.element.contentEditable = 'true'; setTimeout(function() { self.element.contentEditable = 'inherit'; // 'false'; }, 1); }, true); } if (!('useMouse' in this.options) || this.options.useMouse) { // Listen for mouse events and translate // them into applications mouse protocols. this.bindMouse(); } // this.emit('open'); if (!('useFocus' in this.options) || this.options.useFocus) { // This can be useful for pasting, // as well as the iPad fix. setTimeout(function() { self.element.focus(); self.focus(); }, 100); } // Figure out whether boldness affects // the character width of monospace fonts. if (Terminal.brokenBold == null) { Terminal.brokenBold = isBoldBroken(this.document); } this.emit('open'); }; Terminal.prototype.setRawMode = function(value) { this.isRaw = !!value; }; Terminal.prototype.getCoords = function(ev) { var x, y, w, h, el; var self = this; // ignore browsers without pageX for now if (ev.pageX == null) return; x = ev.pageX; y = ev.pageY; el = self.element; x -= el.clientLeft; y -= el.clientTop; // should probably check offsetParent // but this is more portable while (el && el !== self.document.documentElement) { x -= el.offsetLeft; y -= el.offsetTop; el = 'offsetParent' in el ? el.offsetParent : el.parentNode; } // convert to cols/rows w = self.element.clientWidth; h = self.element.clientHeight; var cols = Math.floor((x / w) * self.cols); var rows = Math.floor((y / h) * self.rows); // be sure to avoid sending // bad positions to the program if (cols < 0) cols = 0; if (cols > self.cols) cols = self.cols; if (rows < 0) rows = 0; if (rows > self.rows) rows = self.rows; // xterm sends raw bytes and // starts at 32 (SP) for each. //x += 32; //y += 32; return { x: cols, y: rows, }; } // XTerm mouse events // http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking // To better understand these // the xterm code is very helpful: // Relevant files: // button.c, charproc.c, misc.c // Relevant functions in xterm/button.c: // BtnCode, EmitButtonCode, EditorButton, SendMousePosition Terminal.prototype.bindMouse = function() { var el = this.element , self = this , pressed = 32; var wheelEvent = 'onmousewheel' in this.context ? 'mousewheel' : 'DOMMouseScroll'; // mouseup, mousedown, mousewheel // left click: ^[[M 3<^[[M#3< // mousewheel up: ^[[M`3> function sendButton(ev) { var button , pos; // get the xterm-style button button = getButton(ev); // get mouse coordinates pos = getCoords(ev); if (!pos) return; sendEvent(button, pos); switch (ev.type) { case 'mousedown': pressed = button; break; case 'mouseup': // keep it at the left // button, just in case. pressed = 32; break; case wheelEvent: // nothing. don't // interfere with // `pressed`. break; } } // motion example of a left click: // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7< function sendMove(ev) { var button = pressed , pos; pos = getCoords(ev); if (!pos) return; // buttons marked as motions // are incremented by 32 button += 32; sendEvent(button, pos); } // encode button and // position to characters function encode(data, ch) { if (!self.utfMouse) { if (ch === 255) return data.push(0); if (ch > 127) ch = 127; data.push(ch); } else { if (ch === 2047) return data.push(0); if (ch < 127) { data.push(ch); } else { if (ch > 2047) ch = 2047; data.push(0xC0 | (ch >> 6)); data.push(0x80 | (ch & 0x3F)); } } } // send a mouse event: // regular/utf8: ^[[M Cb Cx Cy // urxvt: ^[[ Cb ; Cx ; Cy M // sgr: ^[[ Cb ; Cx ; Cy M/m // vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \r // locator: CSI P e ; P b ; P r ; P c ; P p & w function sendEvent(button, pos) { // self.emit('mouse', { // x: pos.x - 32, // y: pos.x - 32, // button: button // }); if (self.vt300Mouse) { // NOTE: Unstable. // http://www.vt100.net/docs/vt3xx-gp/chapter15.html button &= 3; pos.x -= 32; pos.y -= 32; var data = '\x1b[24'; if (button === 0) data += '1'; else if (button === 1) data += '3'; else if (button === 2) data += '5'; else if (button === 3) return; else data += '0'; data += '~[' + pos.x + ',' + pos.y + ']\r'; self.send(data); return; } if (self.decLocator) { // NOTE: Unstable. button &= 3; pos.x -= 32; pos.y -= 32; if (button === 0) button = 2; else if (button === 1) button = 4; else if (button === 2) button = 6; else if (button === 3) button = 3; self.send('\x1b[' + button + ';' + (button === 3 ? 4 : 0) + ';' + pos.y + ';' + pos.x + ';' + (pos.page || 0) + '&w'); return; } if (self.urxvtMouse) { pos.x -= 32; pos.y -= 32; pos.x++; pos.y++; self.send('\x1b[' + button + ';' + pos.x + ';' + pos.y + 'M'); return; } if (self.sgrMouse) { pos.x -= 32; pos.y -= 32; self.send('\x1b[<' + ((button & 3) === 3 ? button & ~3 : button) + ';' + pos.x + ';' + pos.y + ((button & 3) === 3 ? 'm' : 'M')); return; } var data = []; encode(data, button); encode(data, pos.x); encode(data, pos.y); self.send('\x1b[M' + String.fromCharCode.apply(String, data)); } function getButton(ev) { var button , shift , meta , ctrl , mod; // two low bits: // 0 = left // 1 = middle // 2 = right // 3 = release // wheel up/down: // 1, and 2 - with 64 added switch (ev.type) { case 'mousedown': button = ev.button != null ? +ev.button : ev.which != null ? ev.which - 1 : null; if (self.isMSIE) { button = button === 1 ? 0 : button === 4 ? 1 : button; } break; case 'mouseup': button = 3; break; case 'DOMMouseScroll': button = ev.detail < 0 ? 64 : 65; break; case 'mousewheel': button = ev.wheelDeltaY > 0 ? 64 : 65; break; } // next three bits are the modifiers: // 4 = shift, 8 = meta, 16 = control shift = ev.shiftKey ? 4 : 0; meta = ev.metaKey ? 8 : 0; ctrl = ev.ctrlKey ? 16 : 0; mod = shift | meta | ctrl; // no mods if (self.vt200Mouse) { // ctrl only mod &= ctrl; } else if (!self.normalMouse) { mod = 0; } // increment to SP button = (32 + (mod << 2)) + button; return button; } // mouse coordinates measured in cols/rows function getCoords(ev) { var x, y, w, h, el; // ignore browsers without pageX for now if (ev.pageX == null) return; x = ev.pageX; y = ev.pageY; el = self.element; // should probably check offsetParent // but this is more portable while (el && el !== self.document.documentElement) { x -= el.offsetLeft; y -= el.offsetTop; el = 'offsetParent' in el ? el.offsetParent : el.parentNode; } // convert to cols/rows w = self.element.clientWidth; h = self.element.clientHeight; x = Math.round((x / w) * self.cols); y = Math.round((y / h) * self.rows); // be sure to avoid sending // bad positions to the program if (x < 0) x = 0; if (x > self.cols) x = self.cols; if (y < 0) y = 0; if (y > self.rows) y = self.rows; // xterm sends raw bytes and // starts at 32 (SP) for each. x += 32; y += 32; return { x: x, y: y, type: ev.type === wheelEvent ? 'mousewheel' : ev.type }; } on(el, 'mousedown', function(ev) { if (!self.mouseEvents) return; // send the button sendButton(ev); // ensure focus self.focus(); // fix for odd bug //if (self.vt200Mouse && !self.normalMouse) { // XXX This seems to break certain programs. // if (self.vt200Mouse) { // sendButton({ __proto__: ev, type: 'mouseup' }); // return cancel(ev); // } // bind events if (self.normalMouse) on(self.document, 'mousemove', sendMove); // x10 compatibility mode can't send button releases if (!self.x10Mouse) { on(self.document, 'mouseup', function up(ev) { sendButton(ev); if (self.normalMouse) off(self.document, 'mousemove', sendMove); off(self.document, 'mouseup', up); return cancel(ev); }); } return cancel(ev); }); //if (self.normalMouse) { // on(self.document, 'mousemove', sendMove); //} on(el, wheelEvent, function(ev) { if (!self.mouseEvents) return; if (self.x10Mouse || self.vt300Mouse || self.decLocator) return; sendButton(ev); return cancel(ev); }); // allow mousewheel scrolling in // the shell for example on(el, wheelEvent, function(ev) { if (self.mouseEvents) return; if (self.applicationKeypad) return; if (ev.type === 'DOMMouseScroll') { self.scrollDisp(ev.detail < 0 ? -5 : 5); } else { self.scrollDisp(ev.wheelDeltaY > 0 ? -5 : 5); } return cancel(ev); }); }; /** * Destroy Terminal */ Terminal.prototype.close = Terminal.prototype.destroySoon = Terminal.prototype.destroy = function() { if (this.destroyed) { return; } if (this._blink) { clearInterval(this._blink); delete this._blink; } this.readable = false; this.writable = false; this.destroyed = true; this._events = {}; this.handler = function() {}; this.write = function() {}; this.end = function() {}; if (this.element.parentNode) { this.element.parentNode.removeChild(this.element); } this.emit('end'); this.emit('close'); this.emit('finish'); this.emit('destroy'); }; /** * Rendering Engine */ // In the screen buffer, each character // is stored as a an array with a character // and a 32-bit integer. // First value: a utf-16 character. // Second value: // Next 9 bits: background color (0-511). // Next 9 bits: foreground color (0-511). // Next 14 bits: a mask for misc. flags: // 1=bold, 2=underline, 4=blink, 8=inverse, 16=invisible Terminal.prototype.refresh = function(start, end) { var x , y , i , line , out , ch , width , data , attr , bg , fg , flags , row , parent; var characterWidth = this.element.clientWidth / this.cols; var characterHeight = this.element.clientHeight / this.rows; var focused; if (end - start >= this.rows / 2) { focused = (Terminal.focus == this); parent = this.element.parentNode; if (parent) parent.removeChild(this.element); } width = this.cols; y = start; if (end >= this.lines.length) { this.log('`end` is too large. Most likely a bad CSR.'); end = this.lines.length - 1; } for (; y <= end; y++) { row = y + this.ydisp; line = this.lines[row]; out = ''; if (y === this.y && this.cursorState && (this.ydisp === this.ybase || this.selectMode) && !this.cursorHidden) { x = this.x; } else { x = -1; } attr = this.defAttr; i = 0; for (; i < width; i++) { data = line[i][0]; ch = line[i][1]; if (i === x) data = -1; if (data !== attr) { if (attr !== this.defAttr) { out += '</span>'; } if (data !== this.defAttr) { if (data === -1) { out += '<span class="reverse-video applications-cursor">'; } else { out += '<span style="'; bg = data & 0x1ff; fg = (data >> 9) & 0x1ff; flags = data >> 18; // bold if (flags & 1) { if (!Terminal.brokenBold) { out += 'font-weight:bold;'; } // See: XTerm*boldColors if (fg < 8) fg += 8; } // underline if (flags & 2) { out += 'text-decoration:underline;'; } // blink if (flags & 4) { if (flags & 2) { out = out.slice(0, -1); out += ' blink;'; } else { out += 'text-decoration:blink;'; } } // inverse if (flags & 8) { bg = (data >> 9) & 0x1ff; fg = data & 0x1ff; // Should inverse just be before the // above boldColors effect instead? if ((flags & 1) && fg < 8) fg += 8; } // invisible if (flags & 16) { out += 'visibility:hidden;'; } // out += '" class="' // + 'term-bg-color-' + bg // + ' ' // + 'term-fg-color-' + fg // + '">'; if (bg !== 256) { out += 'background-color:' + this.colors[bg] + ';'; } if (fg !== 257) { out += 'color:' + this.colors[fg] + ';'; } out += '">'; } } } switch (ch) { case '&': out += '&'; break; case '<': out += '<'; break; case '>': out += '>'; break; default: if (ch <= ' ') { out += ' '; } else { if (isWide(ch)) { i++; out += '<span style="display:inline-block; width:' + characterWidth * 2 + 'px; height:' + characterHeight + 'px; line-height:' + characterHeight + 'px;">' + ch + '</span>'; } else { out += ch; } } break; } attr = data; } if (attr !== this.defAttr) { out += '</span>'; } this.children[y].innerHTML = out; } if (parent) { parent.appendChild(this.element); if (focused) { this.focus(); } } if (this._textarea) { var cursorElement = this.element.querySelector('.applications-cursor'); if(cursorElement){ var cursor_x = cursorElement.offsetLeft; var cursor_y = cursorElement.offsetTop; this._textarea.style.left = cursor_x + 'px'; this._textarea.style.top = cursor_y + 'px'; } } }; Terminal.prototype._cursorBlink = function() { if (Terminal.focus !== this) return; this.cursorState ^= 1; this.refresh(this.y, this.y); }; Terminal.prototype.showCursor = function() { if (!this.cursorState) { this.cursorState = 1; this.refresh(this.y, this.y); } else { // Temporarily disabled: // this.refreshBlink(); } }; Terminal.prototype.startBlink = function() { if (!this.cursorBlink) return; var self = this; this._blinker = function() { self._cursorBlink(); }; this._blink = setInterval(this._blinker, 500); }; Terminal.prototype.refreshBlink = function() { if (!this.cursorBlink || !this._blink) return; clearInterval(this._blink); this._blink = setInterval(this._blinker, 500); }; Terminal.prototype.scroll = function() { var row; if (++this.ybase === this.scrollback) { this.ybase = this.ybase / 2 | 0; this.lines = this.lines.slice(-(this.ybase + this.rows) + 1); } this.ydisp = this.ybase; // last line row = this.ybase + this.rows - 1; // subtract the bottom scroll region row -= this.rows - 1 - this.scrollBottom; if (row === this.lines.length) { // potential optimization: // pushing is faster than splicing // when they amount to the same // behavior. this.lines.push(this.blankLine()); } else { // add our new line this.lines.splice(row, 0, this.blankLine()); } if (this.scrollTop !== 0) { if (this.ybase !== 0) { this.ybase--; this.ydisp = this.ybase; } this.lines.splice(this.ybase + this.scrollTop, 1); } // this.maxRange(); this.updateRange(this.scrollTop); this.updateRange(this.scrollBottom); }; Terminal.prototype.scrollDisp = function(disp) { this.ydisp += disp; if (this.ydisp > this.ybase) { this.ydisp = this.ybase; } else if (this.ydisp < 0) { this.ydisp = 0; } this.refresh(0, this.rows - 1); }; Terminal.prototype.write = function(data) { var l = data.length , i = 0 , j , cs , ch; this.refreshStart = this.y; this.refreshEnd = this.y; if (this.ybase !== this.ydisp) { this.ydisp = this.ybase; this.maxRange(); } // this.log(JSON.stringify(data.replace(/\x1b/g, '^['))); for (; i < l; i++, this.lch = ch) { ch = data[i]; switch (this.state) { case normal: switch (ch) { // '\0' // case '\0': // case '\200': // break; // '\a' case '\x07': this.bell(); break; // '\n', '\v', '\f' case '\n': case '\x0b': case '\x0c': if (this.convertEol) { this.x = 0; } // TODO: Implement eat_newline_glitch. // if (this.realX >= this.cols) break; // this.realX = 0; this.y++; if (this.y > this.scrollBottom) { this.y--; this.scroll(); } break; // '\r' case '\r': this.x = 0; break; // '\b' case '\x08': if (this.x > 0) { this.x--; } break; // '\t' case '\t': this.x = this.nextStop(); break; // shift out case '\x0e': this.setgLevel(1); break; // shift in case '\x0f': this.setgLevel(0); break; // '\e' case '\x1b': this.state = escaped; break; default: // ' ' if (ch >= ' ') { if (this.charset && this.charset[ch]) { ch = this.charset[ch]; } if (this.x >= this.cols) { this.x = 0; this.y++; if (this.y > this.scrollBottom) { this.y--; this.scroll(); } } this.lines[this.y + this.ybase][this.x] = [this.curAttr, ch]; this.x++; this.updateRange(this.y); if (isWide(ch)) { j = this.y + this.ybase; if (this.cols < 2 || this.x >= this.cols) { this.lines[j][this.x - 1] = [this.curAttr, ' ']; break; } this.lines[j][this.x] = [this.curAttr, ' ']; this.x++; } } break; } break; case escaped: switch (ch) { // ESC [ Control Sequence Introducer ( CSI is 0x9b). case '[': this.params = []; this.currentParam = 0; this.state = csi; break; // ESC ] Operating System Command ( OSC is 0x9d). case ']': this.params = []; this.currentParam = 0; this.state = osc; break; // ESC P Device Control String ( DCS is 0x90). case 'P': this.params = []; this.prefix = ''; this.currentParam = ''; this.state = dcs; break; // ESC _ Application Program Command ( APC is 0x9f). case '_': this.state = ignore; break; // ESC ^ Privacy Message ( PM is 0x9e). case '^': this.state = ignore; break; // ESC c Full Reset (RIS). case 'c': this.reset(); break; // ESC E Next Line ( NEL is 0x85). // ESC D Index ( IND is 0x84). case 'E': this.x = 0; ; case 'D': this.index(); break; // ESC M Reverse Index ( RI is 0x8d). case 'M': this.reverseIndex(); break; // ESC % Select default/utf-8 character set. // @ = default, G = utf-8 case '%': //this.charset = null; this.setgLevel(0); this.setgCharset(0, Terminal.charsets.US); this.state = normal; i++; break; // ESC (,),*,+,-,. Designate G0-G2 Character Set. case '(': // <-- this seems to get all the attention case ')': case '*': case '+': case '-': case '.': switch (ch) { case '(': this.gcharset = 0; break; case ')': this.gcharset = 1; break; case '*': this.gcharset = 2; break; case '+': this.gcharset = 3; break; case '-': this.gcharset = 1; break; case '.': this.gcharset = 2; break; } this.state = charset; break; // Designate G3 Character Set (VT300). // A = ISO Latin-1 Supplemental. // Not implemented. case '/': this.gcharset = 3; this.state = charset; i--; break; // ESC N // Single Shift Select of G2 Character Set // ( SS2 is 0x8e). This affects next character only. case 'N': break; // ESC O // Single Shift Select of G3 Character Set // ( SS3 is 0x8f). This affects next character only. case 'O': break; // ESC n // Invoke the G2 Character Set as GL (LS2). case 'n': this.setgLevel(2); break; // ESC o // Invoke the G3 Character Set as GL (LS3). case 'o': this.setgLevel(3); break; // ESC | // Invoke the G3 Character Set as GR (LS3R). case '|': this.setgLevel(3); break; // ESC } // Invoke the G2 Character Set as GR (LS2R). case '}': this.setgLevel(2); break; // ESC ~ // Invoke the G1 Character Set as GR (LS1R). case '~': this.setgLevel(1); break; // ESC 7 Save Cursor (DECSC). case '7': this.saveCursor(); this.state = normal; break; // ESC 8 Restore Cursor (DECRC). case '8': this.restoreCursor(); this.state = normal; break; // ESC # 3 DEC line height/width case '#': this.state = normal; i++; break; // ESC H Tab Set (HTS is 0x88). case 'H': this.tabSet(); break; // ESC = Application Keypad (DECPAM). case '=': this.log('Serial port requested applications keypad.'); this.applicationKeypad = true; this.state = normal; break; // ESC > Normal Keypad (DECPNM). case '>': this.log('Switching back to normal keypad.'); this.applicationKeypad = false; this.state = normal; break; default: this.state = normal; this.error('Unknown ESC control: %s.', ch); break; } break; case charset: switch (ch) { case '0': // DEC Special Character and Line Drawing Set. cs = Terminal.charsets.SCLD; break; case 'A': // UK cs = Terminal.charsets.UK; break; case 'B': // United States (USASCII). cs = Terminal.charsets.US; break; case '4': // Dutch cs = Terminal.charsets.Dutch; break; case 'C': // Finnish case '5': cs = Terminal.charsets.Finnish; break; case 'R': // French cs = Terminal.charsets.French; break; case 'Q': // FrenchCanadian cs = Terminal.charsets.FrenchCanadian; break; case 'K': // German cs = Terminal.charsets.German; break; case 'Y': // Italian cs = Terminal.charsets.Italian; break; case 'E': // NorwegianDanish case '6': cs = Terminal.charsets.NorwegianDanish; break; case 'Z': // Spanish cs = Terminal.charsets.Spanish; break; case 'H': // Swedish case '7': cs = Terminal.charsets.Swedish; break; case '=': // Swiss cs = Terminal.charsets.Swiss; break; case '/': // ISOLatin (actually /A) cs = Terminal.charsets.ISOLatin; i++; break; default: // Default cs = Terminal.charsets.US; break; } this.setgCharset(this.gcharset, cs); this.gcharset = null; this.state = normal; break; case osc: // OSC Ps ; Pt ST // OSC Ps ; Pt BEL // Set Text Parameters. if ((this.lch === '\x1b' && ch === '\\') || ch === '\x07') { if (this.lch === '\x1b') { if (typeof this.currentParam === 'string') { this.currentParam = this.currentParam.slice(0, -1); } else if (typeof this.currentParam == 'number') { this.currentParam = (this.currentParam - ('\x1b'.charCodeAt(0) - 48)) / 10; } } this.params.push(this.currentParam); switch (this.params[0]) { case 0: case 1: case 2: if (this.params[1]) { this.title = this.params[1]; this.handleTitle(this.title); } break; case 3: // set X property break; case 4: case 5: // change dynamic colors break; case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 19: // change dynamic ui colors break; case 46: // change log file break; case 50: // dynamic font break; case 51: // emacs shell break; case 52: // manipulate selection data break; case 104: case 105: case 110: case 111: case 112: case 113: case 114: case 115: case 116: case 117: case 118: // reset colors break; } this.params = []; this.currentParam = 0; this.state = normal; } else { if (!this.params.length) { if (ch >= '0' && ch <= '9') { this.currentParam = this.currentParam * 10 + ch.charCodeAt(0) - 48; } else if (ch === ';') { this.params.push(this.currentParam); this.currentParam = ''; } } else { this.currentParam += ch; } } break; case csi: // '?', '>', '!' if (ch === '?' || ch === '>' || ch === '!') { this.prefix = ch; break; } // 0 - 9 if (ch >= '0' && ch <= '9') { this.currentParam = this.currentParam * 10 + ch.charCodeAt(0) - 48; break; } // '$', '"', ' ', '\'' if (ch === '$' || ch === '"' || ch === ' ' || ch === '\'') { this.postfix = ch; break; } this.params.push(this.currentParam); this.currentParam = 0; // ';' if (ch === ';') break; this.state = normal; switch (ch) { // CSI Ps A // Cursor Up Ps Times (default = 1) (CUU). case 'A': this.cursorUp(this.params); break; // CSI Ps B // Cursor Down Ps Times (default = 1) (CUD). case 'B': this.cursorDown(this.params); break; // CSI Ps C // Cursor Forward Ps Times (default = 1) (CUF). case 'C': this.cursorForward(this.params); break; // CSI Ps D // Cursor Backward Ps Times (default = 1) (CUB). case 'D': this.cursorBackward(this.params); break; // CSI Ps ; Ps H // Cursor Position [row;column] (default = [1,1]) (CUP). case 'H': this.cursorPos(this.params); break; // CSI Ps J Erase in Display (ED). case 'J': this.eraseInDisplay(this.params); break; // CSI Ps K Erase in Line (EL). case 'K': this.eraseInLine(this.params); break; // CSI Pm m Character Attributes (SGR). case 'm': if (!this.prefix) { this.charAttributes(this.params); } break; // CSI Ps n Device Status Report (DSR). case 'n': if (!this.prefix) { this.deviceStatus(this.params); } break; /** * Additions */ // CSI Ps @ // Insert Ps (Blank) Character(s) (default = 1) (ICH). case '@': this.insertChars(this.params); break; // CSI Ps E // Cursor Next Line Ps Times (default = 1) (CNL). case 'E': this.cursorNextLine(this.params); break; // CSI Ps F // Cursor Preceding Line Ps Times (default = 1) (CNL). case 'F': this.cursorPrecedingLine(this.params); break; // CSI Ps G // Cursor Character Absolute [column] (default = [row,1]) (CHA). case 'G': this.cursorCharAbsolute(this.params); break; // CSI Ps L // Insert Ps Line(s) (default = 1) (IL). case 'L': this.insertLines(this.params); break; // CSI Ps M // Delete Ps Line(s) (default = 1) (DL). case 'M': this.deleteLines(this.params); break; // CSI Ps P // Delete Ps Character(s) (default = 1) (DCH). case 'P': this.deleteChars(this.params); break; // CSI Ps X // Erase Ps Character(s) (default = 1) (ECH). case 'X': this.eraseChars(this.params); break; // CSI Pm ` Character Position Absolute // [column] (default = [row,1]) (HPA). case '`': this.charPosAbsolute(this.params); break; // 141 61 a * HPR - // Horizontal Position Relative case 'a': this.HPositionRelative(this.params); break; // CSI P s c // Send Device Attributes (Primary DA). // CSI > P s c // Send Device Attributes (Secondary DA) case 'c': this.sendDeviceAttributes(this.params); break; // CSI Pm d // Line Position Absolute [row] (default = [1,column]) (VPA). case 'd': this.linePosAbsolute(this.params); break; // 145 65 e * VPR - Vertical Position Relative case 'e': this.VPositionRelative(this.params); break; // CSI Ps ; Ps f // Horizontal and Vertical Position [row;column] (default = // [1,1]) (HVP). case 'f': this.HVPosition(this.params); break; // CSI Pm h Set Mode (SM). // CSI ? Pm h - mouse escape codes, cursor escape codes case 'h': this.setMode(this.params); break; // CSI Pm l Reset Mode (RM). // CSI ? Pm l case 'l': this.resetMode(this.params); break; // CSI Ps ; Ps r // Set Scrolling Region [top;bottom] (default = full size of win- // dow) (DECSTBM). // CSI ? Pm r case 'r': this.setScrollRegion(this.params); break; // CSI s // Save cursor (ANSI.SYS). case 's': this.saveCursor(this.params); break; // CSI u // Restore cursor (ANSI.SYS). case 'u': this.restoreCursor(this.params); break; /** * Lesser Used */ // CSI Ps I // Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). case 'I': this.cursorForwardTab(this.params); break; // CSI Ps S Scroll up Ps lines (default = 1) (SU). case 'S': this.scrollUp(this.params); break; // CSI Ps T Scroll down Ps lines (default = 1) (SD). // CSI Ps ; Ps ; Ps ; Ps ; Ps T // CSI > Ps; Ps T case 'T': // if (this.prefix === '>') { // this.resetTitleModes(this.params); // break; // } // if (this.params.length > 2) { // this.initMouseTracking(this.params); // break; // } if (this.params.length < 2 && !this.prefix) { this.scrollDown(this.params); } break; // CSI Ps Z // Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). case 'Z': this.cursorBackwardTab(this.params); break; // CSI Ps b Repeat the preceding graphic character Ps times (REP). case 'b': this.repeatPrecedingCharacter(this.params); break; // CSI Ps g Tab Clear (TBC). case 'g': this.tabClear(this.params); break; // CSI Pm i Media Copy (MC). // CSI ? Pm i // case 'i': // this.mediaCopy(this.params); // break; // CSI Pm m Character Attributes (SGR). // CSI > Ps; Ps m // case 'm': // duplicate // if (this.prefix === '>') { // this.setResources(this.params); // } else { // this.charAttributes(this.params); // } // break; // CSI Ps n Device Status Report (DSR). // CSI > Ps n // case 'n': // duplicate // if (this.prefix === '>') { // this.disableModifiers(this.params); // } else { // this.deviceStatus(this.params); // } // break; // CSI > Ps p Set pointer mode. // CSI ! p Soft applications reset (DECSTR). // CSI Ps$ p // Request ANSI mode (DECRQM). // CSI ? Ps$ p // Request DEC private mode (DECRQM). // CSI Ps ; Ps " p case 'p': switch (this.prefix) { // case '>': // this.setPointerMode(this.params); // break; case '!': this.softReset(this.params); break; // case '?': // if (this.postfix === '$') { // this.requestPrivateMode(this.params); // } // break; // default: // if (this.postfix === '"') { // this.setConformanceLevel(this.params); // } else if (this.postfix === '$') { // this.requestAnsiMode(this.params); // } // break; } break; // CSI Ps q Load LEDs (DECLL). // CSI Ps SP q // CSI Ps " q // case 'q': // if (this.postfix === ' ') { // this.setCursorStyle(this.params); // break; // } // if (this.postfix === '"') { // this.setCharProtectionAttr(this.params); // break; // } // this.loadLEDs(this.params); // break; // CSI Ps ; Ps r // Set Scrolling Region [top;bottom] (default = full size of win- // dow) (DECSTBM). // CSI ? Pm r // CSI Pt; Pl; Pb; Pr; Ps$ r // case 'r': // duplicate // if (this.prefix === '?') { // this.restorePrivateValues(this.params); // } else if (this.postfix === '$') { // this.setAttrInRectangle(this.params); // } else { // this.setScrollRegion(this.params); // } // break; // CSI s Save cursor (ANSI.SYS). // CSI ? Pm s // case 's': // duplicate // if (this.prefix === '?') { // this.savePrivateValues(this.params); // } else { // this.saveCursor(this.params); // } // break; // CSI Ps ; Ps ; Ps t // CSI Pt; Pl; Pb; Pr; Ps$ t // CSI > Ps; Ps t // CSI Ps SP t // case 't': // if (this.postfix === '$') { // this.reverseAttrInRectangle(this.params); // } else if (this.postfix === ' ') { // this.setWarningBellVolume(this.params); // } else { // if (this.prefix === '>') { // this.setTitleModeFeature(this.params); // } else { // this.manipulateWindow(this.params); // } // } // break; // CSI u Restore cursor (ANSI.SYS). // CSI Ps SP u // case 'u': // duplicate // if (this.postfix === ' ') { // this.setMarginBellVolume(this.params); // } else { // this.restoreCursor(this.params); // } // break; // CSI Pt; Pl; Pb; Pr; Pp; Pt; Pl; Pp$ v // case 'v': // if (this.postfix === '$') { // this.copyRectagle(this.params); // } // break; // CSI Pt ; Pl ; Pb ; Pr ' w // case 'w': // if (this.postfix === '\'') { // this.enableFilterRectangle(this.params); // } // break; // CSI Ps x Request Terminal Parameters (DECREQTPARM). // CSI Ps x Select Attribute Change Extent (DECSACE). // CSI Pc; Pt; Pl; Pb; Pr$ x // case 'x': // if (this.postfix === '$') { // this.fillRectangle(this.params); // } else { // this.requestParameters(this.params); // //this.__(this.params); // } // break; // CSI Ps ; Pu ' z // CSI Pt; Pl; Pb; Pr$ z // case 'z': // if (this.postfix === '\'') { // this.enableLocatorReporting(this.params); // } else if (this.postfix === '$') { // this.eraseRectangle(this.params); // } // break; // CSI Pm ' { // CSI Pt; Pl; Pb; Pr$ { // case '{': // if (this.postfix === '\'') { // this.setLocatorEvents(this.params); // } else if (this.postfix === '$') { // this.selectiveEraseRectangle(this.params); // } // break; // CSI Ps ' | // case '|': // if (this.postfix === '\'') { // this.requestLocatorPosition(this.params); // } // break; // CSI P m SP } // Insert P s Column(s) (default = 1) (DECIC), VT420 and up. // case '}': // if (this.postfix === ' ') { // this.insertColumns(this.params); // } // break; // CSI P m SP ~ // Delete P s Column(s) (default = 1) (DECDC), VT420 and up // case '~': // if (this.postfix === ' ') { // this.deleteColumns(this.params); // } // break; default: this.error('Unknown CSI code: %s.', ch); break; } this.prefix = ''; this.postfix = ''; break; case dcs: if ((this.lch === '\x1b' && ch === '\\') || ch === '\x07') { // Workarounds: if (this.prefix === 'tmux;\x1b') { // `DCS tmux; Pt ST` may contain a Pt with an ST // XXX Does tmux work this way? // if (this.lch === '\x1b' & data[i + 1] === '\x1b' && data[i + 2] === '\\') { // this.currentParam += ch; // continue; // } // Tmux only accepts ST, not BEL: if (ch === '\x07') { this.currentParam += ch; continue; } } if (this.lch === '\x1b') { if (typeof this.currentParam === 'string') { this.currentParam = this.currentParam.slice(0, -1); } else if (typeof this.currentParam == 'number') { this.currentParam = (this.currentParam - ('\x1b'.charCodeAt(0) - 48)) / 10; } } this.params.push(this.currentParam); var pt = this.params[this.params.length - 1]; switch (this.prefix) { // User-Defined Keys (DECUDK). // DCS Ps; Ps| Pt ST case UDK: this.emit('udk', { clearAll: this.params[0] === 0, eraseBelow: this.params[0] === 1, lockKeys: this.params[1] === 0, dontLockKeys: this.params[1] === 1, keyList: (this.params[2] + '').split(';').map(function(part) { part = part.split('/'); return { keyCode: part[0], hexKeyValue: part[1] }; }) }); break; // Request Status String (DECRQSS). // DCS $ q Pt ST // test: echo -e '\eP$q"p\e\\' case '$q': var valid = 0; switch (pt) { // DECSCA // CSI Ps " q case '"q': pt = '0"q'; valid = 1; break; // DECSCL // CSI Ps ; Ps " p case '"p': pt = '61;0"p'; valid = 1; break; // DECSTBM // CSI Ps ; Ps r case 'r': pt = '' + (this.scrollTop + 1) + ';' + (this.scrollBottom + 1) + 'r'; valid = 1; break; // SGR // CSI Pm m case 'm': // TODO: Parse this.curAttr here. // pt = '0m'; // valid = 1; valid = 0; // Not implemented. break; default: this.error('Unknown DCS Pt: %s.', pt); valid = 0; // unimplemented break; } this.send('\x1bP' + valid + '$r' + pt + '\x1b\\'); break; // Set Termcap/Terminfo Data (xterm, experimental). // DCS + p Pt ST case '+p': this.emit('set terminfo', { name: this.params[0] }); break; // Request Termcap/Terminfo String (xterm, experimental) // Regular xterm does not even respond to this sequence. // This can cause a small glitch in vim. // DCS + q Pt ST // test: echo -ne '\eP+q6b64\e\\' case '+q': var valid = false; this.send('\x1bP' + +valid + '+r' + pt + '\x1b\\'); break; // Implement tmux sequence forwarding is // someone uses term.js for a multiplexer. // DCS tmux; ESC Pt ST case 'tmux;\x1b': this.emit('passthrough', pt); break; default: this.error('Unknown DCS prefix: %s.', pt); break; } this.currentParam = 0; this.prefix = ''; this.state = normal; } else { this.currentParam += ch; if (!this.prefix) { if (/^\d*;\d*\|/.test(this.currentParam)) { this.prefix = UDK; this.params = this.currentParam.split(/[;|]/).map(function(n) { if (!n.length) return 0; return +n; }).slice(0, -1); this.currentParam = ''; } else if (/^[$+][a-zA-Z]/.test(this.currentParam) || /^\w+;\x1b/.test(this.currentParam)) { this.prefix = this.currentParam; this.currentParam = ''; } } } break; case ignore: // For PM and APC. if ((this.lch === '\x1b' && ch === '\\') || ch === '\x07') { this.state = normal; } break; } } this.updateRange(this.y); this.refresh(this.refreshStart, this.refreshEnd); return true; }; Terminal.prototype.writeln = function(data) { return this.write(data + '\r\n'); }; Terminal.prototype.end = function(data) { var ret = true; if (data) { ret = this.write(data); } this.destroySoon(); return ret; }; Terminal.prototype.resume = function() { ; }; Terminal.prototype.pause = function() { ; }; // Key Resources: // https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent Terminal.prototype.keyDown = function(ev) { var self = this , key; switch (ev.keyCode) { // backspace case 8: if (ev.altKey) { key = '\x17'; break; } if (ev.shiftKey) { key = '\x08'; // ^H break; } key = '\x7f'; // ^? break; // tab case 9: if (ev.shiftKey) { key = '\x1b[Z'; break; } key = '\t'; break; // return/enter case 13: key = '\r'; break; // escape case 27: key = '\x1b'; break; // left-arrow case 37: if (this.applicationCursor) { key = '\x1bOD'; // SS3 as ^[O for 7-bit //key = '\x8fD'; // SS3 as 0x8f for 8-bit break; } if (ev.ctrlKey) { key = '\x1b[5D'; break; } key = '\x1b[D'; break; // right-arrow case 39: if (this.applicationCursor) { key = '\x1bOC'; break; } if (ev.ctrlKey) { key = '\x1b[5C'; break; } key = '\x1b[C'; break; // up-arrow case 38: if (this.applicationCursor) { key = '\x1bOA'; break; } if (ev.ctrlKey) { this.scrollDisp(-1); return cancel(ev); } else { key = '\x1b[A'; } break; // down-arrow case 40: if (this.applicationCursor) { key = '\x1bOB'; break; } if (ev.ctrlKey) { this.scrollDisp(1); return cancel(ev); } else { key = '\x1b[B'; } break; // delete case 46: key = '\x1b[3~'; break; // insert case 45: key = '\x1b[2~'; break; // home case 36: if (this.applicationKeypad) { key = '\x1bOH'; break; } key = '\x1bOH'; break; // end case 35: if (this.applicationKeypad) { key = '\x1bOF'; break; } key = '\x1bOF'; break; // page up case 33: if (ev.shiftKey) { this.scrollDisp(-(this.rows - 1)); return cancel(ev); } else { key = '\x1b[5~'; } break; // page down case 34: if (ev.shiftKey) { this.scrollDisp(this.rows - 1); return cancel(ev); } else { key = '\x1b[6~'; } break; // F1 case 112: key = '\x1bOP'; break; // F2 case 113: key = '\x1bOQ'; break; // F3 case 114: key = '\x1bOR'; break; // F4 case 115: key = '\x1bOS'; break; // F5 case 116: key = '\x1b[15~'; break; // F6 case 117: key = '\x1b[17~'; break; // F7 case 118: key = '\x1b[18~'; break; // F8 case 119: key = '\x1b[19~'; break; // F9 case 120: key = '\x1b[20~'; break; // F10 case 121: key = '\x1b[21~'; break; // F11 case 122: key = '\x1b[23~'; break; // F12 case 123: key = '\x1b[24~'; break; default: // a-z and space if (ev.ctrlKey) { if (ev.keyCode >= 65 && ev.keyCode <= 90) { // Ctrl-A if (this.screenKeys) { if (!this.prefixMode && !this.selectMode && ev.keyCode === 65) { this.enterPrefix(); return cancel(ev); } } // Ctrl-V if (this.prefixMode && ev.keyCode === 86) { this.leavePrefix(); return; } // Ctrl-C if ((this.prefixMode || this.selectMode) && ev.keyCode === 67) { if (this.visualMode) { setTimeout(function() { self.leaveVisual(); }, 1); } return; } key = String.fromCharCode(ev.keyCode - 64); } else if (ev.keyCode === 32) { // NUL key = String.fromCharCode(0); } else if (ev.keyCode >= 51 && ev.keyCode <= 55) { // escape, file sep, group sep, record sep, unit sep key = String.fromCharCode(ev.keyCode - 51 + 27); } else if (ev.keyCode === 56) { // delete key = String.fromCharCode(127); } else if (ev.keyCode === 219) { // ^[ - escape key = String.fromCharCode(27); } else if (ev.keyCode === 221) { // ^] - group sep key = String.fromCharCode(29); } } else if (ev.altKey) { if (ev.keyCode >= 65 && ev.keyCode <= 90) { key = '\x1b' + String.fromCharCode(ev.keyCode + 32); } else if (ev.keyCode === 192) { key = '\x1b`'; } else if (ev.keyCode >= 48 && ev.keyCode <= 57) { key = '\x1b' + (ev.keyCode - 48); } } break; } if (!key) return true; if (this.prefixMode) { this.leavePrefix(); return cancel(ev); } if (this.selectMode) { this.keySelect(ev, key); return cancel(ev); } this.emit('keydown', ev); this.emit('key', key, ev); this.showCursor(); this.handler(key); return cancel(ev); }; Terminal.prototype.setgLevel = function(g) { this.glevel = g; this.charset = this.charsets[g]; }; Terminal.prototype.setgCharset = function(g, charset) { this.charsets[g] = charset; if (this.glevel === g) { this.charset = charset; } }; Terminal.prototype.keyPress = function(ev) { var key; if (this._textarea) { return; } cancel(ev); if (ev.charCode) { key = ev.charCode; } else if (ev.which == null) { key = ev.keyCode; } else if (ev.which !== 0 && ev.charCode !== 0) { key = ev.which; } else { return false; } if (!key || ev.ctrlKey || ev.altKey || ev.metaKey) return false; key = String.fromCharCode(key); if (this.prefixMode) { this.leavePrefix(); this.keyPrefix(ev, key); return false; } if (this.selectMode) { this.keySelect(ev, key); return false; } this.emit('keypress', key, ev); this.emit('key', key, ev); this.showCursor(); this.handler(key); return false; }; Terminal.prototype.send = function(data) { var self = this; if (!this.queue) { setTimeout(function() { self.handler(self.queue); self.queue = ''; }, 1); } this.queue += data; }; Terminal.prototype.bell = function() { this.emit('bell'); if (!this.visualBell) return; var self = this; this.element.style.borderColor = 'white'; setTimeout(function() { self.element.style.borderColor = ''; }, 10); if (this.popOnBell) this.focus(); }; Terminal.prototype.log = function() { if (!this.debug) return; if (!this.context.console || !this.context.console.log) return; var args = Array.prototype.slice.call(arguments); this.context.console.log.apply(this.context.console, args); }; Terminal.prototype.error = function() { if (!this.debug) return; if (!this.context.console || !this.context.console.error) return; var args = Array.prototype.slice.call(arguments); this.context.console.error.apply(this.context.console, args); }; Terminal.prototype.resize = function(x, y) { var line , el , i , j , ch; if (x < 1) x = 1; if (y < 1) y = 1; // resize cols j = this.cols; if (j < x) { ch = [this.defAttr, ' ']; // does xterm use the default attr? i = this.lines.length; while (i--) { while (this.lines[i].length < x) { this.lines[i].push(ch); } } } else if (j > x) { i = this.lines.length; while (i--) { while (this.lines[i].length > x) { this.lines[i].pop(); } } } this.setupStops(j); this.cols = x; this.columns = x; // resize rows j = this.rows; if (j < y) { el = this.element; while (j++ < y) { if (this.lines.length < y + this.ybase) { this.lines.push(this.blankLine()); } if (this.children.length < y) { line = this.document.createElement('div'); el.appendChild(line); this.children.push(line); } } } else if (j > y) { while (j-- > y) { if (this.lines.length > y + this.ybase) { this.lines.pop(); } if (this.children.length > y) { el = this.children.pop(); if (!el) continue; el.parentNode.removeChild(el); } } } this.rows = y; // make sure the cursor stays on screen if (this.y >= y) this.y = y - 1; if (this.x >= x) this.x = x - 1; this.scrollTop = 0; this.scrollBottom = y - 1; this.refresh(0, this.rows - 1); // it's a real nightmare trying // to resize the original // screen buffer. just set it // to null for now. this.normal = null; // Act as though we are a node TTY stream: this.emit('resize'); }; Terminal.prototype.updateRange = function(y) { if (y < this.refreshStart) this.refreshStart = y; if (y > this.refreshEnd) this.refreshEnd = y; // if (y > this.refreshEnd) { // this.refreshEnd = y; // if (y > this.rows - 1) { // this.refreshEnd = this.rows - 1; // } // } }; Terminal.prototype.maxRange = function() { this.refreshStart = 0; this.refreshEnd = this.rows - 1; }; Terminal.prototype.setupStops = function(i) { if (i != null) { if (!this.tabs[i]) { i = this.prevStop(i); } } else { this.tabs = {}; i = 0; } for (; i < this.cols; i += 8) { this.tabs[i] = true; } }; Terminal.prototype.prevStop = function(x) { if (x == null) x = this.x; while (!this.tabs[--x] && x > 0); return x >= this.cols ? this.cols - 1 : x < 0 ? 0 : x; }; Terminal.prototype.nextStop = function(x) { if (x == null) x = this.x; while (!this.tabs[++x] && x < this.cols); return x >= this.cols ? this.cols - 1 : x < 0 ? 0 : x; }; // back_color_erase feature for xterm. Terminal.prototype.eraseAttr = function() { // if (this.is('screen')) return this.defAttr; return (this.defAttr & ~0x1ff) | (this.curAttr & 0x1ff); }; Terminal.prototype.eraseRight = function(x, y) { var line = this.lines[this.ybase + y] , ch = [this.eraseAttr(), ' ']; // xterm for (; x < this.cols; x++) { line[x] = ch; } this.updateRange(y); }; Terminal.prototype.eraseLeft = function(x, y) { var line = this.lines[this.ybase + y] , ch = [this.eraseAttr(), ' ']; // xterm x++; while (x--) line[x] = ch; this.updateRange(y); }; Terminal.prototype.eraseLine = function(y) { this.eraseRight(0, y); }; Terminal.prototype.blankLine = function(cur) { var attr = cur ? this.eraseAttr() : this.defAttr; var ch = [attr, ' '] , line = [] , i = 0; for (; i < this.cols; i++) { line[i] = ch; } return line; }; Terminal.prototype.ch = function(cur) { return cur ? [this.eraseAttr(), ' '] : [this.defAttr, ' ']; }; Terminal.prototype.is = function(term) { var name = this.termName; return (name + '').indexOf(term) === 0; }; Terminal.prototype.handler = function(data) { this.emit('data', data); }; Terminal.prototype.handleTitle = function(title) { this.emit('title', title); }; /** * ESC */ // ESC D Index (IND is 0x84). Terminal.prototype.index = function() { this.y++; if (this.y > this.scrollBottom) { this.y--; this.scroll(); } this.state = normal; }; // ESC M Reverse Index (RI is 0x8d). Terminal.prototype.reverseIndex = function() { var j; this.y--; if (this.y < this.scrollTop) { this.y++; // possibly move the code below to term.reverseScroll(); // test: echo -ne '\e[1;1H\e[44m\eM\e[0m' // blankLine(true) is xterm/linux behavior this.lines.splice(this.y + this.ybase, 0, this.blankLine(true)); j = this.rows - 1 - this.scrollBottom; this.lines.splice(this.rows - 1 + this.ybase - j + 1, 1); // this.maxRange(); this.updateRange(this.scrollTop); this.updateRange(this.scrollBottom); } this.state = normal; }; // ESC c Full Reset (RIS). Terminal.prototype.reset = function() { this.options.rows = this.rows; this.options.cols = this.cols; Terminal.call(this, this.options); this.refresh(0, this.rows - 1); }; // ESC H Tab Set (HTS is 0x88). Terminal.prototype.tabSet = function() { this.tabs[this.x] = true; this.state = normal; }; /** * CSI */ // CSI Ps A // Cursor Up Ps Times (default = 1) (CUU). Terminal.prototype.cursorUp = function(params) { var param = params[0]; if (param < 1) param = 1; this.y -= param; if (this.y < 0) this.y = 0; }; // CSI Ps B // Cursor Down Ps Times (default = 1) (CUD). Terminal.prototype.cursorDown = function(params) { var param = params[0]; if (param < 1) param = 1; this.y += param; if (this.y >= this.rows) { this.y = this.rows - 1; } }; // CSI Ps C // Cursor Forward Ps Times (default = 1) (CUF). Terminal.prototype.cursorForward = function(params) { var param = params[0]; if (param < 1) param = 1; this.x += param; if (this.x >= this.cols) { this.x = this.cols - 1; } }; // CSI Ps D // Cursor Backward Ps Times (default = 1) (CUB). Terminal.prototype.cursorBackward = function(params) { var param = params[0]; if (param < 1) param = 1; this.x -= param; if (this.x < 0) this.x = 0; }; // CSI Ps ; Ps H // Cursor Position [row;column] (default = [1,1]) (CUP). Terminal.prototype.cursorPos = function(params) { var row, col; row = params[0] - 1; if (params.length >= 2) { col = params[1] - 1; } else { col = 0; } if (row < 0) { row = 0; } else if (row >= this.rows) { row = this.rows - 1; } if (col < 0) { col = 0; } else if (col >= this.cols) { col = this.cols - 1; } this.x = col; this.y = row; }; // CSI Ps J Erase in Display (ED). // Ps = 0 -> Erase Below (default). // Ps = 1 -> Erase Above. // Ps = 2 -> Erase All. // Ps = 3 -> Erase Saved Lines (xterm). // CSI ? Ps J // Erase in Display (DECSED). // Ps = 0 -> Selective Erase Below (default). // Ps = 1 -> Selective Erase Above. // Ps = 2 -> Selective Erase All. Terminal.prototype.eraseInDisplay = function(params) { var j; switch (params[0]) { case 0: this.eraseRight(this.x, this.y); j = this.y + 1; for (; j < this.rows; j++) { this.eraseLine(j); } break; case 1: this.eraseLeft(this.x, this.y); j = this.y; while (j--) { this.eraseLine(j); } break; case 2: j = this.rows; while (j--) this.eraseLine(j); break; case 3: ; // no saved lines break; } }; // CSI Ps K Erase in Line (EL). // Ps = 0 -> Erase to Right (default). // Ps = 1 -> Erase to Left. // Ps = 2 -> Erase All. // CSI ? Ps K // Erase in Line (DECSEL). // Ps = 0 -> Selective Erase to Right (default). // Ps = 1 -> Selective Erase to Left. // Ps = 2 -> Selective Erase All. Terminal.prototype.eraseInLine = function(params) { switch (params[0]) { case 0: this.eraseRight(this.x, this.y); break; case 1: this.eraseLeft(this.x, this.y); break; case 2: this.eraseLine(this.y); break; } }; // CSI Pm m Character Attributes (SGR). // Ps = 0 -> Normal (default). // Ps = 1 -> Bold. // Ps = 4 -> Underlined. // Ps = 5 -> Blink (appears as Bold). // Ps = 7 -> Inverse. // Ps = 8 -> Invisible, i.e., hidden (VT300). // Ps = 2 2 -> Normal (neither bold nor faint). // Ps = 2 4 -> Not underlined. // Ps = 2 5 -> Steady (not blinking). // Ps = 2 7 -> Positive (not inverse). // Ps = 2 8 -> Visible, i.e., not hidden (VT300). // Ps = 3 0 -> Set foreground color to Black. // Ps = 3 1 -> Set foreground color to Red. // Ps = 3 2 -> Set foreground color to Green. // Ps = 3 3 -> Set foreground color to Yellow. // Ps = 3 4 -> Set foreground color to Blue. // Ps = 3 5 -> Set foreground color to Magenta. // Ps = 3 6 -> Set foreground color to Cyan. // Ps = 3 7 -> Set foreground color to White. // Ps = 3 9 -> Set foreground color to default (original). // Ps = 4 0 -> Set background color to Black. // Ps = 4 1 -> Set background color to Red. // Ps = 4 2 -> Set background color to Green. // Ps = 4 3 -> Set background color to Yellow. // Ps = 4 4 -> Set background color to Blue. // Ps = 4 5 -> Set background color to Magenta. // Ps = 4 6 -> Set background color to Cyan. // Ps = 4 7 -> Set background color to White. // Ps = 4 9 -> Set background color to default (original). // If 16-color support is compiled, the following apply. Assume // that xterm's resources are set so that the ISO color codes are // the first 8 of a set of 16. Then the aixterm colors are the // bright versions of the ISO colors: // Ps = 9 0 -> Set foreground color to Black. // Ps = 9 1 -> Set foreground color to Red. // Ps = 9 2 -> Set foreground color to Green. // Ps = 9 3 -> Set foreground color to Yellow. // Ps = 9 4 -> Set foreground color to Blue. // Ps = 9 5 -> Set foreground color to Magenta. // Ps = 9 6 -> Set foreground color to Cyan. // Ps = 9 7 -> Set foreground color to White. // Ps = 1 0 0 -> Set background color to Black. // Ps = 1 0 1 -> Set background color to Red. // Ps = 1 0 2 -> Set background color to Green. // Ps = 1 0 3 -> Set background color to Yellow. // Ps = 1 0 4 -> Set background color to Blue. // Ps = 1 0 5 -> Set background color to Magenta. // Ps = 1 0 6 -> Set background color to Cyan. // Ps = 1 0 7 -> Set background color to White. // If xterm is compiled with the 16-color support disabled, it // supports the following, from rxvt: // Ps = 1 0 0 -> Set foreground and background color to // default. // If 88- or 256-color support is compiled, the following apply. // Ps = 3 8 ; 5 ; Ps -> Set foreground color to the second // Ps. // Ps = 4 8 ; 5 ; Ps -> Set background color to the second // Ps. Terminal.prototype.charAttributes = function(params) { // Optimize a single SGR0. if (params.length === 1 && params[0] === 0) { this.curAttr = this.defAttr; return; } var l = params.length , i = 0 , flags = this.curAttr >> 18 , fg = (this.curAttr >> 9) & 0x1ff , bg = this.curAttr & 0x1ff , p; for (; i < l; i++) { p = params[i]; if (p >= 30 && p <= 37) { // fg color 8 fg = p - 30; } else if (p >= 40 && p <= 47) { // bg color 8 bg = p - 40; } else if (p >= 90 && p <= 97) { // fg color 16 p += 8; fg = p - 90; } else if (p >= 100 && p <= 107) { // bg color 16 p += 8; bg = p - 100; } else if (p === 0) { // default flags = this.defAttr >> 18; fg = (this.defAttr >> 9) & 0x1ff; bg = this.defAttr & 0x1ff; // flags = 0; // fg = 0x1ff; // bg = 0x1ff; } else if (p === 1) { // bold text flags |= 1; } else if (p === 4) { // underlined text flags |= 2; } else if (p === 5) { // blink flags |= 4; } else if (p === 7) { // inverse and positive // test with: echo -e '\e[31m\e[42mhello\e[7mworld\e[27mhi\e[m' flags |= 8; } else if (p === 8) { // invisible flags |= 16; } else if (p === 22) { // not bold flags &= ~1; } else if (p === 24) { // not underlined flags &= ~2; } else if (p === 25) { // not blink flags &= ~4; } else if (p === 27) { // not inverse flags &= ~8; } else if (p === 28) { // not invisible flags &= ~16; } else if (p === 39) { // reset fg fg = (this.defAttr >> 9) & 0x1ff; } else if (p === 49) { // reset bg bg = this.defAttr & 0x1ff; } else if (p === 38) { // fg color 256 if (params[i + 1] === 2) { i += 2; fg = matchColor( params[i] & 0xff, params[i + 1] & 0xff, params[i + 2] & 0xff); if (fg === -1) fg = 0x1ff; i += 2; } else if (params[i + 1] === 5) { i += 2; p = params[i] & 0xff; fg = p; } } else if (p === 48) { // bg color 256 if (params[i + 1] === 2) { i += 2; bg = matchColor( params[i] & 0xff, params[i + 1] & 0xff, params[i + 2] & 0xff); if (bg === -1) bg = 0x1ff; i += 2; } else if (params[i + 1] === 5) { i += 2; p = params[i] & 0xff; bg = p; } } else if (p === 100) { // reset fg/bg fg = (this.defAttr >> 9) & 0x1ff; bg = this.defAttr & 0x1ff; } else { this.error('Unknown SGR attribute: %d.', p); } } this.curAttr = (flags << 18) | (fg << 9) | bg; }; // CSI Ps n Device Status Report (DSR). // Ps = 5 -> Status Report. Result (``OK'') is // CSI 0 n // Ps = 6 -> Report Cursor Position (CPR) [row;column]. // Result is // CSI r ; c R // CSI ? Ps n // Device Status Report (DSR, DEC-specific). // Ps = 6 -> Report Cursor Position (CPR) [row;column] as CSI // ? r ; c R (assumes page is zero). // Ps = 1 5 -> Report Printer status as CSI ? 1 0 n (ready). // or CSI ? 1 1 n (not ready). // Ps = 2 5 -> Report UDK status as CSI ? 2 0 n (unlocked) // or CSI ? 2 1 n (locked). // Ps = 2 6 -> Report Keyboard status as // CSI ? 2 7 ; 1 ; 0 ; 0 n (North American). // The last two parameters apply to VT400 & up, and denote key- // board ready and LK01 respectively. // Ps = 5 3 -> Report Locator status as // CSI ? 5 3 n Locator available, if compiled-in, or // CSI ? 5 0 n No Locator, if not. Terminal.prototype.deviceStatus = function(params) { if (!this.prefix) { switch (params[0]) { case 5: // status report this.send('\x1b[0n'); break; case 6: // cursor position this.send('\x1b[' + (this.y + 1) + ';' + (this.x + 1) + 'R'); break; } } else if (this.prefix === '?') { // modern xterm doesnt seem to // respond to any of these except ?6, 6, and 5 switch (params[0]) { case 6: // cursor position this.send('\x1b[?' + (this.y + 1) + ';' + (this.x + 1) + 'R'); break; case 15: // no printer // this.send('\x1b[?11n'); break; case 25: // dont support user defined keys // this.send('\x1b[?21n'); break; case 26: // north american keyboard // this.send('\x1b[?27;1;0;0n'); break; case 53: // no dec locator/mouse // this.send('\x1b[?50n'); break; } } }; /** * Additions */ // CSI Ps @ // Insert Ps (Blank) Character(s) (default = 1) (ICH). Terminal.prototype.insertChars = function(params) { var param, row, j, ch; param = params[0]; if (param < 1) param = 1; row = this.y + this.ybase; j = this.x; ch = [this.eraseAttr(), ' ']; // xterm while (param-- && j < this.cols) { this.lines[row].splice(j++, 0, ch); this.lines[row].pop(); } }; // CSI Ps E // Cursor Next Line Ps Times (default = 1) (CNL). // same as CSI Ps B ? Terminal.prototype.cursorNextLine = function(params) { var param = params[0]; if (param < 1) param = 1; this.y += param; if (this.y >= this.rows) { this.y = this.rows - 1; } this.x = 0; }; // CSI Ps F // Cursor Preceding Line Ps Times (default = 1) (CNL). // reuse CSI Ps A ? Terminal.prototype.cursorPrecedingLine = function(params) { var param = params[0]; if (param < 1) param = 1; this.y -= param; if (this.y < 0) this.y = 0; this.x = 0; }; // CSI Ps G // Cursor Character Absolute [column] (default = [row,1]) (CHA). Terminal.prototype.cursorCharAbsolute = function(params) { var param = params[0]; if (param < 1) param = 1; this.x = param - 1; }; // CSI Ps L // Insert Ps Line(s) (default = 1) (IL). Terminal.prototype.insertLines = function(params) { var param, row, j; param = params[0]; if (param < 1) param = 1; row = this.y + this.ybase; j = this.rows - 1 - this.scrollBottom; j = this.rows - 1 + this.ybase - j + 1; while (param--) { // test: echo -e '\e[44m\e[1L\e[0m' // blankLine(true) - xterm/linux behavior this.lines.splice(row, 0, this.blankLine(true)); this.lines.splice(j, 1); } // this.maxRange(); this.updateRange(this.y); this.updateRange(this.scrollBottom); }; // CSI Ps M // Delete Ps Line(s) (default = 1) (DL). Terminal.prototype.deleteLines = function(params) { var param, row, j; param = params[0]; if (param < 1) param = 1; row = this.y + this.ybase; j = this.rows - 1 - this.scrollBottom; j = this.rows - 1 + this.ybase - j; while (param--) { // test: echo -e '\e[44m\e[1M\e[0m' // blankLine(true) - xterm/linux behavior this.lines.splice(j + 1, 0, this.blankLine(true)); this.lines.splice(row, 1); } // this.maxRange(); this.updateRange(this.y); this.updateRange(this.scrollBottom); }; // CSI Ps P // Delete Ps Character(s) (default = 1) (DCH). Terminal.prototype.deleteChars = function(params) { var param, row, ch; param = params[0]; if (param < 1) param = 1; row = this.y + this.ybase; ch = [this.eraseAttr(), ' ']; // xterm while (param--) { this.lines[row].splice(this.x, 1); this.lines[row].push(ch); } }; // CSI Ps X // Erase Ps Character(s) (default = 1) (ECH). Terminal.prototype.eraseChars = function(params) { var param, row, j, ch; param = params[0]; if (param < 1) param = 1; row = this.y + this.ybase; j = this.x; ch = [this.eraseAttr(), ' ']; // xterm while (param-- && j < this.cols) { this.lines[row][j++] = ch; } }; // CSI Pm ` Character Position Absolute // [column] (default = [row,1]) (HPA). Terminal.prototype.charPosAbsolute = function(params) { var param = params[0]; if (param < 1) param = 1; this.x = param - 1; if (this.x >= this.cols) { this.x = this.cols - 1; } }; // 141 61 a * HPR - // Horizontal Position Relative // reuse CSI Ps C ? Terminal.prototype.HPositionRelative = function(params) { var param = params[0]; if (param < 1) param = 1; this.x += param; if (this.x >= this.cols) { this.x = this.cols - 1; } }; // CSI Ps c Send Device Attributes (Primary DA). // Ps = 0 or omitted -> request attributes from applications. The // response depends on the decTerminalID resource setting. // -> CSI ? 1 ; 2 c (``VT100 with Advanced Video Option'') // -> CSI ? 1 ; 0 c (``VT101 with No Options'') // -> CSI ? 6 c (``VT102'') // -> CSI ? 6 0 ; 1 ; 2 ; 6 ; 8 ; 9 ; 1 5 ; c (``VT220'') // The VT100-style response parameters do not mean anything by // themselves. VT220 parameters do, telling the host what fea- // tures the applications supports: // Ps = 1 -> 132-columns. // Ps = 2 -> Printer. // Ps = 6 -> Selective erase. // Ps = 8 -> User-defined keys. // Ps = 9 -> National replacement character sets. // Ps = 1 5 -> Technical characters. // Ps = 2 2 -> ANSI color, e.g., VT525. // Ps = 2 9 -> ANSI text locator (i.e., DEC Locator mode). // CSI > Ps c // Send Device Attributes (Secondary DA). // Ps = 0 or omitted -> request the applications's identification // code. The response depends on the decTerminalID resource set- // ting. It should apply only to VT220 and up, but xterm extends // this to VT100. // -> CSI > Pp ; Pv ; Pc c // where Pp denotes the applications type // Pp = 0 -> ``VT100''. // Pp = 1 -> ``VT220''. // and Pv is the firmware version (for xterm, this was originally // the XFree86 patch number, starting with 95). In a DEC termi- // nal, Pc indicates the ROM cartridge registration number and is // always zero. // More information: // xterm/charproc.c - line 2012, for more information. // vim responds with ^[[?0c or ^[[?1c after the applications's response (?) Terminal.prototype.sendDeviceAttributes = function(params) { if (params[0] > 0) return; if (!this.prefix) { if (this.is('xterm') || this.is('rxvt-unicode') || this.is('screen')) { this.send('\x1b[?1;2c'); } else if (this.is('linux')) { this.send('\x1b[?6c'); } } else if (this.prefix === '>') { // xterm and urxvt // seem to spit this // out around ~370 times (?). if (this.is('xterm')) { this.send('\x1b[>0;276;0c'); } else if (this.is('rxvt-unicode')) { this.send('\x1b[>85;95;0c'); } else if (this.is('linux')) { // not supported by linux console. // linux console echoes parameters. this.send(params[0] + 'c'); } else if (this.is('screen')) { this.send('\x1b[>83;40003;0c'); } } }; // CSI Pm d // Line Position Absolute [row] (default = [1,column]) (VPA). Terminal.prototype.linePosAbsolute = function(params) { var param = params[0]; if (param < 1) param = 1; this.y = param - 1; if (this.y >= this.rows) { this.y = this.rows - 1; } }; // 145 65 e * VPR - Vertical Position Relative // reuse CSI Ps B ? Terminal.prototype.VPositionRelative = function(params) { var param = params[0]; if (param < 1) param = 1; this.y += param; if (this.y >= this.rows) { this.y = this.rows - 1; } }; // CSI Ps ; Ps f // Horizontal and Vertical Position [row;column] (default = // [1,1]) (HVP). Terminal.prototype.HVPosition = function(params) { if (params[0] < 1) params[0] = 1; if (params[1] < 1) params[1] = 1; this.y = params[0] - 1; if (this.y >= this.rows) { this.y = this.rows - 1; } this.x = params[1] - 1; if (this.x >= this.cols) { this.x = this.cols - 1; } }; // CSI Pm h Set Mode (SM). // Ps = 2 -> Keyboard Action Mode (AM). // Ps = 4 -> Insert Mode (IRM). // Ps = 1 2 -> Send/receive (SRM). // Ps = 2 0 -> Automatic Newline (LNM). // CSI ? Pm h // DEC Private Mode Set (DECSET). // Ps = 1 -> Application Cursor Keys (DECCKM). // Ps = 2 -> Designate USASCII for character sets G0-G3 // (DECANM), and set VT100 mode. // Ps = 3 -> 132 Column Mode (DECCOLM). // Ps = 4 -> Smooth (Slow) Scroll (DECSCLM). // Ps = 5 -> Reverse Video (DECSCNM). // Ps = 6 -> Origin Mode (DECOM). // Ps = 7 -> Wraparound Mode (DECAWM). // Ps = 8 -> Auto-repeat Keys (DECARM). // Ps = 9 -> Send Mouse X & Y on button press. See the sec- // tion Mouse Tracking. // Ps = 1 0 -> Show toolbar (rxvt). // Ps = 1 2 -> Start Blinking Cursor (att610). // Ps = 1 8 -> Print form feed (DECPFF). // Ps = 1 9 -> Set print extent to full screen (DECPEX). // Ps = 2 5 -> Show Cursor (DECTCEM). // Ps = 3 0 -> Show scrollbar (rxvt). // Ps = 3 5 -> Enable font-shifting functions (rxvt). // Ps = 3 8 -> Enter Tektronix Mode (DECTEK). // Ps = 4 0 -> Allow 80 -> 132 Mode. // Ps = 4 1 -> more(1) fix (see curses resource). // Ps = 4 2 -> Enable Nation Replacement Character sets (DECN- // RCM). // Ps = 4 4 -> Turn On Margin Bell. // Ps = 4 5 -> Reverse-wraparound Mode. // Ps = 4 6 -> Start Logging. This is normally disabled by a // compile-time option. // Ps = 4 7 -> Use Alternate Screen Buffer. (This may be dis- // abled by the titeInhibit resource). // Ps = 6 6 -> Application keypad (DECNKM). // Ps = 6 7 -> Backarrow key sends backspace (DECBKM). // Ps = 1 0 0 0 -> Send Mouse X & Y on button press and // release. See the section Mouse Tracking. // Ps = 1 0 0 1 -> Use Hilite Mouse Tracking. // Ps = 1 0 0 2 -> Use Cell Motion Mouse Tracking. // Ps = 1 0 0 3 -> Use All Motion Mouse Tracking. // Ps = 1 0 0 4 -> Send FocusIn/FocusOut events. // Ps = 1 0 0 5 -> Enable Extended Mouse Mode. // Ps = 1 0 1 0 -> Scroll to bottom on tty output (rxvt). // Ps = 1 0 1 1 -> Scroll to bottom on key press (rxvt). // Ps = 1 0 3 4 -> Interpret "meta" key, sets eighth bit. // (enables the eightBitInput resource). // Ps = 1 0 3 5 -> Enable special modifiers for Alt and Num- // Lock keys. (This enables the numLock resource). // Ps = 1 0 3 6 -> Send ESC when Meta modifies a key. (This // enables the metaSendsEscape resource). // Ps = 1 0 3 7 -> Send DEL from the editing-keypad Delete // key. // Ps = 1 0 3 9 -> Send ESC when Alt modifies a key. (This // enables the altSendsEscape resource). // Ps = 1 0 4 0 -> Keep selection even if not highlighted. // (This enables the keepSelection resource). // Ps = 1 0 4 1 -> Use the CLIPBOARD selection. (This enables // the selectToClipboard resource). // Ps = 1 0 4 2 -> Enable Urgency window manager hint when // Control-G is received. (This enables the bellIsUrgent // resource). // Ps = 1 0 4 3 -> Enable raising of the window when Control-G // is received. (enables the popOnBell resource). // Ps = 1 0 4 7 -> Use Alternate Screen Buffer. (This may be // disabled by the titeInhibit resource). // Ps = 1 0 4 8 -> Save cursor as in DECSC. (This may be dis- // abled by the titeInhibit resource). // Ps = 1 0 4 9 -> Save cursor as in DECSC and use Alternate // Screen Buffer, clearing it first. (This may be disabled by // the titeInhibit resource). This combines the effects of the 1 // 0 4 7 and 1 0 4 8 modes. Use this with terminfo-based // applications rather than the 4 7 mode. // Ps = 1 0 5 0 -> Set terminfo/termcap function-key mode. // Ps = 1 0 5 1 -> Set Sun function-key mode. // Ps = 1 0 5 2 -> Set HP function-key mode. // Ps = 1 0 5 3 -> Set SCO function-key mode. // Ps = 1 0 6 0 -> Set legacy keyboard emulation (X11R6). // Ps = 1 0 6 1 -> Set VT220 keyboard emulation. // Ps = 2 0 0 4 -> Set bracketed paste mode. // Modes: // http://vt100.net/docs/vt220-rm/chapter4.html Terminal.prototype.setMode = function(params) { if (typeof params === 'object') { var l = params.length , i = 0; for (; i < l; i++) { this.setMode(params[i]); } return; } if (!this.prefix) { switch (params) { case 4: this.insertMode = true; break; case 20: //this.convertEol = true; break; } } else if (this.prefix === '?') { switch (params) { case 1: this.applicationCursor = true; break; case 2: this.setgCharset(0, Terminal.charsets.US); this.setgCharset(1, Terminal.charsets.US); this.setgCharset(2, Terminal.charsets.US); this.setgCharset(3, Terminal.charsets.US); // set VT100 mode here break; case 3: // 132 col mode this.savedCols = this.cols; this.resize(132, this.rows); break; case 6: this.originMode = true; break; case 7: this.wraparoundMode = true; break; case 12: // this.cursorBlink = true; break; case 66: this.log('Serial port requested applications keypad.'); this.applicationKeypad = true; break; case 9: // X10 Mouse // no release, no motion, no wheel, no modifiers. case 1000: // vt200 mouse // no motion. // no modifiers, except control on the wheel. case 1002: // button event mouse case 1003: // any event mouse // any event - sends motion events, // even if there is no button held down. this.x10Mouse = params === 9; this.vt200Mouse = params === 1000; this.normalMouse = params > 1000; this.mouseEvents = true; this.element.style.cursor = 'default'; this.log('Binding to mouse events.'); break; case 1004: // send focusin/focusout events // focusin: ^[[I // focusout: ^[[O this.sendFocus = true; break; case 1005: // utf8 applications mode mouse this.utfMouse = true; // for wide terminals // simply encodes large values as utf8 characters break; case 1006: // sgr applications mode mouse this.sgrMouse = true; // for wide terminals // does not add 32 to fields // press: ^[[<b;x;yM // release: ^[[<b;x;ym break; case 1015: // urxvt applications mode mouse this.urxvtMouse = true; // for wide terminals // numbers for fields // press: ^[[b;x;yM // motion: ^[[b;x;yT break; case 25: // show cursor this.cursorHidden = false; break; case 1049: // alt screen buffer cursor //this.saveCursor(); ; // FALL-THROUGH case 47: // alt screen buffer case 1047: // alt screen buffer if (!this.normal) { var normal = { lines: this.lines, ybase: this.ybase, ydisp: this.ydisp, x: this.x, y: this.y, scrollTop: this.scrollTop, scrollBottom: this.scrollBottom, tabs: this.tabs // XXX save charset(s) here? // charset: this.charset, // glevel: this.glevel, // charsets: this.charsets }; this.reset(); this.normal = normal; this.showCursor(); } break; } } }; // CSI Pm l Reset Mode (RM). // Ps = 2 -> Keyboard Action Mode (AM). // Ps = 4 -> Replace Mode (IRM). // Ps = 1 2 -> Send/receive (SRM). // Ps = 2 0 -> Normal Linefeed (LNM). // CSI ? Pm l // DEC Private Mode Reset (DECRST). // Ps = 1 -> Normal Cursor Keys (DECCKM). // Ps = 2 -> Designate VT52 mode (DECANM). // Ps = 3 -> 80 Column Mode (DECCOLM). // Ps = 4 -> Jump (Fast) Scroll (DECSCLM). // Ps = 5 -> Normal Video (DECSCNM). // Ps = 6 -> Normal Cursor Mode (DECOM). // Ps = 7 -> No Wraparound Mode (DECAWM). // Ps = 8 -> No Auto-repeat Keys (DECARM). // Ps = 9 -> Don't send Mouse X & Y on button press. // Ps = 1 0 -> Hide toolbar (rxvt). // Ps = 1 2 -> Stop Blinking Cursor (att610). // Ps = 1 8 -> Don't print form feed (DECPFF). // Ps = 1 9 -> Limit print to scrolling region (DECPEX). // Ps = 2 5 -> Hide Cursor (DECTCEM). // Ps = 3 0 -> Don't show scrollbar (rxvt). // Ps = 3 5 -> Disable font-shifting functions (rxvt). // Ps = 4 0 -> Disallow 80 -> 132 Mode. // Ps = 4 1 -> No more(1) fix (see curses resource). // Ps = 4 2 -> Disable Nation Replacement Character sets (DEC- // NRCM). // Ps = 4 4 -> Turn Off Margin Bell. // Ps = 4 5 -> No Reverse-wraparound Mode. // Ps = 4 6 -> Stop Logging. (This is normally disabled by a // compile-time option). // Ps = 4 7 -> Use Normal Screen Buffer. // Ps = 6 6 -> Numeric keypad (DECNKM). // Ps = 6 7 -> Backarrow key sends delete (DECBKM). // Ps = 1 0 0 0 -> Don't send Mouse X & Y on button press and // release. See the section Mouse Tracking. // Ps = 1 0 0 1 -> Don't use Hilite Mouse Tracking. // Ps = 1 0 0 2 -> Don't use Cell Motion Mouse Tracking. // Ps = 1 0 0 3 -> Don't use All Motion Mouse Tracking. // Ps = 1 0 0 4 -> Don't send FocusIn/FocusOut events. // Ps = 1 0 0 5 -> Disable Extended Mouse Mode. // Ps = 1 0 1 0 -> Don't scroll to bottom on tty output // (rxvt). // Ps = 1 0 1 1 -> Don't scroll to bottom on key press (rxvt). // Ps = 1 0 3 4 -> Don't interpret "meta" key. (This disables // the eightBitInput resource). // Ps = 1 0 3 5 -> Disable special modifiers for Alt and Num- // Lock keys. (This disables the numLock resource). // Ps = 1 0 3 6 -> Don't send ESC when Meta modifies a key. // (This disables the metaSendsEscape resource). // Ps = 1 0 3 7 -> Send VT220 Remove from the editing-keypad // Delete key. // Ps = 1 0 3 9 -> Don't send ESC when Alt modifies a key. // (This disables the altSendsEscape resource). // Ps = 1 0 4 0 -> Do not keep selection when not highlighted. // (This disables the keepSelection resource). // Ps = 1 0 4 1 -> Use the PRIMARY selection. (This disables // the selectToClipboard resource). // Ps = 1 0 4 2 -> Disable Urgency window manager hint when // Control-G is received. (This disables the bellIsUrgent // resource). // Ps = 1 0 4 3 -> Disable raising of the window when Control- // G is received. (This disables the popOnBell resource). // Ps = 1 0 4 7 -> Use Normal Screen Buffer, clearing screen // first if in the Alternate Screen. (This may be disabled by // the titeInhibit resource). // Ps = 1 0 4 8 -> Restore cursor as in DECRC. (This may be // disabled by the titeInhibit resource). // Ps = 1 0 4 9 -> Use Normal Screen Buffer and restore cursor // as in DECRC. (This may be disabled by the titeInhibit // resource). This combines the effects of the 1 0 4 7 and 1 0 // 4 8 modes. Use this with terminfo-based applications rather // than the 4 7 mode. // Ps = 1 0 5 0 -> Reset terminfo/termcap function-key mode. // Ps = 1 0 5 1 -> Reset Sun function-key mode. // Ps = 1 0 5 2 -> Reset HP function-key mode. // Ps = 1 0 5 3 -> Reset SCO function-key mode. // Ps = 1 0 6 0 -> Reset legacy keyboard emulation (X11R6). // Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style. // Ps = 2 0 0 4 -> Reset bracketed paste mode. Terminal.prototype.resetMode = function(params) { if (typeof params === 'object') { var l = params.length , i = 0; for (; i < l; i++) { this.resetMode(params[i]); } return; } if (!this.prefix) { switch (params) { case 4: this.insertMode = false; break; case 20: //this.convertEol = false; break; } } else if (this.prefix === '?') { switch (params) { case 1: this.applicationCursor = false; break; case 3: if (this.cols === 132 && this.savedCols) { this.resize(this.savedCols, this.rows); } delete this.savedCols; break; case 6: this.originMode = false; break; case 7: this.wraparoundMode = false; break; case 12: // this.cursorBlink = false; break; case 66: this.log('Switching back to normal keypad.'); this.applicationKeypad = false; break; case 9: // X10 Mouse case 1000: // vt200 mouse case 1002: // button event mouse case 1003: // any event mouse this.x10Mouse = false; this.vt200Mouse = false; this.normalMouse = false; this.mouseEvents = false; this.element.style.cursor = ''; break; case 1004: // send focusin/focusout events this.sendFocus = false; break; case 1005: // utf8 applications mode mouse this.utfMouse = false; break; case 1006: // sgr applications mode mouse this.sgrMouse = false; break; case 1015: // urxvt applications mode mouse this.urxvtMouse = false; break; case 25: // hide cursor this.cursorHidden = true; break; case 1049: // alt screen buffer cursor ; // FALL-THROUGH case 47: // normal screen buffer case 1047: // normal screen buffer - clearing it first if (this.normal) { this.lines = this.normal.lines; this.ybase = this.normal.ybase; this.ydisp = this.normal.ydisp; this.x = this.normal.x; this.y = this.normal.y; this.scrollTop = this.normal.scrollTop; this.scrollBottom = this.normal.scrollBottom; this.tabs = this.normal.tabs; this.normal = null; // if (params === 1049) { // this.x = this.savedX; // this.y = this.savedY; // } this.refresh(0, this.rows - 1); this.showCursor(); } break; } } }; // CSI Ps ; Ps r // Set Scrolling Region [top;bottom] (default = full size of win- // dow) (DECSTBM). // CSI ? Pm r Terminal.prototype.setScrollRegion = function(params) { if (this.prefix) return; this.scrollTop = (params[0] || 1) - 1; this.scrollBottom = (params[1] || this.rows) - 1; this.x = 0; this.y = 0; }; // CSI s // Save cursor (ANSI.SYS). Terminal.prototype.saveCursor = function(params) { this.savedX = this.x; this.savedY = this.y; }; // CSI u // Restore cursor (ANSI.SYS). Terminal.prototype.restoreCursor = function(params) { this.x = this.savedX || 0; this.y = this.savedY || 0; }; /** * Lesser Used */ // CSI Ps I // Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). Terminal.prototype.cursorForwardTab = function(params) { var param = params[0] || 1; while (param--) { this.x = this.nextStop(); } }; // CSI Ps S Scroll up Ps lines (default = 1) (SU). Terminal.prototype.scrollUp = function(params) { var param = params[0] || 1; while (param--) { this.lines.splice(this.ybase + this.scrollTop, 1); this.lines.splice(this.ybase + this.scrollBottom, 0, this.blankLine()); } // this.maxRange(); this.updateRange(this.scrollTop); this.updateRange(this.scrollBottom); }; // CSI Ps T Scroll down Ps lines (default = 1) (SD). Terminal.prototype.scrollDown = function(params) { var param = params[0] || 1; while (param--) { this.lines.splice(this.ybase + this.scrollBottom, 1); this.lines.splice(this.ybase + this.scrollTop, 0, this.blankLine()); } // this.maxRange(); this.updateRange(this.scrollTop); this.updateRange(this.scrollBottom); }; // CSI Ps ; Ps ; Ps ; Ps ; Ps T // Initiate highlight mouse tracking. Parameters are // [func;startx;starty;firstrow;lastrow]. See the section Mouse // Tracking. Terminal.prototype.initMouseTracking = function(params) { // Relevant: DECSET 1001 }; // CSI > Ps; Ps T // Reset one or more features of the title modes to the default // value. Normally, "reset" disables the feature. It is possi- // ble to disable the ability to reset features by compiling a // different default for the title modes into xterm. // Ps = 0 -> Do not set window/icon labels using hexadecimal. // Ps = 1 -> Do not query window/icon labels using hexadeci- // mal. // Ps = 2 -> Do not set window/icon labels using UTF-8. // Ps = 3 -> Do not query window/icon labels using UTF-8. // (See discussion of "Title Modes"). Terminal.prototype.resetTitleModes = function(params) { ; }; // CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). Terminal.prototype.cursorBackwardTab = function(params) { var param = params[0] || 1; while (param--) { this.x = this.prevStop(); } }; // CSI Ps b Repeat the preceding graphic character Ps times (REP). Terminal.prototype.repeatPrecedingCharacter = function(params) { var param = params[0] || 1 , line = this.lines[this.ybase + this.y] , ch = line[this.x - 1] || [this.defAttr, ' ']; while (param--) line[this.x++] = ch; }; // CSI Ps g Tab Clear (TBC). // Ps = 0 -> Clear Current Column (default). // Ps = 3 -> Clear All. // Potentially: // Ps = 2 -> Clear Stops on Line. // http://vt100.net/annarbor/aaa-ug/section6.html Terminal.prototype.tabClear = function(params) { var param = params[0]; if (param <= 0) { delete this.tabs[this.x]; } else if (param === 3) { this.tabs = {}; } }; // CSI Pm i Media Copy (MC). // Ps = 0 -> Print screen (default). // Ps = 4 -> Turn off printer controller mode. // Ps = 5 -> Turn on printer controller mode. // CSI ? Pm i // Media Copy (MC, DEC-specific). // Ps = 1 -> Print line containing cursor. // Ps = 4 -> Turn off autoprint mode. // Ps = 5 -> Turn on autoprint mode. // Ps = 1 0 -> Print composed display, ignores DECPEX. // Ps = 1 1 -> Print all pages. Terminal.prototype.mediaCopy = function(params) { ; }; // CSI > Ps; Ps m // Set or reset resource-values used by xterm to decide whether // to construct escape sequences holding information about the // modifiers pressed with a given key. The first parameter iden- // tifies the resource to set/reset. The second parameter is the // value to assign to the resource. If the second parameter is // omitted, the resource is reset to its initial value. // Ps = 1 -> modifyCursorKeys. // Ps = 2 -> modifyFunctionKeys. // Ps = 4 -> modifyOtherKeys. // If no parameters are given, all resources are reset to their // initial values. Terminal.prototype.setResources = function(params) { ; }; // CSI > Ps n // Disable modifiers which may be enabled via the CSI > Ps; Ps m // sequence. This corresponds to a resource value of "-1", which // cannot be set with the other sequence. The parameter identi- // fies the resource to be disabled: // Ps = 1 -> modifyCursorKeys. // Ps = 2 -> modifyFunctionKeys. // Ps = 4 -> modifyOtherKeys. // If the parameter is omitted, modifyFunctionKeys is disabled. // When modifyFunctionKeys is disabled, xterm uses the modifier // keys to make an extended sequence of functions rather than // adding a parameter to each function key to denote the modi- // fiers. Terminal.prototype.disableModifiers = function(params) { ; }; // CSI > Ps p // Set resource value pointerMode. This is used by xterm to // decide whether to hide the pointer cursor as the user types. // Valid values for the parameter: // Ps = 0 -> never hide the pointer. // Ps = 1 -> hide if the mouse tracking mode is not enabled. // Ps = 2 -> always hide the pointer. If no parameter is // given, xterm uses the default, which is 1 . Terminal.prototype.setPointerMode = function(params) { ; }; // CSI ! p Soft applications reset (DECSTR). // http://vt100.net/docs/vt220-rm/table4-10.html Terminal.prototype.softReset = function(params) { this.cursorHidden = false; this.insertMode = false; this.originMode = false; this.wraparoundMode = false; // autowrap this.applicationKeypad = false; // ? this.applicationCursor = false; this.scrollTop = 0; this.scrollBottom = this.rows - 1; this.curAttr = this.defAttr; this.x = this.y = 0; // ? this.charset = null; this.glevel = 0; // ?? this.charsets = [null]; // ?? }; // CSI Ps$ p // Request ANSI mode (DECRQM). For VT300 and up, reply is // CSI Ps; Pm$ y // where Ps is the mode number as in RM, and Pm is the mode // value: // 0 - not recognized // 1 - set // 2 - reset // 3 - permanently set // 4 - permanently reset Terminal.prototype.requestAnsiMode = function(params) { ; }; // CSI ? Ps$ p // Request DEC private mode (DECRQM). For VT300 and up, reply is // CSI ? Ps; Pm$ p // where Ps is the mode number as in DECSET, Pm is the mode value // as in the ANSI DECRQM. Terminal.prototype.requestPrivateMode = function(params) { ; }; // CSI Ps ; Ps " p // Set conformance level (DECSCL). Valid values for the first // parameter: // Ps = 6 1 -> VT100. // Ps = 6 2 -> VT200. // Ps = 6 3 -> VT300. // Valid values for the second parameter: // Ps = 0 -> 8-bit controls. // Ps = 1 -> 7-bit controls (always set for VT100). // Ps = 2 -> 8-bit controls. Terminal.prototype.setConformanceLevel = function(params) { ; }; // CSI Ps q Load LEDs (DECLL). // Ps = 0 -> Clear all LEDS (default). // Ps = 1 -> Light Num Lock. // Ps = 2 -> Light Caps Lock. // Ps = 3 -> Light Scroll Lock. // Ps = 2 1 -> Extinguish Num Lock. // Ps = 2 2 -> Extinguish Caps Lock. // Ps = 2 3 -> Extinguish Scroll Lock. Terminal.prototype.loadLEDs = function(params) { ; }; // CSI Ps SP q // Set cursor style (DECSCUSR, VT520). // Ps = 0 -> blinking block. // Ps = 1 -> blinking block (default). // Ps = 2 -> steady block. // Ps = 3 -> blinking underline. // Ps = 4 -> steady underline. Terminal.prototype.setCursorStyle = function(params) { ; }; // CSI Ps " q // Select character protection attribute (DECSCA). Valid values // for the parameter: // Ps = 0 -> DECSED and DECSEL can erase (default). // Ps = 1 -> DECSED and DECSEL cannot erase. // Ps = 2 -> DECSED and DECSEL can erase. Terminal.prototype.setCharProtectionAttr = function(params) { ; }; // CSI ? Pm r // Restore DEC Private Mode Values. The value of Ps previously // saved is restored. Ps values are the same as for DECSET. Terminal.prototype.restorePrivateValues = function(params) { ; }; // CSI Pt; Pl; Pb; Pr; Ps$ r // Change Attributes in Rectangular Area (DECCARA), VT400 and up. // Pt; Pl; Pb; Pr denotes the rectangle. // Ps denotes the SGR attributes to change: 0, 1, 4, 5, 7. // NOTE: xterm doesn't enable this code by default. Terminal.prototype.setAttrInRectangle = function(params) { var t = params[0] , l = params[1] , b = params[2] , r = params[3] , attr = params[4]; var line , i; for (; t < b + 1; t++) { line = this.lines[this.ybase + t]; for (i = l; i < r; i++) { line[i] = [attr, line[i][1]]; } } // this.maxRange(); this.updateRange(params[0]); this.updateRange(params[2]); }; // CSI ? Pm s // Save DEC Private Mode Values. Ps values are the same as for // DECSET. Terminal.prototype.savePrivateValues = function(params) { ; }; // CSI Ps ; Ps ; Ps t // Window manipulation (from dtterm, as well as extensions). // These controls may be disabled using the allowWindowOps // resource. Valid values for the first (and any additional // parameters) are: // Ps = 1 -> De-iconify window. // Ps = 2 -> Iconify window. // Ps = 3 ; x ; y -> Move window to [x, y]. // Ps = 4 ; height ; width -> Resize the xterm window to // height and width in pixels. // Ps = 5 -> Raise the xterm window to the front of the stack- // ing order. // Ps = 6 -> Lower the xterm window to the bottom of the // stacking order. // Ps = 7 -> Refresh the xterm window. // Ps = 8 ; height ; width -> Resize the text area to // [height;width] in characters. // Ps = 9 ; 0 -> Restore maximized window. // Ps = 9 ; 1 -> Maximize window (i.e., resize to screen // size). // Ps = 1 0 ; 0 -> Undo full-screen mode. // Ps = 1 0 ; 1 -> Change to full-screen. // Ps = 1 1 -> Report xterm window state. If the xterm window // is open (non-iconified), it returns CSI 1 t . If the xterm // window is iconified, it returns CSI 2 t . // Ps = 1 3 -> Report xterm window position. Result is CSI 3 // ; x ; y t // Ps = 1 4 -> Report xterm window in pixels. Result is CSI // 4 ; height ; width t // Ps = 1 8 -> Report the size of the text area in characters. // Result is CSI 8 ; height ; width t // Ps = 1 9 -> Report the size of the screen in characters. // Result is CSI 9 ; height ; width t // Ps = 2 0 -> Report xterm window's icon label. Result is // OSC L label ST // Ps = 2 1 -> Report xterm window's title. Result is OSC l // label ST // Ps = 2 2 ; 0 -> Save xterm icon and window title on // stack. // Ps = 2 2 ; 1 -> Save xterm icon title on stack. // Ps = 2 2 ; 2 -> Save xterm window title on stack. // Ps = 2 3 ; 0 -> Restore xterm icon and window title from // stack. // Ps = 2 3 ; 1 -> Restore xterm icon title from stack. // Ps = 2 3 ; 2 -> Restore xterm window title from stack. // Ps >= 2 4 -> Resize to Ps lines (DECSLPP). Terminal.prototype.manipulateWindow = function(params) { ; }; // CSI Pt; Pl; Pb; Pr; Ps$ t // Reverse Attributes in Rectangular Area (DECRARA), VT400 and // up. // Pt; Pl; Pb; Pr denotes the rectangle. // Ps denotes the attributes to reverse, i.e., 1, 4, 5, 7. // NOTE: xterm doesn't enable this code by default. Terminal.prototype.reverseAttrInRectangle = function(params) { ; }; // CSI > Ps; Ps t // Set one or more features of the title modes. Each parameter // enables a single feature. // Ps = 0 -> Set window/icon labels using hexadecimal. // Ps = 1 -> Query window/icon labels using hexadecimal. // Ps = 2 -> Set window/icon labels using UTF-8. // Ps = 3 -> Query window/icon labels using UTF-8. (See dis- // cussion of "Title Modes") Terminal.prototype.setTitleModeFeature = function(params) { ; }; // CSI Ps SP t // Set warning-bell volume (DECSWBV, VT520). // Ps = 0 or 1 -> off. // Ps = 2 , 3 or 4 -> low. // Ps = 5 , 6 , 7 , or 8 -> high. Terminal.prototype.setWarningBellVolume = function(params) { ; }; // CSI Ps SP u // Set margin-bell volume (DECSMBV, VT520). // Ps = 1 -> off. // Ps = 2 , 3 or 4 -> low. // Ps = 0 , 5 , 6 , 7 , or 8 -> high. Terminal.prototype.setMarginBellVolume = function(params) { ; }; // CSI Pt; Pl; Pb; Pr; Pp; Pt; Pl; Pp$ v // Copy Rectangular Area (DECCRA, VT400 and up). // Pt; Pl; Pb; Pr denotes the rectangle. // Pp denotes the source page. // Pt; Pl denotes the target location. // Pp denotes the target page. // NOTE: xterm doesn't enable this code by default. Terminal.prototype.copyRectangle = function(params) { ; }; // CSI Pt ; Pl ; Pb ; Pr ' w // Enable Filter Rectangle (DECEFR), VT420 and up. // Parameters are [top;left;bottom;right]. // Defines the coordinates of a filter rectangle and activates // it. Anytime the locator is detected outside of the filter // rectangle, an outside rectangle event is generated and the // rectangle is disabled. Filter rectangles are always treated // as "one-shot" events. Any parameters that are omitted default // to the current locator position. If all parameters are omit- // ted, any locator motion will be reported. DECELR always can- // cels any prevous rectangle definition. Terminal.prototype.enableFilterRectangle = function(params) { ; }; // CSI Ps x Request Terminal Parameters (DECREQTPARM). // if Ps is a "0" (default) or "1", and xterm is emulating VT100, // the control sequence elicits a response of the same form whose // parameters describe the applications: // Ps -> the given Ps incremented by 2. // Pn = 1 <- no parity. // Pn = 1 <- eight bits. // Pn = 1 <- 2 8 transmit 38.4k baud. // Pn = 1 <- 2 8 receive 38.4k baud. // Pn = 1 <- clock multiplier. // Pn = 0 <- STP flags. Terminal.prototype.requestParameters = function(params) { ; }; // CSI Ps x Select Attribute Change Extent (DECSACE). // Ps = 0 -> from start to end position, wrapped. // Ps = 1 -> from start to end position, wrapped. // Ps = 2 -> rectangle (exact). Terminal.prototype.selectChangeExtent = function(params) { ; }; // CSI Pc; Pt; Pl; Pb; Pr$ x // Fill Rectangular Area (DECFRA), VT420 and up. // Pc is the character to use. // Pt; Pl; Pb; Pr denotes the rectangle. // NOTE: xterm doesn't enable this code by default. Terminal.prototype.fillRectangle = function(params) { var ch = params[0] , t = params[1] , l = params[2] , b = params[3] , r = params[4]; var line , i; for (; t < b + 1; t++) { line = this.lines[this.ybase + t]; for (i = l; i < r; i++) { line[i] = [line[i][0], String.fromCharCode(ch)]; } } // this.maxRange(); this.updateRange(params[1]); this.updateRange(params[3]); }; // CSI Ps ; Pu ' z // Enable Locator Reporting (DECELR). // Valid values for the first parameter: // Ps = 0 -> Locator disabled (default). // Ps = 1 -> Locator enabled. // Ps = 2 -> Locator enabled for one report, then disabled. // The second parameter specifies the coordinate unit for locator // reports. // Valid values for the second parameter: // Pu = 0 <- or omitted -> default to character cells. // Pu = 1 <- device physical pixels. // Pu = 2 <- character cells. Terminal.prototype.enableLocatorReporting = function(params) { var val = params[0] > 0; //this.mouseEvents = val; //this.decLocator = val; }; // CSI Pt; Pl; Pb; Pr$ z // Erase Rectangular Area (DECERA), VT400 and up. // Pt; Pl; Pb; Pr denotes the rectangle. // NOTE: xterm doesn't enable this code by default. Terminal.prototype.eraseRectangle = function(params) { var t = params[0] , l = params[1] , b = params[2] , r = params[3]; var line , i , ch; ch = [this.eraseAttr(), ' ']; // xterm? for (; t < b + 1; t++) { line = this.lines[this.ybase + t]; for (i = l; i < r; i++) { line[i] = ch; } } // this.maxRange(); this.updateRange(params[0]); this.updateRange(params[2]); }; // CSI Pm ' { // Select Locator Events (DECSLE). // Valid values for the first (and any additional parameters) // are: // Ps = 0 -> only respond to explicit host requests (DECRQLP). // (This is default). It also cancels any filter // rectangle. // Ps = 1 -> report button down transitions. // Ps = 2 -> do not report button down transitions. // Ps = 3 -> report button up transitions. // Ps = 4 -> do not report button up transitions. Terminal.prototype.setLocatorEvents = function(params) { ; }; // CSI Pt; Pl; Pb; Pr$ { // Selective Erase Rectangular Area (DECSERA), VT400 and up. // Pt; Pl; Pb; Pr denotes the rectangle. Terminal.prototype.selectiveEraseRectangle = function(params) { ; }; // CSI Ps ' | // Request Locator Position (DECRQLP). // Valid values for the parameter are: // Ps = 0 , 1 or omitted -> transmit a single DECLRP locator // report. // If Locator Reporting has been enabled by a DECELR, xterm will // respond with a DECLRP Locator Report. This report is also // generated on button up and down events if they have been // enabled with a DECSLE, or when the locator is detected outside // of a filter rectangle, if filter rectangles have been enabled // with a DECEFR. // -> CSI Pe ; Pb ; Pr ; Pc ; Pp & w // Parameters are [event;button;row;column;page]. // Valid values for the event: // Pe = 0 -> locator unavailable - no other parameters sent. // Pe = 1 -> request - xterm received a DECRQLP. // Pe = 2 -> left button down. // Pe = 3 -> left button up. // Pe = 4 -> middle button down. // Pe = 5 -> middle button up. // Pe = 6 -> right button down. // Pe = 7 -> right button up. // Pe = 8 -> M4 button down. // Pe = 9 -> M4 button up. // Pe = 1 0 -> locator outside filter rectangle. // ``button'' parameter is a bitmask indicating which buttons are // pressed: // Pb = 0 <- no buttons down. // Pb & 1 <- right button down. // Pb & 2 <- middle button down. // Pb & 4 <- left button down. // Pb & 8 <- M4 button down. // ``row'' and ``column'' parameters are the coordinates of the // locator position in the xterm window, encoded as ASCII deci- // mal. // The ``page'' parameter is not used by xterm, and will be omit- // ted. Terminal.prototype.requestLocatorPosition = function(params) { ; }; // CSI P m SP } // Insert P s Column(s) (default = 1) (DECIC), VT420 and up. // NOTE: xterm doesn't enable this code by default. Terminal.prototype.insertColumns = function() { var param = params[0] , l = this.ybase + this.rows , ch = [this.eraseAttr(), ' '] // xterm? , i; while (param--) { for (i = this.ybase; i < l; i++) { this.lines[i].splice(this.x + 1, 0, ch); this.lines[i].pop(); } } this.maxRange(); }; // CSI P m SP ~ // Delete P s Column(s) (default = 1) (DECDC), VT420 and up // NOTE: xterm doesn't enable this code by default. Terminal.prototype.deleteColumns = function() { var param = params[0] , l = this.ybase + this.rows , ch = [this.eraseAttr(), ' '] // xterm? , i; while (param--) { for (i = this.ybase; i < l; i++) { this.lines[i].splice(this.x, 1); this.lines[i].push(ch); } } this.maxRange(); }; /** * Prefix/Select/Visual/Search Modes */ Terminal.prototype.enterPrefix = function() { this.prefixMode = true; }; Terminal.prototype.leavePrefix = function() { this.prefixMode = false; }; Terminal.prototype.enterSelect = function() { this._real = { x: this.x, y: this.y, ydisp: this.ydisp, ybase: this.ybase, cursorHidden: this.cursorHidden, lines: this.copyBuffer(this.lines), write: this.write }; this.write = function() {}; this.selectMode = true; this.visualMode = false; this.cursorHidden = false; this.refresh(this.y, this.y); }; Terminal.prototype.leaveSelect = function() { this.x = this._real.x; this.y = this._real.y; this.ydisp = this._real.ydisp; this.ybase = this._real.ybase; this.cursorHidden = this._real.cursorHidden; this.lines = this._real.lines; this.write = this._real.write; delete this._real; this.selectMode = false; this.visualMode = false; this.refresh(0, this.rows - 1); }; Terminal.prototype.enterVisual = function() { this._real.preVisual = this.copyBuffer(this.lines); this.selectText(this.x, this.x, this.ydisp + this.y, this.ydisp + this.y); this.visualMode = true; }; Terminal.prototype.leaveVisual = function() { this.lines = this._real.preVisual; delete this._real.preVisual; delete this._selected; this.visualMode = false; this.refresh(0, this.rows - 1); }; Terminal.prototype.enterSearch = function(down) { this.entry = ''; this.searchMode = true; this.searchDown = down; this._real.preSearch = this.copyBuffer(this.lines); this._real.preSearchX = this.x; this._real.preSearchY = this.y; var bottom = this.ydisp + this.rows - 1; for (var i = 0; i < this.entryPrefix.length; i++) { //this.lines[bottom][i][0] = (this.defAttr & ~0x1ff) | 4; //this.lines[bottom][i][1] = this.entryPrefix[i]; this.lines[bottom][i] = [ (this.defAttr & ~0x1ff) | 4, this.entryPrefix[i] ]; } this.y = this.rows - 1; this.x = this.entryPrefix.length; this.refresh(this.rows - 1, this.rows - 1); }; Terminal.prototype.leaveSearch = function() { this.searchMode = false; if (this._real.preSearch) { this.lines = this._real.preSearch; this.x = this._real.preSearchX; this.y = this._real.preSearchY; delete this._real.preSearch; delete this._real.preSearchX; delete this._real.preSearchY; } this.refresh(this.rows - 1, this.rows - 1); }; Terminal.prototype.copyBuffer = function(lines) { var lines = lines || this.lines , out = []; for (var y = 0; y < lines.length; y++) { out[y] = []; for (var x = 0; x < lines[y].length; x++) { out[y][x] = [lines[y][x][0], lines[y][x][1]]; } } return out; }; Terminal.prototype.getCopyTextarea = function(text) { var textarea = this._copyTextarea , document = this.document; if (!textarea) { 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'; document.getElementsByTagName('body')[0].appendChild(textarea); this._copyTextarea = textarea; } return textarea; }; // NOTE: Only works for primary selection on X11. // Non-X11 users should use Ctrl-C instead. Terminal.prototype.copyText = function(text) { var self = this , textarea = this.getCopyTextarea(); this.emit('copy', text); textarea.focus(); textarea.textContent = text; textarea.value = text; textarea.setSelectionRange(0, text.length); setTimeout(function() { self.element.focus(); self.focus(); }, 1); }; Terminal.prototype.clearSelectedText = function() { if (this._selected) { var ox1 , ox2 , oy1 , oy2 , tmp , x , y , xl , attr; ox1 = this._selected.x1; ox2 = this._selected.x2; oy1 = this._selected.y1; oy2 = this._selected.y2; if (oy2 < oy1) { tmp = ox2; ox2 = ox1; ox1 = tmp; tmp = oy2; oy2 = oy1; oy1 = tmp; } if (ox2 < ox1 && oy1 === oy2) { tmp = ox2; ox2 = ox1; ox1 = tmp; } for (y = oy1; y <= oy2; y++) { x = 0; xl = this.cols - 1; if (y === oy1) { x = ox1; } if (y === oy2) { xl = ox2; } for (; x <= xl; x++) { if (this.lines[y][x].old != null) { //this.lines[y][x][0] = this.lines[y][x].old; //delete this.lines[y][x].old; attr = this.lines[y][x].old; delete this.lines[y][x].old; this.lines[y][x] = [attr, this.lines[y][x][1]]; } } } delete this._selected; } }; Terminal.prototype.selectText = function(x1, x2, y1, y2) { var tmp , x , y , xl , attr; if (this._selected) { this.clearSelectedText(); } y1 = Math.max(y1, 0); y1 = Math.min(y1, this.ydisp + this.rows - 1); y2 = Math.max(y2, 0); y2 = Math.min(y2, this.ydisp + this.rows - 1); this._selected = { x1: x1, x2: x2, y1: y1, y2: y2 }; if (y2 < y1) { tmp = x2; x2 = x1; x1 = tmp; tmp = y2; y2 = y1; y1 = tmp; } if (x2 < x1 && y1 === y2) { tmp = x2; x2 = x1; x1 = tmp; } for (y = y1; y <= y2; y++) { x = 0; xl = this.cols - 1; if (y === y1) { x = x1; } if (y === y2) { xl = x2; } for (; x <= xl; x++) { //this.lines[y][x].old = this.lines[y][x][0]; //this.lines[y][x][0] &= ~0x1ff; //this.lines[y][x][0] |= (0x1ff << 9) | 4; attr = this.lines[y][x][0]; this.lines[y][x] = [ (attr & ~0x1ff) | ((0x1ff << 9) | 4), this.lines[y][x][1] ]; this.lines[y][x].old = attr; } } y1 = y1 - this.ydisp; y2 = y2 - this.ydisp; y1 = Math.max(y1, 0); y1 = Math.min(y1, this.rows - 1); y2 = Math.max(y2, 0); y2 = Math.min(y2, this.rows - 1); //this.refresh(y1, y2); this.refresh(0, this.rows - 1); }; Terminal.prototype.grabText = function(x1, x2, y1, y2) { var out = '' , buf = '' , ch , x , y , xl , tmp; if (y2 < y1) { tmp = x2; x2 = x1; x1 = tmp; tmp = y2; y2 = y1; y1 = tmp; } if (x2 < x1 && y1 === y2) { tmp = x2; x2 = x1; x1 = tmp; } for (y = y1; y <= y2; y++) { x = 0; xl = this.cols - 1; if (y === y1) { x = x1; } if (y === y2) { xl = x2; } for (; x <= xl; x++) { ch = this.lines[y][x][1]; if (ch === ' ') { buf += ch; continue; } if (buf) { out += buf; buf = ''; } out += ch; if (isWide(ch)) x++; } buf = ''; out += '\n'; } // If we're not at the end of the // line, don't add a newline. for (x = x2, y = y2; x < this.cols; x++) { if (this.lines[y][x][1] !== ' ') { out = out.slice(0, -1); break; } } return out; }; Terminal.prototype.keyPrefix = function(ev, key) { if (key === 'k' || key === '&') { this.destroy(); } else if (key === 'p' || key === ']') { this.emit('request paste'); } else if (key === 'c') { this.emit('request create'); } else if (key >= '0' && key <= '9') { key = +key - 1; if (!~key) key = 9; this.emit('request term', key); } else if (key === 'n') { this.emit('request term next'); } else if (key === 'P') { this.emit('request term previous'); } else if (key === ':') { this.emit('request command mode'); } else if (key === '[') { this.enterSelect(); } }; Terminal.prototype.keySelect = function(ev, key) { this.showCursor(); if (this.searchMode || key === 'n' || key === 'N') { return this.keySearch(ev, key); } if (key === '\x04') { // ctrl-d var y = this.ydisp + this.y; if (this.ydisp === this.ybase) { // Mimic vim behavior this.y = Math.min(this.y + (this.rows - 1) / 2 | 0, this.rows - 1); this.refresh(0, this.rows - 1); } else { this.scrollDisp((this.rows - 1) / 2 | 0); } if (this.visualMode) { this.selectText(this.x, this.x, y, this.ydisp + this.y); } return; } if (key === '\x15') { // ctrl-u var y = this.ydisp + this.y; if (this.ydisp === 0) { // Mimic vim behavior this.y = Math.max(this.y - (this.rows - 1) / 2 | 0, 0); this.refresh(0, this.rows - 1); } else { this.scrollDisp(-(this.rows - 1) / 2 | 0); } if (this.visualMode) { this.selectText(this.x, this.x, y, this.ydisp + this.y); } return; } if (key === '\x06') { // ctrl-f var y = this.ydisp + this.y; this.scrollDisp(this.rows - 1); if (this.visualMode) { this.selectText(this.x, this.x, y, this.ydisp + this.y); } return; } if (key === '\x02') { // ctrl-b var y = this.ydisp + this.y; this.scrollDisp(-(this.rows - 1)); if (this.visualMode) { this.selectText(this.x, this.x, y, this.ydisp + this.y); } return; } if (key === 'k' || key === '\x1b[A') { var y = this.ydisp + this.y; this.y--; if (this.y < 0) { this.y = 0; this.scrollDisp(-1); } if (this.visualMode) { this.selectText(this.x, this.x, y, this.ydisp + this.y); } else { this.refresh(this.y, this.y + 1); } return; } if (key === 'j' || key === '\x1b[B') { var y = this.ydisp + this.y; this.y++; if (this.y >= this.rows) { this.y = this.rows - 1; this.scrollDisp(1); } if (this.visualMode) { this.selectText(this.x, this.x, y, this.ydisp + this.y); } else { this.refresh(this.y - 1, this.y); } return; } if (key === 'h' || key === '\x1b[D') { var x = this.x; this.x--; if (this.x < 0) { this.x = 0; } if (this.visualMode) { this.selectText(x, this.x, this.ydisp + this.y, this.ydisp + this.y); } else { this.refresh(this.y, this.y); } return; } if (key === 'l' || key === '\x1b[C') { var x = this.x; this.x++; if (this.x >= this.cols) { this.x = this.cols - 1; } if (this.visualMode) { this.selectText(x, this.x, this.ydisp + this.y, this.ydisp + this.y); } else { this.refresh(this.y, this.y); } return; } if (key === 'v' || key === ' ') { if (!this.visualMode) { this.enterVisual(); } else { this.leaveVisual(); } return; } if (key === 'y') { if (this.visualMode) { var text = this.grabText( this._selected.x1, this._selected.x2, this._selected.y1, this._selected.y2); this.copyText(text); this.leaveVisual(); // this.leaveSelect(); } return; } if (key === 'q' || key === '\x1b') { if (this.visualMode) { this.leaveVisual(); } else { this.leaveSelect(); } return; } if (key === 'w' || key === 'W') { var ox = this.x; var oy = this.y; var oyd = this.ydisp; var x = this.x; var y = this.y; var yb = this.ydisp; var saw_space = false; for (;;) { var line = this.lines[yb + y]; while (x < this.cols) { if (line[x][1] <= ' ') { saw_space = true; } else if (saw_space) { break; } x++; } if (x >= this.cols) x = this.cols - 1; if (x === this.cols - 1 && line[x][1] <= ' ') { x = 0; if (++y >= this.rows) { y--; if (++yb > this.ybase) { yb = this.ybase; x = this.x; break; } } continue; } break; } this.x = x, this.y = y; this.scrollDisp(-this.ydisp + yb); if (this.visualMode) { this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); } return; } if (key === 'b' || key === 'B') { var ox = this.x; var oy = this.y; var oyd = this.ydisp; var x = this.x; var y = this.y; var yb = this.ydisp; for (;;) { var line = this.lines[yb + y]; var saw_space = x > 0 && line[x][1] > ' ' && line[x - 1][1] > ' '; while (x >= 0) { if (line[x][1] <= ' ') { if (saw_space && (x + 1 < this.cols && line[x + 1][1] > ' ')) { x++; break; } else { saw_space = true; } } x--; } if (x < 0) x = 0; if (x === 0 && (line[x][1] <= ' ' || !saw_space)) { x = this.cols - 1; if (--y < 0) { y++; if (--yb < 0) { yb++; x = 0; break; } } continue; } break; } this.x = x, this.y = y; this.scrollDisp(-this.ydisp + yb); if (this.visualMode) { this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); } return; } if (key === 'e' || key === 'E') { var x = this.x + 1; var y = this.y; var yb = this.ydisp; if (x >= this.cols) x--; for (;;) { var line = this.lines[yb + y]; while (x < this.cols) { if (line[x][1] <= ' ') { x++; } else { break; } } while (x < this.cols) { if (line[x][1] <= ' ') { if (x - 1 >= 0 && line[x - 1][1] > ' ') { x--; break; } } x++; } if (x >= this.cols) x = this.cols - 1; if (x === this.cols - 1 && line[x][1] <= ' ') { x = 0; if (++y >= this.rows) { y--; if (++yb > this.ybase) { yb = this.ybase; break; } } continue; } break; } this.x = x, this.y = y; this.scrollDisp(-this.ydisp + yb); if (this.visualMode) { this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); } return; } if (key === '^' || key === '0') { var ox = this.x; if (key === '0') { this.x = 0; } else if (key === '^') { var line = this.lines[this.ydisp + this.y]; var x = 0; while (x < this.cols) { if (line[x][1] > ' ') { break; } x++; } if (x >= this.cols) x = this.cols - 1; this.x = x; } if (this.visualMode) { this.selectText(ox, this.x, this.ydisp + this.y, this.ydisp + this.y); } else { this.refresh(this.y, this.y); } return; } if (key === '$') { var ox = this.x; var line = this.lines[this.ydisp + this.y]; var x = this.cols - 1; while (x >= 0) { if (line[x][1] > ' ') { if (this.visualMode && x < this.cols - 1) x++; break; } x--; } if (x < 0) x = 0; this.x = x; if (this.visualMode) { this.selectText(ox, this.x, this.ydisp + this.y, this.ydisp + this.y); } else { this.refresh(this.y, this.y); } return; } if (key === 'g' || key === 'G') { var ox = this.x; var oy = this.y; var oyd = this.ydisp; if (key === 'g') { this.x = 0, this.y = 0; this.scrollDisp(-this.ydisp); } else if (key === 'G') { this.x = 0, this.y = this.rows - 1; this.scrollDisp(this.ybase); } if (this.visualMode) { this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); } return; } if (key === 'H' || key === 'M' || key === 'L') { var ox = this.x; var oy = this.y; if (key === 'H') { this.x = 0, this.y = 0; } else if (key === 'M') { this.x = 0, this.y = this.rows / 2 | 0; } else if (key === 'L') { this.x = 0, this.y = this.rows - 1; } if (this.visualMode) { this.selectText(ox, this.x, this.ydisp + oy, this.ydisp + this.y); } else { this.refresh(oy, oy); this.refresh(this.y, this.y); } return; } if (key === '{' || key === '}') { var ox = this.x; var oy = this.y; var oyd = this.ydisp; var line; var saw_full = false; var found = false; var first_is_space = -1; var y = this.y + (key === '{' ? -1 : 1); var yb = this.ydisp; var i; if (key === '{') { if (y < 0) { y++; if (yb > 0) yb--; } } else if (key === '}') { if (y >= this.rows) { y--; if (yb < this.ybase) yb++; } } for (;;) { line = this.lines[yb + y]; for (i = 0; i < this.cols; i++) { if (line[i][1] > ' ') { if (first_is_space === -1) { first_is_space = 0; } saw_full = true; break; } else if (i === this.cols - 1) { if (first_is_space === -1) { first_is_space = 1; } else if (first_is_space === 0) { found = true; } else if (first_is_space === 1) { if (saw_full) found = true; } break; } } if (found) break; if (key === '{') { y--; if (y < 0) { y++; if (yb > 0) yb--; else break; } } else if (key === '}') { y++; if (y >= this.rows) { y--; if (yb < this.ybase) yb++; else break; } } } if (!found) { if (key === '{') { y = 0; yb = 0; } else if (key === '}') { y = this.rows - 1; yb = this.ybase; } } this.x = 0, this.y = y; this.scrollDisp(-this.ydisp + yb); if (this.visualMode) { this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); } return; } if (key === '/' || key === '?') { if (!this.visualMode) { this.enterSearch(key === '/'); } return; } return false; }; Terminal.prototype.keySearch = function(ev, key) { if (key === '\x1b') { this.leaveSearch(); return; } if (key === '\r' || (!this.searchMode && (key === 'n' || key === 'N'))) { this.leaveSearch(); var entry = this.entry; if (!entry) { this.refresh(0, this.rows - 1); return; } var ox = this.x; var oy = this.y; var oyd = this.ydisp; var line; var found = false; var wrapped = false; var x = this.x + 1; var y = this.ydisp + this.y; var yb, i; var up = key === 'N' ? this.searchDown : !this.searchDown; for (;;) { line = this.lines[y]; while (x < this.cols) { for (i = 0; i < entry.length; i++) { if (x + i >= this.cols) break; if (line[x + i][1] !== entry[i]) { break; } else if (line[x + i][1] === entry[i] && i === entry.length - 1) { found = true; break; } } if (found) break; x += i + 1; } if (found) break; x = 0; if (!up) { y++; if (y > this.ybase + this.rows - 1) { if (wrapped) break; // this.setMessage('Search wrapped. Continuing at TOP.'); wrapped = true; y = 0; } } else { y--; if (y < 0) { if (wrapped) break; // this.setMessage('Search wrapped. Continuing at BOTTOM.'); wrapped = true; y = this.ybase + this.rows - 1; } } } if (found) { if (y - this.ybase < 0) { yb = y; y = 0; if (yb > this.ybase) { y = yb - this.ybase; yb = this.ybase; } } else { yb = this.ybase; y -= this.ybase; } this.x = x, this.y = y; this.scrollDisp(-this.ydisp + yb); if (this.visualMode) { this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); } return; } // this.setMessage("No matches found."); this.refresh(0, this.rows - 1); return; } if (key === '\b' || key === '\x7f') { if (this.entry.length === 0) return; var bottom = this.ydisp + this.rows - 1; this.entry = this.entry.slice(0, -1); var i = this.entryPrefix.length + this.entry.length; //this.lines[bottom][i][1] = ' '; this.lines[bottom][i] = [ this.lines[bottom][i][0], ' ' ]; this.x--; this.refresh(this.rows - 1, this.rows - 1); this.refresh(this.y, this.y); return; } if (key.length === 1 && key >= ' ' && key <= '~') { var bottom = this.ydisp + this.rows - 1; this.entry += key; var i = this.entryPrefix.length + this.entry.length - 1; //this.lines[bottom][i][0] = (this.defAttr & ~0x1ff) | 4; //this.lines[bottom][i][1] = key; this.lines[bottom][i] = [ (this.defAttr & ~0x1ff) | 4, key ]; this.x++; this.refresh(this.rows - 1, this.rows - 1); this.refresh(this.y, this.y); return; } return false; }; /** * Character Sets */ Terminal.charsets = {}; // DEC Special Character and Line Drawing Set. // http://vt100.net/docs/vt102-ug/table5-13.html // A lot of curses applications use this if they see TERM=xterm. // testing: echo -e '\e(0a\e(B' // The xterm output sometimes seems to conflict with the // reference above. xterm seems in line with the reference // when running vttest however. // The table below now uses xterm's output from vttest. Terminal.charsets.SCLD = { // (0 '`': '\u25c6', // '◆' 'a': '\u2592', // '▒' 'b': '\u0009', // '\t' 'c': '\u000c', // '\f' 'd': '\u000d', // '\r' 'e': '\u000a', // '\n' 'f': '\u00b0', // '°' 'g': '\u00b1', // '±' 'h': '\u2424', // '\u2424' (NL) 'i': '\u000b', // '\v' 'j': '\u2518', // '┘' 'k': '\u2510', // '┐' 'l': '\u250c', // '┌' 'm': '\u2514', // '└' 'n': '\u253c', // '┼' 'o': '\u23ba', // '⎺' 'p': '\u23bb', // '⎻' 'q': '\u2500', // '─' 'r': '\u23bc', // '⎼' 's': '\u23bd', // '⎽' 't': '\u251c', // '├' 'u': '\u2524', // '┤' 'v': '\u2534', // '┴' 'w': '\u252c', // '┬' 'x': '\u2502', // '│' 'y': '\u2264', // '≤' 'z': '\u2265', // '≥' '{': '\u03c0', // 'π' '|': '\u2260', // '≠' '}': '\u00a3', // '£' '~': '\u00b7' // '·' }; Terminal.charsets.UK = null; // (A Terminal.charsets.US = null; // (B (USASCII) Terminal.charsets.Dutch = null; // (4 Terminal.charsets.Finnish = null; // (C or (5 Terminal.charsets.French = null; // (R Terminal.charsets.FrenchCanadian = null; // (Q Terminal.charsets.German = null; // (K Terminal.charsets.Italian = null; // (Y Terminal.charsets.NorwegianDanish = null; // (E or (6 Terminal.charsets.Spanish = null; // (Z Terminal.charsets.Swedish = null; // (H or (7 Terminal.charsets.Swiss = null; // (= Terminal.charsets.ISOLatin = null; // /A /** * Helpers */ function on(el, type, handler, capture) { el.addEventListener(type, handler, capture || false); } function off(el, type, handler, capture) { el.removeEventListener(type, handler, capture || false); } function cancel(ev) { if (ev.preventDefault) ev.preventDefault(); ev.returnValue = false; if (ev.stopPropagation) ev.stopPropagation(); ev.cancelBubble = true; return false; } function inherits(child, parent) { function f() { this.constructor = child; } f.prototype = parent.prototype; child.prototype = new f; } // if bold is broken, we can't // use it in the applications. function isBoldBroken(document) { var body = document.getElementsByTagName('body')[0]; var terminal = document.createElement('div'); terminal.className = 'terminal'; var line = document.createElement('div'); var el = document.createElement('span'); el.innerHTML = 'hello world'; line.appendChild(el); terminal.appendChild(line); body.appendChild(terminal); var w1 = el.scrollWidth; el.style.fontWeight = 'bold'; var w2 = el.scrollWidth; body.removeChild(terminal); return w1 !== w2; } var String = this.String; var setTimeout = this.setTimeout; var setInterval = this.setInterval; function indexOf(obj, el) { var i = obj.length; while (i--) { if (obj[i] === el) return i; } return -1; } /* Ref: https://github.com/ajaxorg/ace/blob/0c66e1eda418477a9efbd0d3ef61698478cc607f/lib/ace/edit_session.js#L2434 */ function isFullWidth(c) { if (c < 0x1100) return false; return c >= 0x1100 && c <= 0x115F || c >= 0x11A3 && c <= 0x11A7 || c >= 0x11FA && c <= 0x11FF || c >= 0x2329 && c <= 0x232A || c >= 0x2E80 && c <= 0x2E99 || c >= 0x2E9B && c <= 0x2EF3 || c >= 0x2F00 && c <= 0x2FD5 || c >= 0x2FF0 && c <= 0x2FFB || c >= 0x3000 && c <= 0x303E || c >= 0x3041 && c <= 0x3096 || c >= 0x3099 && c <= 0x30FF || c >= 0x3105 && c <= 0x312D || c >= 0x3131 && c <= 0x318E || c >= 0x3190 && c <= 0x31BA || c >= 0x31C0 && c <= 0x31E3 || c >= 0x31F0 && c <= 0x321E || c >= 0x3220 && c <= 0x3247 || c >= 0x3250 && c <= 0x32FE || c >= 0x3300 && c <= 0x4DBF || c >= 0x4E00 && c <= 0xA48C || c >= 0xA490 && c <= 0xA4C6 || c >= 0xA960 && c <= 0xA97C || c >= 0xAC00 && c <= 0xD7A3 || c >= 0xD7B0 && c <= 0xD7C6 || c >= 0xD7CB && c <= 0xD7FB || c >= 0xF900 && c <= 0xFAFF || c >= 0xFE10 && c <= 0xFE19 || c >= 0xFE30 && c <= 0xFE52 || c >= 0xFE54 && c <= 0xFE66 || c >= 0xFE68 && c <= 0xFE6B || c >= 0xFF01 && c <= 0xFF60 || c >= 0xFFE0 && c <= 0xFFE6; }; function isWide(ch) { var c = ch.charCodeAt(0); return isFullWidth(c); } function matchColor(r1, g1, b1) { var hash = (r1 << 16) | (g1 << 8) | b1; if (matchColor._cache[hash] != null) { return matchColor._cache[hash]; } var ldiff = Infinity , li = -1 , i = 0 , c , r2 , g2 , b2 , diff; for (; i < Terminal.vcolors.length; i++) { c = Terminal.vcolors[i]; r2 = c[0]; g2 = c[1]; b2 = c[2]; diff = matchColor.distance(r1, g1, b1, r2, g2, b2); if (diff === 0) { li = i; break; } if (diff < ldiff) { ldiff = diff; li = i; } } return matchColor._cache[hash] = li; } matchColor._cache = {}; // http://stackoverflow.com/questions/1633828 matchColor.distance = function(r1, g1, b1, r2, g2, b2) { return Math.pow(30 * (r1 - r2), 2) + Math.pow(59 * (g1 - g2), 2) + Math.pow(11 * (b1 - b2), 2); }; function each(obj, iter, con) { if (obj.forEach) return obj.forEach(iter, con); for (var i = 0; i < obj.length; i++) { iter.call(con, obj[i], i, obj); } } function keys(obj) { if (Object.keys) return Object.keys(obj); var key, keys = []; for (key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { keys.push(key); } } return keys; } /** * Expose */ Terminal.EventEmitter = EventEmitter; Terminal.Stream = Stream; Terminal.inherits = inherits; Terminal.on = on; Terminal.off = off; Terminal.cancel = cancel; if (typeof module !== 'undefined') { module.exports = Terminal; } else { this.Terminal = Terminal; } }).call(function() { return this || (typeof window !== 'undefined' ? window : global); }());