Skip to content

Commit

Permalink
feat(wasi): add common wasi command runner
Browse files Browse the repository at this point in the history
  • Loading branch information
JiyuShao committed Jun 5, 2024
1 parent 2b427f3 commit e8d8731
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 152 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ export function registerFileSystem(context: vscode.ExtensionContext) {
new TextEncoder().encode('console.log("TypeScript")'),
{ create: true, overwrite: true }
);
memFs.writeFile(
vscode.Uri.parse(`${FS_SCHEME}:/index.ts`),
new TextEncoder().encode('console.log("TypeScript")'),
{ create: true, overwrite: true }
);
memFs.writeFile(
vscode.Uri.parse(`${FS_SCHEME}:/file.css`),
new TextEncoder().encode('* { color: green; }'),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import vscode from 'vscode';
import { registerCommand } from '../utils/commands';
import { FS_SCHEME } from '../config';
import { esbuild, toWasm } from '../utils/wasi/esbuild';
import { runWasiCommand, toWasm } from '../utils/wasi';
import { fileExists } from '../utils/file';

export async function registerJSRunner(context: vscode.ExtensionContext) {
Expand All @@ -22,15 +22,16 @@ export async function registerJSRunner(context: vscode.ExtensionContext) {
}
if (!wasmOutFile) {
throw new Error(
`Map VS Code URI to WASM FS Failed: ${FS_SCHEME}:/file.ts`
`Map VS Code URI to WASM FS Failed: ${FS_SCHEME}:/out.js`
);
}

await esbuild(context, [
await runWasiCommand(context, [
'esbuild',
wasmEntryFile,
`--outfile=${wasmOutFile}`,
'--bundle',
// '--loader=ts',
'--loader:.ts=ts',
'--format=esm',
]);
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,19 @@
import vscode from 'vscode';
import { registerCommand } from '../utils/commands';
import { coreutils } from '../utils/wasi/coreutils';
import { esbuild } from '../utils/wasi/esbuild';
import { runWasiCommand } from '../utils/wasi';

export async function registerWasi(context: vscode.ExtensionContext) {
registerCommand(context, 'wasi.command', async () => {
try {
const command = await vscode.window.showInputBox({
value: '-h',
valueSelection: [0, 2],
value: 'help',
valueSelection: [0, 4],
prompt: 'Please input command.',
});

const args = command ? command.split(' ') : [];
if (args.length === 0) {
throw new Error('Please input wasi command');
}
const commandArr = command ? command.trim().split(' ') : [];
vscode.workspace.saveAll(false);
if (args[0] === 'esbuild') {
await esbuild(context, args.slice(1));
} else {
await coreutils(context, args);
}
await runWasiCommand(context, commandArr);
} catch (error) {
vscode.window.showErrorMessage((error as Error).message);
}
Expand Down
163 changes: 163 additions & 0 deletions packages/extensions/js-runner-and-debugger/src/web/utils/wasi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import vscode from 'vscode';
import { workspace, ExtensionContext, Uri } from 'vscode';
import { Wasm, RootFileSystem, WasmPseudoterminal } from '@vscode/wasm-wasi';
import { ansiColor } from './ansi-color';

export interface WasiEnv {
wasm: Wasm;
fs: RootFileSystem;
module: WebAssembly.Module;
}

const wasiEnvMap: Record<string, WasiEnv> = {};

// Load the WASM module.
export async function requireWasiEnv(
context: ExtensionContext,
wasmPath: string
): Promise<WasiEnv> {
if (!wasiEnvMap[wasmPath]) {
const wasm = await Wasm.load();
const fs = await wasm.createRootFileSystem([{ kind: 'workspaceFolder' }]);
const bits = await workspace.fs.readFile(
Uri.joinPath(context.extensionUri, wasmPath)
);
const module = await WebAssembly.compile(bits);
wasiEnvMap[wasmPath] = { wasm, fs, module };
}
return wasiEnvMap[wasmPath];
}

export interface WasiTerminal {
pty: WasmPseudoterminal;
terminal: vscode.Terminal;
}
let wasiTerminalMap: WasiTerminal | undefined = undefined;
export function requirePseudoTerminal(wasm: Wasm, _name: string): WasiTerminal {
if (!wasiTerminalMap) {
const pty = wasm.createPseudoterminal();
const terminal = vscode.window.createTerminal({
name: 'wasm-wasi',
pty,
isTransient: true,
});
terminal.show(true);
wasiTerminalMap = {
pty,
terminal,
};
}
return wasiTerminalMap;
}

/**
* Maps a given VS Code URI to an absolute path in the WASM filesystem. Returns undefined if the URI cannot be mapped.
* @param {vscode.ExtensionContext} context
* @param {vscode.Uri} uri
*/
export async function toWasm(
_context: vscode.ExtensionContext,
uri: vscode.Uri
): Promise<string | undefined> {
// const { fs } = await requireWasiEnv(context, ESBUILD_WASM_WASI_PATH);
// const result = fs.toWasm(uri);
// the official toWasm is not working
const result = `/workspace${uri.path}`;
return result;
}

export interface RunWasiCommandOptions {
prerun?: (wasiEnv: WasiEnv, wasiTerminal: WasiTerminal) => void;
postrun?: (wasiEnv: WasiEnv, wasiTerminal: WasiTerminal) => void;
}

const WASI_COMMAND_MAPPING: Record<string, string> = {
coreutils: 'assets/coreutils/coreutils.async.wasm',
esbuild: 'assets/esbuild-wasm-wasi/esbuild.wasm',
};
const WASI_MOCK_COMMAND_RESPONSE: Record<string, string> = {};
WASI_MOCK_COMMAND_RESPONSE['help'] = `
Usage: command [arguments...]
command --help
Currently defined command:
base32, base64, basename, cat, cksum, comm, cp, csplit, cut, date, df,
dircolors, dirname, echo, env, expand, expr, factor, false, fmt, fold,
hashsum, head, join, link, ln, ls, md5sum, mkdir, mktemp, more, mv, nl,
od, paste, printenv, printf, ptx, pwd, readlink, realpath, relpath, rm,
rmdir, seq, sha1sum, sha224sum, sha256sum, sha3-224sum, sha3-256sum,
sha3-384sum, sha3-512sum, sha384sum, sha3sum, sha512sum, shake128sum,
shake256sum, shred, shuf, sleep, sort, split, sum, tac, tail, tee, test,
touch, tr, true, truncate, tsort, unexpand, uniq, wc, yes, esbuild
`;
WASI_MOCK_COMMAND_RESPONSE['-h'] = WASI_MOCK_COMMAND_RESPONSE['help'];

export async function runWasiCommand(
context: vscode.ExtensionContext,
args: (string | vscode.Uri)[]
) {
if (args.length === 0) {
throw new Error('Please input wasi command');
}

// Use coreutils as default command
let commandName = 'coreutils';
let commandDisplayName = '';
let commandArgs = [];
if (typeof args[0] === 'string' && WASI_COMMAND_MAPPING[args[0]]) {
commandName = args[0];
}
if (commandName === 'coreutils') {
commandArgs = [...args];
} else {
commandDisplayName = commandName;
commandArgs = [...args.slice(1)];
}

// Load the WASM module.
const { wasm, fs, module } = await requireWasiEnv(
context,
WASI_COMMAND_MAPPING[commandName]
);

// Create a pseudoterminal to provide stdio to the WASM process.
const { pty } = requirePseudoTerminal(wasm, commandName);
// Create a WASM process.
pty.write(
new TextEncoder().encode(
ansiColor(
`$ ${[commandDisplayName, ...commandArgs]?.join(' ')}\n\n`,
'green'
)
)
);

// Handle mock command
if (
typeof commandArgs[0] === 'string' &&
WASI_MOCK_COMMAND_RESPONSE[commandArgs[0]]
) {
pty.write(
new TextEncoder().encode(
WASI_MOCK_COMMAND_RESPONSE[commandArgs[0]].trim() + '\n\n'
)
);
return;
}

// Run real command
const process = await wasm.createProcess(commandName, module, {
rootFileSystem: fs,
stdio: pty.stdio,
args: commandArgs,
});
// Run the process and wait for its result.
const result = await process.run();
if (result !== 0) {
throw new Error(
`Process ${commandName} ${commandArgs.join(' ')} ended with error: ${result}`
);
}
pty.write(new TextEncoder().encode('\n\n'));
}

This file was deleted.

This file was deleted.

This file was deleted.

0 comments on commit e8d8731

Please # to comment.