~yolanda.robla/ubuntu/trusty/nodejs/add_distribution

« back to all changes in this revision

Viewing changes to lib/readline.js

  • Committer: Package Import Robot
  • Author(s): Jérémy Lal
  • Date: 2013-08-14 00:16:46 UTC
  • mfrom: (7.1.40 sid)
  • Revision ID: package-import@ubuntu.com-20130814001646-bzlysfh8sd6mukbo
Tags: 0.10.15~dfsg1-4
* Update 2005 patch, adding a handful of tests that can fail on
  slow platforms.
* Add 1004 patch to fix test failures when writing NaN to buffer
  on mipsel.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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');
35
 
 
36
 
 
37
 
exports.createInterface = function(input, output, completer) {
38
 
  return new Interface(input, output, completer);
 
34
 
 
35
 
 
36
exports.createInterface = function(input, output, completer, terminal) {
 
37
  var rl;
 
38
  if (arguments.length === 1) {
 
39
    rl = new Interface(input);
 
40
  } else {
 
41
    rl = new Interface(input, output, completer, terminal);
 
42
  }
 
43
  return rl;
39
44
};
40
45
 
41
46
 
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);
45
50
  }
 
51
 
 
52
  this._sawReturn = false;
 
53
 
46
54
  EventEmitter.call(this);
47
55
 
 
56
  if (arguments.length === 1) {
 
57
    // an options object was given
 
58
    output = input.output;
 
59
    completer = input.completer;
 
60
    terminal = input.terminal;
 
61
    input = input.input;
 
62
  }
 
63
 
48
64
  completer = completer || function() { return []; };
49
65
 
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');
 
68
  }
 
69
 
 
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;
52
74
  }
53
75
 
54
76
  var self = this;
55
77
 
56
78
  this.output = output;
57
79
  this.input = input;
58
 
  input.resume();
59
80
 
60
81
  // Check arity, 2 - for async, 1 for sync
