mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
repl: simplify repl autocompletion
This refactors the repl autocompletion code for simplicity and readability. Signed-off-by: Ruben Bridgewater <ruben@bridgewater.de> PR-URL: https://github.com/nodejs/node/pull/33450 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
This commit is contained in:
137
lib/repl.js
137
lib/repl.js
@@ -1111,12 +1111,28 @@ 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 {}
|
||||
}
|
||||
|
||||
function completeFSFunctions(line) {
|
||||
let baseName = '';
|
||||
let filePath = line.match(fsAutoCompleteRE)[1];
|
||||
let fileList = gracefulReaddir(filePath, { withFileTypes: true });
|
||||
|
||||
if (!fileList) {
|
||||
baseName = path.basename(filePath);
|
||||
filePath = path.dirname(filePath);
|
||||
fileList = gracefulReaddir(filePath, { withFileTypes: true }) || [];
|
||||
}
|
||||
|
||||
const completions = fileList
|
||||
.filter((dirent) => dirent.name.startsWith(baseName))
|
||||
.map((d) => d.name);
|
||||
|
||||
return [[completions], baseName];
|
||||
}
|
||||
|
||||
// Provide a list of completions for the given leading text. This is
|
||||
@@ -1145,8 +1161,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);
|
||||
@@ -1173,11 +1187,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.
|
||||
@@ -1193,7 +1203,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}`);
|
||||
@@ -1209,31 +1219,8 @@ function complete(line, callback) {
|
||||
if (!subdir) {
|
||||
completionGroups.push(_builtinLibs);
|
||||
}
|
||||
|
||||
completionGroupsLoaded();
|
||||
} else if (fsAutoCompleteRE.test(line)) {
|
||||
filter = '';
|
||||
let filePath = line.match(fsAutoCompleteRE)[1];
|
||||
let fileList;
|
||||
|
||||
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 {}
|
||||
}
|
||||
|
||||
completionGroupsLoaded();
|
||||
[completionGroups, completeOn] = completeFSFunctions(line);
|
||||
// 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
|
||||
@@ -1245,25 +1232,22 @@ function complete(line, callback) {
|
||||
// foo<|> # all scope vars with filter 'foo'
|
||||
// foo.<|> # completions for 'foo' with filter ''
|
||||
} else if (line.length === 0 || /\w|\.|\$/.test(line[line.length - 1])) {
|
||||
const match = simpleExpressionRE.exec(line);
|
||||
const [match] = simpleExpressionRE.exec(line) || [''];
|
||||
if (line.length !== 0 && !match) {
|
||||
completionGroupsLoaded();
|
||||
return;
|
||||
}
|
||||
let expr;
|
||||
completeOn = (match ? match[0] : '');
|
||||
if (line.length === 0) {
|
||||
expr = '';
|
||||
} else if (line[line.length - 1] === '.') {
|
||||
expr = match[0].slice(0, match[0].length - 1);
|
||||
} else {
|
||||
const bits = match[0].split('.');
|
||||
let expr = '';
|
||||
completeOn = match;
|
||||
if (line.endsWith('.')) {
|
||||
expr = match.slice(0, -1);
|
||||
} else if (line.length !== 0) {
|
||||
const bits = match.split('.');
|
||||
filter = bits.pop();
|
||||
expr = bits.join('.');
|
||||
}
|
||||
|
||||
// Resolve expr and get its completions.
|
||||
const memberGroups = [];
|
||||
if (!expr) {
|
||||
// Get global vars synchronously
|
||||
completionGroups.push(getGlobalLexicalScopeNames(this[kContextId]));
|
||||
@@ -1284,39 +1268,34 @@ function complete(line, callback) {
|
||||
}
|
||||
|
||||
let chaining = '.';
|
||||
if (expr[expr.length - 1] === '?') {
|
||||
if (expr.endsWith('?')) {
|
||||
expr = expr.slice(0, -1);
|
||||
chaining = '?.';
|
||||
}
|
||||
|
||||
const memberGroups = [];
|
||||
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) {
|
||||
@@ -1331,21 +1310,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((str) => str.startsWith(filter));
|
||||
if (filteredGroup.length) {
|
||||
newCompletionGroups.push(filteredGroup);
|
||||
}
|
||||
}
|
||||
completionGroups = newCompletionGroups;
|
||||
|
||||
Reference in New Issue
Block a user