24
24
var net = require('net');
25
25
var TTY = process.binding('tty_wrap').TTY;
26
26
var isTTY = process.binding('tty_wrap').isTTY;
27
var util = require('util');
31
29
exports.isatty = function(fd) {
36
exports.setRawMode = function(flag) {
37
assert.ok(stdinHandle, 'stdin must be initialized before calling setRawMode');
38
stdinHandle.setRawMode(flag);
42
exports.getWindowSize = function() {
43
//throw new Error("implement me");
48
exports.setWindowSize = function() {
49
throw new Error('implement me');
53
function ReadStream(fd) {
54
if (!(this instanceof ReadStream)) return new ReadStream(fd);
55
net.Socket.call(this, {
35
exports.setRawMode = util.deprecate(function(flag) {
36
if (!process.stdin.isTTY) {
37
throw new Error('can\'t set raw mode on non-tty');
39
process.stdin.setRawMode(flag);
40
}, 'tty.setRawMode: Use `process.stdin.setRawMode()` instead.');
43
function ReadStream(fd, options) {
44
if (!(this instanceof ReadStream))
45
return new ReadStream(fd, options);
47
options = util._extend({
56
51
handle: new TTY(fd, true)
59
this.writable = false;
62
keypressListeners = this.listeners('keypress');
65
if (keypressListeners.length) {
68
// Nobody's watching anyway
69
self.removeListener('data', onData);
70
self.on('newListener', onNewListener);
74
function onNewListener(event) {
75
if (event == 'keypress') {
76
self.on('data', onData);
77
self.removeListener('newListener', onNewListener);
81
if (!stdinHandle) stdinHandle = this._handle;
83
this.on('newListener', onNewListener);
54
net.Socket.call(this, options);
85
59
inherits(ReadStream, net.Socket);
87
61
exports.ReadStream = ReadStream;
89
ReadStream.prototype.pause = function() {
91
return net.Socket.prototype.pause.call(this);
94
ReadStream.prototype.resume = function() {
96
return net.Socket.prototype.resume.call(this);
100
ReadStream.prototype.isTTY = true;
104
Some patterns seen in terminal key escape codes, derived from combos seen
105
at http://www.midnight-commander.org/browser/lib/tty/key.c
109
ESC [ modifier letter
110
ESC [ 1 ; modifier letter
112
ESC [ num ; modifier char
114
ESC O modifier letter
115
ESC O 1 ; modifier letter
117
ESC [ [ num ; modifier char
118
ESC [ [ 1 ; modifier letter
122
- char is usually ~ but $ and ^ also happen with rxvt
128
- two leading ESCs apparently mean the same as one leading ESC
132
// Regexes used for ansi escape code splitting
133
var metaKeyCodeRe = /^(?:\x1b)([a-zA-Z0-9])$/;
134
var functionKeyCodeRe =
135
/^(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/;
138
ReadStream.prototype._emitKey = function(s) {
148
if (Buffer.isBuffer(s)) {
149
if (s[0] > 127 && s[1] === undefined) {
151
s = '\x1b' + s.toString(this.encoding || 'utf-8');
153
s = s.toString(this.encoding || 'utf-8');
157
if (s === '\r' || s === '\n') {
161
} else if (s === '\t') {
165
} else if (s === '\b' || s === '\x7f' ||
166
s === '\x1b\x7f' || s === '\x1b\b') {
167
// backspace or ctrl+h
168
key.name = 'backspace';
169
key.meta = (s.charAt(0) === '\x1b');
171
} else if (s === '\x1b' || s === '\x1b\x1b') {
174
key.meta = (s.length === 2);
176
} else if (s === ' ' || s === '\x1b ') {
178
key.meta = (s.length === 2);
180
} else if (s <= '\x1a') {
182
key.name = String.fromCharCode(s.charCodeAt(0) + 'a'.charCodeAt(0) - 1);
185
} else if (s.length === 1 && s >= 'a' && s <= 'z') {
189
} else if (s.length === 1 && s >= 'A' && s <= 'Z') {
191
key.name = s.toLowerCase();
194
} else if (parts = metaKeyCodeRe.exec(s)) {
195
// meta+character key
196
key.name = parts[1].toLowerCase();
198
key.shift = /^[A-Z]$/.test(parts[1]);
200
} else if (parts = functionKeyCodeRe.exec(s)) {
201
// ansi escape sequence
203
// reassemble the key code leaving out leading \x1b's,
204
// the modifier key bitflag and any meaningless "1;" sequence
205
var code = (parts[1] || '') + (parts[2] || '') +
206
(parts[4] || '') + (parts[6] || ''),
207
modifier = (parts[3] || parts[5] || 1) - 1;
209
// Parse the key modifier
210
key.ctrl = !!(modifier & 4);
211
key.meta = !!(modifier & 10);
212
key.shift = !!(modifier & 1);
214
// Parse the key itself
216
/* xterm/gnome ESC O letter */
217
case 'OP': key.name = 'f1'; break;
218
case 'OQ': key.name = 'f2'; break;
219
case 'OR': key.name = 'f3'; break;
220
case 'OS': key.name = 'f4'; break;
222
/* xterm/rxvt ESC [ number ~ */
223
case '[11~': key.name = 'f1'; break;
224
case '[12~': key.name = 'f2'; break;
225
case '[13~': key.name = 'f3'; break;
226
case '[14~': key.name = 'f4'; break;
228
/* from Cygwin and used in libuv */
229
case '[[A': key.name = 'f1'; break;
230
case '[[B': key.name = 'f2'; break;
231
case '[[C': key.name = 'f3'; break;
232
case '[[D': key.name = 'f4'; break;
233
case '[[E': key.name = 'f5'; break;
236
case '[15~': key.name = 'f5'; break;
237
case '[17~': key.name = 'f6'; break;
238
case '[18~': key.name = 'f7'; break;
239
case '[19~': key.name = 'f8'; break;
240
case '[20~': key.name = 'f9'; break;
241
case '[21~': key.name = 'f10'; break;
242
case '[23~': key.name = 'f11'; break;
243
case '[24~': key.name = 'f12'; break;
245
/* xterm ESC [ letter */
246
case '[A': key.name = 'up'; break;
247
case '[B': key.name = 'down'; break;
248
case '[C': key.name = 'right'; break;
249
case '[D': key.name = 'left'; break;
250
case '[E': key.name = 'clear'; break;
251
case '[F': key.name = 'end'; break;
252
case '[H': key.name = 'home'; break;
254
/* xterm/gnome ESC O letter */
255
case 'OA': key.name = 'up'; break;
256
case 'OB': key.name = 'down'; break;
257
case 'OC': key.name = 'right'; break;
258
case 'OD': key.name = 'left'; break;
259
case 'OE': key.name = 'clear'; break;
260
case 'OF': key.name = 'end'; break;
261
case 'OH': key.name = 'home'; break;
263
/* xterm/rxvt ESC [ number ~ */
264
case '[1~': key.name = 'home'; break;
265
case '[2~': key.name = 'insert'; break;
266
case '[3~': key.name = 'delete'; break;
267
case '[4~': key.name = 'end'; break;
268
case '[5~': key.name = 'pageup'; break;
269
case '[6~': key.name = 'pagedown'; break;
272
case '[[5~': key.name = 'pageup'; break;
273
case '[[6~': key.name = 'pagedown'; break;
276
case '[7~': key.name = 'home'; break;
277
case '[8~': key.name = 'end'; break;
279
/* rxvt keys with modifiers */
280
case '[a': key.name = 'up'; key.shift = true; break;
281
case '[b': key.name = 'down'; key.shift = true; break;
282
case '[c': key.name = 'right'; key.shift = true; break;
283
case '[d': key.name = 'left'; key.shift = true; break;
284
case '[e': key.name = 'clear'; key.shift = true; break;
286
case '[2$': key.name = 'insert'; key.shift = true; break;
287
case '[3$': key.name = 'delete'; key.shift = true; break;
288
case '[5$': key.name = 'pageup'; key.shift = true; break;
289
case '[6$': key.name = 'pagedown'; key.shift = true; break;
290
case '[7$': key.name = 'home'; key.shift = true; break;
291
case '[8$': key.name = 'end'; key.shift = true; break;
293
case 'Oa': key.name = 'up'; key.ctrl = true; break;
294
case 'Ob': key.name = 'down'; key.ctrl = true; break;
295
case 'Oc': key.name = 'right'; key.ctrl = true; break;
296
case 'Od': key.name = 'left'; key.ctrl = true; break;
297
case 'Oe': key.name = 'clear'; key.ctrl = true; break;
299
case '[2^': key.name = 'insert'; key.ctrl = true; break;
300
case '[3^': key.name = 'delete'; key.ctrl = true; break;
301
case '[5^': key.name = 'pageup'; key.ctrl = true; break;
302
case '[6^': key.name = 'pagedown'; key.ctrl = true; break;
303
case '[7^': key.name = 'home'; key.ctrl = true; break;
304
case '[8^': key.name = 'end'; key.ctrl = true; break;
307
case '[Z': key.name = 'tab'; key.shift = true; break;
310
} else if (s.length > 1 && s[0] !== '\x1b') {
311
// Got a longer-than-one string of characters.
312
// Probably a paste, since it wasn't a control sequence.
313
Array.prototype.forEach.call(s, this._emitKey, this);
317
// Don't emit a key if no name was found
318
if (key.name === undefined) {
322
if (s.length === 1) {
327
this.emit('keypress', char, key);
63
ReadStream.prototype.setRawMode = function(flag) {
65
this._handle.setRawMode(flag);
332
71
function WriteStream(fd) {
333
72
if (!(this instanceof WriteStream)) return new WriteStream(fd);
334
73
net.Socket.call(this, {
335
handle: new TTY(fd, false)
74
handle: new TTY(fd, false),
338
this.readable = false;
339
this.writable = true;
79
var winSize = this._handle.getWindowSize();
81
this.columns = winSize[0];
82
this.rows = winSize[1];
341
85
inherits(WriteStream, net.Socket);
342
86
exports.WriteStream = WriteStream;
345
89
WriteStream.prototype.isTTY = true;
92
WriteStream.prototype._refreshSize = function() {
93
var oldCols = this.columns;
94
var oldRows = this.rows;
95
var winSize = this._handle.getWindowSize();
97
this.emit('error', errnoException(process._errno, 'getWindowSize'));
100
var newCols = winSize[0];
101
var newRows = winSize[1];
102
if (oldCols !== newCols || oldRows !== newRows) {
103
this.columns = newCols;
348
111
WriteStream.prototype.cursorTo = function(x, y) {
349
if (typeof x !== 'number' && typeof y !== 'number')
352
if (typeof x !== 'number')
353
throw new Error("Can't set cursor row without also setting it's column");
355
if (typeof y !== 'number') {
356
this.write('\x1b[' + (x + 1) + 'G');
358
this.write('\x1b[' + (y + 1) + ';' + (x + 1) + 'H');
112
require('readline').cursorTo(this, x, y);
363
114
WriteStream.prototype.moveCursor = function(dx, dy) {
365
this.write('\x1b[' + (-dx) + 'D');
367
this.write('\x1b[' + dx + 'C');
371
this.write('\x1b[' + (-dy) + 'A');
373
this.write('\x1b[' + dy + 'B');
115
require('readline').moveCursor(this, dx, dy);
378
117
WriteStream.prototype.clearLine = function(dir) {
381
this.write('\x1b[1K');
382
} else if (dir > 0) {
384
this.write('\x1b[0K');
387
this.write('\x1b[2K');
118
require('readline').clearLine(this, dir);
120
WriteStream.prototype.clearScreenDown = function() {
121
require('readline').clearScreenDown(this);
392
123
WriteStream.prototype.getWindowSize = function() {
393
return this._handle.getWindowSize();
124
return [this.columns, this.rows];
128
// TODO share with net_uv and others
129
function errnoException(errorno, syscall) {
130
var e = new Error(syscall + ' ' + errorno);
131
e.errno = e.code = errorno;