439 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			439 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  **      ==============================
 | |
|  **       O           O      O   OOOO
 | |
|  **       O           O     O O  O   O
 | |
|  **       O           O     O O  O   O
 | |
|  **       OOOO   OOOO O     OOO  OOOO
 | |
|  **       O   O       O    O   O O   O
 | |
|  **       O   O       O    O   O O   O
 | |
|  **       OOOO        OOOO O   O OOOO
 | |
|  **      ==============================
 | |
|  **      Dr. Stefan Bosse http://www.bsslab.de
 | |
|  **
 | |
|  **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 | |
|  **                 BY THE AUTHOR(S).
 | |
|  **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 | |
|  **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 | |
|  **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 | |
|  **
 | |
|  **    $AUTHORS:     Christopher Jeffrey, Stefan Bosse
 | |
|  **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 | |
|  **    $MODIFIED:    by sbosse
 | |
|  **    $REVESIO:     1.2.2
 | |
|  **
 | |
|  **    $INFO:
 | |
|  **
 | |
|  **    terminal.js - term.js terminal element for blessed
 | |
|  **    TODO: reolsve pty.js dependency
 | |
|  **
 | |
|  **    $ENDOFINFO
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Modules
 | |
|  */
 | |
| var Comp = Require('com/compat');
 | |
| var nextTick = global.setImmediate || process.nextTick.bind(process);
 | |
| 
 | |
| var Node = Require('term/widgets/node');
 | |
| var Box = Require('term/widgets/box');
 | |
| 
 | |
| /**
 | |
|  * Terminal
 | |
|  */
 | |
| 
 | |
| function Terminal(options) {
 | |
|   if (!instanceOf(this,Node)) {
 | |
|     return new Terminal(options);
 | |
|   }
 | |
| 
 | |
|   options = options || {};
 | |
|   options.scrollable = false;
 | |
| 
 | |
|   Box.call(this, options);
 | |
| 
 | |
|   // XXX Workaround for all motion
 | |
|   if (this.screen.program.tmux && this.screen.program.tmuxVersion >= 2) {
 | |
|     this.screen.program.enableMouse();
 | |
|   }
 | |
| 
 | |
|   this.handler = options.handler;
 | |
|   this.shell = options.shell || process.env.SHELL || 'sh';
 | |
|   this.args = options.args || [];
 | |
| 
 | |
|   this.cursor = this.options.cursor;
 | |
|   this.cursorBlink = this.options.cursorBlink;
 | |
|   this.screenKeys = this.options.screenKeys;
 | |
| 
 | |
|   this.style = this.style || {};
 | |
|   this.style.bg = this.style.bg || 'default';
 | |
|   this.style.fg = this.style.fg || 'default';
 | |
| 
 | |
|   this.termName = options.terminal
 | |
|     || options.term
 | |
|     || process.env.TERM
 | |
|     || 'xterm';
 | |
| 
 | |
|   this.bootstrap();
 | |
| }
 | |
| 
 | |
| //Terminal.prototype.__proto__ = Box.prototype;
 | |
| inheritPrototype(Terminal,Box);
 | |
| 
 | |
| Terminal.prototype.type = 'terminal';
 | |
| 
 | |
