diff --git a/static/js/terminal/index.js b/static/js/terminal/index.js index 9c1d87f..7c66567 100644 --- a/static/js/terminal/index.js +++ b/static/js/terminal/index.js @@ -1,3 +1,5607 @@ /** - * Created by nuintun on 2015/11/24. + * 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. + * + * 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'; + +var Stream = require('stream'); + +/** + * Shared + */ +var document = this.document; + +/** + * States + */ +var normal = 0; +var escaped = 1; +var csi = 2; +var osc = 3; +var charset = 4; +var dcs = 5; +var ignore = 6; +var 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 = false; + this.isTTY = true; + this.isRaw = true; + this.columns = this.cols; + + 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 = false; + 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 = true; + this.entry = ''; + this.entryPrefix = 'Search: '; + this._real = null; + this._selected = null; + this._textarea = null; + + // charset + this.charset = null; + this.gcharset = null; + this.glevel = 0; + this.charsets = [null]; + + // mouse properties + this.decLocator = null; + this.x10Mouse = null; + this.vt200Mouse = null; + this.vt300Mouse = null; + this.normalMouse = null; + this.mouseEvents = null; + this.sendFocus = null; + this.utfMouse = null; + this.sgrMouse = null; + this.urxvtMouse = null; + + // misc + this.element = null; + this.children = null; + this.refreshStart = null; + this.refreshEnd = null; + this.savedX = null; + this.savedY = null; + this.savedCols = null; + + // 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 = null; + this.setupStops(); +} + +inherits(Terminal, Stream); + +// 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 i; + var colors = Terminal.tangoColors.slice(); + var r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]; + + // 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 = []; + var colors = Terminal.colors; + var i = 0; + var 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 +}; +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 (Terminal.focus === this) return; + + if (Terminal.focus) { + Terminal.focus.blur(); + } + + if (this.sendFocus) this.send('\x1b[I'); + + this.showCursor(); + + 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'); + + 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.isMobile) { + this.fixMobile(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 (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._textarea + || target === Terminal.focus.parent) { + return Terminal.focus.keyDown(ev); + } + }, true); + + on(document, 'keypress', function (ev){ + if (!Terminal.focus) return; + + var target = ev.target || ev.srcElement; + + if (!target) return; + + if (target === Terminal.focus.element + || target === Terminal.focus.context + || target === Terminal.focus.document + || target === Terminal.focus.body + || target === Terminal._textarea + || target === Terminal.focus.parent) { + return Terminal.focus.keyPress(ev); + } + }, true); + + // If we click somewhere other than a + // terminal, unfocus the terminal. + on(document, 'mousedown', function (ev){ + if (!Terminal.focus) return; + + var el = ev.target || ev.srcElement; + + if (!el) 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; + + // 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.fixMobile = function (document){ + var self = this; + var textarea = document.createElement('textarea'); + + textarea.style.position = 'absolute'; + textarea.style.left = '-32000px'; + textarea.style.top = '-32000px'; + textarea.style.width = '0px'; + textarea.style.height = '0px'; + textarea.style.opacity = '0'; + textarea.style.backgroundColor = 'transparent'; + textarea.style.borderStyle = 'none'; + textarea.style.outlineStyle = 'none'; + textarea.autocapitalize = 'none'; + textarea.autocorrect = 'off'; + + document.getElementsByTagName('body')[0].appendChild(textarea); + + Terminal._textarea = textarea; + + setTimeout(function (){ + textarea.focus(); + }, 1000); + + if (this.isAndroid) { + on(textarea, 'change', function (){ + var value = textarea.textContent || textarea.value; + + textarea.value = ''; + textarea.textContent = ''; + self.send(value + '\r'); + }); + } +}; + +/** + * 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; + + style = document.createElement('style'); + style.id = 'term-style'; + + // textContent doesn't work well with IE for