Skip to content

Commit 1c7938f

Browse files
committed
repl: add auto-indentation for multiline input
Automatically insert 2-space indentation on continuation lines based on brace/bracket/paren depth when entering multiline input. Refs: #48164 Signed-off-by: hemanth <hemanth.hm@gmail.com>
1 parent c286592 commit 1c7938f

4 files changed

Lines changed: 399 additions & 1 deletion

File tree

doc/api/repl.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ result. Input and output may be from `stdin` and `stdout`, respectively, or may
2727
be connected to any Node.js [stream][].
2828

2929
Instances of [`repl.REPLServer`][] support automatic completion of inputs,
30-
completion preview, simplistic Emacs-style line editing, multi-line inputs,
30+
completion preview, automatic indentation, simplistic Emacs-style line editing, multi-line inputs,
3131
[ZSH][]-like reverse-i-search, [ZSH][]-like substring-based history search,
3232
ANSI-styled output, saving and restoring current REPL session state, error
3333
recovery, and customizable evaluation functions. Terminals that do not support
@@ -232,6 +232,18 @@ undefined
232232
undefined
233233
```
234234

235+
### Auto-indentation
236+
237+
<!-- YAML
238+
added: REPLACEME
239+
-->
240+
241+
When entering
242+
multi-line input (e.g., a function body or an object literal), the REPL
243+
automatically indents continuation lines based on the current nesting
244+
depth of braces (`{}`), brackets (`[]`), and parentheses (`()`). The REPL
245+
uses 2-space indentation following the Node.js coding convention.
246+
235247
### Reverse-i-search
236248

237249
<!-- YAML

lib/internal/repl/utils.js

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const {
1111
RegExpPrototypeExec,
1212
SafeSet,
1313
SafeStringIterator,
14+
StringPrototypeCharCodeAt,
1415
StringPrototypeIndexOf,
1516
StringPrototypeLastIndexOf,
1617
StringPrototypeReplaceAll,
@@ -143,6 +144,130 @@ function isRecoverableError(e, code) {
143144
}
144145
}
145146

147+
function setupAutoIndent(repl) {
148+
if (!repl.terminal) {
149+
return;
150+
}
151+
152+
const kIndent = ' '; // 2-space indentation (Node.js convention)
153+
154+
/**
155+
* Calculates the net depth change from unmatched braces, brackets,
156+
* and parentheses in a given code string.
157+
* @returns {number} The net depth change.
158+
*/
159+
function calculateDepth(code) {
160+
let depth = 0;
161+
let inString = false;
162+
let stringChar = '';
163+
let inTemplate = false;
164+
let inLineComment = false;
165+
let inBlockComment = false;
166+
let escaped = false;
167+
168+
for (let i = 0; i < code.length; i++) {
169+
const ch = StringPrototypeCharCodeAt(code, i);
170+
171+
if (escaped) {
172+
escaped = false;
173+
continue;
174+
}
175+
176+
// Backslash escape
177+
if (ch === 92) { // '\'
178+
escaped = true;
179+
continue;
180+
}
181+
182+
// Line comments
183+
if (inLineComment) {
184+
if (ch === 10 || ch === 13) inLineComment = false; // newline
185+
continue;
186+
}
187+
188+
// Block comments
189+
if (inBlockComment) {
190+
if (ch === 42 && StringPrototypeCharCodeAt(code, i + 1) === 47) { // */
191+
inBlockComment = false;
192+
i++;
193+
}
194+
continue;
195+
}
196+
197+
// String literals
198+
if (inString) {
199+
if (ch === StringPrototypeCharCodeAt(stringChar, 0)) {
200+
inString = false;
201+
}
202+
continue;
203+
}
204+
205+
// Template literals
206+
if (inTemplate && ch === 96) { // backtick
207+
inTemplate = false;
208+
continue;
209+
}
210+
211+
// Check for comment starts
212+
if (ch === 47) { // '/'
213+
const next = StringPrototypeCharCodeAt(code, i + 1);
214+
if (next === 47) { // '//'
215+
inLineComment = true;
216+
i++;
217+
continue;
218+
}
219+
if (next === 42) { // '/*'
220+
inBlockComment = true;
221+
i++;
222+
continue;
223+
}
224+
}
225+
226+
// String start
227+
if (ch === 39 || ch === 34) { // ' or "
228+
inString = true;
229+
stringChar = code[i];
230+
continue;
231+
}
232+
233+
// Template literal
234+
if (ch === 96) { // backtick
235+
inTemplate = true;
236+
continue;
237+
}
238+
239+
// Opening delimiters
240+
if (ch === 123 || ch === 91 || ch === 40) { // { [ (
241+
depth++;
242+
}
243+
// Closing delimiters
244+
if (ch === 125 || ch === 93 || ch === 41) { // } ] )
245+
depth--;
246+
}
247+
}
248+
249+
return depth;
250+
}
251+
252+
/**
253+
* Gets the indentation level for the next line based on the
254+
* full buffered command so far.
255+
* @returns {string} The indentation string.
256+
*/
257+
repl._getAutoIndent = function(bufferedCmd) {
258+
if (!bufferedCmd) return '';
259+
260+
const depth = calculateDepth(bufferedCmd);
261+
if (depth <= 0) return '';
262+
263+
let indent = '';
264+
for (let i = 0; i < depth; i++) {
265+
indent += kIndent;
266+
}
267+
return indent;
268+
};
269+
}
270+
146271
function setupPreview(repl, contextSymbol, bufferSymbol, active) {
147272
// Simple terminals can't handle previews.
148273
if (process.env.TERM === 'dumb' || !active) {
@@ -867,6 +992,7 @@ module.exports = {
867992
isRecoverableError,
868993
kStandaloneREPL: Symbol('kStandaloneREPL'),
869994
setupPreview,
995+
setupAutoIndent,
870996
setupReverseSearch,
871997
isObjectLiteral,
872998
isValidSyntax,

lib/repl.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ const {
127127
REPL_MODE_STRICT,
128128
kStandaloneREPL,
129129
setupPreview,
130+
setupAutoIndent,
130131
setupReverseSearch,
131132
kContextId,
132133
getREPLResourceName,
@@ -620,6 +621,14 @@ class REPLServer extends Interface {
620621
if (e instanceof Recoverable && !sawCtrlD) {
621622
if (self.terminal) {
622623
self[kAddNewLineOnTTY]();
624+
if (typeof self._getAutoIndent === 'function') {
625+
const buffered = self[kBufferedCommandSymbol] + cmd + '\n';
626+
const indent = self._getAutoIndent(buffered);
627+
if (indent) {
628+
self._insertString(indent);
629+
}
630+
}
631+
623632
} else {
624633
self[kBufferedCommandSymbol] += cmd + '\n';
625634
self.displayPrompt();
@@ -675,6 +684,8 @@ class REPLServer extends Interface {
675684
});
676685

677686
const { reverseSearch } = setupReverseSearch(this);
687+
setupAutoIndent(this);
688+
678689

679690
const {
680691
clearPreview, showPreview,

0 commit comments

Comments
 (0)