Skip to content

Commit b5e62c9

Browse files
committed
readline: support history + repl changes
1 parent 50ac4b7 commit b5e62c9

File tree

5 files changed

+120
-19
lines changed

5 files changed

+120
-19
lines changed

doc/api/readline.markdown

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ program to gracefully exit:
1313

1414
var rl = readline.createInterface({
1515
input: process.stdin,
16-
output: process.stdout
16+
output: process.stdout,
17+
history: ['foo', 'bar', ...]
1718
});
1819

1920
rl.question("What do you think of node.js? ", function(answer) {
@@ -38,6 +39,8 @@ the following values:
3839
- `terminal` - pass `true` if the `input` and `output` streams should be
3940
treated like a TTY, and have ANSI/VT100 escape codes written to it.
4041
Defaults to checking `isTTY` on the `output` stream upon instantiation.
42+
43+
- `history` - pass history(Array) to start the cli with previous history (Optional).
4144

4245
The `completer` function is given the current line entered by the user, and
4346
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:
7073
var readline = require('readline');
7174
var rl = readline.createInterface({
7275
input: process.stdin,
73-
output: process.stdout
76+
output: process.stdout,
77+
// start the cli with previous history
78+
history: ['foo', 'bar', ...]
7479
});
7580

7681
Once you have a readline instance, you most commonly listen for the
77-
`"line"` event.
82+
`"line"` and `"close"` events:
83+
84+
rl
85+
.on('line', function(line) {
86+
// ...
87+
lr.pause();
88+
fs.appendFile('path/to/history', line, rl.resume.bind(rl));
89+
})
90+
.on('close', function() {
91+
var history = rl.history;
92+
// save history and exit()
93+
});
7894

7995
If `terminal` is `true` for this instance then the `output` stream will get
8096
the best compatibility if it defines an `output.columns` property, and fires
@@ -91,6 +107,10 @@ stream.
91107
Sets the prompt, for example when you run `node` on the command line, you see
92108
`> `, which is node's prompt.
93109

110+
### rl.setHistorySize(size)
111+
112+
Sets the length of history size, the default is 30.
113+
94114
### rl.prompt([preserveCursor])
95115

96116
Readies readline for input from the user, putting the current `setPrompt`

lib/readline.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@
55
// * http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
66

77
'use strict';
8-
9-
const kHistorySize = 30;
10-
118
const util = require('util');
129
const inherits = util.inherits;
1310
const EventEmitter = require('events').EventEmitter;
@@ -24,9 +21,9 @@ exports.createInterface = function(input, output, completer, terminal) {
2421
};
2522

2623

27-
function Interface(input, output, completer, terminal) {
24+
function Interface(input, output, completer, terminal, history) {
2825
if (!(this instanceof Interface)) {
29-
return new Interface(input, output, completer, terminal);
26+
return new Interface(input, output, completer, terminal, history);
3027
}
3128

3229
this._sawReturn = false;
@@ -38,9 +35,9 @@ function Interface(input, output, completer, terminal) {
3835
output = input.output;
3936
completer = input.completer;
4037
terminal = input.terminal;
38+
history = input.history;
4139
input = input.input;
4240
}
43-
4441
completer = completer || function() { return []; };
4542

4643
if (!util.isFunction(completer)) {
@@ -120,7 +117,8 @@ function Interface(input, output, completer, terminal) {
120117
// Cursor position on the line.
121118
this.cursor = 0;
122119

123-
this.history = [];
120+
this._historySize = 30;
121+
this.history = history || [];
124122
this.historyIndex = -1;
125123

126124
if (!util.isNullOrUndefined(output))
@@ -151,6 +149,10 @@ Interface.prototype.setPrompt = function(prompt) {
151149
this._prompt = prompt;
152150
};
153151

152+
Interface.prototype.setHistorySize = function(historySize) {
153+
this._historySize = historySize;
154+
};
155+
154156

155157
Interface.prototype._setRawMode = function(mode) {
156158
if (util.isFunction(this.input.setRawMode)) {
@@ -210,7 +212,7 @@ Interface.prototype._addHistory = function() {
210212
this.history.unshift(this.line);
211213

212214
// Only store so many
213-
if (this.history.length > kHistorySize) this.history.pop();
215+
if (this.history.length > this._historySize) this.history.pop();
214216
}
215217

216218
this.historyIndex = -1;

lib/repl.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,8 @@ function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) {
173173
self.inputStream,
174174
self.outputStream,
175175
complete,
176-
options.terminal
176+
options.terminal,
177+
options.history
177178
]);
178179

179180
self.setPrompt(!util.isUndefined(prompt) ? prompt : '> ');
@@ -310,7 +311,7 @@ function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) {
310311

311312
// Display prompt again
312313
self.displayPrompt();
313-
};
314+
}
314315
});
315316

316317
self.on('SIGCONT', function() {
@@ -460,7 +461,7 @@ REPLServer.prototype.complete = function(line, callback) {
460461
var completeOn, match, filter, i, group, c;
461462

462463
// REPL commands (e.g. ".break").
463-
var match = null;
464+
match = null;
464465
match = line.match(/^\s*(\.\w*)$/);
465466
if (match) {
466467
completionGroups.push(Object.keys(this.commands));
@@ -478,7 +479,7 @@ REPLServer.prototype.complete = function(line, callback) {
478479

479480
completeOn = match[1];
480481
var subdir = match[2] || '';
481-
var filter = match[1];
482+
filter = match[1];
482483
var dir, files, f, name, base, ext, abs, subfiles, s;
483484
group = [];
484485
var paths = module.paths.concat(require('module').globalPaths);

src/node.js

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,21 +116,70 @@
116116

117117
// If -i or --interactive were passed, or stdin is a TTY.
118118
if (process._forceRepl || NativeModule.require('tty').isatty(0)) {
119+
var history;
120+
var path = NativeModule.require('path');
121+
var fs = NativeModule.require('fs');
122+
// This code comes from Sindre Sorhus `user-home` module
123+
// https://github.com/sindresorhus/user-home
124+
var home = (function getUserHome() {
125+
var env = process.env;
126+
var home = env.HOME;
127+
var user = env.LOGNAME || env.USER || env.LNAME || env.USERNAME;
128+
129+
if (process.platform === 'win32') {
130+
return env.USERPROFILE || env.HOMEDRIVE + env.HOMEPATH || home || null;
131+
} else if (process.platform === 'darwin') {
132+
return home || (user ? '/Users/' + user : null);
133+
} else if (process.platform === 'linux') {
134+
return home ||
135+
(user ? (process.getuid() === 0 ? '/root' : '/home/' + user) : null);
136+
}
137+
return home || null;
138+
})()
139+
var HISTORY_PATH = home ? path.join(home,'.iojs_history') : null;
140+
119141
// REPL
120142
var opts = {
121143
useGlobal: true,
122144
ignoreUndefined: false
123145
};
146+
147+
// If we got user-home dir
148+
if(HISTORY_PATH) {
149+
// Get history if exist
150+
try {
151+
history = fs.readFileSync(HISTORY_PATH, 'utf8')
152+
.replace(/\n$/, '') // Ignore the last \n
153+
.split('\n');
154+
} catch(e) {
155+
history = [];
156+
}
157+
opts.history = history;
158+
}
159+
124160
if (parseInt(process.env['NODE_NO_READLINE'], 10)) {
125161
opts.terminal = false;
126162
}
127163
if (parseInt(process.env['NODE_DISABLE_COLORS'], 10)) {
128164
opts.useColors = false;
129165
}
130166
var repl = Module.requireRepl().start(opts);
131-
repl.on('exit', function() {
132-
process.exit();
133-
});
167+
repl
168+
.on('line', function(line) {
169+
if(HISTORY_PATH)
170+
try {
171+
fs.appendFileSync(HISTORY_PATH, line + '\n');
172+
} catch(e) {}
173+
})
174+
.on('exit', function() {
175+
if(HISTORY_PATH && repl.history)
176+
try {
177+
fs.writeFileSync(HISTORY_PATH, repl.history
178+
.join('\n'));
179+
} catch(e) {}
180+
181+
process.exit();
182+
});
134183

135184
} else {
136185
// Read all of stdin - execute it.

test/parallel/test-readline-interface.js

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ function isWarned(emitter) {
206206
callCount++;
207207
if (ch) assert(!key.code);
208208
assert.equal(key.sequence, remainingKeypresses.shift());
209-
};
209+
}
210210
readline.emitKeypressEvents(fi);
211211
fi.on('keypress', keypressListener);
212212
fi.emit('data', keypresses.join(''));
@@ -315,5 +315,34 @@ function isWarned(emitter) {
315315
})
316316
});
317317

318+
// Test readline support history
319+
function testHistory() {
320+
var history = ['foo', 'bar'];
321+
var fi = new FakeInput();
322+
var rl = new readline.Interface({
323+
input: fi,
324+
output: null,
325+
terminal: true,
326+
history: history
327+
});
328+
329+
// Test history size
330+
rl.setHistorySize(2);
331+
fi.emit('data', 'baz\n');
332+
fi.emit('data', 'bug\n');
333+
assert.deepEqual(rl.history, history);
334+
assert.deepEqual(rl.history, ['bug', 'baz']);
335+
336+
// Increase history size
337+
rl.setHistorySize(Infinity);
338+
fi.emit('data', 'foo\n');
339+
fi.emit('data', 'bar\n');
340+
assert.deepEqual(rl.history, history);
341+
assert.deepEqual(rl.history, ['bar', 'foo', 'bug', 'baz']);
342+
343+
rl.close();
344+
}
345+
346+
testHistory();
318347
});
319348

0 commit comments

Comments
 (0)