Skip to content

Commit 2fe558d

Browse files
committed
repl: make history navigation work with big inputs
Fixes: #59938
1 parent 534442f commit 2fe558d

File tree

2 files changed

+72
-7
lines changed

2 files changed

+72
-7
lines changed

lib/internal/readline/interface.js

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -513,11 +513,30 @@ class Interface extends InterfaceConstructor {
513513

514514
if (this[kIsMultiline]) {
515515
const lines = StringPrototypeSplit(this.line, '\n');
516-
// Write first line with normal prompt
517-
this[kWriteToOutput](this[kPrompt] + lines[0]);
516+
const terminalRows = this.output?.rows;
517+
let startLine = 0;
518+
let endLine = lineRows;
519+
520+
const isTallerThanTerminalHeight = terminalRows && (lineRows >= terminalRows);
521+
522+
if (isTallerThanTerminalHeight) {
523+
const firstVisibleLineIdx = lineRows - terminalRows;
524+
525+
if (cursorPos.rows > firstVisibleLineIdx) {
526+
startLine = firstVisibleLineIdx;
527+
} else {
528+
startLine = cursorPos.rows;
529+
endLine = cursorPos.rows + terminalRows - 1;
530+
}
531+
}
532+
533+
if (startLine === 0) {
534+
this[kWriteToOutput](this[kPrompt] + lines[0]);
535+
startLine++;
536+
}
518537

519538
// For continuation lines, add the "|" prefix
520-
for (let i = 1; i < lines.length; i++) {
539+
for (let i = startLine; i <= endLine; i++) {
521540
this[kWriteToOutput](`\n${kMultilinePrompt.description}` + lines[i]);
522541
}
523542
} else {
@@ -1156,10 +1175,16 @@ class Interface extends InterfaceConstructor {
11561175

11571176
[kMoveUpOrHistoryPrev]() {
11581177
const cursorPos = this.getCursorPos();
1159-
if (this[kIsMultiline] && cursorPos.rows > 0) {
1160-
const splitLines = StringPrototypeSplit(this.line, '\n');
1161-
this[kMultilineMove](-1, splitLines, cursorPos);
1162-
return;
1178+
if (this[kIsMultiline]) {
1179+
if (cursorPos.rows > 0) {
1180+
const splitLines = StringPrototypeSplit(this.line, '\n');
1181+
this[kMultilineMove](-1, splitLines, cursorPos);
1182+
return;
1183+
} else if (cursorPos.rows === 0 && this.historyIndex === -1) {
1184+
// Pressing up when at the first line of a new multiline input, should do nothing
1185+
// we don't want the user to lose what he has written so far.
1186+
return;
1187+
}
11631188
}
11641189
this[kPreviousCursorCols] = -1;
11651190
this[kHistoryPrev]();

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,3 +310,43 @@ tmpdir.refresh();
310310
checkResults
311311
);
312312
}
313+
314+
{
315+
const historyPath = tmpdir.resolve(`.${Math.floor(Math.random() * 10000)}`);
316+
// Make sure the cursor is at the right places when navigating an input which is
317+
// bigger than the terminal height
318+
const checkResults = common.mustSucceed((r) => {
319+
r.write('let fff = `I am a');
320+
r.input.run([{ name: 'enter' }]);
321+
r.write('1111111111111');
322+
r.input.run([{ name: 'enter' }]);
323+
r.write('22222222222222');
324+
r.input.run([{ name: 'enter' }]);
325+
r.write('333333333333333');
326+
r.input.run([{ name: 'enter' }]);
327+
r.write('4444444444`');
328+
329+
// Make sure that if we press up while adding a new multiline input,
330+
// and the cursor is at the first line, we don't lose what we have written so far.
331+
for (let i = 0; i < 5; i++) {
332+
r.input.run([{ name: 'up' }]);
333+
}
334+
335+
assert.strictEqual(r.line, 'let fff = `I am a\r1111111111111\r22222222222222\r333333333333333\r4444444444`');
336+
assert.strictEqual(r.history.length, 1);
337+
});
338+
339+
repl.createInternalRepl(
340+
{ NODE_REPL_HISTORY: historyPath },
341+
{
342+
terminal: true,
343+
input: new ActionStream(),
344+
output: Object.assign(new stream.Writable({
345+
write(chunk, _, next) {
346+
next();
347+
}
348+
}), { rows: 3 }),
349+
},
350+
checkResults
351+
);
352+
}

0 commit comments

Comments
 (0)