mirror of
https://github.com/HeyPuter/puter.git
synced 2025-02-02 23:28:39 +08:00
Merge pull request #336 from AtkinsSJ/app-tab-completion
Phoenix: Add tab-completion for command names
This commit is contained in:
commit
4d0e6b4772
@ -23,7 +23,7 @@ const { AppUnderUserActorType } = require("../../services/auth/Actor");
|
|||||||
const { DB_WRITE } = require("../../services/database/consts");
|
const { DB_WRITE } = require("../../services/database/consts");
|
||||||
const { Context } = require("../../util/context");
|
const { Context } = require("../../util/context");
|
||||||
const { origin_from_url } = require("../../util/urlutil");
|
const { origin_from_url } = require("../../util/urlutil");
|
||||||
const { Eq, Or } = require("../query/query");
|
const { Eq, Like, Or } = require("../query/query");
|
||||||
const { BaseES } = require("./BaseES");
|
const { BaseES } = require("./BaseES");
|
||||||
|
|
||||||
const uuidv4 = require('uuid').v4;
|
const uuidv4 = require('uuid').v4;
|
||||||
@ -34,13 +34,19 @@ class AppES extends BaseES {
|
|||||||
const services = this.context.get('services');
|
const services = this.context.get('services');
|
||||||
this.db = services.get('database').get(DB_WRITE, 'apps');
|
this.db = services.get('database').get(DB_WRITE, 'apps');
|
||||||
},
|
},
|
||||||
async create_predicate (id) {
|
async create_predicate (id, ...args) {
|
||||||
if ( id === 'user-can-edit' ) {
|
if ( id === 'user-can-edit' ) {
|
||||||
return new Eq({
|
return new Eq({
|
||||||
key: 'owner',
|
key: 'owner',
|
||||||
value: Context.get('user').id,
|
value: Context.get('user').id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if ( id === 'name-like' ) {
|
||||||
|
return new Like({
|
||||||
|
key: 'name',
|
||||||
|
value: args[0],
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async delete (uid, extra) {
|
async delete (uid, extra) {
|
||||||
const svc_appInformation = this.context.get('services').get('app-information');
|
const svc_appInformation = this.context.get('services').get('app-information');
|
||||||
|
@ -22,7 +22,7 @@ const { BaseES } = require("./BaseES");
|
|||||||
const APIError = require("../../api/APIError");
|
const APIError = require("../../api/APIError");
|
||||||
const { Entity } = require("./Entity");
|
const { Entity } = require("./Entity");
|
||||||
const { WeakConstructorTrait } = require("../../traits/WeakConstructorTrait");
|
const { WeakConstructorTrait } = require("../../traits/WeakConstructorTrait");
|
||||||
const { And, Or, Eq, Predicate, Null, PredicateUtil } = require("../query/query");
|
const { And, Or, Eq, Like, Null, Predicate, PredicateUtil } = require("../query/query");
|
||||||
const { DB_WRITE } = require("../../services/database/consts");
|
const { DB_WRITE } = require("../../services/database/consts");
|
||||||
|
|
||||||
class RawCondition extends AdvancedBase {
|
class RawCondition extends AdvancedBase {
|
||||||
@ -355,6 +355,22 @@ class SQLES extends BaseES {
|
|||||||
|
|
||||||
return new RawCondition({ sql, values });
|
return new RawCondition({ sql, values });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( om_query instanceof Like ) {
|
||||||
|
const key = om_query.key;
|
||||||
|
let value = om_query.value;
|
||||||
|
const prop = this.om.properties[key];
|
||||||
|
|
||||||
|
value = await prop.sql_reference(value);
|
||||||
|
|
||||||
|
const options = prop.descriptor.sql ?? {};
|
||||||
|
const col_name = options.column_name ?? prop.name;
|
||||||
|
|
||||||
|
const sql = `${col_name} LIKE ?`;
|
||||||
|
const values = [value];
|
||||||
|
|
||||||
|
return new RawCondition({ sql, values });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,15 @@ class Eq extends Predicate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Like extends Predicate {
|
||||||
|
async check (entity) {
|
||||||
|
// Convert SQL LIKE pattern to RegExp
|
||||||
|
// TODO: Support escaping the pattern characters
|
||||||
|
const regex = new RegExp(this.value.replaceAll('%', '.*').replaceAll('_', '.'), 'i');
|
||||||
|
return regex.test(await entity.get(this.key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Predicate.prototype.and = function (other) {
|
Predicate.prototype.and = function (other) {
|
||||||
return new And({ children: [this, other] });
|
return new And({ children: [this, other] });
|
||||||
}
|
}
|
||||||
@ -105,4 +114,5 @@ module.exports = {
|
|||||||
And,
|
And,
|
||||||
Or,
|
Or,
|
||||||
Eq,
|
Eq,
|
||||||
|
Like,
|
||||||
};
|
};
|
||||||
|
@ -17,9 +17,9 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import { Context } from '../../context/context.js';
|
import { Context } from '../../context/context.js';
|
||||||
import { CommandCompleter } from '../../puter-shell/completers/command_completer.js';
|
import { CommandCompleter } from '../../puter-shell/completers/CommandCompleter.js';
|
||||||
import { FileCompleter } from '../../puter-shell/completers/file_completer.js';
|
import { FileCompleter } from '../../puter-shell/completers/FileCompleter.js';
|
||||||
import { OptionCompleter } from '../../puter-shell/completers/option_completer.js';
|
import { OptionCompleter } from '../../puter-shell/completers/OptionCompleter.js';
|
||||||
import { Uint8List } from '../../util/bytes.js';
|
import { Uint8List } from '../../util/bytes.js';
|
||||||
import { StatefulProcessorBuilder } from '../../util/statemachine.js';
|
import { StatefulProcessorBuilder } from '../../util/statemachine.js';
|
||||||
import { ANSIContext } from '../ANSIContext.js';
|
import { ANSIContext } from '../ANSIContext.js';
|
||||||
|
@ -25,15 +25,12 @@ export class CommandCompleter {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const completions = [];
|
return (await ctx.externs.commandProvider.complete(query, { ctx }))
|
||||||
|
// Remove any duplicate results
|
||||||
// TODO: Match executable names as well as builtins
|
.filter((item, pos, self) => self.indexOf(item) === pos)
|
||||||
for ( const commandName of Object.keys(builtins) ) {
|
// TODO: Sort completions?
|
||||||
if ( commandName.startsWith(query) ) {
|
// Remove the `query` part of each result, as that's what is expected
|
||||||
completions.push(commandName.slice(query.length));
|
// TODO: Supply whole results instead?
|
||||||
}
|
.map(it => it.slice(query.length));
|
||||||
}
|
|
||||||
|
|
||||||
return completions;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -31,4 +31,9 @@ export class BuiltinCommandProvider {
|
|||||||
}
|
}
|
||||||
return undefined;
|
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;
|
if ( results.length === 0 ) return undefined;
|
||||||
return results;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import path_ from "path-browserify";
|
import path_ from "node:path";
|
||||||
import child_process from "node:child_process";
|
import child_process from "node:child_process";
|
||||||
import stream from "node:stream";
|
import stream from "node:stream";
|
||||||
import { signals } from '../../ansi-shell/signals.js';
|
import { signals } from '../../ansi-shell/signals.js';
|
||||||
@ -167,9 +167,9 @@ function makeCommand(id, executablePath) {
|
|||||||
|
|
||||||
async function findCommandsInPath(id, ctx, firstOnly) {
|
async function findCommandsInPath(id, ctx, firstOnly) {
|
||||||
const PATH = ctx.env['PATH'];
|
const PATH = ctx.env['PATH'];
|
||||||
if (!PATH)
|
if (!PATH || id.includes(path_.sep))
|
||||||
return;
|
return;
|
||||||
const pathDirectories = PATH.split(':');
|
const pathDirectories = PATH.split(path_.delimiter);
|
||||||
|
|
||||||
const results = [];
|
const results = [];
|
||||||
|
|
||||||
@ -200,4 +200,26 @@ export class PathCommandProvider {
|
|||||||
async lookupAll(id, { ctx }) {
|
async lookupAll(id, { ctx }) {
|
||||||
return findCommandsInPath(id, ctx, false);
|
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;
|
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;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async complete (query, { ctx }) {
|
||||||
|
// TODO: Implement this
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user