61
82
  this.completer = completer.length === 2 ? completer : function(v, callback) {
64
85
 
65
86
  this.setPrompt('> ');
66
87
 
67
 
  this.enabled = output.isTTY;
68
 
 
69
 
  if (parseInt(process.env['NODE_NO_READLINE'], 10)) {
70
 
    this.enabled = false;
71
 
  }
72
 
 
73
 
  if (!this.enabled) {
74
 
    input.on('data', function(data) {
75
 
      self._normalWrite(data);
 
88
  this.terminal = !!terminal;
 
89
 
 
90
  function ondata(data) {
 
91
    self._normalWrite(data);
 
92
  }
 
93
 
 
94
  function onend() {
 
95
    self.close();
 
96
  }
 
97
 
 
98
  function onkeypress(s, key) {
 
99
    self._ttyWrite(s, key);
 
100
  }
 
101
 
 
102
  function onresize() {
 
103
    self._refreshLine();
 
104
  }
 
105
 
 
106
  if (!this.terminal) {
 
107
    input.on('data', ondata);
 
108
    input.on('end', onend);
 
109
    self.once('close', function() {
 
110
      input.removeListener('data', ondata);
 
111
      input.removeListener('end', onend);
76
112
    });
 
113
    var StringDecoder = require('string_decoder').StringDecoder; // lazy load
 
114
    this._decoder = new StringDecoder('utf8');
77
115
 
78
116
  } else {
79
117
 
 
118
    exports.emitKeypressEvents(input);
 
119
 
80
120
    // input usually refers to stdin
81
 
    input.on('keypress', function(s, key) {
82
 
      self._ttyWrite(s, key);
83
 
    });
 
121
    input.on('keypress', onkeypress);
84
122
 
85
123
    // Current line
86
124
    this.line = '';
87
125
 
88
 
    // Check process.env.TERM ?
89
 
    tty.setRawMode(true);
90
 
    this.enabled = true;
 
126
    this._setRawMode(true);
 
127
    this.terminal = true;
91
128
 
92
129
    // Cursor position on the line.
93
130
    this.cursor = 0;
95
132
    this.history = [];
96
133
    this.historyIndex = -1;
97
134
 
98
 
    var winSize = output.getWindowSize();
99
 
    exports.columns = winSize[0];
100
 
 
101
 
    if (process.listeners('SIGWINCH').length === 0) {
102
 
      process.on('SIGWINCH', function() {
103
 
        var winSize = output.getWindowSize();
104
 
        exports.columns = winSize[0];
105
 
      });
106
 
    }
 
135
    output.on('resize', onresize);
 
136
    self.once('close', function() {
 
137
      input.removeListener('keypress', onkeypress);
 
138
      output.removeListener('resize', onresize);
 
139
    });
107
140
  }
 
141
 
 
142
  input.resume();
108
143
}
109
144
 
110
145
inherits(Interface, EventEmitter);
111
146
 
112
147
Interface.prototype.__defineGetter__('columns', function() {
113
 
  return exports.columns;
 
148
  return this.output.columns || Infinity;
114
149
});
115
150
 
116
151
Interface.prototype.setPrompt = function(prompt, length) {
120
155
  } else {
121
156
    var lines = prompt.split(/[\r\n]/);
122
157
    var lastLine = lines[lines.length - 1];
123
 
    this._promptLength = Buffer.byteLength(lastLine);
124
 
  }
125
 
};
126
 
 
127
 
 
128
 
Interface.prototype.prompt = function() {
129
 
  if (this.enabled) {
130
 
    this.cursor = 0;
 
158
    this._promptLength = lastLine.length;
 
159
  }
 
160
};
 
161
 
 
162
 
 
163
Interface.prototype._setRawMode = function(mode) {
 
164
  if (typeof this.input.setRawMode === 'function') {
 
165
    return this.input.setRawMode(mode);
 
166
  }
 
167
};
 
168
 
 
169
 
 
170
Interface.prototype.prompt = function(preserveCursor) {
 
171
  if (this.paused) this.resume();
 
172
  if (this.terminal) {
 
173
    if (!preserveCursor) this.cursor = 0;
131
174
    this._refreshLine();
132
175
  } else {
133
176
    this.output.write(this._prompt);
136
179
 
137
180
 
138
181
Interface.prototype.question = function(query, cb) {
139
 
  if (cb) {
140
 
    this.resume();
 
182
  if (typeof cb === 'function') {
141
183
    if (this._questionCallback) {
142
 
      this.output.write('\n');
143
184
      this.prompt();
144
185
    } else {
145
186
      this._oldPrompt = this._prompt;
146
187
      this.setPrompt(query);
147
188
      this._questionCallback = cb;
148
 
      this.output.write('\n');
149
189
      this.prompt();
150
190
    }
151
191
  }
167
207
Interface.prototype._addHistory = function() {
168
208
  if (this.line.length === 0) return '';
169
209
 
170
 
  this.history.unshift(this.line);
171
 
  this.line = '';
 
210
  if (this.history.length === 0 || this.history[0] !== this.line) {
 
211
    this.history.unshift(this.line);
 
212
 
 
213
    // Only store so many
 
214
    if (this.history.length > kHistorySize) this.history.pop();
 
215
  }
 
216
 
172
217
  this.historyIndex = -1;
173
 
 
174
 
  this.cursor = 0;
175
 
 
176
 
  // Only store so many
177
 
  if (this.history.length > kHistorySize) this.history.pop();
178
 
 
179
218
  return this.history[0];
180
219
};
181
220
 
182
221
 
183
222
Interface.prototype._refreshLine = function() {
184
 
  if (this._closed) return;
 
223
  var columns = this.columns;
 
224
 
 
225
  // line length
 
226
  var line = this._prompt + this.line;
 
227
  var lineLength = line.length;
 
228
  var lineCols = lineLength % columns;
 
229
  var lineRows = (lineLength - lineCols) / columns;
 
230
 
 
231
  // cursor position
 
232
  var cursorPos = this._getCursorPos();
 
233
 
 
234
  // first move to the bottom of the current line, based on cursor pos
 
235
  var prevRows = this.prevRows || 0;
 
236
  if (prevRows > 0) {
 
237
    exports.moveCursor(this.output, 0, -prevRows);
 
238
  }
185
239
 
186
240
  // Cursor to left edge.
187
 
  this.output.cursorTo(0);
 
241
  exports.cursorTo(this.output, 0);
 
242
  // erase data
 
243
  exports.clearScreenDown(this.output);
188
244
 
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);
192
247
 
193
 
  // Erase to right.
194
 
  this.output.clearLine(1);
 
248
  // Force terminal to allocate a new line
 
249
  if (lineCols === 0) {
 
250
    this.output.write(' ');
 
251
  }
195
252
 
196
253
  // Move cursor to original position.
197
 
  this.output.cursorTo(this._promptLength + this.cursor);
 
254
  exports.cursorTo(this.output, cursorPos.cols);
 
255
 
 
256
  var diff = lineRows - cursorPos.rows;
 
257
  if (diff > 0) {
 
258
    exports.moveCursor(this.output, 0, -diff);
 
259
  }
 
260
 
 
261
  this.prevRows = cursorPos.rows;
198
262
};
199
263
 
200
264
 
201
 
Interface.prototype.close = function(d) {
202
 
  if (this._closing) return;
203
 
  this._closing = true;
204
 
  if (this.enabled) {
205
 
    tty.setRawMode(false);
 
265
Interface.prototype.close = function() {
 
266
  if (this.closed) return;
 
267
  if (this.terminal) {
 
268
    this._setRawMode(false);
206
269
  }
 
270
  this.pause();
 
271
  this.closed = true;
207
272
  this.emit('close');
208
 
  this._closed = true;
209
273
};
210
274
 
211
275
 
212
276
Interface.prototype.pause = function() {
213
 
  if (this.enabled) {
214
 
    tty.setRawMode(false);
215
 
  }
 
277
  if (this.paused) return;
 
278
  this.input.pause();
 
279
  this.paused = true;
 
280
  this.emit('pause');
216
281
};
217
282
 
218
283
 
219
284
Interface.prototype.resume = function() {
220
 
  if (this.enabled) {
221
 
    tty.setRawMode(true);
222
 
  }
 
285
  if (!this.paused) return;
 
286
  this.input.resume();
 
287
  this.paused = false;
 
288
  this.emit('resume');
223
289
};
224
290
 
225
291
 
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);
229
295
};
230
296
 
231
 
 
 
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
234
 
  // new lines.
235
 
  if (b !== undefined)
236
 
    this._onLine(b.toString());
 
300
  if (b === undefined) {
 
301
    return;
 
302
  }
 
303
  var string = this._decoder.write(b);
 
304
  if (this._sawReturn) {
 
305
    string = string.replace(/^\n/, '');
 
306
    this._sawReturn = false;
 
307
  }
 
308
 
 
309
  if (this._line_buffer) {
 
310
    string = this._line_buffer + string;
 
311
    this._line_buffer = null;
 
312
  }
 
313
  if (lineEnding.test(string)) {
 
314
    this._sawReturn = /\r$/.test(string);
 
315
 
 
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) {
 
322
      this._onLine(line);
 
323
    }, this);
 
324
  } else if (string) {
 
325
    // no newlines this time, save what we have for next time
 
326
    this._line_buffer = string;
 
327
  }
237
328
};
238
329
 
239
330
Interface.prototype._insertString = function(c) {
249
340
  } else {
250
341
    this.line += c;
251
342
    this.cursor += c.length;
252
 
    this.output.write(c);
 
343
 
 
344
    if (this._getCursorPos().cols === 0) {
 
345
      this._refreshLine();
 
346
    } else {
 
347
      this.output.write(c);
 
348
    }
 
349
 
 
350
    // a hack to get the line refreshed if it's needed
 
351
    this._moveCursor(0);
253
352
  }
254
353
};
255
354
 
277
376
          return a.length > b.length ? a : b;
278
377
        }).length + 2;  // 2 space padding
