aboutsummaryrefslogtreecommitdiff
path: root/bundle/src/main.js
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-21 11:08:50 +0000
committer_ov4 <ov4@ov4.org>2026-10-21 11:08:50 +0000
commit7b94b9003daa1126ce78df37481fc52774c2f0b4 (patch)
treec5e491c2a33d031da2f3400143ec74dab437e291 /bundle/src/main.js
parent5a254e2ab4741bc88c373ae1673c2ad9a7e6a588 (diff)
feat(bundle): run the bundled compiler with input and streaming output
Save before running, close prefilled stdin at EOF, and stream output bytes through Tauri channels. Stop and reap the child before exit or another run. Disable embedded stdout buffering and exercise the runner with real programs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'bundle/src/main.js')
-rw-r--r--bundle/src/main.js71
1 files changed, 69 insertions, 2 deletions
diff --git a/bundle/src/main.js b/bundle/src/main.js
index 0d3a010..56cbff6 100644
--- a/bundle/src/main.js
+++ b/bundle/src/main.js
@@ -1,4 +1,4 @@
-import { invoke } from '@tauri-apps/api/core';
+import { invoke, Channel } from '@tauri-apps/api/core';
import { getCurrentWindow } from '@tauri-apps/api/window';
import { open, save, message } from '@tauri-apps/plugin-dialog';
import { createEditor } from './editor.js';
@@ -6,6 +6,9 @@ import './style.css';
const status = document.querySelector('#status');
const filename = document.querySelector('#filename');
+const input = document.querySelector('#input');
+const output = document.querySelector('#output');
+const runButton = document.querySelector('#run');
const window = getCurrentWindow();
const filters = [{ name: 'Pseudocode', extensions: ['cps'] }];
let path = null;
@@ -13,6 +16,57 @@ let savedText = '';
let busy = false;
let closing = false;
let editor;
+let running = false;
+
+function setRunning(value) {
+ running = value;
+ runButton.textContent = value ? 'Stop' : 'Run';
+ for (const id of ['new', 'open', 'save']) document.getElementById(id).disabled = value;
+ input.disabled = value;
+ editor.updateOptions({ readOnly: value });
+}
+
+async function runFile() {
+ if (!await saveFile()) return;
+ setRunning(true);
+ output.textContent = '';
+ status.textContent = 'Running';
+ const decoders = { stdout: new TextDecoder(), stderr: new TextDecoder() };
+ let pending = '';
+ let frame = null;
+ function flush() {
+ frame = null;
+ if (!pending) return;
+ output.append(document.createTextNode(pending));
+ pending = '';
+ output.scrollTop = output.scrollHeight;
+ }
+ function append(text) {
+ pending += text;
+ if (frame === null) frame = requestAnimationFrame(flush);
+ }
+ let finish;
+ const finished = new Promise(resolve => { finish = resolve; });
+ const channel = new Channel(event => {
+ if (event.event === 'data') {
+ append(decoders[event.stream].decode(Uint8Array.from(event.bytes), { stream: true }));
+ } else if (event.event === 'finished') {
+ for (const decoder of Object.values(decoders)) append(decoder.decode());
+ if (frame !== null) cancelAnimationFrame(frame);
+ flush();
+ status.textContent = event.stopped ? 'Stopped' : `Exit code: ${event.code ?? 'terminated'}`;
+ finish();
+ }
+ });
+ try {
+ await invoke('run_file', { path, input: input.value, output: channel });
+ await finished;
+ } finally {
+ if (frame !== null) cancelAnimationFrame(frame);
+ flush();
+ setRunning(false);
+ }
+}
function dirty() { return editor.getValue() !== savedText; }
function refreshTitle() {
@@ -43,7 +97,7 @@ async function canReplace() {
return answer === 'Discard' || await saveFile();
}
async function action(callback) {
- if (busy) return;
+ if (busy || running) return;
busy = true;
try { await callback(); } catch (error) { report(error); } finally { busy = false; }
}
@@ -51,6 +105,13 @@ async function action(callback) {
try {
editor = await createEditor(document.querySelector('#editor'));
editor.onDidChangeModelContent(refreshTitle);
+ runButton.onclick = () => {
+ if (running) {
+ invoke('stop_run').catch(report);
+ } else {
+ action(runFile);
+ }
+ };
document.querySelector('#save').onclick = () => action(saveFile);
document.querySelector('#new').onclick = () => action(async () => {
if (!await canReplace()) return;
@@ -80,6 +141,12 @@ try {
await window.onCloseRequested(async event => {
if (closing) return;
event.preventDefault();
+ if (running) {
+ try { await invoke('stop_run'); } catch (error) { report(error); return; }
+ // The command drains pipes before completing; let runFile release its UI lock.
+ busy = false;
+ setRunning(false);
+ }
await action(async () => {
if (!await canReplace()) return;
closing = true;