From b3164ae22efc547d7f619f27185228118dcfb191 Mon Sep 17 00:00:00 2001 From: Diosney Sarmiento Date: Sun, 3 Jul 2016 18:29:34 -0400 Subject: [PATCH] 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 Reviewed-By: Colin Ihrig Reviewed-By: Lance Ball --- doc/api/repl.md | 3 ++ lib/repl.js | 17 ++++--- test/parallel/test-repl-tab-complete.js | 67 ++++++++++++++++++++++++- 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/doc/api/repl.md b/doc/api/repl.md index 56efa5df5d..636f38d706 100644 --- a/doc/api/repl.md +++ b/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 diff --git a/lib/repl.js b/lib/repl.js index 9260776032..6cfffe03d0 100644 --- a/lib/repl.js +++ b/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]); } -}; +} /** diff --git a/test/parallel/test-repl-tab-complete.js b/test/parallel/test-repl-tab-complete.js index 3d32d8e0ce..73a3fd148b 100644 --- a/test/parallel/test-repl-tab-complete.js +++ b/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' + ]); +}));