diff --git a/doc/api/readline.markdown b/doc/api/readline.markdown index e707a271fa..7c9c00c1cf 100644 --- a/doc/api/readline.markdown +++ b/doc/api/readline.markdown @@ -160,6 +160,14 @@ Example of listening for `resume`: console.log('Readline resumed.'); }); +### Event: 'end' + +`function () {}` + +Emitted when the `input` stream receives its "end" event, or when `^D` is +pressed by the user. It's generally a good idea to consider this `Interface` +instance as completed after this is emitted. + ### Event: 'SIGINT' `function () {}` diff --git a/doc/api/repl.markdown b/doc/api/repl.markdown index c61c5751b7..622abdf8e3 100644 --- a/doc/api/repl.markdown +++ b/doc/api/repl.markdown @@ -116,7 +116,8 @@ see: https://gist.github.com/2053342 `function () {}` Emitted when the user exits the REPL in any of the defined ways. Namely, typing -`.exit` at the repl, or pressing Ctrl+C twice to signal SIGINT. +`.exit` at the repl, pressing Ctrl+C twice to signal SIGINT, or pressing Ctrl+D +to signal "end" on the `input` stream. Example of listening for `exit`: diff --git a/lib/readline.js b/lib/readline.js index 94d88ed259..f7dbe0585d 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -90,6 +90,9 @@ function Interface(input, output, completer, terminal) { input.on('data', function(data) { self._normalWrite(data); }); + input.on('end', function() { + self.emit('end'); + }); } else { @@ -575,6 +578,7 @@ Interface.prototype._ttyWrite = function(s, key) { case 'd': // delete right or EOF if (this.cursor === 0 && this.line.length === 0) { this.pause(); + this.emit('end'); } else if (this.cursor < this.line.length) { this._deleteRight(); } diff --git a/lib/repl.js b/lib/repl.js index c96cbe14f4..84293e2c4e 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -168,6 +168,11 @@ function REPLServer(prompt, stream, eval, useGlobal, ignoreUndefined) { rli.setPrompt(self.prompt); + rli.on('end', function() { + self.rli.output.write('\n'); + self.emit('exit'); + }); + var sawSIGINT = false; rli.on('SIGINT', function() { var empty = rli.line.length === 0; diff --git a/test/simple/test-repl-end-emits-exit.js b/test/simple/test-repl-end-emits-exit.js new file mode 100644 index 0000000000..375c0f814f --- /dev/null +++ b/test/simple/test-repl-end-emits-exit.js @@ -0,0 +1,77 @@ +// 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. + +var common = require('../common'), + assert = require('assert'), + Stream = require('stream'), + repl = require('repl'), + gotTerminalExit = false, + gotRegularExit = false; + +// create a dummy stream that does nothing +var stream = new Stream(); +stream.write = stream.pause = stream.resume = function(){}; +stream.readable = stream.writable = true; + +function testTerminalMode() { + var r1 = repl.start({ + input: stream, + output: stream, + terminal: true + }); + + process.nextTick(function() { + // manually fire a ^D keypress + stream.emit('data', '\u0004'); + }); + + r1.on('exit', function() { + // should be fired from the simulated ^D keypress + gotTerminalExit = true; + testRegularMode(); + }); +} + +function testRegularMode() { + var r2 = repl.start({ + input: stream, + output: stream, + terminal: false + }); + + process.nextTick(function() { + stream.emit('end'); + }); + + r2.on('exit', function() { + // should be fired from the simulated 'end' event + gotRegularExit = true; + }); +} + +process.on('exit', function() { + assert(gotTerminalExit); + assert(gotRegularExit); +}); + + +// start +testTerminalMode();