279
378
        var maxColumns = Math.floor(self.columns / width) || 1;
280
 
 
281
 
        function handleGroup(group) {
282
 
          if (group.length == 0) {
283
 
            return;
284
 
          }
285
 
          var minRows = Math.ceil(group.length / maxColumns);
286
 
          for (var row = 0; row < minRows; row++) {
287
 
            for (var col = 0; col < maxColumns; col++) {
288
 
              var idx = row * maxColumns + col;
289
 
              if (idx >= group.length) {
290
 
                break;
291
 
              }
292
 
              var item = group[idx];
293
 
              self.output.write(item);
294
 
              if (col < maxColumns - 1) {
295
 
                for (var s = 0, itemLen = item.length; s < width - itemLen;
296
 
                     s++) {
297
 
                  self.output.write(' ');
298
 
                }
299
 
              }
300
 
            }
301
 
            self.output.write('\r\n');
302
 
          }
303
 
          self.output.write('\r\n');
304
 
        }
305
 
 
306
379
        var group = [], c;
307
380
        for (var i = 0, compLen = completions.length; i < compLen; i++) {
308
381
          c = completions[i];
309
382
          if (c === '') {
310
 
            handleGroup(group);
 
383
            handleGroup(self, group, width, maxColumns);
311
384
            group = [];
312
385
          } else {
313
386
            group.push(c);
314
387
          }
315
388
        }
