31
31
var util = require('util');
32
32
var inherits = require('util').inherits;
33
33
var EventEmitter = require('events').EventEmitter;
34
var tty = require('tty');
37
exports.createInterface = function(input, output, completer) {
38
return new Interface(input, output, completer);
36
exports.createInterface = function(input, output, completer, terminal) {
38
if (arguments.length === 1) {
39
rl = new Interface(input);
41
rl = new Interface(input, output, completer, terminal);
42
function Interface(input, output, completer) {
47
function Interface(input, output, completer, terminal) {
43
48
if (!(this instanceof Interface)) {
44
return new Interface(input, output, completer);
49
return new Interface(input, output, completer, terminal);
52
this._sawReturn = false;
46
54
EventEmitter.call(this);
56
if (arguments.length === 1) {
57
// an options object was given
58
output = input.output;
59
completer = input.completer;
60
terminal = input.terminal;
48
64
completer = completer || function() { return []; };
50
66
if (typeof completer !== 'function') {
51
throw new TypeError("Argument 'completer' must be a function");
67
throw new TypeError('Argument \'completer\' must be a function');
70
// backwards compat; check the isTTY prop of the output stream
71
// when `terminal` was not specified
72
if (typeof terminal == 'undefined') {
73
terminal = !!output.isTTY;
56
78
this.output = output;
57
79
this.input = input;
60
81
// Check arity, 2 - for async, 1 for sync
61
82
this.completer = completer.length === 2 ? completer : function(v, callback) {
167
207
Interface.prototype._addHistory = function() {
168
208
if (this.line.length === 0) return '';
170
this.history.unshift(this.line);
210
if (this.history.length === 0 || this.history[0] !== this.line) {
211
this.history.unshift(this.line);
213
// Only store so many
214
if (this.history.length > kHistorySize) this.history.pop();
172
217
this.historyIndex = -1;
176
// Only store so many
177
if (this.history.length > kHistorySize) this.history.pop();
179
218
return this.history[0];
183
222
Interface.prototype._refreshLine = function() {
184
if (this._closed) return;
223
var columns = this.columns;
226
var line = this._prompt + this.line;
227
var lineLength = line.length;
228
var lineCols = lineLength % columns;
229
var lineRows = (lineLength - lineCols) / columns;
232
var cursorPos = this._getCursorPos();
234
// first move to the bottom of the current line, based on cursor pos
235
var prevRows = this.prevRows || 0;
237
exports.moveCursor(this.output, 0, -prevRows);
186
240
// Cursor to left edge.
187
this.output.cursorTo(0);
241
exports.cursorTo(this.output, 0);
243
exports.clearScreenDown(this.output);
189
245
// Write the prompt and the current buffer content.
190
this.output.write(this._prompt);
191
this.output.write(this.line);
246
this.output.write(line);
194
this.output.clearLine(1);
248
// Force terminal to allocate a new line
249
if (lineCols === 0) {
250
this.output.write(' ');
196
253
// Move cursor to original position.
197
this.output.cursorTo(this._promptLength + this.cursor);
254
exports.cursorTo(this.output, cursorPos.cols);
256
var diff = lineRows - cursorPos.rows;
258
exports.moveCursor(this.output, 0, -diff);
261
this.prevRows = cursorPos.rows;
201
Interface.prototype.close = function(d) {
202
if (this._closing) return;
203
this._closing = true;
205
tty.setRawMode(false);
265
Interface.prototype.close = function() {
266
if (this.closed) return;
268
this._setRawMode(false);
207
272
this.emit('close');
212
276
Interface.prototype.pause = function() {
214
tty.setRawMode(false);
277
if (this.paused) return;
219
284
Interface.prototype.resume = function() {
221
tty.setRawMode(true);
285
if (!this.paused) return;
226
292
Interface.prototype.write = function(d, key) {
227
if (this._closed) return;
228
this.enabled ? this._ttyWrite(d, key) : this._normalWrite(d, key);
293
if (this.paused) this.resume();
294
this.terminal ? this._ttyWrite(d, key) : this._normalWrite(d);
297
// \r\n, \n, or \r followed by something other than \n
298
var lineEnding = /\r?\n|\r(?!\n)/;
232
299
Interface.prototype._normalWrite = function(b) {
233
// Very simple implementation right now. Should try to break on
236
this._onLine(b.toString());
300
if (b === undefined) {
303
var string = this._decoder.write(b);
304
if (this._sawReturn) {
305
string = string.replace(/^\n/, '');
306
this._sawReturn = false;
309
if (this._line_buffer) {
310
string = this._line_buffer + string;
311
this._line_buffer = null;
313
if (lineEnding.test(string)) {
314
this._sawReturn = /\r$/.test(string);
316
// got one or more newlines; process into "line" events
317
var lines = string.split(lineEnding);
318
// either '' or (concievably) the unfinished portion of the next line
319
string = lines.pop();
320
this._line_buffer = string;
321
lines.forEach(function(line) {
325
// no newlines this time, save what we have for next time
326
this._line_buffer = string;
239
330
Interface.prototype._insertString = function(c) {
457
Interface.prototype._attemptClose = function() {
458
if (this.listeners('attemptClose').length) {
459
// User is to call interface.close() manually.
460
this.emit('attemptClose');
562
// Returns current cursor's position and line
563
Interface.prototype._getCursorPos = function() {
564
var columns = this.columns;
565
var cursorPos = this.cursor + this._promptLength;
566
var cols = cursorPos % columns;
567
var rows = (cursorPos - cols) / columns;
568
return {cols: cols, rows: rows};
572
// This function moves cursor dx places to the right
573
// (-dx for left) and refreshes the line if it is needed
574
Interface.prototype._moveCursor = function(dx) {
575
var oldcursor = this.cursor;
576
var oldPos = this._getCursorPos();
580
if (this.cursor < 0) this.cursor = 0;
581
if (this.cursor > this.line.length) this.cursor = this.line.length;
583
var newPos = this._getCursorPos();
585
// check if cursors are in the same line
586
if (oldPos.rows === newPos.rows) {
587
exports.moveCursor(this.output, this.cursor - oldcursor, 0);
588
this.prevRows = newPos.rows;
467
595
// handle a write from the tty
468
596
Interface.prototype._ttyWrite = function(s, key) {
469
var next_word, next_non_word, previous_word, previous_non_word;
599
// Ignore escape key - Fixes #2876
600
if (key.name == 'escape') return;
472
602
if (key.ctrl && key.shift) {
473
603
/* Control and shift pressed */
474
604
switch (key.name) {
668
824
exports.Interface = Interface;
829
* accepts a readable Stream instance and makes it emit "keypress" events
832
function emitKeypressEvents(stream) {
833
if (stream._keypressDecoder) return;
834
var StringDecoder = require('string_decoder').StringDecoder; // lazy load
835
stream._keypressDecoder = new StringDecoder('utf8');
838
if (EventEmitter.listenerCount(stream, 'keypress') > 0) {
839
var r = stream._keypressDecoder.write(b);
840
if (r) emitKey(stream, r);
842
// Nobody's watching anyway
843
stream.removeListener('data', onData);
844
stream.on('newListener', onNewListener);
848
function onNewListener(event) {
849
if (event == 'keypress') {
850
stream.on('data', onData);
851
stream.removeListener('newListener', onNewListener);
855
if (EventEmitter.listenerCount(stream, 'keypress') > 0) {
856
stream.on('data', onData);
858
stream.on('newListener', onNewListener);
861
exports.emitKeypressEvents = emitKeypressEvents;
864
Some patterns seen in terminal key escape codes, derived from combos seen
865
at http://www.midnight-commander.org/browser/lib/tty/key.c
869
ESC [ modifier letter
870
ESC [ 1 ; modifier letter
872
ESC [ num ; modifier char
874
ESC O modifier letter
875
ESC O 1 ; modifier letter
877
ESC [ [ num ; modifier char
878
ESC [ [ 1 ; modifier letter
882
- char is usually ~ but $ and ^ also happen with rxvt
888
- two leading ESCs apparently mean the same as one leading ESC
891
// Regexes used for ansi escape code splitting
892
var metaKeyCodeRe = /^(?:\x1b)([a-zA-Z0-9])$/;
893
var functionKeyCodeRe =
894
/^(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/;
896
function emitKey(stream, s) {
906
if (Buffer.isBuffer(s)) {
907
if (s[0] > 127 && s[1] === undefined) {
909
s = '\x1b' + s.toString(stream.encoding || 'utf-8');
911
s = s.toString(stream.encoding || 'utf-8');
921
} else if (s === '\n') {
922
// enter, should have been called linefeed
925
} else if (s === '\t') {
929
} else if (s === '\b' || s === '\x7f' ||
930
s === '\x1b\x7f' || s === '\x1b\b') {
931
// backspace or ctrl+h
932
key.name = 'backspace';
933
key.meta = (s.charAt(0) === '\x1b');
935
} else if (s === '\x1b' || s === '\x1b\x1b') {
938
key.meta = (s.length === 2);
940
} else if (s === ' ' || s === '\x1b ') {
942
key.meta = (s.length === 2);
944
} else if (s <= '\x1a') {
946
key.name = String.fromCharCode(s.charCodeAt(0) + 'a'.charCodeAt(0) - 1);
949
} else if (s.length === 1 && s >= 'a' && s <= 'z') {
953
} else if (s.length === 1 && s >= 'A' && s <= 'Z') {
955
key.name = s.toLowerCase();
958
} else if (parts = metaKeyCodeRe.exec(s)) {
959
// meta+character key
960
key.name = parts[1].toLowerCase();
962
key.shift = /^[A-Z]$/.test(parts[1]);
964
} else if (parts = functionKeyCodeRe.exec(s)) {
965
// ansi escape sequence
967
// reassemble the key code leaving out leading \x1b's,
968
// the modifier key bitflag and any meaningless "1;" sequence
969
var code = (parts[1] || '') + (parts[2] || '') +
970
(parts[4] || '') + (parts[6] || ''),
971
modifier = (parts[3] || parts[5] || 1) - 1;
973
// Parse the key modifier
974
key.ctrl = !!(modifier & 4);
975
key.meta = !!(modifier & 10);
976
key.shift = !!(modifier & 1);
979
// Parse the key itself
981
/* xterm/gnome ESC O letter */
982
case 'OP': key.name = 'f1'; break;
983
case 'OQ': key.name = 'f2'; break;
984
case 'OR': key.name = 'f3'; break;
985
case 'OS': key.name = 'f4'; break;
987
/* xterm/rxvt ESC [ number ~ */
988
case '[11~': key.name = 'f1'; break;
989
case '[12~': key.name = 'f2'; break;
990
case '[13~': key.name = 'f3'; break;
991
case '[14~': key.name = 'f4'; break;
993
/* from Cygwin and used in libuv */
994
case '[[A': key.name = 'f1'; break;
995
case '[[B': key.name = 'f2'; break;
996
case '[[C': key.name = 'f3'; break;
997
case '[[D': key.name = 'f4'; break;
998
case '[[E': key.name = 'f5'; break;
1001
case '[15~': key.name = 'f5'; break;
1002
case '[17~': key.name = 'f6'; break;
1003
case '[18~': key.name = 'f7'; break;
1004
case '[19~': key.name = 'f8'; break;
1005
case '[20~': key.name = 'f9'; break;
1006
case '[21~': key.name = 'f10'; break;
1007
case '[23~': key.name = 'f11'; break;
1008
case '[24~': key.name = 'f12'; break;
1010
/* xterm ESC [ letter */
1011
case '[A': key.name = 'up'; break;
1012
case '[B': key.name = 'down'; break;
1013
case '[C': key.name = 'right'; break;
1014
case '[D': key.name = 'left'; break;
1015
case '[E': key.name = 'clear'; break;
1016
case '[F': key.name = 'end'; break;
1017
case '[H': key.name = 'home'; break;
1019
/* xterm/gnome ESC O letter */
1020
case 'OA': key.name = 'up'; break;
1021
case 'OB': key.name = 'down'; break;
1022
case 'OC': key.name = 'right'; break;
1023
case 'OD': key.name = 'left'; break;
1024
case 'OE': key.name = 'clear'; break;
1025
case 'OF': key.name = 'end'; break;
1026
case 'OH': key.name = 'home'; break;
1028
/* xterm/rxvt ESC [ number ~ */
1029
case '[1~': key.name = 'home'; break;
1030
case '[2~': key.name = 'insert'; break;
1031
case '[3~': key.name = 'delete'; break;
1032
case '[4~': key.name = 'end'; break;
1033
case '[5~': key.name = 'pageup'; break;
1034
case '[6~': key.name = 'pagedown'; break;
1037
case '[[5~': key.name = 'pageup'; break;
1038
case '[[6~': key.name = 'pagedown'; break;
1041
case '[7~': key.name = 'home'; break;
1042
case '[8~': key.name = 'end'; break;
1044
/* rxvt keys with modifiers */
1045
case '[a': key.name = 'up'; key.shift = true; break;
1046
case '[b': key.name = 'down'; key.shift = true; break;
1047
case '[c': key.name = 'right'; key.shift = true; break;
1048
case '[d': key.name = 'left'; key.shift = true; break;
1049
case '[e': key.name = 'clear'; key.shift = true; break;
1051
case '[2$': key.name = 'insert'; key.shift = true; break;
1052
case '[3$': key.name = 'delete'; key.shift = true; break;
1053
case '[5$': key.name = 'pageup'; key.shift = true; break;
1054
case '[6$': key.name = 'pagedown'; key.shift = true; break;
1055
case '[7$': key.name = 'home'; key.shift = true; break;
1056
case '[8$': key.name = 'end'; key.shift = true; break;
1058
case 'Oa': key.name = 'up'; key.ctrl = true; break;
1059
case 'Ob': key.name = 'down'; key.ctrl = true; break;
1060
case 'Oc': key.name = 'right'; key.ctrl = true; break;
1061
case 'Od': key.name = 'left'; key.ctrl = true; break;
1062
case 'Oe': key.name = 'clear'; key.ctrl = true; break;
1064
case '[2^': key.name = 'insert'; key.ctrl = true; break;
1065
case '[3^': key.name = 'delete'; key.ctrl = true; break;
1066
case '[5^': key.name = 'pageup'; key.ctrl = true; break;
1067
case '[6^': key.name = 'pagedown'; key.ctrl = true; break;
1068
case '[7^': key.name = 'home'; key.ctrl = true; break;
1069
case '[8^': key.name = 'end'; key.ctrl = true; break;
1072
case '[Z': key.name = 'tab'; key.shift = true; break;
1073
default: key.name = 'undefined'; break;
1076
} else if (s.length > 1 && s[0] !== '\x1b') {
1077
// Got a longer-than-one string of characters.
1078
// Probably a paste, since it wasn't a control sequence.
1079
Array.prototype.forEach.call(s, function(c) {
1085
// Don't emit a key if no name was found
1086
if (key.name === undefined) {
1090
if (s.length === 1) {
1095
stream.emit('keypress', ch, key);
1101
* moves the cursor to the x and y coordinate on the given stream
1104
function cursorTo(stream, x, y) {
1105
if (typeof x !== 'number' && typeof y !== 'number')
1108
if (typeof x !== 'number')
1109
throw new Error("Can't set cursor row without also setting it's column");
1111
if (typeof y !== 'number') {
1112
stream.write('\x1b[' + (x + 1) + 'G');
1114
stream.write('\x1b[' + (y + 1) + ';' + (x + 1) + 'H');
1117
exports.cursorTo = cursorTo;
1121
* moves the cursor relative to its current location
1124
function moveCursor(stream, dx, dy) {
1126
stream.write('\x1b[' + (-dx) + 'D');
1127
} else if (dx > 0) {
1128
stream.write('\x1b[' + dx + 'C');
1132
stream.write('\x1b[' + (-dy) + 'A');
1133
} else if (dy > 0) {
1134
stream.write('\x1b[' + dy + 'B');
1137
exports.moveCursor = moveCursor;
1141
* clears the current line the cursor is on:
1142
* -1 for left of the cursor
1143
* +1 for right of the cursor
1144
* 0 for the entire line
1147
function clearLine(stream, dir) {
1150
stream.write('\x1b[1K');
1151
} else if (dir > 0) {
1153
stream.write('\x1b[0K');
1156
stream.write('\x1b[2K');
1159
exports.clearLine = clearLine;
1163
* clears the screen from the current position of the cursor down
1166
function clearScreenDown(stream) {
1167
stream.write('\x1b[0J');
1169
exports.clearScreenDown = clearScreenDown;