| Terminal.prototype.bootstrap = function() {
 | |
|   var self = this;
 | |
| 
 | |
|   var element = {
 | |
|     // window
 | |
|     get document() { return element; },
 | |
|     navigator: { userAgent: 'node.js' },
 | |
| 
 | |
|     // document
 | |
|     get defaultView() { return element; },
 | |
|     get documentElement() { return element; },
 | |
|     createElement: function() { return element; },
 | |
| 
 | |
|     // element
 | |
|     get ownerDocument() { return element; },
 | |
|     addEventListener: function() {},
 | |
|     removeEventListener: function() {},
 | |
|     getElementsByTagName: function() { return [element]; },
 | |
|     getElementById: function() { return element; },
 | |
|     parentNode: null,
 | |
|     offsetParent: null,
 | |
|     appendChild: function() {},
 | |
|     removeChild: function() {},
 | |
|     setAttribute: function() {},
 | |
|     getAttribute: function() {},
 | |
|     style: {},
 | |
|     focus: function() {},
 | |
|     blur: function() {},
 | |
|     console: console
 | |
|   };
 | |
| 
 | |
|   element.parentNode = element;
 | |
|   element.offsetParent = element;
 | |
| 
 | |
|   this.term = Require('term/widgets/term')({
 | |
|     termName: this.termName,
 | |
|     cols: this.width - this.iwidth,
 | |
|     rows: this.height - this.iheight,
 | |
|     context: element,
 | |
|     document: element,
 | |
|     body: element,
 | |
|     parent: element,
 | |
|     cursorBlink: this.cursorBlink,
 | |
|     screenKeys: this.screenKeys
 | |
|   });
 | |
| 
 | |
|   this.term.refresh = function() {
 | |
|     self.screen.render();
 | |
|   };
 | |
| 
 | |
|   this.term.keyDown = function() {};
 | |
|   this.term.keyPress = function() {};
 | |
| 
 | |
|   this.term.open(element);
 | |
| 
 | |
|   // Emits key sequences in html-land.
 | |
|   // Technically not necessary here.
 | |
|   // In reality if we wanted to be neat, we would overwrite the keyDown and
 | |
|   // keyPress methods with our own node.js-keys->terminal-keys methods, but
 | |
|   // since all the keys are already coming in as escape sequences, we can just
 | |
|   // send the input directly to the handler/socket (see below).
 | |
|   // this.term.on('data', function(data) {
 | |
|   //   self.handler(data);
 | |
|   // });
 | |
| 
 | |
|   // Incoming keys and mouse inputs.
 | |
|   // NOTE: Cannot pass mouse events - coordinates will be off!
 | |
|   this.screen.program.input.on('data', this._onData = function(data) {
 | |
|     if (self.screen.focused === self && !self._isMouse(data)) {
 | |
|       self.handler(data);
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   this.onScreenEvent('mouse', function(data) {
 | |
|     if (self.screen.focused !== self) return;
 | |
| 
 | |
|     if (data.x < self.aleft + self.ileft) return;
 | |
|     if (data.y < self.atop + self.itop) return;
 | |
|     if (data.x > self.aleft - self.ileft + self.width) return;
 | |
|     if (data.y > self.atop - self.itop + self.height) return;
 | |
| 
 | |
|     if (self.term.x10Mouse
 | |
|         || self.term.vt200Mouse
 | |
|         || self.term.normalMouse
 | |
|         || self.term.mouseEvents
 | |
|         || self.term.utfMouse
 | |
|         || self.term.sgrMouse
 | |
|         || self.term.urxvtMouse) {
 | |
|       ;
 | |
|     } else {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     var b = data.raw[0]
 | |
|       , x = data.x - self.aleft
 | |
|       , y = data.y - self.atop
 | |
|       , s;
 | |
| 
 | |
|     if (self.term.urxvtMouse) {
 | |
|       if (self.screen.program.sgrMouse) {
 | |
|         b += 32;
 | |
|       }
 | |
|       s = '\x1b[' + b + ';' + (x + 32) + ';' + (y + 32) + 'M';
 | |
|     } else if (self.term.sgrMouse) {
 | |
|       if (!self.screen.program.sgrMouse) {
 | |
|         b -= 32;
 | |
|       }
 | |
|       s = '\x1b[<' + b + ';' + x + ';' + y
 | |
|         + (data.action === 'mousedown' ? 'M' : 'm');
 | |
|     } else {
 | |
|       if (self.screen.program.sgrMouse) {
 | |
|         b += 32;
 | |
|       }
 | |
|       s = '\x1b[M'
 | |
|         + String.fromCharCode(b)
 | |
|         + String.fromCharCode(x + 32)
 | |
|         + String.fromCharCode(y + 32);
 | |
|     }
 | |
| 
 | |
|     self.handler(s);
 | |
|   });
 | |
| 
 | |
|   this.on('focus', function() {
 | |
|     self.term.focus();
 | |
|   });
 | |
| 
 | |
|   this.on('blur', function() {
 | |
|     self.term.blur();
 | |
|   });
 | |
| 
 | |
|   this.term.on('title', function(title) {
 | |
|     self.title = title;
 | |
|     self.emit('title', title);
 | |
|   });
 | |
| 
 | |
|   this.term.on('passthrough', function(data) {
 | |
|     self.screen.program.flush();
 | |
|     self.screen.program._owrite(data);
 | |
|   });
 | |
| 
 | |
|   this.on('resize', function() {
 | |
|     nextTick(function() {
 | |
|       self.term.resize(self.width - self.iwidth, self.height - self.iheight);
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   this.once('render', function() {
 | |
|     self.term.resize(self.width - self.iwidth, self.height - self.iheight);
 | |
|   });
 | |
| 
 | |
|   this.on('destroy', function() {
 | |
|     self.kill();
 | |
|     self.screen.program.input.removeListener('data', self._onData);
 | |
|   });
 | |
| 
 | |
|   if (this.handler) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   this.pty = Require('term/widgets/pty.js').fork(this.shell, this.args, {
 | |
|     name: this.termName,
 | |
|     cols: this.width - this.iwidth,
 | |
|     rows: this.height - this.iheight,
 | |
|     cwd: process.env.HOME,
 | |
|     env: this.options.env || process.env
 | |
|   });
 | |
| 
 | |
|   this.on('resize', function() {
 | |
|     nextTick(function() {
 | |
|       try {
 | |
|         self.pty.resize(self.width - self.iwidth, self.height - self.iheight);
 | |
|       } catch (e) {
 | |
|         ;
 | |
|       }
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   this.handler = function(data) {
 | |
|     self.pty.write(data);
 | |
|     self.screen.render();
 | |
|   };
 | |
| 
 | |
|   this.pty.on('data', function(data) {
 | |
|     self.write(data);
 | |
|     self.screen.render();
 | |
|   });
 | |
| 
 | |
|   this.pty.on('exit', function(code) {
 | |
|     self.emit('exit', code || null);
 | |
|   });
 | |
| 
 | |
|   this.onScreenEvent('keypress', function() {
 | |
|     self.screen.render();
 | |
|   });
 | |
| 
 | |
|   this.screen._listenKeys(this);
 | |
| };
 | |
| 
 | |
| Terminal.prototype.write = function(data) {
 | |
|   return this.term.write(data);
 | |
| };
 | |
| 
 | |
| Terminal.prototype.render = function() {
 | |
|   var ret = this._render();
 | |
|   if (!ret) return;
 | |
| 
 | |
|   this.dattr = this.sattr(this.style);
 | |
| 
 | |
|   var xi = ret.xi + this.ileft
 | |
|     , xl = ret.xl - this.iright
 | |
|     , yi = ret.yi + this.itop
 | |
|     , yl = ret.yl - this.ibottom
 | |
|     , cursor;
 | |
| 
 | |
|   var scrollback = this.term.lines.length - (yl - yi);
 | |
| 
 | |
|   for (var y = Math.max(yi, 0); y < yl; y++) {
 | |
|     var line = this.screen.lines[y];
 | |
|     if (!line || !this.term.lines[scrollback + y - yi]) break;
 | |
| 
 | |
|     if (y === yi + this.term.y
 | |
|         && this.term.cursorState
 | |
|         && this.screen.focused === this
 | |
|         && (this.term.ydisp === this.term.ybase || this.term.selectMode)
 | |
|         && !this.term.cursorHidden) {
 | |
|       cursor = xi + this.term.x;
 | |
|     } else {
 | |
|       cursor = -1;
 | |
|     }
 | |
| 
 | |
|     for (var x = Math.max(xi, 0); x < xl; x++) {
 | |
|       if (!line[x] || !this.term.lines[scrollback + y - yi][x - xi]) break;
 | |
| 
 | |
|       line[x][0] = this.term.lines[scrollback + y - yi][x - xi][0];
 | |
| 
 | |
|       if (x === cursor) {
 | |
|         if (this.cursor === 'line') {
 | |
|           line[x][0] = this.dattr;
 | |
|           line[x][1] = '\u2502';
 | |
|           continue;
 | |
|         } else if (this.cursor === 'underline') {
 | |
|           line[x][0] = this.dattr | (2 << 18);
 | |
|         } else if (this.cursor === 'block' || !this.cursor) {
 | |
|           line[x][0] = this.dattr | (8 << 18);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       line[x][1] = this.term.lines[scrollback + y - yi][x - xi][1];
 | |
| 
 | |
|       // default foreground = 257
 | |
|       if (((line[x][0] >> 9) & 0x1ff) === 257) {
 | |
|         line[x][0] &= ~(0x1ff << 9);
 | |
|         line[x][0] |= ((this.dattr >> 9) & 0x1ff) << 9;
 | |
|       }
 | |
| 
 | |
|       // default background = 256
 | |
|       if ((line[x][0] & 0x1ff) === 256) {
 | |
|         line[x][0] &= ~0x1ff;
 | |
|         line[x][0] |= this.dattr & 0x1ff;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     line.dirty = true;
 | |
|   }
 | |
| 
 | |
|   return ret;
 | |
| };
 | |
| 
 | |
| Terminal.prototype._isMouse = function(buf) {
 | |
|   var s = buf;
 | |
|   if (Buffer.isBuffer(s)) {
 | |
|     if (s[0] > 127 && s[1] === undefined) {
 | |
|       s[0] -= 128;
 | |
|       s = '\x1b' + s.toString('utf-8');
 | |
|     } else {
 | |
|       s = s.toString('utf-8');
 | |
|     }
 | |
|   }
 | |
|   return (buf[0] === 0x1b && buf[1] === 0x5b && buf[2] === 0x4d)
 | |
|     || /^\x1b\[M([\x00\u0020-\ufffe]{3})/.test(s)
 | |
|     || /^\x1b\[(\d+;\d+;\d+)M/.test(s)
 | |
|     || /^\x1b\[<(\d+;\d+;\d+)([mM])/.test(s)
 | |
|     || /^\x1b\[<(\d+;\d+;\d+;\d+)&w/.test(s)
 | |
|     || /^\x1b\[24([0135])~\[(\d+),(\d+)\]\r/.test(s)
 | |
|     || /^\x1b\[(O|I)/.test(s);
 | |
| };
 | |
| 
 | |
| Terminal.prototype.setScroll =
 | |
| Terminal.prototype.scrollTo = function(offset) {
 | |
|   this.term.ydisp = offset;
 | |
|   return this.emit('scroll');
 | |
| };
 | |
| 
 | |
| Terminal.prototype.getScroll = function() {
 | |
|   return this.term.ydisp;
 | |
| };
 | |
| 
 | |
| Terminal.prototype.scroll = function(offset) {
 | |
|   this.term.scrollDisp(offset);
 | |
|   return this.emit('scroll');
 | |
| };
 | |
| 
 | |
| Terminal.prototype.resetScroll = function() {
 | |
|   this.term.ydisp = 0;
 | |
|   this.term.ybase = 0;
 | |
|   return this.emit('scroll');
 | |
| };
 | |
| 
 | |
| Terminal.prototype.getScrollHeight = function() {
 | |
|   return this.term.rows - 1;
 | |
| };
 | |
| 
 | |
| Terminal.prototype.getScrollPerc = function() {
 | |
|   return (this.term.ydisp / this.term.ybase) * 100;
 | |
| };
 | |
| 
 | |
| Terminal.prototype.setScrollPerc = function(i) {
 | |
|   return this.setScroll((i / 100) * this.term.ybase | 0);
 | |
| };
 | |
| 
 | |
| Terminal.prototype.screenshot = function(xi, xl, yi, yl) {
 | |
|   xi = 0 + (xi || 0);
 | |
|   if (xl != null) {
 | |
|     xl = 0 + (xl || 0);
 | |
|   } else {
 | |
|     xl = this.term.lines[0].length;
 | |
|   }
 | |
|   yi = 0 + (yi || 0);
 | |
|   if (yl != null) {
 | |
|     yl = 0 + (yl || 0);
 | |
|   } else {
 | |
|     yl = this.term.lines.length;
 | |
|   }
 | |
|   return this.screen.screenshot(xi, xl, yi, yl, this.term);
 | |
| };
 | |
| 
 | |
| Terminal.prototype.kill = function() {
 | |
|   if (this.pty) {
 | |
|     this.pty.destroy();
 | |
|     this.pty.kill();
 | |
|   }
 | |
|   this.term.refresh = function() {};
 | |
|   this.term.write('\x1b[H\x1b[J');
 | |
|   if (this.term._blink) {
 | |
|     clearInterval(this.term._blink);
 | |
|   }
 | |
|   this.term.destroy();
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Expose
 | |
|  */
 | |
| 
 | |
| module.exports = Terminal;
 |