diff --git a/lib/internal/repl.js b/lib/internal/repl.js index 371446a83bd4fd..4ec6ae3c156407 100644 --- a/lib/internal/repl.js +++ b/lib/internal/repl.js @@ -1,19 +1,10 @@ 'use strict'; -const Interface = require('readline').Interface; const REPL = require('repl'); -const path = require('path'); -const fs = require('fs'); -const os = require('os'); -const debug = require('util').debuglog('repl'); module.exports = Object.create(REPL); module.exports.createInternalRepl = createRepl; -// XXX(chrisdickinson): The 15ms debounce value is somewhat arbitrary. -// The debounce is to guard against code pasted into the REPL. -const kDebounceHistoryMS = 15; - function createRepl(env, opts, cb) { if (typeof opts === 'function') { cb = opts; @@ -55,175 +46,11 @@ function createRepl(env, opts, cb) { } const repl = REPL.start(opts); + if (opts.terminal) { - return setupHistory(repl, env.NODE_REPL_HISTORY, - env.NODE_REPL_HISTORY_FILE, cb); + return repl.setupHistory(env.NODE_REPL_HISTORY, + env.NODE_REPL_HISTORY_FILE, cb); } - repl._historyPrev = _replHistoryMessage; cb(null, repl); } - -function setupHistory(repl, historyPath, oldHistoryPath, ready) { - // Empty string disables persistent history. - - if (typeof historyPath === 'string') - historyPath = historyPath.trim(); - - if (historyPath === '') { - repl._historyPrev = _replHistoryMessage; - return ready(null, repl); - } - - if (!historyPath) { - try { - historyPath = path.join(os.homedir(), '.node_repl_history'); - } catch (err) { - repl._writeToOutput('\nError: Could not get the home directory.\n' + - 'REPL session history will not be persisted.\n'); - repl._refreshLine(); - - debug(err.stack); - repl._historyPrev = _replHistoryMessage; - return ready(null, repl); - } - } - - var timer = null; - var writing = false; - var pending = false; - repl.pause(); - fs.open(historyPath, 'a+', oninit); - - function oninit(err, hnd) { - if (err) { - // Cannot open history file. - // Don't crash, just don't persist history. - repl._writeToOutput('\nError: Could not open history file.\n' + - 'REPL session history will not be persisted.\n'); - repl._refreshLine(); - debug(err.stack); - - repl._historyPrev = _replHistoryMessage; - repl.resume(); - return ready(null, repl); - } - fs.close(hnd, onclose); - } - - function onclose(err) { - if (err) { - return ready(err); - } - fs.readFile(historyPath, 'utf8', onread); - } - - function onread(err, data) { - if (err) { - return ready(err); - } - - if (data) { - repl.history = data.split(/[\n\r]+/, repl.historySize); - } else if (oldHistoryPath === historyPath) { - // If pre-v3.0, the user had set NODE_REPL_HISTORY_FILE to - // ~/.node_repl_history, warn the user about it and proceed. - repl._writeToOutput( - '\nThe old repl history file has the same name and location as ' + - `the new one i.e., ${historyPath} and is empty.\nUsing it as is.\n`); - repl._refreshLine(); - - } else if (oldHistoryPath) { - // Grab data from the older pre-v3.0 JSON NODE_REPL_HISTORY_FILE format. - repl._writeToOutput( - '\nConverting old JSON repl history to line-separated history.\n' + - `The new repl history file can be found at ${historyPath}.\n`); - repl._refreshLine(); - - try { - // Pre-v3.0, repl history was stored as JSON. - // Try and convert it to line separated history. - const oldReplJSONHistory = fs.readFileSync(oldHistoryPath, 'utf8'); - - // Only attempt to use the history if there was any. - if (oldReplJSONHistory) repl.history = JSON.parse(oldReplJSONHistory); - - if (!Array.isArray(repl.history)) { - throw new Error('Expected array, got ' + typeof repl.history); - } - repl.history = repl.history.slice(0, repl.historySize); - } catch (err) { - if (err.code !== 'ENOENT') { - return ready( - new Error(`Could not parse history data in ${oldHistoryPath}.`)); - } - } - } - - fs.open(historyPath, 'w', onhandle); - } - - function onhandle(err, hnd) { - if (err) { - return ready(err); - } - repl._historyHandle = hnd; - repl.on('line', online); - - // reading the file data out erases it - repl.once('flushHistory', function() { - repl.resume(); - ready(null, repl); - }); - flushHistory(); - } - - // ------ history listeners ------ - function online() { - repl._flushing = true; - - if (timer) { - clearTimeout(timer); - } - - timer = setTimeout(flushHistory, kDebounceHistoryMS); - } - - function flushHistory() { - timer = null; - if (writing) { - pending = true; - return; - } - writing = true; - const historyData = repl.history.join(os.EOL); - fs.write(repl._historyHandle, historyData, 0, 'utf8', onwritten); - } - - function onwritten(err, data) { - writing = false; - if (pending) { - pending = false; - online(); - } else { - repl._flushing = Boolean(timer); - if (!repl._flushing) { - repl.emit('flushHistory'); - } - } - } -} - - -function _replHistoryMessage() { - if (this.history.length === 0) { - this._writeToOutput( - '\nPersistent history support disabled. ' + - 'Set the NODE_REPL_HISTORY environment\nvariable to ' + - 'a valid, user-writable path to enable.\n' - ); - this._refreshLine(); - } - this._historyPrev = Interface.prototype._historyPrev; - return this._historyPrev(); -} diff --git a/lib/repl.js b/lib/repl.js index ab9f1bf56dce1e..8bb75003f3f917 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -34,7 +34,7 @@ const Console = require('console').Console; const Module = require('module'); const domain = require('domain'); const debug = util.debuglog('repl'); - +const os = require('os'); const parentModule = module; const replMap = new WeakMap(); @@ -1141,6 +1141,165 @@ REPLServer.prototype.convertToContext = function(cmd) { return cmd; }; +REPLServer.prototype.setupHistory = function(historyPath, oldHistoryPath, cb) { + const repl = this; + + // XXX(chrisdickinson): The 15ms debounce value is somewhat arbitrary. + // The debounce is to guard against code pasted into the REPL. + const kDebounceHistoryMS = 15; + + if (typeof oldHistoryPath === 'function') { + cb = oldHistoryPath; + oldHistoryPath = null; + } + + if (typeof historyPath === 'string') + historyPath = historyPath.trim(); + + // Empty string disables persistent history. + if (historyPath === '') { + repl._historyPrev = _replHistoryMessage; + return cb(null, repl); + } + + if (!historyPath) { + try { + historyPath = path.join(os.homedir(), '.node_repl_history'); + } catch (err) { + repl._writeToOutput('\nError: Could not get the home directory.\n' + + 'REPL session history will not be persisted.\n'); + repl._refreshLine(); + + debug(err.stack); + repl._historyPrev = _replHistoryMessage; + return cb(null, repl); + } + } + + var timer = null; + var writing = false; + var pending = false; + repl.pause(); + fs.open(historyPath, 'a+', oninit); + + function oninit(err, hnd) { + if (err) { + // Cannot open history file. + // Don't crash, just don't persist history. + repl._writeToOutput('\nError: Could not open history file.\n' + + 'REPL session history will not be persisted.\n'); + repl._refreshLine(); + debug(err.stack); + + repl._historyPrev = _replHistoryMessage; + repl.resume(); + return cb(null, repl); + } + fs.close(hnd, onclose); + } + + function onclose(err) { + if (err) { + return cb(err); + } + fs.readFile(historyPath, 'utf8', onread); + } + + function onread(err, data) { + if (err) { + return cb(err); + } + + if (data) { + repl.history = data.split(/[\n\r]+/, repl.historySize); + } else if (oldHistoryPath === historyPath) { + // If pre-v3.0, the user had set NODE_REPL_HISTORY_FILE to + // ~/.node_repl_history, warn the user about it and proceed. + repl._writeToOutput( + '\nThe old repl history file has the same name and location as ' + + `the new one i.e., ${historyPath} and is empty.\nUsing it as is.\n`); + repl._refreshLine(); + + } else if (oldHistoryPath) { + // Grab data from the older pre-v3.0 JSON NODE_REPL_HISTORY_FILE format. + repl._writeToOutput( + '\nConverting old JSON repl history to line-separated history.\n' + + `The new repl history file can be found at ${historyPath}.\n`); + repl._refreshLine(); + + try { + // Pre-v3.0, repl history was stored as JSON. + // Try and convert it to line separated history. + const oldReplJSONHistory = fs.readFileSync(oldHistoryPath, 'utf8'); + + // Only attempt to use the history if there was any. + if (oldReplJSONHistory) repl.history = JSON.parse(oldReplJSONHistory); + + if (!Array.isArray(repl.history)) { + throw new Error('Expected array, got ' + typeof repl.history); + } + repl.history = repl.history.slice(0, repl.historySize); + } catch (err) { + if (err.code !== 'ENOENT') { + return cb( + new Error(`Could not parse history data in ${oldHistoryPath}.`)); + } + } + } + + fs.open(historyPath, 'w', onhandle); + } + + function onhandle(err, hnd) { + if (err) { + return cb(err); + } + repl._historyHandle = hnd; + repl.on('line', online); + + // reading the file data out erases it + repl.once('flushHistory', function() { + repl.resume(); + cb(null, repl); + }); + flushHistory(); + } + + // ------ history listeners ------ + function online() { + repl._flushing = true; + + if (timer) { + clearTimeout(timer); + } + + timer = setTimeout(flushHistory, kDebounceHistoryMS); + } + + function flushHistory() { + timer = null; + if (writing) { + pending = true; + return; + } + writing = true; + const historyData = repl.history.join(os.EOL); + fs.write(repl._historyHandle, historyData, 0, 'utf8', onwritten); + } + + function onwritten(err, data) { + writing = false; + if (pending) { + pending = false; + online(); + } else { + repl._flushing = Boolean(timer); + if (!repl._flushing) { + repl.emit('flushHistory'); + } + } + } +}; // If the error is that we've unexpectedly ended the input, // then let the user try to recover by adding more input. @@ -1164,3 +1323,16 @@ function Recoverable(err) { this.err = err; } inherits(Recoverable, SyntaxError); + +function _replHistoryMessage() { + if (this.history.length === 0) { + this._writeToOutput( + '\nPersistent history support disabled. ' + + 'Set the NODE_REPL_HISTORY environment\nvariable to ' + + 'a valid, user-writable path to enable.\n' + ); + this._refreshLine(); + } + this._historyPrev = Interface.prototype._historyPrev; + return this._historyPrev(); +}