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