316
 
        handleGroup(group);
 
389
        handleGroup(self, group, width, maxColumns);
317
390
 
318
391
        // If there is a common prefix to all matches, then apply that
319
392
        // portion.
329
402
  });
330
403
};
331
404
 
 
405
// this = Interface instance
 
406
function handleGroup(self, group, width, maxColumns) {
 
407
  if (group.length == 0) {
 
408
    return;
 
409
  }
 
410
  var minRows = Math.ceil(group.length / maxColumns);
 
411
  for (var row = 0; row < minRows; row++) {
 
412
    for (var col = 0; col < maxColumns; col++) {
 
413
      var idx = row * maxColumns + col;
 
414
      if (idx >= group.length) {
 
415
        break;
 
416
      }
 
417
      var item = group[idx];
 
418
      self.output.write(item);
 
419
      if (col < maxColumns - 1) {
 
420
        for (var s = 0, itemLen = item.length; s < width - itemLen;
 
421
             s++) {
 
422
          self.output.write(' ');
 
423
        }
 
424
      }
 
425
    }
 
426
    self.output.write('\r\n');
 
427
  }
 
428
  self.output.write('\r\n');
 
429
}
332
430
 
333
431
function commonPrefix(strings) {
334
432
  if (!strings || strings.length == 0) {
350
448
  if (this.cursor > 0) {
351
449
    var leading = this.line.slice(0, this.cursor);
352
450
    var match = leading.match(/([^\w\s]+|\w+|)\s*$/);
353
 
    this.cursor -= match[0].length;
354
 
    this._refreshLine();
 
451
    this._moveCursor(-match[0].length);
355
452
  }
356
453
};
357
454
 
360
457
  if (this.cursor < this.line.length) {
361
458
    var trailing = this.line.slice(this.cursor);
362
459
    var match = trailing.match(/^(\s+|\W+|\w+)\s*/);
363
 
    this.cursor += match[0].length;
364
 
    this._refreshLine();
 
460
    this._moveCursor(match[0].length);
365
461
  }
366
462
};
367
463
 
420
516
};
421
517
 
422
518
 
 
519
Interface.prototype.clearLine = function() {
 
520
  this._moveCursor(+Infinity);
 
521
  this.output.write('\r\n');
 
522
  this.line = '';
 
523
  this.cursor = 0;
 
524
  this.prevRows = 0;
 
525
};
 
