Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions lib/internal/readline/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const {
StringPrototypeCodePointAt,
StringPrototypeEndsWith,
StringPrototypeRepeat,
StringPrototypeReplaceAll,
StringPrototypeSlice,
StringPrototypeStartsWith,
StringPrototypeTrim,
Expand Down Expand Up @@ -950,7 +951,7 @@ class Interface extends InterfaceConstructor {
if (index === -1) {
this.line = search;
} else {
this.line = this.history[index];
this.line = StringPrototypeReplaceAll(this.history[index], '\r', '\n');
}
this.historyIndex = index;
this.cursor = this.line.length; // Set cursor to end of line.
Expand All @@ -973,7 +974,7 @@ class Interface extends InterfaceConstructor {
if (index === this.history.length) {
this.line = search;
} else {
this.line = this.history[index];
this.line = StringPrototypeReplaceAll(this.history[index], '\r', '\n');
}
this.historyIndex = index;
this.cursor = this.line.length; // Set cursor to end of line.
Expand Down
2 changes: 1 addition & 1 deletion lib/internal/repl/history.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ function setupHistory(repl, historyPath, ready) {
}

if (data) {
repl.history = RegExpPrototypeSymbolSplit(/[\n\r]+/, data, repl.historySize);
repl.history = RegExpPrototypeSymbolSplit(/[\n]+/, data, repl.historySize);
} else {
repl.history = [];
}
Expand Down
18 changes: 18 additions & 0 deletions lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ const {
StringPrototypeCodePointAt,
StringPrototypeEndsWith,
StringPrototypeIncludes,
StringPrototypeIndexOf,
StringPrototypeRepeat,
StringPrototypeReplaceAll,
StringPrototypeSlice,
StringPrototypeSplit,
StringPrototypeStartsWith,
Expand Down Expand Up @@ -952,6 +954,22 @@ function REPLServer(prompt,
self._domain.emit('error', e.err || e);
}

// In the next two if blocks, we do not use os.EOL instead of '\n'
// because on Windows it is '\r\n'
if (cmd && StringPrototypeIndexOf(cmd, '\n') !== -1) { // If you are editing a multiline command
self.history[0] = StringPrototypeReplaceAll(cmd, '\n', '\r');
} else if (self[kBufferedCommandSymbol]) { // If a new multiline command was entered
// Remove the first N lines from the self.history array
// where N is the number of lines in the buffered command

const lines = StringPrototypeSplit(self[kBufferedCommandSymbol], '\n');
self.history = ArrayPrototypeSlice(self.history, lines.length);
ArrayPrototypePop(lines);
// And replace them with the single command split by '\r'
ArrayPrototypePush(lines, cmd);
ArrayPrototypeUnshift(self.history, ArrayPrototypeJoin(lines, '\r'));
}

// Clear buffer if no SyntaxErrors
self.clearBufferedCommand();
sawCtrlD = false;
Expand Down
4 changes: 4 additions & 0 deletions test/fixtures/.node_repl_history-multiline
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
var d = [ { a: 1, b: 2, }, { a: 3, b: 4, c: [{ a: 1, b: 2 }, { a: 3, b: 4, } ] }]
const c = [ { a: 1, b: 2, }]
`const b = [ 1, 2, 3, 4,]`
a = `I am a multiline stringI can be as long as I want`
Expand Down
36 changes: 36 additions & 0 deletions test/parallel/test-repl-history-navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,42 @@ const tests = [
],
clean: false
},
{
// Test that the multiline history is correctly navigated and it can be edited
env: { NODE_REPL_HISTORY: defaultHistoryPath },
skip: !process.features.inspector,
test: [
'let a = ``',
ENTER,
'a = `I am a multiline strong',
ENTER,
'which ends here`',
ENTER,
UP,
// press LEFT 19 times to reach the typo
...Array(19).fill(LEFT),
BACKSPACE,
'i',
ENTER,
],
expected: [
prompt, ...'let a = ``',
'undefined\n',
prompt, ...'a = `I am a multiline strong',
'... ',
...'which ends here`',
"'I am a multiline strong\\nwhich ends here'\n",
prompt,
`${prompt}a = \`I am a multiline strong\nwhich ends here\``,
`${prompt}a = \`I am a multiline strong\nwhich ends here\``,
`${prompt}a = \`I am a multiline strng\nwhich ends here\``,
`${prompt}a = \`I am a multiline string\nwhich ends here\``,
`${prompt}a = \`I am a multiline string\nwhich ends here\``,
"'I am a multiline string\\nwhich ends here'\n",
prompt,
],
clean: true
},
];
const numtests = tests.length;

Expand Down
90 changes: 90 additions & 0 deletions test/parallel/test-repl-load-multiline-from-history.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
'use strict';

// Flags: --expose-internals

const common = require('../common');

const assert = require('assert');
const repl = require('internal/repl');
const stream = require('stream');
const fixtures = require('../common/fixtures');

class ActionStream extends stream.Stream {
run(data) {
const _iter = data[Symbol.iterator]();
const doAction = () => {
const next = _iter.next();
if (next.done) {
// Close the repl. Note that it must have a clean prompt to do so.
this.emit('keypress', '', { ctrl: true, name: 'd' });
return;
}
const action = next.value;

if (typeof action === 'object') {
this.emit('keypress', '', action);
}
setImmediate(doAction);
};
doAction();
}
resume() {}
pause() {}
}
ActionStream.prototype.readable = true;

{
// Testing multiline history loading
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
const replHistoryPath = fixtures.path('.node_repl_history-multiline');

const checkResults = common.mustSucceed((r) => {
assert.strictEqual(r.history.length, 4);

r.input.run([{ name: 'up' }]);
assert.strictEqual(
r.line,
'var d = [\n' +
' {\n' +
' a: 1,\n' +
' b: 2,\n' +
' },\n' +
' {\n' +
' a: 3,\n' +
' b: 4,\n' +
' c: [{ a: 1, b: 2 },\n' +
' {\n' +
' a: 3,\n' +
' b: 4,\n' +
' }\n' +
' ]\n' +
' }\n' +
']'
);

r.input.run([{ name: 'up' }]);
assert.strictEqual(r.line, 'const c = [\n {\n a: 1,\n b: 2,\n }\n]');

r.input.run([{ name: 'up' }]);
assert.strictEqual(r.line, '`const b = [\n 1,\n 2,\n 3,\n 4,\n]`');

r.input.run([{ name: 'up' }]);
assert.strictEqual(r.line, 'a = `\nI am a multiline string\nI can be as long as I want`');

});

repl.createInternalRepl(
{ NODE_REPL_HISTORY: replHistoryPath },
{
terminal: true,
input: new ActionStream(),
output: new stream.Writable({
write(chunk, _, next) {
next();
}
}),
},
checkResults
);
}
Loading