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