526
 
 
527
 
423
528
Interface.prototype._line = function() {
424
529
  var line = this._addHistory();
425
 
  this.output.write('\r\n');
 
530
  this.clearLine();
426
531
  this._onLine(line);
427
532
};
428
533
 
454
559
};
455
560
 
456
561
 
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};
 
569
};
 
570
 
 
571
 
 
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();
 
577
  this.cursor += dx;
 
578
 
 
579
  // bounds check
 
580
  if (this.cursor < 0) this.cursor = 0;
 
581
  if (this.cursor > this.line.length) this.cursor = this.line.length;
 
582
 
 
583
  var newPos = this._getCursorPos();
 
584
 
 
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;
461
589
  } else {
462
 
    this.close();
 
590
    this._refreshLine();
463
591
  }
464
592
};
465
593
 
466
594
 
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;
470
597
  key = key || {};
471
598
 
 
599
  // Ignore escape key - Fixes #2876
 
600
  if (key.name == 'escape') return;
 
601
 
472
602
  if (key.ctrl && key.shift) {
473
603
    /* Control and shift pressed */
474
604
    switch (key.name) {
486
616
 
487
617
    switch (key.name) {
488
618
      case 'c':
489
 
        if (this.listeners('SIGINT').length) {
 
619
        if (EventEmitter.listenerCount(this, 'SIGINT') > 0) {
490
620
          this.emit('SIGINT');
491
621
        } else {
492
 
          // default behavior, end the readline
493
 
          this._attemptClose();
 
622
          // This readline instance is finished
 
623
          this.close();
494
624
        }
495
625
        break;
496
626
 
500
630
 
501
631
      case 'd': // delete right or EOF
502
632
        if (this.cursor === 0 && this.line.length === 0) {
503
 
          this._attemptClose();
 
633
          // This readline instance is finished
 
634
          this.close();
504
635
        } else if (this.cursor < this.line.length) {
505
636
          this._deleteRight();
506
637
        }
517
648
        break;
518
649
 
519
650
      case 'a': // go to the start of the line
520
 
        this.cursor = 0;
521
 
        this._refreshLine();
 
651
        this._moveCursor(-Infinity);
522
652
        break;
523
653
 
524
654
      case 'e': // go to the end of the line
525
 
        this.cursor = this.line.length;
526
 
        this._refreshLine();
 
655
        this._moveCursor(+Infinity);
527
656
        break;
528
657
 
529
658
      case 'b': // back one character
530
 
        if (this.cursor > 0) {
531
 
          this.cursor--;
532
 
          this._refreshLine();
533
 
        }
 
659
        this._moveCursor(-1);
534
660
        break;
535
661
 
536
662
      case 'f': // forward one character
537
 
        if (this.cursor != this.line.length) {
538
 
          this.cursor++;
539
 
          this._refreshLine();
540
 
        }
 
663
        this._moveCursor(+1);
 
664
        break;
 
665
 
 
666
      case 'l': // clear the whole screen
 
667
        exports.cursorTo(this.output, 0, 0);
 
668
        exports.clearScreenDown(this.output);
 
669
        this._refreshLine();
541
670
        break;
542
671
 
543
672
      case 'n': // next history item
549
678
        break;
550
679
 
551
680
      case 'z':
552
 
        process.kill(process.pid, 'SIGTSTP');
553
 
        return;
 
681
        if (process.platform == 'win32') break;
 
682
        if (EventEmitter.listenerCount(this, 'SIGTSTP') > 0) {
 
683
          this.emit('SIGTSTP');
 
684
        } else {
 
685
          process.once('SIGCONT', (function(self) {
 
686
            return function() {
 
687
              // Don't raise events if stream has already been abandoned.
 
688
              if (!self.paused) {
 
689
                // Stream must be paused and resumed after SIGCONT to catch
 
690
                // SIGINT, SIGTSTP, and EOF.
 
691
                self.pause();
 
692
                self.emit('SIGCONT');
 
693
              }
 
694
              // explictly re-enable "raw mode" and move the cursor to
 
695
              // the correct position.
 
696
              // See https://github.com/joyent/node/issues/3295.
 
697
              self._setRawMode(true);
 
698
              self._refreshLine();
 
699
            };
 
700
          })(this));
 
701
          this._setRawMode(false);
 
702
          process.kill(process.pid, 'SIGTSTP');
 
703
        }
 
704
        break;
554
705
 
555
706
      case 'w': // delete backwards to a word boundary
556
707
      case 'backspace':
571
722
 
572
723
      case 'right':
573
724
        this._wordRight();
 
725
        break;
574
726
    }
575
727
 
576
728
  } else if (key.meta) {
598
750
  } else {
599
751
    /* No modifier keys used */
600
752
 
 
753
    // \r bookkeeping is only relevant if a \n comes right after.
 
754
    if (this._sawReturn && key.name !== 'enter')
 
755
      this._sawReturn = false;
 
756
 
601
757
    switch (key.name) {
 
758
      case 'return':  // carriage return, i.e. \r
 
759
        this._sawReturn = true;
 
760
        this._line();
 
761
        break;
 
762
 
602
763
      case 'enter':
603
 
        this._line();
 
764
        if (this._sawReturn)
 
765
          this._sawReturn = false;
 
766
        else
 
767
          this._line();
604
768
        break;
605
769
 
606
770
      case 'backspace':
616
780
        break;
617
781
 
618
782
      case 'left':
619
 
        if (this.cursor > 0) {
620
 
          this.cursor--;
621
 
          this.output.moveCursor(-1, 0);
622
 
        }
 
783
        this._moveCursor(-1);
623
784
        break;
624
785
 
625
786
      case 'right':
626
 
        if (this.cursor != this.line.length) {
627
 
          this.cursor++;
628
 
          this.output.moveCursor(1, 0);
629
 
        }
 
787
        this._moveCursor(+1);
630
788
        break;
631
789
 
632
790
      case 'home':
633
 
        this.cursor = 0;
634
 
        this._refreshLine();
 
791
        this._moveCursor(-Infinity);
635
792
        break;
636
793
 
637
794
      case 'end':
638
 
        this.cursor = this.line.length;
639
 
        this._refreshLine();
 
795
        this._moveCursor(+Infinity);
640
796
        break;
641
797
 
642
798
      case 'up':
666
822
 
667
823
 
668
824
exports.Interface = Interface;
 
825
 
 
826
 
 
827
 
 
828
/**
 
829
 * accepts a readable Stream instance and makes it emit "keypress" events
 
830
 */
 
831
 
 
832
function emitKeypressEvents(stream) {
 
833
  if (stream._keypressDecoder) return;
 
834
  var StringDecoder = require('string_decoder').StringDecoder; // lazy load
 
835
  stream._keypressDecoder = new StringDecoder('utf8');
 
836
 
 
837
  function onData(b) {
 
838
    if (EventEmitter.listenerCount(stream, 'keypress') > 0) {
 
839
      var r = stream._keypressDecoder.write(b);
 
840
      if (r) emitKey(stream, r);
 
841
    } else {
 
842
      // Nobody's watching anyway
 
843
      stream.removeListener('data', onData);
 
844
      stream.on('newListener', onNewListener);
 
845
    }
 
846
  }
 
847
 
 
848
  function onNewListener(event) {
 
849
    if (event == 'keypress') {
 
850
      stream.on('data', onData);
 
851
      stream.removeListener('newListener', onNewListener);
 
852
    }
 
853
  }
 
854
 
 
855
  if (EventEmitter.listenerCount(stream, 'keypress') > 0) {
 
856
    stream.on('data', onData);
 
857
  } else {
 
858
    stream.on('newListener', onNewListener);
 
859
  }
 
860
}
 
861
exports.emitKeypressEvents = emitKeypressEvents;
 
862
 
 
863
/*
 
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
 
866
 
 
867
  ESC letter
 
868
  ESC [ letter
 
869
  ESC [ modifier letter
 
870
  ESC [ 1 ; modifier letter
 
871
  ESC [ num char
 
872
  ESC [ num ; modifier char
 
873
  ESC O letter
 
874
  ESC O modifier letter
 
875
  ESC O 1 ; modifier letter
 
876
  ESC N letter
 
877
  ESC [ [ num ; modifier char
 
878
  ESC [ [ 1 ; modifier letter
 
879
  ESC ESC [ num char
 
880
  ESC ESC O letter
 
881
 
 
882
  - char is usually ~ but $ and ^ also happen with rxvt
 
883
  - modifier is 1 +
 
884
                (shift     * 1) +
 
885
                (left_alt  * 2) +
 
886
                (ctrl      * 4) +
 
887
                (right_alt * 8)
 
888
  - two leading ESCs apparently mean the same as one leading ESC
 
889
*/
 
890
 
 
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]))/;
 
895
 
 
896
function emitKey(stream, s) {
 
897
  var ch,
 
898
      key = {
 
899
        name: undefined,
 
900
        ctrl: false,
 
901
        meta: false,
 
902
        shift: false
 
903
      },
 
904
      parts;
 
905
 
 
906
  if (Buffer.isBuffer(s)) {
 
907
    if (s[0] > 127 && s[1] === undefined) {
 
908
      s[0] -= 128;
 
909
      s = '\x1b' + s.toString(stream.encoding || 'utf-8');
 
910
    } else {
 
911
      s = s.toString(stream.encoding || 'utf-8');
 
912
    }
 
913
  }
 
914
 
 
915
  key.sequence = s;
 
916
 
 
917
  if (s === '\r') {
 
918
    // carriage return
 
919
    key.name = 'return';
 
920
 
 
921
  } else if (s === '\n') {
 
922
    // enter, should have been called linefeed
 
923
    key.name = 'enter';
 
924
 
 
925
  } else if (s === '\t') {
 
926
    // tab
 
927
    key.name = 'tab';
 
928
 
 
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');
 
934
 
 
935
  } else if (s === '\x1b' || s === '\x1b\x1b') {
 
936
    // escape key
 
937
    key.name = 'escape';
 
938
    key.meta = (s.length === 2);
 
939
 
 
940
  } else if (s === ' ' || s === '\x1b ') {
 
941
    key.name = 'space';
 
942
    key.meta = (s.length === 2);
 
943
 
 
944
  } else if (s <= '\x1a') {
 
945
    // ctrl+letter
 
946
    key.name = String.fromCharCode(s.charCodeAt(0) + 'a'.charCodeAt(0) - 1);
 
947
    key.ctrl = true;
 
948
 
 
949
  } else if (s.length === 1 && s >= 'a' && s <= 'z') {
 
950
    // lowercase letter
 
951
    key.name = s;
 
952
 
 
953
  } else if (s.length === 1 && s >= 'A' && s <= 'Z') {
 
954
    // shift+letter
 
955
    key.name = s.toLowerCase();
 
956
    key.shift = true;
 
957
 
 
958
  } else if (parts = metaKeyCodeRe.exec(s)) {
 
959
    // meta+character key
 
960
    key.name = parts[1].toLowerCase();
 
961
    key.meta = true;
 
962
    key.shift = /^[A-Z]$/.test(parts[1]);
 
963
 
 
964
  } else if (parts = functionKeyCodeRe.exec(s)) {
 
965
    // ansi escape sequence
 
966
 
 
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;
 
972
 
 
973
    // Parse the key modifier
 
974
    key.ctrl = !!(modifier & 4);
 
975
    key.meta = !!(modifier & 10);
 
976
    key.shift = !!(modifier & 1);
 
977
    key.code = code;
 
978
 
 
979
    // Parse the key itself
 
980
    switch (code) {
 
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;
 
986
 
 
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;
 
992
 
 
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;
 
999
 
 
1000
      /* common */
 
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;
 
1009
 
 
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;
 
1018
 
 
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;
 
1027
 
 
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;
 
1035
 
 
1036
      /* putty */
 
1037
      case '[[5~': key.name = 'pageup'; break;
 
1038
      case '[[6~': key.name = 'pagedown'; break;
 
1039
 
 
1040
      /* rxvt */
 
1041
      case '[7~': key.name = 'home'; break;
 
1042
      case '[8~': key.name = 'end'; break;
 
1043
 
 
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;
 
1050
 
 
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;
 
1057
 
 
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;
 
1063
 
 
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;
 
1070
 
 
1071
      /* misc. */
 
1072
      case '[Z': key.name = 'tab'; key.shift = true; break;
 
1073
      default: key.name = 'undefined'; break;
 
1074
 
 
1075
    }
 
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) {
 
1080
      emitKey(stream, c);
 
1081
    });
 
1082
    return;
 
1083
  }
 
