diff options
author | Anna Henningsen <anna@addaleax.net> | 2016-05-08 03:30:23 +0200 |
---|---|---|
committer | Jeremiah Senkpiel <fishrock123@rocketmail.com> | 2016-07-05 22:10:23 +0200 |
commit | da8e510ee065fe50ee9a63e1e9e20b2ba7d5a85a (patch) | |
tree | 305c27684030d9971c28e44ec981ffd42700c251 /lib | |
parent | 72d659a000917e848f49c81e63ee2b0524b73de0 (diff) | |
download | node-new-da8e510ee065fe50ee9a63e1e9e20b2ba7d5a85a.tar.gz |
repl: break on sigint/ctrl+c
Adds the ability to stop execution of the current REPL command
when receiving SIGINT. This applies only to the default eval
function.
Fixes: https://github.com/nodejs/node/issues/6612
PR-URL: https://github.com/nodejs/node/pull/6635
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/internal/repl.js | 3 | ||||
-rw-r--r-- | lib/repl.js | 51 |
2 files changed, 48 insertions, 6 deletions
diff --git a/lib/internal/repl.js b/lib/internal/repl.js index b72741609b..dd14f42fa5 100644 --- a/lib/internal/repl.js +++ b/lib/internal/repl.js @@ -22,7 +22,8 @@ function createRepl(env, opts, cb) { opts = opts || { ignoreUndefined: false, terminal: process.stdout.isTTY, - useGlobal: true + useGlobal: true, + breakEvalOnSigint: true }; if (parseInt(env.NODE_NO_READLINE)) { diff --git a/lib/repl.js b/lib/repl.js index 387e3b5446..db5754ec04 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -24,6 +24,7 @@ const internalModule = require('internal/module'); const internalUtil = require('internal/util'); const util = require('util'); +const utilBinding = process.binding('util'); const inherits = util.inherits; const Stream = require('stream'); const vm = require('vm'); @@ -178,7 +179,7 @@ function REPLServer(prompt, replMode); } - var options, input, output, dom; + var options, input, output, dom, breakEvalOnSigint; if (prompt !== null && typeof prompt === 'object') { // an options object was given options = prompt; @@ -191,10 +192,17 @@ function REPLServer(prompt, 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 behaviour of the default eval(). + throw new Error('Cannot specify both breakEvalOnSigint and eval for REPL'); + } + var self = this; self._domain = dom || domain.create(); @@ -204,6 +212,7 @@ function REPLServer(prompt, self.replMode = replMode || exports.REPL_MODE_SLOPPY; self.underscoreAssigned = false; self.last = undefined; + self.breakEvalOnSigint = !!breakEvalOnSigint; self._inTemplateLiteral = false; @@ -267,14 +276,46 @@ function REPLServer(prompt, regExMatcher.test(savedRegExMatches.join(sep)); 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. + utilBinding.startSigintWatchdog(); + previouslyInRawMode = self._setRawMode(false); + } + try { - if (self.useGlobal) { - result = script.runInThisContext({ displayErrors: false }); - } else { - result = script.runInContext(context, { displayErrors: false }); + 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 (utilBinding.stopSigintWatchdog()) { + self.emit('SIGINT'); + } + } } } catch (e) { err = e; + if (err.message === 'Script execution interrupted.') { + // The stack trace for this case is not very useful anyway. + Object.defineProperty(err, 'stack', { value: '' }); + } + if (err && process.domain) { debug('not recoverable, send to domain'); process.domain.emit('error', err); |