Skip to content

Commit 925dd8e

Browse files
committed
repl: fix preview of lines that exceed the terminal columns
This adds support for very long input lines to still display the input preview correct. PR-URL: #31006 Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Anto Aravinth <[email protected]> Reviewed-By: Rich Trott <[email protected]>
1 parent 7611d5b commit 925dd8e

File tree

3 files changed

+79
-35
lines changed

3 files changed

+79
-35
lines changed

lib/internal/repl/utils.js

+15-14
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,19 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
132132
let previewCompletionCounter = 0;
133133
let completionPreview = null;
134134

135+
function getPreviewPos() {
136+
const displayPos = repl._getDisplayPos(`${repl._prompt}${repl.line}`);
137+
const cursorPos = repl._getCursorPos();
138+
const rows = 1 + displayPos.rows - cursorPos.rows;
139+
return { rows, cols: cursorPos.cols };
140+
}
141+
135142
const clearPreview = () => {
136143
if (inputPreview !== null) {
137-
moveCursor(repl.output, 0, 1);
144+
const { rows } = getPreviewPos();
145+
moveCursor(repl.output, 0, rows);
138146
clearLine(repl.output);
139-
moveCursor(repl.output, 0, -1);
147+
moveCursor(repl.output, 0, -rows);
140148
lastInputPreview = inputPreview;
141149
inputPreview = null;
142150
}
@@ -280,16 +288,6 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
280288
return;
281289
}
282290

283-
// Do not show previews in case the current line is longer than the column
284-
// width.
285-
// TODO(BridgeAR): Fix me. This should not be necessary. It currently breaks
286-
// the output though. We also have to check for characters that have more
287-
// than a single byte as length. Check Interface.prototype._moveCursor. It
288-
// contains the necessary logic.
289-
if (repl.line.length + repl._prompt.length > repl.columns) {
290-
return;
291-
}
292-
293291
// Add the autocompletion preview.
294292
// TODO(BridgeAR): Trigger the input preview after the completion preview.
295293
// That way it's possible to trigger the input prefix including the
@@ -344,9 +342,12 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
344342
`\u001b[90m${inspected}\u001b[39m` :
345343
`// ${inspected}`;
346344

345+
const { rows: previewRows, cols: cursorCols } = getPreviewPos();
346+
if (previewRows !== 1)
347+
moveCursor(repl.output, 0, previewRows - 1);
348+
const { cols: resultCols } = repl._getDisplayPos(result);
347349
repl.output.write(`\n${result}`);
348-
moveCursor(repl.output, 0, -1);
349-
cursorTo(repl.output, repl._prompt.length + repl.cursor);
350+
moveCursor(repl.output, cursorCols - resultCols, -previewRows);
350351
});
351352
};
352353

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

