feat: Add command names to phoenix tab-completion

Gives CommandProviders a `complete(query, {ctx})` method where they can provide completed command names, and then make use of this in CommandCompleter.

Supported CommandProvider sources:
- Shell built-ins (was supported previously)
- PATH executables (when running under Node)
- Puter app names (when running in Puter)

Script filenames are not yet supported.
This commit is contained in:
Sam Atkins
2024-04-24 12:55:44 +01:00
parent dc5b010d09
commit cf0eee1fa3
6 changed files with 80 additions and 10 deletions
@@ -25,15 +25,12 @@ export class CommandCompleter {
return [];
}
const completions = [];
// TODO: Match executable names as well as builtins
for ( const commandName of Object.keys(builtins) ) {
if ( commandName.startsWith(query) ) {
completions.push(commandName.slice(query.length));
}
}
return completions;
return (await ctx.externs.commandProvider.complete(query, { ctx }))
// Remove any duplicate results
.filter((item, pos, self) => self.indexOf(item) === pos)
// TODO: Sort completions?
// Remove the `query` part of each result, as that's what is expected
// TODO: Supply whole results instead?
.map(it => it.slice(query.length));
}
}
@@ -31,4 +31,9 @@ export class BuiltinCommandProvider {
}
return undefined;
}
async complete (query) {
return Object.keys(builtins)
.filter(commandName => commandName.startsWith(query));
}
}
@@ -42,4 +42,15 @@ export class CompositeCommandProvider {
if ( results.length === 0 ) return undefined;
return results;
}
async complete (...a) {
const query = a[0];
if (query === '') return [];
const results = [];
for (const provider of this.providers) {
results.push(...await provider.complete(...a));
}
return results;
}
}
@@ -200,4 +200,26 @@ export class PathCommandProvider {
async lookupAll(id, { ctx }) {
return findCommandsInPath(id, ctx, false);
}
async complete(query, { ctx }) {
if (query === '') return [];
const PATH = ctx.env['PATH'];
if (!PATH)
return [];
const path_directories = PATH.split(path_.delimiter);
const results = [];
for (const dir of path_directories) {
const dir_entries = await ctx.platform.filesystem.readdir(dir);
for (const dir_entry of dir_entries) {
if (dir_entry.name.startsWith(query)) {
results.push(dir_entry.name);
}
}
}
return results;
}
}
@@ -114,4 +114,34 @@ export class PuterAppCommandProvider {
}
return undefined;
}
async complete (query, { ctx }) {
if (query === '') return [];
const results = [];
for (const app_name of BUILT_IN_APPS) {
if (app_name.startsWith(query)) {
results.push(app_name);
}
}
const request = await fetch(`${puter.APIOrigin}/drivers/call`, {
"headers": {
"Content-Type": "application/json",
"Authorization": `Bearer ${puter.authToken}`,
},
"body": JSON.stringify({ interface: 'puter-apps', method: 'select', args: { predicate: [ 'name-like', query + '%' ] } }),
"method": "POST",
});
const json = await request.json();
if (json.success) {
for (const app of json.result) {
results.push(app.name);
}
}
return results;
}
}
@@ -64,4 +64,9 @@ export class ScriptCommandProvider {
}
return undefined;
}
async complete (query, { ctx }) {
// TODO: Implement this
return [];
}
}