mirror of
https://github.com/remko/waforth
synced 2025-01-17 18:11:39 +01:00
notebook: Move some files
This commit is contained in:
parent
3494e15a65
commit
a29e0f9567
17 changed files with 307 additions and 42 deletions
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
|
@ -13,5 +13,6 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- uses: ./.github/actions/setup
|
||||
- run: yarnpkg build
|
||||
- run: make -C src/web/notebook
|
||||
- run: yarnpkg lint
|
||||
- run: yarnpkg test --coverage
|
||||
|
|
|
@ -164,9 +164,9 @@ Because it is powered by WebAssembly, this extension works both in the desktop v
|
|||
|
||||
<div align="center">
|
||||
<div>
|
||||
<a href="https://github.dev/remko/waforth/blob/master/src/web/vscode-extension/examples/drawing-with-forth.wafnb"><img src="https://raw.githubusercontent.com/remko/waforth/master/src/web/vscode-extension/doc/notebook.gif" alt="WAForth notebook"></a>
|
||||
<a href="https://github.dev/remko/waforth/blob/master/src/web/notebook/examples/drawing-with-forth.wafnb"><img src="https://raw.githubusercontent.com/remko/waforth/master/src/web/vscode-extension/doc/notebook.gif" alt="WAForth notebook"></a>
|
||||
</div>
|
||||
<figcaption><em><a href="https://github.dev/remko/waforth/blob/master/src/web/vscode-extension/examples/drawing-with-forth.wafnb">WAForth notebook</a></em></figcaption>
|
||||
<figcaption><em><a href="https://github.dev/remko/waforth/blob/master/src/web/notebook/examples/drawing-with-forth.wafnb">WAForth notebook</a></em></figcaption>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
"devDependencies": {
|
||||
"@types/file-saver": "^2.0.5",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/marked": "^4.0.7",
|
||||
"@types/node": "^17.0.31",
|
||||
"@types/vscode": "^1.73.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.5",
|
||||
|
@ -20,6 +21,7 @@
|
|||
"file-saver": "^2.0.5",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
"marked": "^4.2.2",
|
||||
"mocha": "^9.2.2",
|
||||
"prettier": "^2.6.2",
|
||||
"react": "^18.0.0",
|
||||
|
|
|
@ -3,13 +3,7 @@
|
|||
|
||||
const { createServer } = require("http");
|
||||
|
||||
function withWatcher(
|
||||
config,
|
||||
handleBuildFinished = () => {
|
||||
/* do nothing */
|
||||
},
|
||||
port = 8880
|
||||
) {
|
||||
function withWatcher(config, handleBuildFinished = undefined, port = 8880) {
|
||||
const watchClients = [];
|
||||
createServer((req, res) => {
|
||||
return watchClients.push(
|
||||
|
@ -31,7 +25,9 @@ function withWatcher(
|
|||
if (error) {
|
||||
console.error(error);
|
||||
} else {
|
||||
await handleBuildFinished(result);
|
||||
if (handleBuildFinished != null) {
|
||||
await handleBuildFinished(result);
|
||||
}
|
||||
watchClients.forEach((res) => res.write("data: update\n\n"));
|
||||
watchClients.length = 0;
|
||||
}
|
||||
|
|
2
src/web/notebook/.gitignore
vendored
Normal file
2
src/web/notebook/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/dist
|
||||
/examples/*.html
|
8
src/web/notebook/Makefile
Normal file
8
src/web/notebook/Makefile
Normal file
|
@ -0,0 +1,8 @@
|
|||
all:
|
||||
./build.js
|
||||
|
||||
dev:
|
||||
./build.js --development --watch
|
||||
|
||||
clean:
|
||||
-rm -rf dist
|
106
src/web/notebook/build.js
Executable file
106
src/web/notebook/build.js
Executable file
|
@ -0,0 +1,106 @@
|
|||
#!/usr/bin/env node
|
||||
/* eslint-env node */
|
||||
/* eslint @typescript-eslint/no-var-requires:0 */
|
||||
|
||||
const esbuild = require("esbuild");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const { wasmTextPlugin } = require("../../../scripts/esbuild/wasm-text");
|
||||
|
||||
let dev = false;
|
||||
let watch = false;
|
||||
for (const arg of process.argv.slice(2)) {
|
||||
switch (arg) {
|
||||
case "--development":
|
||||
dev = true;
|
||||
break;
|
||||
case "--watch":
|
||||
watch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const buildConfig = {
|
||||
bundle: true,
|
||||
logLevel: "info",
|
||||
// target: "es6",
|
||||
minify: !dev,
|
||||
loader: {
|
||||
".wasm": "binary",
|
||||
".fs": "text",
|
||||
},
|
||||
plugins: [wasmTextPlugin({ debug: true })],
|
||||
};
|
||||
|
||||
let nbBuildConfig = {
|
||||
...buildConfig,
|
||||
outdir: path.join(__dirname, "dist"),
|
||||
entryPoints: [path.join(__dirname, "src", "wafnb.ts")],
|
||||
publicPath: "/dist",
|
||||
assetNames: "[name].txt",
|
||||
sourcemap: !!dev,
|
||||
loader: {
|
||||
...buildConfig.loader,
|
||||
".svg": "dataurl",
|
||||
},
|
||||
};
|
||||
|
||||
let generatorBuildConfig = {
|
||||
...buildConfig,
|
||||
banner: { js: "#!/usr/bin/env node" },
|
||||
platform: "node",
|
||||
outfile: path.join(__dirname, "dist", "wafnb2html"),
|
||||
entryPoints: [path.join(__dirname, "src", "wafnb2html.mjs")],
|
||||
sourcemap: dev ? "inline" : undefined,
|
||||
loader: {
|
||||
...buildConfig.loader,
|
||||
".js": "text",
|
||||
".css": "text",
|
||||
},
|
||||
define: watch
|
||||
? {
|
||||
WAFNB_CSS_PATH: JSON.stringify(path.join(__dirname, "/dist/wafnb.css")),
|
||||
WAFNB_JS_PATH: JSON.stringify(path.join(__dirname, "/dist/wafnb.js")),
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
function handleGeneratorBuildFinished(result) {
|
||||
return fs.chmodSync(path.join(__dirname, "dist", "wafnb2html"), "755");
|
||||
}
|
||||
|
||||
if (watch) {
|
||||
nbBuildConfig = {
|
||||
...nbBuildConfig,
|
||||
watch: {
|
||||
async onRebuild(error) {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
generatorBuildConfig = {
|
||||
...generatorBuildConfig,
|
||||
watch: {
|
||||
async onRebuild(error, result) {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
return handleGeneratorBuildFinished(result);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
await esbuild.build(nbBuildConfig);
|
||||
await handleGeneratorBuildFinished(
|
||||
await esbuild.build(generatorBuildConfig)
|
||||
);
|
||||
} catch (e) {
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
|
@ -53,18 +53,13 @@
|
|||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
"value": "# Combining words\n\nWe can create more complex figures by using the `SQUARE` word from above, and repeating it.\n\n| *Tip*: Play with the numbers in `FLOWER` to create variations of the flower "
|
||||
"value": "## Combining words\n\nWe can create more complex figures by using the `SQUARE` word from above, and repeating it.\n\n| *Tip*: Play with the numbers in `FLOWER` to create variations of the flower "
|
||||
},
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "waforth",
|
||||
"value": ": SQUARE ( n -- )\n 4 0 DO\n DUP FORWARD\n 90 RIGHT\n LOOP\n DROP\n;\n\n: FLOWER ( n -- )\n 24 0 DO\n DUP SQUARE\n 15 RIGHT\n LOOP\n DROP\n;\n\n250 FLOWER"
|
||||
},
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "waforth",
|
||||
"value": " 4 0 DO\n DUP FORWARD\n 90 RIGHT\n LOOP\n DROP\n;\n\n: FLOWER ( n -- )\n 24 0 DO\n DUP SQUARE\n 15 RIGHT\n LOOP\n DROP\n;\n\n250 FLOWER"
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
|
@ -88,7 +83,7 @@
|
|||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
"value": "# Fractals\n\nYou can create more complex recursive drawings, called *fractals*. \n\nA famous fractal is the *Koch snowflake*.\n\n| *Tip*: Change the `DEPTH` constant to make a coarser or finer grained snowflake"
|
||||
"value": "## Fractals\n\nYou can create more complex recursive drawings, called *fractals*. \n\nA famous fractal is the *Koch snowflake*.\n\n| *Tip*: Change the `DEPTH` constant to make a coarser or finer grained snowflake"
|
||||
},
|
||||
{
|
||||
"kind": 2,
|
21
src/web/notebook/src/Notebook.ts
Normal file
21
src/web/notebook/src/Notebook.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
export type Notebook = {
|
||||
cells: NotebookCell[];
|
||||
};
|
||||
|
||||
export type NotebookCell = {
|
||||
language: string;
|
||||
value: string;
|
||||
kind: number;
|
||||
editable?: boolean;
|
||||
};
|
||||
|
||||
export function parseNotebook(contents: string): Notebook {
|
||||
if (contents.trim().length === 0) {
|
||||
return { cells: [] };
|
||||
}
|
||||
return JSON.parse(contents);
|
||||
}
|
||||
|
||||
export function serializeNotebook(notebook: Notebook) {
|
||||
return JSON.stringify(notebook, undefined, 2);
|
||||
}
|
88
src/web/notebook/src/generator.ts
Normal file
88
src/web/notebook/src/generator.ts
Normal file
|
@ -0,0 +1,88 @@
|
|||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { parseNotebook } from "./Notebook";
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const marked = require("../../../../node_modules/marked/lib/marked.cjs").marked;
|
||||
|
||||
declare let WAFNB_JS_PATH: string | undefined;
|
||||
declare let WAFNB_CSS_PATH: string | undefined;
|
||||
|
||||
const titleRE = /^#[^#](.*)$/;
|
||||
|
||||
function escapeHTML(s: string) {
|
||||
return s
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
export async function generate({
|
||||
file,
|
||||
css,
|
||||
js,
|
||||
}: {
|
||||
bundle?: boolean;
|
||||
js?: string;
|
||||
css?: string;
|
||||
file: string;
|
||||
}) {
|
||||
let outfile = file.replace(".wafnb", ".html");
|
||||
if (outfile === file) {
|
||||
outfile = outfile + ".html";
|
||||
}
|
||||
|
||||
let style: string;
|
||||
let script: string;
|
||||
if (typeof WAFNB_JS_PATH !== "undefined") {
|
||||
const cssPath = path.relative(path.dirname(outfile), WAFNB_CSS_PATH!);
|
||||
const jsPath = path.relative(path.dirname(outfile), WAFNB_JS_PATH!);
|
||||
style = `<link rel="stylesheet" href="${cssPath}">`;
|
||||
script = `<script type="application/javascript" src="${jsPath}"></script>`;
|
||||
} else {
|
||||
style = `<style>${css}</style>`;
|
||||
script = `<script type="application/javascript">${js}</script>`;
|
||||
}
|
||||
|
||||
const nb = parseNotebook(
|
||||
await fs.promises.readFile(file, {
|
||||
encoding: "utf-8",
|
||||
flag: "r",
|
||||
})
|
||||
);
|
||||
let title: string | null = null;
|
||||
let out = ["<div class='content'>"];
|
||||
for (const cell of nb.cells) {
|
||||
switch (cell.kind) {
|
||||
case 1:
|
||||
if (title == null) {
|
||||
let m: RegExpExecArray | null = null;
|
||||
for (const v of cell.value.split("\n")) {
|
||||
m = titleRE.exec(v);
|
||||
if (m != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (m != null) {
|
||||
title = m[1].trim();
|
||||
}
|
||||
}
|
||||
out.push(
|
||||
"<div class='text-cell'>" + marked.parse(cell.value) + "</div>"
|
||||
);
|
||||
break;
|
||||
case 2:
|
||||
out.push(
|
||||
"<div data-hook='code-cell' class='raw-code-cell'>" +
|
||||
escapeHTML(cell.value) +
|
||||
"</div>"
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Error("unexpected kind");
|
||||
}
|
||||
}
|
||||
out.push("</div>");
|
||||
|
||||
out = [`<title>${escapeHTML(title ?? "")}</title>`, style, ...out, script];
|
||||
return fs.promises.writeFile(outfile, out.join("\n"));
|
||||
}
|
21
src/web/notebook/src/wafnb.css
Normal file
21
src/web/notebook/src/wafnb.css
Normal file
|
@ -0,0 +1,21 @@
|
|||
body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 40em;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
code.raw-code-cell {
|
||||
font-family: monospace;
|
||||
padding: 0.5em 1em;
|
||||
display: block;
|
||||
white-space: pre;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.editor {
|
||||
flex: 1;
|
||||
}
|
12
src/web/notebook/src/wafnb.ts
Normal file
12
src/web/notebook/src/wafnb.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import "./wafnb.css";
|
||||
import Editor from "../../thurtle/Editor";
|
||||
|
||||
for (const n of document.querySelectorAll("[data-hook=code-cell")) {
|
||||
n.className = "code-cell";
|
||||
const program = n.textContent ?? "";
|
||||
const editor = new Editor();
|
||||
editor.setValue(program);
|
||||
n.innerHTML = "";
|
||||
n.appendChild(editor.el);
|
||||
editor.el.style.minHeight = "10em"; // FIXME
|
||||
}
|
13
src/web/notebook/src/wafnb2html.mjs
Normal file
13
src/web/notebook/src/wafnb2html.mjs
Normal file
|
@ -0,0 +1,13 @@
|
|||
import wafnbJS from "../dist/wafnb.js";
|
||||
import wafnbCSS from "../dist/wafnb.css";
|
||||
import { generate } from "./generator";
|
||||
import process from "process";
|
||||
|
||||
(async () => {
|
||||
const file = process.argv[2];
|
||||
if (file == null) {
|
||||
console.error("missing file");
|
||||
process.exit(-1);
|
||||
}
|
||||
await generate({ file, js: wafnbJS, css: wafnbCSS });
|
||||
})();
|
|
@ -8,15 +8,15 @@ Logo-like Forth Turtle graphics.
|
|||
|
||||
<div align="center">
|
||||
<div>
|
||||
<a href="https://github.dev/remko/waforth/blob/master/src/web/vscode-extension/examples/drawing-with-forth.wafnb"><img src="https://raw.githubusercontent.com/remko/waforth/master/src/web/vscode-extension/doc/notebook.gif" alt="WAForth notebook"></a>
|
||||
<a href="https://github.dev/remko/waforth/blob/master/src/web/notebook/examples/drawing-with-forth.wafnb"><img src="https://raw.githubusercontent.com/remko/waforth/master/src/web/notebook/doc/notebook.gif" alt="WAForth notebook"></a>
|
||||
</div>
|
||||
<figcaption><em><a href="https://github.dev/remko/waforth/blob/master/src/web/vscode-extension/examples/drawing-with-forth.wafnb">WAForth notebook</a></em></figcaption>
|
||||
<figcaption><em><a href="https://github.dev/remko/waforth/blob/master/src/web/notebook/examples/drawing-with-forth.wafnb">WAForth notebook</a></em></figcaption>
|
||||
</div>
|
||||
|
||||
## Installation & Usage
|
||||
|
||||
Install the extension from the [Visual Studio Code Marketplace](https://marketplace.visualstudio.com/items?itemName=remko.waforth-vscode-extension).
|
||||
Once the extension is installed, you can create a new WAForth notebook from the command pallet (*WAForth: New notebook*), or open [this example notebook](https://raw.githubusercontent.com/remko/waforth/master/src/web/vscode-extension/examples/drawing-with-forth.wafnb).
|
||||
Once the extension is installed, you can create a new WAForth notebook from the command pallet (*WAForth: New notebook*), or open [this example notebook](https://raw.githubusercontent.com/remko/waforth/master/src/web/notebook/examples/drawing-with-forth.wafnb).
|
||||
|
||||
The extension also works in the web-based VS Code environments.
|
||||
For example, install the extension on https://github.dev, and then open [this example notebook](https://github.dev/remko/waforth/blob/master/src/web/vscode-extension/examples/drawing-with-forth.wafnb).
|
||||
For example, install the extension on https://github.dev, and then open [this example notebook](https://github.dev/remko/waforth/blob/master/src/web/notebook/examples/drawing-with-forth.wafnb).
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "waforth-vscode-extension",
|
||||
"version": "0.1.6",
|
||||
"version": "0.1.7",
|
||||
"displayName": "WAForth",
|
||||
"description": "WAForth interactive notebooks",
|
||||
"categories": [
|
||||
|
|
|
@ -2,6 +2,11 @@ import * as vscode from "vscode";
|
|||
import WAForth, { ErrorCode, isSuccess } from "../../waforth";
|
||||
import draw from "../../thurtle/draw";
|
||||
import JSJSX from "thurtle/jsjsx";
|
||||
import {
|
||||
parseNotebook,
|
||||
Notebook,
|
||||
serializeNotebook,
|
||||
} from "../../notebook/src/Notebook";
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(
|
||||
|
@ -41,30 +46,15 @@ export function deactivate() {
|
|||
// Notebook Serializer
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
interface RawNotebookData {
|
||||
cells: RawNotebookCell[];
|
||||
}
|
||||
|
||||
interface RawNotebookCell {
|
||||
language: string;
|
||||
value: string;
|
||||
kind: vscode.NotebookCellKind;
|
||||
editable?: boolean;
|
||||
}
|
||||
|
||||
export class NotebookSerializer implements vscode.NotebookSerializer {
|
||||
public readonly label: string = "WAForth Content Serializer";
|
||||
|
||||
public async deserializeNotebook(
|
||||
data: Uint8Array
|
||||
): Promise<vscode.NotebookData> {
|
||||
const contents = new TextDecoder().decode(data);
|
||||
if (contents.trim().length === 0) {
|
||||
return new vscode.NotebookData([]);
|
||||
}
|
||||
let raw: RawNotebookData;
|
||||
let raw: Notebook;
|
||||
try {
|
||||
raw = <RawNotebookData>JSON.parse(contents);
|
||||
raw = parseNotebook(new TextDecoder().decode(data));
|
||||
} catch (e) {
|
||||
vscode.window.showErrorMessage("Error parsing:", (e as any).message);
|
||||
raw = { cells: [] };
|
||||
|
@ -79,7 +69,7 @@ export class NotebookSerializer implements vscode.NotebookSerializer {
|
|||
public async serializeNotebook(
|
||||
data: vscode.NotebookData
|
||||
): Promise<Uint8Array> {
|
||||
const contents: RawNotebookData = { cells: [] };
|
||||
const contents: Notebook = { cells: [] };
|
||||
for (const cell of data.cells) {
|
||||
contents.cells.push({
|
||||
kind: cell.kind,
|
||||
|
@ -87,12 +77,12 @@ export class NotebookSerializer implements vscode.NotebookSerializer {
|
|||
value: cell.value,
|
||||
});
|
||||
}
|
||||
return new TextEncoder().encode(JSON.stringify(contents, undefined, 2));
|
||||
return new TextEncoder().encode(serializeNotebook(contents));
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Notebook Serializer
|
||||
// Notebook Controller
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
async function createNotebookController(
|
||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -112,6 +112,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2"
|
||||
integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==
|
||||
|
||||
"@types/marked@^4.0.7":
|
||||
version "4.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-4.0.7.tgz#400a76809fd08c2bbd9e25f3be06ea38c8e0a1d3"
|
||||
integrity sha512-eEAhnz21CwvKVW+YvRvcTuFKNU9CV1qH+opcgVK3pIMI6YZzDm6gc8o2vHjldFk6MGKt5pueSB7IOpvpx5Qekw==
|
||||
|
||||
"@types/node@^17.0.31":
|
||||
version "17.0.31"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.31.tgz#a5bb84ecfa27eec5e1c802c6bbf8139bdb163a5d"
|
||||
|
@ -1307,6 +1312,11 @@ make-error@^1.1.1:
|
|||
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
|
||||
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
|
||||
|
||||
marked@^4.2.2:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/marked/-/marked-4.2.2.tgz#1d2075ad6cdfe42e651ac221c32d949a26c0672a"
|
||||
integrity sha512-JjBTFTAvuTgANXx82a5vzK9JLSMoV6V3LBVn4Uhdso6t7vXrGx7g1Cd2r6NYSsxrYbQGFCMqBDhFHyK5q2UvcQ==
|
||||
|
||||
merge2@^1.3.0, merge2@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||
|
|
Loading…
Reference in a new issue