diff --git a/doc/api/repl.md b/doc/api/repl.md index 4ed95f61116b11..b789513867abee 100644 --- a/doc/api/repl.md +++ b/doc/api/repl.md @@ -39,6 +39,7 @@ The following special commands are supported by all REPL instances: * `.load` - Load a file into the current REPL session. `> .load ./file/to/load.js` * `.editor` - Enter editor mode (`-D` to finish, `-C` to cancel) +* `.load-editor` - Load a file and enter editor mode (`-D` to finish, `-C` to cancel) ```js diff --git a/lib/repl.js b/lib/repl.js index f7452830979038..0dcc9150dff4cb 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -1284,6 +1284,35 @@ function defineDefaultCommands(repl) { '// Entering editor mode (^D to finish, ^C to cancel)\n'); } }); + + repl.defineCommand('load-editor', { + help: 'Load a file and enter editor mode', + action: function(file) { + if (!this.terminal) return; + this.editorMode = true; + REPLServer.super_.prototype.setPrompt.call(this, ''); + this.outputStream.write( + '// Entering editor mode (^D to finish, ^C to cancel)\n'); + try { + var stats = fs.statSync(file); + if (stats && stats.isFile()) { + var data = fs.readFileSync(file, 'utf8'); + var lines = data.split('\n'); + this.displayPrompt(); + for (var n = 0; n < lines.length; n++) { + if (lines[n]) + this.write(`${lines[n]}\n`); + } + } else { + this.outputStream.write('Failed to load:' + file + + ' is not a valid file\n'); + } + } catch (e) { + this.outputStream.write('Failed to load:' + file + '\n'); + } + this.displayPrompt(); + } + }); } function regexpEscape(s) { diff --git a/test/parallel/test-repl-definecommand.js b/test/parallel/test-repl-definecommand.js index b6368bbd940d50..07c37afbc88b3f 100644 --- a/test/parallel/test-repl-definecommand.js +++ b/test/parallel/test-repl-definecommand.js @@ -35,7 +35,7 @@ r.defineCommand('say2', function() { }); inputStream.write('.help\n'); -assert(/\n\.say1 help for say1\n/.test(output), +assert(/\n\.say1 help for say1\n/.test(output), 'help for say1 not present'); assert(/\n\.say2\n/.test(output), 'help for say2 not present'); inputStream.write('.say1 node developer\n'); diff --git a/test/parallel/test-repl-load-editor.js b/test/parallel/test-repl-load-editor.js new file mode 100644 index 00000000000000..001317ec7f7ec6 --- /dev/null +++ b/test/parallel/test-repl-load-editor.js @@ -0,0 +1,112 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const repl = require('repl'); + +// \u001b[1G - Moves the cursor to 1st column +// \u001b[0J - Clear screen +// \u001b[3G - Moves the cursor to 3rd column +const terminalCode = '\u001b[1G\u001b[0J> \u001b[3G'; +const terminalCodeRegex = new RegExp(terminalCode.replace(/\[/g, '\\['), 'g'); + +function run({ input, filename, errormsg, output, event, + checkTerminalCodes = true }) { + const stream = new common.ArrayStream(); + let found = ''; + + stream.write = (msg) => found += msg.replace('\r', ''); + + let expected = `${terminalCode}.load-editor ${filename}\n` + + '// Entering editor mode (^D to finish, ^C to cancel)\n' + + `${errormsg}${input}${output}\n${terminalCode}`; + + const replServer = repl.start({ + prompt: '> ', + terminal: true, + input: stream, + output: stream, + useColors: false + }); + + stream.emit('data', `.load-editor ${filename}\n`); + stream.emit('data', input); + replServer.write('', event); + replServer.close(); + + if (!checkTerminalCodes) { + found = found.replace(terminalCodeRegex, '').replace(/\n/g, ''); + expected = expected.replace(terminalCodeRegex, '').replace(/\n/g, ''); + } + + assert.strictEqual(found, expected); +} + +const tests = [ + { + input: '', + filename: 'notexistfile.js', + errormsg: `Failed to load:notexistfile.js\n${terminalCode}\n`, + output: '(To exit, press ^C again or type .exit)', + event: { ctrl: true, name: 'c' } + }, + { + input: 'var i = 1;\ni + 3', + filename: 'notexistfile.js', + errormsg: `Failed to load:notexistfile.js\n${terminalCode}`, + 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 outputStream = new common.ArrayStream(); + + stream.write = () => { throw new Error('Writing not allowed!'); }; + + const replServer = repl.start({ + prompt: '> ', + terminal: true, + input: stream, + output: outputStream, + useColors: false + }); + + stream.emit('data', '.load-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: ' ' + } +]; + +codeAlignmentTests.forEach(testCodeAligment);