Skip to content

Commit 1ce305f

Browse files
BridgeARtargos
authored andcommitted
repl: do not preview while pasting code
This makes sure no previews are triggered while pasting code. The very last character is allowed to trigger the preview. The output should be completely identical to the user. PR-URL: nodejs#31315 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Minwoo Jung <nodecorelab@gmail.com>
1 parent 9b64025 commit 1ce305f

8 files changed

+97
-140
lines changed

lib/internal/repl/utils.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -298,10 +298,9 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
298298
}, () => callback(new ERR_INSPECTOR_NOT_AVAILABLE()));
299299
}
300300

301-
// TODO(BridgeAR): Prevent previews while pasting code.
302301
const showPreview = () => {
303302
// Prevent duplicated previews after a refresh.
304-
if (inputPreview !== null) {
303+
if (inputPreview !== null || !repl.isCompletionEnabled) {
305304
return;
306305
}
307306

lib/readline.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ function Interface(input, output, completer, terminal) {
9797
}
9898

9999
this._sawReturnAt = 0;
100+
// TODO(BridgeAR): Document this property. The name is not ideal, so we might
101+
// want to expose an alias and document that instead.
100102
this.isCompletionEnabled = true;
101103
this._sawKeyPress = false;
102104
this._previousKey = null;
@@ -1044,8 +1046,7 @@ Interface.prototype._ttyWrite = function(s, key) {
10441046
this._tabComplete(lastKeypressWasTab);
10451047
break;
10461048
}
1047-
// falls through
1048-
1049+
// falls through
10491050
default:
10501051
if (typeof s === 'string' && s) {
10511052
const lines = s.split(/\r\n|\n|\r/);

lib/repl.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -293,11 +293,13 @@ function REPLServer(prompt,
293293
if (!paused) return;
294294
paused = false;
295295
let entry;
296+
const tmpCompletionEnabled = self.isCompletionEnabled;
296297
while (entry = pausedBuffer.shift()) {
297-
const [type, payload] = entry;
298+
const [type, payload, isCompletionEnabled] = entry;
298299
switch (type) {
299300
case 'key': {
300301
const [d, key] = payload;
302+
self.isCompletionEnabled = isCompletionEnabled;
301303
self._ttyWrite(d, key);
302304
break;
303305
}
@@ -309,6 +311,7 @@ function REPLServer(prompt,
309311
break;
310312
}
311313
}
314+
self.isCompletionEnabled = tmpCompletionEnabled;
312315
}
313316

314317
function defaultEval(code, context, file, cb) {
@@ -834,7 +837,7 @@ function REPLServer(prompt,
834837
self._ttyWrite = (d, key) => {
835838
key = key || {};
836839
if (paused && !(self.breakEvalOnSigint && key.ctrl && key.name === 'c')) {
837-
pausedBuffer.push(['key', [d, key]]);
840+
pausedBuffer.push(['key', [d, key], self.isCompletionEnabled]);
838841
return;
839842
}
840843
if (!self.editorMode || !self.terminal) {

test/parallel/test-repl-editor.js

+1-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ const ArrayStream = require('../common/arraystream');
99
// \u001b[0J - Clear screen
1010
// \u001b[0K - Clear to line end
1111
const terminalCode = '\u001b[1G\u001b[0J> \u001b[3G';
12-
const previewCode = (str, n) => ` // ${str}\x1B[${n}G\x1B[0K`;
1312
const terminalCodeRegex = new RegExp(terminalCode.replace(/\[/g, '\\['), 'g');
1413

1514
function run({ input, output, event, checkTerminalCodes = true }) {
@@ -18,9 +17,7 @@ function run({ input, output, event, checkTerminalCodes = true }) {
1817

1918
stream.write = (msg) => found += msg.replace('\r', '');
2019

21-
let expected = `${terminalCode}.ed${previewCode('itor', 6)}i` +
22-
`${previewCode('tor', 7)}t${previewCode('or', 8)}o` +
23-
`${previewCode('r', 9)}r\n` +
20+
let expected = `${terminalCode}.editor\n` +
2421
'// Entering editor mode (^D to finish, ^C to cancel)\n' +
2522
`${input}${output}\n${terminalCode}`;
2623

test/parallel/test-repl-history-navigation.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,8 @@ const tests = [
321321
showEscapeCodes: true,
322322
skip: !process.features.inspector,
323323
test: [
324-
'fun',
324+
'fu',
325+
'n',
325326
RIGHT,
326327
BACKSPACE,
327328
LEFT,
@@ -419,8 +420,8 @@ const tests = [
419420
'[', ' ', ']',
420421
'\n// []', '\n// []', '\n// []',
421422
'> util.inspect.replDefaults.showHidden',
422-
'\n// false', ' ', '\n// false',
423-
'=', ' ', 't', 'r', 'u', ' // e', 'e',
423+
'\n// false',
424+
' ', '=', ' ', 't', 'r', 'u', 'e',
424425
'true\n',
425426
'> ', '[', ' ', ']',
426427
'\n// [ [length]: 0 ]',

test/parallel/test-repl-multiline.js

+4-18
Original file line numberDiff line numberDiff line change
@@ -23,28 +23,14 @@ function run({ useColors }) {
2323
r.on('exit', common.mustCall(() => {
2424
const actual = output.split('\n');
2525

26-
const firstLine = useColors ?
27-
'\x1B[1G\x1B[0J \x1B[1Gco\x1B[90mn\x1B[39m\x1B[3G\x1B[0Knst ' +
28-
'fo\x1B[90mr\x1B[39m\x1B[9G\x1B[0Ko = {' :
29-
'\x1B[1G\x1B[0J \x1B[1Gco // n\x1B[3G\x1B[0Knst ' +
30-
'fo // r\x1B[9G\x1B[0Ko = {';
31-
3226
// Validate the output, which contains terminal escape codes.
33-
assert.strictEqual(actual.length, 6 + process.features.inspector);
34-
assert.strictEqual(actual[0], firstLine);
27+
assert.strictEqual(actual.length, 6);
28+
assert.ok(actual[0].endsWith(input[0]));
3529
assert.ok(actual[1].includes('... '));
3630
assert.ok(actual[1].endsWith(input[1]));
3731
assert.ok(actual[2].includes('undefined'));
38-
if (process.features.inspector) {
39-
assert.ok(
40-
actual[3].endsWith(input[2]),
41-
`"${actual[3]}" should end with "${input[2]}"`
42-
);
43-
assert.ok(actual[4].includes(actual[5]));
44-
assert.strictEqual(actual[4].includes('//'), !useColors);
45-
}
46-
assert.strictEqual(actual[4 + process.features.inspector], '{}');
47-
// Ignore the last line, which is nothing but escape codes.
32+
assert.ok(actual[3].endsWith(input[2]));
33+
assert.strictEqual(actual[4], '{}');
4834
}));
4935

5036
inputStream.run(input);

test/parallel/test-repl-preview.js

+17-6
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
11
'use strict';
22

33
const common = require('../common');
4-
const ArrayStream = require('../common/arraystream');
54
const assert = require('assert');
65
const { REPLServer } = require('repl');
6+
const { Stream } = require('stream');
77

88
common.skipIfInspectorDisabled();
99

1010
const PROMPT = 'repl > ';
1111

12-
class REPLStream extends ArrayStream {
12+
class REPLStream extends Stream {
13+
readable = true;
14+
writable = true;
15+
1316
constructor() {
1417
super();
1518
this.lines = [''];
1619
}
20+
run(data) {
21+
for (const entry of data) {
22+
this.emit('data', entry);
23+
}
24+
this.emit('data', '\n');
25+
}
1726
write(chunk) {
1827
const chunkLines = chunk.toString('utf8').split('\n');
1928
this.lines[this.lines.length - 1] += chunkLines[0];
@@ -41,12 +50,14 @@ class REPLStream extends ArrayStream {
4150
this.on('line', onLine);
4251
});
4352
}
53+
pause() {}
54+
resume() {}
4455
}
4556

4657
function runAndWait(cmds, repl) {
4758
const promise = repl.inputStream.wait();
4859
for (const cmd of cmds) {
49-
repl.inputStream.run([cmd]);
60+
repl.inputStream.run(cmd);
5061
}
5162
return promise;
5263
}
@@ -93,9 +104,9 @@ async function tests(options) {
93104
'\x1B[33mtrue\x1B[39m',
94105
'\x1B[1G\x1B[0Jrepl > \x1B[8G'],
95106
[' \t { a: true};', [2, 5], '\x1B[33mtrue\x1B[39m',
96-
' \t { a: tru\x1B[90me\x1B[39m\x1B[26G\x1B[0Ke}',
97-
'\x1B[90m{ a: true }\x1B[39m\x1B[28G\x1B[1A\x1B[1B\x1B[2K\x1B[1A;',
98-
'\x1B[90mtrue\x1B[39m\x1B[29G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r',
107+
' { a: tru\x1B[90me\x1B[39m\x1B[18G\x1B[0Ke}',
108+
'\x1B[90m{ a: true }\x1B[39m\x1B[20G\x1B[1A\x1B[1B\x1B[2K\x1B[1A;',
109+
'\x1B[90mtrue\x1B[39m\x1B[21G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r',
99110
'\x1B[33mtrue\x1B[39m',
100111
'\x1B[1G\x1B[0Jrepl > \x1B[8G']
101112
];

test/parallel/test-repl-top-level-await.js

+62-103
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class REPLStream extends ArrayStream {
3232
return true;
3333
}
3434

35-
wait(lookFor = PROMPT) {
35+
wait() {
3636
if (this.waitingForResponse) {
3737
throw new Error('Currently waiting for response to another command');
3838
}
@@ -43,7 +43,7 @@ class REPLStream extends ArrayStream {
4343
reject(err);
4444
};
4545
const onLine = () => {
46-
if (this.lines[this.lines.length - 1].includes(lookFor)) {
46+
if (this.lines[this.lines.length - 1].includes(PROMPT)) {
4747
this.removeListener('error', onError);
4848
this.removeListener('line', onLine);
4949
resolve(this.lines);
@@ -64,8 +64,8 @@ const testMe = repl.start({
6464
breakEvalOnSigint: true
6565
});
6666

67-
function runAndWait(cmds, lookFor) {
68-
const promise = putIn.wait(lookFor);
67+
function runAndWait(cmds) {
68+
const promise = putIn.wait();
6969
for (const cmd of cmds) {
7070
if (typeof cmd === 'string') {
7171
putIn.run([cmd]);
@@ -84,108 +84,67 @@ async function ordinaryTests() {
8484
'function koo() { return Promise.resolve(4); }'
8585
]);
8686
const testCases = [
87-
[ 'await Promise.resolve(0)',
88-
// Auto completion preview with colors stripped.
89-
['awaitaititt Proroomiseisesee.resolveolvelvevee(0)\r', '0']
90-
],
91-
[ '{ a: await Promise.resolve(1) }',
92-
// Auto completion preview with colors stripped.
93-
['{ a: awaitaititt Proroomiseisesee.resolveolvelvevee(1) }\r',
94-
'{ a: 1 }']
95-
],
96-
[ '_', '{ a: 1 }\r', { line: 0 } ],
97-
[ 'let { aa, bb } = await Promise.resolve({ aa: 1, bb: 2 }), f = 5;',
98-
[
99-
'letett { aa, bb } = awaitaititt Proroomiseisesee.resolveolvelvevee' +
100-
'({ aa: 1, bb: 2 }), f = 5;\r'
101-
]
102-
],
103-
[ 'aa', ['1\r', '1'] ],
104-
[ 'bb', ['2\r', '2'] ],
105-
[ 'f', ['5\r', '5'] ],
106-
[ 'let cc = await Promise.resolve(2)',
107-
['letett cc = awaitaititt Proroomiseisesee.resolveolvelvevee(2)\r']
108-
],
109-
[ 'cc', ['2\r', '2'] ],
110-
[ 'let dd;', ['letett dd;\r'] ],
111-
[ 'dd', ['undefined\r'] ],
112-
[ 'let [ii, { abc: { kk } }] = [0, { abc: { kk: 1 } }];',
113-
['letett [ii, { abc: { kook } }] = [0, { abc: { kook: 1 } }];\r'] ],
114-
[ 'ii', ['0\r', '0'] ],
115-
[ 'kk', ['1\r', '1'] ],
116-
[ 'var ll = await Promise.resolve(2);',
117-
['var letl = awaitaititt Proroomiseisesee.resolveolvelvevee(2);\r']
118-
],
119-
[ 'll', ['2\r', '2'] ],
120-
[ 'foo(await koo())',
121-
['f', '5oo', '[Function: foo](awaitaititt kooo())\r', '4'] ],
122-
[ '_', ['4\r', '4'] ],
123-
[ 'const m = foo(await koo());',
124-
['connst module = foo(awaitaititt kooo());\r'] ],
125-
[ 'm', ['4\r', '4' ] ],
126-
[ 'const n = foo(await\nkoo());',
127-
['connst n = foo(awaitaititt\r', '... kooo());\r', 'undefined'] ],
128-
[ 'n', ['4\r', '4'] ],
87+
['await Promise.resolve(0)', '0'],
88+
['{ a: await Promise.resolve(1) }', '{ a: 1 }'],
89+
['_', '{ a: 1 }'],
90+
['let { aa, bb } = await Promise.resolve({ aa: 1, bb: 2 }), f = 5;'],
91+
['aa', '1'],
92+
['bb', '2'],
93+
['f', '5'],
94+
['let cc = await Promise.resolve(2)'],
95+
['cc', '2'],
96+
['let dd;'],
97+
['dd'],
98+
['let [ii, { abc: { kk } }] = [0, { abc: { kk: 1 } }];'],
99+
['ii', '0'],
100+
['kk', '1'],
101+
['var ll = await Promise.resolve(2);'],
102+
['ll', '2'],
103+
['foo(await koo())', '4'],
104+
['_', '4'],
105+
['const m = foo(await koo());'],
106+
['m', '4'],
107+
['const n = foo(await\nkoo());',
108+
['const n = foo(await\r', '... koo());\r', 'undefined']],
109+
['n', '4'],
129110
// eslint-disable-next-line no-template-curly-in-string
130-
[ '`status: ${(await Promise.resolve({ status: 200 })).status}`',
131-
[
132-
'`stratus: ${(awaitaititt Proroomiseisesee.resolveolvelvevee' +
133-
'({ stratus: 200 })).stratus}`\r',
134-
"'status: 200'"
135-
]
136-
],
137-
[ 'for (let i = 0; i < 2; ++i) await i',
138-
['f', '5or (lett i = 0; i < 2; ++i) awaitaititt i\r', 'undefined'] ],
139-
[ 'for (let i = 0; i < 2; ++i) { await i }',
140-
['f', '5or (lett i = 0; i < 2; ++i) { awaitaititt i }\r', 'undefined']
141-
],
142-
[ 'await 0', ['awaitaititt 0\r', '0'] ],
143-
[ 'await 0; function foo() {}',
144-
['awaitaititt 0; functionnctionctiontioniononn foo() {}\r']
145-
],
146-
[ 'foo',
147-
['f', '5oo', '[Function: foo]\r', '[Function: foo]'] ],
148-
[ 'class Foo {}; await 1;', ['class Foo {}; awaitaititt 1;\r', '1'] ],
149-
[ 'Foo', ['Fooo', '[Function: Foo]\r', '[Function: Foo]'] ],
150-
[ 'if (await true) { function bar() {}; }',
151-
['if (awaitaititt truee) { functionnctionctiontioniononn bar() {}; }\r']
152-
],
153-
[ 'bar', ['barr', '[Function: bar]\r', '[Function: bar]'] ],
154-
[ 'if (await true) { class Bar {}; }',
155-
['if (awaitaititt truee) { class Bar {}; }\r']
156-
],
157-
[ 'Bar', 'Uncaught ReferenceError: Bar is not defined' ],
158-
[ 'await 0; function* gen(){}',
159-
['awaitaititt 0; functionnctionctiontioniononn* globalen(){}\r']
160-
],
161-
[ 'for (var i = 0; i < 10; ++i) { await i; }',
162-
['f', '5or (var i = 0; i < 10; ++i) { awaitaititt i; }\r', 'undefined'] ],
163-
[ 'i', ['10\r', '10'] ],
164-
[ 'for (let j = 0; j < 5; ++j) { await j; }',
165-
['f', '5or (lett j = 0; j < 5; ++j) { awaitaititt j; }\r', 'undefined'] ],
166-
[ 'j', 'Uncaught ReferenceError: j is not defined', { line: 0 } ],
167-
[ 'gen',
168-
['genn', '[GeneratorFunction: gen]\r', '[GeneratorFunction: gen]']
169-
],
170-
[ 'return 42; await 5;', 'Uncaught SyntaxError: Illegal return statement',
171-
{ line: 3 } ],
172-
[ 'let o = await 1, p', ['lett os = awaitaititt 1, p\r'] ],
173-
[ 'p', ['undefined\r'] ],
174-
[ 'let q = 1, s = await 2', ['lett que = 1, s = awaitaititt 2\r'] ],
175-
[ 's', ['2\r', '2'] ],
176-
[ 'for await (let i of [1,2,3]) console.log(i)',
177-
[
178-
'f',
179-
'5or awaitaititt (lett i of [1,2,3]) connsolelee.logogg(i)\r',
180-
'1',
181-
'2',
182-
'3',
183-
'undefined'
184-
]
111+
['`status: ${(await Promise.resolve({ status: 200 })).status}`',
112+
"'status: 200'"],
113+
['for (let i = 0; i < 2; ++i) await i'],
114+
['for (let i = 0; i < 2; ++i) { await i }'],
115+
['await 0', '0'],
116+
['await 0; function foo() {}'],
117+
['foo', '[Function: foo]'],
118+
['class Foo {}; await 1;', '1'],
119+
['Foo', '[Function: Foo]'],
120+
['if (await true) { function bar() {}; }'],
121+
['bar', '[Function: bar]'],
122+
['if (await true) { class Bar {}; }'],
123+
['Bar', 'Uncaught ReferenceError: Bar is not defined'],
124+
['await 0; function* gen(){}'],
125+
['for (var i = 0; i < 10; ++i) { await i; }'],
126+
['i', '10'],
127+
['for (let j = 0; j < 5; ++j) { await j; }'],
128+
['j', 'Uncaught ReferenceError: j is not defined', { line: 0 }],
129+
['gen', '[GeneratorFunction: gen]'],
130+
['return 42; await 5;', 'Uncaught SyntaxError: Illegal return statement',
131+
{ line: 3 }],
132+
['let o = await 1, p'],
133+
['p'],
134+
['let q = 1, s = await 2'],
135+
['s', '2'],
136+
['for await (let i of [1,2,3]) console.log(i)',
137+
[
138+
'for await (let i of [1,2,3]) console.log(i)\r',
139+
'1',
140+
'2',
141+
'3',
142+
'undefined'
143+
]
185144
]
186145
];
187146

188-
for (const [input, expected, options = {}] of testCases) {
147+
for (const [input, expected = [`${input}\r`], options = {}] of testCases) {
189148
console.log(`Testing ${input}`);
190149
const toBeRun = input.split('\n');
191150
const lines = await runAndWait(toBeRun);
@@ -216,7 +175,7 @@ async function ctrlCTest() {
216175
'await timeout(100000)',
217176
{ ctrl: true, name: 'c' }
218177
]), [
219-
'awaitaititt timeoutmeouteoutoututt(100000)\r',
178+
'await timeout(100000)\r',
220179
'Uncaught:',
221180
'[Error [ERR_SCRIPT_EXECUTION_INTERRUPTED]: ' +
222181
'Script execution was interrupted by `SIGINT`] {',

0 commit comments

Comments
 (0)