wasm-idle 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -2
- package/dist/clang/app.d.ts +4 -0
- package/dist/clang/app.js +330 -15
- package/dist/clang/index.d.ts +10 -0
- package/dist/clang/index.js +284 -25
- package/dist/debug/controller.svelte.d.ts +43 -0
- package/dist/debug/controller.svelte.js +268 -0
- package/dist/debug/editor/index.d.ts +2 -1
- package/dist/debug/editor/index.js +1 -1
- package/dist/debug/editor/monaco.d.ts +8 -0
- package/dist/debug/editor/monaco.js +23 -0
- package/dist/debug/expression.d.ts +24 -0
- package/dist/debug/expression.js +307 -138
- package/dist/debug/index.d.ts +4 -1
- package/dist/debug/index.js +2 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.js +3 -3
- package/dist/playground/assetBridge.d.ts +20 -0
- package/dist/playground/assetBridge.js +192 -0
- package/dist/playground/assets.d.ts +37 -0
- package/dist/playground/assets.js +105 -0
- package/dist/playground/clang.d.ts +11 -1
- package/dist/playground/clang.js +58 -4
- package/dist/playground/index.d.ts +5 -5
- package/dist/playground/index.js +48 -6
- package/dist/playground/java.d.ts +7 -2
- package/dist/playground/java.js +31 -5
- package/dist/playground/javaStdin.js +235 -2
- package/dist/playground/options.d.ts +18 -0
- package/dist/playground/options.js +15 -1
- package/dist/playground/python.d.ts +7 -1
- package/dist/playground/python.js +50 -5
- package/dist/playground/rust.d.ts +32 -0
- package/dist/playground/rust.js +175 -0
- package/dist/playground/sandbox.d.ts +19 -4
- package/dist/playground/wasmRustVersion.d.ts +1 -0
- package/dist/playground/wasmRustVersion.js +1 -0
- package/dist/playground/worker/assets.d.ts +12 -0
- package/dist/playground/worker/assets.js +257 -0
- package/dist/playground/worker/clang.js +22 -6
- package/dist/playground/worker/java.js +15 -35
- package/dist/playground/worker/python.js +13 -33
- package/dist/playground/worker/rust.d.ts +1 -0
- package/dist/playground/worker/rust.js +159 -0
- package/dist/terminal/Terminal.svelte +121 -18
- package/dist/terminal/Terminal.svelte.d.ts +4 -0
- package/dist/terminal/theme.js +2 -2
- package/dist/terminal/types.d.ts +4 -0
- package/package.json +19 -13
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { flushQueuedStdin, readBufferedStdin, waitForBufferedStdin } from '../stdinBuffer';
|
|
2
|
+
import { configureWorkerRuntimeAssets, handleWorkerAssetMessage } from './assets';
|
|
2
3
|
self.document = {
|
|
3
4
|
querySelector() {
|
|
4
5
|
return null;
|
|
@@ -7,8 +8,7 @@ self.document = {
|
|
|
7
8
|
return [];
|
|
8
9
|
}
|
|
9
10
|
};
|
|
10
|
-
let stdinBufferPyodide, debugBufferPyodide, watchBufferPyodide, watchResultBufferPyodide, interruptBufferPyodide, pyodide,
|
|
11
|
-
let jungolRobotReady = false;
|
|
11
|
+
let stdinBufferPyodide, debugBufferPyodide, watchBufferPyodide, watchResultBufferPyodide, interruptBufferPyodide, pyodide, baseUrl = '', packageBaseUrl = '';
|
|
12
12
|
const imageHook = `
|
|
13
13
|
if not globals().get("__wasm_idle_img_inited__", False):
|
|
14
14
|
globals()["__wasm_idle_img_inited__"] = True
|
|
@@ -157,33 +157,12 @@ async function loadPyodide(path) {
|
|
|
157
157
|
if (pyodide)
|
|
158
158
|
return;
|
|
159
159
|
const { loadPyodide } = await import('pyodide');
|
|
160
|
-
pyodide = await loadPyodide({ indexURL: path
|
|
161
|
-
packageBaseUrl = path
|
|
160
|
+
pyodide = await loadPyodide({ indexURL: path });
|
|
161
|
+
packageBaseUrl = path;
|
|
162
162
|
const setCdnUrl = pyodide?.setCdnUrl;
|
|
163
163
|
if (typeof setCdnUrl === 'function')
|
|
164
164
|
setCdnUrl(packageBaseUrl);
|
|
165
165
|
}
|
|
166
|
-
async function ensureRobotJungol() {
|
|
167
|
-
if (jungolRobotReady || !pyodide)
|
|
168
|
-
return;
|
|
169
|
-
try {
|
|
170
|
-
const res = await fetch(path + '/jungol-robot/jungol_robot.zip');
|
|
171
|
-
if (!res.ok)
|
|
172
|
-
throw new Error('jungol-robot zip not found');
|
|
173
|
-
const data = await res.arrayBuffer();
|
|
174
|
-
const siteRaw = pyodide.runPython('import site; site.getsitepackages()[0]');
|
|
175
|
-
const sitePath = typeof siteRaw === 'string' ? siteRaw : siteRaw.toString();
|
|
176
|
-
const unpackArchive = pyodide?.unpackArchive;
|
|
177
|
-
if (typeof unpackArchive !== 'function')
|
|
178
|
-
throw new Error('pyodide.unpackArchive unavailable');
|
|
179
|
-
unpackArchive(data, 'zip', sitePath);
|
|
180
|
-
pyodide.runPython('import importlib; importlib.invalidate_caches()');
|
|
181
|
-
jungolRobotReady = true;
|
|
182
|
-
}
|
|
183
|
-
catch (e) {
|
|
184
|
-
console.warn('jungol-robot preload failed', e);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
166
|
async function loadPackages(code) {
|
|
188
167
|
if (!code)
|
|
189
168
|
return;
|
|
@@ -203,20 +182,22 @@ async function loadPackages(code) {
|
|
|
203
182
|
}
|
|
204
183
|
}
|
|
205
184
|
self.onmessage = async (event) => {
|
|
206
|
-
|
|
185
|
+
if (handleWorkerAssetMessage(event.data))
|
|
186
|
+
return;
|
|
187
|
+
const { code, buffer, debugBuffer, watchBuffer, watchResultBuffer, load, interrupt, assets, prepare, debug = false, breakpoints = [], pauseOnEntry = false } = event.data;
|
|
207
188
|
if (load) {
|
|
208
|
-
|
|
189
|
+
const runtimeAssets = assets;
|
|
190
|
+
baseUrl = runtimeAssets?.baseUrl || baseUrl;
|
|
191
|
+
configureWorkerRuntimeAssets(runtimeAssets || null);
|
|
209
192
|
postMessage({ output: 'Loading Pyodide...' });
|
|
210
|
-
await loadPyodide(
|
|
211
|
-
await ensureRobotJungol();
|
|
193
|
+
await loadPyodide(baseUrl);
|
|
212
194
|
postMessage({ output: ' Done.\n\r' });
|
|
213
195
|
postMessage({ load: true });
|
|
214
196
|
}
|
|
215
197
|
else if (prepare) {
|
|
216
198
|
postMessage({ output: 'Loading packages...' });
|
|
217
199
|
try {
|
|
218
|
-
await loadPyodide(
|
|
219
|
-
await ensureRobotJungol();
|
|
200
|
+
await loadPyodide(baseUrl);
|
|
220
201
|
await loadPackages(code);
|
|
221
202
|
postMessage({ output: ' Done.\n\r' });
|
|
222
203
|
self.postMessage({ results: true });
|
|
@@ -226,8 +207,7 @@ self.onmessage = async (event) => {
|
|
|
226
207
|
}
|
|
227
208
|
}
|
|
228
209
|
else if (code) {
|
|
229
|
-
await loadPyodide(
|
|
230
|
-
await ensureRobotJungol();
|
|
210
|
+
await loadPyodide(baseUrl);
|
|
231
211
|
await loadPackages(code);
|
|
232
212
|
const ts = Date.now();
|
|
233
213
|
stdinBufferPyodide = new Int32Array(buffer);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { waitForBufferedStdin } from '../stdinBuffer';
|
|
2
|
+
self.document = {
|
|
3
|
+
querySelectorAll() {
|
|
4
|
+
return [];
|
|
5
|
+
}
|
|
6
|
+
};
|
|
7
|
+
let stdinBufferRust = null;
|
|
8
|
+
let compilerUrl = '';
|
|
9
|
+
let runtimeBaseUrl = '';
|
|
10
|
+
let loadedCompilerUrl = '';
|
|
11
|
+
let compilerPromise = null;
|
|
12
|
+
let compiledArtifact = null;
|
|
13
|
+
let compiledCacheKey = '';
|
|
14
|
+
async function loadCompiler(url) {
|
|
15
|
+
if (!url) {
|
|
16
|
+
throw new Error('Rust runtime is not configured. Set PUBLIC_WASM_RUST_COMPILER_URL or runtimeAssets.rust.compilerUrl.');
|
|
17
|
+
}
|
|
18
|
+
if (loadedCompilerUrl === url && compilerPromise) {
|
|
19
|
+
return await compilerPromise;
|
|
20
|
+
}
|
|
21
|
+
loadedCompilerUrl = url;
|
|
22
|
+
try {
|
|
23
|
+
runtimeBaseUrl = new URL('./runtime/', url).toString();
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
runtimeBaseUrl = url;
|
|
27
|
+
}
|
|
28
|
+
compiledArtifact = null;
|
|
29
|
+
compiledCacheKey = '';
|
|
30
|
+
compilerPromise = (async () => {
|
|
31
|
+
const module = await import(/* @vite-ignore */ url);
|
|
32
|
+
const factory = typeof module.createRustCompiler === 'function'
|
|
33
|
+
? module.createRustCompiler
|
|
34
|
+
: typeof module.default === 'function'
|
|
35
|
+
? module.default
|
|
36
|
+
: null;
|
|
37
|
+
if (!factory) {
|
|
38
|
+
throw new Error('wasm-rust module must export createRustCompiler or a default factory');
|
|
39
|
+
}
|
|
40
|
+
if (typeof module.executeBrowserRustArtifact !== 'function') {
|
|
41
|
+
throw new Error('wasm-rust module must export executeBrowserRustArtifact');
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
compiler: await factory(),
|
|
45
|
+
executeBrowserRustArtifact: module.executeBrowserRustArtifact
|
|
46
|
+
};
|
|
47
|
+
})();
|
|
48
|
+
return await compilerPromise;
|
|
49
|
+
}
|
|
50
|
+
self.onmessage = async (event) => {
|
|
51
|
+
const { load, compilerUrl: nextCompilerUrl, buffer, code, prepare, args = [], targetTriple = 'wasm32-wasip1', log } = event.data;
|
|
52
|
+
try {
|
|
53
|
+
if (load) {
|
|
54
|
+
compilerUrl = nextCompilerUrl;
|
|
55
|
+
if (log) {
|
|
56
|
+
console.log(`[wasm-idle:rust-worker] load compilerUrl=${compilerUrl}`);
|
|
57
|
+
}
|
|
58
|
+
await loadCompiler(compilerUrl);
|
|
59
|
+
postMessage({ load: true });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
stdinBufferRust = new Int32Array(buffer);
|
|
63
|
+
const runtime = await loadCompiler(compilerUrl);
|
|
64
|
+
const compileCacheKey = `${targetTriple}\n${code}`;
|
|
65
|
+
if (!compiledArtifact || compiledCacheKey !== compileCacheKey) {
|
|
66
|
+
if (log) {
|
|
67
|
+
console.log(`[wasm-idle:rust-worker] compile start prepare=${String(prepare)} target=${targetTriple} bytes=${code.length}`);
|
|
68
|
+
}
|
|
69
|
+
const result = await runtime.compiler.compile({
|
|
70
|
+
code,
|
|
71
|
+
edition: '2024',
|
|
72
|
+
crateType: 'bin',
|
|
73
|
+
targetTriple,
|
|
74
|
+
prepare,
|
|
75
|
+
log,
|
|
76
|
+
onProgress(progress) {
|
|
77
|
+
postMessage({ progress });
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
if (log) {
|
|
81
|
+
console.log(`[wasm-idle:rust-worker] compile settled success=${String(result.success)} hasWasm=${String(Boolean(result.artifact?.wasm))} stdout=${String(Boolean(result.stdout))} stderr=${String(Boolean(result.stderr))}`);
|
|
82
|
+
}
|
|
83
|
+
for (const diagnostic of result.diagnostics || []) {
|
|
84
|
+
postMessage({ diagnostic });
|
|
85
|
+
}
|
|
86
|
+
if (result.stdout)
|
|
87
|
+
postMessage({ output: result.stdout });
|
|
88
|
+
if (!result.success) {
|
|
89
|
+
throw new Error(result.stderr ||
|
|
90
|
+
result.diagnostics?.map((diagnostic) => diagnostic.message).join('\n') ||
|
|
91
|
+
'Rust compilation failed');
|
|
92
|
+
}
|
|
93
|
+
if (result.stderr)
|
|
94
|
+
postMessage({ output: result.stderr });
|
|
95
|
+
if (!result.artifact?.wasm) {
|
|
96
|
+
throw new Error('wasm-rust did not return a wasm artifact');
|
|
97
|
+
}
|
|
98
|
+
compiledArtifact = result.artifact;
|
|
99
|
+
compiledCacheKey = compileCacheKey;
|
|
100
|
+
if (log) {
|
|
101
|
+
console.log(`[wasm-idle:rust-worker] cached artifact target=${compiledArtifact.targetTriple} format=${compiledArtifact.format}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (prepare) {
|
|
105
|
+
if (log) {
|
|
106
|
+
console.log('[wasm-idle:rust-worker] prepare complete');
|
|
107
|
+
}
|
|
108
|
+
postMessage({ results: true });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (log) {
|
|
112
|
+
console.log(`[wasm-idle:rust-worker] runtime start target=${compiledArtifact.targetTriple} format=${compiledArtifact.format}`);
|
|
113
|
+
}
|
|
114
|
+
const execution = await runtime.executeBrowserRustArtifact(compiledArtifact, runtimeBaseUrl, {
|
|
115
|
+
args,
|
|
116
|
+
env: {
|
|
117
|
+
USER: 'jungol'
|
|
118
|
+
},
|
|
119
|
+
stdin: () => {
|
|
120
|
+
const chunk = waitForBufferedStdin(stdinBufferRust, () => postMessage({ buffer: true }));
|
|
121
|
+
if (chunk == null) {
|
|
122
|
+
if (log) {
|
|
123
|
+
console.log('[wasm-idle:rust-stdin] fd_read(bytes=0, eof=true)');
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
if (log) {
|
|
128
|
+
console.log(`[wasm-idle:rust-stdin] fd_fill(bytes=${new TextEncoder().encode(chunk).byteLength}, text=${JSON.stringify(chunk)})`);
|
|
129
|
+
}
|
|
130
|
+
return chunk;
|
|
131
|
+
},
|
|
132
|
+
stdout: (output) => {
|
|
133
|
+
if (output) {
|
|
134
|
+
postMessage({ output });
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
stderr: (output) => {
|
|
138
|
+
if (output) {
|
|
139
|
+
postMessage({ output });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
if (log) {
|
|
144
|
+
console.log(`[wasm-idle:rust-worker] wasi run complete exitCode=${String(execution.exitCode)}`);
|
|
145
|
+
}
|
|
146
|
+
if (execution.exitCode !== 0) {
|
|
147
|
+
throw new Error(execution.stderr
|
|
148
|
+
? `Rust program exited with code ${execution.exitCode}\n${execution.stderr}`
|
|
149
|
+
: `Rust program exited with code ${execution.exitCode}`);
|
|
150
|
+
}
|
|
151
|
+
postMessage({ results: true });
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
if (log) {
|
|
155
|
+
console.error('[wasm-idle:rust-worker] failed', error);
|
|
156
|
+
}
|
|
157
|
+
postMessage({ error: error?.message || String(error) });
|
|
158
|
+
}
|
|
159
|
+
};
|
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
DebugSessionEvent,
|
|
6
6
|
SandboxExecutionOptions
|
|
7
7
|
} from '../playground/options';
|
|
8
|
-
import type {
|
|
8
|
+
import type { PlaygroundRuntimeAssets } from '../playground/assets';
|
|
9
|
+
import type {
|
|
10
|
+
BoundSandbox,
|
|
11
|
+
PlaygroundBinding,
|
|
12
|
+
SandboxRuntimeAssets
|
|
13
|
+
} from '../playground/sandbox';
|
|
9
14
|
import type { TerminalControl } from './types';
|
|
10
15
|
import { Theme, registerAllPlugins } from './';
|
|
11
16
|
import load from '../playground';
|
|
@@ -16,6 +21,8 @@
|
|
|
16
21
|
interface Props {
|
|
17
22
|
dark?: boolean;
|
|
18
23
|
path?: string;
|
|
24
|
+
runtimeAssets?: string | PlaygroundRuntimeAssets;
|
|
25
|
+
playground?: PlaygroundBinding;
|
|
19
26
|
font?: string;
|
|
20
27
|
|
|
21
28
|
onload?: () => void;
|
|
@@ -30,6 +37,8 @@
|
|
|
30
37
|
let {
|
|
31
38
|
dark = false,
|
|
32
39
|
path = '',
|
|
40
|
+
runtimeAssets = undefined,
|
|
41
|
+
playground = undefined,
|
|
33
42
|
font = "'D2 coding', monospace",
|
|
34
43
|
onload,
|
|
35
44
|
onfinish,
|
|
@@ -44,13 +53,37 @@
|
|
|
44
53
|
clientWidth = $state(0),
|
|
45
54
|
clientHeight = $state(0),
|
|
46
55
|
term = $state<TerminalType>(),
|
|
56
|
+
debugOutput = $state(''),
|
|
47
57
|
finish = true,
|
|
48
58
|
input = '',
|
|
49
|
-
sandbox:
|
|
59
|
+
sandbox: BoundSandbox,
|
|
50
60
|
first = true,
|
|
51
61
|
tc = 0,
|
|
52
62
|
plugin = $state(),
|
|
53
|
-
ll: string | null = null
|
|
63
|
+
ll: string | null = null,
|
|
64
|
+
loadedRuntimeAssets = $state<SandboxRuntimeAssets | undefined>(undefined),
|
|
65
|
+
loadedPlayground = $state<PlaygroundBinding | undefined>(undefined),
|
|
66
|
+
stopRequested = false;
|
|
67
|
+
|
|
68
|
+
function writeTerminalOutput(text: string) {
|
|
69
|
+
if (!text) return;
|
|
70
|
+
debugOutput += text;
|
|
71
|
+
term?.write(text);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function phaseProgress(
|
|
75
|
+
progress: { set?: (value: number) => void } | undefined,
|
|
76
|
+
start: number,
|
|
77
|
+
end: number
|
|
78
|
+
) {
|
|
79
|
+
if (!progress) return undefined;
|
|
80
|
+
return {
|
|
81
|
+
set(value: number) {
|
|
82
|
+
const clamped = Number.isFinite(value) ? Math.min(Math.max(value, 0), 1) : 0;
|
|
83
|
+
progress.set?.(start + (end - start) * clamped);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
54
87
|
|
|
55
88
|
function wait() {
|
|
56
89
|
return new Promise<void>((r) => {
|
|
@@ -64,34 +97,46 @@
|
|
|
64
97
|
}
|
|
65
98
|
|
|
66
99
|
async function initSandbox(language: string) {
|
|
100
|
+
const currentPlayground = playground;
|
|
101
|
+
const currentRuntimeAssets = currentPlayground?.runtimeAssets || runtimeAssets || path;
|
|
67
102
|
let _tc = ++tc;
|
|
68
103
|
await wait();
|
|
69
104
|
if (sandbox) await sandbox.clear();
|
|
70
105
|
input = '';
|
|
71
106
|
finish = false;
|
|
72
|
-
if (
|
|
73
|
-
|
|
107
|
+
if (
|
|
108
|
+
ll !== language ||
|
|
109
|
+
loadedRuntimeAssets !== currentRuntimeAssets ||
|
|
110
|
+
loadedPlayground !== currentPlayground
|
|
111
|
+
) {
|
|
112
|
+
sandbox = currentPlayground
|
|
113
|
+
? await currentPlayground.load(language)
|
|
114
|
+
: await load(language, currentRuntimeAssets);
|
|
74
115
|
await sandbox.clear();
|
|
75
116
|
ll = language;
|
|
117
|
+
loadedRuntimeAssets = currentRuntimeAssets;
|
|
118
|
+
loadedPlayground = currentPlayground;
|
|
76
119
|
}
|
|
77
120
|
sandbox.image = onimage;
|
|
78
121
|
sandbox.ondebug = ondebug;
|
|
79
122
|
sandbox.oncompilerdiagnostic = oncompilediagnostic;
|
|
80
123
|
sandbox.output = (output: string) =>
|
|
81
|
-
_tc === tc &&
|
|
124
|
+
_tc === tc && writeTerminalOutput(output.replaceAll('\n', '\r\n'));
|
|
82
125
|
}
|
|
83
126
|
|
|
84
127
|
function runSandbox<T>(pr: Promise<T>) {
|
|
85
128
|
return pr
|
|
86
129
|
.then((x) => {
|
|
87
|
-
|
|
130
|
+
writeTerminalOutput(`\r\nProcess finished after ${sandbox.elapse}ms\u001B[?25l`);
|
|
88
131
|
return x;
|
|
89
132
|
})
|
|
90
133
|
.catch((msg) => {
|
|
91
|
-
|
|
134
|
+
if (stopRequested) return false;
|
|
135
|
+
writeTerminalOutput(`\r\n\x1B[1;3;31m${msg}\u001B[?25l`);
|
|
92
136
|
return false;
|
|
93
137
|
})
|
|
94
138
|
.finally(() => {
|
|
139
|
+
stopRequested = false;
|
|
95
140
|
onfinish?.();
|
|
96
141
|
finish = true;
|
|
97
142
|
if (term) term.options.cursorBlink = false;
|
|
@@ -102,6 +147,7 @@
|
|
|
102
147
|
await wait();
|
|
103
148
|
if (!term) return;
|
|
104
149
|
term.options.cursorBlink = blink;
|
|
150
|
+
term.write('\u001B[?25h');
|
|
105
151
|
term.focus();
|
|
106
152
|
|
|
107
153
|
if (!first) term?.write(`\r\n\x1b[0m`);
|
|
@@ -116,7 +162,7 @@
|
|
|
116
162
|
|
|
117
163
|
function submitCurrentInput() {
|
|
118
164
|
term?.write('\r\n');
|
|
119
|
-
sandbox
|
|
165
|
+
sandbox?.write?.(input + '\n');
|
|
120
166
|
input = '';
|
|
121
167
|
}
|
|
122
168
|
|
|
@@ -128,12 +174,21 @@
|
|
|
128
174
|
}
|
|
129
175
|
}
|
|
130
176
|
|
|
177
|
+
async function waitForInput() {
|
|
178
|
+
await wait();
|
|
179
|
+
const startedAt = Date.now();
|
|
180
|
+
while (!sandbox && Date.now() - startedAt < 30_000) {
|
|
181
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
131
185
|
const terminalControl: TerminalControl = {
|
|
132
186
|
async clear() {
|
|
133
187
|
await wait();
|
|
134
188
|
term?.reset();
|
|
135
189
|
term?.write(`\u001B[?25l\x1b[0m\x1b[?25h`);
|
|
136
190
|
if (term) term.options.cursorBlink = false;
|
|
191
|
+
debugOutput = '';
|
|
137
192
|
input = '';
|
|
138
193
|
first = true;
|
|
139
194
|
await new Promise((r) => setTimeout(r, 100));
|
|
@@ -146,11 +201,23 @@
|
|
|
146
201
|
args: string[] = [],
|
|
147
202
|
options: SandboxExecutionOptions = {}
|
|
148
203
|
) {
|
|
204
|
+
prog?.set?.(0);
|
|
205
|
+
const loadProgress =
|
|
206
|
+
language === 'RUST' ? phaseProgress(prog, 0, 0.05) : phaseProgress(prog, 0, 0.85);
|
|
207
|
+
const prepareProgress =
|
|
208
|
+
language === 'RUST'
|
|
209
|
+
? phaseProgress(prog, 0.05, 0.99)
|
|
210
|
+
: phaseProgress(prog, 0.85, 0.99);
|
|
149
211
|
await Promise.all([
|
|
150
|
-
initSandbox(language).then(() =>
|
|
212
|
+
initSandbox(language).then(() =>
|
|
213
|
+
sandbox.load(code, log, args, options, loadProgress)
|
|
214
|
+
),
|
|
151
215
|
initTerm(false)
|
|
152
216
|
]);
|
|
153
|
-
|
|
217
|
+
prepareProgress?.set?.(0);
|
|
218
|
+
return !!(await runSandbox(
|
|
219
|
+
sandbox.run(code, true, log, prepareProgress, args, options)
|
|
220
|
+
));
|
|
154
221
|
},
|
|
155
222
|
async run(
|
|
156
223
|
language: string,
|
|
@@ -161,7 +228,7 @@
|
|
|
161
228
|
options: SandboxExecutionOptions = {}
|
|
162
229
|
) {
|
|
163
230
|
await Promise.all([
|
|
164
|
-
initSandbox(language).then(() => sandbox.load(
|
|
231
|
+
initSandbox(language).then(() => sandbox.load(code, log, args, options, prog)),
|
|
165
232
|
initTerm()
|
|
166
233
|
]);
|
|
167
234
|
return await runSandbox(sandbox.run(code, false, log, prog, args, options));
|
|
@@ -171,18 +238,36 @@
|
|
|
171
238
|
term?.dispose();
|
|
172
239
|
if (sandbox) await sandbox.clear();
|
|
173
240
|
},
|
|
241
|
+
async stop() {
|
|
242
|
+
await wait();
|
|
243
|
+
stopRequested = true;
|
|
244
|
+
finish = true;
|
|
245
|
+
if (sandbox?.kill) sandbox.kill();
|
|
246
|
+
else sandbox?.terminate?.();
|
|
247
|
+
},
|
|
174
248
|
async debugCommand(command: DebugCommand) {
|
|
175
249
|
await wait();
|
|
176
250
|
sandbox.debugCommand?.(command);
|
|
177
251
|
},
|
|
252
|
+
async setBreakpoints(lines: number[]) {
|
|
253
|
+
await wait();
|
|
254
|
+
sandbox.setBreakpoints?.(lines);
|
|
255
|
+
},
|
|
178
256
|
async debugEvaluate(expression: string) {
|
|
179
257
|
await wait();
|
|
180
258
|
return (await sandbox.debugEvaluate?.(expression)) || '?';
|
|
181
259
|
},
|
|
260
|
+
async waitForInput() {
|
|
261
|
+
await waitForInput();
|
|
262
|
+
},
|
|
182
263
|
async write(input: string) {
|
|
183
|
-
await
|
|
264
|
+
await waitForInput();
|
|
184
265
|
applyPastedText(input);
|
|
185
266
|
if (!input.endsWith('\n') && !input.endsWith('\r')) submitCurrentInput();
|
|
267
|
+
},
|
|
268
|
+
async eof() {
|
|
269
|
+
await waitForInput();
|
|
270
|
+
sandbox?.eof?.();
|
|
186
271
|
}
|
|
187
272
|
};
|
|
188
273
|
|
|
@@ -214,9 +299,19 @@
|
|
|
214
299
|
});
|
|
215
300
|
term.open(ref);
|
|
216
301
|
term.onKey((e: { key: string; domEvent: KeyboardEvent }) => {
|
|
217
|
-
if (
|
|
302
|
+
if (!term) return;
|
|
218
303
|
const ev = e.domEvent;
|
|
219
304
|
const printable = !ev.altKey && !ev.ctrlKey && !ev.metaKey;
|
|
305
|
+
const isCopyShortcut = (ev.ctrlKey || ev.metaKey) && ev.key.toLowerCase() === 'c';
|
|
306
|
+
if (isCopyShortcut && term.hasSelection()) {
|
|
307
|
+
const selectedText = term.getSelection();
|
|
308
|
+
if (selectedText) {
|
|
309
|
+
ev.preventDefault();
|
|
310
|
+
navigator.clipboard.writeText(selectedText).catch(() => {});
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (finish) return;
|
|
220
315
|
onkey?.(ev);
|
|
221
316
|
if (ev.key === 'Enter') {
|
|
222
317
|
submitCurrentInput();
|
|
@@ -233,12 +328,13 @@
|
|
|
233
328
|
) {
|
|
234
329
|
appendInputText(e.key);
|
|
235
330
|
}
|
|
236
|
-
} else if (
|
|
331
|
+
} else if (isCopyShortcut) {
|
|
332
|
+
ev.preventDefault();
|
|
237
333
|
sandbox.kill?.();
|
|
238
|
-
} else if ((ev.ctrlKey || ev.metaKey) && ev.key === 'd') {
|
|
334
|
+
} else if ((ev.ctrlKey || ev.metaKey) && ev.key.toLowerCase() === 'd') {
|
|
239
335
|
if (input.length > 0) submitCurrentInput();
|
|
240
|
-
|
|
241
|
-
} else if ((ev.ctrlKey || ev.metaKey) && ev.key === 'v') {
|
|
336
|
+
sandbox?.eof?.();
|
|
337
|
+
} else if ((ev.ctrlKey || ev.metaKey) && ev.key.toLowerCase() === 'v') {
|
|
242
338
|
navigator.clipboard.readText().then((text) => {
|
|
243
339
|
applyPastedText(text);
|
|
244
340
|
});
|
|
@@ -258,6 +354,7 @@
|
|
|
258
354
|
|
|
259
355
|
<main>
|
|
260
356
|
<div bind:this={ref} bind:clientWidth bind:clientHeight></div>
|
|
357
|
+
<pre data-testid="terminal-debug-output" style="display: none;">{debugOutput}</pre>
|
|
261
358
|
</main>
|
|
262
359
|
|
|
263
360
|
<style>
|
|
@@ -272,4 +369,10 @@
|
|
|
272
369
|
width: 100%;
|
|
273
370
|
height: 100%;
|
|
274
371
|
}
|
|
372
|
+
|
|
373
|
+
:global(.xterm),
|
|
374
|
+
:global(.xterm .xterm-viewport),
|
|
375
|
+
:global(.xterm .composition-view) {
|
|
376
|
+
background-color: transparent;
|
|
377
|
+
}
|
|
275
378
|
</style>
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import type { CompilerDiagnostic, DebugSessionEvent } from '../playground/options';
|
|
2
|
+
import type { PlaygroundRuntimeAssets } from '../playground/assets';
|
|
3
|
+
import type { PlaygroundBinding } from '../playground/sandbox';
|
|
2
4
|
import type { TerminalControl } from './types';
|
|
3
5
|
import '@xterm/xterm/css/xterm.css';
|
|
4
6
|
interface Props {
|
|
5
7
|
dark?: boolean;
|
|
6
8
|
path?: string;
|
|
9
|
+
runtimeAssets?: string | PlaygroundRuntimeAssets;
|
|
10
|
+
playground?: PlaygroundBinding;
|
|
7
11
|
font?: string;
|
|
8
12
|
onload?: () => void;
|
|
9
13
|
onfinish?: () => void;
|
package/dist/terminal/theme.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const theme = {
|
|
2
2
|
Tango_Dark: {
|
|
3
3
|
foreground: '#eee',
|
|
4
|
-
background: '
|
|
4
|
+
background: 'rgba(0, 0, 0, 0)',
|
|
5
5
|
cursor: '#FFFFFF',
|
|
6
6
|
cursorAccent: '#ffffff',
|
|
7
7
|
selection: '#ffffff44',
|
|
@@ -24,7 +24,7 @@ const theme = {
|
|
|
24
24
|
},
|
|
25
25
|
Tango_Light: {
|
|
26
26
|
foreground: '#111',
|
|
27
|
-
background: '
|
|
27
|
+
background: 'rgba(255, 255, 255, 0)',
|
|
28
28
|
cursor: '#000000',
|
|
29
29
|
cursorAccent: '#000000',
|
|
30
30
|
selection: '#00000044',
|
package/dist/terminal/types.d.ts
CHANGED
|
@@ -7,8 +7,12 @@ export interface TerminalControl {
|
|
|
7
7
|
prepare: (language: string, code: string, log?: boolean, prog?: ProgressLike, args?: string[], options?: SandboxExecutionOptions) => Promise<boolean>;
|
|
8
8
|
run: (language: string, code: string, log?: boolean, prog?: ProgressLike, args?: string[], options?: SandboxExecutionOptions) => Promise<boolean | string>;
|
|
9
9
|
destroy: () => Promise<void>;
|
|
10
|
+
stop?: () => Promise<void>;
|
|
10
11
|
debugCommand: (command: DebugCommand) => Promise<void>;
|
|
12
|
+
setBreakpoints?: (lines: number[]) => Promise<void>;
|
|
11
13
|
debugEvaluate?: (expression: string) => Promise<string>;
|
|
14
|
+
waitForInput?: () => Promise<void>;
|
|
12
15
|
write: (input: string) => Promise<void>;
|
|
16
|
+
eof?: () => Promise<void>;
|
|
13
17
|
}
|
|
14
18
|
export type { CompilerDiagnostic, DebugCommand, DebugFrame, DebugSessionEvent, DebugVariable, SandboxExecutionOptions };
|
package/package.json
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wasm-idle",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A simple web app to run C++/Python code in the browser",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "A simple web app to run C++/Python/Java code in the browser",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"dev": "vite dev",
|
|
9
9
|
"build": "vite build && npm run package",
|
|
10
10
|
"preview": "vite preview",
|
|
11
|
+
"probe:rust-browser": "pnpm run sync:wasm-rust && pnpm build && node scripts/probe-rust-browser.mjs",
|
|
12
|
+
"test:browser:playwright": "pnpm run sync:wasm-rust && pnpm build && WASM_IDLE_RUN_REAL_BROWSER_RUST=1 pnpm exec vitest run src/lib/playground/rust.playwright.test.ts",
|
|
13
|
+
"sync:wasm-rust": "node scripts/sync-wasm-rust.mjs",
|
|
11
14
|
"package": "svelte-kit sync && svelte-package && publint",
|
|
12
15
|
"prepublishOnly": "npm run package",
|
|
13
16
|
"test": "vitest run",
|
|
@@ -22,32 +25,35 @@
|
|
|
22
25
|
"@eslint/js": "^10.0.1",
|
|
23
26
|
"@sveltejs/adapter-auto": "^7.0.1",
|
|
24
27
|
"@sveltejs/adapter-static": "^3.0.10",
|
|
25
|
-
"@sveltejs/kit": "^2.
|
|
28
|
+
"@sveltejs/kit": "^2.55.0",
|
|
26
29
|
"@sveltejs/package": "^2.5.7",
|
|
27
|
-
"@sveltejs/vite-plugin-svelte": "^
|
|
30
|
+
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
|
28
31
|
"@testing-library/svelte": "^5.3.1",
|
|
29
32
|
"@testing-library/user-event": "^14.6.1",
|
|
30
|
-
"@
|
|
31
|
-
"@typescript-eslint/
|
|
32
|
-
"eslint": "^
|
|
33
|
+
"@types/node": "^25.5.0",
|
|
34
|
+
"@typescript-eslint/eslint-plugin": "^8.57.2",
|
|
35
|
+
"@typescript-eslint/parser": "^8.57.2",
|
|
36
|
+
"eslint": "^10.1.0",
|
|
33
37
|
"eslint-config-prettier": "^10.1.8",
|
|
34
|
-
"eslint-plugin-svelte": "^3.
|
|
38
|
+
"eslint-plugin-svelte": "^3.16.0",
|
|
35
39
|
"eslint-plugin-svelte3": "^4.0.0",
|
|
36
40
|
"gh-pages": "^6.3.0",
|
|
37
41
|
"globals": "^17.4.0",
|
|
38
|
-
"jsdom": "^
|
|
42
|
+
"jsdom": "^29.0.1",
|
|
39
43
|
"monaco-editor": "^0.55.1",
|
|
44
|
+
"playwright-core": "^1.58.2",
|
|
40
45
|
"prettier": "^3.8.1",
|
|
41
46
|
"prettier-plugin-svelte": "^3.5.1",
|
|
42
|
-
"svelte": "^5.
|
|
47
|
+
"svelte": "^5.55.0",
|
|
43
48
|
"svelte-check": "^4.4.5",
|
|
44
49
|
"tslib": "^2.8.1",
|
|
45
50
|
"typescript": "^5.9.3",
|
|
46
|
-
"typescript-eslint": "^8.57.
|
|
47
|
-
"vite": "^
|
|
48
|
-
"vitest": "^4.
|
|
51
|
+
"typescript-eslint": "^8.57.1",
|
|
52
|
+
"vite": "^8.0.3",
|
|
53
|
+
"vitest": "^4.1.1"
|
|
49
54
|
},
|
|
50
55
|
"dependencies": {
|
|
56
|
+
"@bjorn3/browser_wasi_shim": "^0.4.2",
|
|
51
57
|
"@hancomac/monaco-languageclient": "^1.1.0",
|
|
52
58
|
"@xterm/addon-canvas": "^0.7.0",
|
|
53
59
|
"@xterm/addon-fit": "^0.11.0",
|