Skip to content

Commit

Permalink
repl: add support for custom completions
Browse files Browse the repository at this point in the history
Allow user code to override the default `complete()` function from
`readline.Interface`. See:
https://nodejs.org/api/readline.html#readline_use_of_the_completer_function

Ref: nodejs/node-v0.x-archive#8484

PR-URL: #7527
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Lance Ball <lball@redhat.com>
  • Loading branch information
diosney authored and cjihrig committed Aug 10, 2016
1 parent c967af8 commit b3164ae
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 7 deletions.
3 changes: 3 additions & 0 deletions doc/api/repl.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,8 @@ added: v0.1.91
`undefined`. Defaults to `false`.
* `writer` {Function} The function to invoke to format the output of each
command before writing to `output`. Defaults to [`util.inspect()`][].
* `completer` {Function} An optional function used for custom Tab auto
completion. See [`readline.InterfaceCompleter`][] for an example.
* `replMode` - A flag that specifies whether the default evaluator executes
all JavaScript commands in strict mode, default mode, or a hybrid mode
("magic" mode.) Acceptable values are:
Expand Down Expand Up @@ -526,3 +528,4 @@ see: https://gist.github.com/2053342
[`util.inspect()`]: util.html#util_util_inspect_object_options
[here]: util.html#util_custom_inspect_function_on_objects
[`readline.Interface`]: readline.html#readline_class_interface
[`readline.InterfaceCompleter`]: readline.html#readline_use_of_the_completer_function
17 changes: 11 additions & 6 deletions lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -386,14 +386,15 @@ function REPLServer(prompt,
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;

Interface.call(this, {
input: self.inputStream,
output: self.outputStream,
completer: complete,
completer: self.completer,
terminal: options.terminal,
historySize: options.historySize,
prompt
Expand Down Expand Up @@ -706,6 +707,10 @@ function filteredOwnPropertyNames(obj) {
return Object.getOwnPropertyNames(obj).filter(intFilter);
}

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

// Provide a list of completions for the given leading text. This is
// given to the readline interface for handling tab completion.
//
Expand All @@ -716,7 +721,7 @@ function filteredOwnPropertyNames(obj) {
//
// 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 (this.bufferedCommand !== undefined && this.bufferedCommand.length) {
// Get a new array of inputed lines
Expand Down Expand Up @@ -975,7 +980,7 @@ REPLServer.prototype.complete = function(line, callback) {

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


/**
Expand Down
67 changes: 66 additions & 1 deletion test/parallel/test-repl-tab-complete.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ testMe.complete('console.lo', common.mustCall(function(error, data) {
assert.deepStrictEqual(data, [['console.log'], 'console.lo']);
}));

// Tab Complete will return globaly scoped variables
// Tab Complete will return globally scoped variables
putIn.run(['};']);
testMe.complete('inner.o', common.mustCall(function(error, data) {
assert.deepStrictEqual(data, works);
Expand Down Expand Up @@ -283,3 +283,68 @@ if (typeof Intl === 'object') {
testNonGlobal.complete('I', common.mustCall((error, data) => {
assert.deepStrictEqual(data, builtins);
}));

// To test custom completer function.
// Sync mode.
const customCompletions = 'aaa aa1 aa2 bbb bb1 bb2 bb3 ccc ddd eee'.split(' ');
const testCustomCompleterSyncMode = repl.start({
prompt: '',
input: putIn,
output: putIn,
completer: function completerSyncMode(line) {
const hits = customCompletions.filter((c) => {
return c.indexOf(line) === 0;
});
// Show all completions if none found.
return [hits.length ? hits : customCompletions, line];
}
});

// On empty line should output all the custom completions
// without complete anything.
testCustomCompleterSyncMode.complete('', common.mustCall((error, data) => {
assert.deepStrictEqual(data, [
customCompletions,
''
]);
}));

// On `a` should output `aaa aa1 aa2` and complete until `aa`.
testCustomCompleterSyncMode.complete('a', common.mustCall((error, data) => {
assert.deepStrictEqual(data, [
'aaa aa1 aa2'.split(' '),
'a'
]);
}));

// To test custom completer function.
// Async mode.
const testCustomCompleterAsyncMode = repl.start({
prompt: '',
input: putIn,
output: putIn,
completer: function completerAsyncMode(line, callback) {
const hits = customCompletions.filter((c) => {
return c.indexOf(line) === 0;
});
// Show all completions if none found.
callback(null, [hits.length ? hits : customCompletions, line]);
}
});

// On empty line should output all the custom completions
// without complete anything.
testCustomCompleterAsyncMode.complete('', common.mustCall((error, data) => {
assert.deepStrictEqual(data, [
customCompletions,
''
]);
}));

// On `a` should output `aaa aa1 aa2` and complete until `aa`.
testCustomCompleterAsyncMode.complete('a', common.mustCall((error, data) => {
assert.deepStrictEqual(data, [
'aaa aa1 aa2'.split(' '),
'a'
]);
}));

0 comments on commit b3164ae

Please sign in to comment.