Browse Source

repl: add support for custom completions

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: https://github.com/nodejs/node-v0.x-archive/pull/8484

PR-URL: https://github.com/nodejs/node/pull/7527
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Lance Ball <lball@redhat.com>
v6.x
Diosney Sarmiento 9 years ago
committed by cjihrig
parent
commit
b3164ae22e
  1. 3
      doc/api/repl.md
  2. 17
      lib/repl.js
  3. 67
      test/parallel/test-repl-tab-complete.js

3
doc/api/repl.md

@ -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:
@ -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
lib/repl.js

@ -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
@ -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.
//
@ -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
@ -975,7 +980,7 @@ REPLServer.prototype.complete = function(line, callback) {
callback(null, [completions || [], completeOn]);
}
};
}
/**

67
test/parallel/test-repl-tab-complete.js

@ -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);
@ -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'
]);
}));

Loading…
Cancel
Save