diff options
Diffstat (limited to 'bundle/src/main.js')
| -rw-r--r-- | bundle/src/main.js | 71 |
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; |
