diff --git a/lib/internal/repl/utils.js b/lib/internal/repl/utils.js index 6082fe7908ba73..3449eab785fe2c 100644 --- a/lib/internal/repl/utils.js +++ b/lib/internal/repl/utils.js @@ -298,10 +298,9 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { }, () => callback(new ERR_INSPECTOR_NOT_AVAILABLE())); } - // TODO(BridgeAR): Prevent previews while pasting code. const showPreview = () => { // Prevent duplicated previews after a refresh. - if (inputPreview !== null) { + if (inputPreview !== null || !repl.isCompletionEnabled) { return; } diff --git a/lib/readline.js b/lib/readline.js index 281a8b63fa8d7d..a87cd8f183c973 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -97,6 +97,8 @@ function Interface(input, output, completer, terminal) { } this._sawReturnAt = 0; + // TODO(BridgeAR): Document this property. The name is not ideal, so we might + // want to expose an alias and document that instead. this.isCompletionEnabled = true; this._sawKeyPress = false; this._previousKey = null; @@ -1044,8 +1046,7 @@ Interface.prototype._ttyWrite = function(s, key) { this._tabComplete(lastKeypressWasTab); break; } - // falls through - + // falls through default: if (typeof s === 'string' && s) { const lines = s.split(/\r\n|\n|\r/); diff --git a/lib/repl.js b/lib/repl.js index 89ed77be4c902c..11d88917490d19 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -293,11 +293,13 @@ function REPLServer(prompt, if (!paused) return; paused = false; let entry; + const tmpCompletionEnabled = self.isCompletionEnabled; while (entry = pausedBuffer.shift()) { - const [type, payload] = entry; + const [type, payload, isCompletionEnabled] = entry; switch (type) { case 'key': { const [d, key] = payload; + self.isCompletionEnabled = isCompletionEnabled; self._ttyWrite(d, key); break; } @@ -309,6 +311,7 @@ function REPLServer(prompt, break; } } + self.isCompletionEnabled = tmpCompletionEnabled; } function defaultEval(code, context, file, cb) { @@ -838,7 +841,7 @@ function REPLServer(prompt, self._ttyWrite = (d, key) => { key = key || {}; if (paused && !(self.breakEvalOnSigint && key.ctrl && key.name === 'c')) { - pausedBuffer.push(['key', [d, key]]); + pausedBuffer.push(['key', [d, key], self.isCompletionEnabled]); return; } if (!self.editorMode || !self.terminal) { diff --git a/test/parallel/test-repl-editor.js b/test/parallel/test-repl-editor.js index b340bd66313971..6871fbdcb1a882 100644 --- a/test/parallel/test-repl-editor.js +++ b/test/parallel/test-repl-editor.js @@ -9,7 +9,6 @@ const ArrayStream = require('../common/arraystream'); // \u001b[0J - Clear screen // \u001b[0K - Clear to line end const terminalCode = '\u001b[1G\u001b[0J> \u001b[3G'; -const previewCode = (str, n) => ` // ${str}\x1B[${n}G\x1B[0K`; const terminalCodeRegex = new RegExp(terminalCode.replace(/\[/g, '\\['), 'g'); function run({ input, output, event, checkTerminalCodes = true }) { @@ -18,9 +17,7 @@ function run({ input, output, event, checkTerminalCodes = true }) { stream.write = (msg) => found += msg.replace('\r', ''); - let expected = `${terminalCode}.ed${previewCode('itor', 6)}i` + - `${previewCode('tor', 7)}t${previewCode('or', 8)}o` + - `${previewCode('r', 9)}r\n` + + let expected = `${terminalCode}.editor\n` + '// Entering editor mode (^D to finish, ^C to cancel)\n' + `${input}${output}\n${terminalCode}`; diff --git a/test/parallel/test-repl-history-navigation.js b/test/parallel/test-repl-history-navigation.js index 69ed7e062c9357..8a9f1a6dadf386 100644 --- a/test/parallel/test-repl-history-navigation.js +++ b/test/parallel/test-repl-history-navigation.js @@ -321,7 +321,8 @@ const tests = [ showEscapeCodes: true, skip: !process.features.inspector, test: [ - 'fun', + 'fu', + 'n', RIGHT, BACKSPACE, LEFT, @@ -419,8 +420,8 @@ const tests = [ '[', ' ', ']', '\n// []', '\n// []', '\n// []', '> util.inspect.replDefaults.showHidden', - '\n// false', ' ', '\n// false', - '=', ' ', 't', 'r', 'u', ' // e', 'e', + '\n// false', + ' ', '=', ' ', 't', 'r', 'u', 'e', 'true\n', '> ', '[', ' ', ']', '\n// [ [length]: 0 ]', diff --git a/test/parallel/test-repl-multiline.js b/test/parallel/test-repl-multiline.js index f99b91c84b0a85..e458555cd87f76 100644 --- a/test/parallel/test-repl-multiline.js +++ b/test/parallel/test-repl-multiline.js @@ -23,28 +23,14 @@ function run({ useColors }) { r.on('exit', common.mustCall(() => { const actual = output.split('\n'); - const firstLine = useColors ? - '\x1B[1G\x1B[0J \x1B[1Gco\x1B[90mn\x1B[39m\x1B[3G\x1B[0Knst ' + - 'fo\x1B[90mr\x1B[39m\x1B[9G\x1B[0Ko = {' : - '\x1B[1G\x1B[0J \x1B[1Gco // n\x1B[3G\x1B[0Knst ' + - 'fo // r\x1B[9G\x1B[0Ko = {'; - // Validate the output, which contains terminal escape codes. - assert.strictEqual(actual.length, 6 + process.features.inspector); - assert.strictEqual(actual[0], firstLine); + assert.strictEqual(actual.length, 6); + assert.ok(actual[0].endsWith(input[0])); assert.ok(actual[1].includes('... ')); assert.ok(actual[1].endsWith(input[1])); assert.ok(actual[2].includes('undefined')); - if (process.features.inspector) { - assert.ok( - actual[3].endsWith(input[2]), - `"${actual[3]}" should end with "${input[2]}"` - ); - assert.ok(actual[4].includes(actual[5])); - assert.strictEqual(actual[4].includes('//'), !useColors); - } - assert.strictEqual(actual[4 + process.features.inspector], '{}'); - // Ignore the last line, which is nothing but escape codes. + assert.ok(actual[3].endsWith(input[2])); + assert.strictEqual(actual[4], '{}'); })); inputStream.run(input); diff --git a/test/parallel/test-repl-preview.js b/test/parallel/test-repl-preview.js index 179f4f78e2e473..82f1a9e507e750 100644 --- a/test/parallel/test-repl-preview.js +++ b/test/parallel/test-repl-preview.js @@ -1,19 +1,28 @@ 'use strict'; const common = require('../common'); -const ArrayStream = require('../common/arraystream'); const assert = require('assert'); const { REPLServer } = require('repl'); +const { Stream } = require('stream'); common.skipIfInspectorDisabled(); const PROMPT = 'repl > '; -class REPLStream extends ArrayStream { +class REPLStream extends Stream { + readable = true; + writable = true; + constructor() { super(); this.lines = ['']; } + run(data) { + for (const entry of data) { + this.emit('data', entry); + } + this.emit('data', '\n'); + } write(chunk) { const chunkLines = chunk.toString('utf8').split('\n'); this.lines[this.lines.length - 1] += chunkLines[0]; @@ -41,12 +50,14 @@ class REPLStream extends ArrayStream { this.on('line', onLine); }); } + pause() {} + resume() {} } function runAndWait(cmds, repl) { const promise = repl.inputStream.wait(); for (const cmd of cmds) { - repl.inputStream.run([cmd]); + repl.inputStream.run(cmd); } return promise; } @@ -93,9 +104,9 @@ async function tests(options) { '\x1B[33mtrue\x1B[39m', '\x1B[1G\x1B[0Jrepl > \x1B[8G'], [' \t { a: true};', [2, 5], '\x1B[33mtrue\x1B[39m', - ' \t { a: tru\x1B[90me\x1B[39m\x1B[26G\x1B[0Ke}', - '\x1B[90m{ a: true }\x1B[39m\x1B[28G\x1B[1A\x1B[1B\x1B[2K\x1B[1A;', - '\x1B[90mtrue\x1B[39m\x1B[29G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', + ' { a: tru\x1B[90me\x1B[39m\x1B[18G\x1B[0Ke}', + '\x1B[90m{ a: true }\x1B[39m\x1B[20G\x1B[1A\x1B[1B\x1B[2K\x1B[1A;', + '\x1B[90mtrue\x1B[39m\x1B[21G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', '\x1B[33mtrue\x1B[39m', '\x1B[1G\x1B[0Jrepl > \x1B[8G'] ]; diff --git a/test/parallel/test-repl-top-level-await.js b/test/parallel/test-repl-top-level-await.js index 47fcb8530dee77..59f0cb1617ad4a 100644 --- a/test/parallel/test-repl-top-level-await.js +++ b/test/parallel/test-repl-top-level-await.js @@ -32,7 +32,7 @@ class REPLStream extends ArrayStream { return true; } - wait(lookFor = PROMPT) { + wait() { if (this.waitingForResponse) { throw new Error('Currently waiting for response to another command'); } @@ -43,7 +43,7 @@ class REPLStream extends ArrayStream { reject(err); }; const onLine = () => { - if (this.lines[this.lines.length - 1].includes(lookFor)) { + if (this.lines[this.lines.length - 1].includes(PROMPT)) { this.removeListener('error', onError); this.removeListener('line', onLine); resolve(this.lines); @@ -64,8 +64,8 @@ const testMe = repl.start({ breakEvalOnSigint: true }); -function runAndWait(cmds, lookFor) { - const promise = putIn.wait(lookFor); +function runAndWait(cmds) { + const promise = putIn.wait(); for (const cmd of cmds) { if (typeof cmd === 'string') { putIn.run([cmd]); @@ -84,108 +84,67 @@ async function ordinaryTests() { 'function koo() { return Promise.resolve(4); }' ]); const testCases = [ - [ 'await Promise.resolve(0)', - // Auto completion preview with colors stripped. - ['awaitaititt Proroomiseisesee.resolveolvelvevee(0)\r', '0'] - ], - [ '{ a: await Promise.resolve(1) }', - // Auto completion preview with colors stripped. - ['{ a: awaitaititt Proroomiseisesee.resolveolvelvevee(1) }\r', - '{ a: 1 }'] - ], - [ '_', '{ a: 1 }\r', { line: 0 } ], - [ 'let { aa, bb } = await Promise.resolve({ aa: 1, bb: 2 }), f = 5;', - [ - 'letett { aa, bb } = awaitaititt Proroomiseisesee.resolveolvelvevee' + - '({ aa: 1, bb: 2 }), f = 5;\r' - ] - ], - [ 'aa', ['1\r', '1'] ], - [ 'bb', ['2\r', '2'] ], - [ 'f', ['5\r', '5'] ], - [ 'let cc = await Promise.resolve(2)', - ['letett cc = awaitaititt Proroomiseisesee.resolveolvelvevee(2)\r'] - ], - [ 'cc', ['2\r', '2'] ], - [ 'let dd;', ['letett dd;\r'] ], - [ 'dd', ['undefined\r'] ], - [ 'let [ii, { abc: { kk } }] = [0, { abc: { kk: 1 } }];', - ['letett [ii, { abc: { kook } }] = [0, { abc: { kook: 1 } }];\r'] ], - [ 'ii', ['0\r', '0'] ], - [ 'kk', ['1\r', '1'] ], - [ 'var ll = await Promise.resolve(2);', - ['var letl = awaitaititt Proroomiseisesee.resolveolvelvevee(2);\r'] - ], - [ 'll', ['2\r', '2'] ], - [ 'foo(await koo())', - ['f', '5oo', '[Function: foo](awaitaititt kooo())\r', '4'] ], - [ '_', ['4\r', '4'] ], - [ 'const m = foo(await koo());', - ['connst module = foo(awaitaititt kooo());\r'] ], - [ 'm', ['4\r', '4' ] ], - [ 'const n = foo(await\nkoo());', - ['connst n = foo(awaitaititt\r', '... kooo());\r', 'undefined'] ], - [ 'n', ['4\r', '4'] ], + ['await Promise.resolve(0)', '0'], + ['{ a: await Promise.resolve(1) }', '{ a: 1 }'], + ['_', '{ a: 1 }'], + ['let { aa, bb } = await Promise.resolve({ aa: 1, bb: 2 }), f = 5;'], + ['aa', '1'], + ['bb', '2'], + ['f', '5'], + ['let cc = await Promise.resolve(2)'], + ['cc', '2'], + ['let dd;'], + ['dd'], + ['let [ii, { abc: { kk } }] = [0, { abc: { kk: 1 } }];'], + ['ii', '0'], + ['kk', '1'], + ['var ll = await Promise.resolve(2);'], + ['ll', '2'], + ['foo(await koo())', '4'], + ['_', '4'], + ['const m = foo(await koo());'], + ['m', '4'], + ['const n = foo(await\nkoo());', + ['const n = foo(await\r', '... koo());\r', 'undefined']], + ['n', '4'], // eslint-disable-next-line no-template-curly-in-string - [ '`status: ${(await Promise.resolve({ status: 200 })).status}`', - [ - '`stratus: ${(awaitaititt Proroomiseisesee.resolveolvelvevee' + - '({ stratus: 200 })).stratus}`\r', - "'status: 200'" - ] - ], - [ 'for (let i = 0; i < 2; ++i) await i', - ['f', '5or (lett i = 0; i < 2; ++i) awaitaititt i\r', 'undefined'] ], - [ 'for (let i = 0; i < 2; ++i) { await i }', - ['f', '5or (lett i = 0; i < 2; ++i) { awaitaititt i }\r', 'undefined'] - ], - [ 'await 0', ['awaitaititt 0\r', '0'] ], - [ 'await 0; function foo() {}', - ['awaitaititt 0; functionnctionctiontioniononn foo() {}\r'] - ], - [ 'foo', - ['f', '5oo', '[Function: foo]\r', '[Function: foo]'] ], - [ 'class Foo {}; await 1;', ['class Foo {}; awaitaititt 1;\r', '1'] ], - [ 'Foo', ['Fooo', '[Function: Foo]\r', '[Function: Foo]'] ], - [ 'if (await true) { function bar() {}; }', - ['if (awaitaititt truee) { functionnctionctiontioniononn bar() {}; }\r'] - ], - [ 'bar', ['barr', '[Function: bar]\r', '[Function: bar]'] ], - [ 'if (await true) { class Bar {}; }', - ['if (awaitaititt truee) { class Bar {}; }\r'] - ], - [ 'Bar', 'Uncaught ReferenceError: Bar is not defined' ], - [ 'await 0; function* gen(){}', - ['awaitaititt 0; functionnctionctiontioniononn* globalen(){}\r'] - ], - [ 'for (var i = 0; i < 10; ++i) { await i; }', - ['f', '5or (var i = 0; i < 10; ++i) { awaitaititt i; }\r', 'undefined'] ], - [ 'i', ['10\r', '10'] ], - [ 'for (let j = 0; j < 5; ++j) { await j; }', - ['f', '5or (lett j = 0; j < 5; ++j) { awaitaititt j; }\r', 'undefined'] ], - [ 'j', 'Uncaught ReferenceError: j is not defined', { line: 0 } ], - [ 'gen', - ['genn', '[GeneratorFunction: gen]\r', '[GeneratorFunction: gen]'] - ], - [ 'return 42; await 5;', 'Uncaught SyntaxError: Illegal return statement', - { line: 3 } ], - [ 'let o = await 1, p', ['lett os = awaitaititt 1, p\r'] ], - [ 'p', ['undefined\r'] ], - [ 'let q = 1, s = await 2', ['lett que = 1, s = awaitaititt 2\r'] ], - [ 's', ['2\r', '2'] ], - [ 'for await (let i of [1,2,3]) console.log(i)', - [ - 'f', - '5or awaitaititt (lett i of [1,2,3]) connsolelee.logogg(i)\r', - '1', - '2', - '3', - 'undefined' - ] + ['`status: ${(await Promise.resolve({ status: 200 })).status}`', + "'status: 200'"], + ['for (let i = 0; i < 2; ++i) await i'], + ['for (let i = 0; i < 2; ++i) { await i }'], + ['await 0', '0'], + ['await 0; function foo() {}'], + ['foo', '[Function: foo]'], + ['class Foo {}; await 1;', '1'], + ['Foo', '[Function: Foo]'], + ['if (await true) { function bar() {}; }'], + ['bar', '[Function: bar]'], + ['if (await true) { class Bar {}; }'], + ['Bar', 'Uncaught ReferenceError: Bar is not defined'], + ['await 0; function* gen(){}'], + ['for (var i = 0; i < 10; ++i) { await i; }'], + ['i', '10'], + ['for (let j = 0; j < 5; ++j) { await j; }'], + ['j', 'Uncaught ReferenceError: j is not defined', { line: 0 }], + ['gen', '[GeneratorFunction: gen]'], + ['return 42; await 5;', 'Uncaught SyntaxError: Illegal return statement', + { line: 3 }], + ['let o = await 1, p'], + ['p'], + ['let q = 1, s = await 2'], + ['s', '2'], + ['for await (let i of [1,2,3]) console.log(i)', + [ + 'for await (let i of [1,2,3]) console.log(i)\r', + '1', + '2', + '3', + 'undefined' + ] ] ]; - for (const [input, expected, options = {}] of testCases) { + for (const [input, expected = [`${input}\r`], options = {}] of testCases) { console.log(`Testing ${input}`); const toBeRun = input.split('\n'); const lines = await runAndWait(toBeRun); @@ -216,7 +175,7 @@ async function ctrlCTest() { 'await timeout(100000)', { ctrl: true, name: 'c' } ]), [ - 'awaitaititt timeoutmeouteoutoututt(100000)\r', + 'await timeout(100000)\r', 'Uncaught:', '[Error [ERR_SCRIPT_EXECUTION_INTERRUPTED]: ' + 'Script execution was interrupted by `SIGINT`] {',