| // Copyright Joyent, Inc. and other Node contributors. |
| // |
| // Permission is hereby granted, free of charge, to any person obtaining a |
| // copy of this software and associated documentation files (the |
| // "Software"), to deal in the Software without restriction, including |
| // without limitation the rights to use, copy, modify, merge, publish, |
| // distribute, sublicense, and/or sell copies of the Software, and to permit |
| // persons to whom the Software is furnished to do so, subject to the |
| // following conditions: |
| // |
| // The above copyright notice and this permission notice shall be included |
| // in all copies or substantial portions of the Software. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN |
| // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
| // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
| // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE |
| // USE OR OTHER DEALINGS IN THE SOFTWARE. |
| |
| /* A repl library that you can include in your own code to get a runtime |
| * interface to your program. |
| * |
| * var repl = require("repl"); |
| * // start repl on stdin |
| * repl.start("prompt> "); |
| * |
| * // listen for unix socket connections and start repl on them |
| * net.createServer(function(socket) { |
| * repl.start("node via Unix socket> ", socket); |
| * }).listen("/tmp/node-repl-sock"); |
| * |
| * // listen for TCP socket connections and start repl on them |
| * net.createServer(function(socket) { |
| * repl.start("node via TCP socket> ", socket); |
| * }).listen(5001); |
| * |
| * // expose foo to repl context |
| * repl.start("node > ").context.foo = "stdin is fun"; |
| */ |
| |
| 'use strict'; |
| |
| const { |
| builtinLibs, |
| makeRequireFunction, |
| addBuiltinLibsToObject |
| } = require('internal/modules/cjs/helpers'); |
| const { |
| isIdentifierStart, |
| isIdentifierChar |
| } = require('internal/deps/acorn/dist/acorn'); |
| const internalUtil = require('internal/util'); |
| const util = require('util'); |
| const { inherits } = util; |
| const Stream = require('stream'); |
| const vm = require('vm'); |
| const path = require('path'); |
| const fs = require('fs'); |
| const { Interface } = require('readline'); |
| const { Console } = require('console'); |
| const CJSModule = require('internal/modules/cjs/loader'); |
| const domain = require('domain'); |
| const debug = util.debuglog('repl'); |
| const { |
| ERR_CANNOT_WATCH_SIGINT, |
| ERR_INVALID_ARG_TYPE, |
| ERR_INVALID_REPL_EVAL_CONFIG, |
| ERR_SCRIPT_EXECUTION_INTERRUPTED |
| } = require('internal/errors').codes; |
| const { sendInspectorCommand } = require('internal/util/inspector'); |
| const { experimentalREPLAwait } = process.binding('config'); |
| const { isRecoverableError } = require('internal/repl/recoverable'); |
| const { |
| getOwnNonIndexProperties, |
| propertyFilter: { |
| ALL_PROPERTIES, |
| SKIP_SYMBOLS |
| }, |
| startSigintWatchdog, |
| stopSigintWatchdog |
| } = internalBinding('util'); |
| |
| // Lazy-loaded. |
| let processTopLevelAwait; |
| |
| const parentModule = module; |
| const replMap = new WeakMap(); |
| |
| const GLOBAL_OBJECT_PROPERTIES = [ |
| 'NaN', 'Infinity', 'undefined', 'eval', 'parseInt', 'parseFloat', 'isNaN', |
| 'isFinite', 'decodeURI', 'decodeURIComponent', 'encodeURI', |
| 'encodeURIComponent', 'Object', 'Function', 'Array', 'String', 'Boolean', |
| 'Number', 'Date', 'RegExp', 'Error', 'EvalError', 'RangeError', |
| 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError', 'Math', 'JSON' |
| ]; |
| const GLOBAL_OBJECT_PROPERTY_MAP = {}; |
| for (var n = 0; n < GLOBAL_OBJECT_PROPERTIES.length; n++) { |
| GLOBAL_OBJECT_PROPERTY_MAP[GLOBAL_OBJECT_PROPERTIES[n]] = |
| GLOBAL_OBJECT_PROPERTIES[n]; |
| } |
| const kBufferedCommandSymbol = Symbol('bufferedCommand'); |
| const kContextId = Symbol('contextId'); |
| |
| try { |
| // Hack for require.resolve("./relative") to work properly. |
| module.filename = path.resolve('repl'); |
| } catch (e) { |
| // path.resolve('repl') fails when the current working directory has been |
| // deleted. Fall back to the directory name of the (absolute) executable |
| // path. It's not really correct but what are the alternatives? |
| const dirname = path.dirname(process.execPath); |
| module.filename = path.resolve(dirname, 'repl'); |
| } |
| |
| // Hack for repl require to work properly with node_modules folders |
| module.paths = CJSModule._nodeModulePaths(module.filename); |
| |
| // If obj.hasOwnProperty has been overridden, then calling |
| // obj.hasOwnProperty(prop) will break. |
| // See: https://github.com/joyent/node/issues/1707 |
| function hasOwnProperty(obj, prop) { |
| return Object.prototype.hasOwnProperty.call(obj, prop); |
| } |
| |
| // Can overridden with custom print functions, such as `probe` or `eyes.js`. |
| // This is the default "writer" value if none is passed in the REPL options. |
| const writer = exports.writer = (obj) => util.inspect(obj, writer.options); |
| writer.options = Object.assign({}, |
| util.inspect.defaultOptions, |
| { showProxy: true, depth: 2 }); |
| |
| exports._builtinLibs = builtinLibs; |
| |
| function REPLServer(prompt, |
| stream, |
| eval_, |
| useGlobal, |
| ignoreUndefined, |
| replMode) { |
| if (!(this instanceof REPLServer)) { |
| return new REPLServer(prompt, |
| stream, |
| eval_, |
| useGlobal, |
| ignoreUndefined, |
| replMode); |
| } |
| |
| var options, input, output, dom, breakEvalOnSigint; |
| if (prompt !== null && typeof prompt === 'object') { |
| // an options object was given |
| options = prompt; |
| stream = options.stream || options.socket; |
| input = options.input; |
| output = options.output; |
| eval_ = options.eval; |
| useGlobal = options.useGlobal; |
| ignoreUndefined = options.ignoreUndefined; |
| prompt = options.prompt; |
| dom = options.domain; |
| replMode = options.replMode; |
| breakEvalOnSigint = options.breakEvalOnSigint; |
| } else { |
| options = {}; |
| } |
| |
| if (breakEvalOnSigint && eval_) { |
| // Allowing this would not reflect user expectations. |
| // breakEvalOnSigint affects only the behavior of the default eval(). |
| throw new ERR_INVALID_REPL_EVAL_CONFIG(); |
| } |
| |
| var self = this; |
| |
| self._domain = dom || domain.create(); |
| self.useGlobal = !!useGlobal; |
| self.ignoreUndefined = !!ignoreUndefined; |
| self.replMode = replMode || exports.REPL_MODE_SLOPPY; |
| self.underscoreAssigned = false; |
| self.last = undefined; |
| self.underscoreErrAssigned = false; |
| self.lastError = undefined; |
| self.breakEvalOnSigint = !!breakEvalOnSigint; |
| self.editorMode = false; |
| // Context id for use with the inspector protocol. |
| self[kContextId] = undefined; |
| |
| // Just for backwards compat, see github.com/joyent/node/pull/7127 |
| self.rli = this; |
| |
| const savedRegExMatches = ['', '', '', '', '', '', '', '', '', '']; |
| const sep = '\u0000\u0000\u0000'; |
| const regExMatcher = new RegExp(`^${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` + |
| `${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` + |
| `${sep}(.*)$`); |
| |
| eval_ = eval_ || defaultEval; |
| |
| // Pause taking in new input, and store the keys in a buffer. |
| const pausedBuffer = []; |
| let paused = false; |
| function pause() { |
| paused = true; |
| } |
| function unpause() { |
| if (!paused) return; |
| paused = false; |
| let entry; |
| while (entry = pausedBuffer.shift()) { |
| const [type, payload] = entry; |
| switch (type) { |
| case 'key': { |
| const [d, key] = payload; |
| self._ttyWrite(d, key); |
| break; |
| } |
| case 'close': |
| self.emit('exit'); |
| break; |
| } |
| if (paused) { |
| break; |
| } |
| } |
| } |
| |
| function defaultEval(code, context, file, cb) { |
| var err, result, script, wrappedErr; |
| var wrappedCmd = false; |
| var awaitPromise = false; |
| var input = code; |
| |
| if (/^\s*\{/.test(code) && /\}\s*$/.test(code)) { |
| // It's confusing for `{ a : 1 }` to be interpreted as a block |
| // statement rather than an object literal. So, we first try |
| // to wrap it in parentheses, so that it will be interpreted as |
| // an expression. Note that if the above condition changes, |
| // lib/internal/repl/recoverable.js needs to be changed to match. |
| code = `(${code.trim()})\n`; |
| wrappedCmd = true; |
| } |
| |
| if (experimentalREPLAwait && code.includes('await')) { |
| if (processTopLevelAwait === undefined) { |
| ({ processTopLevelAwait } = require('internal/repl/await')); |
| } |
| |
| const potentialWrappedCode = processTopLevelAwait(code); |
| if (potentialWrappedCode !== null) { |
| code = potentialWrappedCode; |
| wrappedCmd = true; |
| awaitPromise = true; |
| } |
| } |
| |
| // First, create the Script object to check the syntax |
| if (code === '\n') |
| return cb(null); |
| |
| while (true) { |
| try { |
| if (!/^\s*$/.test(code) && |
| self.replMode === exports.REPL_MODE_STRICT) { |
| // "void 0" keeps the repl from returning "use strict" as the result |
| // value for statements and declarations that don't return a value. |
| code = `'use strict'; void 0;\n${code}`; |
| } |
| script = vm.createScript(code, { |
| filename: file, |
| displayErrors: true |
| }); |
| } catch (e) { |
| debug('parse error %j', code, e); |
| if (wrappedCmd) { |
| // Unwrap and try again |
| wrappedCmd = false; |
| awaitPromise = false; |
| code = input; |
| wrappedErr = e; |
| continue; |
| } |
| // Preserve original error for wrapped command |
| const error = wrappedErr || e; |
| if (isRecoverableError(error, code)) |
| err = new Recoverable(error); |
| else |
| err = error; |
| } |
| break; |
| } |
| |
| // This will set the values from `savedRegExMatches` to corresponding |
| // predefined RegExp properties `RegExp.$1`, `RegExp.$2` ... `RegExp.$9` |
| regExMatcher.test(savedRegExMatches.join(sep)); |
| |
| let finished = false; |
| function finishExecution(err, result) { |
| if (finished) return; |
| finished = true; |
| |
| // After executing the current expression, store the values of RegExp |
| // predefined properties back in `savedRegExMatches` |
| for (var idx = 1; idx < savedRegExMatches.length; idx += 1) { |
| savedRegExMatches[idx] = RegExp[`$${idx}`]; |
| } |
| |
| cb(err, result); |
| } |
| |
| if (!err) { |
| // Unset raw mode during evaluation so that Ctrl+C raises a signal. |
| let previouslyInRawMode; |
| if (self.breakEvalOnSigint) { |
| // Start the SIGINT watchdog before entering raw mode so that a very |
| // quick Ctrl+C doesn't lead to aborting the process completely. |
| if (!startSigintWatchdog()) |
| throw new ERR_CANNOT_WATCH_SIGINT(); |
| previouslyInRawMode = self._setRawMode(false); |
| } |
| |
| try { |
| try { |
| const scriptOptions = { |
| displayErrors: false, |
| breakOnSigint: self.breakEvalOnSigint |
| }; |
| |
| if (self.useGlobal) { |
| result = script.runInThisContext(scriptOptions); |
| } else { |
| result = script.runInContext(context, scriptOptions); |
| } |
| } finally { |
| if (self.breakEvalOnSigint) { |
| // Reset terminal mode to its previous value. |
| self._setRawMode(previouslyInRawMode); |
| |
| // Returns true if there were pending SIGINTs *after* the script |
| // has terminated without being interrupted itself. |
| if (stopSigintWatchdog()) { |
| self.emit('SIGINT'); |
| } |
| } |
| } |
| } catch (e) { |
| err = e; |
| |
| if (process.domain) { |
| debug('not recoverable, send to domain'); |
| process.domain.emit('error', err); |
| process.domain.exit(); |
| return; |
| } |
| } |
| |
| if (awaitPromise && !err) { |
| let sigintListener; |
| pause(); |
| let promise = result; |
| if (self.breakEvalOnSigint) { |
| const interrupt = new Promise((resolve, reject) => { |
| sigintListener = () => { |
| const tmp = Error.stackTraceLimit; |
| Error.stackTraceLimit = 0; |
| const err = new ERR_SCRIPT_EXECUTION_INTERRUPTED(); |
| Error.stackTraceLimit = tmp; |
| reject(err); |
| }; |
| prioritizedSigintQueue.add(sigintListener); |
| }); |
| promise = Promise.race([promise, interrupt]); |
| } |
| |
| promise.then((result) => { |
| // Remove prioritized SIGINT listener if it was not called. |
| // TODO(TimothyGu): Use Promise.prototype.finally when it becomes |
| // available. |
| prioritizedSigintQueue.delete(sigintListener); |
| |
| finishExecution(undefined, result); |
| unpause(); |
| }, (err) => { |
| // Remove prioritized SIGINT listener if it was not called. |
| prioritizedSigintQueue.delete(sigintListener); |
| |
| unpause(); |
| if (err && process.domain) { |
| debug('not recoverable, send to domain'); |
| process.domain.emit('error', err); |
| process.domain.exit(); |
| return; |
| } |
| finishExecution(err); |
| }); |
| } |
| } |
| |
| if (!awaitPromise || err) { |
| finishExecution(err, result); |
| } |
| } |
| |
| self.eval = self._domain.bind(eval_); |
| |
| self._domain.on('error', function debugDomainError(e) { |
| debug('domain error'); |
| let errStack = ''; |
| |
| if (typeof e === 'object' && e !== null) { |
| const pstrace = Error.prepareStackTrace; |
| Error.prepareStackTrace = prepareStackTrace(pstrace); |
| internalUtil.decorateErrorStack(e); |
| Error.prepareStackTrace = pstrace; |
| |
| if (e.domainThrown) { |
| delete e.domain; |
| delete e.domainThrown; |
| } |
| |
| if (internalUtil.isError(e)) { |
| if (e.stack) { |
| if (e.name === 'SyntaxError') { |
| // Remove stack trace. |
| e.stack = e.stack |
| .replace(/^repl:\d+\r?\n/, '') |
| .replace(/^\s+at\s.*\n?/gm, ''); |
| } else if (self.replMode === exports.REPL_MODE_STRICT) { |
| e.stack = e.stack.replace(/(\s+at\s+repl:)(\d+)/, |
| (_, pre, line) => pre + (line - 1)); |
| } |
| } |
| errStack = util.inspect(e); |
| |
| // Remove one line error braces to keep the old style in place. |
| if (errStack[errStack.length - 1] === ']') { |
| errStack = errStack.slice(1, -1); |
| } |
| } |
| } |
| |
| if (errStack === '') { |
| errStack = `Thrown: ${util.inspect(e)}`; |
| } |
| |
| if (!self.underscoreErrAssigned) { |
| self.lastError = e; |
| } |
| |
| const top = replMap.get(self); |
| top.outputStream.write(`${errStack}\n`); |
| top.clearBufferedCommand(); |
| top.lines.level = []; |
| top.displayPrompt(); |
| }); |
| |
| if (!input && !output) { |
| // legacy API, passing a 'stream'/'socket' option |
| if (!stream) { |
| // Use stdin and stdout as the default streams if none were given |
| stream = process; |
| } |
| // We're given a duplex readable/writable Stream, like a `net.Socket` |
| // or a custom object with 2 streams, or the `process` object |
| input = stream.stdin || stream; |
| output = stream.stdout || stream; |
| } |
| |
| self.inputStream = input; |
| self.outputStream = output; |
| |
| self.resetContext(); |
| self.lines.level = []; |
| |
| self.clearBufferedCommand(); |
| Object.defineProperty(this, 'bufferedCommand', { |
| get: util.deprecate(() => self[kBufferedCommandSymbol], |
| 'REPLServer.bufferedCommand is deprecated', 'DEP0074'), |
| set: util.deprecate((val) => self[kBufferedCommandSymbol] = val, |
| 'REPLServer.bufferedCommand is deprecated', 'DEP0074'), |
| enumerable: true |
| }); |
| |
| // Figure out which "complete" function to use. |
| self.completer = (typeof options.completer === 'function') ? |
| options.completer : completer; |
| |
| function completer(text, cb) { |
| complete.call(self, text, self.editorMode ? |
| self.completeOnEditorMode(cb) : cb); |
| } |
| |
| Interface.call(this, { |
| input: self.inputStream, |
| output: self.outputStream, |
| completer: self.completer, |
| terminal: options.terminal, |
| historySize: options.historySize, |
| prompt |
| }); |
| |
| this.commands = Object.create(null); |
| defineDefaultCommands(this); |
| |
| // Figure out which "writer" function to use |
| self.writer = options.writer || exports.writer; |
| |
| if (options.useColors === undefined) { |
| options.useColors = self.terminal; |
| } |
| self.useColors = !!options.useColors; |
| |
| if (self.useColors && self.writer === writer) { |
| // Turn on ANSI coloring. |
| self.writer = (obj) => util.inspect(obj, self.writer.options); |
| self.writer.options = Object.assign({}, writer.options, { colors: true }); |
| } |
| |
| function filterInternalStackFrames(structuredStack) { |
| // Search from the bottom of the call stack to |
| // find the first frame with a null function name |
| if (typeof structuredStack !== 'object') |
| return structuredStack; |
| const idx = structuredStack.reverse().findIndex( |
| (frame) => frame.getFunctionName() === null); |
| |
| // If found, get rid of it and everything below it |
| structuredStack = structuredStack.splice(idx + 1); |
| return structuredStack; |
| } |
| |
| function prepareStackTrace(fn) { |
| return (error, stackFrames) => { |
| const frames = filterInternalStackFrames(stackFrames); |
| if (fn) { |
| return fn(error, frames); |
| } |
| frames.push(error); |
| return frames.reverse().join('\n at '); |
| }; |
| } |
| |
| function _parseREPLKeyword(keyword, rest) { |
| var cmd = this.commands[keyword]; |
| if (cmd) { |
| cmd.action.call(this, rest); |
| return true; |
| } |
| return false; |
| } |
| |
| self.parseREPLKeyword = util.deprecate( |
| _parseREPLKeyword, |
| 'REPLServer.parseREPLKeyword() is deprecated', |
| 'DEP0075'); |
| |
| self.on('close', function emitExit() { |
| if (paused) { |
| pausedBuffer.push(['close']); |
| return; |
| } |
| self.emit('exit'); |
| }); |
| |
| var sawSIGINT = false; |
| var sawCtrlD = false; |
| const prioritizedSigintQueue = new Set(); |
| self.on('SIGINT', function onSigInt() { |
| if (prioritizedSigintQueue.size > 0) { |
| for (const task of prioritizedSigintQueue) { |
| task(); |
| } |
| return; |
| } |
| |
| var empty = self.line.length === 0; |
| self.clearLine(); |
| _turnOffEditorMode(self); |
| |
| const cmd = self[kBufferedCommandSymbol]; |
| if (!(cmd && cmd.length > 0) && empty) { |
| if (sawSIGINT) { |
| self.close(); |
| sawSIGINT = false; |
| return; |
| } |
| self.output.write('(To exit, press ^C again or type .exit)\n'); |
| sawSIGINT = true; |
| } else { |
| sawSIGINT = false; |
| } |
| |
| self.clearBufferedCommand(); |
| self.lines.level = []; |
| self.displayPrompt(); |
| }); |
| |
| self.on('line', function onLine(cmd) { |
| debug('line %j', cmd); |
| cmd = cmd || ''; |
| sawSIGINT = false; |
| |
| if (self.editorMode) { |
| self[kBufferedCommandSymbol] += cmd + '\n'; |
| |
| // code alignment |
| const matches = self._sawKeyPress ? cmd.match(/^\s+/) : null; |
| if (matches) { |
| const prefix = matches[0]; |
| self.write(prefix); |
| self.line = prefix; |
| self.cursor = prefix.length; |
| } |
| _memory.call(self, cmd); |
| return; |
| } |
| |
| // Check REPL keywords and empty lines against a trimmed line input. |
| const trimmedCmd = cmd.trim(); |
| |
| // Check to see if a REPL keyword was used. If it returns true, |
| // display next prompt and return. |
| if (trimmedCmd) { |
| if (trimmedCmd.charAt(0) === '.' && trimmedCmd.charAt(1) !== '.' && |
| Number.isNaN(parseFloat(trimmedCmd))) { |
| const matches = trimmedCmd.match(/^\.([^\s]+)\s*(.*)$/); |
| const keyword = matches && matches[1]; |
| const rest = matches && matches[2]; |
| if (_parseREPLKeyword.call(self, keyword, rest) === true) { |
| return; |
| } |
| if (!self[kBufferedCommandSymbol]) { |
| self.outputStream.write('Invalid REPL keyword\n'); |
| finish(null); |
| return; |
| } |
| } |
| } |
| |
| const evalCmd = self[kBufferedCommandSymbol] + cmd + '\n'; |
| |
| debug('eval %j', evalCmd); |
| self.eval(evalCmd, self.context, 'repl', finish); |
| |
| function finish(e, ret) { |
| debug('finish', e, ret); |
| _memory.call(self, cmd); |
| |
| if (e && !self[kBufferedCommandSymbol] && cmd.trim().startsWith('npm ')) { |
| self.outputStream.write('npm should be run outside of the ' + |
| 'node repl, in your normal shell.\n' + |
| '(Press Control-D to exit.)\n'); |
| self.clearBufferedCommand(); |
| self.displayPrompt(); |
| return; |
| } |
| |
| // If error was SyntaxError and not JSON.parse error |
| if (e) { |
| if (e instanceof Recoverable && !sawCtrlD) { |
| // Start buffering data like that: |
| // { |
| // ... x: 1 |
| // ... } |
| self[kBufferedCommandSymbol] += cmd + '\n'; |
| self.displayPrompt(); |
| return; |
| } else { |
| self._domain.emit('error', e.err || e); |
| } |
| } |
| |
| // Clear buffer if no SyntaxErrors |
| self.clearBufferedCommand(); |
| sawCtrlD = false; |
| |
| // If we got any output - print it (if no error) |
| if (!e && |
| // When an invalid REPL command is used, error message is printed |
| // immediately. We don't have to print anything else. So, only when |
| // the second argument to this function is there, print it. |
| arguments.length === 2 && |
| (!self.ignoreUndefined || ret !== undefined)) { |
| if (!self.underscoreAssigned) { |
| self.last = ret; |
| } |
| self.outputStream.write(self.writer(ret) + '\n'); |
| } |
| |
| // Display prompt again |
| self.displayPrompt(); |
| } |
| }); |
| |
| self.on('SIGCONT', function onSigCont() { |
| if (self.editorMode) { |
| self.outputStream.write(`${self._initialPrompt}.editor\n`); |
| self.outputStream.write( |
| '// Entering editor mode (^D to finish, ^C to cancel)\n'); |
| self.outputStream.write(`${self[kBufferedCommandSymbol]}\n`); |
| self.prompt(true); |
| } else { |
| self.displayPrompt(true); |
| } |
| }); |
| |
| // Wrap readline tty to enable editor mode and pausing. |
| const ttyWrite = self._ttyWrite.bind(self); |
| self._ttyWrite = (d, key) => { |
| key = key || {}; |
| if (paused && !(self.breakEvalOnSigint && key.ctrl && key.name === 'c')) { |
| pausedBuffer.push(['key', [d, key]]); |
| return; |
| } |
| if (!self.editorMode || !self.terminal) { |
| ttyWrite(d, key); |
| return; |
| } |
| |
| // Editor mode |
| if (key.ctrl && !key.shift) { |
| switch (key.name) { |
| case 'd': // End editor mode |
| _turnOffEditorMode(self); |
| sawCtrlD = true; |
| ttyWrite(d, { name: 'return' }); |
| break; |
| case 'n': // Override next history item |
| case 'p': // Override previous history item |
| break; |
| default: |
| ttyWrite(d, key); |
| } |
| } else { |
| switch (key.name) { |
| case 'up': // Override previous history item |
| case 'down': // Override next history item |
| break; |
| case 'tab': |
| // Prevent double tab behavior |
| self._previousKey = null; |
| ttyWrite(d, key); |
| break; |
| default: |
| ttyWrite(d, key); |
| } |
| } |
| }; |
| |
| self.displayPrompt(); |
| } |
| inherits(REPLServer, Interface); |
| exports.REPLServer = REPLServer; |
| |
| exports.REPL_MODE_SLOPPY = Symbol('repl-sloppy'); |
| exports.REPL_MODE_STRICT = Symbol('repl-strict'); |
| |
| // prompt is a string to print on each line for the prompt, |
| // source is a stream to use for I/O, defaulting to stdin/stdout. |
| exports.start = function(prompt, |
| source, |
| eval_, |
| useGlobal, |
| ignoreUndefined, |
| replMode) { |
| var repl = new REPLServer(prompt, |
| source, |
| eval_, |
| useGlobal, |
| ignoreUndefined, |
| replMode); |
| if (!exports.repl) exports.repl = repl; |
| replMap.set(repl, repl); |
| return repl; |
| }; |
| |
| REPLServer.prototype.clearBufferedCommand = function clearBufferedCommand() { |
| this[kBufferedCommandSymbol] = ''; |
| }; |
| |
| REPLServer.prototype.close = function close() { |
| if (this.terminal && this._flushing && !this._closingOnFlush) { |
| this._closingOnFlush = true; |
| this.once('flushHistory', () => |
| Interface.prototype.close.call(this) |
| ); |
| |
| return; |
| } |
| process.nextTick(() => |
| Interface.prototype.close.call(this) |
| ); |
| }; |
| |
| REPLServer.prototype.createContext = function() { |
| var context; |
| if (this.useGlobal) { |
| context = global; |
| } else { |
| sendInspectorCommand((session) => { |
| session.post('Runtime.enable'); |
| session.once('Runtime.executionContextCreated', ({ params }) => { |
| this[kContextId] = params.context.id; |
| }); |
| context = vm.createContext(); |
| session.post('Runtime.disable'); |
| }, () => { |
| context = vm.createContext(); |
| }); |
| context.global = context; |
| const _console = new Console(this.outputStream); |
| Object.defineProperty(context, 'console', { |
| configurable: true, |
| writable: true, |
| value: _console |
| }); |
| |
| var names = Object.getOwnPropertyNames(global); |
| for (var n = 0; n < names.length; n++) { |
| var name = names[n]; |
| if (name === 'console' || name === 'global') |
| continue; |
| if (GLOBAL_OBJECT_PROPERTY_MAP[name] === undefined) { |
| Object.defineProperty(context, name, |
| Object.getOwnPropertyDescriptor(global, name)); |
| } |
| } |
| } |
| |
| var module = new CJSModule('<repl>'); |
| module.paths = |
| CJSModule._resolveLookupPaths('<repl>', parentModule, true) || []; |
| |
| Object.defineProperty(context, 'module', { |
| configurable: true, |
| writable: true, |
| value: module |
| }); |
| Object.defineProperty(context, 'require', { |
| configurable: true, |
| writable: true, |
| value: makeRequireFunction(module) |
| }); |
| |
| addBuiltinLibsToObject(context); |
| |
| return context; |
| }; |
| |
| REPLServer.prototype.resetContext = function() { |
| this.context = this.createContext(); |
| this.underscoreAssigned = false; |
| this.underscoreErrAssigned = false; |
| this.lines = []; |
| this.lines.level = []; |
| |
| Object.defineProperty(this.context, '_', { |
| configurable: true, |
| get: () => this.last, |
| set: (value) => { |
| this.last = value; |
| if (!this.underscoreAssigned) { |
| this.underscoreAssigned = true; |
| this.outputStream.write('Expression assignment to _ now disabled.\n'); |
| } |
| } |
| }); |
| |
| Object.defineProperty(this.context, '_error', { |
| configurable: true, |
| get: () => this.lastError, |
| set: (value) => { |
| this.lastError = value; |
| if (!this.underscoreErrAssigned) { |
| this.underscoreErrAssigned = true; |
| this.outputStream.write( |
| 'Expression assignment to _error now disabled.\n'); |
| } |
| } |
| }); |
| |
| // Allow REPL extensions to extend the new context |
| this.emit('reset', this.context); |
| }; |
| |
| REPLServer.prototype.displayPrompt = function(preserveCursor) { |
| var prompt = this._initialPrompt; |
| if (this[kBufferedCommandSymbol].length) { |
| prompt = '...'; |
| const len = this.lines.level.length ? this.lines.level.length - 1 : 0; |
| const levelInd = '..'.repeat(len); |
| prompt += levelInd + ' '; |
| } |
| |
| // Do not overwrite `_initialPrompt` here |
| REPLServer.super_.prototype.setPrompt.call(this, prompt); |
| this.prompt(preserveCursor); |
| }; |
| |
| // When invoked as an API method, overwrite _initialPrompt |
| REPLServer.prototype.setPrompt = function setPrompt(prompt) { |
| this._initialPrompt = prompt; |
| REPLServer.super_.prototype.setPrompt.call(this, prompt); |
| }; |
| |
| REPLServer.prototype.turnOffEditorMode = util.deprecate( |
| function() { _turnOffEditorMode(this); }, |
| 'REPLServer.turnOffEditorMode() is deprecated', |
| 'DEP0078'); |
| |
| // A stream to push an array into a REPL |
| // used in REPLServer.complete |
| function ArrayStream() { |
| Stream.call(this); |
| |
| this.run = function(data) { |
| for (var n = 0; n < data.length; n++) |
| this.emit('data', `${data[n]}\n`); |
| }; |
| } |
| util.inherits(ArrayStream, Stream); |
| ArrayStream.prototype.readable = true; |
| ArrayStream.prototype.writable = true; |
| ArrayStream.prototype.resume = function() {}; |
| ArrayStream.prototype.write = function() {}; |
| |
| const requireRE = /\brequire\s*\(['"](([\w@./-]+\/)?(?:[\w@./-]*))/; |
| const simpleExpressionRE = |
| /(?:[a-zA-Z_$](?:\w|\$)*\.)*[a-zA-Z_$](?:\w|\$)*\.?$/; |
| |
| function isIdentifier(str) { |
| if (str === '') { |
| return false; |
| } |
| const first = str.codePointAt(0); |
| if (!isIdentifierStart(first)) { |
| return false; |
| } |
| const firstLen = first > 0xffff ? 2 : 1; |
| for (var i = firstLen; i < str.length; i += 1) { |
| const cp = str.codePointAt(i); |
| if (!isIdentifierChar(cp)) { |
| return false; |
| } |
| if (cp > 0xffff) { |
| i += 1; |
| } |
| } |
| return true; |
| } |
| |
| function filteredOwnPropertyNames(obj) { |
| if (!obj) return []; |
| const filter = ALL_PROPERTIES | SKIP_SYMBOLS; |
| return getOwnNonIndexProperties(obj, filter).filter(isIdentifier); |
| } |
| |
| function getGlobalLexicalScopeNames(contextId) { |
| return sendInspectorCommand((session) => { |
| let names = []; |
| session.post('Runtime.globalLexicalScopeNames', { |
| executionContextId: contextId |
| }, (error, result) => { |
| if (!error) names = result.names; |
| }); |
| return names; |
| }, () => []); |
| } |
| |
| REPLServer.prototype.complete = function() { |
| this.completer.apply(this, arguments); |
| }; |
| |
| // Provide a list of completions for the given leading text. This is |
| // given to the readline interface for handling tab completion. |
| // |
| // Example: |
| // complete('var foo = util.') |
| // -> [['util.print', 'util.debug', 'util.log', 'util.inspect'], |
| // 'util.' ] |
| // |
| // Warning: This eval's code like "foo.bar.baz", so it will run property |
| // getter code. |
| function complete(line, callback) { |
| // There may be local variables to evaluate, try a nested REPL |
| if (this[kBufferedCommandSymbol] !== undefined && |
| this[kBufferedCommandSymbol].length) { |
| // Get a new array of inputted lines |
| var tmp = this.lines.slice(); |
| // Kill off all function declarations to push all local variables into |
| // global scope |
| for (var n = 0; n < this.lines.level.length; n++) { |
| var kill = this.lines.level[n]; |
| if (kill.isFunction) |
| tmp[kill.line] = ''; |
| } |
| var flat = new ArrayStream(); // make a new "input" stream |
| var magic = new REPLServer('', flat); // make a nested REPL |
| replMap.set(magic, replMap.get(this)); |
| flat.run(tmp); // eval the flattened code |
| // all this is only profitable if the nested REPL |
| // does not have a bufferedCommand |
| if (!magic[kBufferedCommandSymbol]) { |
| magic._domain.on('error', (err) => { throw err; }); |
| return magic.complete(line, callback); |
| } |
| } |
| |
| var completions; |
| // List of completion lists, one for each inheritance "level" |
| var completionGroups = []; |
| var completeOn, i, group, c; |
| |
| // REPL commands (e.g. ".break"). |
| var filter; |
| var match = null; |
| match = line.match(/^\s*\.(\w*)$/); |
| if (match) { |
| completionGroups.push(Object.keys(this.commands)); |
| completeOn = match[1]; |
| if (match[1].length) { |
| filter = match[1]; |
| } |
| |
| completionGroupsLoaded(); |
| } else if (match = line.match(requireRE)) { |
| // require('...<Tab>') |
| const exts = Object.keys(this.context.require.extensions); |
| var indexRe = new RegExp('^index(?:' + exts.map(regexpEscape).join('|') + |
| ')$'); |
| var versionedFileNamesRe = /-\d+\.\d+/; |
| |
| completeOn = match[1]; |
| var subdir = match[2] || ''; |
| filter = match[1]; |
| var dir, files, f, name, base, ext, abs, subfiles, s, isDirectory; |
| group = []; |
| let paths = []; |
| |
| if (completeOn === '.') { |
| group = ['./', '../']; |
| } else if (completeOn === '..') { |
| group = ['../']; |
| } else if (/^\.\.?\//.test(completeOn)) { |
| paths = [process.cwd()]; |
| } else { |
| paths = module.paths.concat(CJSModule.globalPaths); |
| } |
| |
| for (i = 0; i < paths.length; i++) { |
| dir = path.resolve(paths[i], subdir); |
| try { |
| files = fs.readdirSync(dir); |
| } catch (e) { |
| continue; |
| } |
| for (f = 0; f < files.length; f++) { |
| name = files[f]; |
| ext = path.extname(name); |
| base = name.slice(0, -ext.length); |
| if (versionedFileNamesRe.test(base) || name === '.npm') { |
| // Exclude versioned names that 'npm' installs. |
| continue; |
| } |
| abs = path.resolve(dir, name); |
| try { |
| isDirectory = fs.statSync(abs).isDirectory(); |
| } catch (e) { |
| continue; |
| } |
| if (isDirectory) { |
| group.push(subdir + name + '/'); |
| try { |
| subfiles = fs.readdirSync(abs); |
| } catch (e) { |
| continue; |
| } |
| for (s = 0; s < subfiles.length; s++) { |
| if (indexRe.test(subfiles[s])) { |
| group.push(subdir + name); |
| } |
| } |
| } else if (exts.includes(ext) && (!subdir || base !== 'index')) { |
| group.push(subdir + base); |
| } |
| } |
| } |
| if (group.length) { |
| completionGroups.push(group); |
| } |
| |
| if (!subdir) { |
| completionGroups.push(exports._builtinLibs); |
| } |
| |
| completionGroupsLoaded(); |
| |
| // Handle variable member lookup. |
| // We support simple chained expressions like the following (no function |
| // calls, etc.). That is for simplicity and also because we *eval* that |
| // leading expression so for safety (see WARNING above) don't want to |
| // eval function calls. |
| // |
| // foo.bar<|> # completions for 'foo' with filter 'bar' |
| // spam.eggs.<|> # completions for 'spam.eggs' with filter '' |
| // foo<|> # all scope vars with filter 'foo' |
| // foo.<|> # completions for 'foo' with filter '' |
| } else if (line.length === 0 || /\w|\.|\$/.test(line[line.length - 1])) { |
| match = simpleExpressionRE.exec(line); |
| if (line.length === 0 || match) { |
| var expr; |
| completeOn = (match ? match[0] : ''); |
| if (line.length === 0) { |
| filter = ''; |
| expr = ''; |
| } else if (line[line.length - 1] === '.') { |
| filter = ''; |
| expr = match[0].slice(0, match[0].length - 1); |
| } else { |
| var bits = match[0].split('.'); |
| filter = bits.pop(); |
| expr = bits.join('.'); |
| } |
| |
| // Resolve expr and get its completions. |
| var memberGroups = []; |
| if (!expr) { |
| // If context is instance of vm.ScriptContext |
| // Get global vars synchronously |
| if (this.useGlobal || vm.isContext(this.context)) { |
| completionGroups.push(getGlobalLexicalScopeNames(this[kContextId])); |
| var contextProto = this.context; |
| while (contextProto = Object.getPrototypeOf(contextProto)) { |
| completionGroups.push( |
| filteredOwnPropertyNames.call(this, contextProto)); |
| } |
| completionGroups.push( |
| filteredOwnPropertyNames.call(this, this.context)); |
| addStandardGlobals(completionGroups, filter); |
| completionGroupsLoaded(); |
| } else { |
| this.eval('.scope', this.context, 'repl', function ev(err, globals) { |
| if (err || !Array.isArray(globals)) { |
| addStandardGlobals(completionGroups, filter); |
| } else if (Array.isArray(globals[0])) { |
| // Add grouped globals |
| for (var n = 0; n < globals.length; n++) |
| completionGroups.push(globals[n]); |
| } else { |
| completionGroups.push(globals); |
| addStandardGlobals(completionGroups, filter); |
| } |
| completionGroupsLoaded(); |
| }); |
| } |
| } else { |
| const evalExpr = `try { ${expr} } catch (e) {}`; |
| this.eval(evalExpr, this.context, 'repl', (e, obj) => { |
| if (obj != null) { |
| if (typeof obj === 'object' || typeof obj === 'function') { |
| try { |
| memberGroups.push(filteredOwnPropertyNames.call(this, obj)); |
| } catch (ex) { |
| // Probably a Proxy object without `getOwnPropertyNames` trap. |
| // We simply ignore it here, as we don't want to break the |
| // autocompletion. Fixes the bug |
| // https://github.com/nodejs/node/issues/2119 |
| } |
| } |
| // works for non-objects |
| try { |
| var sentinel = 5; |
| var p; |
| if (typeof obj === 'object' || typeof obj === 'function') { |
| p = Object.getPrototypeOf(obj); |
| } else { |
| p = obj.constructor ? obj.constructor.prototype : null; |
| } |
| while (p !== null) { |
| memberGroups.push(filteredOwnPropertyNames.call(this, p)); |
| p = Object.getPrototypeOf(p); |
| // Circular refs possible? Let's guard against that. |
| sentinel--; |
| if (sentinel <= 0) { |
| break; |
| } |
| } |
| } catch (e) {} |
| } |
| |
| if (memberGroups.length) { |
| for (i = 0; i < memberGroups.length; i++) { |
| completionGroups.push( |
| memberGroups[i].map((member) => `${expr}.${member}`)); |
| } |
| if (filter) { |
| filter = `${expr}.${filter}`; |
| } |
| } |
| |
| completionGroupsLoaded(); |
| }); |
| } |
| } else { |
| completionGroupsLoaded(); |
| } |
| } else { |
| completionGroupsLoaded(); |
| } |
| |
| // Will be called when all completionGroups are in place |
| // Useful for async autocompletion |
| function completionGroupsLoaded() { |
| // Filter, sort (within each group), uniq and merge the completion groups. |
| if (completionGroups.length && filter) { |
| var newCompletionGroups = []; |
| for (i = 0; i < completionGroups.length; i++) { |
| group = completionGroups[i] |
| .filter((elem) => elem.indexOf(filter) === 0); |
| if (group.length) { |
| newCompletionGroups.push(group); |
| } |
| } |
| completionGroups = newCompletionGroups; |
| } |
| |
| if (completionGroups.length) { |
| var uniq = {}; // Unique completions across all groups |
| completions = []; |
| // Completion group 0 is the "closest" |
| // (least far up the inheritance chain) |
| // so we put its completions last: to be closest in the REPL. |
| for (i = 0; i < completionGroups.length; i++) { |
| group = completionGroups[i]; |
| group.sort(); |
| for (var j = group.length - 1; j >= 0; j--) { |
| c = group[j]; |
| if (!hasOwnProperty(uniq, c)) { |
| completions.unshift(c); |
| uniq[c] = true; |
| } |
| } |
| completions.unshift(''); // Separator btwn groups |
| } |
| while (completions.length && completions[0] === '') { |
| completions.shift(); |
| } |
| } |
| |
| callback(null, [completions || [], completeOn]); |
| } |
| } |
| |
| function longestCommonPrefix(arr = []) { |
| const cnt = arr.length; |
| if (cnt === 0) return ''; |
| if (cnt === 1) return arr[0]; |
| |
| const first = arr[0]; |
| // complexity: O(m * n) |
| for (var m = 0; m < first.length; m++) { |
| const c = first[m]; |
| for (var n = 1; n < cnt; n++) { |
| const entry = arr[n]; |
| if (m >= entry.length || c !== entry[m]) { |
| return first.substring(0, m); |
| } |
| } |
| } |
| return first; |
| } |
| |
| REPLServer.prototype.completeOnEditorMode = (callback) => (err, results) => { |
| if (err) return callback(err); |
| |
| const [completions, completeOn = ''] = results; |
| const prefixLength = completeOn.length; |
| |
| if (prefixLength === 0) return callback(null, [[], completeOn]); |
| |
| const isNotEmpty = (v) => v.length > 0; |
| const trimCompleteOnPrefix = (v) => v.substring(prefixLength); |
| const data = completions.filter(isNotEmpty).map(trimCompleteOnPrefix); |
| |
| callback(null, [[`${completeOn}${longestCommonPrefix(data)}`], completeOn]); |
| }; |
| |
| REPLServer.prototype.defineCommand = function(keyword, cmd) { |
| if (typeof cmd === 'function') { |
| cmd = { action: cmd }; |
| } else if (typeof cmd.action !== 'function') { |
| throw new ERR_INVALID_ARG_TYPE('action', 'Function', cmd.action); |
| } |
| this.commands[keyword] = cmd; |
| }; |
| |
| REPLServer.prototype.memory = util.deprecate( |
| _memory, |
| 'REPLServer.memory() is deprecated', |
| 'DEP0082'); |
| |
| function _memory(cmd) { |
| const self = this; |
| self.lines = self.lines || []; |
| self.lines.level = self.lines.level || []; |
| |
| // Save the line so I can do magic later |
| if (cmd) { |
| // TODO should I tab the level? |
| const len = self.lines.level.length ? self.lines.level.length - 1 : 0; |
| self.lines.push(' '.repeat(len) + cmd); |
| } else { |
| // I don't want to not change the format too much... |
| self.lines.push(''); |
| } |
| |
| // I need to know "depth." |
| // Because I can not tell the difference between a } that |
| // closes an object literal and a } that closes a function |
| if (cmd) { |
| // Going down is { and ( e.g. function() { |
| // going up is } and ) |
| var dw = cmd.match(/{|\(/g); |
| var up = cmd.match(/}|\)/g); |
| up = up ? up.length : 0; |
| dw = dw ? dw.length : 0; |
| var depth = dw - up; |
| |
| if (depth) { |
| (function workIt() { |
| if (depth > 0) { |
| // Going... down. |
| // Push the line#, depth count, and if the line is a function. |
| // Since JS only has functional scope I only need to remove |
| // "function() {" lines, clearly this will not work for |
| // "function() |
| // {" but nothing should break, only tab completion for local |
| // scope will not work for this function. |
| self.lines.level.push({ |
| line: self.lines.length - 1, |
| depth: depth, |
| isFunction: /\bfunction\b/.test(cmd) |
| }); |
| } else if (depth < 0) { |
| // Going... up. |
| var curr = self.lines.level.pop(); |
| if (curr) { |
| var tmp = curr.depth + depth; |
| if (tmp < 0) { |
| // More to go, recurse |
| depth += curr.depth; |
| workIt(); |
| } else if (tmp > 0) { |
| // Remove and push back |
| curr.depth += depth; |
| self.lines.level.push(curr); |
| } |
| } |
| } |
| }()); |
| } |
| |
| // it is possible to determine a syntax error at this point. |
| // if the REPL still has a bufferedCommand and |
| // self.lines.level.length === 0 |
| // TODO? keep a log of level so that any syntax breaking lines can |
| // be cleared on .break and in the case of a syntax error? |
| // TODO? if a log was kept, then I could clear the bufferedCommand and |
| // eval these lines and throw the syntax error |
| } else { |
| self.lines.level = []; |
| } |
| } |
| |
| function addStandardGlobals(completionGroups, filter) { |
| // Global object properties |
| // (http://www.ecma-international.org/publications/standards/Ecma-262.htm) |
| completionGroups.push(GLOBAL_OBJECT_PROPERTIES); |
| // Common keywords. Exclude for completion on the empty string, b/c |
| // they just get in the way. |
| if (filter) { |
| completionGroups.push([ |
| 'async', 'await', 'break', 'case', 'catch', 'const', 'continue', |
| 'debugger', 'default', 'delete', 'do', 'else', 'export', 'false', |
| 'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof', 'let', |
| 'new', 'null', 'return', 'switch', 'this', 'throw', 'true', 'try', |
| 'typeof', 'undefined', 'var', 'void', 'while', 'with', 'yield' |
| ]); |
| } |
| } |
| |
| function _turnOnEditorMode(repl) { |
| repl.editorMode = true; |
| REPLServer.super_.prototype.setPrompt.call(repl, ''); |
| } |
| |
| function _turnOffEditorMode(repl) { |
| repl.editorMode = false; |
| repl.setPrompt(repl._initialPrompt); |
| } |
| |
| function defineDefaultCommands(repl) { |
| repl.defineCommand('break', { |
| help: 'Sometimes you get stuck, this gets you out', |
| action: function() { |
| this.clearBufferedCommand(); |
| this.displayPrompt(); |
| } |
| }); |
| |
| var clearMessage; |
| if (repl.useGlobal) { |
| clearMessage = 'Alias for .break'; |
| } else { |
| clearMessage = 'Break, and also clear the local context'; |
| } |
| repl.defineCommand('clear', { |
| help: clearMessage, |
| action: function() { |
| this.clearBufferedCommand(); |
| if (!this.useGlobal) { |
| this.outputStream.write('Clearing context...\n'); |
| this.resetContext(); |
| } |
| this.displayPrompt(); |
| } |
| }); |
| |
| repl.defineCommand('exit', { |
| help: 'Exit the repl', |
| action: function() { |
| this.close(); |
| } |
| }); |
| |
| repl.defineCommand('help', { |
| help: 'Print this help message', |
| action: function() { |
| const names = Object.keys(this.commands).sort(); |
| const longestNameLength = names.reduce( |
| (max, name) => Math.max(max, name.length), |
| 0 |
| ); |
| for (var n = 0; n < names.length; n++) { |
| var name = names[n]; |
| var cmd = this.commands[name]; |
| var spaces = ' '.repeat(longestNameLength - name.length + 3); |
| var line = `.${name}${cmd.help ? spaces + cmd.help : ''}\n`; |
| this.outputStream.write(line); |
| } |
| this.displayPrompt(); |
| } |
| }); |
| |
| repl.defineCommand('save', { |
| help: 'Save all evaluated commands in this REPL session to a file', |
| action: function(file) { |
| try { |
| fs.writeFileSync(file, this.lines.join('\n') + '\n'); |
| this.outputStream.write('Session saved to: ' + file + '\n'); |
| } catch (e) { |
| this.outputStream.write('Failed to save: ' + file + '\n'); |
| } |
| this.displayPrompt(); |
| } |
| }); |
| |
| repl.defineCommand('load', { |
| help: 'Load JS from a file into the REPL session', |
| action: function(file) { |
| try { |
| var stats = fs.statSync(file); |
| if (stats && stats.isFile()) { |
| _turnOnEditorMode(this); |
| var data = fs.readFileSync(file, 'utf8'); |
| var lines = data.split('\n'); |
| for (var n = 0; n < lines.length; n++) { |
| if (lines[n]) |
| this.write(`${lines[n]}\n`); |
| } |
| _turnOffEditorMode(this); |
| this.write('\n'); |
| } else { |
| this.outputStream.write('Failed to load: ' + file + |
| ' is not a valid file\n'); |
| } |
| } catch { |
| this.outputStream.write('Failed to load: ' + file + '\n'); |
| } |
| this.displayPrompt(); |
| } |
| }); |
| |
| repl.defineCommand('editor', { |
| help: 'Enter editor mode', |
| action() { |
| if (!this.terminal) return; |
| _turnOnEditorMode(this); |
| this.outputStream.write( |
| '// Entering editor mode (^D to finish, ^C to cancel)\n'); |
| } |
| }); |
| } |
| |
| function regexpEscape(s) { |
| return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); |
| } |
| |
| function Recoverable(err) { |
| this.err = err; |
| } |
| inherits(Recoverable, SyntaxError); |
| exports.Recoverable = Recoverable; |