aboutsummaryrefslogtreecommitdiff
path: root/bundle/src/main.js
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-22 01:45:15 +0000
committer_ov4 <ov4@ov4.org>2026-10-22 01:45:15 +0000
commit660d618f8b0df318d636cd780e547890d0248c0c (patch)
treeea3f7746105d271558287ccd7268834245df68fd /bundle/src/main.js
parent427acc420c6fce10fa77e603f7696f3e89d342d6 (diff)
feat(bundle): color compiler diagnostics in Output
The runner now passes -fdiagnostics-color=always, since auto only sees a pipe. src/ansi.js turns stderr's SGR sequences into bold and colored runs, keeping style and cut-off sequences across chunks and dropping other sequences such as gcc's erase-line. Program stdout stays verbatim. Colors come from --vscode-terminal-ansi* variables. 2026 Dark leaves them unset, so vendor/vscode/terminal-colors.json pins VS Code's dark terminal defaults from the same upstream commit as the themes; a theme value still wins. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'bundle/src/main.js')
-rw-r--r--bundle/src/main.js32
1 files changed, 23 insertions, 9 deletions
diff --git a/bundle/src/main.js b/bundle/src/main.js
index b71ed27..991ef06 100644
--- a/bundle/src/main.js
+++ b/bundle/src/main.js
@@ -3,7 +3,8 @@ import { getCurrentWindow } from '@tauri-apps/api/window';
import { open, save, message } from '@tauri-apps/plugin-dialog';
import { KeyCode, KeyMod } from 'monaco-editor/editor/editor.api.js';
import { StandardKeyboardEvent } from 'monaco-editor/base/browser/keyboardEvent.js';
-import { createEditor } from './editor.js';
+import { createEditor, themeVariable } from './editor.js';
+import { createAnsiParser } from './ansi.js';
import './style.css';
const status = document.querySelector('#status');
@@ -34,26 +35,39 @@ async function runFile() {
output.textContent = '';
status.textContent = 'Running';
const decoders = { stdout: new TextDecoder(), stderr: new TextDecoder() };
- let pending = '';
+ // Only compiler diagnostics on stderr are colored; program output stays verbatim.
+ const diagnostics = createAnsiParser();
+ const pending = document.createDocumentFragment();
let frame = null;
function flush() {
frame = null;
- if (!pending) return;
- output.append(document.createTextNode(pending));
- pending = '';
+ if (!pending.hasChildNodes()) return;
+ output.append(pending);
output.scrollTop = output.scrollHeight;
}
- function append(text) {
- pending += text;
+ function append(stream, text) {
+ for (const run of stream === 'stderr' ? diagnostics(text) : [{ text }]) {
+ if (!run.text) continue;
+ if (!run.bold && !run.color) {
+ if (pending.lastChild?.nodeType === Node.TEXT_NODE) pending.lastChild.appendData(run.text);
+ else pending.append(run.text);
+ continue;
+ }
+ const span = document.createElement('span');
+ if (run.bold) span.style.fontWeight = 'bold';
+ if (run.color) span.style.color = `var(${themeVariable(run.color)})`;
+ span.textContent = run.text;
+ pending.append(span);
+ }
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 }));
+ append(event.stream, 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());
+ for (const [stream, decoder] of Object.entries(decoders)) append(stream, decoder.decode());
if (frame !== null) cancelAnimationFrame(frame);
flush();
status.textContent = event.stopped ? 'Stopped' : `Exit code: ${event.code ?? 'terminated'}`;