bafef00e0dd91f078165c63b00ea26e338b6f2fb
[platform/upstream/nodejs.git] / lib / readline.js
1 // Copyright Joyent, Inc. and other Node contributors.
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining a
4 // copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to permit
8 // persons to whom the Software is furnished to do so, subject to the
9 // following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included
12 // in all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17 // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18 // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20 // USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 // Inspiration for this code comes from Salvatore Sanfilippo's linenoise.
23 // https://github.com/antirez/linenoise
24 // Reference:
25 // * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
26 // * http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
27
28 var kHistorySize = 30;
29
30 var util = require('util');
31 var inherits = require('util').inherits;
32 var EventEmitter = require('events').EventEmitter;
33
34
35 exports.createInterface = function(input, output, completer, terminal) {
36   var rl;
37   if (arguments.length === 1) {
38     rl = new Interface(input);
39   } else {
40     rl = new Interface(input, output, completer, terminal);
41   }
42   return rl;
43 };
44
45
46 function Interface(input, output, completer, terminal) {
47   if (!(this instanceof Interface)) {
48     return new Interface(input, output, completer, terminal);
49   }
50
51   this._sawReturn = false;
52
53   EventEmitter.call(this);
54
55   if (arguments.length === 1) {
56     // an options object was given
57     output = input.output;
58     completer = input.completer;
59     terminal = input.terminal;
60     input = input.input;
61   }
62
63   completer = completer || function() { return []; };
64
65   if (!util.isFunction(completer)) {
66     throw new TypeError('Argument \'completer\' must be a function');
67   }
68
69   // backwards compat; check the isTTY prop of the output stream
70   //  when `terminal` was not specified
71   if (util.isUndefined(terminal) && !util.isNullOrUndefined(output)) {
72     terminal = !!output.isTTY;
73   }
74
75   var self = this;
76
77   this.output = output;
78   this.input = input;
79
80   // Check arity, 2 - for async, 1 for sync
81   this.completer = completer.length === 2 ? completer : function(v, callback) {
82     callback(null, completer(v));
83   };
84
85   this.setPrompt('> ');
86
87   this.terminal = !!terminal;
88
89   function ondata(data) {
90     self._normalWrite(data);
91   }
92
93   function onend() {
94     if (util.isString(self._line_buffer) && self._line_buffer.length > 0) {
95       self.emit('line', self._line_buffer);
96     }
97     self.close();
98   }
99
100   function ontermend() {
101     if (util.isString(self.line) && self.line.length > 0) {
102       self.emit('line', self.line);
103     }
104     self.close();
105   }
106
107   function onkeypress(s, key) {
108     self._ttyWrite(s, key);
109   }
110
111   function onresize() {
112     self._refreshLine();
113   }
114
115   if (!this.terminal) {
116     input.on('data', ondata);
117     input.on('end', onend);
118     self.once('close', function() {
119       input.removeListener('data', ondata);
120       input.removeListener('end', onend);
121     });
122     var StringDecoder = require('string_decoder').StringDecoder; // lazy load
123     this._decoder = new StringDecoder('utf8');
124
125   } else {
126
127     exports.emitKeypressEvents(input);
128
129     // input usually refers to stdin
130     input.on('keypress', onkeypress);
131     input.on('end', ontermend);
132
133     // Current line
134     this.line = '';
135
136     this._setRawMode(true);
137     this.terminal = true;
138
139     // Cursor position on the line.
140     this.cursor = 0;
141
142     this.history = [];
143     this.historyIndex = -1;
144
145     if (!util.isNullOrUndefined(output))
146       output.on('resize', onresize);
147
148     self.once('close', function() {
149       input.removeListener('keypress', onkeypress);
150       input.removeListener('end', ontermend);
151       if (!util.isNullOrUndefined(output)) {
152         output.removeListener('resize', onresize);
153       }
154     });
155   }
156
157   input.resume();
158 }
159
160 inherits(Interface, EventEmitter);
161
162 Interface.prototype.__defineGetter__('columns', function() {
163   var columns = Infinity;
164   if (this.output && this.output.columns)
165     columns = this.output.columns;
166   return columns;
167 });
168
169 Interface.prototype.setPrompt = function(prompt) {
170   this._prompt = prompt;
171 };
172
173
174 Interface.prototype._setRawMode = function(mode) {
175   if (util.isFunction(this.input.setRawMode)) {
176     return this.input.setRawMode(mode);
177   }
178 };
179
180
181 Interface.prototype.prompt = function(preserveCursor) {
182   if (this.paused) this.resume();
183   if (this.terminal) {
184     if (!preserveCursor) this.cursor = 0;
185     this._refreshLine();
186   } else {
187     this._writeToOutput(this._prompt);
188   }
189 };
190
191
192 Interface.prototype.question = function(query, cb) {
193   if (util.isFunction(cb)) {
194     if (this._questionCallback) {
195       this.prompt();
196     } else {
197       this._oldPrompt = this._prompt;
198       this.setPrompt(query);
199       this._questionCallback = cb;
200       this.prompt();
201     }
202   }
203 };
204
205
206 Interface.prototype._onLine = function(line) {
207   if (this._questionCallback) {
208     var cb = this._questionCallback;
209     this._questionCallback = null;
210     this.setPrompt(this._oldPrompt);
211     cb(line);
212   } else {
213     this.emit('line', line);
214   }
215 };
216
217 Interface.prototype._writeToOutput = function _writeToOutput(stringToWrite) {
218   if (!util.isString(stringToWrite))
219     throw new TypeError('stringToWrite must be a string');
220
221   if (!util.isNullOrUndefined(this.output))
222     this.output.write(stringToWrite);
223 };
224
225 Interface.prototype._addHistory = function() {
226   if (this.line.length === 0) return '';
227
228   if (this.history.length === 0 || this.history[0] !== this.line) {
229     this.history.unshift(this.line);
230
231     // Only store so many
232     if (this.history.length > kHistorySize) this.history.pop();
233   }
234
235   this.historyIndex = -1;
236   return this.history[0];
237 };
238
239
240 Interface.prototype._refreshLine = function() {
241   // line length
242   var line = this._prompt + this.line;
243   var dispPos = this._getDisplayPos(line);
244   var lineCols = dispPos.cols;
245   var lineRows = dispPos.rows;
246
247   // cursor position
248   var cursorPos = this._getCursorPos();
249
250   // first move to the bottom of the current line, based on cursor pos
251   var prevRows = this.prevRows || 0;
252   if (prevRows > 0) {
253     exports.moveCursor(this.output, 0, -prevRows);
254   }
255
256   // Cursor to left edge.
257   exports.cursorTo(this.output, 0);
258   // erase data
259   exports.clearScreenDown(this.output);
260
261   // Write the prompt and the current buffer content.
262   this._writeToOutput(line);
263
264   // Force terminal to allocate a new line
265   if (lineCols === 0) {
266     this._writeToOutput(' ');
267   }
268
269   // Move cursor to original position.
270   exports.cursorTo(this.output, cursorPos.cols);
271
272   var diff = lineRows - cursorPos.rows;
273   if (diff > 0) {
274     exports.moveCursor(this.output, 0, -diff);
275   }
276
277   this.prevRows = cursorPos.rows;
278 };
279
280
281 Interface.prototype.close = function() {
282   if (this.closed) return;
283   this.pause();
284   if (this.terminal) {
285     this._setRawMode(false);
286   }
287   this.closed = true;
288   this.emit('close');
289 };
290
291
292 Interface.prototype.pause = function() {
293   if (this.paused) return;
294   this.input.pause();
295   this.paused = true;
296   this.emit('pause');
297   return this;
298 };
299
300
301 Interface.prototype.resume = function() {
302   if (!this.paused) return;
303   this.input.resume();
304   this.paused = false;
305   this.emit('resume');
306   return this;
307 };
308
309
310 Interface.prototype.write = function(d, key) {
311   if (this.paused) this.resume();
312   this.terminal ? this._ttyWrite(d, key) : this._normalWrite(d);
313 };
314
315 // \r\n, \n, or \r followed by something other than \n
316 var lineEnding = /\r?\n|\r(?!\n)/;
317 Interface.prototype._normalWrite = function(b) {
318   if (util.isUndefined(b)) {
319     return;
320   }
321   var string = this._decoder.write(b);
322   if (this._sawReturn) {
323     string = string.replace(/^\n/, '');
324     this._sawReturn = false;
325   }
326
327   // Run test() on the new string chunk, not on the entire line buffer.
328   var newPartContainsEnding = lineEnding.test(string);
329
330   if (this._line_buffer) {
331     string = this._line_buffer + string;
332     this._line_buffer = null;
333   }
334   if (newPartContainsEnding) {
335     this._sawReturn = /\r$/.test(string);
336
337     // got one or more newlines; process into "line" events
338     var lines = string.split(lineEnding);
339     // either '' or (concievably) the unfinished portion of the next line
340     string = lines.pop();
341     this._line_buffer = string;
342     lines.forEach(function(line) {
343       this._onLine(line);
344     }, this);
345   } else if (string) {
346     // no newlines this time, save what we have for next time
347     this._line_buffer = string;
348   }
349 };
350
351 Interface.prototype._insertString = function(c) {
352   //BUG: Problem when adding tabs with following content.
353   //     Perhaps the bug is in _refreshLine(). Not sure.
354   //     A hack would be to insert spaces instead of literal '\t'.
355   if (this.cursor < this.line.length) {
356     var beg = this.line.slice(0, this.cursor);
357     var end = this.line.slice(this.cursor, this.line.length);
358     this.line = beg + c + end;
359     this.cursor += c.length;
360     this._refreshLine();
361   } else {
362     this.line += c;
363     this.cursor += c.length;
364
365     if (this._getCursorPos().cols === 0) {
366       this._refreshLine();
367     } else {
368       this._writeToOutput(c);
369     }
370
371     // a hack to get the line refreshed if it's needed
372     this._moveCursor(0);
373   }
374 };
375
376 Interface.prototype._tabComplete = function() {
377   var self = this;
378
379   self.pause();
380   self.completer(self.line.slice(0, self.cursor), function(err, rv) {
381     self.resume();
382
383     if (err) {
384       // XXX Log it somewhere?
385       return;
386     }
387
388     var completions = rv[0],
389         completeOn = rv[1];  // the text that was completed
390     if (completions && completions.length) {
391       // Apply/show completions.
392       if (completions.length === 1) {
393         self._insertString(completions[0].slice(completeOn.length));
394       } else {
395         self._writeToOutput('\r\n');
396         var width = completions.reduce(function(a, b) {
397           return a.length > b.length ? a : b;
398         }).length + 2;  // 2 space padding
399         var maxColumns = Math.floor(self.columns / width) || 1;
400         var group = [], c;
401         for (var i = 0, compLen = completions.length; i < compLen; i++) {
402           c = completions[i];
403           if (c === '') {
404             handleGroup(self, group, width, maxColumns);
405             group = [];
406           } else {
407             group.push(c);
408           }
409         }
410         handleGroup(self, group, width, maxColumns);
411
412         // If there is a common prefix to all matches, then apply that
413         // portion.
414         var f = completions.filter(function(e) { if (e) return e; });
415         var prefix = commonPrefix(f);
416         if (prefix.length > completeOn.length) {
417           self._insertString(prefix.slice(completeOn.length));
418         }
419
420       }
421       self._refreshLine();
422     }
423   });
424 };
425
426 // this = Interface instance
427 function handleGroup(self, group, width, maxColumns) {
428   if (group.length == 0) {
429     return;
430   }
431   var minRows = Math.ceil(group.length / maxColumns);
432   for (var row = 0; row < minRows; row++) {
433     for (var col = 0; col < maxColumns; col++) {
434       var idx = row * maxColumns + col;
435       if (idx >= group.length) {
436         break;
437       }
438       var item = group[idx];
439       self._writeToOutput(item);
440       if (col < maxColumns - 1) {
441         for (var s = 0, itemLen = item.length; s < width - itemLen;
442              s++) {
443           self._writeToOutput(' ');
444         }
445       }
446     }
447     self._writeToOutput('\r\n');
448   }
449   self._writeToOutput('\r\n');
450 }
451
452 function commonPrefix(strings) {
453   if (!strings || strings.length == 0) {
454     return '';
455   }
456   var sorted = strings.slice().sort();
457   var min = sorted[0];
458   var max = sorted[sorted.length - 1];
459   for (var i = 0, len = min.length; i < len; i++) {
460     if (min[i] != max[i]) {
461       return min.slice(0, i);
462     }
463   }
464   return min;
465 }
466
467
468 Interface.prototype._wordLeft = function() {
469   if (this.cursor > 0) {
470     var leading = this.line.slice(0, this.cursor);
471     var match = leading.match(/([^\w\s]+|\w+|)\s*$/);
472     this._moveCursor(-match[0].length);
473   }
474 };
475
476
477 Interface.prototype._wordRight = function() {
478   if (this.cursor < this.line.length) {
479     var trailing = this.line.slice(this.cursor);
480     var match = trailing.match(/^(\s+|\W+|\w+)\s*/);
481     this._moveCursor(match[0].length);
482   }
483 };
484
485
486 Interface.prototype._deleteLeft = function() {
487   if (this.cursor > 0 && this.line.length > 0) {
488     this.line = this.line.slice(0, this.cursor - 1) +
489                 this.line.slice(this.cursor, this.line.length);
490
491     this.cursor--;
492     this._refreshLine();
493   }
494 };
495
496
497 Interface.prototype._deleteRight = function() {
498   this.line = this.line.slice(0, this.cursor) +
499               this.line.slice(this.cursor + 1, this.line.length);
500   this._refreshLine();
501 };
502
503
504 Interface.prototype._deleteWordLeft = function() {
505   if (this.cursor > 0) {
506     var leading = this.line.slice(0, this.cursor);
507     var match = leading.match(/([^\w\s]+|\w+|)\s*$/);
508     leading = leading.slice(0, leading.length - match[0].length);
509     this.line = leading + this.line.slice(this.cursor, this.line.length);
510     this.cursor = leading.length;
511     this._refreshLine();
512   }
513 };
514
515
516 Interface.prototype._deleteWordRight = function() {
517   if (this.cursor < this.line.length) {
518     var trailing = this.line.slice(this.cursor);
519     var match = trailing.match(/^(\s+|\W+|\w+)\s*/);
520     this.line = this.line.slice(0, this.cursor) +
521                 trailing.slice(match[0].length);
522     this._refreshLine();
523   }
524 };
525
526
527 Interface.prototype._deleteLineLeft = function() {
528   this.line = this.line.slice(this.cursor);
529   this.cursor = 0;
530   this._refreshLine();
531 };
532
533
534 Interface.prototype._deleteLineRight = function() {
535   this.line = this.line.slice(0, this.cursor);
536   this._refreshLine();
537 };
538
539
540 Interface.prototype.clearLine = function() {
541   this._moveCursor(+Infinity);
542   this._writeToOutput('\r\n');
543   this.line = '';
544   this.cursor = 0;
545   this.prevRows = 0;
546 };
547
548
549 Interface.prototype._line = function() {
550   var line = this._addHistory();
551   this.clearLine();
552   this._onLine(line);
553 };
554
555
556 Interface.prototype._historyNext = function() {
557   if (this.historyIndex > 0) {
558     this.historyIndex--;
559     this.line = this.history[this.historyIndex];
560     this.cursor = this.line.length; // set cursor to end of line.
561     this._refreshLine();
562
563   } else if (this.historyIndex === 0) {
564     this.historyIndex = -1;
565     this.cursor = 0;
566     this.line = '';
567     this._refreshLine();
568   }
569 };
570
571
572 Interface.prototype._historyPrev = function() {
573   if (this.historyIndex + 1 < this.history.length) {
574     this.historyIndex++;
575     this.line = this.history[this.historyIndex];
576     this.cursor = this.line.length; // set cursor to end of line.
577
578     this._refreshLine();
579   }
580 };
581
582
583 // Returns the last character's display position of the given string
584 Interface.prototype._getDisplayPos = function(str) {
585   var offset = 0;
586   var col = this.columns;
587   var row = 0;
588   var code;
589   str = stripVTControlCharacters(str);
590   for (var i = 0, len = str.length; i < len; i++) {
591     code = codePointAt(str, i);
592     if (code >= 0x10000) { // surrogates
593       i++;
594     }
595     if (code === 0x0a) { // new line \n
596       offset = 0;
597       row += 1;
598       continue;
599     }
600     if (isFullWidthCodePoint(code)) {
601       if ((offset + 1) % col === 0) {
602         offset++;
603       }
604       offset += 2;
605     } else {
606       offset++;
607     }
608   }
609   var cols = offset % col;
610   var rows = row + (offset - cols) / col;
611   return {cols: cols, rows: rows};
612 };
613
614
615 // Returns current cursor's position and line
616 Interface.prototype._getCursorPos = function() {
617   var columns = this.columns;
618   var strBeforeCursor = this._prompt + this.line.substring(0, this.cursor);
619   var dispPos = this._getDisplayPos(stripVTControlCharacters(strBeforeCursor));
620   var cols = dispPos.cols;
621   var rows = dispPos.rows;
622   // If the cursor is on a full-width character which steps over the line,
623   // move the cursor to the beginning of the next line.
624   if (cols + 1 === columns &&
625       this.cursor < this.line.length &&
626       isFullWidthCodePoint(codePointAt(this.line, this.cursor))) {
627     rows++;
628     cols = 0;
629   }
630   return {cols: cols, rows: rows};
631 };
632
633
634 // This function moves cursor dx places to the right
635 // (-dx for left) and refreshes the line if it is needed
636 Interface.prototype._moveCursor = function(dx) {
637   var oldcursor = this.cursor;
638   var oldPos = this._getCursorPos();
639   this.cursor += dx;
640
641   // bounds check
642   if (this.cursor < 0) this.cursor = 0;
643   else if (this.cursor > this.line.length) this.cursor = this.line.length;
644
645   var newPos = this._getCursorPos();
646
647   // check if cursors are in the same line
648   if (oldPos.rows === newPos.rows) {
649     var diffCursor = this.cursor - oldcursor;
650     var diffWidth;
651     if (diffCursor < 0) {
652       diffWidth = -getStringWidth(
653           this.line.substring(this.cursor, oldcursor)
654           );
655     } else if (diffCursor > 0) {
656       diffWidth = getStringWidth(
657           this.line.substring(this.cursor, oldcursor)
658           );
659     }
660     exports.moveCursor(this.output, diffWidth, 0);
661     this.prevRows = newPos.rows;
662   } else {
663     this._refreshLine();
664   }
665 };
666
667
668 // handle a write from the tty
669 Interface.prototype._ttyWrite = function(s, key) {
670   key = key || {};
671
672   // Ignore escape key - Fixes #2876
673   if (key.name == 'escape') return;
674
675   if (key.ctrl && key.shift) {
676     /* Control and shift pressed */
677     switch (key.name) {
678       case 'backspace':
679         this._deleteLineLeft();
680         break;
681
682       case 'delete':
683         this._deleteLineRight();
684         break;
685     }
686
687   } else if (key.ctrl) {
688     /* Control key pressed */
689
690     switch (key.name) {
691       case 'c':
692         if (EventEmitter.listenerCount(this, 'SIGINT') > 0) {
693           this.emit('SIGINT');
694         } else {
695           // This readline instance is finished
696           this.close();
697         }
698         break;
699
700       case 'h': // delete left
701         this._deleteLeft();
702         break;
703
704       case 'd': // delete right or EOF
705         if (this.cursor === 0 && this.line.length === 0) {
706           // This readline instance is finished
707           this.close();
708         } else if (this.cursor < this.line.length) {
709           this._deleteRight();
710         }
711         break;
712
713       case 'u': // delete the whole line
714         this.cursor = 0;
715         this.line = '';
716         this._refreshLine();
717         break;
718
719       case 'k': // delete from current to end of line
720         this._deleteLineRight();
721         break;
722
723       case 'a': // go to the start of the line
724         this._moveCursor(-Infinity);
725         break;
726
727       case 'e': // go to the end of the line
728         this._moveCursor(+Infinity);
729         break;
730
731       case 'b': // back one character
732         this._moveCursor(-1);
733         break;
734
735       case 'f': // forward one character
736         this._moveCursor(+1);
737         break;
738
739       case 'l': // clear the whole screen
740         exports.cursorTo(this.output, 0, 0);
741         exports.clearScreenDown(this.output);
742         this._refreshLine();
743         break;
744
745       case 'n': // next history item
746         this._historyNext();
747         break;
748
749       case 'p': // previous history item
750         this._historyPrev();
751         break;
752
753       case 'z':
754         if (process.platform == 'win32') break;
755         if (EventEmitter.listenerCount(this, 'SIGTSTP') > 0) {
756           this.emit('SIGTSTP');
757         } else {
758           process.once('SIGCONT', (function(self) {
759             return function() {
760               // Don't raise events if stream has already been abandoned.
761               if (!self.paused) {
762                 // Stream must be paused and resumed after SIGCONT to catch
763                 // SIGINT, SIGTSTP, and EOF.
764                 self.pause();
765                 self.emit('SIGCONT');
766               }
767               // explicitly re-enable "raw mode" and move the cursor to
768               // the correct position.
769               // See https://github.com/joyent/node/issues/3295.
770               self._setRawMode(true);
771               self._refreshLine();
772             };
773           })(this));
774           this._setRawMode(false);
775           process.kill(process.pid, 'SIGTSTP');
776         }
777         break;
778
779       case 'w': // delete backwards to a word boundary
780       case 'backspace':
781         this._deleteWordLeft();
782         break;
783
784       case 'delete': // delete forward to a word boundary
785         this._deleteWordRight();
786         break;
787
788       case 'left':
789         this._wordLeft();
790         break;
791
792       case 'right':
793         this._wordRight();
794         break;
795     }
796
797   } else if (key.meta) {
798     /* Meta key pressed */
799
800     switch (key.name) {
801       case 'b': // backward word
802         this._wordLeft();
803         break;
804
805       case 'f': // forward word
806         this._wordRight();
807         break;
808
809       case 'd': // delete forward word
810       case 'delete':
811         this._deleteWordRight();
812         break;
813
814       case 'backspace': // delete backwards to a word boundary
815         this._deleteWordLeft();
816         break;
817     }
818
819   } else {
820     /* No modifier keys used */
821
822     // \r bookkeeping is only relevant if a \n comes right after.
823     if (this._sawReturn && key.name !== 'enter')
824       this._sawReturn = false;
825
826     switch (key.name) {
827       case 'return':  // carriage return, i.e. \r
828         this._sawReturn = true;
829         this._line();
830         break;
831
832       case 'enter':
833         if (this._sawReturn)
834           this._sawReturn = false;
835         else
836           this._line();
837         break;
838
839       case 'backspace':
840         this._deleteLeft();
841         break;
842
843       case 'delete':
844         this._deleteRight();
845         break;
846
847       case 'tab': // tab completion
848         this._tabComplete();
849         break;
850
851       case 'left':
852         this._moveCursor(-1);
853         break;
854
855       case 'right':
856         this._moveCursor(+1);
857         break;
858
859       case 'home':
860         this._moveCursor(-Infinity);
861         break;
862
863       case 'end':
864         this._moveCursor(+Infinity);
865         break;
866
867       case 'up':
868         this._historyPrev();
869         break;
870
871       case 'down':
872         this._historyNext();
873         break;
874
875       default:
876         if (util.isBuffer(s))
877           s = s.toString('utf-8');
878
879         if (s) {
880           var lines = s.split(/\r\n|\n|\r/);
881           for (var i = 0, len = lines.length; i < len; i++) {
882             if (i > 0) {
883               this._line();
884             }
885             this._insertString(lines[i]);
886           }
887         }
888     }
889   }
890 };
891
892
893 exports.Interface = Interface;
894
895
896
897 /**
898  * accepts a readable Stream instance and makes it emit "keypress" events
899  */
900
901 function emitKeypressEvents(stream) {
902   if (stream._keypressDecoder) return;
903   var StringDecoder = require('string_decoder').StringDecoder; // lazy load
904   stream._keypressDecoder = new StringDecoder('utf8');
905
906   function onData(b) {
907     if (EventEmitter.listenerCount(stream, 'keypress') > 0) {
908       var r = stream._keypressDecoder.write(b);
909       if (r) emitKeys(stream, r);
910     } else {
911       // Nobody's watching anyway
912       stream.removeListener('data', onData);
913       stream.on('newListener', onNewListener);
914     }
915   }
916
917   function onNewListener(event) {
918     if (event == 'keypress') {
919       stream.on('data', onData);
920       stream.removeListener('newListener', onNewListener);
921     }
922   }
923
924   if (EventEmitter.listenerCount(stream, 'keypress') > 0) {
925     stream.on('data', onData);
926   } else {
927     stream.on('newListener', onNewListener);
928   }
929 }
930 exports.emitKeypressEvents = emitKeypressEvents;
931
932 /*
933   Some patterns seen in terminal key escape codes, derived from combos seen
934   at http://www.midnight-commander.org/browser/lib/tty/key.c
935
936   ESC letter
937   ESC [ letter
938   ESC [ modifier letter
939   ESC [ 1 ; modifier letter
940   ESC [ num char
941   ESC [ num ; modifier char
942   ESC O letter
943   ESC O modifier letter
944   ESC O 1 ; modifier letter
945   ESC N letter
946   ESC [ [ num ; modifier char
947   ESC [ [ 1 ; modifier letter
948   ESC ESC [ num char
949   ESC ESC O letter
950
951   - char is usually ~ but $ and ^ also happen with rxvt
952   - modifier is 1 +
953                 (shift     * 1) +
954                 (left_alt  * 2) +
955                 (ctrl      * 4) +
956                 (right_alt * 8)
957   - two leading ESCs apparently mean the same as one leading ESC
958 */
959
960 // Regexes used for ansi escape code splitting
961 var metaKeyCodeReAnywhere = /(?:\x1b)([a-zA-Z0-9])/;
962 var metaKeyCodeRe = new RegExp('^' + metaKeyCodeReAnywhere.source + '$');
963 var functionKeyCodeReAnywhere = new RegExp('(?:\x1b+)(O|N|\\[|\\[\\[)(?:' + [
964   '(\\d+)(?:;(\\d+))?([~^$])',
965   '(?:M([@ #!a`])(.)(.))', // mouse
966   '(?:1;)?(\\d+)?([a-zA-Z])'
967 ].join('|') + ')');
968 var functionKeyCodeRe = new RegExp('^' + functionKeyCodeReAnywhere.source);
969 var escapeCodeReAnywhere = new RegExp([
970   functionKeyCodeReAnywhere.source, metaKeyCodeReAnywhere.source, /\x1b./.source
971 ].join('|'));
972
973 function emitKeys(stream, s) {
974   if (util.isBuffer(s)) {
975     if (s[0] > 127 && util.isUndefined(s[1])) {
976       s[0] -= 128;
977       s = '\x1b' + s.toString(stream.encoding || 'utf-8');
978     } else {
979       s = s.toString(stream.encoding || 'utf-8');
980     }
981   }
982
983   var buffer = [];
984   var match;
985   while (match = escapeCodeReAnywhere.exec(s)) {
986     buffer = buffer.concat(s.slice(0, match.index).split(''));
987     buffer.push(match[0]);
988     s = s.slice(match.index + match[0].length);
989   }
990   buffer = buffer.concat(s.split(''));
991
992   buffer.forEach(function(s) {
993     var ch,
994         key = {
995           sequence: s,
996           name: undefined,
997           ctrl: false,
998           meta: false,
999           shift: false
1000         },
1001         parts;
1002
1003     if (s === '\r') {
1004       // carriage return
1005       key.name = 'return';
1006
1007     } else if (s === '\n') {
1008       // enter, should have been called linefeed
1009       key.name = 'enter';
1010
1011     } else if (s === '\t') {
1012       // tab
1013       key.name = 'tab';
1014
1015     } else if (s === '\b' || s === '\x7f' ||
1016                s === '\x1b\x7f' || s === '\x1b\b') {
1017       // backspace or ctrl+h
1018       key.name = 'backspace';
1019       key.meta = (s.charAt(0) === '\x1b');
1020
1021     } else if (s === '\x1b' || s === '\x1b\x1b') {
1022       // escape key
1023       key.name = 'escape';
1024       key.meta = (s.length === 2);
1025
1026     } else if (s === ' ' || s === '\x1b ') {
1027       key.name = 'space';
1028       key.meta = (s.length === 2);
1029
1030     } else if (s.length === 1 && s <= '\x1a') {
1031       // ctrl+letter
1032       key.name = String.fromCharCode(s.charCodeAt(0) + 'a'.charCodeAt(0) - 1);
1033       key.ctrl = true;
1034
1035     } else if (s.length === 1 && s >= 'a' && s <= 'z') {
1036       // lowercase letter
1037       key.name = s;
1038
1039     } else if (s.length === 1 && s >= 'A' && s <= 'Z') {
1040       // shift+letter
1041       key.name = s.toLowerCase();
1042       key.shift = true;
1043
1044     } else if (parts = metaKeyCodeRe.exec(s)) {
1045       // meta+character key
1046       key.name = parts[1].toLowerCase();
1047       key.meta = true;
1048       key.shift = /^[A-Z]$/.test(parts[1]);
1049
1050     } else if (parts = functionKeyCodeRe.exec(s)) {
1051       // ansi escape sequence
1052
1053       // reassemble the key code leaving out leading \x1b's,
1054       // the modifier key bitflag and any meaningless "1;" sequence
1055       var code = (parts[1] || '') + (parts[2] || '') +
1056                  (parts[4] || '') + (parts[9] || ''),
1057           modifier = (parts[3] || parts[8] || 1) - 1;
1058
1059       // Parse the key modifier
1060       key.ctrl = !!(modifier & 4);
1061       key.meta = !!(modifier & 10);
1062       key.shift = !!(modifier & 1);
1063       key.code = code;
1064
1065       // Parse the key itself
1066       switch (code) {
1067         /* xterm/gnome ESC O letter */
1068         case 'OP': key.name = 'f1'; break;
1069         case 'OQ': key.name = 'f2'; break;
1070         case 'OR': key.name = 'f3'; break;
1071         case 'OS': key.name = 'f4'; break;
1072
1073         /* xterm/rxvt ESC [ number ~ */
1074         case '[11~': key.name = 'f1'; break;
1075         case '[12~': key.name = 'f2'; break;
1076         case '[13~': key.name = 'f3'; break;
1077         case '[14~': key.name = 'f4'; break;
1078
1079         /* from Cygwin and used in libuv */
1080         case '[[A': key.name = 'f1'; break;
1081         case '[[B': key.name = 'f2'; break;
1082         case '[[C': key.name = 'f3'; break;
1083         case '[[D': key.name = 'f4'; break;
1084         case '[[E': key.name = 'f5'; break;
1085
1086         /* common */
1087         case '[15~': key.name = 'f5'; break;
1088         case '[17~': key.name = 'f6'; break;
1089         case '[18~': key.name = 'f7'; break;
1090         case '[19~': key.name = 'f8'; break;
1091         case '[20~': key.name = 'f9'; break;
1092         case '[21~': key.name = 'f10'; break;
1093         case '[23~': key.name = 'f11'; break;
1094         case '[24~': key.name = 'f12'; break;
1095
1096         /* xterm ESC [ letter */
1097         case '[A': key.name = 'up'; break;
1098         case '[B': key.name = 'down'; break;
1099         case '[C': key.name = 'right'; break;
1100         case '[D': key.name = 'left'; break;
1101         case '[E': key.name = 'clear'; break;
1102         case '[F': key.name = 'end'; break;
1103         case '[H': key.name = 'home'; break;
1104
1105         /* xterm/gnome ESC O letter */
1106         case 'OA': key.name = 'up'; break;
1107         case 'OB': key.name = 'down'; break;
1108         case 'OC': key.name = 'right'; break;
1109         case 'OD': key.name = 'left'; break;
1110         case 'OE': key.name = 'clear'; break;
1111         case 'OF': key.name = 'end'; break;
1112         case 'OH': key.name = 'home'; break;
1113
1114         /* xterm/rxvt ESC [ number ~ */
1115         case '[1~': key.name = 'home'; break;
1116         case '[2~': key.name = 'insert'; break;
1117         case '[3~': key.name = 'delete'; break;
1118         case '[4~': key.name = 'end'; break;
1119         case '[5~': key.name = 'pageup'; break;
1120         case '[6~': key.name = 'pagedown'; break;
1121
1122         /* putty */
1123         case '[[5~': key.name = 'pageup'; break;
1124         case '[[6~': key.name = 'pagedown'; break;
1125
1126         /* rxvt */
1127         case '[7~': key.name = 'home'; break;
1128         case '[8~': key.name = 'end'; break;
1129
1130         /* rxvt keys with modifiers */
1131         case '[a': key.name = 'up'; key.shift = true; break;
1132         case '[b': key.name = 'down'; key.shift = true; break;
1133         case '[c': key.name = 'right'; key.shift = true; break;
1134         case '[d': key.name = 'left'; key.shift = true; break;
1135         case '[e': key.name = 'clear'; key.shift = true; break;
1136
1137         case '[2$': key.name = 'insert'; key.shift = true; break;
1138         case '[3$': key.name = 'delete'; key.shift = true; break;
1139         case '[5$': key.name = 'pageup'; key.shift = true; break;
1140         case '[6$': key.name = 'pagedown'; key.shift = true; break;
1141         case '[7$': key.name = 'home'; key.shift = true; break;
1142         case '[8$': key.name = 'end'; key.shift = true; break;
1143
1144         case 'Oa': key.name = 'up'; key.ctrl = true; break;
1145         case 'Ob': key.name = 'down'; key.ctrl = true; break;
1146         case 'Oc': key.name = 'right'; key.ctrl = true; break;
1147         case 'Od': key.name = 'left'; key.ctrl = true; break;
1148         case 'Oe': key.name = 'clear'; key.ctrl = true; break;
1149
1150         case '[2^': key.name = 'insert'; key.ctrl = true; break;
1151         case '[3^': key.name = 'delete'; key.ctrl = true; break;
1152         case '[5^': key.name = 'pageup'; key.ctrl = true; break;
1153         case '[6^': key.name = 'pagedown'; key.ctrl = true; break;
1154         case '[7^': key.name = 'home'; key.ctrl = true; break;
1155         case '[8^': key.name = 'end'; key.ctrl = true; break;
1156
1157         /* misc. */
1158         case '[Z': key.name = 'tab'; key.shift = true; break;
1159         default: key.name = 'undefined'; break;
1160
1161       }
1162     }
1163
1164     // Don't emit a key if no name was found
1165     if (util.isUndefined(key.name)) {
1166       key = undefined;
1167     }
1168
1169     if (s.length === 1) {
1170       ch = s;
1171     }
1172
1173     if (key || ch) {
1174       stream.emit('keypress', ch, key);
1175     }
1176   });
1177 }
1178
1179
1180 /**
1181  * moves the cursor to the x and y coordinate on the given stream
1182  */
1183
1184 function cursorTo(stream, x, y) {
1185   if (util.isNullOrUndefined(stream))
1186     return;
1187
1188   if (!util.isNumber(x) && !util.isNumber(y))
1189     return;
1190
1191   if (!util.isNumber(x))
1192     throw new Error("Can't set cursor row without also setting it's column");
1193
1194   if (!util.isNumber(y)) {
1195     stream.write('\x1b[' + (x + 1) + 'G');
1196   } else {
1197     stream.write('\x1b[' + (y + 1) + ';' + (x + 1) + 'H');
1198   }
1199 }
1200 exports.cursorTo = cursorTo;
1201
1202
1203 /**
1204  * moves the cursor relative to its current location
1205  */
1206
1207 function moveCursor(stream, dx, dy) {
1208   if (util.isNullOrUndefined(stream))
1209     return;
1210
1211   if (dx < 0) {
1212     stream.write('\x1b[' + (-dx) + 'D');
1213   } else if (dx > 0) {
1214     stream.write('\x1b[' + dx + 'C');
1215   }
1216
1217   if (dy < 0) {
1218     stream.write('\x1b[' + (-dy) + 'A');
1219   } else if (dy > 0) {
1220     stream.write('\x1b[' + dy + 'B');
1221   }
1222 }
1223 exports.moveCursor = moveCursor;
1224
1225
1226 /**
1227  * clears the current line the cursor is on:
1228  *   -1 for left of the cursor
1229  *   +1 for right of the cursor
1230  *    0 for the entire line
1231  */
1232
1233 function clearLine(stream, dir) {
1234   if (util.isNullOrUndefined(stream))
1235     return;
1236
1237   if (dir < 0) {
1238     // to the beginning
1239     stream.write('\x1b[1K');
1240   } else if (dir > 0) {
1241     // to the end
1242     stream.write('\x1b[0K');
1243   } else {
1244     // entire line
1245     stream.write('\x1b[2K');
1246   }
1247 }
1248 exports.clearLine = clearLine;
1249
1250
1251 /**
1252  * clears the screen from the current position of the cursor down
1253  */
1254
1255 function clearScreenDown(stream) {
1256   if (util.isNullOrUndefined(stream))
1257     return;
1258
1259   stream.write('\x1b[0J');
1260 }
1261 exports.clearScreenDown = clearScreenDown;
1262
1263
1264 /**
1265  * Returns the number of columns required to display the given string.
1266  */
1267
1268 function getStringWidth(str) {
1269   var width = 0;
1270   str = stripVTControlCharacters(str);
1271   for (var i = 0, len = str.length; i < len; i++) {
1272     var code = codePointAt(str, i);
1273     if (code >= 0x10000) { // surrogates
1274       i++;
1275     }
1276     if (isFullWidthCodePoint(code)) {
1277       width += 2;
1278     } else {
1279       width++;
1280     }
1281   }
1282   return width;
1283 }
1284 exports.getStringWidth = getStringWidth;
1285
1286
1287 /**
1288  * Returns true if the character represented by a given
1289  * Unicode code point is full-width. Otherwise returns false.
1290  */
1291
1292 function isFullWidthCodePoint(code) {
1293   if (isNaN(code)) {
1294     return false;
1295   }
1296
1297   // Code points are derived from:
1298   // http://www.unicode.org/Public/UNIDATA/EastAsianWidth.txt
1299   if (code >= 0x1100 && (
1300       code <= 0x115f ||  // Hangul Jamo
1301       0x2329 === code || // LEFT-POINTING ANGLE BRACKET
1302       0x232a === code || // RIGHT-POINTING ANGLE BRACKET
1303       // CJK Radicals Supplement .. Enclosed CJK Letters and Months
1304       (0x2e80 <= code && code <= 0x3247 && code !== 0x303f) ||
1305       // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A
1306       0x3250 <= code && code <= 0x4dbf ||
1307       // CJK Unified Ideographs .. Yi Radicals
1308       0x4e00 <= code && code <= 0xa4c6 ||
1309       // Hangul Jamo Extended-A
1310       0xa960 <= code && code <= 0xa97c ||
1311       // Hangul Syllables
1312       0xac00 <= code && code <= 0xd7a3 ||
1313       // CJK Compatibility Ideographs
1314       0xf900 <= code && code <= 0xfaff ||
1315       // Vertical Forms
1316       0xfe10 <= code && code <= 0xfe19 ||
1317       // CJK Compatibility Forms .. Small Form Variants
1318       0xfe30 <= code && code <= 0xfe6b ||
1319       // Halfwidth and Fullwidth Forms
1320       0xff01 <= code && code <= 0xff60 ||
1321       0xffe0 <= code && code <= 0xffe6 ||
1322       // Kana Supplement
1323       0x1b000 <= code && code <= 0x1b001 ||
1324       // Enclosed Ideographic Supplement
1325       0x1f200 <= code && code <= 0x1f251 ||
1326       // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane
1327       0x20000 <= code && code <= 0x3fffd)) {
1328     return true;
1329   }
1330   return false;
1331 }
1332 exports.isFullWidthCodePoint = isFullWidthCodePoint;
1333
1334
1335 /**
1336  * Returns the Unicode code point for the character at the
1337  * given index in the given string. Similar to String.charCodeAt(),
1338  * but this function handles surrogates (code point >= 0x10000).
1339  */
1340
1341 function codePointAt(str, index) {
1342   var code = str.charCodeAt(index);
1343   var low;
1344   if (0xd800 <= code && code <= 0xdbff) { // High surrogate
1345     low = str.charCodeAt(index + 1);
1346     if (!isNaN(low)) {
1347       code = 0x10000 + (code - 0xd800) * 0x400 + (low - 0xdc00);
1348     }
1349   }
1350   return code;
1351 }
1352 exports.codePointAt = codePointAt;
1353
1354
1355 /**
1356  * Tries to remove all VT control characters. Use to estimate displayed
1357  * string width. May be buggy due to not running a real state machine
1358  */
1359 function stripVTControlCharacters(str) {
1360   str = str.replace(new RegExp(functionKeyCodeReAnywhere.source, 'g'), '');
1361   return str.replace(new RegExp(metaKeyCodeReAnywhere.source, 'g'), '');
1362 }
1363 exports.stripVTControlCharacters = stripVTControlCharacters;