|
|
|
'use strict';
|
|
|
|
|
|
|
|
const Interface = require('readline').Interface;
|
|
|
|
const REPL = require('repl');
|
|
|
|
const path = require('path');
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
// XXX(chrisdickinson): hack to make sure that the internal debugger
|
|
|
|
// uses the original repl.
|
|
|
|
function replStart() {
|
|
|
|
return REPL.start.apply(REPL, arguments);
|
|
|
|
}
|
|
|
|
|
|
|
|
function createRepl(env, cb) {
|
|
|
|
const opts = {
|
|
|
|
ignoreUndefined: false,
|
|
|
|
terminal: process.stdout.isTTY,
|
|
|
|
useGlobal: true
|
|
|
|
};
|
|
|
|
|
|
|
|
if (parseInt(env.NODE_NO_READLINE)) {
|
|
|
|
opts.terminal = false;
|
|
|
|
}
|
|
|
|
if (parseInt(env.NODE_DISABLE_COLORS)) {
|
|
|
|
opts.useColors = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
opts.replMode = {
|
|
|
|
'strict': REPL.REPL_MODE_STRICT,
|
|
|
|
'sloppy': REPL.REPL_MODE_SLOPPY,
|
|
|
|
'magic': REPL.REPL_MODE_MAGIC
|
|
|
|
}[String(env.NODE_REPL_MODE).toLowerCase().trim()];
|
|
|
|
|
|
|
|
if (opts.replMode === undefined) {
|
|
|
|
opts.replMode = REPL.REPL_MODE_MAGIC;
|
|
|
|
}
|
|
|
|
|
|
|
|
const historySize = Number(env.NODE_REPL_HISTORY_SIZE);
|
|
|
|
if (!isNaN(historySize) && historySize > 0) {
|
|
|
|
opts.historySize = historySize;
|
|
|
|
} else {
|
|
|
|
// XXX(chrisdickinson): set here to avoid affecting existing applications
|
|
|
|
// using repl instances.
|
|
|
|
opts.historySize = 1000;
|
|
|
|
}
|
|
|
|
|
|
|
|
const repl = REPL.start(opts);
|
|
|
|
if (opts.terminal && env.NODE_REPL_HISTORY_FILE) {
|
|
|
|
return setupHistory(repl, env.NODE_REPL_HISTORY_FILE, cb);
|
|
|
|
}
|
|
|
|
repl._historyPrev = _replHistoryMessage;
|
|
|
|
cb(null, repl);
|
|
|
|
}
|
|
|
|
|
|
|
|
function setupHistory(repl, historyPath, ready) {
|
|
|
|
const fs = require('fs');
|
|
|
|
var timer = null;
|
|
|
|
var writing = false;
|
|
|
|
var pending = false;
|
|
|
|
repl.pause();
|
|
|
|
fs.open(historyPath, 'a+', oninit);
|
|
|
|
|
|
|
|
function oninit(err, hnd) {
|
|
|
|
if (err) {
|
|
|
|
return ready(err);
|
|
|
|
}
|
|
|
|
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) {
|
|
|
|
try {
|
|
|
|
repl.history = JSON.parse(data);
|
|
|
|
if (!Array.isArray(repl.history)) {
|
|
|
|
throw new Error('Expected array, got ' + typeof repl.history);
|
|
|
|
}
|
|
|
|
repl.history.slice(-repl.historySize);
|
|
|
|
} catch (err) {
|
|
|
|
return ready(
|
|
|
|
new Error(`Could not parse history data in ${historyPath}.`));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 = JSON.stringify(repl.history, null, 2);
|
|
|
|
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_FILE environment variable to ' +
|
|
|
|
'a valid, user-writable path to enable.\n'
|
|
|
|
);
|
|
|
|
this._refreshLine();
|
|
|
|
}
|
|
|
|
this._historyPrev = Interface.prototype._historyPrev;
|
|
|
|
return this._historyPrev();
|
|
|
|
}
|