aboutsummaryrefslogtreecommitdiff
path: root/bundle/src/main.js
diff options
context:
space:
mode:
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;