Skip to content

Commit 9fbe456

Browse files
diosneylance
authored andcommitted
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: nodejs/node-v0.x-archive#8484 PR-URL: #7527 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Lance Ball <[email protected]>
1 parent 18ae74c commit 9fbe456

File tree

3 files changed

+80
-7
lines changed

3 files changed

+80
-7
lines changed

doc/api/repl.md

+3
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,8 @@ added: v0.1.91
382382
`undefined`. Defaults to `false`.
383383
* `writer` {Function} The function to invoke to format the output of each
384384
command before writing to `output`. Defaults to [`util.inspect()`][].
385+
* `completer` {Function} An optional function used for custom Tab auto
386+
completion. See [`readline.InterfaceCompleter`][] for an example.
385387
* `replMode` - A flag that specifies whether the default evaluator executes
386388
all JavaScript commands in strict mode, default mode, or a hybrid mode
387389
("magic" mode.) Acceptable values are:
@@ -526,3 +528,4 @@ see: https://gist.github.com/2053342
526528
[`util.inspect()`]: util.html#util_util_inspect_object_options
527529
[here]: util.html#util_custom_inspect_function_on_objects
528530
[`readline.Interface`]: readline.html#readline_class_interface
531+
[`readline.InterfaceCompleter`]: readline.html#readline_use_of_the_completer_function

lib/repl.js

+11-6
Original file line numberDiff line numberDiff line change
@@ -386,14 +386,15 @@ function REPLServer(prompt,
386386
self.bufferedCommand = '';
387387
self.lines.level = [];
388388

389-
function complete(text, callback) {
390-
self.complete(text, callback);
391-
}
389+
// Figure out which "complete" function to use.
390+
self.completer = (typeof options.completer === 'function')
391+
? options.completer
392+
: complete;
392393

393394
Interface.call(this, {
394395
input: self.inputStream,
395396
output: self.outputStream,
396-
completer: complete,
397+
completer: self.completer,
397398
terminal: options.terminal,
398399
historySize: options.historySize,
399400
prompt
@@ -698,6 +699,10 @@ function filteredOwnPropertyNames(obj) {
698699
return Object.getOwnPropertyNames(obj).filter(intFilter);
699700
}
700701

702+
REPLServer.prototype.complete = function() {
703+
this.completer.apply(this, arguments);
704+
};
705+
701706
// Provide a list of completions for the given leading text. This is
702707
// given to the readline interface for handling tab completion.
703708
//
@@ -708,7 +713,7 @@ function filteredOwnPropertyNames(obj) {
708713
//
709714
// Warning: This eval's code like "foo.bar.baz", so it will run property
710715
// getter code.
711-
REPLServer.prototype.complete = function(line, callback) {
716+
function complete(line, callback) {
712717
// There may be local variables to evaluate, try a nested REPL
713718
if (this.bufferedCommand !== undefined && this.bufferedCommand.length) {
714719
// Get a new array of inputed lines
@@ -967,7 +972,7 @@ REPLServer.prototype.complete = function(line, callback) {
967972

968973
callback(null, [completions || [], completeOn]);
969974
}
970-
};
975+
}
971976

972977

973978
/**

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

+66-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ testMe.complete('console.lo', common.mustCall(function(error, data) {
3232
assert.deepStrictEqual(data, [['console.log'], 'console.lo']);
3333
}));
3434

35-
// Tab Complete will return globaly scoped variables
35+
// Tab Complete will return globally scoped variables
3636
putIn.run(['};']);
3737
testMe.complete('inner.o', common.mustCall(function(error, data) {
3838
assert.deepStrictEqual(data, works);
@@ -283,3 +283,68 @@ if (typeof Intl === 'object') {
283283
testNonGlobal.complete('I', common.mustCall((error, data) => {
284284
assert.deepStrictEqual(data, builtins);
285285
}));
286+
287+
// To test custom completer function.
288+
// Sync mode.
289+
const customCompletions = 'aaa aa1 aa2 bbb bb1 bb2 bb3 ccc ddd eee'.split(' ');
290+
const testCustomCompleterSyncMode = repl.start({
291+
prompt: '',
292+
input: putIn,
293+
output: putIn,
294+
completer: function completerSyncMode(line) {
295+
const hits = customCompletions.filter((c) => {
296+
return c.indexOf(line) === 0;
297+
});
298+
// Show all completions if none found.
299+
return [hits.length ? hits : customCompletions, line];
300+
}
301+
});
302+
303+
// On empty line should output all the custom completions
304+
// without complete anything.
305+
testCustomCompleterSyncMode.complete('', common.mustCall((error, data) => {
306+
assert.deepStrictEqual(data, [
307+
customCompletions,
308+
''
309+
]);
310+
}));
311+
312+
// On `a` should output `aaa aa1 aa2` and complete until `aa`.
313+
testCustomCompleterSyncMode.complete('a', common.mustCall((error, data) => {
314+
assert.deepStrictEqual(data, [
315+
'aaa aa1 aa2'.split(' '),
316+
'a'
317+
]);
318+
}));
319+
320+
// To test custom completer function.
321+
// Async mode.
322+
const testCustomCompleterAsyncMode = repl.start({
323+
prompt: '',
324+
input: putIn,
325+
output: putIn,
326+
completer: function completerAsyncMode(line, callback) {
327+
const hits = customCompletions.filter((c) => {
328+
return c.indexOf(line) === 0;
329+
});
330+
// Show all completions if none found.
331+
callback(null, [hits.length ? hits : customCompletions, line]);
332+
}
333+
});
334+
335+
// On empty line should output all the custom completions
336+
// without complete anything.
337+
testCustomCompleterAsyncMode.complete('', common.mustCall((error, data) => {
338+
assert.deepStrictEqual(data, [
339+
customCompletions,
340+
''
341+
]);
342+
}));
343+
344+
// On `a` should output `aaa aa1 aa2` and complete until `aa`.
345+
testCustomCompleterAsyncMode.complete('a', common.mustCall((error, data) => {
346+
assert.deepStrictEqual(data, [
347+
'aaa aa1 aa2'.split(' '),
348+
'a'
349+
]);
350+
}));

0 commit comments

Comments
 (0)