+57-14
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ const tests = [
108108
env: { NODE_REPL_HISTORY: defaultHistoryPath },
109109
skip: !process.features.inspector,
110110
test: [
111-
`const ${'veryLongName'.repeat(30)} = 'I should not be previewed'`,
111+
`const ${'veryLongName'.repeat(30)} = 'I should be previewed'`,
112112
ENTER,
113113
'const e = new RangeError("visible\\ninvisible")',
114114
ENTER,
@@ -127,27 +127,70 @@ const tests = [
127127
{
128128
env: { NODE_REPL_HISTORY: defaultHistoryPath },
129129
columns: 250,
130+
showEscapeCodes: true,
130131
skip: !process.features.inspector,
131132
test: [
132133
UP,
133134
UP,
134135
UP,
136+
WORD_LEFT,
135137
UP,
136138
BACKSPACE
137139
],
140+
// A = Cursor n up
141+
// B = Cursor n down
142+
// C = Cursor n forward
143+
// D = Cursor n back
144+
// G = Cursor to column n
145+
// J = Erase in screen; 0 = right; 1 = left; 2 = total
146+
// K = Erase in line; 0 = right; 1 = left; 2 = total
138147
expected: [
139-
prompt,
148+
// 0. Start
149+
'\x1B[1G', '\x1B[0J',
150+
prompt, '\x1B[3G',
151+
// 1. UP
140152
// This exceeds the maximum columns (250):
141153
// Whitespace + prompt + ' // '.length + 'function'.length
142154
// 236 + 2 + 4 + 8
143-
`${prompt}${' '.repeat(236)} fun`,
144-
`${prompt}${' '.repeat(235)} fun`,
145-
' // ction',
146-
' // ction',
147-
`${prompt}${'veryLongName'.repeat(30)}`,
148-
`${prompt}e`,
149-
'\n// RangeError: visible',
150-
prompt
155+
'\x1B[1G', '\x1B[0J',
156+
`${prompt}${' '.repeat(236)} fun`, '\x1B[243G',
157+
// 2. UP
158+
'\x1B[1G', '\x1B[0J',
159+
`${prompt}${' '.repeat(235)} fun`, '\x1B[242G',
160+
// TODO(BridgeAR): Investigate why the preview is generated twice.
161+
' // ction', '\x1B[242G',
162+
' // ction', '\x1B[242G',
163+
// Preview cleanup
164+
'\x1B[0K',
165+
// 3. UP
166+
'\x1B[1G', '\x1B[0J',
167+
// 'veryLongName'.repeat(30).length === 360
168+
// prompt.length === 2
169+
// 360 % 250 + 2 === 112 (+1)
170+
`${prompt}${'veryLongName'.repeat(30)}`, '\x1B[113G',
171+
// "// 'I should be previewed'".length + 86 === 112 (+1)
172+
"\n// 'I should be previewed'", '\x1B[86C\x1B[1A',
173+
// Preview cleanup
174+
'\x1B[1B', '\x1B[2K', '\x1B[1A',
175+
// 4. WORD LEFT
176+
// Almost identical as above. Just one extra line.
177+
// Math.floor(360 / 250) === 1
178+
'\x1B[1A',
179+
'\x1B[1G', '\x1B[0J',
180+
`${prompt}${'veryLongName'.repeat(30)}`, '\x1B[3G', '\x1B[1A',
181+
'\x1B[1B', "\n// 'I should be previewed'", '\x1B[24D\x1B[2A',
182+
// Preview cleanup
183+
'\x1B[2B', '\x1B[2K', '\x1B[2A',
184+
// 5. UP
185+
'\x1B[1G', '\x1B[0J',
186+
`${prompt}e`, '\x1B[4G',
187+
// '// RangeError: visible'.length - 19 === 3 (+1)
188+
'\n// RangeError: visible', '\x1B[19D\x1B[1A',
189+
// Preview cleanup
190+
'\x1B[1B', '\x1B[2K', '\x1B[1A',
191+
// 6. Backspace
192+
'\x1B[1G', '\x1B[0J',
193+
prompt, '\x1B[3G'
151194
],
152195
clean: true
153196
},
@@ -169,11 +212,11 @@ const tests = [
169212
WORD_RIGHT,
170213
ENTER
171214
],
172-
// C = Cursor forward
173-
// D = Cursor back
215+
// C = Cursor n forward
216+
// D = Cursor n back
174217
// G = Cursor to column n
175-
// J = Erase in screen
176-
// K = Erase in line
218+
// J = Erase in screen; 0 = right; 1 = left; 2 = total
219+
// K = Erase in line; 0 = right; 1 = left; 2 = total
177220
expected: [
178221
// 0.
179222
// 'f'

test/parallel/test-repl-preview.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,12 @@ async function tests(options) {
6868
const testCases = [
6969
['foo', [2, 4], '[Function: foo]',
7070
'foo',
71-
'\x1B[90m[Function: foo]\x1B[39m\x1B[1A\x1B[11G\x1B[1B\x1B[2K\x1B[1A\r',
71+
'\x1B[90m[Function: foo]\x1B[39m\x1B[5D\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r',
7272
'\x1B[36m[Function: foo]\x1B[39m',
7373
'\x1B[1G\x1B[0Jrepl > \x1B[8G'],
7474
['koo', [2, 4], '[Function: koo]',
7575
'k\x1B[90moo\x1B[39m\x1B[9G\x1B[0Ko\x1B[90mo\x1B[39m\x1B[10G\x1B[0Ko',
76-
'\x1B[90m[Function: koo]\x1B[39m\x1B[1A\x1B[11G\x1B[1B\x1B[2K\x1B[1A\r',
76+
'\x1B[90m[Function: koo]\x1B[39m\x1B[5D\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r',
7777
'\x1B[36m[Function: koo]\x1B[39m',
7878
'\x1B[1G\x1B[0Jrepl > \x1B[8G'],
7979
['a', [1, 2], undefined],
@@ -83,19 +83,19 @@ async function tests(options) {
8383
'\x1B[1G\x1B[0Jrepl > \x1B[8G'],
8484
['1n + 2n', [2, 5], '\x1B[33m3n\x1B[39m',
8585
'1n + 2',
86-
'\x1B[90mType[39m\x1B[1A\x1B[14G\x1B[1B\x1B[2K\x1B[1An',
87-
'\x1B[90m3n\x1B[39m\x1B[1A\x1B[15G\x1B[1B\x1B[2K\x1B[1A\r',
86+
'\x1B[90mType[39m\x1B[57D\x1B[1A\x1B[1B\x1B[2K\x1B[1An',
87+
'\x1B[90m3n\x1B[39m\x1B[12C\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r',
8888
'\x1B[33m3n\x1B[39m',
8989
'\x1B[1G\x1B[0Jrepl > \x1B[8G'],
9090
['{ a: true };', [2, 4], '\x1B[33mtrue\x1B[39m',
9191
'{ a: tru\x1B[90me\x1B[39m\x1B[16G\x1B[0Ke };',
92-
'\x1B[90mtrue\x1B[39m\x1B[1A\x1B[20G\x1B[1B\x1B[2K\x1B[1A\r',
92+
'\x1B[90mtrue\x1B[39m\x1B[15C\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r',
9393
'\x1B[33mtrue\x1B[39m',
9494
'\x1B[1G\x1B[0Jrepl > \x1B[8G'],
9595
[' \t { a: true};', [2, 5], '\x1B[33mtrue\x1B[39m',
9696
' \t { a: tru\x1B[90me\x1B[39m\x1B[19G\x1B[0Ke}',
97-
'\x1B[90m{ a: true }\x1B[39m\x1B[1A\x1B[21G\x1B[1B\x1B[2K\x1B[1A;',
98-
'\x1B[90mtrue\x1B[39m\x1B[1A\x1B[22G\x1B[1B\x1B[2K\x1B[1A\r',
97+
'\x1B[90m{ a: true }\x1B[39m\x1B[8C\x1B[1A\x1B[1B\x1B[2K\x1B[1A;',
98+
'\x1B[90mtrue\x1B[39m\x1B[16C\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r',
9999
'\x1B[33mtrue\x1B[39m',
100100
'\x1B[1G\x1B[0Jrepl > \x1B[8G']
101101
];

0 commit comments

Comments
 (0)