1084
 
 
1085
  // Don't emit a key if no name was found
 
1086
  if (key.name === undefined) {
 
1087
    key = undefined;
 
1088
  }
 
1089
 
 
1090
  if (s.length === 1) {
 
1091
    ch = s;
 
1092
  }
 
1093
 
 
1094
  if (key || ch) {
 
1095
    stream.emit('keypress', ch, key);
 
1096
  }
 
1097
}
 
1098
 
 
1099
 
 
1100
/**
 
1101
 * moves the cursor to the x and y coordinate on the given stream
 
1102
 */
 
1103
 
 
1104
function cursorTo(stream, x, y) {
 
1105
  if (typeof x !== 'number' && typeof y !== 'number')
 
1106
    return;
 
1107
 
 
1108
  if (typeof x !== 'number')
 
1109
    throw new Error("Can't set cursor row without also setting it's column");
 
1110
 
 
1111
  if (typeof y !== 'number') {
 
1112
    stream.write('\x1b[' + (x + 1) + 'G');
 
1113
  } else {
 
1114
    stream.write('\x1b[' + (y + 1) + ';' + (x + 1) + 'H');
 
1115
  }
 
1116
}
 
1117
exports.cursorTo = cursorTo;
 
1118
 
 
1119
 
 
1120
/**
 
1121
 * moves the cursor relative to its current location
 
1122
 */
 
