From a25027aceb19a0ab8270bebd30438ecb5eaa45a3 Mon Sep 17 00:00:00 2001 From: Rajat Vig Date: Tue, 5 Jan 2016 14:20:27 -0800 Subject: Update term.js to 0.0.7 --- xstatic/pkg/termjs/__init__.py | 5 +- xstatic/pkg/termjs/data/term.js | 423 ++++++++++++++++++++++++++++++---------- 2 files changed, 322 insertions(+), 106 deletions(-) diff --git a/xstatic/pkg/termjs/__init__.py b/xstatic/pkg/termjs/__init__.py index 85ac28f..c1d3437 100644 --- a/xstatic/pkg/termjs/__init__.py +++ b/xstatic/pkg/termjs/__init__.py @@ -11,9 +11,9 @@ NAME = __name__.split('.')[-1] # package name (e.g. 'foo' or 'foo_bar') # please use a all-lowercase valid python # package name -VERSION = '0.0.4' # version of the packaged files, please use the upstream +VERSION = '0.0.7' # version of the packaged files, please use the upstream # version number -BUILD = '2' # our package build number, so we can release new builds +BUILD = '0' # our package build number, so we can release new builds # with fixes for xstatic stuff. PACKAGE_VERSION = VERSION + '.' + BUILD # version used for PyPi @@ -50,4 +50,3 @@ LOCATIONS = { # information, because either the base dir/url is exactly for this # version or the mapping will care for accessing this version. } - diff --git a/xstatic/pkg/termjs/data/term.js b/xstatic/pkg/termjs/data/term.js index d041e37..f542dd0 100644 --- a/xstatic/pkg/termjs/data/term.js +++ b/xstatic/pkg/termjs/data/term.js @@ -114,6 +114,54 @@ 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 */ @@ -124,7 +172,8 @@ var normal = 0 , osc = 3 , charset = 4 , dcs = 5 - , ignore = 6; + , ignore = 6 + , UDK = { type: 'udk' }; /** * Terminal @@ -137,7 +186,7 @@ function Terminal(options) { return new Terminal(arguments[0], arguments[1], arguments[2]); } - EventEmitter.call(this); + Stream.call(this); if (typeof options === 'number') { options = { @@ -168,7 +217,7 @@ function Terminal(options) { 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.concat( + options.colors = options.colors.slice(0, -2).concat( Terminal._colors.slice(16, -2), options.colors.slice(-2)); } this.colors = options.colors; @@ -183,6 +232,13 @@ function Terminal(options) { 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); } @@ -268,13 +324,7 @@ function Terminal(options) { this.setupStops(); } -inherits(Terminal, EventEmitter); - -// back_color_erase feature for xterm. -Terminal.prototype.eraseAttr = function() { - // if (this.is('screen')) return this.defAttr; - return (this.defAttr & ~0x1ff) | (this.curAttr & 0x1ff); -}; +inherits(Terminal, Stream); /** * Colors @@ -470,8 +520,8 @@ Terminal.prototype.initGlobal = function() { Terminal.bindCopy(document); - if (this.isIpad || this.isIphone) { - Terminal.fixIpad(document); + if (this.isMobile) { + this.fixMobile(document); } if (this.useStyle) { @@ -598,10 +648,12 @@ Terminal.bindCopy = function(document) { }; /** - * Fix iPad - no idea if this works + * Fix Mobile */ -Terminal.fixIpad = function(document) { +Terminal.prototype.fixMobile = function(document) { + var self = this; + var textarea = document.createElement('textarea'); textarea.style.position = 'absolute'; textarea.style.left = '-32000px'; @@ -622,6 +674,15 @@ Terminal.fixIpad = function(document) { 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'); + }); + } }; /** @@ -695,6 +756,8 @@ Terminal.prototype.open = function(parent) { 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'); } @@ -703,6 +766,7 @@ Terminal.prototype.open = function(parent) { 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]; @@ -718,61 +782,77 @@ Terminal.prototype.open = function(parent) { // Draw the screen. this.refresh(0, this.rows - 1); - // Initialize global actions that - // need to be taken on the document. - this.initGlobal(); + if (!('useEvents' in this.options) || this.options.useEvents) { + // Initialize global actions that + // need to be taken on the document. + this.initGlobal(); + } - // Ensure there is a Terminal.focus. - this.focus(); + if (!('useFocus' in this.options) || this.options.useFocus) { + // Ensure there is a Terminal.focus. + this.focus(); - // Start blinking the cursor. - this.startBlink(); + // Start blinking the cursor. + this.startBlink(); - // Bind to DOM events related - // to focus and paste behavior. - on(this.element, 'focus', function() { - self.focus(); - if (self.isIpad || self.isIphone) { - Terminal._textarea.focus(); - } - }); + // Bind to DOM events related + // to focus and paste behavior. + on(this.element, 'focus', function() { + self.focus(); + if (self.isMobile) { + Terminal._textarea.focus(); + } + }); - // This causes slightly funky behavior. - // on(this.element, 'blur', function() { - // self.blur(); - // }); + // This causes slightly funky behavior. + // on(this.element, 'blur', function() { + // self.blur(); + // }); - on(this.element, 'mousedown', function() { - self.focus(); - }); + 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; + } - // 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; - if (button !== 2) return; + self.element.contentEditable = 'true'; + setTimeout(function() { + self.element.contentEditable = 'inherit'; // 'false'; + }, 1); + }, true); + } - 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 terminal mouse protocols. + this.bindMouse(); + } + + // this.emit('open'); - // Listen for mouse events and translate - // them into terminal mouse protocols. - this.bindMouse(); + 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(); + }, 100); + } // Figure out whether boldness affects // the character width of monospace fonts. @@ -780,13 +860,11 @@ Terminal.prototype.open = function(parent) { Terminal.brokenBold = isBoldBroken(this.document); } - // this.emit('open'); + this.emit('open'); +}; - // This can be useful for pasting, - // as well as the iPad fix. - setTimeout(function() { - self.element.focus(); - }, 100); +Terminal.prototype.setRawMode = function(value) { + this.isRaw = !!value; }; // XTerm mouse events @@ -1079,10 +1157,11 @@ Terminal.prototype.bindMouse = function() { // fix for odd bug //if (self.vt200Mouse && !self.normalMouse) { - if (self.vt200Mouse) { - sendButton({ __proto__: ev, type: 'mouseup' }); - return cancel(ev); - } + // 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); @@ -1131,16 +1210,35 @@ Terminal.prototype.bindMouse = function() { * 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('close'); + + this.emit('end'); + this.emit('close'); + this.emit('finish'); + this.emit('destroy'); }; /** @@ -1344,7 +1442,7 @@ Terminal.prototype.startBlink = function() { }; Terminal.prototype.refreshBlink = function() { - if (!this.cursorBlink) return; + if (!this.cursorBlink || !this._blink) return; clearInterval(this._blink); this._blink = setInterval(this._blinker, 500); }; @@ -1418,7 +1516,7 @@ Terminal.prototype.write = function(data) { // this.log(JSON.stringify(data.replace(/\x1b/g, '^['))); - for (; i < l; i++) { + for (; i < l; i++, this.lch = ch) { ch = data[i]; switch (this.state) { case normal: @@ -1534,7 +1632,8 @@ Terminal.prototype.write = function(data) { // ESC P Device Control String ( DCS is 0x90). case 'P': this.params = []; - this.currentParam = 0; + this.prefix = ''; + this.currentParam = ''; this.state = dcs; break; @@ -1757,8 +1856,14 @@ Terminal.prototype.write = function(data) { // OSC Ps ; Pt ST // OSC Ps ; Pt BEL // Set Text Parameters. - if (ch === '\x1b' || ch === '\x07') { - if (ch === '\x1b') i++; + 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); @@ -2290,94 +2395,158 @@ Terminal.prototype.write = function(data) { break; case dcs: - if (ch === '\x1b' || ch === '\x07') { - if (ch === '\x1b') i++; + 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). - case '': + // 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 pt = this.currentParam - , valid = false; + 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"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': - pt = '0m'; + // TODO: Parse this.curAttr here. + // pt = '0m'; + // valid = 1; + valid = 0; // Not implemented. break; default: this.error('Unknown DCS Pt: %s.', pt); - pt = ''; + valid = 0; // unimplemented break; } - this.send('\x1bP' + +valid + '$r' + pt + '\x1b\\'); + 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 pt = this.currentParam - , valid = false; - + 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.', this.prefix); + this.error('Unknown DCS prefix: %s.', pt); break; } this.currentParam = 0; this.prefix = ''; this.state = normal; - } else if (!this.currentParam) { - if (!this.prefix && ch !== '$' && ch !== '+') { - this.currentParam = ch; - } else if (this.prefix.length === 2) { - this.currentParam = ch; - } else { - this.prefix += ch; - } } 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 (ch === '\x1b' || ch === '\x07') { - if (ch === '\x1b') i++; + if ((this.lch === '\x1b' && ch === '\\') || ch === '\x07') { this.state = normal; } break; @@ -2386,10 +2555,29 @@ Terminal.prototype.write = function(data) { this.updateRange(this.y); this.refresh(this.refreshStart, this.refreshEnd); + + return true; }; Terminal.prototype.writeln = function(data) { - this.write(data + '\r\n'); + 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: @@ -2401,6 +2589,10 @@ Terminal.prototype.keyDown = function(ev) { switch (ev.keyCode) { // backspace case 8: + if (ev.altKey) { + key = '\x17'; + break; + } if (ev.shiftKey) { key = '\x08'; // ^H break; @@ -2430,6 +2622,10 @@ Terminal.prototype.keyDown = function(ev) { //key = '\x8fD'; // SS3 as 0x8f for 8-bit break; } + if (ev.ctrlKey) { + key = '\x1b[5D'; + break; + } key = '\x1b[D'; break; // right-arrow @@ -2438,6 +2634,10 @@ Terminal.prototype.keyDown = function(ev) { key = '\x1bOC'; break; } + if (ev.ctrlKey) { + key = '\x1b[5C'; + break; + } key = '\x1b[C'; break; // up-arrow @@ -2598,7 +2798,7 @@ Terminal.prototype.keyDown = function(ev) { // ^] - group sep key = String.fromCharCode(29); } - } else if ((!this.isMac && ev.altKey) || (this.isMac && ev.metaKey)) { + } else if (ev.altKey) { if (ev.keyCode >= 65 && ev.keyCode <= 90) { key = '\x1b' + String.fromCharCode(ev.keyCode + 32); } else if (ev.keyCode === 192) { @@ -2696,6 +2896,7 @@ Terminal.prototype.send = function(data) { }; Terminal.prototype.bell = function() { + this.emit('bell'); if (!this.visualBell) return; var self = this; this.element.style.borderColor = 'white'; @@ -2749,6 +2950,7 @@ Terminal.prototype.resize = function(x, y) { } this.setupStops(j); this.cols = x; + this.columns = x; // resize rows j = this.rows; @@ -2792,6 +2994,9 @@ Terminal.prototype.resize = function(x, y) { // 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) { @@ -2841,6 +3046,12 @@ Terminal.prototype.nextStop = function(x) { : 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 @@ -5637,13 +5848,18 @@ function inherits(child, parent) { // use it in the terminal. 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'; - body.appendChild(el); + line.appendChild(el); + terminal.appendChild(line); + body.appendChild(terminal); var w1 = el.scrollWidth; el.style.fontWeight = 'bold'; var w2 = el.scrollWidth; - body.removeChild(el); + body.removeChild(terminal); return w1 !== w2; } @@ -5740,6 +5956,7 @@ function keys(obj) { */ Terminal.EventEmitter = EventEmitter; +Terminal.Stream = Stream; Terminal.inherits = inherits; Terminal.on = on; Terminal.off = off; -- cgit v1.2.1