Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Readline proposal and bugfixes. #2757

Closed
wants to merge 1 commit into from

3 participants

@Southern

Related: #2737 #2756

  • Removed extra newline from .question(); Users can input a newline if it they require it.
  • Removed .close() due to it only emulating closing, causing a bug where readline is left open to trigger events such as .on('line', ...').
  • Removed ._attemptClose()
  • .pause() now triggers event .on('pause', ...)
  • .resume() now triggers event .on('resume', ...)
  • CTRL-C (SIGINT) in readline will now default to .pause() if no SIGINT event is present.
  • CTRL-D (delete right) will also default to .pause() if there is nothing to delete (signaling the end of the file).
  • Added new event SIGTSTP
  • Added new event SIGCONT
  • Added resume to write to resume the stream if paused.
  • Docs updated.
  • Updated repl.js
@Southern

Working on docs.

@Southern Southern commented on the diff
doc/api/readline.markdown
((15 lines not shown))
-Take a look at this slightly more complicated
-[example](https://gist.github.com/901104), and
-[http-console](https://github.com/cloudhead/http-console) for a real-life use
-case.

These examples were removed because they are no longer valid with the API change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@Southern

May need a few more changes. Will continue testing.

@Southern

Known issues:

  • Sending a script to the background with CTRL-Z (SIGTSTP) and bringing it back to the foreground will result in readline not catching CTRL-C (SIGINT) or CTRL-D (EOF).

Working on a solution.

Edit: Solution implemented.

@indutny indutny commented on the diff
doc/api/readline.markdown
@@ -4,8 +4,8 @@ To use this module, do `require('readline')`. Readline allows reading of a
stream (such as STDIN) on a line-by-line basis.
Note that once you've invoked this module, your node program will not
-terminate until you've closed the interface, and the STDIN stream. Here's how
-to allow your program to gracefully terminate:
+terminate until you've paused the interface. Here's how to allow your
@indutny Owner
indutny added a note

Can you change last sentence? It's meaningless now (you're stating same thing as in first sent. )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@indutny
Owner

Lets talk about issues:

~/Code/git/indutny/node > ./node
> 
(^C again to quit)
> 
repl.js:133
      rli.close();
          ^
TypeError: Object #<Interface> has no method 'close'
    at Interface.<anonymous> (repl.js:133:11)
    at Interface.emit (events.js:88:20)
    at Interface._ttyWrite (readline.js:473:16)
    at ReadStream.<anonymous> (readline.js:82:12)
    at ReadStream.emit (events.js:88:20)
    at ReadStream._emitKey (tty.js:331:10)
    at ReadStream.onData (tty.js:66:12)
    at ReadStream.emit (events.js:67:17)
    at TTY.onread (net.js:377:14)
@indutny
Owner

otherwise, lgtm.

@indutny
Owner

But please squash commits once you'll fix error

@indutny
Owner

May be we should add a deprecation warning to .close() ?

@Southern Southern Readline proposal and bugfixes.
Related: #2737 #2756

- Removed extra newline from .question(); Users can input a
  newline if it they require it.
- Removed .close() due to it only emulating closing, causing a bug where
  readline is left open to trigger events such as .on('line', ...').
- Removed ._attemptClose()
- .pause() now triggers event .on('pause', ...)
- .resume() now triggers event .on('resume', ...)
- CTRL-C (SIGINT) in readline will now default to .pause() if no SIGINT event
  is present.
- CTRL-D (delete right) will also default to .pause() if there is nothing to
  delete (signaling the end of the file).
- Added new event `SIGTSTP`
- Added new event `SIGCONT`
- Added `resume` to `write` to resume the stream if paused.
- Docs updated.
- Updated repl.js
a0f7221
@indutny
Owner

landed in ce48579

Thank you!

@indutny indutny closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 16, 2012
  1. @Southern

    Readline proposal and bugfixes.

    Southern authored
    Related: #2737 #2756
    
    - Removed extra newline from .question(); Users can input a
      newline if it they require it.
    - Removed .close() due to it only emulating closing, causing a bug where
      readline is left open to trigger events such as .on('line', ...').
    - Removed ._attemptClose()
    - .pause() now triggers event .on('pause', ...)
    - .resume() now triggers event .on('resume', ...)
    - CTRL-C (SIGINT) in readline will now default to .pause() if no SIGINT event
      is present.
    - CTRL-D (delete right) will also default to .pause() if there is nothing to
      delete (signaling the end of the file).
    - Added new event `SIGTSTP`
    - Added new event `SIGCONT`
    - Added `resume` to `write` to resume the stream if paused.
    - Docs updated.
    - Updated repl.js
This page is out of date. Refresh to see the latest.
Showing with 128 additions and 67 deletions.
  1. +97 −33 doc/api/readline.markdown
  2. +29 −32 lib/readline.js
  3. +2 −2 lib/repl.js
View
130 doc/api/readline.markdown
@@ -4,8 +4,8 @@ To use this module, do `require('readline')`. Readline allows reading of a
stream (such as STDIN) on a line-by-line basis.
Note that once you've invoked this module, your node program will not
-terminate until you've closed the interface, and the STDIN stream. Here's how
-to allow your program to gracefully terminate:
+terminate until you've paused the interface. Here's how to allow your
@indutny Owner
indutny added a note

Can you change last sentence? It's meaningless now (you're stating same thing as in first sent. )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+program to gracefully pause:
var rl = require('readline');
@@ -14,10 +14,7 @@ to allow your program to gracefully terminate:
// TODO: Log the answer in a database
console.log("Thank you for your valuable feedback.");
- // These two lines together allow the program to terminate. Without
- // them, it would run forever.
- i.close();
- process.stdin.destroy();
+ i.pause();
});
### rl.createInterface(input, output, completer)
@@ -48,6 +45,9 @@ Sets the prompt, for example when you run `node` on the command line, you see
Readies readline for input from the user, putting the current `setPrompt`
options on a new line, giving the user a new spot to write.
+This will also resume the `in` stream used with `createInterface` if it has
+been paused.
+
<!-- ### rl.getColumns() Not available? -->
### rl.question(query, callback)
@@ -56,27 +56,29 @@ Prepends the prompt with `query` and invokes `callback` with the user's
response. Displays the query to the user, and then invokes `callback` with the
user's response after it has been typed.
+This will also resume the `in` stream used with `createInterface` if it has
+been paused.
+
Example usage:
interface.question('What is your favorite food?', function(answer) {
console.log('Oh, so your favorite food is ' + answer);
});
-### rl.close()
-
- Closes tty.
-
### rl.pause()
- Pauses tty.
+Pauses the readline `in` stream, allowing it to be resumed later if needed.
### rl.resume()
- Resumes tty.
+Resumes the readline `in` stream.
### rl.write()
- Writes to tty.
+Writes to tty.
+
+This will also resume the `in` stream used with `createInterface` if it has
+been paused.
### Event: 'line'
@@ -91,27 +93,98 @@ Example of listening for `line`:
console.log('You just typed: '+cmd);
});
-### Event: 'close'
+### Event: 'pause'
`function () {}`
-Emitted whenever the `in` stream receives a `^C` or `^D`, respectively known
-as `SIGINT` and `EOT`. This is a good way to know the user is finished using
-your program.
+Emitted whenever the `in` stream is paused or receives `^D`, respectively known
+as `EOT`. This event is also called if there is no `SIGINT` event listener
+present when the `in` stream receives a `^C`, respectively known as `SIGINT`.
-Example of listening for `close`, and exiting the program afterward:
+Also emitted whenever the `in` stream is not paused and receives the `SIGCONT`
+event. (See events `SIGTSTP` and `SIGCONT`)
- rl.on('close', function() {
- console.log('goodbye!');
- process.exit(0);
+Example of listening for `pause`:
+
+ rl.on('pause', function() {
+ console.log('Readline paused.');
+ });
+
+### Event: 'resume'
+
+`function () {}`
+
+Emitted whenever the `in` stream is resumed.
+
+Example of listening for `resume`:
+
+ rl.on('resume', function() {
+ console.log('Readline resumed.');
});
+### Event: 'SIGINT'
+
+`function () {}`
+
+Emitted whenever the `in` stream receives a `^C`, respectively known as
+`SIGINT`. If there is no `SIGINT` event listener present when the `in` stream
+receives a `SIGINT`, `pause` will be triggered.
+
+Example of listening for `SIGINT`:
+
+ rl.on('SIGINT', function() {
+ rl.question('Are you sure you want to exit?', function(answer) {
+ if (answer.match(/^y(es)?$/i)) rl.pause();
+ });
+ });
+
+### Event: 'SIGTSTP'
+
+`function () {}`
+
+Emitted whenever the `in` stream receives a `^Z`, respectively known as
+`SIGTSTP`. If there is no `SIGTSTP` event listener present when the `in` stream
+receives a `SIGTSTP`, the program will be sent to the background.
+
+When the program is resumed with `fg`, the `pause` and `SIGCONT` events will be
+emitted. You can use either to resume the stream.
+
+The `pause` and `SIGCONT` events will not be triggered if the stream was paused
+before the program was sent to the background.
+
+Example of listening for `SIGTSTP`:
+
+ rl.on('SIGTSTP', function() {
+ // This will override SIGTSTP and prevent the program from going to the
+ // background.
+ console.log('Caught SIGTSTP.');
+ });
+
+### Event: 'SIGCONT'
+
+`function () {}`
+
+Emitted whenever the `in` stream is sent to the background with `^Z`,
+respectively known as `SIGTSTP`, and then continued with `fg`. This event only
+emits if the stream was not paused before sending the program to the
+background.
+
+Example of listening for `SIGCONT`:
+
+ rl.on('SIGCONT', function() {
+ // `prompt` will automatically resume the stream
+ rl.prompt();
+ });
+
+
Here's an example of how to use all these together to craft a tiny command
line interface:
var readline = require('readline'),
- rl = readline.createInterface(process.stdin, process.stdout),
- prefix = 'OHAI> ';
+ rl = readline.createInterface(process.stdin, process.stdout);
+
+ rl.setPrompt('OHAI> ');
+ rl.prompt();
rl.on('line', function(line) {
switch(line.trim()) {
@@ -122,18 +195,9 @@ line interface:
console.log('Say what? I might have heard `' + line.trim() + '`');
break;
}
- rl.setPrompt(prefix, prefix.length);
rl.prompt();
- }).on('close', function() {
+ }).on('pause', function() {
console.log('Have a great day!');
process.exit(0);
});
- console.log(prefix + 'Good to see you. Try typing stuff.');
- rl.setPrompt(prefix, prefix.length);
- rl.prompt();
-
-Take a look at this slightly more complicated
-[example](https://gist.github.com/901104), and
-[http-console](https://github.com/cloudhead/http-console) for a real-life use
-case.

These examples were removed because they are no longer valid with the API change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
View
61 lib/readline.js
@@ -126,6 +126,7 @@ Interface.prototype.setPrompt = function(prompt, length) {
Interface.prototype.prompt = function(preserveCursor) {
+ if (this.paused) this.resume();
if (this.enabled) {
if (!preserveCursor) this.cursor = 0;
this._refreshLine();
@@ -136,16 +137,13 @@ Interface.prototype.prompt = function(preserveCursor) {
Interface.prototype.question = function(query, cb) {
- if (cb) {
- this.resume();
+ if (typeof cb === 'function') {
if (this._questionCallback) {
- this.output.write('\n');
this.prompt();
} else {
this._oldPrompt = this._prompt;
this.setPrompt(query);
this._questionCallback = cb;
- this.output.write('\n');
this.prompt();
}
}
@@ -181,8 +179,6 @@ Interface.prototype._addHistory = function() {
Interface.prototype._refreshLine = function() {
- if (this._closed) return;
-
// Cursor to left edge.
this.output.cursorTo(0);
@@ -198,33 +194,29 @@ Interface.prototype._refreshLine = function() {
};
-Interface.prototype.close = function(d) {
- if (this._closing) return;
- this._closing = true;
- if (this.enabled) {
- tty.setRawMode(false);
- }
- this.emit('close');
- this._closed = true;
-};
-
-
Interface.prototype.pause = function() {
+ if (this.paused) return;
if (this.enabled) {
tty.setRawMode(false);
}
+ this.input.pause();
+ this.paused = true;
+ this.emit('pause');
};
Interface.prototype.resume = function() {
+ this.input.resume();
if (this.enabled) {
tty.setRawMode(true);
}
+ this.paused = false;
+ this.emit('resume');
};
Interface.prototype.write = function(d, key) {
- if (this._closed) return;
+ if (this.paused) this.resume();
this.enabled ? this._ttyWrite(d, key) : this._normalWrite(d, key);
};
@@ -454,16 +446,6 @@ Interface.prototype._historyPrev = function() {
};
-Interface.prototype._attemptClose = function() {
- if (this.listeners('attemptClose').length) {
- // User is to call interface.close() manually.
- this.emit('attemptClose');
- } else {
- this.close();
- }
-};
-
-
// handle a write from the tty
Interface.prototype._ttyWrite = function(s, key) {
var next_word, next_non_word, previous_word, previous_non_word;
@@ -489,8 +471,8 @@ Interface.prototype._ttyWrite = function(s, key) {
if (this.listeners('SIGINT').length) {
this.emit('SIGINT');
} else {
- // default behavior, end the readline
- this._attemptClose();
+ // Pause the stream
+ this.pause();
}
break;
@@ -500,7 +482,7 @@ Interface.prototype._ttyWrite = function(s, key) {
case 'd': // delete right or EOF
if (this.cursor === 0 && this.line.length === 0) {
- this._attemptClose();
+ this.pause();
} else if (this.cursor < this.line.length) {
this._deleteRight();
}
@@ -549,7 +531,22 @@ Interface.prototype._ttyWrite = function(s, key) {
break;
case 'z':
- process.kill(process.pid, 'SIGTSTP');
+ if (this.listeners('SIGTSTP').length) {
+ this.emit('SIGTSTP');
+ } else {
+ process.once('SIGCONT', (function(self) {
+ return function() {
+ // Don't raise events if stream has already been abandoned.
+ if (!self.paused) {
+ // Stream must be paused and resumed after SIGCONT to catch
+ // SIGINT, SIGTSTP, and EOF.
+ self.pause();
+ self.emit('SIGCONT');
+ }
+ };
+ })(this));
+ process.kill(process.pid, 'SIGTSTP');
+ }
return;
case 'w': // delete backwards to a word boundary
View
4 lib/repl.js
@@ -130,7 +130,7 @@ function REPLServer(prompt, stream, eval, useGlobal, ignoreUndefined) {
var sawSIGINT = false;
rli.on('SIGINT', function() {
if (sawSIGINT) {
- rli.close();
+ rli.pause();
process.exit();
}
@@ -733,7 +733,7 @@ function defineDefaultCommands(repl) {
repl.defineCommand('exit', {
help: 'Exit the repl',
action: function() {
- this.rli.close();
+ this.rli.pause();
}
});
Something went wrong with that request. Please try again.