Skip to content

Commit

Permalink
readline: migrate ansi/vt100 logic from tty to readline
Browse files Browse the repository at this point in the history
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 aad12d0
Show file tree
Hide file tree
Showing 8 changed files with 657 additions and 429 deletions.
71 changes: 52 additions & 19 deletions doc/api/readline.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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'

Expand Down
69 changes: 52 additions & 17 deletions doc/api/repl.markdown
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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:

Expand All @@ -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
Expand All @@ -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 () {}`
Expand Down
74 changes: 59 additions & 15 deletions doc/api/tty.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);
});
16 changes: 9 additions & 7 deletions lib/_debugger.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading

0 comments on commit aad12d0

Please sign in to comment.