Skip to content

Commit

Permalink
repl: simplify repl autocompletion
Browse files Browse the repository at this point in the history
This refactors the repl autocompletion code for simplicity and
readability.

Signed-off-by: Ruben Bridgewater <[email protected]>
  • Loading branch information
BridgeAR committed May 18, 2020
1 parent 0acc704 commit 8c487ad
Showing 1 changed file with 42 additions and 65 deletions.
107 changes: 42 additions & 65 deletions lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -1111,12 +1111,10 @@ REPLServer.prototype.complete = function() {
this.completer.apply(this, arguments);
};

function gracefulOperation(fn, args, alternative) {
function gracefulReaddir(...args) {
try {
return fn(...args);
} catch {
return alternative;
}
return fs.readdirSync(...args);
} catch {}
}

// Provide a list of completions for the given leading text. This is
Expand Down Expand Up @@ -1145,8 +1143,6 @@ function complete(line, callback) {
if (completeOn.length) {
filter = completeOn;
}

completionGroupsLoaded();
} else if (requireRE.test(line)) {
// require('...<Tab>')
const extensions = ObjectKeys(this.context.require.extensions);
Expand All @@ -1173,11 +1169,7 @@ function complete(line, callback) {

for (let dir of paths) {
dir = path.resolve(dir, subdir);
const dirents = gracefulOperation(
fs.readdirSync,
[dir, { withFileTypes: true }],
[]
);
const dirents = gracefulReaddir(dir, { withFileTypes: true }) || [];
for (const dirent of dirents) {
if (versionedFileNamesRe.test(dirent.name) || dirent.name === '.npm') {
// Exclude versioned names that 'npm' installs.
Expand All @@ -1193,7 +1185,7 @@ function complete(line, callback) {
}
group.push(`${subdir}${dirent.name}/`);
const absolute = path.resolve(dir, dirent.name);
const subfiles = gracefulOperation(fs.readdirSync, [absolute], []);
const subfiles = gracefulReaddir(absolute) || [];
for (const subfile of subfiles) {
if (indexes.includes(subfile)) {
group.push(`${subdir}${dirent.name}`);
Expand All @@ -1209,31 +1201,22 @@ function complete(line, callback) {
if (!subdir) {
completionGroups.push(_builtinLibs);
}

completionGroupsLoaded();
} else if (fsAutoCompleteRE.test(line)) {
filter = '';
let baseName = '';
let filePath = line.match(fsAutoCompleteRE)[1];
let fileList;
let fileList = gracefulReaddir(filePath, { withFileTypes: true });

try {
fileList = fs.readdirSync(filePath, { withFileTypes: true });
completionGroups.push(fileList.map((dirent) => dirent.name));
completeOn = '';
} catch {
try {
const baseName = path.basename(filePath);
filePath = path.dirname(filePath);
fileList = fs.readdirSync(filePath, { withFileTypes: true });
const filteredValue = fileList.filter((d) =>
d.name.startsWith(baseName))
.map((d) => d.name);
completionGroups.push(filteredValue);
completeOn = baseName;
} catch {}
if (!fileList) {
baseName = path.basename(filePath);
filePath = path.dirname(filePath);
fileList = gracefulReaddir(filePath, { withFileTypes: true }) || [];
}

completionGroupsLoaded();
const filteredValue = fileList
.filter((dirent) => dirent.name.startsWith(baseName))
.map((d) => d.name);
completionGroups.push(filteredValue);
completeOn = baseName;
// Handle variable member lookup.
// We support simple chained expressions like the following (no function
// calls, etc.). That is for simplicity and also because we *eval* that
Expand Down Expand Up @@ -1291,32 +1274,26 @@ function complete(line, callback) {

const evalExpr = `try { ${expr} } catch {}`;
this.eval(evalExpr, this.context, 'repl', (e, obj) => {
if (obj != null) {
if (typeof obj === 'object' || typeof obj === 'function') {
try {
memberGroups.push(filteredOwnPropertyNames(obj));
} catch {
// Probably a Proxy object without `getOwnPropertyNames` trap.
// We simply ignore it here, as we don't want to break the
// autocompletion. Fixes the bug
// https://github.com/nodejs/node/issues/2119
}
try {
let p;
if ((typeof obj === 'object' && obj !== null) ||
typeof obj === 'function') {
memberGroups.push(filteredOwnPropertyNames(obj));
p = ObjectGetPrototypeOf(obj);
} else {
p = obj.constructor ? obj.constructor.prototype : null;
}
// Works for non-objects
try {
let p;
if (typeof obj === 'object' || typeof obj === 'function') {
p = ObjectGetPrototypeOf(obj);
} else {
p = obj.constructor ? obj.constructor.prototype : null;
}
// Circular refs possible? Let's guard against that.
let sentinel = 5;
while (p !== null && sentinel-- !== 0) {
memberGroups.push(filteredOwnPropertyNames(p));
p = ObjectGetPrototypeOf(p);
}
} catch {}
// Circular refs possible? Let's guard against that.
let sentinel = 5;
while (p !== null && sentinel-- !== 0) {
memberGroups.push(filteredOwnPropertyNames(p));
p = ObjectGetPrototypeOf(p);
}
} catch {
// Maybe a Proxy object without `getOwnPropertyNames` trap.
// We simply ignore it here, as we don't want to break the
// autocompletion. Fixes the bug
// https://github.com/nodejs/node/issues/2119
}

if (memberGroups.length) {
Expand All @@ -1331,21 +1308,21 @@ function complete(line, callback) {

completionGroupsLoaded();
});
} else {
completionGroupsLoaded();
return;
}

return completionGroupsLoaded();

// Will be called when all completionGroups are in place
// Useful for async autocompletion
function completionGroupsLoaded() {
// Filter, sort (within each group), uniq and merge the completion groups.
if (completionGroups.length && filter) {
const newCompletionGroups = [];
for (let i = 0; i < completionGroups.length; i++) {
group = completionGroups[i]
.filter((elem) => elem.indexOf(filter) === 0);
if (group.length) {
newCompletionGroups.push(group);
for (const group of completionGroups) {
const filteredGroup = group.filter((e) => e.indexOf(filter) === 0);
if (filteredGroup.length) {
newCompletionGroups.push(filteredGroup);
}
}
completionGroups = newCompletionGroups;
Expand Down

0 comments on commit 8c487ad

Please sign in to comment.