-
Notifications
You must be signed in to change notification settings - Fork 30.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
repl: added support for custom completions. #7527
Changes from 1 commit
1953eab
30c680b
3e368ec
095d147
44688dd
a89f9fe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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.bind(this); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is this binding necessary? (It looks like the context handling is already done by the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Fishrock123 It is because the completer function is passed immediately to the The Is this still viable or maybe you can give me any idea on what change I need to make in order to be accepted? However, I will change |
||
|
||
Interface.call(this, { | ||
input: self.inputStream, | ||
output: self.outputStream, | ||
completer: complete, | ||
completer: self.completer, | ||
terminal: options.terminal, | ||
historySize: options.historySize, | ||
prompt | ||
|
@@ -698,6 +699,10 @@ function filteredOwnPropertyNames(obj) { | |
return Object.getOwnPropertyNames(obj).filter(intFilter); | ||
} | ||
|
||
REPLServer.prototype.complete = function() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The way I read the code, it seems this function was added to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @lance I'm not sure about its origins or all its purposes (besides the one related to testing), since it was already there when I added the feature, I just updated it so nothing is broken. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are correct. My mistake! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not really related to your PR, @diosney, but just to follow up on my comment. I've noticed this in a number of places - methods attached to an object's prototype that are ostensibly public, but undocumented. In my view, this is not ideal and should be rectified by deciding what is intended to be public and what is not, and structuring the code and modifying the docs so they reflect the true intent. @Trott we spoke briefly about issues similar to this (e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @lance If you can't find a place where it's been discussed, opening an issue is totally appropriate in my opinion. Worst case scenario is someone points you to another repo or venue to have the conversation and closes the issue. |
||
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. | ||
// | ||
|
@@ -708,7 +713,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 | ||
|
@@ -967,7 +972,7 @@ REPLServer.prototype.complete = function(line, callback) { | |
|
||
callback(null, [completions || [], completeOn]); | ||
} | ||
}; | ||
} | ||
|
||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ | |
|
||
var common = require('../common'); | ||
var assert = require('assert'); | ||
var repl = require('repl'); | ||
var repl = require('repl'); | ||
|
||
function getNoResultsFunction() { | ||
return common.mustCall((err, data) => { | ||
|
@@ -11,12 +11,13 @@ function getNoResultsFunction() { | |
}); | ||
} | ||
|
||
var works = [['inner.one'], 'inner.o']; | ||
var works = [['inner.one'], 'inner.o']; | ||
const putIn = new common.ArrayStream(); | ||
var testMe = repl.start('', putIn); | ||
var testMe = repl.start('', putIn); | ||
|
||
// Some errors are passed to the domain, but do not callback | ||
testMe._domain.on('error', function(err) { | ||
testMe._domain.on('error', function (err) { | ||
console.log(err) | ||
assert.ifError(err); | ||
}); | ||
|
||
|
@@ -28,13 +29,13 @@ putIn.run([ | |
]); | ||
testMe.complete('inner.o', getNoResultsFunction()); | ||
|
||
testMe.complete('console.lo', common.mustCall(function(error, data) { | ||
testMe.complete('console.lo', common.mustCall(function (error, data) { | ||
assert.deepStrictEqual(data, [['console.log'], 'console.lo']); | ||
})); | ||
|
||
// Tab Complete will return globaly scoped variables | ||
putIn.run(['};']); | ||
testMe.complete('inner.o', common.mustCall(function(error, data) { | ||
testMe.complete('inner.o', common.mustCall(function (error, data) { | ||
assert.deepStrictEqual(data, works); | ||
})); | ||
|
||
|
@@ -55,7 +56,7 @@ putIn.run([ | |
'var top = function() {', | ||
'var inner = {one:1};' | ||
]); | ||
testMe.complete('inner.o', common.mustCall(function(error, data) { | ||
testMe.complete('inner.o', common.mustCall(function (error, data) { | ||
assert.deepStrictEqual(data, works); | ||
})); | ||
|
||
|
@@ -73,7 +74,7 @@ putIn.run([ | |
' one:1', | ||
'};' | ||
]); | ||
testMe.complete('inner.o', common.mustCall(function(error, data) { | ||
testMe.complete('inner.o', common.mustCall(function (error, data) { | ||
assert.deepStrictEqual(data, works); | ||
})); | ||
|
||
|
@@ -87,7 +88,7 @@ putIn.run([ | |
' one:1', | ||
'};' | ||
]); | ||
testMe.complete('inner.o', common.mustCall(function(error, data) { | ||
testMe.complete('inner.o', common.mustCall(function (error, data) { | ||
assert.deepStrictEqual(data, works); | ||
})); | ||
|
||
|
@@ -102,7 +103,7 @@ putIn.run([ | |
' one:1', | ||
'};' | ||
]); | ||
testMe.complete('inner.o', common.mustCall(function(error, data) { | ||
testMe.complete('inner.o', common.mustCall(function (error, data) { | ||
assert.deepStrictEqual(data, works); | ||
})); | ||
|
||
|
@@ -152,47 +153,48 @@ putIn.run(['.clear']); | |
putIn.run([ | ||
'var str = "test";' | ||
]); | ||
testMe.complete('str.len', common.mustCall(function(error, data) { | ||
testMe.complete('str.len', common.mustCall(function (error, data) { | ||
assert.deepStrictEqual(data, [['str.length'], 'str.len']); | ||
})); | ||
|
||
putIn.run(['.clear']); | ||
|
||
// tab completion should not break on spaces | ||
var spaceTimeout = setTimeout(function() { | ||
var spaceTimeout = setTimeout(function () { | ||
throw new Error('timeout'); | ||
}, 1000); | ||
|
||
testMe.complete(' ', common.mustCall(function(error, data) { | ||
testMe.complete(' ', common.mustCall(function (error, data) { | ||
assert.deepStrictEqual(data, [[], undefined]); | ||
clearTimeout(spaceTimeout); | ||
})); | ||
|
||
// tab completion should pick up the global "toString" object, and | ||
// any other properties up the "global" object's prototype chain | ||
testMe.complete('toSt', common.mustCall(function(error, data) { | ||
testMe.complete('toSt', common.mustCall(function (error, data) { | ||
assert.deepStrictEqual(data, [['toString'], 'toSt']); | ||
})); | ||
|
||
// Tab complete provides built in libs for require() | ||
putIn.run(['.clear']); | ||
|
||
testMe.complete('require(\'', common.mustCall(function(error, data) { | ||
testMe.complete('require(\'', common.mustCall(function (error, data) { | ||
assert.strictEqual(error, null); | ||
repl._builtinLibs.forEach(function(lib) { | ||
repl._builtinLibs.forEach(function (lib) { | ||
assert.notStrictEqual(data[0].indexOf(lib), -1, lib + ' not found'); | ||
}); | ||
})); | ||
|
||
testMe.complete('require(\'n', common.mustCall(function(error, data) { | ||
testMe.complete('require(\'n', common.mustCall(function (error, data) { | ||
assert.strictEqual(error, null); | ||
assert.strictEqual(data.length, 2); | ||
assert.strictEqual(data[1], 'n'); | ||
assert.notStrictEqual(data[0].indexOf('net'), -1); | ||
// It's possible to pick up non-core modules too | ||
data[0].forEach(function(completion) { | ||
if (completion) | ||
data[0].forEach(function (completion) { | ||
if (completion) { | ||
assert(/^n/.test(completion)); | ||
} | ||
}); | ||
})); | ||
|
||
|
@@ -202,7 +204,7 @@ putIn.run(['.clear']); | |
putIn.run([ | ||
'var custom = "test";' | ||
]); | ||
testMe.complete('cus', common.mustCall(function(error, data) { | ||
testMe.complete('cus', common.mustCall(function (error, data) { | ||
assert.deepStrictEqual(data, [['custom'], 'cus']); | ||
})); | ||
|
||
|
@@ -214,15 +216,15 @@ putIn.run([ | |
'var proxy = new Proxy({}, {ownKeys: () => { throw new Error(); }});' | ||
]); | ||
|
||
testMe.complete('proxy.', common.mustCall(function(error, data) { | ||
testMe.complete('proxy.', common.mustCall(function (error, data) { | ||
assert.strictEqual(error, null); | ||
})); | ||
|
||
// Make sure tab completion does not include integer members of an Array | ||
putIn.run(['.clear']); | ||
|
||
putIn.run(['var ary = [1,2,3];']); | ||
testMe.complete('ary.', common.mustCall(function(error, data) { | ||
testMe.complete('ary.', common.mustCall(function (error, data) { | ||
assert.strictEqual(data[0].indexOf('ary.0'), -1); | ||
assert.strictEqual(data[0].indexOf('ary.1'), -1); | ||
assert.strictEqual(data[0].indexOf('ary.2'), -1); | ||
|
@@ -232,7 +234,7 @@ testMe.complete('ary.', common.mustCall(function(error, data) { | |
putIn.run(['.clear']); | ||
putIn.run(['var obj = {1:"a","1a":"b",a:"b"};']); | ||
|
||
testMe.complete('obj.', common.mustCall(function(error, data) { | ||
testMe.complete('obj.', common.mustCall(function (error, data) { | ||
assert.strictEqual(data[0].indexOf('obj.1'), -1); | ||
assert.strictEqual(data[0].indexOf('obj.1a'), -1); | ||
assert.notStrictEqual(data[0].indexOf('obj.a'), -1); | ||
|
@@ -269,17 +271,86 @@ testMe.complete('.b', common.mustCall((error, data) => { | |
})); | ||
|
||
const testNonGlobal = repl.start({ | ||
input: putIn, | ||
output: putIn, | ||
input : putIn, | ||
output : putIn, | ||
useGlobal: false | ||
}); | ||
|
||
const builtins = [['Infinity', '', 'Int16Array', 'Int32Array', | ||
'Int8Array'], 'I']; | ||
const builtins = [ | ||
[ | ||
'Infinity', '', 'Int16Array', 'Int32Array', | ||
'Int8Array' | ||
], 'I' | ||
]; | ||
|
||
if (typeof Intl === 'object') { | ||
builtins[0].push('Intl'); | ||
} | ||
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) { | ||
var hits = customCompletions.filter((c) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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) { | ||
var hits = customCompletions.filter((c) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @cjihrig Sorry, old ES5 habits. Updated. |
||
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' | ||
]); | ||
})); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"will be used"
Nit: I would prefer an active tense usage here, such as
An optional function that is used for custom tab auto completion
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lance: Updated doc to fix your nit. Check it out now and let me know.