Skip to content

Commit aad12d0

Browse files
committed
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.
1 parent ab518ae commit aad12d0

File tree

8 files changed

+657
-429
lines changed

8 files changed

+657
-429
lines changed

doc/api/readline.markdown

+52-19
Original file line numberDiff line numberDiff line change
@@ -3,43 +3,77 @@
33
Stability: 3 - Stable
44

55
To use this module, do `require('readline')`. Readline allows reading of a
6-
stream (such as STDIN) on a line-by-line basis.
6+
stream (such as `process.stdin`) on a line-by-line basis.
77

88
Note that once you've invoked this module, your node program will not
99
terminate until you've paused the interface. Here's how to allow your
1010
program to gracefully pause:
1111

1212
var rl = require('readline');
1313

14-
var i = rl.createInterface(process.stdin, process.stdout, null);
15-
i.question("What do you think of node.js?", function(answer) {
14+
var i = rl.createInterface({
15+
input: process.stdin,
16+
output: process.stdout
17+
});
18+
19+
i.question("What do you think of node.js? ", function(answer) {
1620
// TODO: Log the answer in a database
17-
console.log("Thank you for your valuable feedback.");
21+
console.log("Thank you for your valuable feedback:", answer);
1822

1923
i.pause();
2024
});
2125

22-
## rl.createInterface(input, output, completer)
26+
## rl.createInterface(options)
27+
28+
Creates a readline `Interface` instance. Accepts an "options" Object that takes
29+
the following values:
30+
31+
- `input` - the readable stream to listen to (Required).
32+
33+
- `output` - the writable stream to write readline data to (Required).
34+
35+
- `completer` - an optional function that is used for Tab autocompletion. See
36+
below for an example of using this.
37+
38+
- `terminal` - pass `true` if the `input` and `output` streams should be treated
39+
like a TTY, and have ANSI/VT100 escape codes written to it. Defaults to
40+
checking `isTTY` on the `output` stream upon instantiation.
41+
42+
The `completer` function is given a the current line entered by the user, and
43+
is supposed to return an Array with 2 entries:
2344

24-
Takes two streams and creates a readline interface. The `completer` function
25-
is used for autocompletion. When given a substring, it returns `[[substr1,
26-
substr2, ...], originalsubstring]`.
45+
1. An Array with matching entries for the completion.
46+
47+
2. The substring that was used for the matching.
48+
49+
Which ends up looking something like:
50+
`[[substr1, substr2, ...], originalsubstring]`.
2751

2852
Also `completer` can be run in async mode if it accepts two arguments:
2953

30-
function completer(linePartial, callback) {
31-
callback(null, [['123'], linePartial]);
32-
}
54+
function completer(linePartial, callback) {
55+
callback(null, [['123'], linePartial]);
56+
}
3357

3458
`createInterface` is commonly used with `process.stdin` and
3559
`process.stdout` in order to accept user input:
3660

37-
var readline = require('readline'),
38-
rl = readline.createInterface(process.stdin, process.stdout);
61+
var readline = require('readline');
62+
var rl = readline.createInterface({
63+
input: process.stdin,
64+
output: process.stdout
65+
});
66+
67+
Once you have a readline instance, you most commonly listen for the `"line"` event.
68+
69+
If `terminal` is `true` for this instance then the `output` stream will get the
70+
best compatability if it defines an `output.columns` property, and fires
71+
a `"resize"` event on the `output` if/when the columns ever change
72+
(`process.stdout` does this automatically when it is a TTY).
3973

4074
## Class: Interface
4175

42-
The class that represents a readline interface with a stdin and stdout
76+
The class that represents a readline interface with an input and output
4377
stream.
4478

4579
### rl.setPrompt(prompt, length)
@@ -72,18 +106,17 @@ Example usage:
72106

73107
### rl.pause()
74108

75-
Pauses the readline `in` stream, allowing it to be resumed later if needed.
109+
Pauses the readline `input` stream, allowing it to be resumed later if needed.
76110

77111
### rl.resume()
78112

79-
Resumes the readline `in` stream.
113+
Resumes the readline `input` stream.
80114

81115
### rl.write()
82116

83-
Writes to tty.
117+
Writes to `output` stream.
84118

85-
This will also resume the `in` stream used with `createInterface` if it has
86-
been paused.
119+
This will also resume the `input` stream if it has been paused.
87120

88121
### Event: 'line'
89122

doc/api/repl.markdown

+52-17
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# REPL
22

3-
A Read-Eval-Print-Loop (REPL) is available both as a standalone program and easily
4-
includable in other programs. REPL provides a way to interactively run
5-
JavaScript and see the results. It can be used for debugging, testing, or
3+
A Read-Eval-Print-Loop (REPL) is available both as a standalone program and
4+
easily includable in other programs. The REPL provides a way to interactively
5+
run JavaScript and see the results. It can be used for debugging, testing, or
66
just trying things out.
77

88
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.
1919
2
2020
3
2121

22-
For advanced line-editors, start node with the environmental variable `NODE_NO_READLINE=1`.
23-
This will start the REPL in canonical terminal settings which will allow you to use with `rlwrap`.
22+
For advanced line-editors, start node with the environmental variable
23+
`NODE_NO_READLINE=1`. This will start the main and debugger REPL in canonical
24+
terminal settings which will allow you to use with `rlwrap`.
2425

2526
For example, you could add this to your bashrc file:
2627

2728
alias node="env NODE_NO_READLINE=1 rlwrap node"
2829

2930

30-
## repl.start([prompt], [stream], [eval], [useGlobal], [ignoreUndefined])
31+
## repl.start(options)
3132

32-
Returns and starts a REPL with `prompt` as the prompt and `stream` for all I/O.
33-
`prompt` is optional and defaults to `> `. `stream` is optional and defaults to
34-
`process.stdin`. `eval` is optional too and defaults to async wrapper for
35-
`eval()`.
33+
Returns and starts a `REPLServer` instance. Accepts an "options" Object that
34+
takes the following values:
3635

37-
If `useGlobal` is set to true, then the repl will use the global object,
38-
instead of running scripts in a separate context. Defaults to `false`.
36+
- `prompt` - the prompt and `stream` for all I/O. Defaults to `> `.
3937

40-
If `ignoreUndefined` is set to true, then the repl will not output return value
41-
of command if it's `undefined`. Defaults to `false`.
38+
- `input` - the readable stream to listen to. Defaults to `process.stdin`.
39+
40+
- `output` - the writable stream to write readline data to. Defaults to
41+
`process.stdout`.
42+
43+
- `terminal` - pass `true` if the `stream` should be treated like a TTY, and
44+
have ANSI/VT100 escape codes written to it. Defaults to checking `isTTY`
45+
on the `output` stream upon instantiation.
46+
47+
- `eval` - function that will be used to eval each given line. Defaults to
48+
an async wrapper for `eval()`. See below for an example of a custom `eval`.
49+
50+
- `useGlobal` - if set to `true`, then the repl will use the `global` object,
51+
instead of running scripts in a separate context. Defaults to `false`.
52+
53+
- `ignoreUndefined` - if set to `true`, then the repl will not output the
54+
return value of command if it's `undefined`. Defaults to `false`.
4255

4356
You can use your own `eval` function if it has following signature:
4457

@@ -56,16 +69,32 @@ Here is an example that starts a REPL on stdin, a Unix socket, and a TCP socket:
5669

5770
connections = 0;
5871

59-
repl.start("node via stdin> ");
72+
repl.start({
73+
prompt: "node via stdin> ",
74+
input: process.stdin,
75+
output: process.stdout
76+
});
6077

6178
net.createServer(function (socket) {
6279
connections += 1;
63-
repl.start("node via Unix socket> ", socket);
80+
repl.start({
81+
prompt: "node via Unix socket> ",
82+
input: socket,
83+
output: socket
84+
}).on('exit', function() {
85+
socket.end();
86+
})
6487
}).listen("/tmp/node-repl-sock");
6588

6689
net.createServer(function (socket) {
6790
connections += 1;
68-
repl.start("node via TCP socket> ", socket);
91+
repl.start({
92+
prompt: "node via TCP socket> ",
93+
input: socket,
94+
output: socket
95+
}).on('exit', function() {
96+
socket.end();
97+
});
6998
}).listen(5001);
7099

71100
Running this program from the command line will start a REPL on stdin. Other
@@ -76,6 +105,12 @@ TCP sockets.
76105
By starting a REPL from a Unix socket-based server instead of stdin, you can
77106
connect to a long-running node process without restarting it.
78107

108+
For an example of running a "full-featured" (`terminal`) REPL over
109+
a `net.Server` and `net.Socket` instance, see: https://gist.github.com/2209310
110+
111+
For an example of running a REPL instance over `curl(1)`,
112+
see: https://gist.github.com/2053342
113+
79114
### Event: 'exit'
80115

81116
`function () {}`

doc/api/tty.markdown

+59-15
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,18 @@
22

33
Stability: 3 - Stable
44

5-
Use `require('tty')` to access this module.
6-
7-
Example:
8-
9-
var tty = require('tty');
10-
process.stdin.resume();
11-
tty.setRawMode(true);
12-
process.stdin.on('keypress', function(char, key) {
13-
if (key && key.ctrl && key.name == 'c') {
14-
console.log('graceful exit');
15-
process.exit()
16-
}
17-
});
5+
The `tty` module houses the `tty.ReadStream` and `tty.WriteStream` classes. In
6+
most cases, you will not need to use this module directly.
7+
8+
When node detects that it is being run inside a TTY context, then `process.stdin`
9+
will be a `tty.ReadStream` instance and `process.stdout` will be
10+
a `tty.WriteStream` instance. The preferred way to check if node is being run in
11+
a TTY context is to check `process.stdout.isTTY`:
1812

13+
$ node -p -e "Boolean(process.stdout.isTTY)"
14+
true
15+
$ node -p -e "Boolean(process.stdout.isTTY)" | cat
16+
false
1917

2018

2119
## tty.isatty(fd)
@@ -26,5 +24,51 @@ terminal.
2624

2725
## tty.setRawMode(mode)
2826

29-
`mode` should be `true` or `false`. This sets the properties of the current
30-
process's stdin fd to act either as a raw device or default.
27+
Deprecated. Use `tty.ReadStream#setRawMode()` instead.
28+
29+
30+
## Class: ReadStream
31+
32+
A `net.Socket` subclass that represents the readable portion of a tty. In normal
33+
circumstances, `process.stdin` will be the only `tty.ReadStream` instance in any
34+
node program (only when `isatty(0)` is true).
35+
36+
### rs.isRaw
37+
38+
A `Boolean` that is initialized to `false`. It represents the current "raw" state
39+
of the `tty.ReadStream` instance.
40+
41+
### rs.setRawMode(mode)
42+
43+
`mode` should be `true` or `false`. This sets the properties of the
44+
`tty.ReadStream` to act either as a raw device or default. `isRaw` will be set
45+
to the resulting mode.
46+
47+
48+
## Class WriteStream
49+
50+
A `net.Socket` subclass that represents the writable portion of a tty. In normal
51+
circumstances, `process.stdout` will be the only `tty.WriteStream` instance
52+
ever created (and only when `isatty(1)` is true).
53+
54+
### ws.columns
55+
56+
A `Number` that gives the number of columns the TTY currently has. This property
57+
gets updated on "resize" events.
58+
59+
### ws.rows
60+
61+
A `Number` that gives the number of rows the TTY currently has. This property
62+
gets updated on "resize" events.
63+
64+
### Event: 'resize'
65+
66+
`function () {}`
67+
68+
Emitted by `refreshSize()` when either of the `columns` or `rows` properties
69+
has changed.
70+
71+
process.stdout.on('resize', function() {
72+
console.log('screen size has changed!');
73+
console.log(process.stdout.columns + 'x' + process.stdout.rows);
74+
});

lib/_debugger.js

+9-7
Original file line numberDiff line numberDiff line change
@@ -745,15 +745,17 @@ function Interface(stdin, stdout, args) {
745745
this.stdout = stdout;
746746
this.args = args;
747747

748-
var streams = {
749-
stdin: stdin,
750-
stdout: stdout
751-
};
752-
753748
// Two eval modes are available: controlEval and debugEval
754749
// But controlEval is used by default
755-
this.repl = new repl.REPLServer('debug> ', streams,
756-
this.controlEval.bind(this), false, true);
750+
this.repl = repl.start({
751+
prompt: 'debug> ',
752+
input: this.stdin,
753+
output: this.stdout,
754+
terminal: !parseInt(process.env['NODE_NO_READLINE'], 10),
755+
eval: this.controlEval.bind(this),
756+
useGlobal: false,
757+
ignoreUndefined: true
758+
});
757759

758760
// Do not print useless warning
759761
repl._builtinLibs.splice(repl._builtinLibs.indexOf('repl'), 1);

0 commit comments

Comments
 (0)