Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 26 additions & 10 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,38 @@
<link rel="stylesheet" href="style/style.css">
</head>
<body>
<button id="run">Run</button>
<header>
<div class="header-info">
<h1><a href="https://playground.zigtools.org">playground</a></h1>
<span>|</span>
<span>A <a href="https://zigtools.org">zigtools</a> initiative.</span>
<span>|</span>
<span><span id="warning">Warning</span> Zig's self-hosted WebAssembly backend is still experimental!</span>
</div>
<div class="header-controls">
<select id="example-select">
<option value="hello-world">Hello World</option>
<option value="ansi-text">ANSI Text</option>
<option value="fizz-buzz">FizzBuzz</option>
<option value="fibonacci">Fibonacci</option>
<option value="mandelbrot">Mandelbrot</option>
<option value="kitty-image">Kitty Image</option>
</select>
<button id="run">Run</button>
</div>
</header>

<main>
<div id="split-pane" style="--editor-height-percent: 100%;">
<div id="split-pane" style="--editor-width-percent: 60%;">
<div id="editor"></div>
<div id="resize-bar"></div>
<div id="output"></div>
<div id="output-pane">
<div id="execution-status">&nbsp;</div>
<div id="output"></div>
</div>
</div>
</main>

<footer>
<h1><a href="https://playground.zigtools.org">playground</a></h1>
<span><span id="warning">Warning</span> Zig's self-hosted WebAssembly backend is still experimental!</span>
<span>A <a href="https://zigtools.org">zigtools</a> initiative.</span>
</footer>

<script src="/src/editor.ts" type="module"></script>
</body>
</html>
</html>
30 changes: 30 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
"@codemirror/view": "^6.9.3",
"@ndim/codemirror-lang-zig": "^0.2.0",
"@ndim/lezer-zig": "^0.2.0",
"@xterm/addon-fit": "^0.12.0-beta.292",
"@xterm/addon-image": "^0.10.0-beta.292",
"@xterm/xterm": "^6.1.0-beta.292",
"codemirror": "^6.0.1",
"vscode-languageserver-protocol": "^3.17.3"
},
Expand Down
145 changes: 102 additions & 43 deletions src/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,34 @@ import { formatDocument } from "@codemirror/lsp-client";
import { indentWithTab } from "@codemirror/commands";
import { indentUnit, syntaxHighlighting } from "@codemirror/language";
import { zigLanguage } from "@ndim/codemirror-lang-zig";
import { Terminal } from "@xterm/xterm";
import { FitAddon } from "@xterm/addon-fit";
import { ImageAddon } from "@xterm/addon-image";
import "@xterm/xterm/css/xterm.css";
import { editorTheme, highlightStyle } from "./theme.ts";
import { lspClient } from "./lsp.ts";
// @ts-ignore
import ZigWorker from './workers/zig.ts?worker';
// @ts-ignore
import RunnerWorker from './workers/runner.ts?worker';
// @ts-ignore
import zigMainSource from './main.zig?raw';
import helloWorldSource from './examples/hello-world.zig?raw';
// @ts-ignore
import ansiTextSource from './examples/ansi-text.zig?raw';
// @ts-ignore
import fizzBuzzSource from './examples/fizz-buzz.zig?raw';
// @ts-ignore
import fibonacciSource from './examples/fibonacci.zig?raw';
// @ts-ignore
import mandelbrotSource from './examples/mandelbrot.zig?raw';
// @ts-ignore
import kittyImageSource from './examples/kitty-image.zig?raw';

