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.
Files changed (49) hide show
  1. package/README.md +111 -2
  2. package/dist/clang/app.d.ts +4 -0
  3. package/dist/clang/app.js +330 -15
  4. package/dist/clang/index.d.ts +10 -0
  5. package/dist/clang/index.js +284 -25
  6. package/dist/debug/controller.svelte.d.ts +43 -0
  7. package/dist/debug/controller.svelte.js +268 -0
  8. package/dist/debug/editor/index.d.ts +2 -1
  9. package/dist/debug/editor/index.js +1 -1
  10. package/dist/debug/editor/monaco.d.ts +8 -0
  11. package/dist/debug/editor/monaco.js +23 -0
  12. package/dist/debug/expression.d.ts +24 -0
  13. package/dist/debug/expression.js +307 -138
  14. package/dist/debug/index.d.ts +4 -1
  15. package/dist/debug/index.js +2 -1
  16. package/dist/index.d.ts +6 -3
  17. package/dist/index.js +3 -3
  18. package/dist/playground/assetBridge.d.ts +20 -0
  19. package/dist/playground/assetBridge.js +192 -0
  20. package/dist/playground/assets.d.ts +37 -0
  21. package/dist/playground/assets.js +105 -0
  22. package/dist/playground/clang.d.ts +11 -1
  23. package/dist/playground/clang.js +58 -4
  24. package/dist/playground/index.d.ts +5 -5
  25. package/dist/playground/index.js +48 -6
  26. package/dist/playground/java.d.ts +7 -2
  27. package/dist/playground/java.js +31 -5
  28. package/dist/playground/javaStdin.js +235 -2
  29. package/dist/playground/options.d.ts +18 -0
  30. package/dist/playground/options.js +15 -1
  31. package/dist/playground/python.d.ts +7 -1
  32. package/dist/playground/python.js +50 -5
  33. package/dist/playground/rust.d.ts +32 -0
  34. package/dist/playground/rust.js +175 -0
  35. package/dist/playground/sandbox.d.ts +19 -4
  36. package/dist/playground/wasmRustVersion.d.ts +1 -0
  37. package/dist/playground/wasmRustVersion.js +1 -0
  38. package/dist/playground/worker/assets.d.ts +12 -0
  39. package/dist/playground/worker/assets.js +257 -0
  40. package/dist/playground/worker/clang.js +22 -6
  41. package/dist/playground/worker/java.js +15 -35
  42. package/dist/playground/worker/python.js +13 -33
  43. package/dist/playground/worker/rust.d.ts +1 -0
  44. package/dist/playground/worker/rust.js +159 -0
  45. package/dist/terminal/Terminal.svelte +121 -18
  46. package/dist/terminal/Terminal.svelte.d.ts +4 -0
  47. package/dist/terminal/theme.js +2 -2
  48. package/dist/terminal/types.d.ts +4 -0
  49. 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, path = '', packageBaseUrl = '';
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 + '/pyodide' });
161
- packageBaseUrl = path + '/pyodide';
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
- const { code, buffer, debugBuffer, watchBuffer, watchResultBuffer, load, interrupt, path: _p, prepare, debug = false, breakpoints = [], pauseOnEntry = false } = event.data;
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
- path = _p;
189
+ const runtimeAssets = assets;
190
+ baseUrl = runtimeAssets?.baseUrl || baseUrl;
191
+ configureWorkerRuntimeAssets(runtimeAssets || null);
209
192
  postMessage({ output: 'Loading Pyodide...' });
210
- await loadPyodide(path);
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(path);
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(path);
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 { Sandbox } from '../playground/sandbox';
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: 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 (ll !== language) {
73
- sandbox = await load(language);
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 && term && term.write(output.replaceAll('\n', '\r\n'));
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
- term?.write(`\r\nProcess finished after ${sandbox.elapse}ms\u001B[?25l`);
130
+ writeTerminalOutput(`\r\nProcess finished after ${sandbox.elapse}ms\u001B[?25l`);
88
131
  return x;
89
132
  })
90
133
  .catch((msg) => {
91
- term?.write(`\r\n\x1B[1;3;31m${msg}\u001B[?25l`);
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.write?.(input + '\n');
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(() => sandbox.load(path, code, log, args, options)),
212
+ initSandbox(language).then(() =>
213
+ sandbox.load(code, log, args, options, loadProgress)
214
+ ),
151
215
  initTerm(false)
152
216
  ]);
153
- return !!(await runSandbox(sandbox.run(code, true, log, prog, args, options)));
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(path, code, log, args, options)),
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 wait();
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 (finish || !term) return;
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 ((ev.ctrlKey || ev.metaKey) && ev.key === 'c') {
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
- else sandbox.eof?.();
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;
@@ -1,7 +1,7 @@
1
1
  const theme = {
2
2
  Tango_Dark: {
3
3
  foreground: '#eee',
4
- background: '#111827',
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: '#FFFFFF',
27
+ background: 'rgba(255, 255, 255, 0)',
28
28
  cursor: '#000000',
29
29
  cursorAccent: '#000000',
30
30
  selection: '#00000044',
@@ -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.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.53.4",
28
+ "@sveltejs/kit": "^2.55.0",
26
29
  "@sveltejs/package": "^2.5.7",
27
- "@sveltejs/vite-plugin-svelte": "^6.2.4",
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
- "@typescript-eslint/eslint-plugin": "^8.57.0",
31
- "@typescript-eslint/parser": "^8.57.0",
32
- "eslint": "^10.0.3",
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.15.2",
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": "^28.1.0",
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.53.10",
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.0",
47
- "vite": "^7.3.1",
48
- "vitest": "^4.0.18"
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",