diff --git a/packages/extensions/js-runner-and-debugger/src/web/services/file-system.ts b/packages/extensions/js-runner-and-debugger/src/web/services/file-system.ts index 65f9989..2ba20e8 100644 --- a/packages/extensions/js-runner-and-debugger/src/web/services/file-system.ts +++ b/packages/extensions/js-runner-and-debugger/src/web/services/file-system.ts @@ -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; }'), diff --git a/packages/extensions/js-runner-and-debugger/src/web/services/js-runner.ts b/packages/extensions/js-runner-and-debugger/src/web/services/js-runner.ts index 5954739..5d9332e 100644 --- a/packages/extensions/js-runner-and-debugger/src/web/services/js-runner.ts +++ b/packages/extensions/js-runner-and-debugger/src/web/services/js-runner.ts @@ -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) { @@ -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) { diff --git a/packages/extensions/js-runner-and-debugger/src/web/services/wasi.ts b/packages/extensions/js-runner-and-debugger/src/web/services/wasi.ts index 38e3085..459c877 100644 --- a/packages/extensions/js-runner-and-debugger/src/web/services/wasi.ts +++ b/packages/extensions/js-runner-and-debugger/src/web/services/wasi.ts @@ -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); } diff --git a/packages/extensions/js-runner-and-debugger/src/web/utils/wasi.ts b/packages/extensions/js-runner-and-debugger/src/web/utils/wasi.ts new file mode 100644 index 0000000..411c68f --- /dev/null +++ b/packages/extensions/js-runner-and-debugger/src/web/utils/wasi.ts @@ -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 = {}; + +// Load the WASM module. +export async function requireWasiEnv( + context: ExtensionContext, + wasmPath: string +): Promise { + 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 { + // 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 = { + coreutils: 'assets/coreutils/coreutils.async.wasm', + esbuild: 'assets/esbuild-wasm-wasi/esbuild.wasm', +}; +const WASI_MOCK_COMMAND_RESPONSE: Record = {}; +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')); +} diff --git a/packages/extensions/js-runner-and-debugger/src/web/utils/wasi/coreutils.ts b/packages/extensions/js-runner-and-debugger/src/web/utils/wasi/coreutils.ts deleted file mode 100644 index 89136bf..0000000 --- a/packages/extensions/js-runner-and-debugger/src/web/utils/wasi/coreutils.ts +++ /dev/null @@ -1,30 +0,0 @@ -import vscode from 'vscode'; -import { requirePseudoTerminal, requireWasiEnv } from './wasi'; -import { ansiColor } from '../ansi-color'; - -export async function coreutils( - context: vscode.ExtensionContext, - args: (string | vscode.Uri)[] -) { - // Load the WASM module. - const { wasm, fs, module } = await requireWasiEnv( - context, - 'assets/coreutils/coreutils.async.wasm' - ); - // Create a pseudoterminal to provide stdio to the WASM process. - const { pty } = requirePseudoTerminal(wasm, 'coreutils'); - // Create a WASM process. - pty.write( - new TextEncoder().encode(ansiColor(`$ ${args?.join(' ')}\n\n`, 'green')) - ); - const process = await wasm.createProcess('coreutils', module, { - rootFileSystem: fs, - stdio: pty.stdio, - args, - }); - // Run the process and wait for its result. - const result = await process.run(); - if (result !== 0) { - throw new Error(`Process coreutils ended with error: ${result}`); - } -} diff --git a/packages/extensions/js-runner-and-debugger/src/web/utils/wasi/esbuild.ts b/packages/extensions/js-runner-and-debugger/src/web/utils/wasi/esbuild.ts deleted file mode 100644 index bfdc592..0000000 --- a/packages/extensions/js-runner-and-debugger/src/web/utils/wasi/esbuild.ts +++ /dev/null @@ -1,50 +0,0 @@ -import vscode from 'vscode'; -import { requirePseudoTerminal, requireWasiEnv } from './wasi'; -import { ansiColor } from '../ansi-color'; - -const ESBUILD_WASM_WASI_PATH = 'assets/esbuild-wasm-wasi/esbuild.wasm'; - -export async function esbuild( - context: vscode.ExtensionContext, - args: (string | vscode.Uri)[] -) { - // Load the WASM module. - const { wasm, fs, module } = await requireWasiEnv( - context, - ESBUILD_WASM_WASI_PATH - ); - // Create a pseudoterminal to provide stdio to the WASM process. - const { pty } = requirePseudoTerminal(wasm, 'esbuild'); - pty.write( - new TextEncoder().encode( - ansiColor(`$ esbuild ${args?.join(' ')}\n\n`, 'green') - ) - ); - // Create a WASM process. - const process = await wasm.createProcess('esbuild', module, { - rootFileSystem: fs, - stdio: pty.stdio, - args, - }); - // Run the process and wait for its result. - const result = await process.run(); - if (result !== 0) { - throw new Error(`Process esbuild ended with error: ${result}`); - } -} - -/** - * 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 { - // 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; -} diff --git a/packages/extensions/js-runner-and-debugger/src/web/utils/wasi/wasi.ts b/packages/extensions/js-runner-and-debugger/src/web/utils/wasi/wasi.ts deleted file mode 100644 index f80c3e2..0000000 --- a/packages/extensions/js-runner-and-debugger/src/web/utils/wasi/wasi.ts +++ /dev/null @@ -1,55 +0,0 @@ -import vscode from 'vscode'; -import { workspace, ExtensionContext, Uri } from 'vscode'; -import { Wasm, RootFileSystem, WasmPseudoterminal } from '@vscode/wasm-wasi'; - -export interface WasiEnv { - wasm: Wasm; - fs: RootFileSystem; - module: WebAssembly.Module; -} - -const wasiEnvMap: Record = {}; - -// Load the WASM module. -export async function requireWasiEnv( - context: ExtensionContext, - wasmPath: string -): Promise { - 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; -} - -export interface RunWasiCommandOptions { - prerun?: (wasiEnv: WasiEnv, wasiTerminal: WasiTerminal) => void; - postrun?: (wasiEnv: WasiEnv, wasiTerminal: WasiTerminal) => void; -}