Skip to content

Commit

Permalink
repl: REPL enhancements
Browse files Browse the repository at this point in the history
* Welcome message with version and help guide
    * `displayWelcomeMessage` flag is used to
      turn on/off
* Differentiate execute & continue actions
    * ^M or enter key to execute the command
    * ^J to continue building multiline expression.
    * `executeOnTimeout` value is used to determine
      the end of expression when `terminal` is false.
* Pretty stack trace.
    * REPL specific stack frames are removed before
      emitting to output stream.
* Recoverable errors.
    * No more recoverable errors & no false positives.
* Defined commands(like .exit, .load) are meaningful
  only at the top level.
* Remove `.break` command and `.clear`when `useGlobal`
  is false.

Welcome message template
------------------------
```js
$ node
Welcome to Node.js <<version>> (<<vm name>> VM, <<vm version>>)
Type ^M or enter to execute, ^J to continue, ^C to exit
Or try

```

Pretty stack trace
------------------
```js
$ node -i
> throw new Error('tiny stack')
Error: tiny stack
    at repl:1:7
> var x y;
var x y;
      ^
SyntaxError: Unexpected identifier
>
```
  • Loading branch information
princejwesley committed Nov 7, 2016
1 parent 24c9e46 commit 429e046
Show file tree
Hide file tree
Showing 31 changed files with 819 additions and 657 deletions.
70 changes: 27 additions & 43 deletions doc/api/repl.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,7 @@ customizable evaluation functions.

The following special commands are supported by all REPL instances:

* `.break` - When in the process of inputting a multi-line expression, entering
the `.break` command (or pressing the `<ctrl>-C` key combination) will abort
further input or processing of that expression.
* `.clear` - Resets the REPL `context` to an empty object and clears any
multi-line expression currently being input.
* `.clear` - Resets the local REPL `context` to an empty object.
* `.exit` - Close the I/O stream, causing the REPL to exit.
* `.help` - Show this list of special commands.
* `.save` - Save the current REPL session to a file:
Expand All @@ -56,7 +52,7 @@ welcome('Node.js User');

The following key combinations in the REPL have these special effects:

* `<ctrl>-C` - When pressed once, has the same effect as the `.break` command.
* `<ctrl>-C` - When pressed once, it will break the current command.
When pressed twice on a blank line, has the same effect as the `.exit`
command.
* `<ctrl>-D` - Has the same effect as the `.exit` command.
Expand Down Expand Up @@ -174,34 +170,6 @@ function myEval(cmd, context, filename, callback) {
repl.start({prompt: '> ', eval: myEval});
```

#### Recoverable Errors

As a user is typing input into the REPL prompt, pressing the `<enter>` key will
send the current line of input to the `eval` function. In order to support
multi-line input, the eval function can return an instance of `repl.Recoverable`
to the provided callback function:

```js
function eval(cmd, context, filename, callback) {
var result;
try {
result = vm.runInThisContext(cmd);
} catch (e) {
if (isRecoverableError(e)) {
return callback(new repl.Recoverable(e));
}
}
callback(null, result);
}

function isRecoverableError(error) {
if (error.name === 'SyntaxError') {
return /^(Unexpected end of input|Unexpected token)/.test(error.message);
}
return false;
}
```

### Customizing REPL Output

By default, `repl.REPLServer` instances format output using the
Expand Down Expand Up @@ -286,15 +254,15 @@ reset to its initial value using the `.clear` command:

```js
$ ./node example.js
>m
> m
'test'
>m = 1
> m = 1
1
>m
> m
1
>.clear
> .clear
Clearing context...
>m
> m
'test'
>
```
Expand Down Expand Up @@ -408,6 +376,18 @@ added: v0.1.91
* `breakEvalOnSigint` - Stop evaluating the current piece of code when
`SIGINT` is received, i.e. `Ctrl+C` is pressed. This cannot be used together
with a custom `eval` function. Defaults to `false`.
* `executeOnTimeout` {number} If `terminal` is false,`executeOnTimeout`
delay is used to determine the end of expression. Defaults to 50ms.
`executeOnTimeout` will be coerced to `[100, 2000]` range.
* `displayWelcomeMessage` {boolean} If `true`, welcome message will be
displayed. Defaults to `false`.
```js
> node
Welcome to Node.js <<version>> (<<vm name>> VM, <<vm version>>)
Type ^M or enter to execute, ^J to continue, ^C to exit
Or try .help for help, more at https://nodejs.org/dist/<<version>>/docs/api/repl.html
>
```

The `repl.start()` method creates and starts a `repl.REPLServer` instance.

Expand All @@ -419,11 +399,15 @@ without passing any arguments (or by passing the `-i` argument):

```js
$ node
> a = [1, 2, 3];
Welcome to Node.js v6.5.0 (v8 VM, 5.1.281.81)
Type ^M or enter to execute, ^J to continue, ^C to exit
Or try .help for help, more at https://nodejs.org/dist/v6.5.0/docs/api/repl.html

> a = [1, 2, 3]; // ^M or ⏎
[ 1, 2, 3 ]
> a.forEach((v) => {
... console.log(v);
... });
> a.forEach((v) => { // ^J to continue
console.log(v);
});
1
2
3
Expand Down
3 changes: 2 additions & 1 deletion lib/_debugger.js
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,8 @@ function Interface(stdin, stdout, args) {
output: this.stdout,
eval: (code, ctx, file, cb) => this.controlEval(code, ctx, file, cb),
useGlobal: false,
ignoreUndefined: true
ignoreUndefined: true,
displayWelcomeMessage: false,
};
if (parseInt(process.env['NODE_NO_READLINE'], 10)) {
opts.terminal = false;
Expand Down
3 changes: 2 additions & 1 deletion lib/internal/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ function createRepl(env, opts, cb) {
ignoreUndefined: false,
terminal: process.stdout.isTTY,
useGlobal: true,
breakEvalOnSigint: true
breakEvalOnSigint: true,
displayWelcomeMessage: process.execArgv.indexOf('-i') === -1,
}, opts);

if (parseInt(env.NODE_NO_READLINE)) {
Expand Down
2 changes: 1 addition & 1 deletion lib/readline.js
Original file line number Diff line number Diff line change
Expand Up @@ -969,7 +969,7 @@ function emitKeypressEvents(stream, iface) {
clearTimeout(timeoutId);

if (iface) {
iface._sawKeyPress = r.length === 1;
iface._sawKeyPress = r.length === 1 || (r[0] === '\x1b');
}

for (var i = 0; i < r.length; i++) {
Expand Down
Loading

0 comments on commit 429e046

Please sign in to comment.