diff --git a/lib/readline.js b/lib/readline.js index f9ad95d61e6c22..84b83ebdb1b01c 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -42,6 +42,7 @@ function Interface(input, output, completer, terminal) { this._sawReturn = false; this.isCompletionEnabled = true; + this._sawKeyPress = false; EventEmitter.call(this); var historySize; @@ -247,6 +248,9 @@ Interface.prototype._addHistory = function() { // if the history is disabled then return the line if (this.historySize === 0) return this.line; + // if the trimmed line is empty then return the line + if (this.line.trim().length === 0) return this.line; + if (this.history.length === 0 || this.history[0] !== this.line) { this.history.unshift(this.line); @@ -946,6 +950,10 @@ function emitKeypressEvents(stream, iface) { if (r) { clearTimeout(timeoutId); + if (iface) { + iface._sawKeyPress = r.length === 1; + } + for (var i = 0; i < r.length; i++) { if (r[i] === '\t' && typeof r[i + 1] === 'string' && iface) { iface.isCompletionEnabled = false; diff --git a/lib/repl.js b/lib/repl.js index 704992aabc12a9..37d8894a82226f 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -471,6 +471,15 @@ function REPLServer(prompt, if (self.editorMode) { self.bufferedCommand += cmd + '\n'; + + // code alignment + const matches = self._sawKeyPress ? cmd.match(/^\s+/) : null; + if (matches) { + const prefix = matches[0]; + self.inputStream.write(prefix); + self.line = prefix; + self.cursor = prefix.length; + } self.memory(cmd); return; } diff --git a/test/parallel/test-repl-.editor.js b/test/parallel/test-repl-.editor.js index 15765ad517d72a..556662181f7831 100644 --- a/test/parallel/test-repl-.editor.js +++ b/test/parallel/test-repl-.editor.js @@ -9,7 +9,7 @@ const repl = require('repl'); // \u001b[3G - Moves the cursor to 3rd column const terminalCode = '\u001b[1G\u001b[0J> \u001b[3G'; -function run(input, output, event) { +function run({input, output, event}) { const stream = new common.ArrayStream(); let found = ''; @@ -49,7 +49,59 @@ const tests = [ input: 'var i = 1;\ni + 3', output: '\n4', event: {ctrl: true, name: 'd'} + }, + { + input: ' var i = 1;\ni + 3', + output: '\n4', + event: {ctrl: true, name: 'd'} + } +]; + +tests.forEach(run); + +// Auto code alignment for .editor mode +function testCodeAligment({input, cursor = 0, line = ''}) { + const stream = new common.ArrayStream(); + + const replServer = repl.start({ + prompt: '> ', + terminal: true, + input: stream, + output: stream, + useColors: false + }); + + stream.emit('data', '.editor\n'); + input.split('').forEach((ch) => stream.emit('data', ch)); + // Test the content of current line and the cursor position + assert.strictEqual(line, replServer.line); + assert.strictEqual(cursor, replServer.cursor); + + replServer.write('', {ctrl: true, name: 'd'}); + replServer.close(); + // Ensure that empty lines are not saved in history + assert.notStrictEqual(replServer.history[0].trim(), ''); +} + +const codeAlignmentTests = [ + { + input: 'var i = 1;\n' + }, + { + input: ' var i = 1;\n', + cursor: 2, + line: ' ' + }, + { + input: ' var i = 1;\n', + cursor: 5, + line: ' ' + }, + { + input: ' var i = 1;\n var j = 2\n', + cursor: 2, + line: ' ' } ]; -tests.forEach(({input, output, event}) => run(input, output, event)); +codeAlignmentTests.forEach(testCodeAligment);