diff --git a/doc/api/readline.markdown b/doc/api/readline.markdown index 055bc39bdf94dd..b92b140080fa41 100644 --- a/doc/api/readline.markdown +++ b/doc/api/readline.markdown @@ -13,7 +13,8 @@ program to gracefully exit: var rl = readline.createInterface({ input: process.stdin, - output: process.stdout + output: process.stdout, + history: ['foo', 'bar', ...] }); rl.question("What do you think of node.js? ", function(answer) { @@ -38,6 +39,8 @@ the following values: - `terminal` - pass `true` if the `input` and `output` streams should be treated like a TTY, and have ANSI/VT100 escape codes written to it. Defaults to checking `isTTY` on the `output` stream upon instantiation. + + - `history` - pass history(Array) to start the cli with previous history (Optional). The `completer` function is given the current line entered by the user, and is supposed to return an Array with 2 entries: @@ -70,11 +73,24 @@ Also `completer` can be run in async mode if it accepts two arguments: var readline = require('readline'); var rl = readline.createInterface({ input: process.stdin, - output: process.stdout + output: process.stdout, + // start the cli with previous history + history: ['foo', 'bar', ...] }); Once you have a readline instance, you most commonly listen for the -`"line"` event. +`"line"` and `"close"` events: + + rl + .on('line', function(line) { + // ... + lr.pause(); + fs.appendFile('path/to/history', line, rl.resume.bind(rl)); + }) + .on('close', function() { + var history = rl.history; + // save history and exit() + }); If `terminal` is `true` for this instance then the `output` stream will get the best compatibility if it defines an `output.columns` property, and fires @@ -91,6 +107,10 @@ stream. Sets the prompt, for example when you run `node` on the command line, you see `> `, which is node's prompt. +### rl.setHistorySize(size) + +Sets the length of history size, the default is 30. + ### rl.prompt([preserveCursor]) Readies readline for input from the user, putting the current `setPrompt` diff --git a/lib/readline.js b/lib/readline.js index 1a159c9a1bdef9..6807c776fb0b73 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -5,9 +5,6 @@ // * http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html 'use strict'; - -const kHistorySize = 30; - const util = require('util'); const inherits = util.inherits; const EventEmitter = require('events').EventEmitter; @@ -24,9 +21,9 @@ exports.createInterface = function(input, output, completer, terminal) { }; -function Interface(input, output, completer, terminal) { +function Interface(input, output, completer, terminal, history) { if (!(this instanceof Interface)) { - return new Interface(input, output, completer, terminal); + return new Interface(input, output, completer, terminal, history); } this._sawReturn = false; @@ -38,9 +35,9 @@ function Interface(input, output, completer, terminal) { output = input.output; completer = input.completer; terminal = input.terminal; + history = input.history; input = input.input; } - completer = completer || function() { return []; }; if (!util.isFunction(completer)) { @@ -120,7 +117,8 @@ function Interface(input, output, completer, terminal) { // Cursor position on the line. this.cursor = 0; - this.history = []; + this._historySize = 30; + this.history = history || []; this.historyIndex = -1; if (!util.isNullOrUndefined(output)) @@ -151,6 +149,10 @@ Interface.prototype.setPrompt = function(prompt) { this._prompt = prompt; }; +Interface.prototype.setHistorySize = function(historySize) { + this._historySize = historySize; +}; + Interface.prototype._setRawMode = function(mode) { if (util.isFunction(this.input.setRawMode)) { @@ -210,7 +212,7 @@ Interface.prototype._addHistory = function() { this.history.unshift(this.line); // Only store so many - if (this.history.length > kHistorySize) this.history.pop(); + if (this.history.length > this._historySize) this.history.pop(); } this.historyIndex = -1; diff --git a/lib/repl.js b/lib/repl.js index 64d99b00010966..6aecaaa2f6bdbe 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -173,7 +173,8 @@ function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) { self.inputStream, self.outputStream, complete, - options.terminal + options.terminal, + options.history ]); self.setPrompt(!util.isUndefined(prompt) ? prompt : '> '); @@ -310,7 +311,7 @@ function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) { // Display prompt again self.displayPrompt(); - }; + } }); self.on('SIGCONT', function() { @@ -460,7 +461,7 @@ REPLServer.prototype.complete = function(line, callback) { var completeOn, match, filter, i, group, c; // REPL commands (e.g. ".break"). - var match = null; + match = null; match = line.match(/^\s*(\.\w*)$/); if (match) { completionGroups.push(Object.keys(this.commands)); @@ -478,7 +479,7 @@ REPLServer.prototype.complete = function(line, callback) { completeOn = match[1]; var subdir = match[2] || ''; - var filter = match[1]; + filter = match[1]; var dir, files, f, name, base, ext, abs, subfiles, s; group = []; var paths = module.paths.concat(require('module').globalPaths); diff --git a/src/node.js b/src/node.js index 3179378fbc5ae6..460302b219ca1d 100644 --- a/src/node.js +++ b/src/node.js @@ -116,11 +116,47 @@ // If -i or --interactive were passed, or stdin is a TTY. if (process._forceRepl || NativeModule.require('tty').isatty(0)) { + var history; + var path = NativeModule.require('path'); + var fs = NativeModule.require('fs'); + // This code comes from Sindre Sorhus `user-home` module + // https://github.com/sindresorhus/user-home + var home = (function getUserHome() { + var env = process.env; + var home = env.HOME; + var user = env.LOGNAME || env.USER || env.LNAME || env.USERNAME; + + if (process.platform === 'win32') { + return env.USERPROFILE || env.HOMEDRIVE + env.HOMEPATH || home || null; + } else if (process.platform === 'darwin') { + return home || (user ? '/Users/' + user : null); + } else if (process.platform === 'linux') { + return home || + (user ? (process.getuid() === 0 ? '/root' : '/home/' + user) : null); + } + return home || null; + })() + var HISTORY_PATH = home ? path.join(home,'.iojs_history') : null; + // REPL var opts = { useGlobal: true, ignoreUndefined: false }; + + // If we got user-home dir + if(HISTORY_PATH) { + // Get history if exist + try { + history = fs.readFileSync(HISTORY_PATH, 'utf8') + .replace(/\n$/, '') // Ignore the last \n + .split('\n'); + } catch(e) { + history = []; + } + opts.history = history; + } + if (parseInt(process.env['NODE_NO_READLINE'], 10)) { opts.terminal = false; } @@ -128,9 +164,22 @@ opts.useColors = false; } var repl = Module.requireRepl().start(opts); - repl.on('exit', function() { - process.exit(); - }); + repl + .on('line', function(line) { + if(HISTORY_PATH) + try { + fs.appendFileSync(HISTORY_PATH, line + '\n'); + } catch(e) {} + }) + .on('exit', function() { + if(HISTORY_PATH && repl.history) + try { + fs.writeFileSync(HISTORY_PATH, repl.history + .join('\n')); + } catch(e) {} + + process.exit(); + }); } else { // Read all of stdin - execute it. diff --git a/test/parallel/test-readline-interface.js b/test/parallel/test-readline-interface.js index ae8a4188ab5ca4..6e7fffed80d8c4 100644 --- a/test/parallel/test-readline-interface.js +++ b/test/parallel/test-readline-interface.js @@ -206,7 +206,7 @@ function isWarned(emitter) { callCount++; if (ch) assert(!key.code); assert.equal(key.sequence, remainingKeypresses.shift()); - }; + } readline.emitKeypressEvents(fi); fi.on('keypress', keypressListener); fi.emit('data', keypresses.join('')); @@ -315,5 +315,34 @@ function isWarned(emitter) { }) }); + // Test readline support history + function testHistory() { + var history = ['foo', 'bar']; + var fi = new FakeInput(); + var rl = new readline.Interface({ + input: fi, + output: null, + terminal: true, + history: history + }); + + // Test history size + rl.setHistorySize(2); + fi.emit('data', 'baz\n'); + fi.emit('data', 'bug\n'); + assert.deepEqual(rl.history, history); + assert.deepEqual(rl.history, ['bug', 'baz']); + + // Increase history size + rl.setHistorySize(Infinity); + fi.emit('data', 'foo\n'); + fi.emit('data', 'bar\n'); + assert.deepEqual(rl.history, history); + assert.deepEqual(rl.history, ['bar', 'foo', 'bug', 'baz']); + + rl.close(); + } + + testHistory(); });