Skip to content

Commit 2508b06

Browse files
committed
repl: fix screen refresh in multiline viewport overflow
When multiline REPL input exceeds the terminal height, navigating the cursor would move the cursor position correctly but fail to refresh the screen properly, leaving stale content visible. This implements viewport-based rendering in kRefreshLine() that displays only the visible portion of input centered around the cursor position when content exceeds terminal height. Fixes: #59938
1 parent ec23cc2 commit 2508b06

File tree

1 file changed

+39
-6
lines changed

1 file changed

+39
-6
lines changed

lib/internal/readline/interface.js

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const {
1616
MathFloor,
1717
MathMax,
1818
MathMaxApply,
19+
MathMin,
1920
NumberIsFinite,
2021
ObjectDefineProperty,
2122
ObjectSetPrototypeOf,
@@ -364,6 +365,11 @@ class Interface extends InterfaceConstructor {
364365
return Infinity;
365366
}
366367

368+
get rows() {
369+
if (this.output?.rows) return this.output.rows;
370+
return Infinity;
371+
}
372+
367373
/**
368374
* Sets the prompt written to the output.
369375
* @param {string} prompt
@@ -500,6 +506,10 @@ class Interface extends InterfaceConstructor {
500506
// cursor position
501507
const cursorPos = this.getCursorPos();
502508

509+
const terminalRows = this.rows;
510+
511+
const exceedsTerminal = lineRows >= terminalRows && terminalRows !== Infinity;
512+
503513
// First move to the bottom of the current line, based on cursor pos
504514
const prevRows = this.prevRows || 0;
505515
if (prevRows > 0) {
@@ -513,16 +523,41 @@ class Interface extends InterfaceConstructor {
513523

514524
if (this[kIsMultiline]) {
515525
const lines = StringPrototypeSplit(this.line, '\n');
516-
// Write first line with normal prompt
517-
this[kWriteToOutput](this[kPrompt] + lines[0]);
518526

519-
// For continuation lines, add the "|" prefix
520-
for (let i = 1; i < lines.length; i++) {
527+
let startLine = 0;
528+
let endLine = lines.length;
529+
let visibleCursorRow = cursorPos.rows;
530+
531+
if (exceedsTerminal) {
532+
const maxVisibleLines = terminalRows - 2;
533+
534+
const halfViewport = MathFloor(maxVisibleLines / 2);
535+
536+
if (cursorPos.rows < halfViewport) {
537+
startLine = 0;
538+
endLine = MathMin(lines.length, maxVisibleLines);
539+
} else if (cursorPos.rows >= lines.length - halfViewport) {
540+
startLine = MathMax(0, lines.length - maxVisibleLines);
541+
endLine = lines.length;
542+
visibleCursorRow = cursorPos.rows - startLine;
543+
} else {
544+
startLine = cursorPos.rows - halfViewport;
545+
endLine = MathMin(lines.length, startLine + maxVisibleLines);
546+
visibleCursorRow = halfViewport;
547+
}
548+
}
549+
550+
this[kWriteToOutput](this[kPrompt] + lines[startLine]);
551+
552+
for (let i = startLine + 1; i < endLine; i++) {
521553
this[kWriteToOutput](`\n${kMultilinePrompt.description}` + lines[i]);
522554
}
555+
556+
this.prevRows = exceedsTerminal ? visibleCursorRow : cursorPos.rows;
523557
} else {
524558
// Write the prompt and the current buffer content.
525559
this[kWriteToOutput](line);
560+
this.prevRows = cursorPos.rows;
526561
}
527562

528563
// Force terminal to allocate a new line
@@ -537,8 +572,6 @@ class Interface extends InterfaceConstructor {
537572
if (diff > 0) {
538573
moveCursor(this.output, 0, -diff);
539574
}
540-
541-
this.prevRows = cursorPos.rows;
542575
}
543576

544577
/**

0 commit comments

Comments
 (0)