Get things working using monaco-webpack-build

This commit is contained in:
Andrew Morris
2022-12-13 14:07:11 +11:00
parent 193362a92b
commit e80d3d61fc
7 changed files with 328 additions and 1 deletions

View File

@@ -0,0 +1,170 @@
import nil from "./helpers/nil.ts";
function blockTrim(text: string) {
let lines = text.split("\n");
while (lines.length > 0 && /^ *$/.test(lines[0])) {
lines.shift();
}
while (lines.length > 0 && /^ *$/.test(lines[lines.length - 1])) {
lines.pop();
}
let minIndent = Infinity;
for (const line of lines) {
if (line.trim() === "") {
continue;
}
const match = line.match(/^ */);
if (match === null || match[0].length >= minIndent) {
continue;
}
minIndent = match[0].length;
}
lines = lines.map((line) => line.slice(minIndent));
return lines.join("\n");
}
const files: Record<string, string | nil> = {
"tutorial/hello.ts": blockTrim(`
// Welcome to the ValueScript playground!
//
// This playground also acts as a tutorial by describing a variety of
// examples. Please go ahead and make edits to the code, you should see
// the results in real-time!
//
// Keeping with tradition, here is the hello world program.
export default function main() {
return "Hello world!";
}
// When you're ready, click the next arrow ('>') above to continue.
`),
"tutorial/valueSemantics.ts": blockTrim(`
export default function main() {
const leftBowl = ['apple', 'mango'];
let rightBowl = leftBowl;
rightBowl.push('peach');
return {
leftBowl,
rightBowl,
};
}
// In TypeScript, leftBowl also contains 'peach':
//
// {
// leftBowl: ['apple', 'mango', 'peach'],
// rightBowl: ['apple', 'mango', 'peach'],
// }
//
// This is because TypeScript interprets the code to mean that leftBowl and
// rightBowl are the same object, and that object changes.
//
// In ValueScript, objects do not change, but variables do. Pushing onto
// rightBowl is interpreted as a change to the rightBowl variable itself,
// not the data it points to. rightBowl points to some new data, which may
// reference the old data, but only as a performance optimization.
`),
"tutorial/factorial.ts": blockTrim(`
export default function main() {
return factorial(5);
}
function factorial(n: number): number {
if (n === 0) {
return 1;
}
return n * factorial(n - 1);
}
`),
"tutorial/binaryTree.ts": blockTrim(`
export default function main() {
let tree = BinaryTree();
tree.insert(2);
tree.insert(5);
tree.insert(1);
const treeSnapshot = tree;
tree.insert(3);
tree.insert(4);
return [treeSnapshot.toArray(), tree.toArray()];
}
function BinaryTree() {
type BinaryTree = {
data: {
value?: number,
left?: BinaryTree,
right?: BinaryTree,
},
insert(this: BinaryTree, newValue: number): void,
toArray(this: BinaryTree): number[],
};
let tree: BinaryTree = {
data: {},
insert: function(newValue) {
if (this.data.value === undefined) {
this.data.value = newValue;
return;
}
if (newValue < this.data.value) {
this.data.left ??= BinaryTree();
this.data.left.insert(newValue);
} else {
this.data.right ??= BinaryTree();
this.data.right.insert(newValue);
}
},
toArray: function() {
let res: number[] = [];
if (this.data.left) {
res = cat(res, this.data.left.toArray());
}
if (this.data.value !== undefined) {
res.push(this.data.value);
}
if (this.data.right) {
res = cat(res, this.data.right.toArray());
}
return res;
},
};
return tree;
}
function cat(left: number[], right: number[]) {
for (let i = 0; i < right.length; i++) {
left.push(right[i]);
}
return left;
}
`),
};
export default files;

View File

@@ -0,0 +1,8 @@
export default function assert(
value: boolean,
msg = "value was not true",
): asserts value {
if (value !== true) {
throw new Error(`Assertion failed: ${msg}`);
}
}

View File

@@ -0,0 +1,4 @@
const nil = undefined;
type nil = undefined;
export default nil;

