• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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// Flags: --expose-internals
23'use strict';
24const common = require('../common');
25common.skipIfDumbTerminal();
26
27const assert = require('assert');
28const readline = require('readline');
29const {
30  getStringWidth,
31  stripVTControlCharacters
32} = require('internal/util/inspect');
33const EventEmitter = require('events').EventEmitter;
34const { Writable, Readable } = require('stream');
35
36class FakeInput extends EventEmitter {
37  resume() {}
38  pause() {}
39  write() {}
40  end() {}
41}
42
43function isWarned(emitter) {
44  for (const name in emitter) {
45    const listeners = emitter[name];
46    if (listeners.warned) return true;
47  }
48  return false;
49}
50
51{
52  // Default crlfDelay is 100ms
53  const fi = new FakeInput();
54  const rli = new readline.Interface({ input: fi, output: fi });
55  assert.strictEqual(rli.crlfDelay, 100);
56  rli.close();
57}
58
59{
60  // Minimum crlfDelay is 100ms
61  const fi = new FakeInput();
62  const rli = new readline.Interface({ input: fi, output: fi, crlfDelay: 0 });
63  assert.strictEqual(rli.crlfDelay, 100);
64  rli.close();
65}
66
67{
68  // Set crlfDelay to float 100.5ms
69  const fi = new FakeInput();
70  const rli = new readline.Interface({
71    input: fi,
72    output: fi,
73    crlfDelay: 100.5
74  });
75  assert.strictEqual(rli.crlfDelay, 100.5);
76  rli.close();
77}
78
79{
80  // Set crlfDelay to 5000ms
81  const fi = new FakeInput();
82  const rli = new readline.Interface({
83    input: fi,
84    output: fi,
85    crlfDelay: 5000
86  });
87  assert.strictEqual(rli.crlfDelay, 5000);
88  rli.close();
89}
90
91[ true, false ].forEach(function(terminal) {
92  // disable history
93  {
94    const fi = new FakeInput();
95    const rli = new readline.Interface(
96      { input: fi, output: fi, terminal: terminal, historySize: 0 }
97    );
98    assert.strictEqual(rli.historySize, 0);
99
100    fi.emit('data', 'asdf\n');
101    assert.deepStrictEqual(rli.history, terminal ? [] : undefined);
102    rli.close();
103  }
104
105  // Default history size 30
106  {
107    const fi = new FakeInput();
108    const rli = new readline.Interface(
109      { input: fi, output: fi, terminal: terminal }
110    );
111    assert.strictEqual(rli.historySize, 30);
112
113    fi.emit('data', 'asdf\n');
114    assert.deepStrictEqual(rli.history, terminal ? ['asdf'] : undefined);
115    rli.close();
116  }
117
118  // sending a full line
119  {
120    const fi = new FakeInput();
121    const rli = new readline.Interface(
122      { input: fi, output: fi, terminal: terminal }
123    );
124    let called = false;
125    rli.on('line', function(line) {
126      called = true;
127      assert.strictEqual(line, 'asdf');
128    });
129    fi.emit('data', 'asdf\n');
130    assert.ok(called);
131  }
132
133  // Sending a blank line
134  {
135    const fi = new FakeInput();
136    const rli = new readline.Interface(
137      { input: fi, output: fi, terminal: terminal }
138    );
139    let called = false;
140    rli.on('line', function(line) {
141      called = true;
142      assert.strictEqual(line, '');
143    });
144    fi.emit('data', '\n');
145    assert.ok(called);
146  }
147
148  // Sending a single character with no newline
149  {
150    const fi = new FakeInput();
151    const rli = new readline.Interface(fi, {});
152    let called = false;
153    rli.on('line', function(line) {
154      called = true;
155    });
156    fi.emit('data', 'a');
157    assert.ok(!called);
158    rli.close();
159  }
160
161  // Sending a single character with no newline and then a newline
162  {
163    const fi = new FakeInput();
164    const rli = new readline.Interface(
165      { input: fi, output: fi, terminal: terminal }
166    );
167    let called = false;
168    rli.on('line', function(line) {
169      called = true;
170      assert.strictEqual(line, 'a');
171    });
172    fi.emit('data', 'a');
173    assert.ok(!called);
174    fi.emit('data', '\n');
175    assert.ok(called);
176    rli.close();
177  }
178
179  // Sending multiple newlines at once
180  {
181    const fi = new FakeInput();
182    const rli = new readline.Interface(
183      { input: fi, output: fi, terminal: terminal }
184    );
185    const expectedLines = ['foo', 'bar', 'baz'];
186    let callCount = 0;
187    rli.on('line', function(line) {
188      assert.strictEqual(line, expectedLines[callCount]);
189      callCount++;
190    });
191    fi.emit('data', `${expectedLines.join('\n')}\n`);
192    assert.strictEqual(callCount, expectedLines.length);
193    rli.close();
194  }
195
196  // Sending multiple newlines at once that does not end with a new line
197  {
198    const fi = new FakeInput();
199    const rli = new readline.Interface(
200      { input: fi, output: fi, terminal: terminal }
201    );
202    const expectedLines = ['foo', 'bar', 'baz', 'bat'];
203    let callCount = 0;
204    rli.on('line', function(line) {
205      assert.strictEqual(line, expectedLines[callCount]);
206      callCount++;
207    });
208    fi.emit('data', expectedLines.join('\n'));
209    assert.strictEqual(callCount, expectedLines.length - 1);
210    rli.close();
211  }
212
213  // Sending multiple newlines at once that does not end with a new(empty)
214  // line and a `end` event
215  {
216    const fi = new FakeInput();
217    const rli = new readline.Interface(
218      { input: fi, output: fi, terminal: terminal }
219    );
220    const expectedLines = ['foo', 'bar', 'baz', ''];
221    let callCount = 0;
222    rli.on('line', function(line) {
223      assert.strictEqual(line, expectedLines[callCount]);
224      callCount++;
225    });
226    rli.on('close', function() {
227      callCount++;
228    });
229    fi.emit('data', expectedLines.join('\n'));
230    fi.emit('end');
231    assert.strictEqual(callCount, expectedLines.length);
232    rli.close();
233  }
234
235  // Sending multiple newlines at once that does not end with a new line
236  // and a `end` event(last line is)
237
238  // \r should behave like \n when alone
239  {
240    const fi = new FakeInput();
241    const rli = new readline.Interface(
242      { input: fi, output: fi, terminal: true }
243    );
244    const expectedLines = ['foo', 'bar', 'baz', 'bat'];
245    let callCount = 0;
246    rli.on('line', function(line) {
247      assert.strictEqual(line, expectedLines[callCount]);
248      callCount++;
249    });
250    fi.emit('data', expectedLines.join('\r'));
251    assert.strictEqual(callCount, expectedLines.length - 1);
252    rli.close();
253  }
254
255  // \r at start of input should output blank line
256  {
257    const fi = new FakeInput();
258    const rli = new readline.Interface(
259      { input: fi, output: fi, terminal: true }
260    );
261    const expectedLines = ['', 'foo' ];
262    let callCount = 0;
263    rli.on('line', function(line) {
264      assert.strictEqual(line, expectedLines[callCount]);
265      callCount++;
266    });
267    fi.emit('data', '\rfoo\r');
268    assert.strictEqual(callCount, expectedLines.length);
269    rli.close();
270  }
271
272  // Emit two line events when the delay
273  // between \r and \n exceeds crlfDelay
274  {
275    const fi = new FakeInput();
276    const delay = 200;
277    const rli = new readline.Interface({
278      input: fi,
279      output: fi,
280      terminal: terminal,
281      crlfDelay: delay
282    });
283    let callCount = 0;
284    rli.on('line', function(line) {
285      callCount++;
286    });
287    fi.emit('data', '\r');
288    setTimeout(common.mustCall(() => {
289      fi.emit('data', '\n');
290      assert.strictEqual(callCount, 2);
291      rli.close();
292    }), delay * 2);
293  }
294
295  // Set crlfDelay to `Infinity` is allowed
296  {
297    const fi = new FakeInput();
298    const delay = 200;
299    const crlfDelay = Infinity;
300    const rli = new readline.Interface({
301      input: fi,
302      output: fi,
303      terminal: terminal,
304      crlfDelay
305    });
306    let callCount = 0;
307    rli.on('line', function(line) {
308      callCount++;
309    });
310    fi.emit('data', '\r');
311    setTimeout(common.mustCall(() => {
312      fi.emit('data', '\n');
313      assert.strictEqual(callCount, 1);
314      rli.close();
315    }), delay);
316  }
317
318  // \t when there is no completer function should behave like an ordinary
319  // character
320  {
321    const fi = new FakeInput();
322    const rli = new readline.Interface(
323      { input: fi, output: fi, terminal: true }
324    );
325    let called = false;
326    rli.on('line', function(line) {
327      assert.strictEqual(line, '\t');
328      assert.strictEqual(called, false);
329      called = true;
330    });
331    fi.emit('data', '\t');
332    fi.emit('data', '\n');
333    assert.ok(called);
334    rli.close();
335  }
336
337  // \t does not become part of the input when there is a completer function
338  {
339    const fi = new FakeInput();
340    const completer = (line) => [[], line];
341    const rli = new readline.Interface({
342      input: fi,
343      output: fi,
344      terminal: true,
345      completer: completer
346    });
347    let called = false;
348    rli.on('line', function(line) {
349      assert.strictEqual(line, 'foo');
350      assert.strictEqual(called, false);
351      called = true;
352    });
353    for (const character of '\tfo\to\t') {
354      fi.emit('data', character);
355    }
356    fi.emit('data', '\n');
357    assert.ok(called);
358    rli.close();
359  }
360
361  // Constructor throws if completer is not a function or undefined
362  {
363    const fi = new FakeInput();
364    assert.throws(() => {
365      readline.createInterface({
366        input: fi,
367        completer: 'string is not valid'
368      });
369    }, {
370      name: 'TypeError',
371      code: 'ERR_INVALID_OPT_VALUE'
372    });
373  }
374
375  // Constructor throws if historySize is not a positive number
376  {
377    const fi = new FakeInput();
378    assert.throws(() => {
379      readline.createInterface({
380        input: fi, historySize: 'not a number'
381      });
382    }, {
383      name: 'RangeError',
384      code: 'ERR_INVALID_OPT_VALUE'
385    });
386
387    assert.throws(() => {
388      readline.createInterface({
389        input: fi, historySize: -1
390      });
391    }, {
392      name: 'RangeError',
393      code: 'ERR_INVALID_OPT_VALUE'
394    });
395
396    assert.throws(() => {
397      readline.createInterface({
398        input: fi, historySize: NaN
399      });
400    }, {
401      name: 'RangeError',
402      code: 'ERR_INVALID_OPT_VALUE'
403    });
404  }
405
406  // Duplicate lines are removed from history when
407  // `options.removeHistoryDuplicates` is `true`
408  {
409    const fi = new FakeInput();
410    const rli = new readline.Interface({
411      input: fi,
412      output: fi,
413      terminal: true,
414      removeHistoryDuplicates: true
415    });
416    const expectedLines = ['foo', 'bar', 'baz', 'bar', 'bat', 'bat'];
417    // ['foo', 'baz', 'bar', bat'];
418    let callCount = 0;
419    rli.on('line', function(line) {
420      assert.strictEqual(line, expectedLines[callCount]);
421      callCount++;
422    });
423    fi.emit('data', `${expectedLines.join('\n')}\n`);
424    assert.strictEqual(callCount, expectedLines.length);
425    fi.emit('keypress', '.', { name: 'up' }); // 'bat'
426    assert.strictEqual(rli.line, expectedLines[--callCount]);
427    fi.emit('keypress', '.', { name: 'up' }); // 'bar'
428    assert.notStrictEqual(rli.line, expectedLines[--callCount]);
429    assert.strictEqual(rli.line, expectedLines[--callCount]);
430    fi.emit('keypress', '.', { name: 'up' }); // 'baz'
431    assert.strictEqual(rli.line, expectedLines[--callCount]);
432    fi.emit('keypress', '.', { name: 'up' }); // 'foo'
433    assert.notStrictEqual(rli.line, expectedLines[--callCount]);
434    assert.strictEqual(rli.line, expectedLines[--callCount]);
435    assert.strictEqual(callCount, 0);
436    fi.emit('keypress', '.', { name: 'down' }); // 'baz'
437    assert.strictEqual(rli.line, 'baz');
438    assert.strictEqual(rli.historyIndex, 2);
439    fi.emit('keypress', '.', { name: 'n', ctrl: true }); // 'bar'
440    assert.strictEqual(rli.line, 'bar');
441    assert.strictEqual(rli.historyIndex, 1);
442    fi.emit('keypress', '.', { name: 'n', ctrl: true });
443    assert.strictEqual(rli.line, 'bat');
444    assert.strictEqual(rli.historyIndex, 0);
445    // Activate the substring history search.
446    fi.emit('keypress', '.', { name: 'down' }); // 'bat'
447    assert.strictEqual(rli.line, 'bat');
448    assert.strictEqual(rli.historyIndex, -1);
449    // Deactivate substring history search.
450    fi.emit('keypress', '.', { name: 'backspace' }); // 'ba'
451    assert.strictEqual(rli.historyIndex, -1);
452    assert.strictEqual(rli.line, 'ba');
453    // Activate the substring history search.
454    fi.emit('keypress', '.', { name: 'down' }); // 'ba'
455    assert.strictEqual(rli.historyIndex, -1);
456    assert.strictEqual(rli.line, 'ba');
457    fi.emit('keypress', '.', { name: 'down' }); // 'ba'
458    assert.strictEqual(rli.historyIndex, -1);
459    assert.strictEqual(rli.line, 'ba');
460    fi.emit('keypress', '.', { name: 'up' }); // 'bat'
461    assert.strictEqual(rli.historyIndex, 0);
462    assert.strictEqual(rli.line, 'bat');
463    fi.emit('keypress', '.', { name: 'up' }); // 'bar'
464    assert.strictEqual(rli.historyIndex, 1);
465    assert.strictEqual(rli.line, 'bar');
466    fi.emit('keypress', '.', { name: 'up' }); // 'baz'
467    assert.strictEqual(rli.historyIndex, 2);
468    assert.strictEqual(rli.line, 'baz');
469    fi.emit('keypress', '.', { name: 'up' }); // 'ba'
470    assert.strictEqual(rli.historyIndex, 4);
471    assert.strictEqual(rli.line, 'ba');
472    fi.emit('keypress', '.', { name: 'up' }); // 'ba'
473    assert.strictEqual(rli.historyIndex, 4);
474    assert.strictEqual(rli.line, 'ba');
475    // Deactivate substring history search and reset history index.
476    fi.emit('keypress', '.', { name: 'right' }); // 'ba'
477    assert.strictEqual(rli.historyIndex, -1);
478    assert.strictEqual(rli.line, 'ba');
479    // Substring history search activated.
480    fi.emit('keypress', '.', { name: 'up' }); // 'ba'
481    assert.strictEqual(rli.historyIndex, 0);
482    assert.strictEqual(rli.line, 'bat');
483    rli.close();
484  }
485
486  // Duplicate lines are not removed from history when
487  // `options.removeHistoryDuplicates` is `false`
488  {
489    const fi = new FakeInput();
490    const rli = new readline.Interface({
491      input: fi,
492      output: fi,
493      terminal: true,
494      removeHistoryDuplicates: false
495    });
496    const expectedLines = ['foo', 'bar', 'baz', 'bar', 'bat', 'bat'];
497    let callCount = 0;
498    rli.on('line', function(line) {
499      assert.strictEqual(line, expectedLines[callCount]);
500      callCount++;
501    });
502    fi.emit('data', `${expectedLines.join('\n')}\n`);
503    assert.strictEqual(callCount, expectedLines.length);
504    fi.emit('keypress', '.', { name: 'up' }); // 'bat'
505    assert.strictEqual(rli.line, expectedLines[--callCount]);
506    fi.emit('keypress', '.', { name: 'up' }); // 'bar'
507    assert.notStrictEqual(rli.line, expectedLines[--callCount]);
508    assert.strictEqual(rli.line, expectedLines[--callCount]);
509    fi.emit('keypress', '.', { name: 'up' }); // 'baz'
510    assert.strictEqual(rli.line, expectedLines[--callCount]);
511    fi.emit('keypress', '.', { name: 'up' }); // 'bar'
512    assert.strictEqual(rli.line, expectedLines[--callCount]);
513    fi.emit('keypress', '.', { name: 'up' }); // 'foo'
514    assert.strictEqual(rli.line, expectedLines[--callCount]);
515    assert.strictEqual(callCount, 0);
516    rli.close();
517  }
518
519  // Sending a multi-byte utf8 char over multiple writes
520  {
521    const buf = Buffer.from('☮', 'utf8');
522    const fi = new FakeInput();
523    const rli = new readline.Interface(
524      { input: fi, output: fi, terminal: terminal }
525    );
526    let callCount = 0;
527    rli.on('line', function(line) {
528      callCount++;
529      assert.strictEqual(line, buf.toString('utf8'));
530    });
531    [].forEach.call(buf, function(i) {
532      fi.emit('data', Buffer.from([i]));
533    });
534    assert.strictEqual(callCount, 0);
535    fi.emit('data', '\n');
536    assert.strictEqual(callCount, 1);
537    rli.close();
538  }
539
540  // Regression test for repl freeze, #1968:
541  // check that nothing fails if 'keypress' event throws.
542  {
543    const fi = new FakeInput();
544    const rli = new readline.Interface(
545      { input: fi, output: fi, terminal: true }
546    );
547    const keys = [];
548    const err = new Error('bad thing happened');
549    fi.on('keypress', function(key) {
550      keys.push(key);
551      if (key === 'X') {
552        throw err;
553      }
554    });
555    assert.throws(
556      () => fi.emit('data', 'fooX'),
557      (e) => {
558        assert.strictEqual(e, err);
559        return true;
560      }
561    );
562    fi.emit('data', 'bar');
563    assert.strictEqual(keys.join(''), 'fooXbar');
564    rli.close();
565  }
566
567  // Calling readline without `new`
568  {
569    const fi = new FakeInput();
570    const rli = readline.Interface(
571      { input: fi, output: fi, terminal: terminal }
572    );
573    let called = false;
574    rli.on('line', function(line) {
575      called = true;
576      assert.strictEqual(line, 'asdf');
577    });
578    fi.emit('data', 'asdf\n');
579    assert.ok(called);
580    rli.close();
581  }
582
583  // Calling the question callback
584  {
585    let called = false;
586    const fi = new FakeInput();
587    const rli = new readline.Interface(
588      { input: fi, output: fi, terminal: terminal }
589    );
590    rli.question('foo?', function(answer) {
591      called = true;
592      assert.strictEqual(answer, 'bar');
593    });
594    rli.write('bar\n');
595    assert.ok(called);
596    rli.close();
597  }
598
599  if (terminal) {
600    // history is bound
601    {
602      const fi = new FakeInput();
603      const rli = new readline.Interface(
604        { input: fi, output: fi, terminal, historySize: 2 }
605      );
606      const lines = ['line 1', 'line 2', 'line 3'];
607      fi.emit('data', lines.join('\n') + '\n');
608      assert.strictEqual(rli.history.length, 2);
609      assert.strictEqual(rli.history[0], 'line 3');
610      assert.strictEqual(rli.history[1], 'line 2');
611    }
612    // question
613    {
614      const fi = new FakeInput();
615      const rli = new readline.Interface(
616        { input: fi, output: fi, terminal: terminal }
617      );
618      const expectedLines = ['foo'];
619      rli.question(expectedLines[0], function() {
620        rli.close();
621      });
622      const cursorPos = rli.getCursorPos();
623      assert.strictEqual(cursorPos.rows, 0);
624      assert.strictEqual(cursorPos.cols, expectedLines[0].length);
625      rli.close();
626    }
627
628    // Sending a multi-line question
629    {
630      const fi = new FakeInput();
631      const rli = new readline.Interface(
632        { input: fi, output: fi, terminal: terminal }
633      );
634      const expectedLines = ['foo', 'bar'];
635      rli.question(expectedLines.join('\n'), function() {
636        rli.close();
637      });
638      const cursorPos = rli.getCursorPos();
639      assert.strictEqual(cursorPos.rows, expectedLines.length - 1);
640      assert.strictEqual(cursorPos.cols, expectedLines.slice(-1)[0].length);
641      rli.close();
642    }
643
644    {
645      // Beginning and end of line
646      const fi = new FakeInput();
647      const rli = new readline.Interface({
648        input: fi,
649        output: fi,
650        prompt: '',
651        terminal: terminal
652      });
653      fi.emit('data', 'the quick brown fox');
654      fi.emit('keypress', '.', { ctrl: true, name: 'a' });
655      let cursorPos = rli.getCursorPos();
656      assert.strictEqual(cursorPos.rows, 0);
657      assert.strictEqual(cursorPos.cols, 0);
658      fi.emit('keypress', '.', { ctrl: true, name: 'e' });
659      cursorPos = rli.getCursorPos();
660      assert.strictEqual(cursorPos.rows, 0);
661      assert.strictEqual(cursorPos.cols, 19);
662      rli.close();
663    }
664
665    {
666      // Back and Forward one character
667      const fi = new FakeInput();
668      const rli = new readline.Interface({
669        input: fi,
670        output: fi,
671        prompt: '',
672        terminal: terminal
673      });
674      fi.emit('data', 'the quick brown fox');
675      let cursorPos = rli.getCursorPos();
676      assert.strictEqual(cursorPos.rows, 0);
677      assert.strictEqual(cursorPos.cols, 19);
678
679      // Back one character
680      fi.emit('keypress', '.', { ctrl: true, name: 'b' });
681      cursorPos = rli.getCursorPos();
682      assert.strictEqual(cursorPos.rows, 0);
683      assert.strictEqual(cursorPos.cols, 18);
684      // Back one character
685      fi.emit('keypress', '.', { ctrl: true, name: 'b' });
686      cursorPos = rli.getCursorPos();
687      assert.strictEqual(cursorPos.rows, 0);
688      assert.strictEqual(cursorPos.cols, 17);
689      // Forward one character
690      fi.emit('keypress', '.', { ctrl: true, name: 'f' });
691      cursorPos = rli.getCursorPos();
692      assert.strictEqual(cursorPos.rows, 0);
693      assert.strictEqual(cursorPos.cols, 18);
694      // Forward one character
695      fi.emit('keypress', '.', { ctrl: true, name: 'f' });
696      cursorPos = rli.getCursorPos();
697      assert.strictEqual(cursorPos.rows, 0);
698      assert.strictEqual(cursorPos.cols, 19);
699      rli.close();
700    }
701
702    // Back and Forward one astral character
703    {
704      const fi = new FakeInput();
705      const rli = new readline.Interface({
706        input: fi,
707        output: fi,
708        prompt: '',
709        terminal: terminal
710      });
711      fi.emit('data', '��');
712
713      // Move left one character/code point
714      fi.emit('keypress', '.', { name: 'left' });
715      let cursorPos = rli.getCursorPos();
716      assert.strictEqual(cursorPos.rows, 0);
717      assert.strictEqual(cursorPos.cols, 0);
718
719      // Move right one character/code point
720      fi.emit('keypress', '.', { name: 'right' });
721      cursorPos = rli.getCursorPos();
722      assert.strictEqual(cursorPos.rows, 0);
723      assert.strictEqual(cursorPos.cols, 2);
724
725      rli.on('line', common.mustCall((line) => {
726        assert.strictEqual(line, '��');
727      }));
728      fi.emit('data', '\n');
729      rli.close();
730    }
731
732    // Two astral characters left
733    {
734      const fi = new FakeInput();
735      const rli = new readline.Interface({
736        input: fi,
737        output: fi,
738        prompt: '',
739        terminal: terminal
740      });
741      fi.emit('data', '��');
742
743      // Move left one character/code point
744      fi.emit('keypress', '.', { name: 'left' });
745      let cursorPos = rli.getCursorPos();
746      assert.strictEqual(cursorPos.rows, 0);
747      assert.strictEqual(cursorPos.cols, 0);
748
749      fi.emit('data', '��');
750      cursorPos = rli.getCursorPos();
751      assert.strictEqual(cursorPos.rows, 0);
752      assert.strictEqual(cursorPos.cols, 2);
753
754      rli.on('line', common.mustCall((line) => {
755        assert.strictEqual(line, '����');
756      }));
757      fi.emit('data', '\n');
758      rli.close();
759    }
760
761    // Two astral characters right
762    {
763      const fi = new FakeInput();
764      const rli = new readline.Interface({
765        input: fi,
766        output: fi,
767        prompt: '',
768        terminal: terminal
769      });
770      fi.emit('data', '��');
771
772      // Move left one character/code point
773      fi.emit('keypress', '.', { name: 'right' });
774      let cursorPos = rli.getCursorPos();
775      assert.strictEqual(cursorPos.rows, 0);
776      assert.strictEqual(cursorPos.cols, 2);
777
778      fi.emit('data', '��');
779      cursorPos = rli.getCursorPos();
780      assert.strictEqual(cursorPos.rows, 0);
781      assert.strictEqual(cursorPos.cols, 4);
782
783      rli.on('line', common.mustCall((line) => {
784        assert.strictEqual(line, '����');
785      }));
786      fi.emit('data', '\n');
787      rli.close();
788    }
789
790    {
791      // `wordLeft` and `wordRight`
792      const fi = new FakeInput();
793      const rli = new readline.Interface({
794        input: fi,
795        output: fi,
796        prompt: '',
797        terminal: terminal
798      });
799      fi.emit('data', 'the quick brown fox');
800      fi.emit('keypress', '.', { ctrl: true, name: 'left' });
801      let cursorPos = rli.getCursorPos();
802      assert.strictEqual(cursorPos.rows, 0);
803      assert.strictEqual(cursorPos.cols, 16);
804      fi.emit('keypress', '.', { meta: true, name: 'b' });
805      cursorPos = rli.getCursorPos();
806      assert.strictEqual(cursorPos.rows, 0);
807      assert.strictEqual(cursorPos.cols, 10);
808      fi.emit('keypress', '.', { ctrl: true, name: 'right' });
809      cursorPos = rli.getCursorPos();
810      assert.strictEqual(cursorPos.rows, 0);
811      assert.strictEqual(cursorPos.cols, 16);
812      fi.emit('keypress', '.', { meta: true, name: 'f' });
813      cursorPos = rli.getCursorPos();
814      assert.strictEqual(cursorPos.rows, 0);
815      assert.strictEqual(cursorPos.cols, 19);
816      rli.close();
817    }
818
819    {
820      // `deleteWordLeft`
821      [
822        { ctrl: true, name: 'w' },
823        { ctrl: true, name: 'backspace' },
824        { meta: true, name: 'backspace' }
825      ]
826        .forEach((deleteWordLeftKey) => {
827          let fi = new FakeInput();
828          let rli = new readline.Interface({
829            input: fi,
830            output: fi,
831            prompt: '',
832            terminal: terminal
833          });
834          fi.emit('data', 'the quick brown fox');
835          fi.emit('keypress', '.', { ctrl: true, name: 'left' });
836          rli.on('line', common.mustCall((line) => {
837            assert.strictEqual(line, 'the quick fox');
838          }));
839          fi.emit('keypress', '.', deleteWordLeftKey);
840          fi.emit('data', '\n');
841          rli.close();
842
843          // No effect if pressed at beginning of line
844          fi = new FakeInput();
845          rli = new readline.Interface({
846            input: fi,
847            output: fi,
848            prompt: '',
849            terminal: terminal
850          });
851          fi.emit('data', 'the quick brown fox');
852          fi.emit('keypress', '.', { ctrl: true, name: 'a' });
853          rli.on('line', common.mustCall((line) => {
854            assert.strictEqual(line, 'the quick brown fox');
855          }));
856          fi.emit('keypress', '.', deleteWordLeftKey);
857          fi.emit('data', '\n');
858          rli.close();
859        });
860    }
861
862    {
863      // `deleteWordRight`
864      [
865        { ctrl: true, name: 'delete' },
866        { meta: true, name: 'delete' },
867        { meta: true, name: 'd' }
868      ]
869        .forEach((deleteWordRightKey) => {
870          let fi = new FakeInput();
871          let rli = new readline.Interface({
872            input: fi,
873            output: fi,
874            prompt: '',
875            terminal: terminal
876          });
877          fi.emit('data', 'the quick brown fox');
878          fi.emit('keypress', '.', { ctrl: true, name: 'left' });
879          fi.emit('keypress', '.', { ctrl: true, name: 'left' });
880          rli.on('line', common.mustCall((line) => {
881            assert.strictEqual(line, 'the quick fox');
882          }));
883          fi.emit('keypress', '.', deleteWordRightKey);
884          fi.emit('data', '\n');
885          rli.close();
886
887          // No effect if pressed at end of line
888          fi = new FakeInput();
889          rli = new readline.Interface({
890            input: fi,
891            output: fi,
892            prompt: '',
893            terminal: terminal
894          });
895          fi.emit('data', 'the quick brown fox');
896          rli.on('line', common.mustCall((line) => {
897            assert.strictEqual(line, 'the quick brown fox');
898          }));
899          fi.emit('keypress', '.', deleteWordRightKey);
900          fi.emit('data', '\n');
901          rli.close();
902        });
903    }
904
905    // deleteLeft
906    {
907      const fi = new FakeInput();
908      const rli = new readline.Interface({
909        input: fi,
910        output: fi,
911        prompt: '',
912        terminal: terminal
913      });
914      fi.emit('data', 'the quick brown fox');
915      let cursorPos = rli.getCursorPos();
916      assert.strictEqual(cursorPos.rows, 0);
917      assert.strictEqual(cursorPos.cols, 19);
918
919      // Delete left character
920      fi.emit('keypress', '.', { ctrl: true, name: 'h' });
921      cursorPos = rli.getCursorPos();
922      assert.strictEqual(cursorPos.rows, 0);
923      assert.strictEqual(cursorPos.cols, 18);
924      rli.on('line', common.mustCall((line) => {
925        assert.strictEqual(line, 'the quick brown fo');
926      }));
927      fi.emit('data', '\n');
928      rli.close();
929    }
930
931    // deleteLeft astral character
932    {
933      const fi = new FakeInput();
934      const rli = new readline.Interface({
935        input: fi,
936        output: fi,
937        prompt: '',
938        terminal: terminal
939      });
940      fi.emit('data', '��');
941      let cursorPos = rli.getCursorPos();
942      assert.strictEqual(cursorPos.rows, 0);
943      assert.strictEqual(cursorPos.cols, 2);
944      // Delete left character
945      fi.emit('keypress', '.', { ctrl: true, name: 'h' });
946      cursorPos = rli.getCursorPos();
947      assert.strictEqual(cursorPos.rows, 0);
948      assert.strictEqual(cursorPos.cols, 0);
949      rli.on('line', common.mustCall((line) => {
950        assert.strictEqual(line, '');
951      }));
952      fi.emit('data', '\n');
953      rli.close();
954    }
955
956    // deleteRight
957    {
958      const fi = new FakeInput();
959      const rli = new readline.Interface({
960        input: fi,
961        output: fi,
962        prompt: '',
963        terminal: terminal
964      });
965      fi.emit('data', 'the quick brown fox');
966
967      // Go to the start of the line
968      fi.emit('keypress', '.', { ctrl: true, name: 'a' });
969      let cursorPos = rli.getCursorPos();
970      assert.strictEqual(cursorPos.rows, 0);
971      assert.strictEqual(cursorPos.cols, 0);
972
973      // Delete right character
974      fi.emit('keypress', '.', { ctrl: true, name: 'd' });
975      cursorPos = rli.getCursorPos();
976      assert.strictEqual(cursorPos.rows, 0);
977      assert.strictEqual(cursorPos.cols, 0);
978      rli.on('line', common.mustCall((line) => {
979        assert.strictEqual(line, 'he quick brown fox');
980      }));
981      fi.emit('data', '\n');
982      rli.close();
983    }
984
985    // deleteRight astral character
986    {
987      const fi = new FakeInput();
988      const rli = new readline.Interface({
989        input: fi,
990        output: fi,
991        prompt: '',
992        terminal: terminal
993      });
994      fi.emit('data', '��');
995
996      // Go to the start of the line
997      fi.emit('keypress', '.', { ctrl: true, name: 'a' });
998      let cursorPos = rli.getCursorPos();
999      assert.strictEqual(cursorPos.rows, 0);
1000      assert.strictEqual(cursorPos.cols, 0);
1001
1002      // Delete right character
1003      fi.emit('keypress', '.', { ctrl: true, name: 'd' });
1004      cursorPos = rli.getCursorPos();
1005      assert.strictEqual(cursorPos.rows, 0);
1006      assert.strictEqual(cursorPos.cols, 0);
1007      rli.on('line', common.mustCall((line) => {
1008        assert.strictEqual(line, '');
1009      }));
1010      fi.emit('data', '\n');
1011      rli.close();
1012    }
1013
1014    // deleteLineLeft
1015    {
1016      const fi = new FakeInput();
1017      const rli = new readline.Interface({
1018        input: fi,
1019        output: fi,
1020        prompt: '',
1021        terminal: terminal
1022      });
1023      fi.emit('data', 'the quick brown fox');
1024      let cursorPos = rli.getCursorPos();
1025      assert.strictEqual(cursorPos.rows, 0);
1026      assert.strictEqual(cursorPos.cols, 19);
1027
1028      // Delete from current to start of line
1029      fi.emit('keypress', '.', { ctrl: true, shift: true, name: 'backspace' });
1030      cursorPos = rli.getCursorPos();
1031      assert.strictEqual(cursorPos.rows, 0);
1032      assert.strictEqual(cursorPos.cols, 0);
1033      rli.on('line', common.mustCall((line) => {
1034        assert.strictEqual(line, '');
1035      }));
1036      fi.emit('data', '\n');
1037      rli.close();
1038    }
1039
1040    // deleteLineRight
1041    {
1042      const fi = new FakeInput();
1043      const rli = new readline.Interface({
1044        input: fi,
1045        output: fi,
1046        prompt: '',
1047        terminal: terminal
1048      });
1049      fi.emit('data', 'the quick brown fox');
1050
1051      // Go to the start of the line
1052      fi.emit('keypress', '.', { ctrl: true, name: 'a' });
1053      let cursorPos = rli.getCursorPos();
1054      assert.strictEqual(cursorPos.rows, 0);
1055      assert.strictEqual(cursorPos.cols, 0);
1056
1057      // Delete from current to end of line
1058      fi.emit('keypress', '.', { ctrl: true, shift: true, name: 'delete' });
1059      cursorPos = rli.getCursorPos();
1060      assert.strictEqual(cursorPos.rows, 0);
1061      assert.strictEqual(cursorPos.cols, 0);
1062      rli.on('line', common.mustCall((line) => {
1063        assert.strictEqual(line, '');
1064      }));
1065      fi.emit('data', '\n');
1066      rli.close();
1067    }
1068
1069    // Multi-line input cursor position
1070    {
1071      const fi = new FakeInput();
1072      const rli = new readline.Interface({
1073        input: fi,
1074        output: fi,
1075        prompt: '',
1076        terminal: terminal
1077      });
1078      fi.columns = 10;
1079      fi.emit('data', 'multi-line text');
1080      const cursorPos = rli.getCursorPos();
1081      assert.strictEqual(cursorPos.rows, 1);
1082      assert.strictEqual(cursorPos.cols, 5);
1083      rli.close();
1084    }
1085
1086    // Multi-line prompt cursor position
1087    {
1088      const fi = new FakeInput();
1089      const rli = new readline.Interface({
1090        input: fi,
1091        output: fi,
1092        prompt: '\nfilledline\nwraping text\n> ',
1093        terminal: terminal
1094      });
1095      fi.columns = 10;
1096      fi.emit('data', 't');
1097      const cursorPos = rli.getCursorPos();
1098      assert.strictEqual(cursorPos.rows, 4);
1099      assert.strictEqual(cursorPos.cols, 3);
1100      rli.close();
1101    }
1102
1103    // Clear the whole screen
1104    {
1105      const fi = new FakeInput();
1106      const rli = new readline.Interface({
1107        input: fi,
1108        output: fi,
1109        prompt: '',
1110        terminal: terminal
1111      });
1112      const lines = ['line 1', 'line 2', 'line 3'];
1113      fi.emit('data', lines.join('\n'));
1114      fi.emit('keypress', '.', { ctrl: true, name: 'l' });
1115      const cursorPos = rli.getCursorPos();
1116      assert.strictEqual(cursorPos.rows, 0);
1117      assert.strictEqual(cursorPos.cols, 6);
1118      rli.on('line', common.mustCall((line) => {
1119        assert.strictEqual(line, 'line 3');
1120      }));
1121      fi.emit('data', '\n');
1122      rli.close();
1123    }
1124  }
1125
1126  // Wide characters should be treated as two columns.
1127  assert.strictEqual(getStringWidth('a'), 1);
1128  assert.strictEqual(getStringWidth('あ'), 2);
1129  assert.strictEqual(getStringWidth('谢'), 2);
1130  assert.strictEqual(getStringWidth('고'), 2);
1131  assert.strictEqual(getStringWidth(String.fromCodePoint(0x1f251)), 2);
1132  assert.strictEqual(getStringWidth('abcde'), 5);
1133  assert.strictEqual(getStringWidth('古池や'), 6);
1134  assert.strictEqual(getStringWidth('ノード.js'), 9);
1135  assert.strictEqual(getStringWidth('你好'), 4);
1136  assert.strictEqual(getStringWidth('안녕하세요'), 10);
1137  assert.strictEqual(getStringWidth('A\ud83c\ude00BC'), 5);
1138  assert.strictEqual(getStringWidth('��‍��‍��‍��'), 8);
1139  assert.strictEqual(getStringWidth('����あ����'), 9);
1140  // TODO(BridgeAR): This should have a width of 4.
1141  assert.strictEqual(getStringWidth('⓬⓪'), 2);
1142  assert.strictEqual(getStringWidth('\u0301\u200D\u200E'), 0);
1143
1144  // Check if vt control chars are stripped
1145  assert.strictEqual(
1146    stripVTControlCharacters('\u001b[31m> \u001b[39m'),
1147    '> '
1148  );
1149  assert.strictEqual(
1150    stripVTControlCharacters('\u001b[31m> \u001b[39m> '),
1151    '> > '
1152  );
1153  assert.strictEqual(
1154    stripVTControlCharacters('\u001b[31m\u001b[39m'),
1155    ''
1156  );
1157  assert.strictEqual(
1158    stripVTControlCharacters('> '),
1159    '> '
1160  );
1161  assert.strictEqual(getStringWidth('\u001b[31m> \u001b[39m'), 2);
1162  assert.strictEqual(getStringWidth('\u001b[31m> \u001b[39m> '), 4);
1163  assert.strictEqual(getStringWidth('\u001b[31m\u001b[39m'), 0);
1164  assert.strictEqual(getStringWidth('> '), 2);
1165
1166  {
1167    const fi = new FakeInput();
1168    assert.deepStrictEqual(fi.listeners(terminal ? 'keypress' : 'data'), []);
1169  }
1170
1171  // check EventEmitter memory leak
1172  for (let i = 0; i < 12; i++) {
1173    const rl = readline.createInterface({
1174      input: process.stdin,
1175      output: process.stdout
1176    });
1177    rl.close();
1178    assert.strictEqual(isWarned(process.stdin._events), false);
1179    assert.strictEqual(isWarned(process.stdout._events), false);
1180  }
1181
1182  // Can create a new readline Interface with a null output argument
1183  {
1184    const fi = new FakeInput();
1185    const rli = new readline.Interface(
1186      { input: fi, output: null, terminal: terminal }
1187    );
1188
1189    let called = false;
1190    rli.on('line', function(line) {
1191      called = true;
1192      assert.strictEqual(line, 'asdf');
1193    });
1194    fi.emit('data', 'asdf\n');
1195    assert.ok(called);
1196
1197    rli.setPrompt('ddd> ');
1198    rli.prompt();
1199    rli.write('really shouldnt be seeing this');
1200    rli.question('What do you think of node.js? ', function(answer) {
1201      console.log('Thank you for your valuable feedback:', answer);
1202      rli.close();
1203    });
1204  }
1205
1206  {
1207    const expected = terminal ?
1208      ['\u001b[1G', '\u001b[0J', '$ ', '\u001b[3G'] :
1209      ['$ '];
1210
1211    let counter = 0;
1212    const output = new Writable({
1213      write: common.mustCall((chunk, enc, cb) => {
1214        assert.strictEqual(chunk.toString(), expected[counter++]);
1215        cb();
1216        rl.close();
1217      }, expected.length)
1218    });
1219
1220    const rl = readline.createInterface({
1221      input: new Readable({ read: common.mustCall() }),
1222      output: output,
1223      prompt: '$ ',
1224      terminal: terminal
1225    });
1226
1227    rl.prompt();
1228
1229    assert.strictEqual(rl._prompt, '$ ');
1230  }
1231});
1232
1233// For the purposes of the following tests, we do not care about the exact
1234// value of crlfDelay, only that the behaviour conforms to what's expected.
1235// Setting it to Infinity allows the test to succeed even under extreme
1236// CPU stress.
1237const crlfDelay = Infinity;
1238
1239[ true, false ].forEach(function(terminal) {
1240  // Sending multiple newlines at once that does not end with a new line
1241  // and a `end` event(last line is)
1242
1243  // \r\n should emit one line event, not two
1244  {
1245    const fi = new FakeInput();
1246    const rli = new readline.Interface(
1247      {
1248        input: fi,
1249        output: fi,
1250        terminal: terminal,
1251        crlfDelay
1252      }
1253    );
1254    const expectedLines = ['foo', 'bar', 'baz', 'bat'];
1255    let callCount = 0;
1256    rli.on('line', function(line) {
1257      assert.strictEqual(line, expectedLines[callCount]);
1258      callCount++;
1259    });
1260    fi.emit('data', expectedLines.join('\r\n'));
1261    assert.strictEqual(callCount, expectedLines.length - 1);
1262    rli.close();
1263  }
1264
1265  // \r\n should emit one line event when split across multiple writes.
1266  {
1267    const fi = new FakeInput();
1268    const rli = new readline.Interface({
1269      input: fi,
1270      output: fi,
1271      terminal: terminal,
1272      crlfDelay
1273    });
1274    const expectedLines = ['foo', 'bar', 'baz', 'bat'];
1275    let callCount = 0;
1276    rli.on('line', function(line) {
1277      assert.strictEqual(line, expectedLines[callCount]);
1278      callCount++;
1279    });
1280    expectedLines.forEach(function(line) {
1281      fi.emit('data', `${line}\r`);
1282      fi.emit('data', '\n');
1283    });
1284    assert.strictEqual(callCount, expectedLines.length);
1285    rli.close();
1286  }
1287
1288  // Emit one line event when the delay between \r and \n is
1289  // over the default crlfDelay but within the setting value.
1290  {
1291    const fi = new FakeInput();
1292    const delay = 125;
1293    const rli = new readline.Interface({
1294      input: fi,
1295      output: fi,
1296      terminal: terminal,
1297      crlfDelay
1298    });
1299    let callCount = 0;
1300    rli.on('line', () => callCount++);
1301    fi.emit('data', '\r');
1302    setTimeout(common.mustCall(() => {
1303      fi.emit('data', '\n');
1304      assert.strictEqual(callCount, 1);
1305      rli.close();
1306    }), delay);
1307  }
1308});
1309
1310// Ensure that the _wordLeft method works even for large input
1311{
1312  const input = new Readable({
1313    read() {
1314      this.push('\x1B[1;5D'); // CTRL + Left
1315      this.push(null);
1316    },
1317  });
1318  const output = new Writable({
1319    write: common.mustCall((data, encoding, cb) => {
1320      assert.strictEqual(rl.cursor, rl.line.length - 1);
1321      cb();
1322    }),
1323  });
1324  const rl = new readline.createInterface({
1325    input: input,
1326    output: output,
1327    terminal: true,
1328  });
1329  rl.line = `a${' '.repeat(1e6)}a`;
1330  rl.cursor = rl.line.length;
1331}
1332