Skip to content

Commit ab99ee6

Browse files
Giovanni BucciRafaelGSS
Giovanni Bucci
authored andcommitted
repl: fix multiline history editing string order
PR-URL: #57874 Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Luigi Pinca <[email protected]>
1 parent e92e100 commit ab99ee6

File tree

2 files changed

+21
-23
lines changed

2 files changed

+21
-23
lines changed

lib/internal/readline/interface.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -467,9 +467,15 @@ class Interface extends InterfaceConstructor {
467467
}
468468

469469
// Convert newlines to a consistent format for history storage
470-
[kNormalizeHistoryLineEndings](line, from, to) {
470+
[kNormalizeHistoryLineEndings](line, from, to, reverse = true) {
471471
// Multiline history entries are saved reversed
472-
if (StringPrototypeIncludes(line, '\r')) {
472+
// History is structured with the newest entries at the top
473+
// and the oldest at the bottom. Multiline histories, however, only occupy
474+
// one line in the history file. When loading multiline history with
475+
// an old node binary, the history will be saved in the old format.
476+
// This is why we need to reverse the multilines.
477+
// Reversing the multilines is necessary when adding / editing and displaying them
478+
if (reverse) {
473479
// First reverse the lines for proper order, then convert separators
474480
return ArrayPrototypeJoin(
475481
ArrayPrototypeReverse(StringPrototypeSplit(line, from)),
@@ -488,7 +494,7 @@ class Interface extends InterfaceConstructor {
488494

489495
// If the trimmed line is empty then return the line
490496
if (StringPrototypeTrim(this.line).length === 0) return this.line;
491-
const normalizedLine = this[kNormalizeHistoryLineEndings](this.line, '\n', '\r');
497+
const normalizedLine = this[kNormalizeHistoryLineEndings](this.line, '\n', '\r', false);
492498

493499
if (this.history.length === 0 || this.history[0] !== normalizedLine) {
494500
if (this[kLastCommandErrored] && this.historyIndex === 0) {

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

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ const common = require('../common');
77
const assert = require('assert');
88
const repl = require('internal/repl');
99
const stream = require('stream');
10-
const fs = require('fs');
1110

1211
class ActionStream extends stream.Stream {
1312
run(data) {
@@ -42,23 +41,11 @@ class ActionStream extends stream.Stream {
4241
}
4342
ActionStream.prototype.readable = true;
4443

45-
function cleanupTmpFile() {
46-
try {
47-
// Write over the file, clearing any history
48-
fs.writeFileSync(defaultHistoryPath, '');
49-
} catch (err) {
50-
if (err.code === 'ENOENT') return true;
51-
throw err;
52-
}
53-
return true;
54-
}
55-
5644
const tmpdir = require('../common/tmpdir');
5745
tmpdir.refresh();
58-
const defaultHistoryPath = tmpdir.resolve('.node_repl_history');
5946

6047
{
61-
cleanupTmpFile();
48+
const historyPath = tmpdir.resolve(`.${Math.floor(Math.random() * 10000)}`);
6249
// Make sure the cursor is at the right places.
6350
// If the cursor is at the end of a long line and the down key is pressed,
6451
// Move the cursor to the end of the next line, if shorter.
@@ -97,7 +84,7 @@ const defaultHistoryPath = tmpdir.resolve('.node_repl_history');
9784
});
9885

9986
repl.createInternalRepl(
100-
{ NODE_REPL_HISTORY: defaultHistoryPath },
87+
{ NODE_REPL_HISTORY: historyPath },
10188
{
10289
terminal: true,
10390
input: new ActionStream(),
@@ -112,7 +99,7 @@ const defaultHistoryPath = tmpdir.resolve('.node_repl_history');
11299
}
113100

114101
{
115-
cleanupTmpFile();
102+
const historyPath = tmpdir.resolve(`.${Math.floor(Math.random() * 10000)}`);
116103
// If the last command errored and the user is trying to edit it,
117104
// The errored line should be removed from history
118105
const checkResults = common.mustSucceed((r) => {
@@ -130,12 +117,17 @@ const defaultHistoryPath = tmpdir.resolve('.node_repl_history');
130117
r.input.run([{ name: 'enter' }]);
131118

132119
assert.strictEqual(r.history.length, 1);
133-
assert.strictEqual(r.history[0], 'let lineWithMistake = `I have some\rproblem with my syntax`');
120+
// Check that the line is properly set in the history structure
121+
assert.strictEqual(r.history[0], 'problem with my syntax`\rlet lineWithMistake = `I have some');
134122
assert.strictEqual(r.line, '');
123+
124+
r.input.run([{ name: 'up' }]);
125+
// Check that the line is properly displayed
126+
assert.strictEqual(r.line, 'let lineWithMistake = `I have some\nproblem with my syntax`');
135127
});
136128

137129
repl.createInternalRepl(
138-
{ NODE_REPL_HISTORY: defaultHistoryPath },
130+
{ NODE_REPL_HISTORY: historyPath },
139131
{
140132
terminal: true,
141133
input: new ActionStream(),
@@ -150,7 +142,7 @@ const defaultHistoryPath = tmpdir.resolve('.node_repl_history');
150142
}
151143

152144
{
153-
cleanupTmpFile();
145+
const historyPath = tmpdir.resolve(`.${Math.floor(Math.random() * 10000)}`);
154146
const outputBuffer = [];
155147

156148
// Test that the REPL preview is properly shown on multiline commands
@@ -182,7 +174,7 @@ const defaultHistoryPath = tmpdir.resolve('.node_repl_history');
182174
});
183175

184176
repl.createInternalRepl(
185-
{ NODE_REPL_HISTORY: defaultHistoryPath },
177+
{ NODE_REPL_HISTORY: historyPath },
186178
{
187179
preview: true,
188180
terminal: true,

0 commit comments

Comments
 (0)