const editor = new EditorView({
extensions: [],
parent: document.getElementById("editor")!,
state: EditorState.create({
doc: zigMainSource,
doc: helloWorldSource,
extensions: [
basicSetup,
editorTheme,
Expand All @@ -37,58 +51,85 @@ const editor = new EditorView({
}),
});

const output = document.getElementById("output")!;
const executionStatus = document.getElementById("execution-status")!;
const outputStyle = getComputedStyle(document.documentElement);
const terminal = new Terminal({
disableStdin: true,
convertEol: true,
cursorBlink: false,
fontSize: 16,
fontFamily: "monospace",
scrollback: 1000,
theme: {
background: outputStyle.getPropertyValue("--output-background").trim(),
foreground: outputStyle.getPropertyValue("--output-text").trim(),
},
});
const fitAddon = new FitAddon();
terminal.loadAddon(fitAddon);
terminal.loadAddon(new ImageAddon());
terminal.open(output);
terminal.write("\x1b[?25l"); // hide cursor
fitAddon.fit();
new ResizeObserver(() => fitAddon.fit()).observe(output);

function revealOutputWindow() {
const outputs = document.getElementById("output")!;
outputs.scrollTo(0, outputs.scrollHeight!);
const editorHeightPercent = parseFloat(splitPane.style.getPropertyValue("--editor-height-percent"));
if (editorHeightPercent == 100) {
splitPane.style.setProperty("--editor-height-percent", `${resizeBarPreviousSize}%`);
const editorWidthPercent = parseFloat(splitPane.style.getPropertyValue("--editor-width-percent"));
if (editorWidthPercent == 100) {
splitPane.style.setProperty("--editor-width-percent", `${resizeBarPreviousSize}%`);
}
}

let zigWorker = new ZigWorker();
let compilationStartedAt = 0;
let isCompiling = false;
let executionId = 0;

zigWorker.onmessage = (ev: MessageEvent) => {
if (ev.data.stderr) {
document.querySelector(".zig-output:last-child")!.textContent += ev.data.stderr;
terminal.write(ev.data.stderr);
revealOutputWindow();
return;
} else if (ev.data.failed) {
const outputSplit = document.createElement("div");
outputSplit.classList.add("output-split");
document.getElementById("output")!.appendChild(outputSplit);
} else if (ev.data.compiled) {
let runnerWorker = new RunnerWorker();
const compilationTime = Math.round(performance.now() - compilationStartedAt);
const currentExecutionId = executionId;
const runStartedAt = performance.now();
isCompiling = false;
executionStatus.textContent = `Compiled in ${compilationTime} ms | Running...`;

const zigOutput = document.createElement("div");
zigOutput.classList.add("runner-output");
zigOutput.classList.add("latest");
document.getElementById("output")!.appendChild(zigOutput);
let runnerWorker = new RunnerWorker();

runnerWorker.postMessage({ run: ev.data.compiled });

runnerWorker.onmessage = (rev: MessageEvent) => {
if (rev.data.stderr) {
document.querySelector(".runner-output:last-child")!.textContent += rev.data.stderr;
terminal.write(rev.data.stderr);
revealOutputWindow();
return;
} else if (rev.data.done) {
if (currentExecutionId == executionId) {
const runTime = Math.round(performance.now() - runStartedAt);
executionStatus.textContent = `Compiled in ${compilationTime} ms | Ran in ${runTime} ms | Exit code ${rev.data.exitCode}`;
}
runnerWorker.terminate();
const outputSplit = document.createElement("div");
outputSplit.classList.add("output-split");
document.getElementById("output")!.appendChild(outputSplit);
}
}
} else if (ev.data.failed) {
const compilationTime = Math.round(performance.now() - compilationStartedAt);
isCompiling = false;
executionStatus.textContent = `Compilation failed in ${compilationTime} ms`;
}
}

const splitPane = document.getElementById("split-pane")! as HTMLDivElement;
const resizeBar = document.getElementById("resize-bar")! as HTMLDivElement;
let resizeBarPreviousSize = 70;
let resizeBarPreviousSize = 60;

function onResizeBarMove(event: MouseEvent) {
const percent = Math.min(Math.max(10, event.clientY / splitPane.getBoundingClientRect().height * 100), 100);
splitPane.style.setProperty("--editor-height-percent", `${percent}%`);
const bounds = splitPane.getBoundingClientRect();
const percent = Math.min(Math.max(10, (event.clientX - bounds.left) / bounds.width * 100), 100);
splitPane.style.setProperty("--editor-width-percent", `${percent}%`);
}
function onResizeBarMouseUp(event: MouseEvent) {
window.removeEventListener("mousemove", onResizeBarMove);
Expand All @@ -98,9 +139,9 @@ function onResizeBarMouseUp(event: MouseEvent) {
document.body.style.removeProperty("cursor");

// fully close the output window when it's almost closed
const editorHeightPercent = parseFloat(splitPane.style.getPropertyValue("--editor-height-percent"));
if (editorHeightPercent >= 90) {
splitPane.style.setProperty("--editor-height-percent", "100%");
const editorWidthPercent = parseFloat(splitPane.style.getPropertyValue("--editor-width-percent"));
if (editorWidthPercent >= 90) {
splitPane.style.setProperty("--editor-width-percent", "100%");
}
}

Expand All @@ -109,32 +150,50 @@ resizeBar.addEventListener("mousedown", event => {
window.addEventListener("mousemove", onResizeBarMove);
window.addEventListener("mouseup", onResizeBarMouseUp);
document.body.style.userSelect = "none";
document.body.style.cursor = "row-resize";
document.body.style.cursor = "col-resize";
}
});
resizeBar.addEventListener("dblclick", event => {
const editorHeightPercent = parseFloat(splitPane.style.getPropertyValue("--editor-height-percent"));
if (editorHeightPercent == 100) {
splitPane.style.setProperty("--editor-height-percent", `${resizeBarPreviousSize}%`);
const editorWidthPercent = parseFloat(splitPane.style.getPropertyValue("--editor-width-percent"));
if (editorWidthPercent == 100) {
splitPane.style.setProperty("--editor-width-percent", `${resizeBarPreviousSize}%`);
} else {
resizeBarPreviousSize = editorHeightPercent;
splitPane.style.setProperty("--editor-height-percent", `100%`);
resizeBarPreviousSize = editorWidthPercent;
splitPane.style.setProperty("--editor-width-percent", `100%`);
}
});

const outputsRun = document.getElementById("run")! as HTMLButtonElement;
const exampleSelect = document.getElementById("example-select")! as HTMLSelectElement;

const examples: Record<string, string> = {
"hello-world": helloWorldSource,
"ansi-text": ansiTextSource,
"fizz-buzz": fizzBuzzSource,
"fibonacci": fibonacciSource,
"mandelbrot": mandelbrotSource,
"kitty-image": kittyImageSource,
};

exampleSelect.addEventListener("change", () => {
editor.dispatch({
changes: {
from: 0,
to: editor.state.doc.length,
insert: examples[exampleSelect.value],
},
});
});

outputsRun.addEventListener("click", async () => {
for (const zo of document.querySelectorAll(".zig-output")) {
zo.classList.remove("latest");
}
for (const ro of document.querySelectorAll(".runner-output")) {
ro.classList.remove("latest");
}
if (isCompiling) return;

const zigOutput = document.createElement("div");
zigOutput.classList.add("zig-output");
zigOutput.classList.add("latest");
document.getElementById("output")!.appendChild(zigOutput);
isCompiling = true;
executionId += 1;
compilationStartedAt = performance.now();
executionStatus.textContent = "Compiling...";
terminal.reset();
terminal.write("\x1b[?25l"); // hide cursor
revealOutputWindow();

zigWorker.postMessage({
Expand Down
17 changes: 17 additions & 0 deletions src/examples/ansi-text.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const std = @import("std");

pub fn main(init: std.process.Init) !void {
var buffer: [4096]u8 = undefined;
var file_writer = std.Io.File.stdout().writerStreaming(init.io, &buffer);
const stdout = &file_writer.interface;

try stdout.writeAll("\x1b[31mred\n");
try stdout.writeAll("\x1b[32mgreen\n");
try stdout.writeAll("\x1b[33myellow\n");
try stdout.writeAll("\x1b[34mblue\n");
try stdout.writeAll("\x1b[35mmagenta\n");
try stdout.writeAll("\x1b[36mcyan\n");
try stdout.writeAll("\x1b[37mwhite\n");
try stdout.writeAll("\x1b[0mreset\n");
try stdout.flush();
}
19 changes: 19 additions & 0 deletions src/examples/fibonacci.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const std = @import("std");

pub fn main(init: std.process.Init) !void {
var buffer: [4096]u8 = undefined;
var file_writer = std.Io.File.stdout().writerStreaming(init.io, &buffer);
const stdout = &file_writer.interface;

var a: u32 = 0;
var b: u32 = 1;

for (0..12) |_| {
try stdout.print("{d}\n", .{a});
const c = a + b;
a = b;
b = c;
}

try stdout.flush();
}
21 changes: 21 additions & 0 deletions src/examples/fizz-buzz.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const std = @import("std");

pub fn main(init: std.process.Init) !void {
var buffer: [4096]u8 = undefined;
var file_writer = std.Io.File.stdout().writerStreaming(init.io, &buffer);
const stdout = &file_writer.interface;

for (1..21) |number| {
if (number % 15 == 0) {
try stdout.writeAll("FizzBuzz\n");
} else if (number % 3 == 0) {
try stdout.writeAll("Fizz\n");
} else if (number % 5 == 0) {
try stdout.writeAll("Buzz\n");
} else {
try stdout.print("{d}\n", .{number});
}
}

try stdout.flush();
}
File renamed without changes.
Loading