Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nodejs/node.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tty.js')
-rw-r--r--lib/tty.js376
1 files changed, 376 insertions, 0 deletions
diff --git a/lib/tty.js b/lib/tty.js
new file mode 100644
index 00000000000..78971dda315
--- /dev/null
+++ b/lib/tty.js
@@ -0,0 +1,376 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// 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.
+
+var assert = require('assert');
+var inherits = require('util').inherits;
+var net = require('net');
+var TTY = process.binding('tty_wrap').TTY;
+var isTTY = process.binding('tty_wrap').isTTY;
+
+var stdinHandle;
+
+
+exports.isatty = function(fd) {
+ return isTTY(fd);
+};
+
+
+exports.setRawMode = function(flag) {
+ assert.ok(stdinHandle, 'stdin must be initialized before calling setRawMode');
+ stdinHandle.setRawMode(flag);
+};
+
+
+exports.getWindowSize = function() {
+ //throw new Error("implement me");
+ return 80;
+};
+
+
+exports.setWindowSize = function() {
+ throw new Error('implement me');
+};
+
+
+function ReadStream(fd) {
+ if (!(this instanceof ReadStream)) return new ReadStream(fd);
+ net.Socket.call(this, {
+ handle: new TTY(fd, true)
+ });
+
+ this.writable = false;
+
+ var self = this,
+ keypressListeners = this.listeners('keypress');
+
+ function onData(b) {
+ if (keypressListeners.length) {
+ self._emitKey(b);
+ } else {
+ // Nobody's watching anyway
+ self.removeListener('data', onData);
+ self.on('newListener', onNewListener);
+ }
+ }
+
+ function onNewListener(event) {
+ if (event == 'keypress') {
+ self.on('data', onData);
+ self.removeListener('newListener', onNewListener);
+ }
+ }
+
+ if (!stdinHandle) stdinHandle = this._handle;
+
+ this.on('newListener', onNewListener);
+}
+inherits(ReadStream, net.Socket);
+exports.ReadStream = ReadStream;
+
+
+ReadStream.prototype.isTTY = true;
+
+
+/*
+ Some patterns seen in terminal key escape codes, derived from combos seen
+ at http://www.midnight-commander.org/browser/lib/tty/key.c
+
+ ESC letter
+ ESC [ letter
+ ESC [ modifier letter
+ ESC [ 1 ; modifier letter
+ ESC [ num char
+ ESC [ num ; modifier char
+ ESC O letter
+ ESC O modifier letter
+ ESC O 1 ; modifier letter
+ ESC N letter
+ ESC [ [ num ; modifier char
+ ESC [ [ 1 ; modifier letter
+ ESC ESC [ num char
+ ESC ESC O letter
+
+ - char is usually ~ but $ and ^ also happen with rxvt
+ - modifier is 1 +
+ (shift * 1) +
+ (left_alt * 2) +
+ (ctrl * 4) +
+ (right_alt * 8)
+ - two leading ESCs apparently mean the same as one leading ESC
+*/
+
+
+// Regexes used for ansi escape code splitting
+var metaKeyCodeRe = /^(?:\x1b)([a-zA-Z0-9])$/;
+var functionKeyCodeRe =
+ /^(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/;
+
+
+ReadStream.prototype._emitKey = function(s) {
+ var char,
+ key = {
+ name: undefined,
+ ctrl: false,
+ meta: false,
+ shift: false
+ },
+ parts;
+
+ if (Buffer.isBuffer(s)) {
+ if (s[0] > 127 && s[1] === undefined) {
+ s[0] -= 128;
+ s = '\x1b' + s.toString(this.encoding || 'utf-8');
+ } else {
+ s = s.toString(this.encoding || 'utf-8');
+ }
+ }
+
+ if (s === '\r' || s === '\n') {
+ // enter
+ key.name = 'enter';
+
+ } else if (s === '\t') {
+ // tab
+ key.name = 'tab';
+
+ } else if (s === '\b' || s === '\x7f' ||
+ s === '\x1b\x7f' || s === '\x1b\b') {
+ // backspace or ctrl+h
+ key.name = 'backspace';
+ key.meta = (s.charAt(0) === '\x1b');
+
+ } else if (s === '\x1b' || s === '\x1b\x1b') {
+ // escape key
+ key.name = 'escape';
+ key.meta = (s.length === 2);
+
+ } else if (s === ' ' || s === '\x1b ') {
+ key.name = 'space';
+ key.meta = (s.length === 2);
+
+ } else if (s <= '\x1a') {
+ // ctrl+letter
+ key.name = String.fromCharCode(s.charCodeAt(0) + 'a'.charCodeAt(0) - 1);
+ key.ctrl = true;
+
+ } else if (s.length === 1 && s >= 'a' && s <= 'z') {
+ // lowercase letter
+ key.name = s;
+
+ } else if (s.length === 1 && s >= 'A' && s <= 'Z') {
+ // shift+letter
+ key.name = s.toLowerCase();
+ key.shift = true;
+
+ } else if (parts = metaKeyCodeRe.exec(s)) {
+ // meta+character key
+ key.name = parts[1].toLowerCase();
+ key.meta = true;
+ key.shift = /^[A-Z]$/.test(parts[1]);
+
+ } else if (parts = functionKeyCodeRe.exec(s)) {
+ // ansi escape sequence
+
+ // reassemble the key code leaving out leading \x1b's,
+ // the modifier key bitflag and any meaningless "1;" sequence
+ var code = (parts[1] || '') + (parts[2] || '') +
+ (parts[4] || '') + (parts[6] || ''),
+ modifier = (parts[3] || parts[5] || 1) - 1;
+
+ // Parse the key modifier
+ key.ctrl = !!(modifier & 4);
+ key.meta = !!(modifier & 10);
+ key.shift = !!(modifier & 1);
+
+ // Parse the key itself
+ switch (code) {
+ /* xterm/gnome ESC O letter */
+ case 'OP': key.name = 'f1'; break;
+ case 'OQ': key.name = 'f2'; break;
+ case 'OR': key.name = 'f3'; break;
+ case 'OS': key.name = 'f4'; break;
+
+ /* xterm/rxvt ESC [ number ~ */
+ case '[11~': key.name = 'f1'; break;
+ case '[12~': key.name = 'f2'; break;
+ case '[13~': key.name = 'f3'; break;
+ case '[14~': key.name = 'f4'; break;
+
+ /* common */
+ case '[15~': key.name = 'f5'; break;
+ case '[17~': key.name = 'f6'; break;
+ case '[18~': key.name = 'f7'; break;
+ case '[19~': key.name = 'f8'; break;
+ case '[20~': key.name = 'f9'; break;
+ case '[21~': key.name = 'f10'; break;
+ case '[23~': key.name = 'f11'; break;
+ case '[24~': key.name = 'f12'; break;
+
+ /* xterm ESC [ letter */
+ case '[A': key.name = 'up'; break;
+ case '[B': key.name = 'down'; break;
+ case '[C': key.name = 'right'; break;
+ case '[D': key.name = 'left'; break;
+ case '[E': key.name = 'clear'; break;
+ case '[F': key.name = 'end'; break;
+ case '[H': key.name = 'home'; break;
+
+ /* xterm/gnome ESC O letter */
+ case 'OA': key.name = 'up'; break;
+ case 'OB': key.name = 'down'; break;
+ case 'OC': key.name = 'right'; break;
+ case 'OD': key.name = 'left'; break;
+ case 'OE': key.name = 'clear'; break;
+ case 'OF': key.name = 'end'; break;
+ case 'OH': key.name = 'home'; break;
+
+ /* xterm/rxvt ESC [ number ~ */
+ case '[1~': key.name = 'home'; break;
+ case '[2~': key.name = 'insert'; break;
+ case '[3~': key.name = 'delete'; break;
+ case '[4~': key.name = 'end'; break;
+ case '[5~': key.name = 'pageup'; break;
+ case '[6~': key.name = 'pagedown'; break;
+
+ /* putty */
+ case '[[5~': key.name = 'pageup'; break;
+ case '[[6~': key.name = 'pagedown'; break;
+
+ /* rxvt */
+ case '[7~': key.name = 'home'; break;
+ case '[8~': key.name = 'end'; break;
+
+ /* rxvt keys with modifiers */
+ case '[a': key.name = 'up'; key.shift = true; break;
+ case '[b': key.name = 'down'; key.shift = true; break;
+ case '[c': key.name = 'right'; key.shift = true; break;
+ case '[d': key.name = 'left'; key.shift = true; break;
+ case '[e': key.name = 'clear'; key.shift = true; break;
+
+ case '[2$': key.name = 'insert'; key.shift = true; break;
+ case '[3$': key.name = 'delete'; key.shift = true; break;
+ case '[5$': key.name = 'pageup'; key.shift = true; break;
+ case '[6$': key.name = 'pagedown'; key.shift = true; break;
+ case '[7$': key.name = 'home'; key.shift = true; break;
+ case '[8$': key.name = 'end'; key.shift = true; break;
+
+ case 'Oa': key.name = 'up'; key.ctrl = true; break;
+ case 'Ob': key.name = 'down'; key.ctrl = true; break;
+ case 'Oc': key.name = 'right'; key.ctrl = true; break;
+ case 'Od': key.name = 'left'; key.ctrl = true; break;
+ case 'Oe': key.name = 'clear'; key.ctrl = true; break;
+
+ case '[2^': key.name = 'insert'; key.ctrl = true; break;
+ case '[3^': key.name = 'delete'; key.ctrl = true; break;
+ case '[5^': key.name = 'pageup'; key.ctrl = true; break;
+ case '[6^': key.name = 'pagedown'; key.ctrl = true; break;
+ case '[7^': key.name = 'home'; key.ctrl = true; break;
+ case '[8^': key.name = 'end'; key.ctrl = true; break;
+
+ /* misc. */
+ case '[Z': key.name = 'tab'; key.shift = true; break;
+
+ }
+ } else if (s.length > 1 && s[0] !== '\x1b') {
+ // Got a longer-than-one string of characters.
+ // Probably a paste, since it wasn't a control sequence.
+ Array.prototype.forEach.call(s, this._emitKey, this);
+ return;
+ }
+
+ // Don't emit a key if no name was found
+ if (key.name === undefined) {
+ key = undefined;
+ }
+
+ if (s.length === 1) {
+ char = s;
+ }
+
+ if (key || char) {
+ this.emit('keypress', char, key);
+ }
+};
+
+
+function WriteStream(fd) {
+ if (!(this instanceof WriteStream)) return new WriteStream(fd);
+ net.Socket.call(this, {
+ handle: new TTY(fd, false)
+ });
+
+ this.readable = false;
+ this.writable = true;
+}
+inherits(WriteStream, net.Socket);
+exports.WriteStream = WriteStream;
+
+
+WriteStream.prototype.isTTY = true;
+
+
+WriteStream.prototype.cursorTo = function(x, y) {
+ if (typeof x !== 'number' && typeof y !== 'number')
+ return;
+
+ if (typeof x !== 'number')
+ throw new Error("Can't set cursor row without also setting it's column");
+
+ if (typeof y !== 'number') {
+ this.write('\x1b[' + (x + 1) + 'G');
+ } else {
+ this.write('\x1b[' + (y + 1) + ';' + (x + 1) + 'H');
+ }
+};
+
+
+WriteStream.prototype.moveCursor = function(dx, dy) {
+ if (dx < 0) {
+ this.write('\x1b[' + (-dx) + 'D');
+ } else if (dx > 0) {
+ this.write('\x1b[' + dx + 'C');
+ }
+
+ if (dy < 0) {
+ this.write('\x1b[' + (-dy) + 'A');
+ } else if (dy > 0) {
+ this.write('\x1b[' + dy + 'B');
+ }
+};
+
+
+WriteStream.prototype.clearLine = function(dir) {
+ if (dir < 0) {
+ // to the beginning
+ this.write('\x1b[1K');
+ } else if (dir > 0) {
+ // to the end
+ this.write('\x1b[0K');
+ } else {
+ // entire line
+ this.write('\x1b[2K');
+ }
+};
+
+
+WriteStream.prototype.getWindowSize = function() {
+ return this._handle.getWindowSize();
+};