View File

@@ -0,0 +1,9 @@
import nil from "./nil.ts";
export default function notNil<T>(value: T | nil): T {
if (value === nil) {
throw new Error();
}
return value;
}

View File

@@ -1 +1,107 @@
import * as monaco from "https://esm.sh/monaco-editor@0.34.1";
import monacoPromise from "./monacoPromise.ts";
import files from "./files.ts";
import assert from "./helpers/assert.ts";
import nil from "./helpers/nil.ts";
import notNil from "./helpers/notNil.ts";
function domQuery<T = HTMLElement>(query: string): T {
return <T> <unknown> notNil(document.querySelector(query) ?? nil);
}
const editorEl = domQuery("#editor");
const selectEl = domQuery<HTMLSelectElement>("#file-location select");
const filePreviousEl = domQuery("#file-previous");
const fileNextEl = domQuery("#file-next");
for (const filename of Object.keys(files)) {
const option = document.createElement("option");
option.textContent = filename;
selectEl.appendChild(option);
}
let currentFile = "";
editorEl.innerHTML = "";
monacoPromise.then((monaco) => {
const editor = monaco.editor.create(editorEl, {
theme: "vs-dark",
value: "",
language: "typescript",
});
setTimeout(() => changeFile(location.hash.slice(1)));
globalThis.addEventListener("hashchange", () => {
changeFile(location.hash.slice(1));
});
globalThis.addEventListener("resize", () => editor.layout());
const model = notNil(editor.getModel() ?? nil);
model.updateOptions({ tabSize: 2, insertSpaces: true });
function changeFile(newFile: string) {
if (currentFile === "") {
currentFile = Object.keys(files)[0];
} else if (newFile === currentFile) {
return;
}
if (newFile === "") {
newFile = Object.keys(files)[0];
}
const fileIdx = Object.keys(files).indexOf(newFile);
if (fileIdx !== -1) {
currentFile = newFile;
}
location.hash = currentFile;
selectEl.selectedIndex = fileIdx;
const content = files[currentFile];
assert(content !== nil);
model.setValue(content);
}
selectEl.addEventListener("change", () => {
changeFile(selectEl.value);
});
const moveFileIndex = (change: number) => () => {
const filenames = Object.keys(files);
let idx = filenames.indexOf(currentFile);
if (idx === -1) {
throw new Error("This should not happen");
}
idx += change;
idx = Math.max(idx, 0);
idx = Math.min(idx, filenames.length - 1);
changeFile(filenames[idx]);
};
filePreviousEl.addEventListener("click", moveFileIndex(-1));
fileNextEl.addEventListener("click", moveFileIndex(1));
let timerId: undefined | number = undefined;
model.onDidChangeContent(() => {
files[currentFile] = model.getValue();
clearTimeout(timerId);
timerId = setTimeout(handleUpdate, 200);
});
function handleUpdate() {
console.log("TODO: handle update");
}
});

View File

@@ -0,0 +1,27 @@
/// <reference types="https://esm.sh/v99/monaco-editor@0.34.1/esm/vs/editor/editor.api.d.ts" />
import * as MonacoImport from "https://esm.sh/v99/monaco-editor@0.34.1/esm/vs/editor/editor.api.d.ts";
// export * from "https://esm.sh/v99/monaco-editor@0.34.1/esm/vs/editor/editor.api.d.ts";
const script = document.createElement("script");
script.src = "/monaco.bundle.js";
document.head.append(script);
const monacoPromise = new Promise<typeof MonacoImport>((resolve, reject) => {
script.onload = () => {
// deno-lint-ignore no-explicit-any
const monaco = (globalThis as any).monaco;
if (monaco === undefined) {
throw new Error("Missing monaco definition");
}
resolve(monaco);
};
script.onerror = (evt) => {
reject(new Error(evt.toString()));
};
});
export default monacoPromise;

View File

@@ -0,0 +1,3 @@
*.bundle.js
*.bundle.js.*
*.ttf