Skip to content

Commit 85926d4

Browse files
BridgeARMylesBorins
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: #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 985f980 commit 85926d4

File tree

8 files changed

+97
-140
lines changed

8 files changed

+97
-140
lines changed

lib/internal/repl/utils.js

Lines changed: 1 addition & 2 deletions
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

Lines changed: 3 additions & 2 deletions
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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,11 +292,13 @@ function REPLServer(prompt,
292292
if (!paused) return;
293293
paused = false;
294294
let entry;
295+
const tmpCompletionEnabled = self.isCompletionEnabled;
295296
while (entry = pausedBuffer.shift()) {
296-
const [type, payload] = entry;
297+
const [type, payload, isCompletionEnabled] = entry;
297298
switch (type) {
298299
case 'key': {
299300
const [d, key] = payload;
301+
self.isCompletionEnabled = isCompletionEnabled;
300302
self._ttyWrite(d, key);
301303
break;
302304
}
@@ -308,6 +310,7 @@ function REPLServer(prompt,
308310
break;
309311
}
310312
}
313+
self.isCompletionEnabled = tmpCompletionEnabled;
311314
}
312315

313316
function defaultEval(code, context, file, cb) {
@@ -833,7 +836,7 @@ function REPLServer(prompt,
833836
self._ttyWrite = (d, key) => {
834837
key = key || {};
835838
if (paused && !(self.breakEvalOnSigint && key.ctrl && key.name === 'c')) {
836-
pausedBuffer.push(['key', [d, key]]);
839+
pausedBuffer.push(['key', [d, key], self.isCompletionEnabled]);
837840
return;
838841
}
839842
if (!self.editorMode || !self.terminal) {

test/parallel/test-repl-editor.js

Lines changed: 1 addition & 4 deletions
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

Lines changed: 4 additions & 3 deletions
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

Lines changed: 4 additions & 18 deletions
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

Lines changed: 17 additions & 6 deletions
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

Lines changed: 62 additions & 103 deletions
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)