Permalink
Browse files

readline: migrate ansi/vt100 logic from tty to readline

The overall goal here is to make readline more interoperable with other node
Streams like say a net.Socket instance, in "terminal" mode.

See #2922 for all the details.
Closes #2922.
  • Loading branch information...
TooTallNate committed Mar 26, 2012
1 parent ab518ae commit aad12d0b265c9b06ae029d6ee168849260a91dd6
Showing with 657 additions and 429 deletions.
  1. +52 −19 doc/api/readline.markdown
  2. +52 −17 doc/api/repl.markdown
  3. +59 −15 doc/api/tty.markdown
  4. +9 −7 lib/_debugger.js
  5. +394 −40 lib/readline.js
  6. +45 −17 lib/repl.js
  7. +35 −313 lib/tty.js
  8. +11 −1 src/node.js
View
@@ -3,43 +3,77 @@
Stability: 3 - Stable
To use this module, do `require('readline')`. Readline allows reading of a
-stream (such as STDIN) on a line-by-line basis.
+stream (such as `process.stdin`) on a line-by-line basis.
Note that once you've invoked this module, your node program will not
terminate until you've paused the interface. Here's how to allow your
program to gracefully pause:
var rl = require('readline');
- var i = rl.createInterface(process.stdin, process.stdout, null);
- i.question("What do you think of node.js?", function(answer) {
+ var i = rl.createInterface({
+ input: process.stdin,
+ output: process.stdout
+ });
+
+ i.question("What do you think of node.js? ", function(answer) {
// TODO: Log the answer in a database
- console.log("Thank you for your valuable feedback.");
+ console.log("Thank you for your valuable feedback:", answer);
i.pause();
});
-## rl.createInterface(input, output, completer)
+## rl.createInterface(options)
+
+Creates a readline `Interface` instance. Accepts an "options" Object that takes
+the following values:
+
+ - `input` - the readable stream to listen to (Required).
+
+ - `output` - the writable stream to write readline data to (Required).
+
+ - `completer` - an optional function that is used for Tab autocompletion. See
+ below for an example of using this.
+
+ - `terminal` - pass `true` if the `input` and `output` streams should be treated
+ like a TTY, and have ANSI/VT100 escape codes written to it. Defaults to
+ checking `isTTY` on the `output` stream upon instantiation.
+
+The `completer` function is given a the current line entered by the user, and
+is supposed to return an Array with 2 entries:
-Takes two streams and creates a readline interface. The `completer` function
-is used for autocompletion. When given a substring, it returns `[[substr1,
-substr2, ...], originalsubstring]`.
+ 1. An Array with matching entries for the completion.
+
+ 2. The substring that was used for the matching.
+
+Which ends up looking something like:
+`[[substr1, substr2, ...], originalsubstring]`.
Also `completer` can be run in async mode if it accepts two arguments:
- function completer(linePartial, callback) {
- callback(null, [['123'], linePartial]);
- }
+ function completer(linePartial, callback) {
+ callback(null, [['123'], linePartial]);
+ }
`createInterface` is commonly used with `process.stdin` and
`process.stdout` in order to accept user input:
- var readline = require('readline'),
- rl = readline.createInterface(process.stdin, process.stdout);
+ var readline = require('readline');
+ var rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout
+ });
+
+Once you have a readline instance, you most commonly listen for the `"line"` event.
+
+If `terminal` is `true` for this instance then the `output` stream will get the
+best compatability if it defines an `output.columns` property, and fires
+a `"resize"` event on the `output` if/when the columns ever change
+(`process.stdout` does this automatically when it is a TTY).
## Class: Interface
-The class that represents a readline interface with a stdin and stdout
+The class that represents a readline interface with an input and output
stream.
### rl.setPrompt(prompt, length)
@@ -72,18 +106,17 @@ Example usage:
### rl.pause()
-Pauses the readline `in` stream, allowing it to be resumed later if needed.
+Pauses the readline `input` stream, allowing it to be resumed later if needed.
### rl.resume()
-Resumes the readline `in` stream.
+Resumes the readline `input` stream.
### rl.write()
-Writes to tty.
+Writes to `output` stream.
-This will also resume the `in` stream used with `createInterface` if it has
-been paused.
+This will also resume the `input` stream if it has been paused.
### Event: 'line'
View
@@ -1,8 +1,8 @@
# REPL
-A Read-Eval-Print-Loop (REPL) is available both as a standalone program and easily
-includable in other programs. REPL provides a way to interactively run
-JavaScript and see the results. It can be used for debugging, testing, or
+A Read-Eval-Print-Loop (REPL) is available both as a standalone program and
+easily includable in other programs. The REPL provides a way to interactively
+run JavaScript and see the results. It can be used for debugging, testing, or
just trying things out.
By executing `node` without any arguments from the command-line you will be
@@ -19,26 +19,39 @@ dropped into the REPL. It has simplistic emacs line-editing.
2
3
-For advanced line-editors, start node with the environmental variable `NODE_NO_READLINE=1`.
-This will start the REPL in canonical terminal settings which will allow you to use with `rlwrap`.
+For advanced line-editors, start node with the environmental variable
+`NODE_NO_READLINE=1`. This will start the main and debugger REPL in canonical
+terminal settings which will allow you to use with `rlwrap`.
For example, you could add this to your bashrc file:
alias node="env NODE_NO_READLINE=1 rlwrap node"
-## repl.start([prompt], [stream], [eval], [useGlobal], [ignoreUndefined])
+## repl.start(options)
-Returns and starts a REPL with `prompt` as the prompt and `stream` for all I/O.
-`prompt` is optional and defaults to `> `. `stream` is optional and defaults to
-`process.stdin`. `eval` is optional too and defaults to async wrapper for
-`eval()`.
+Returns and starts a `REPLServer` instance. Accepts an "options" Object that
+takes the following values:
-If `useGlobal` is set to true, then the repl will use the global object,
-instead of running scripts in a separate context. Defaults to `false`.
+ - `prompt` - the prompt and `stream` for all I/O. Defaults to `> `.
-If `ignoreUndefined` is set to true, then the repl will not output return value
-of command if it's `undefined`. Defaults to `false`.
+ - `input` - the readable stream to listen to. Defaults to `process.stdin`.
+
+ - `output` - the writable stream to write readline data to. Defaults to
+ `process.stdout`.
+
+ - `terminal` - pass `true` if the `stream` should be treated like a TTY, and
+ have ANSI/VT100 escape codes written to it. Defaults to checking `isTTY`
+ on the `output` stream upon instantiation.
+
+ - `eval` - function that will be used to eval each given line. Defaults to
+ an async wrapper for `eval()`. See below for an example of a custom `eval`.
+
+ - `useGlobal` - if set to `true`, then the repl will use the `global` object,
+ instead of running scripts in a separate context. Defaults to `false`.
+
+ - `ignoreUndefined` - if set to `true`, then the repl will not output the
+ return value of command if it's `undefined`. Defaults to `false`.
You can use your own `eval` function if it has following signature:
@@ -56,16 +69,32 @@ Here is an example that starts a REPL on stdin, a Unix socket, and a TCP socket:
connections = 0;
- repl.start("node via stdin> ");
+ repl.start({
+ prompt: "node via stdin> ",
+ input: process.stdin,
+ output: process.stdout
+ });
net.createServer(function (socket) {
connections += 1;
- repl.start("node via Unix socket> ", socket);
+ repl.start({
+ prompt: "node via Unix socket> ",
+ input: socket,
+ output: socket
+ }).on('exit', function() {
+ socket.end();
+ })
}).listen("/tmp/node-repl-sock");
net.createServer(function (socket) {
connections += 1;
- repl.start("node via TCP socket> ", socket);
+ repl.start({
+ prompt: "node via TCP socket> ",
+ input: socket,
+ output: socket
+ }).on('exit', function() {
+ socket.end();
+ });
}).listen(5001);
Running this program from the command line will start a REPL on stdin. Other
@@ -76,6 +105,12 @@ TCP sockets.
By starting a REPL from a Unix socket-based server instead of stdin, you can
connect to a long-running node process without restarting it.
+For an example of running a "full-featured" (`terminal`) REPL over
+a `net.Server` and `net.Socket` instance, see: https://gist.github.com/2209310
+
+For an example of running a REPL instance over `curl(1)`,
+see: https://gist.github.com/2053342
+
### Event: 'exit'
`function () {}`
View
@@ -2,20 +2,18 @@
Stability: 3 - Stable
-Use `require('tty')` to access this module.
-
-Example:
-
- var tty = require('tty');
- process.stdin.resume();
- tty.setRawMode(true);
- process.stdin.on('keypress', function(char, key) {
- if (key && key.ctrl && key.name == 'c') {
- console.log('graceful exit');
- process.exit()
- }
- });
+The `tty` module houses the `tty.ReadStream` and `tty.WriteStream` classes. In
+most cases, you will not need to use this module directly.
+
+When node detects that it is being run inside a TTY context, then `process.stdin`
+will be a `tty.ReadStream` instance and `process.stdout` will be
+a `tty.WriteStream` instance. The preferred way to check if node is being run in
+a TTY context is to check `process.stdout.isTTY`:
+ $ node -p -e "Boolean(process.stdout.isTTY)"
+ true
+ $ node -p -e "Boolean(process.stdout.isTTY)" | cat
+ false
## tty.isatty(fd)
@@ -26,5 +24,51 @@ terminal.
## tty.setRawMode(mode)
-`mode` should be `true` or `false`. This sets the properties of the current
-process's stdin fd to act either as a raw device or default.
+Deprecated. Use `tty.ReadStream#setRawMode()` instead.
+
+
+## Class: ReadStream
+
+A `net.Socket` subclass that represents the readable portion of a tty. In normal
+circumstances, `process.stdin` will be the only `tty.ReadStream` instance in any
+node program (only when `isatty(0)` is true).
+
+### rs.isRaw
+
+A `Boolean` that is initialized to `false`. It represents the current "raw" state
+of the `tty.ReadStream` instance.
+
+### rs.setRawMode(mode)
+
+`mode` should be `true` or `false`. This sets the properties of the
+`tty.ReadStream` to act either as a raw device or default. `isRaw` will be set
+to the resulting mode.
+
+
+## Class WriteStream
+
+A `net.Socket` subclass that represents the writable portion of a tty. In normal
+circumstances, `process.stdout` will be the only `tty.WriteStream` instance
+ever created (and only when `isatty(1)` is true).
+
+### ws.columns
+
+A `Number` that gives the number of columns the TTY currently has. This property
+gets updated on "resize" events.
+
+### ws.rows
+
+A `Number` that gives the number of rows the TTY currently has. This property
+gets updated on "resize" events.
+
+### Event: 'resize'
+
+`function () {}`
+
+Emitted by `refreshSize()` when either of the `columns` or `rows` properties
+has changed.
+
+ process.stdout.on('resize', function() {
+ console.log('screen size has changed!');
+ console.log(process.stdout.columns + 'x' + process.stdout.rows);
+ });
View
@@ -745,15 +745,17 @@ function Interface(stdin, stdout, args) {
this.stdout = stdout;
this.args = args;
- var streams = {
- stdin: stdin,
- stdout: stdout
- };
-
// Two eval modes are available: controlEval and debugEval
// But controlEval is used by default
- this.repl = new repl.REPLServer('debug> ', streams,
- this.controlEval.bind(this), false, true);
+ this.repl = repl.start({
+ prompt: 'debug> ',
+ input: this.stdin,
+ output: this.stdout,
+ terminal: !parseInt(process.env['NODE_NO_READLINE'], 10),
+ eval: this.controlEval.bind(this),
+ useGlobal: false,
+ ignoreUndefined: true
+ });
// Do not print useless warning
repl._builtinLibs.splice(repl._builtinLibs.indexOf('repl'), 1);
Oops, something went wrong.

0 comments on commit aad12d0

Please sign in to comment.