1123
 
 
1124
function moveCursor(stream, dx, dy) {
 
1125
  if (dx < 0) {
 
1126
    stream.write('\x1b[' + (-dx) + 'D');
 
1127
  } else if (dx > 0) {
 
1128
    stream.write('\x1b[' + dx + 'C');
 
1129
  }
 
1130
 
 
1131
  if (dy < 0) {
 
1132
    stream.write('\x1b[' + (-dy) + 'A');
 
1133
  } else if (dy > 0) {
 
1134
    stream.write('\x1b[' + dy + 'B');
 
1135
  }
 
1136
}
 
1137
exports.moveCursor = moveCursor;
 
1138
 
 
1139
 
 
1140
/**
 
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
 
1145
 */
 
1146
 
 
1147
function clearLine(stream, dir) {
 
1148
  if (dir < 0) {
 
1149
    // to the beginning
 
1150
    stream.write('\x1b[1K');
 
1151
  } else if (dir > 0) {
 
1152
    // to the end
 
1153
    stream.write('\x1b[0K');
 
1154
  } else {
 
1155
    // entire line
 
1156
    stream.write('\x1b[2K');
 
1157
  }
 
1158
}
 
1159
exports.clearLine = clearLine;
 
1160
 
 
1161
 
 
1162
/**
 
1163
 * clears the screen from the current position of the cursor down
 
1164
 */
 
1165
 
 
1166
function clearScreenDown(stream) {
 
1167
  stream.write('\x1b[0J');
 
1168
}
 
1169
exports.clearScreenDown = clearScreenDown;