UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

192 lines (165 loc) 4.57 kB
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/repl/history.js import { Interface } from "nstdlib/lib/readline"; import * as path from "nstdlib/lib/path"; import * as fs from "nstdlib/lib/fs"; import * as os from "nstdlib/lib/os"; import * as permission from "nstdlib/lib/internal/process/permission"; import { clearTimeout, setTimeout } from "nstdlib/lib/timers"; const noop = Function.prototype; // XXX(chrisdickinson): The 15ms debounce value is somewhat arbitrary. // The debounce is to guard against code pasted into the REPL. const kDebounceHistoryMS = 15; export default setupHistory; function _writeToOutput(repl, message) { repl._writeToOutput(message); repl._refreshLine(); } function setupHistory(repl, historyPath, ready) { // Empty string disables persistent history if (typeof historyPath === "string") historyPath = String.prototype.trim.call(historyPath); if (historyPath === "") { repl._historyPrev = _replHistoryMessage; return ready(null, repl); } if (!historyPath) { try { historyPath = path.join(os.homedir(), ".node_repl_history"); } catch (err) { _writeToOutput( repl, "\nError: Could not get the home directory.\n" + "REPL session history will not be persisted.\n", ); { /* debug */ } repl._historyPrev = _replHistoryMessage; return ready(null, repl); } } if ( permission.isEnabled() && permission.has("fs.write", historyPath) === false ) { _writeToOutput( repl, "\nAccess to FileSystemWrite is restricted.\n" + "REPL session history will not be persisted.\n", ); return ready(null, repl); } let timer = null; let writing = false; let pending = false; repl.pause(); // History files are conventionally not readable by others: // https://github.com/nodejs/node/issues/3392 // https://github.com/nodejs/node/pull/3394 fs.open(historyPath, "a+", 0o0600, oninit); function oninit(err, hnd) { if (err) { // Cannot open history file. // Don't crash, just don't persist history. _writeToOutput( repl, "\nError: Could not open history file.\n" + "REPL session history will not be persisted.\n", ); { /* debug */ } 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 = RegExp.prototype[Symbol.split].call( /[\n\r]+/, data, repl.historySize, ); } else { repl.history = []; } fs.open(historyPath, "r+", onhandle); } function onhandle(err, hnd) { if (err) { return ready(err); } fs.ftruncate(hnd, 0, (err) => { repl._historyHandle = hnd; repl.on("line", online); repl.once("exit", onexit); // Reading the file data out erases it repl.once("flushHistory", function () { repl.resume(); ready(null, repl); }); flushHistory(); }); } // ------ history listeners ------ function online(line) { repl._flushing = true; if (timer) { clearTimeout(timer); } timer = setTimeout(flushHistory, kDebounceHistoryMS); } function flushHistory() { timer = null; if (writing) { pending = true; return; } writing = true; const historyData = Array.prototype.join.call(repl.history, 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 onexit() { if (repl._flushing) { repl.once("flushHistory", onexit); return; } repl.off("line", online); fs.close(repl._historyHandle, noop); } } function _replHistoryMessage() { if (this.history.length === 0) { _writeToOutput( this, "\nPersistent history support disabled. " + "Set the NODE_REPL_HISTORY environment\nvariable to " + "a valid, user-writable path to enable.\n", ); } this._historyPrev = Interface.prototype._historyPrev; return this._historyPrev(); }