Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 27 additions & 8 deletions lib/internal/readline/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ const ESCAPE_CODE_TIMEOUT = 500;
// Max length of the kill ring
const kMaxLengthOfKillRing = 32;

const kMultilinePrompt = Symbol('| ');
const kMultilinePrompt = Symbol('multilinePrompt');

const kAddHistory = Symbol('_addHistory');
const kBeforeEdit = Symbol('_beforeEdit');
Expand Down Expand Up @@ -237,6 +237,7 @@ function InterfaceConstructor(input, output, completer, terminal) {
this[kUndoStack] = [];
this[kRedoStack] = [];
this[kPreviousCursorCols] = -1;
this[kMultilinePrompt] ||= { description: '| ' };

// The kill ring is a global list of blocks of text that were previously
// killed (deleted). If its size exceeds kMaxLengthOfKillRing, the oldest
Expand Down Expand Up @@ -415,6 +416,23 @@ class Interface extends InterfaceConstructor {
});
}

/**
* Sets the multiline prompt.
* @param {string} prompt
* @returns {void}
*/
setMultilinePrompt(prompt) {
this[kMultilinePrompt].description = prompt;
}

/**
* Returns the current multiline prompt.
* @returns {string}
*/
getMultilinePrompt() {
return this[kMultilinePrompt].description;
}

[kSetRawMode](mode) {
const wasInRawMode = this.input.isRaw;

Expand Down Expand Up @@ -522,7 +540,7 @@ class Interface extends InterfaceConstructor {

// For continuation lines, add the "|" prefix
for (let i = 1; i < lines.length; i++) {
this[kWriteToOutput](`\n${kMultilinePrompt.description}` + lines[i]);
this[kWriteToOutput](`\n${this[kMultilinePrompt].description}` + lines[i]);
}
} else {
// Write the prompt and the current buffer content.
Expand Down Expand Up @@ -987,7 +1005,8 @@ class Interface extends InterfaceConstructor {
const dy = splitEnd.length + 1;

// Calculate how many Xs we need to move on the right to get to the end of the line
const dxEndOfLineAbove = (splitBeg[splitBeg.length - 2] || '').length + kMultilinePrompt.description.length;
const dxEndOfLineAbove = (splitBeg[splitBeg.length - 2] || '').length +
this[kMultilinePrompt].description.length;
moveCursor(this.output, dxEndOfLineAbove, -dy);

// This is the line that was split in the middle
Expand All @@ -1008,9 +1027,9 @@ class Interface extends InterfaceConstructor {
}

if (needsRewriteFirstLine) {
this[kWriteToOutput](`${this[kPrompt]}${beforeCursor}\n${kMultilinePrompt.description}`);
this[kWriteToOutput](`${this[kPrompt]}${beforeCursor}\n${this[kMultilinePrompt].description}`);
} else {
this[kWriteToOutput](kMultilinePrompt.description);
this[kWriteToOutput](this[kMultilinePrompt].description);
}

// Write the rest and restore the cursor to where the user left it
Expand All @@ -1022,7 +1041,7 @@ class Interface extends InterfaceConstructor {
const formattedEndContent = StringPrototypeReplaceAll(
afterCursor,
'\n',
`\n${kMultilinePrompt.description}`,
`\n${this[kMultilinePrompt].description}`,
);

this[kWriteToOutput](formattedEndContent);
Expand Down Expand Up @@ -1083,7 +1102,7 @@ class Interface extends InterfaceConstructor {
const curr = splitLines[rows];
const down = direction === 1;
const adj = splitLines[rows + direction];
const promptLen = kMultilinePrompt.description.length;
const promptLen = this[kMultilinePrompt].description.length;
let amountToMove;
// Clamp distance to end of current + prompt + next/prev line + newline
const clamp = down ?
Expand Down Expand Up @@ -1174,7 +1193,7 @@ class Interface extends InterfaceConstructor {
// Rows must be incremented by 1 even if offset = 0 or col = +Infinity.
rows += MathCeil(offset / col) || 1;
// Only add prefix offset for continuation lines in user input (not prompts)
offset = this[kIsMultiline] ? kMultilinePrompt.description.length : 0;
offset = this[kIsMultiline] ? this[kMultilinePrompt].description.length : 0;
continue;
}
// Tabs must be aligned by an offset of the tab size.
Expand Down
1 change: 1 addition & 0 deletions lib/internal/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ function createRepl(env, opts, cb) {
ignoreUndefined: false,
useGlobal: true,
breakEvalOnSigint: true,
multilinePrompt: opts?.multilinePrompt ?? '| ',
...opts,
};

Expand Down
3 changes: 2 additions & 1 deletion lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -1212,7 +1212,8 @@ REPLServer.prototype.resetContext = function() {
REPLServer.prototype.displayPrompt = function(preserveCursor) {
let prompt = this._initialPrompt;
if (this[kBufferedCommandSymbol].length) {
prompt = kMultilinePrompt.description;
this[kMultilinePrompt].description = '| ';
prompt = this[kMultilinePrompt].description;
}

// Do not overwrite `_initialPrompt` here
Expand Down
53 changes: 53 additions & 0 deletions test/parallel/test-repl-multiline-prompt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const assert = require('assert');
const repl = require('repl');

const input = [
'const foo = {', // start object
'};', // end object
'foo', // evaluate variable
];

function runPromptTest(promptStr, { useColors }) {
const inputStream = new ArrayStream();
const outputStream = new ArrayStream();
let output = '';

outputStream.write = (data) => { output += data.replace('\r', ''); };

const r = repl.start({
prompt: '',
input: inputStream,
output: outputStream,
terminal: true,
useColors
});

// Set the custom multiline prompt
r.setMultilinePrompt(promptStr);

r.on('exit', common.mustCall(() => {
const lines = output.split('\n');

// Validate REPL output
assert.ok(lines[0].endsWith(input[0])); // first line
assert.ok(lines[1].includes(promptStr)); // continuation line
assert.ok(lines[1].endsWith(input[1])); // second line content
assert.ok(lines[2].includes('undefined')); // first eval result
assert.ok(lines[3].endsWith(input[2])); // final variable
assert.ok(lines[4].includes('{}')); // printed object
}));

inputStream.run(input);
r.close();
}

// Test with custom `... ` prompt
runPromptTest('... ', { useColors: true });
runPromptTest('... ', { useColors: false });

// Test with default `| ` prompt
runPromptTest('| ', { useColors: true });
runPromptTest('| ', { useColors: false });
Loading