Skip to content
This repository has been archived by the owner on Apr 13, 2024. It is now read-only.

Commit

Permalink
Add tab completion
Browse files Browse the repository at this point in the history
  • Loading branch information
KernelDeimos committed May 24, 2023
1 parent e164032 commit 01f1f7a
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 3 deletions.
66 changes: 64 additions & 2 deletions src/ansi-shell/readline.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Context } from "../context/context";
import { FileCompleter } from "../puter-shell/completers/file_completer";
import { Uint8List } from "../util/bytes";
import { Log } from "../util/log";
import { StatefulProcessorBuilder } from "../util/statemachine";
Expand Down Expand Up @@ -34,6 +35,7 @@ const ReadlineProcessorBuilder = builder => builder
.external('in_', { required: true })
.external('history', { required: true })
.external('prompt', { required: true })
.external('commandCtx', { required: true })
.beforeAll('get-byte', async ctx => {
const { locals, externs } = ctx;

Expand Down Expand Up @@ -72,6 +74,65 @@ const ReadlineProcessorBuilder = builder => builder
}));
// NEXT: get tab completer for input state
console.log('input state', inputState);

let completer = null;
if ( inputState.$ === 'redirect' ) {
completer = new FileCompleter();
}

// TODO: try to get a completer from the command
if ( inputState.$ === 'command' ) {
completer = new FileCompleter();
}

if ( completer === null ) return;

const completions = await completer.getCompetions(
externs.commandCtx,
inputState,
);

const applyCompletion = txt => {
const p1 = vars.result.slice(0, vars.cursor);
const p2 = vars.result.slice(vars.cursor);
console.log({ p1, p2 });
vars.result = p1 + txt + p2;
vars.cursor += txt.length;
externs.out.write(txt);
};

if ( completions.length === 0 ) return;

if ( completions.length === 1 ) {
applyCompletion(completions[0]);
}

if ( completions.length > 1 ) {
let inCommon = '';
for ( let i=0 ; true ; i++ ) {
if ( ! completions.every(completion => {
return completion.length > i;
}) ) break;

let matches = true;

const chrFirst = completions[0][i];
for ( let ci=1 ; ci < completions.length ; ci++ ) {
const chrOther = completions[ci][i];
if ( chrFirst !== chrOther ) {
matches = false;
break;
}
}

if ( ! matches ) break;
inCommon += chrFirst;
}

if ( inCommon.length > 0 ) {
applyCompletion(inCommon);
}
}
return;
}

Expand Down Expand Up @@ -239,7 +300,7 @@ class Readline {
this.history = new HistoryManager();
}

async readline (prompt) {
async readline (prompt, commandCtx) {
const out = this.internal_.out;
const in_ = this.internal_.in;

Expand All @@ -250,7 +311,8 @@ class Readline {
} = await ReadlineProcessor.run({
prompt,
out, in_,
history: this.history
history: this.history,
commandCtx,
});

this.history.save(result);
Expand Down
2 changes: 2 additions & 0 deletions src/ansi-shell/rl_comprehend.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ export const readline_comprehend = (ctx) => {
$: 'command',
id: tokens[0],
tokens: argTokens,
input: endsWithWhitespace ?
'' : argTokens[argTokens.length - 1],
endsWithWhitespace,
};
};
11 changes: 10 additions & 1 deletion src/puter-shell/PuterANSIShell.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,17 @@ export class PuterANSIShell extends EventTarget {
async doPromptIteration() {
console.log('prompt iteration');
const { readline } = this.ctx.externs;
// DRY: created the same way in runPipeline
const executionCtx = this.ctx.sub({
vars: this.variables,
env: this.env,
locals: {
pwd: this.variables.pwd,
}
});
const input = await readline(
this.expandPromptString(this.env.PS1)
this.expandPromptString(this.env.PS1),
executionCtx,
);

await this.runPipeline(input);
Expand Down
31 changes: 31 additions & 0 deletions src/puter-shell/completers/file_completer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import path_ from "path-browserify";

// DRY: also done in many other places
const resolve = (ctx, relPath) => {
if ( relPath.startsWith('/') ) {
return relPath;
}
console.log('wrong context?', ctx);
return path_.resolve(ctx.vars.pwd, relPath);
}

export class FileCompleter {
async getCompetions (ctx, inputState) {
const { puterShell } = ctx.externs;

let path = resolve(ctx, inputState.input);
let dir = path_.dirname(path);
let base = path_.basename(path);

const completions = [];

const result = await puterShell.command('list', { path: dir });
for ( const item of result ) {
if ( item.name.startsWith(base) ) {
completions.push(item.name.slice(base.length));
}
}

return completions;
}
}

0 comments on commit 01f1f7a

Please sign in to comment.