Skip to content

Commit fc75cba

Browse files
Giovanni Bucciaduh95
Giovanni Bucci
authored andcommitted
repl: fix multiline history editing string order
PR-URL: #57874 Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
1 parent 0cd7081 commit fc75cba

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
@@ -459,9 +459,15 @@ class Interface extends InterfaceConstructor {
459459
}
460460

461461
// Convert newlines to a consistent format for history storage
462-
[kNormalizeHistoryLineEndings](line, from, to) {
462+
[kNormalizeHistoryLineEndings](line, from, to, reverse = true) {
463463
// Multiline history entries are saved reversed
464-
if (StringPrototypeIncludes(line, '\r')) {
464+
// History is structured with the newest entries at the top
465+
// and the oldest at the bottom. Multiline histories, however, only occupy
466+
// one line in the history file. When loading multiline history with
467+
// an old node binary, the history will be saved in the old format.
468+
// This is why we need to reverse the multilines.
469+
// Reversing the multilines is necessary when adding / editing and displaying them
470+
if (reverse) {
465471
// First reverse the lines for proper order, then convert separators
466472
return ArrayPrototypeJoin(
467473
ArrayPrototypeReverse(StringPrototypeSplit(line, from)),
@@ -480,7 +486,7 @@ class Interface extends InterfaceConstructor {
480486

481487
// If the trimmed line is empty then return the line
482488
if (StringPrototypeTrim(this.line).length === 0) return this.line;
483-
const normalizedLine = this[kNormalizeHistoryLineEndings](this.line, '\n', '\r');
489+
const normalizedLine = this[kNormalizeHistoryLineEndings](this.line, '\n', '\r', false);
484490

485491
if (this.history.length === 0 || this.history[0] !== normalizedLine) {
486492
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)