Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

repl: added support for custom completions #8484

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion doc/api/repl.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ For example, you could add this to your bashrc file:
## repl.start(options)

Returns and starts a `REPLServer` instance, that inherits from
[Readline Interface][]. Accepts an "options" Object that takes
[Readline Interface](readline.html#readline_readline). Accepts an "options" Object that takes
the following values:

- `prompt` - the prompt and `stream` for all I/O. Defaults to `> `.
Expand Down Expand Up @@ -64,6 +64,9 @@ the following values:
returns the formatting (including coloring) to display. Defaults to
`util.inspect`.

- `completer` - an optional function that is used for Tab autocompletion.
See [Readline Interface](readline.html#readline_readline) for an example of using this.

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

function eval(cmd, context, filename, callback) {
Expand Down
16 changes: 10 additions & 6 deletions lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,15 @@ function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) {
self.bufferedCommand = '';
self.lines.level = [];

function complete(text, callback) {
self.complete(text, callback);
}
// Figure out which "complete" function to use.
self.completer = (typeof options.completer === 'function')
? options.completer
: complete;

rl.Interface.apply(this, [
self.inputStream,
self.outputStream,
complete,
self.completer,
options.terminal
]);

Expand Down Expand Up @@ -429,6 +430,9 @@ var requireRE = /\brequire\s*\(['"](([\w\.\/-]+\/)?([\w\.\/-]*))/;
var simpleExpressionRE =
/(([a-zA-Z_$](?:\w|\$)*)\.)*([a-zA-Z_$](?:\w|\$)*)\.?$/;

REPLServer.prototype.complete = function() {
this.completer.apply(this, arguments);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just pass line and callback by name?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to avoid in the future if anyone adds more parameters to the original function but forgot to put them there. A little silly but removes complexity by having only one source of truth.

};

// Provide a list of completions for the given leading text. This is
// given to the readline interface for handling tab completion.
Expand All @@ -440,7 +444,7 @@ var simpleExpressionRE =
//
// Warning: This eval's code like "foo.bar.baz", so it will run property
// getter code.
REPLServer.prototype.complete = function(line, callback) {
function complete(line, callback) {
// There may be local variables to evaluate, try a nested REPL
if (!util.isUndefined(this.bufferedCommand) && this.bufferedCommand.length) {
// Get a new array of inputed lines
Expand Down Expand Up @@ -692,7 +696,7 @@ REPLServer.prototype.complete = function(line, callback) {

callback(null, [completions || [], completeOn]);
}
};
}


/**
Expand Down
33 changes: 33 additions & 0 deletions test/simple/test-repl-tab-complete.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,36 @@ testMe.complete(' ', function(error, data) {
testMe.complete('toSt', function(error, data) {
assert.deepEqual(data, [['toString'], 'toSt']);
});

// To test custom completer function.
var putIn2 = new ArrayStream();
var testMe2 = repl.start({
prompt: '',
input : putIn2,
output: putIn2,
completer: function customCompleter(line, cb) {
var completions = 'aaa aa1 aa2 bbb bb1 bb2 bb3 ccc ddd eee'.split(' ');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having all of these strings and calling split(), can you just start with arrays.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its easier to read but this could be changed .

var hits = completions.filter(function (item) {
return item.indexOf(line) === 0;
});
// Show all completions if none was found.
cb([hits.length ? hits : completions, line]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you not need to pass an error argument here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean? Can you be more explicit?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most callbacks take an error as their first argument. Is that not the case here?

}
});

putIn2.run(['.clear']);

// On empty line should output all the custom completions
// without complete anything.
putIn2.run(['']);
testMe2.complete('', function(error, data) {
assert.deepEqual(data, [
'aaa aa1 aa2 bbb bb1 bb2 bb3 ccc ddd eee'.split(' '), ''
]);
});

// On `a` should output `aaa aa1 aa2` and complete until `aa`.
putIn2.run(['a']);
testMe2.complete('a', function(error, data) {
assert.deepEqual(data, ['aaa aa1 aa2'.split(' '), 'a']);
});