From d4a1eb387dc71ba56d7e59a870a6b30909687b1a Mon Sep 17 00:00:00 2001 From: nuintun Date: Mon, 30 Nov 2015 14:16:33 +0800 Subject: [PATCH] update files --- static/css/index.css | 2 +- static/js/terminal/xterm.js | 467 ++++++++++++++++++++++++++++++++++++ 2 files changed, 468 insertions(+), 1 deletion(-) create mode 100644 static/js/terminal/xterm.js diff --git a/static/css/index.css b/static/css/index.css index 1ec9237..b0270d6 100644 --- a/static/css/index.css +++ b/static/css/index.css @@ -156,7 +156,7 @@ body { body { border: 1px solid #ccc; box-sizing: border-box; - min-width: 1024px; !important; + min-width: 1024px !important; min-height: 768px !important; } a { diff --git a/static/js/terminal/xterm.js b/static/js/terminal/xterm.js new file mode 100644 index 0000000..169835b --- /dev/null +++ b/static/js/terminal/xterm.js @@ -0,0 +1,467 @@ +/** + * Created by nuintun on 2015/11/30. + */ + +'use strict'; + +/** + * range function for numbers [a, .., b-1] + * + * @param {number} a + * @param {number} b + * @return {Array} + */ +function r(a, b){ + var c = b - a; + var arr = new Array(c); + + while (c--) { + arr[c] = --b; + } + + return arr; +} + +/** + * Add a transition to the transition table. + * + * @param table - table to add transition + * @param {number} inp - input character code + * @param {number} state - current state + * @param {number=} action - action to be taken + * @param {number=} next - next state + */ +function add(table, inp, state, action, next){ + table[state << 8 | inp] = ((action | 0) << 4) | ((next === undefined) ? state : next); +} + +/** + * Add multiple transitions to the transition table. + * + * @param table - table to add transition + * @param {Array} inps - array of input character codes + * @param {number} state - current state + * @param {number=} action - action to be taken + * @param {number=} next - next state + */ +function add_list(table, inps, state, action, next){ + for (var i = 0; i < inps.length; i++) + add(table, inps[i], state, action, next); +} + +/** global definition of printables and executables */ +var PRINTABLES = r(0x20, 0x7f); +var EXECUTABLES = r(0x00, 0x18); +EXECUTABLES.push(0x19); +EXECUTABLES.concat(r(0x1c, 0x20)); + +/** + * create the standard transition table - used by all parser instances + * + * table[state << 8 | character code] = action << 4 | next state + * + * - states are numbers from 0 to 13 + * - control character codes defined from 0 to 159 (C0 and C1) + * - actions are numbers from 0 to 14 + * - any higher character than 159 is handled by the 'error' action + * + * state replacements (14 states): + * 'GROUND' -> 0 + * 'ESCAPE' -> 1 + * 'ESCAPE_INTERMEDIATE' -> 2 + * 'CSI_ENTRY' -> 3 + * 'CSI_PARAM' -> 4 + * 'CSI_INTERMEDIATE' -> 5 + * 'CSI_IGNORE' -> 6 + * 'SOS_PM_APC_STRING' -> 7 + * 'OSC_STRING' -> 8 + * 'DCS_ENTRY' -> 9 + * 'DCS_PARAM' -> 10 + * 'DCS_IGNORE' -> 11 + * 'DCS_INTERMEDIATE' -> 12 + * 'DCS_PASSTHROUGH' -> 13 + * + * action replacements (15 actions): + * 'no action' -> 0 + * 'error' -> 1 + * 'print' -> 2 + * 'execute' -> 3 + * 'osc_start' -> 4 + * 'osc_put' -> 5 + * 'osc_end' -> 6 + * 'csi_dispatch' -> 7 + * 'param' -> 8 + * 'collect' -> 9 + * 'esc_dispatch' -> 10 + * 'clear' -> 11 + * 'dcs_hook' -> 12 + * 'dcs_put' -> 13 + * 'dcs_unhook' -> 14 + */ +var TRANSITION_TABLE = (function (){ + //var table = []; + var table = new Uint8Array(4095); + + // table with default transition [any] --> [error, GROUND] + for (var state = 0; state < 14; ++state) { + for (var code = 0; code < 160; ++code) { + table[state << 8 | code] = 16; + } + } + + // apply transitions + // printables + add_list(table, PRINTABLES, 0, 2); + + // global anywhere rules + for (state = 0; state < 14; ++state) { + add_list(table, [0x18, 0x1a, 0x99, 0x9a], state, 3, 0); + add_list(table, r(0x80, 0x90), state, 3, 0); + add_list(table, r(0x90, 0x98), state, 3, 0); + add(table, 0x9c, state, 0, 0); // ST as terminator + add(table, 0x1b, state, 11, 1); // ESC + add(table, 0x9d, state, 4, 8); // OSC + add_list(table, [0x98, 0x9e, 0x9f], state, 0, 7); + add(table, 0x9b, state, 11, 3); // CSI + add(table, 0x90, state, 11, 9); // DCS + } + + // rules for executables and 7f + add_list(table, EXECUTABLES, 0, 3); + add_list(table, EXECUTABLES, 1, 3); + add(table, 0x7f, 1); + add_list(table, EXECUTABLES, 8); + add_list(table, EXECUTABLES, 3, 3); + add(table, 0x7f, 3); + add_list(table, EXECUTABLES, 4, 3); + add(table, 0x7f, 4); + add_list(table, EXECUTABLES, 6, 3); + add_list(table, EXECUTABLES, 5, 3); + add(table, 0x7f, 5); + add_list(table, EXECUTABLES, 2, 3); + add(table, 0x7f, 2); + // osc + add(table, 0x5d, 1, 4, 8); + add_list(table, PRINTABLES, 8, 5); + add(table, 0x7f, 8, 5); + add_list(table, [0x9c, 0x1b, 0x18, 0x1a, 0x07], 8, 6, 0); + add_list(table, r(0x1c, 0x20), 8, 0); + // sos/pm/apc does nothing + add_list(table, [0x58, 0x5e, 0x5f], 1, 0, 7); + add_list(table, PRINTABLES, 7); + add_list(table, EXECUTABLES, 7); + add(table, 0x9c, 7, 0, 0); + // csi entries + add(table, 0x5b, 1, 11, 3); + add_list(table, r(0x40, 0x7f), 3, 7, 0); + add_list(table, r(0x30, 0x3a), 3, 8, 4); + add(table, 0x3b, 3, 8, 4); + add_list(table, [0x3c, 0x3d, 0x3e, 0x3f], 3, 9, 4); + add_list(table, r(0x30, 0x3a), 4, 8); + add(table, 0x3b, 4, 8); + add_list(table, r(0x40, 0x7f), 4, 7, 0); + add_list(table, [0x3a, 0x3c, 0x3d, 0x3e, 0x3f], 4, 0, 6); + add_list(table, r(0x20, 0x40), 6); + add(table, 0x7f, 6); + add_list(table, r(0x40, 0x7f), 6, 0, 0); + add(table, 0x3a, 3, 0, 6); + add_list(table, r(0x20, 0x30), 3, 9, 5); + add_list(table, r(0x20, 0x30), 5, 9); + add_list(table, r(0x30, 0x40), 5, 0, 6); + add_list(table, r(0x40, 0x7f), 5, 7, 0); + add_list(table, r(0x20, 0x30), 4, 9, 5); + // esc_intermediate + add_list(table, r(0x20, 0x30), 1, 9, 2); + add_list(table, r(0x20, 0x30), 2, 9); + add_list(table, r(0x30, 0x7f), 2, 10, 0); + add_list(table, r(0x30, 0x50), 1, 10, 0); + add_list(table, [0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x59, 0x5a, 0x5c], 1, 10, 0); + add_list(table, r(0x60, 0x7f), 1, 10, 0); + // dcs entry + add(table, 0x50, 1, 11, 9); + add_list(table, EXECUTABLES, 9); + add(table, 0x7f, 9); + add_list(table, r(0x1c, 0x20), 9); + add_list(table, r(0x20, 0x30), 9, 9, 12); + add(table, 0x3a, 9, 0, 11); + add_list(table, r(0x30, 0x3a), 9, 8, 10); + add(table, 0x3b, 9, 8, 10); + add_list(table, [0x3c, 0x3d, 0x3e, 0x3f], 9, 9, 10); + add_list(table, EXECUTABLES, 11); + add_list(table, r(0x20, 0x80), 11); + add_list(table, r(0x1c, 0x20), 11); + add_list(table, EXECUTABLES, 10); + add(table, 0x7f, 10); + add_list(table, r(0x1c, 0x20), 10); + add_list(table, r(0x30, 0x3a), 10, 8); + add(table, 0x3b, 10, 8); + add_list(table, [0x3a, 0x3c, 0x3d, 0x3e, 0x3f], 10, 0, 11); + add_list(table, r(0x20, 0x30), 10, 9, 12); + add_list(table, EXECUTABLES, 12); + add(table, 0x7f, 12); + add_list(table, r(0x1c, 0x20), 12); + add_list(table, r(0x20, 0x30), 12, 9); + add_list(table, r(0x30, 0x40), 12, 0, 11); + add_list(table, r(0x40, 0x7f), 12, 12, 13); + add_list(table, r(0x40, 0x7f), 10, 12, 13); + add_list(table, r(0x40, 0x7f), 9, 12, 13); + add_list(table, EXECUTABLES, 13, 13); + add_list(table, PRINTABLES, 13, 13); + add(table, 0x7f, 13); + add_list(table, [0x1b, 0x9c], 13, 14, 0); + + return table; +})(); + +/** + * helper for param conversion + * @param {string} params - params string with ; + * @return {Array} + */ +function parse_params(params){ + // params are separated by ';' + // empty defaults to 0 + var p = params.split(';'); + + for (var i = 0; i < p.length; ++i) { + p[i] = Number(p[i]); + } + + return p; +} + +/** + * AnsiParser - Parser for ANSI terminal escape sequences. + * + * @param {Object=} terminal + * @constructor + */ +function AnsiParser(terminal){ + this.initial_state = 0; // 'GROUND' is default + this.current_state = this.initial_state; + + // global non pushable buffers for multiple parse invocations + this.osc = ''; + this.params = ''; + this.collected = ''; + + // back reference to terminal + this.term = terminal || {}; + var instructions = [ + 'inst_p', 'inst_o', 'inst_x', 'inst_c', + 'inst_e', 'inst_H', 'inst_P', 'inst_U', 'inst_E' + ]; + + for (var i = 0; i < instructions.length; ++i) { + if (!(instructions[i] in this.term)) { + this.term[instructions[i]] = function (){}; + } + } +} + +/** + * Reset the parser. + */ +AnsiParser.prototype.reset = function (){ + this.current_state = this.initial_state; + this.osc = ''; + this.params = ''; + this.collected = ''; +}; + +/** + * Parse string s. + * @param {string} s + */ +AnsiParser.prototype.parse = function (s){ + var error = false; + var c, code, transition; + var current_state = this.current_state; + + // local buffers + var printed = ''; + var dcs = ''; + var osc = this.osc; + var collected = this.collected; + var params = this.params; + + // process input string + for (var i = 0; i < s.length; ++i) { + c = s.charAt(i); + code = c.charCodeAt(0); + + if (code < 0xa0) { + transition = TRANSITION_TABLE[current_state << 8 | code]; + } else { + transition = 16; + } + + switch (transition >> 4) { + case 0: + // no action + break; + case 1: + // error + // NOTE: real error recovery is not implemented + // handle unicode chars in write buffers w'o state change + if (code > 0x9f) { + switch (current_state) { + case 0: + // GROUND -> add char to print string + printed += c; + break; + case 8: + // OSC_STRING -> add char to osc string + osc += c; + transition |= 8; + break; + case 6: + // CSI_IGNORE -> ignore char + transition |= 6; + break; + case 11: + // DCS_IGNORE -> ignore char + transition |= 11; + break; + case 13: + // DCS_PASSTHROUGH -> add char to dcs + dcs += c; + transition |= 13; + break; + default: + error = true; + } + } else { + error = true; + } + + if (error) { + if ( + this.term.inst_E({ + pos: i, // position in parse string + character: c, // wrong character + state: current_state, // in state + print: printed, // print buffer + dcs: dcs, // dcs buffer + osc: osc, // osc buffer + collect: collected, // collect buffer + params: params // params buffer + }) + ) { + return; + } + error = false; + } + break; + case 2: + // print + printed += c; + break; + case 3: + // execute + if (printed) { + this.term.inst_p(printed); + } + + printed = ''; + + this.term.inst_x(c); + break; + case 7: + // csi_dispatch + this.term.inst_c(collected, parse_params(params), c); + break; + case 8: + // param + params += c; + break; + case 9: + // collect + collected += c; + break; + case 10: + // esc_dispatch + this.term.inst_e(collected, c); + break; + case 11: + // clear + if (printed) { + this.term.inst_p(printed); + } + + printed = ''; + osc = ''; + params = ''; + collected = ''; + dcs = ''; + break; + case 4: + // osc_start + if (printed) { + this.term.inst_p(printed); + } + + printed = ''; + osc = ''; + break; + case 5: + // osc_put + osc += c; + break; + case 6: + // osc_end + if (osc && code !== 0x18 && code !== 0x1a) { + this.term.inst_o(osc); + } + + if (code === 0x1b) { + transition |= 1; + } + + osc = ''; + params = ''; + collected = ''; + dcs = ''; + break; + case 12: // dcs_hook + this.term.inst_H(collected, parse_params(params), c); + break; + case 13: // dcs_put + dcs += c; + break; + case 14: // dcs_unhook + if (dcs) { + this.term.inst_P(dcs); + } + + this.term.inst_U(); + + if (code === 0x1b) { + transition |= 1; + } + + osc = ''; + params = ''; + collected = ''; + dcs = ''; + break; + } + + current_state = transition & 15; + } + + // push leftover pushable buffers to terminal + if (!current_state && printed) { + this.term.inst_p(printed); + } else if (current_state === 13 && dcs) { + this.term.inst_P(dcs); + } + + // save non pushable buffers + this.osc = osc; + this.collected = collected; + this.params = params; + + // save state + this.current_state = current_state; +};