From 396f23eb9119e5d7d0823dca42b84aaca26e3b3f Mon Sep 17 00:00:00 2001 From: Denis Bardadym Date: Thu, 15 Jan 2015 14:54:15 +0300 Subject: [PATCH] repl: save repl session in file Extract history in separate function to manage it all in one place as a result add subclass that write entries to file (by default ~/.iojs_history). Also allow to disable history write via env variable NODE_DISABLE_HISTORY. --- lib/readline.js | 115 ++++++++++++++++++----- lib/repl.js | 3 +- src/node.js | 6 +- test/parallel/test-repl-timeout-throw.js | 1 + 4 files changed, 97 insertions(+), 28 deletions(-) diff --git a/lib/readline.js b/lib/readline.js index bf53b02ffd0484..ddf238297f5ff4 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -12,6 +12,8 @@ var util = require('util'); var inherits = require('util').inherits; var EventEmitter = require('events').EventEmitter; +var fs = require('fs'); +var path = require('path'); exports.createInterface = function(input, output, completer, terminal) { var rl; @@ -24,7 +26,7 @@ exports.createInterface = function(input, output, completer, terminal) { }; -function Interface(input, output, completer, terminal) { +function Interface(input, output, completer, terminal, saveHistory) { if (!(this instanceof Interface)) { return new Interface(input, output, completer, terminal); } @@ -120,8 +122,9 @@ function Interface(input, output, completer, terminal) { // Cursor position on the line. this.cursor = 0; - this.history = []; - this.historyIndex = -1; + this.history = saveHistory ? + new ReadlineFileHistory({ maxEntries: kHistorySize }) : + new ReadlineHistory({ maxEntries: kHistorySize }); if (!util.isNullOrUndefined(output)) output.on('resize', onresize); @@ -205,16 +208,8 @@ Interface.prototype._writeToOutput = function _writeToOutput(stringToWrite) { Interface.prototype._addHistory = function() { if (this.line.length === 0) return ''; - - if (this.history.length === 0 || this.history[0] !== this.line) { - this.history.unshift(this.line); - - // Only store so many - if (this.history.length > kHistorySize) this.history.pop(); - } - - this.historyIndex = -1; - return this.history[0]; + this.history.addEntry(this.line); + return this.line; }; @@ -535,27 +530,20 @@ Interface.prototype._line = function() { Interface.prototype._historyNext = function() { - if (this.historyIndex > 0) { - this.historyIndex--; - this.line = this.history[this.historyIndex]; + var hline = this.history.nextEntry(); + if (this.line != hline) { + this.line = hline; this.cursor = this.line.length; // set cursor to end of line. this._refreshLine(); - - } else if (this.historyIndex === 0) { - this.historyIndex = -1; - this.cursor = 0; - this.line = ''; - this._refreshLine(); } }; Interface.prototype._historyPrev = function() { - if (this.historyIndex + 1 < this.history.length) { - this.historyIndex++; - this.line = this.history[this.historyIndex]; + var hline = this.history.prevEntry(); + if (this.line != hline) { + this.line = hline; this.cursor = this.line.length; // set cursor to end of line. - this._refreshLine(); } }; @@ -873,6 +861,81 @@ Interface.prototype._ttyWrite = function(s, key) { exports.Interface = Interface; +function ReadlineHistory(options) { + this._maxEntries = options.maxEntries; + this._index = 0; + this._entries = []; +} + +ReadlineHistory.prototype.addEntry = function(line) { + var added = false; + if (line && this._entries[this._entries.length - 1] !== line) { + if (this._maxEntries == this._entries.length) { + this._entries.shift(); + } + this._entries.push(line); + added = true; + } + this._index = this._entries.length; + return added; +}; + +ReadlineHistory.prototype.nextEntry = function() { + this._index += 1; + if (this._index >= this._entries.length) { + this._index = this._entries.length; + return ''; + } + return this._entries[this._index]; +}; + +ReadlineHistory.prototype.prevEntry = function() { + this._index -= 1; + if (this._index < 0) this._index = 0; + return this._entries[this._index]; +}; + +function ReadlineFileHistory(options) { + this._filePath = options.filePath || + path.resolve( + process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE, + '.iojs_history'); + this._fileEncoding = 'utf8'; + + ReadlineHistory.call(this, options); + + this._load(); +} + +inherits(ReadlineFileHistory, ReadlineHistory); + +ReadlineFileHistory.prototype._load = function() { + try { + var lines = fs.readFileSync(this._filePath, {encoding: this._fileEncoding}) + .split('\n'); + lines.pop();//last line always empty + if (lines.length > this._maxEntries) { + lines = lines.slice(lines.length - this._maxEntries); + fs.writeFileSync(this._filePath, + lines.join('\n') + '\n', + {encoding: this._fileEncoding}); + } + this._entries = lines; + this._index = this._entries.length; + } catch (e) { + //ignore errors, just init empty history + this._entries = []; + this._index = 0; + } +}; + +ReadlineFileHistory.prototype.addEntry = function(line) { + if (ReadlineHistory.prototype.addEntry.call(this, line)) { + fs.appendFileSync(this._filePath, + line + '\n', + {encoding: this._fileEncoding}); + } +}; /** diff --git a/lib/repl.js b/lib/repl.js index b9ce053c915d7f..7317c0074ddb2a 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -171,7 +171,8 @@ function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) { self.inputStream, self.outputStream, complete, - options.terminal + options.terminal, + options.saveHistory ]); self.setPrompt(!util.isUndefined(prompt) ? prompt : '> '); diff --git a/src/node.js b/src/node.js index e6056797b2a344..3bbffebce8d04b 100644 --- a/src/node.js +++ b/src/node.js @@ -119,7 +119,8 @@ // REPL var opts = { useGlobal: true, - ignoreUndefined: false + ignoreUndefined: false, + saveHistory: true }; if (parseInt(process.env['NODE_NO_READLINE'], 10)) { opts.terminal = false; @@ -127,6 +128,9 @@ if (parseInt(process.env['NODE_DISABLE_COLORS'], 10)) { opts.useColors = false; } + if (parseInt(process.env['NODE_DISABLE_HISTORY'], 10)) { + opts.saveHistory = false; + } var repl = Module.requireRepl().start(opts); repl.on('exit', function() { process.exit(); diff --git a/test/parallel/test-repl-timeout-throw.js b/test/parallel/test-repl-timeout-throw.js index 2febf2e3ce0565..64bf3f043758a0 100644 --- a/test/parallel/test-repl-timeout-throw.js +++ b/test/parallel/test-repl-timeout-throw.js @@ -1,3 +1,4 @@ +process.env.NODE_DISABLE_HISTORY = 1; var assert = require('assert'); var common = require('../common.js');