Skip to content

Commit

Permalink
readline: support history + repl changes
Browse files Browse the repository at this point in the history
  • Loading branch information
a8m committed Jan 31, 2015
1 parent 50ac4b7 commit b5e62c9
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 19 deletions.
26 changes: 23 additions & 3 deletions doc/api/readline.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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`
Expand Down
18 changes: 10 additions & 8 deletions lib/readline.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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)) {
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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;
Expand Down
9 changes: 5 additions & 4 deletions lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 : '> ');
Expand Down Expand Up @@ -310,7 +311,7 @@ function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) {

// Display prompt again
self.displayPrompt();
};
}
});

self.on('SIGCONT', function() {
Expand Down Expand Up @@ -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));
Expand All @@ -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);
Expand Down
55 changes: 52 additions & 3 deletions src/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,21 +116,70 @@

// 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;
}
if (parseInt(process.env['NODE_DISABLE_COLORS'], 10)) {
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.
Expand Down
31 changes: 30 additions & 1 deletion test/parallel/test-readline-interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(''));
Expand Down Expand Up @@ -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();
});

0 comments on commit b5e62c9

Please sign in to comment.