diff --git a/playground/.eslintrc b/playground/.eslintrc
new file mode 100644
index 0000000..db7f5ad
--- /dev/null
+++ b/playground/.eslintrc
@@ -0,0 +1,20 @@
+{
+ "env": {
+ "browser": true,
+ "commonjs": true,
+ "es6": true,
+ "node": true
+ },
+ "parserOptions": {
+ "ecmaVersion": 6,
+ "sourceType": "module",
+ "ecmaFeatures": {
+ "jsx": true,
+ "classes": true,
+ "defaultParams": true
+ }
+ },
+ "rules": {
+ "curly": ["error", "multi"]
+ }
+}
diff --git a/playground/index.html b/playground/index.html
index d0bd34e..7ee3ffc 100644
--- a/playground/index.html
+++ b/playground/index.html
@@ -3,7 +3,7 @@
-
+
Newt Playground
diff --git a/playground/package-lock.json b/playground/package-lock.json
index 8c07732..2b4ef72 100644
--- a/playground/package-lock.json
+++ b/playground/package-lock.json
@@ -9,15 +9,113 @@
"version": "0.0.0",
"dependencies": {
"@preact/signals": "^1.3.0",
+ "codemirror": "^6.0.1",
"monaco-editor": "^0.52.0",
"preact": "^10.24.3"
},
"devDependencies": {
+ "@codemirror/theme-one-dark": "^6.1.2",
+ "@lezer/generator": "^1.7.3",
+ "@lezer/lr": "^1.4.2",
"esbuild": "^0.25.0",
"typescript": "~5.6.2",
"vite": "^6.1.0"
}
},
+ "node_modules/@codemirror/autocomplete": {
+ "version": "6.18.6",
+ "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz",
+ "integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.17.0",
+ "@lezer/common": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/commands": {
+ "version": "6.8.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz",
+ "integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.4.0",
+ "@codemirror/view": "^6.27.0",
+ "@lezer/common": "^1.1.0"
+ }
+ },
+ "node_modules/@codemirror/language": {
+ "version": "6.11.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.1.tgz",
+ "integrity": "sha512-5kS1U7emOGV84vxC+ruBty5sUgcD0te6dyupyRVG2zaSjhTDM73LhVKUtVwiqSe6QwmEoA4SCiU8AKPFyumAWQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.23.0",
+ "@lezer/common": "^1.1.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0",
+ "style-mod": "^4.0.0"
+ }
+ },
+ "node_modules/@codemirror/lint": {
+ "version": "6.8.5",
+ "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz",
+ "integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.35.0",
+ "crelt": "^1.0.5"
+ }
+ },
+ "node_modules/@codemirror/search": {
+ "version": "6.5.11",
+ "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz",
+ "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0",
+ "crelt": "^1.0.5"
+ }
+ },
+ "node_modules/@codemirror/state": {
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
+ "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
+ "license": "MIT",
+ "dependencies": {
+ "@marijn/find-cluster-break": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/theme-one-dark": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz",
+ "integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0",
+ "@lezer/highlight": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/view": {
+ "version": "6.37.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.37.2.tgz",
+ "integrity": "sha512-XD3LdgQpxQs5jhOOZ2HRVT+Rj59O4Suc7g2ULvZ+Yi8eCkickrkZ5JFuoDhs2ST1mNI5zSsNYgR3NGa4OUrbnw==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/state": "^6.5.0",
+ "crelt": "^1.0.6",
+ "style-mod": "^4.1.0",
+ "w3c-keyname": "^2.2.4"
+ }
+ },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
@@ -443,6 +541,50 @@
"node": ">=18"
}
},
+ "node_modules/@lezer/common": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
+ "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==",
+ "license": "MIT"
+ },
+ "node_modules/@lezer/generator": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@lezer/generator/-/generator-1.7.3.tgz",
+ "integrity": "sha512-vAI2O1tPF8QMMgp+bdUeeJCneJNkOZvqsrtyb4ohnFVFdboSqPwBEacnt0HH4E+5h+qsIwTHUSAhffU4hzKl1A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@lezer/common": "^1.1.0",
+ "@lezer/lr": "^1.3.0"
+ },
+ "bin": {
+ "lezer-generator": "src/lezer-generator.cjs"
+ }
+ },
+ "node_modules/@lezer/highlight": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
+ "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
+ "license": "MIT",
+ "dependencies": {
+ "@lezer/common": "^1.0.0"
+ }
+ },
+ "node_modules/@lezer/lr": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
+ "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
+ "license": "MIT",
+ "dependencies": {
+ "@lezer/common": "^1.0.0"
+ }
+ },
+ "node_modules/@marijn/find-cluster-break": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
+ "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
+ "license": "MIT"
+ },
"node_modules/@preact/signals": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@preact/signals/-/signals-1.3.2.tgz",
@@ -470,9 +612,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.34.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.7.tgz",
- "integrity": "sha512-l6CtzHYo8D2TQ3J7qJNpp3Q1Iye56ssIAtqbM2H8axxCEEwvN7o8Ze9PuIapbxFL3OHrJU2JBX6FIIVnP/rYyw==",
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.43.0.tgz",
+ "integrity": "sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==",
"cpu": [
"arm"
],
@@ -484,9 +626,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.34.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.7.tgz",
- "integrity": "sha512-KvyJpFUueUnSp53zhAa293QBYqwm94TgYTIfXyOTtidhm5V0LbLCJQRGkQClYiX3FXDQGSvPxOTD/6rPStMMDg==",
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.43.0.tgz",
+ "integrity": "sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==",
"cpu": [
"arm64"
],
@@ -498,9 +640,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.34.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.7.tgz",
- "integrity": "sha512-jq87CjmgL9YIKvs8ybtIC98s/M3HdbqXhllcy9EdLV0yMg1DpxES2gr65nNy7ObNo/vZ/MrOTxt0bE5LinL6mA==",
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.43.0.tgz",
+ "integrity": "sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==",
"cpu": [
"arm64"
],
@@ -512,9 +654,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.34.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.7.tgz",
- "integrity": "sha512-rSI/m8OxBjsdnMMg0WEetu/w+LhLAcCDEiL66lmMX4R3oaml3eXz3Dxfvrxs1FbzPbJMaItQiksyMfv1hoIxnA==",
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.43.0.tgz",
+ "integrity": "sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==",
"cpu": [
"x64"
],
@@ -526,9 +668,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.34.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.7.tgz",
- "integrity": "sha512-oIoJRy3ZrdsXpFuWDtzsOOa/E/RbRWXVokpVrNnkS7npz8GEG++E1gYbzhYxhxHbO2om1T26BZjVmdIoyN2WtA==",
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.43.0.tgz",
+ "integrity": "sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==",
"cpu": [
"arm64"
],
@@ -540,9 +682,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.34.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.7.tgz",
- "integrity": "sha512-X++QSLm4NZfZ3VXGVwyHdRf58IBbCu9ammgJxuWZYLX0du6kZvdNqPwrjvDfwmi6wFdvfZ/s6K7ia0E5kI7m8Q==",
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.43.0.tgz",
+ "integrity": "sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==",
"cpu": [
"x64"
],
@@ -554,9 +696,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.34.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.7.tgz",
- "integrity": "sha512-Z0TzhrsNqukTz3ISzrvyshQpFnFRfLunYiXxlCRvcrb3nvC5rVKI+ZXPFG/Aa4jhQa1gHgH3A0exHaRRN4VmdQ==",
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.43.0.tgz",
+ "integrity": "sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==",
"cpu": [
"arm"
],
@@ -568,9 +710,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.34.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.7.tgz",
- "integrity": "sha512-nkznpyXekFAbvFBKBy4nNppSgneB1wwG1yx/hujN3wRnhnkrYVugMTCBXED4+Ni6thoWfQuHNYbFjgGH0MBXtw==",
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.43.0.tgz",
+ "integrity": "sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==",
"cpu": [
"arm"
],
@@ -582,9 +724,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.34.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.7.tgz",
- "integrity": "sha512-KCjlUkcKs6PjOcxolqrXglBDcfCuUCTVlX5BgzgoJHw+1rWH1MCkETLkLe5iLLS9dP5gKC7mp3y6x8c1oGBUtA==",
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.43.0.tgz",
+ "integrity": "sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==",
"cpu": [
"arm64"
],
@@ -596,9 +738,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.34.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.7.tgz",
- "integrity": "sha512-uFLJFz6+utmpbR313TTx+NpPuAXbPz4BhTQzgaP0tozlLnGnQ6rCo6tLwaSa6b7l6gRErjLicXQ1iPiXzYotjw==",
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.43.0.tgz",
+ "integrity": "sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==",
"cpu": [
"arm64"
],
@@ -610,9 +752,9 @@
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
- "version": "4.34.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.7.tgz",
- "integrity": "sha512-ws8pc68UcJJqCpneDFepnwlsMUFoWvPbWXT/XUrJ7rWUL9vLoIN3GAasgG+nCvq8xrE3pIrd+qLX/jotcLy0Qw==",
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.43.0.tgz",
+ "integrity": "sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==",
"cpu": [
"loong64"
],
@@ -624,9 +766,9 @@
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.34.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.7.tgz",
- "integrity": "sha512-vrDk9JDa/BFkxcS2PbWpr0C/LiiSLxFbNOBgfbW6P8TBe9PPHx9Wqbvx2xgNi1TOAyQHQJ7RZFqBiEohm79r0w==",
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.43.0.tgz",
+ "integrity": "sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==",
"cpu": [
"ppc64"
],
@@ -638,9 +780,23 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.34.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.7.tgz",
- "integrity": "sha512-rB+ejFyjtmSo+g/a4eovDD1lHWHVqizN8P0Hm0RElkINpS0XOdpaXloqM4FBkF9ZWEzg6bezymbpLmeMldfLTw==",
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.43.0.tgz",
+ "integrity": "sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.43.0.tgz",
+ "integrity": "sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==",
"cpu": [
"riscv64"
],
@@ -652,9 +808,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.34.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.7.tgz",
- "integrity": "sha512-nNXNjo4As6dNqRn7OrsnHzwTgtypfRA3u3AKr0B3sOOo+HkedIbn8ZtFnB+4XyKJojIfqDKmbIzO1QydQ8c+Pw==",
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.43.0.tgz",
+ "integrity": "sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==",
"cpu": [
"s390x"
],
@@ -666,9 +822,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.34.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.7.tgz",
- "integrity": "sha512-9kPVf9ahnpOMSGlCxXGv980wXD0zRR3wyk8+33/MXQIpQEOpaNe7dEHm5LMfyRZRNt9lMEQuH0jUKj15MkM7QA==",
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.43.0.tgz",
+ "integrity": "sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==",
"cpu": [
"x64"
],
@@ -680,9 +836,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.34.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.7.tgz",
- "integrity": "sha512-7wJPXRWTTPtTFDFezA8sle/1sdgxDjuMoRXEKtx97ViRxGGkVQYovem+Q8Pr/2HxiHp74SSRG+o6R0Yq0shPwQ==",
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.43.0.tgz",
+ "integrity": "sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==",
"cpu": [
"x64"
],
@@ -694,9 +850,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.34.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.7.tgz",
- "integrity": "sha512-MN7aaBC7mAjsiMEZcsJvwNsQVNZShgES/9SzWp1HC9Yjqb5OpexYnRjF7RmE4itbeesHMYYQiAtUAQaSKs2Rfw==",
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.43.0.tgz",
+ "integrity": "sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==",
"cpu": [
"arm64"
],
@@ -708,9 +864,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.34.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.7.tgz",
- "integrity": "sha512-aeawEKYswsFu1LhDM9RIgToobquzdtSc4jSVqHV8uApz4FVvhFl/mKh92wc8WpFc6aYCothV/03UjY6y7yLgbg==",
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.43.0.tgz",
+ "integrity": "sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==",
"cpu": [
"ia32"
],
@@ -722,9 +878,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.34.7",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.7.tgz",
- "integrity": "sha512-4ZedScpxxIrVO7otcZ8kCX1mZArtH2Wfj3uFCxRJ9NO80gg1XV0U/b2f/MKaGwj2X3QopHfoWiDQ917FRpwY3w==",
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.43.0.tgz",
+ "integrity": "sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==",
"cpu": [
"x64"
],
@@ -736,12 +892,33 @@
]
},
"node_modules/@types/estree": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
- "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
+ "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
"dev": true,
"license": "MIT"
},
+ "node_modules/codemirror": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
+ "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/autocomplete": "^6.0.0",
+ "@codemirror/commands": "^6.0.0",
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/lint": "^6.0.0",
+ "@codemirror/search": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0"
+ }
+ },
+ "node_modules/crelt": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
+ "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
+ "license": "MIT"
+ },
"node_modules/esbuild": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
@@ -783,6 +960,21 @@
"@esbuild/win32-x64": "0.25.0"
}
},
+ "node_modules/fdir": {
+ "version": "6.4.6",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
+ "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -830,6 +1022,19 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/postcss": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
@@ -870,13 +1075,13 @@
}
},
"node_modules/rollup": {
- "version": "4.34.7",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.7.tgz",
- "integrity": "sha512-8qhyN0oZ4x0H6wmBgfKxJtxM7qS98YJ0k0kNh5ECVtuchIJ7z9IVVvzpmtQyT10PXKMtBxYr1wQ5Apg8RS8kXQ==",
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.43.0.tgz",
+ "integrity": "sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/estree": "1.0.6"
+ "@types/estree": "1.0.7"
},
"bin": {
"rollup": "dist/bin/rollup"
@@ -886,25 +1091,26 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.34.7",
- "@rollup/rollup-android-arm64": "4.34.7",
- "@rollup/rollup-darwin-arm64": "4.34.7",
- "@rollup/rollup-darwin-x64": "4.34.7",
- "@rollup/rollup-freebsd-arm64": "4.34.7",
- "@rollup/rollup-freebsd-x64": "4.34.7",
- "@rollup/rollup-linux-arm-gnueabihf": "4.34.7",
- "@rollup/rollup-linux-arm-musleabihf": "4.34.7",
- "@rollup/rollup-linux-arm64-gnu": "4.34.7",
- "@rollup/rollup-linux-arm64-musl": "4.34.7",
- "@rollup/rollup-linux-loongarch64-gnu": "4.34.7",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.34.7",
- "@rollup/rollup-linux-riscv64-gnu": "4.34.7",
- "@rollup/rollup-linux-s390x-gnu": "4.34.7",
- "@rollup/rollup-linux-x64-gnu": "4.34.7",
- "@rollup/rollup-linux-x64-musl": "4.34.7",
- "@rollup/rollup-win32-arm64-msvc": "4.34.7",
- "@rollup/rollup-win32-ia32-msvc": "4.34.7",
- "@rollup/rollup-win32-x64-msvc": "4.34.7",
+ "@rollup/rollup-android-arm-eabi": "4.43.0",
+ "@rollup/rollup-android-arm64": "4.43.0",
+ "@rollup/rollup-darwin-arm64": "4.43.0",
+ "@rollup/rollup-darwin-x64": "4.43.0",
+ "@rollup/rollup-freebsd-arm64": "4.43.0",
+ "@rollup/rollup-freebsd-x64": "4.43.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.43.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.43.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.43.0",
+ "@rollup/rollup-linux-arm64-musl": "4.43.0",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.43.0",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.43.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.43.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.43.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.43.0",
+ "@rollup/rollup-linux-x64-gnu": "4.43.0",
+ "@rollup/rollup-linux-x64-musl": "4.43.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.43.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.43.0",
+ "@rollup/rollup-win32-x64-msvc": "4.43.0",
"fsevents": "~2.3.2"
}
},
@@ -918,6 +1124,29 @@
"node": ">=0.10.0"
}
},
+ "node_modules/style-mod": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
+ "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==",
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.14",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
+ "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
"node_modules/typescript": {
"version": "5.6.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
@@ -933,15 +1162,18 @@
}
},
"node_modules/vite": {
- "version": "6.2.5",
- "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz",
- "integrity": "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==",
+ "version": "6.3.5",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
+ "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2",
"postcss": "^8.5.3",
- "rollup": "^4.30.1"
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.13"
},
"bin": {
"vite": "bin/vite.js"
@@ -1003,6 +1235,12 @@
"optional": true
}
}
+ },
+ "node_modules/w3c-keyname": {
+ "version": "2.2.8",
+ "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
+ "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
+ "license": "MIT"
}
}
}
diff --git a/playground/package.json b/playground/package.json
index acb22a3..c0a6ae4 100644
--- a/playground/package.json
+++ b/playground/package.json
@@ -9,12 +9,16 @@
"preview": "vite preview"
},
"devDependencies": {
+ "@codemirror/theme-one-dark": "^6.1.2",
+ "@lezer/generator": "^1.7.3",
+ "@lezer/lr": "^1.4.2",
"esbuild": "^0.25.0",
"typescript": "~5.6.2",
"vite": "^6.1.0"
},
"dependencies": {
"@preact/signals": "^1.3.0",
+ "codemirror": "^6.0.1",
"monaco-editor": "^0.52.0",
"preact": "^10.24.3"
}
diff --git a/playground/src/cmeditor.ts b/playground/src/cmeditor.ts
new file mode 100644
index 0000000..bc51ce1
--- /dev/null
+++ b/playground/src/cmeditor.ts
@@ -0,0 +1,196 @@
+import { AbstractEditor, EditorDelegate, Marker } from "./types";
+import { basicSetup } from "codemirror";
+import { EditorView, hoverTooltip, Tooltip } from "@codemirror/view";
+import { Compartment } from "@codemirror/state";
+import { parser } from "./parser.js";
+import { oneDark, oneDark as themeOneDark } from "@codemirror/theme-one-dark";
+import { styleTags, tags as t } from "@lezer/highlight";
+import { linter, Diagnostic } from "@codemirror/lint";
+import {
+ LanguageSupport,
+ LRLanguage,
+ StreamLanguage,
+ StringStream,
+} from "@codemirror/language";
+
+let parserWithMetadata = parser.configure({
+ props: [
+ styleTags({
+ Identifier: t.variableName,
+ LineComment: t.lineComment,
+ "if then else data where": t.keyword,
+ }),
+ // indentNodeProp, foldNodeProp
+ ],
+});
+
+const newtLanguage = LRLanguage.define({
+ parser: parserWithMetadata,
+ languageData: {
+ commentTokens: {
+ line: "--",
+ },
+ },
+});
+// prettier did this...
+const keywords = [
+ "let",
+ "in",
+ "where",
+ "case",
+ "of",
+ "data",
+ "U",
+ "do",
+ "ptype",
+ "pfunc",
+ "module",
+ "infixl",
+ "infixr",
+ "infix",
+ "∀",
+ "forall",
+ "import",
+ "uses",
+ "class",
+ "instance",
+ "record",
+ "constructor",
+ "if",
+ "then",
+ "else",
+ "$",
+ "λ",
+ "?",
+ "@",
+ ".",
+ "->",
+ "→",
+ ":",
+ "=>",
+ ":=",
+ "$=",
+ "=",
+ "<-",
+ "\\",
+ "_",
+ "|",
+];
+
+interface State {
+ tokenizer(stream: StringStream, state: State): string | null;
+}
+
+function tokenizer(stream: StringStream, state: State): string | null {
+ stream.eatSpace();
+ if (stream.match("--")) {
+ stream.skipToEnd();
+ return "comment";
+ }
+ if (stream.match(/^[/]-/)) {
+ state.tokenizer = commentTokenizer;
+ return state.tokenizer(stream, state);
+ }
+ if (stream.match(/^[\w_][\w\d_']*/)) {
+ let word = stream.current();
+ if (keywords.includes(word)) return "keyword";
+ if (word[0] >= "A" && word[0] <= "Z") return "typename";
+ return "identifier";
+ }
+ // unhandled
+ stream.next();
+ return null;
+}
+
+function commentTokenizer(stream: StringStream, state: State): string | null {
+ console.log("ctok");
+ let dash = false;
+ let ch;
+ while ((ch = stream.next())) {
+ if (dash && ch === "/") {
+ state.tokenizer = tokenizer;
+ return "comment";
+ }
+ dash = ch === "-";
+ }
+ console.log("XX", stream.current());
+ return "comment";
+}
+
+const newtLanguage2 = StreamLanguage.define({
+ startState: () => ({ tokenizer }),
+ token(stream, st) {
+ return st.tokenizer(stream, st);
+ },
+});
+
+function newt() {
+ return new LanguageSupport(newtLanguage2);
+}
+
+export class CMEditor implements AbstractEditor {
+ view: EditorView;
+ delegate: EditorDelegate;
+ theme: Compartment;
+ constructor(container: HTMLElement, doc: string, delegate: EditorDelegate) {
+ this.delegate = delegate;
+ this.theme = new Compartment();
+
+ this.view = new EditorView({
+ doc,
+ parent: container,
+ extensions: [
+ basicSetup,
+ linter((view) => this.delegate.lint(view)),
+ this.theme.of(EditorView.baseTheme({})),
+ hoverTooltip((view, pos) => {
+ let cursor = this.view.state.doc.lineAt(pos);
+ let line = cursor.number;
+ let range = this.view.state.wordAt(pos);
+ console.log(range);
+ if (range) {
+ let col = range.from - cursor.from;
+ let word = this.view.state.doc.sliceString(range.from, range.to);
+ let entry = this.delegate.getEntry(word, line, col);
+ console.log("entry", entry);
+ if (entry) {
+ let rval: Tooltip = {
+ pos: range.head,
+ above: true,
+ create: () => {
+ let dom = document.createElement("div");
+ dom.className = "tooltip";
+ dom.textContent = entry.type;
+ return { dom };
+ },
+ };
+ return rval;
+ }
+ }
+ // we'll iterate the syntax tree for word.
+ // let entry = delegate.getEntry(word, line, col)
+ return null;
+ }),
+ newt(),
+ ],
+ });
+ }
+ setDark(isDark: boolean) {
+ this.view.dispatch({
+ effects: this.theme.reconfigure(
+ isDark ? oneDark : EditorView.baseTheme({})
+ ),
+ });
+ }
+ setValue(_doc: string) {
+ // Is this all we can do?
+ this.view.dispatch({
+ changes: { from: 0, to: this.view.state.doc.length, insert: _doc },
+ });
+ }
+ getValue() {
+ // maybe?
+ return this.view.state.doc.toString();
+ }
+ setMarkers(_: Marker[]) {}
+}
diff --git a/playground/src/main.ts b/playground/src/main.ts
index a827627..57a88dd 100644
--- a/playground/src/main.ts
+++ b/playground/src/main.ts
@@ -1,91 +1,25 @@
import { effect, signal } from "@preact/signals";
-import { newtConfig, newtTokens } from "./monarch.ts";
-import * as monaco from "monaco-editor";
+import { Diagnostic } from "@codemirror/lint";
import { useEffect, useRef, useState } from "preact/hooks";
import { h, render } from "preact";
import { ChangeEvent } from "preact/compat";
import { archive, preload } from "./preload.ts";
-import { CompileReq, CompileRes, Message } from "./types.ts";
-import { ABBREV } from "./abbrev.ts";
-
-// editor.(createModel / setModel / getModels) to switch files
-
-// TODO - remember files and allow switching?
-
-// static zip filesystem with user changes overlaid via localStorage
-// download individual files (we could use the cheap compression from the pdflib or no compression to make zip download)
-// would need way to reset/delete
-
-interface FC {
- file: string;
- line: number;
- col: number;
-}
-
-interface TopEntry {
- fc: FC;
- name: String;
- type: String;
-}
-interface TopData {
- context: TopEntry[];
-}
+import {
+ AbstractEditor,
+ EditorDelegate,
+ CompileReq,
+ CompileRes,
+ Message,
+ TopData,
+ Marker,
+} from "./types.ts";
+import { CMEditor } from "./cmeditor.ts";
let topData: undefined | TopData;
-// we need to fix the definition of word
-monaco.languages.register({ id: "newt" });
-monaco.languages.setMonarchTokensProvider("newt", newtTokens);
-monaco.languages.setLanguageConfiguration("newt", newtConfig);
-monaco.languages.registerDefinitionProvider("newt", {
- provideDefinition(model, position, token) {
- if (!topData) return;
- // HACK - we don't know our filename which was generated from `module` decl, so
- // assume the last context entry is in our file.
- let last = topData.context[topData.context.length - 1];
- let file = last.fc.file;
-
- const info = model.getWordAtPosition(position);
- if (!info) return;
- let entry = topData.context.find((entry) => entry.name === info.word);
- // we can't switch files at the moment
- if (!entry || entry.fc.file !== file) return;
- let lineNumber = entry.fc.line + 1;
- let column = entry.fc.col + 1;
- let word = model.getWordAtPosition({ lineNumber, column });
- let range = new monaco.Range(lineNumber, column, lineNumber, column);
- if (word) {
- range = new monaco.Range(
- lineNumber,
- word.startColumn,
- lineNumber,
- word.endColumn
- );
- }
- return { uri: model.uri, range };
- },
-});
-monaco.languages.registerHoverProvider("newt", {
- provideHover(model, position, token, context) {
- if (!topData) return;
- const info = model.getWordAtPosition(position);
- if (!info) return;
- let entry = topData.context.find((entry) => entry.name === info.word);
- if (!entry) return;
- return {
- range: new monaco.Range(
- position.lineNumber,
- info.startColumn,
- position.lineNumber,
- info.endColumn
- ),
- contents: [{ value: `${entry.name} : ${entry.type}` }],
- };
- },
-});
const newtWorker = new Worker("worker.js");
+// :FIXME use because Safari
let postMessage = (msg: CompileReq) => newtWorker.postMessage(msg);
-
// Safari/MobileSafari have small stacks in webworkers.
if (navigator.vendor.includes("Apple")) {
const workerFrame = document.createElement("iframe");
@@ -104,7 +38,7 @@ document.body.appendChild(iframe);
function run(src: string) {
console.log("SEND TO", iframe.contentWindow);
const fileName = state.currentFile.value;
- postMessage({ type: "compileRequest", fileName, src });
+ // postMessage({ type: "compileRequest", fileName, src });
}
function runOutput() {
@@ -134,6 +68,9 @@ function setOutput(output: string) {
state.output.value = output;
}
+let lastID = 0;
+const nextID = () => "" + lastID++;
+
window.onmessage = (ev: MessageEvent) => {
console.log("window got", ev.data);
if ("messages" in ev.data) state.messages.value = ev.data.messages;
@@ -142,28 +79,47 @@ window.onmessage = (ev: MessageEvent) => {
}
// safari callback
if ("output" in ev.data) {
+ newtWorker.onmessage?.(ev)
setOutput(ev.data.output);
state.javascript.value = ev.data.javascript;
}
};
+// TODO wrap up IPC
+
+type Suspense = {
+ resolve: (value: T | PromiseLike) => void;
+ reject: (reason?: any) => void;
+};
+
+const callbacks: Record> = {};
+
newtWorker.onmessage = (ev: MessageEvent) => {
+ let suspense = callbacks[ev.data.id];
+ if (suspense) {
+ suspense.resolve(ev.data.output);
+ delete callbacks[ev.data.id];
+ }
+ console.log('result', ev.data, 'suspense', suspense)
+ // FIXME - we want to have the callback take a response for its command
setOutput(ev.data.output);
state.javascript.value = ev.data.javascript;
};
-self.MonacoEnvironment = {
- getWorkerUrl(moduleId, _label) {
- console.log("Get worker", moduleId);
- return moduleId;
- },
-};
+function runCommand(req: CompileReq) {
+ return new Promise(
+ (resolve, reject) => {
+ callbacks[req.id] = { resolve, reject }
+ postMessage(req);
+ }
+ );
+}
const state = {
output: signal(""),
javascript: signal(""),
messages: signal([]),
- editor: signal(null),
+ editor: signal(null),
dark: signal(false),
files: signal(["Tour.newt"]),
currentFile: signal(localStorage.currentFile ?? "Tour.newt"),
@@ -174,13 +130,15 @@ if (window.matchMedia) {
function checkDark(ev: { matches: boolean }) {
console.log("CHANGE", ev);
if (ev.matches) {
- monaco.editor.setTheme("vs-dark");
+ // monaco.editor.setTheme("vs-dark");
document.body.className = "dark";
state.dark.value = true;
+ state.editor.value?.setDark(true);
} else {
- monaco.editor.setTheme("vs");
+ // monaco.editor.setTheme("vs");
document.body.className = "light";
state.dark.value = false;
+ state.editor.value?.setDark(false);
}
}
let query = window.matchMedia("(prefers-color-scheme: dark)");
@@ -215,69 +173,76 @@ let initialVertical = localStorage.vertical == "true";
effect(() => {
let text = state.output.value;
let editor = state.editor.value;
- if (editor) processOutput(editor, text);
+ // TODO - abstract this for both editors
+ // if (editor) processOutput(editor, text);
});
interface EditorProps {
initialValue: string;
}
+const language: EditorDelegate = {
+ getEntry(word, _row, _col) {
+ return topData?.context.find((entry) => entry.name === word);
+ },
+ onChange(value) {
+ // clearTimeout(timeout);
+ // timeout = setTimeout(() => {
+ // run(value);
+ // localStorage.code = value;
+ // }, 1000);
+ },
+ getFileName() {
+ if (!topData) return "";
+ let last = topData.context[topData.context.length - 1];
+ return last.fc.file;
+ },
+ async lint(view) {
+ console.log("LINT");
+ let src = view.state.doc.toString();
+ const fileName = state.currentFile.value;
+ console.log("FN", fileName);
+ // console.log("SRC", src);
+ try {
+ let out = await runCommand({
+ id: nextID(),
+ type: "compileRequest",
+ fileName,
+ src,
+ });
+ console.log("OUT", out);
+ let markers = processOutput(out);
+ let diags: Diagnostic[] = []
+ for (let marker of markers) {
+ let col = marker.startColumn
+
+ let line = view.state.doc.line(marker.startLineNumber)
+ const pos = line.from + col - 1;
+ let word = view.state.wordAt(pos)
+ diags.push({
+ from: word?.from ?? pos,
+ to: word?.to ?? pos+1,
+ severity: marker.severity,
+ message: marker.message,
+ })
+ }
+ return diags
+ } catch (e) {
+ console.log("ERR", e);
+ }
+
+ return [];
+ },
+};
function Editor({ initialValue }: EditorProps) {
const ref = useRef(null);
+
useEffect(() => {
const container = ref.current!;
- const editor = monaco.editor.create(container, {
- value,
- language: "newt",
- fontFamily: "Comic Code, Menlo, Monaco, Courier New, sans",
- automaticLayout: true,
- acceptSuggestionOnEnter: "off",
- unicodeHighlight: { ambiguousCharacters: false },
- minimap: { enabled: false },
- });
+ const editor = new CMEditor(container, value, language);
+ // const editor = new MonacoEditor(container, value, language)
state.editor.value = editor;
-
- editor.onDidChangeModelContent((ev) => {
- clearTimeout(timeout);
- timeout = setTimeout(() => {
- let value = editor.getValue();
- run(value);
- localStorage.code = value;
- }, 1000);
- let last = ev.changes[ev.changes.length - 1];
- const model = editor.getModel();
- // figure out heuristics here, what chars do we want to trigger?
- // the lean one will only be active if it sees you type \
- // and bail if it decides you've gone elsewhere
- // it maintains an underline annotation, too.
- if (model && last.text && " ')\\".includes(last.text)) {
- console.log('LAST', last)
- let { startLineNumber, startColumn } = last.range;
- const text = model.getValueInRange(
- new monaco.Range(
- startLineNumber,
- Math.max(1, startColumn - 10),
- startLineNumber,
- startColumn
- )
- );
- const m = text.match(/(\\[^ ]+)$/);
- if (m) {
- let cand = m[0];
- console.log("GOT", cand);
- let text = ABBREV[cand];
- if (text) {
- const range = new monaco.Range(
- startLineNumber,
- startColumn - cand.length,
- startLineNumber,
- startColumn
- );
- editor.executeEdits("replaceSequence", [{ range, text: text }]);
- }
- }
- }
- });
+ editor.setDark(state.dark.value);
if (initialValue === LOADING) loadFile("Tour.newt");
else run(initialValue);
}, []);
@@ -431,11 +396,12 @@ let timeout: number | undefined;
// Adapted from the vscode extension, but types are slightly different
// and positions are 1-based.
const processOutput = (
- editor: monaco.editor.IStandaloneCodeEditor,
+ // editor: AbstractEditor,
output: string
) => {
- let model = editor.getModel()!;
- let markers: monaco.editor.IMarkerData[] = [];
+ // let model = editor.getModel()!;
+ console.log('process output', output)
+ let markers: Marker[] = [];
let lines = output.split("\n");
let m = lines[0].match(/.*Process (.*)/);
let fn = "";
@@ -451,11 +417,8 @@ const processOutput = (
if (fn && fn !== file) {
lineNumber = column = 0;
}
- let start = { column, lineNumber };
// we don't have the full range, so grab the surrounding word
let endColumn = column + 1;
- let word = model.getWordAtPosition(start);
- if (word) endColumn = word.endColumn;
// heuristics to grab the entire message:
// anything indented
@@ -464,13 +427,9 @@ const processOutput = (
while (lines[i + 1]?.match(/^( )/)) {
message += "\n" + lines[++i];
}
- const severity =
- kind === "ERROR"
- ? monaco.MarkerSeverity.Error
- : monaco.MarkerSeverity.Info;
if (kind === "ERROR" || lineNumber)
markers.push({
- severity,
+ severity: kind === 'ERROR' ? 'error' : 'info',
message,
startLineNumber: lineNumber,
endLineNumber: lineNumber,
@@ -479,5 +438,7 @@ const processOutput = (
});
}
}
- monaco.editor.setModelMarkers(model, "newt", markers);
+ console.log('markers', markers)
+ // editor.setMarkers(markers)
+ return markers;
};
diff --git a/playground/src/monaco.ts b/playground/src/monaco.ts
new file mode 100644
index 0000000..d103023
--- /dev/null
+++ b/playground/src/monaco.ts
@@ -0,0 +1,210 @@
+import { ABBREV } from "./abbrev.ts";
+import { newtConfig, newtTokens } from "./monarch.ts";
+import * as monaco from "monaco-editor";
+import { AbstractEditor, EditorDelegate, Marker } from "./types.ts";
+
+// we need to fix the definition of word
+monaco.languages.register({ id: "newt" });
+monaco.languages.setMonarchTokensProvider("newt", newtTokens);
+monaco.languages.setLanguageConfiguration("newt", newtConfig);
+
+self.MonacoEnvironment = {
+ getWorkerUrl(moduleId, _label) {
+ console.log("Get worker", moduleId);
+ return moduleId;
+ },
+};
+
+export class MonacoEditor implements AbstractEditor {
+ editor: monaco.editor.IStandaloneCodeEditor;
+ delegate: EditorDelegate;
+
+ constructor(container: HTMLElement, value: string, language: EditorDelegate) {
+ this.delegate = language;
+ let editor = (this.editor = monaco.editor.create(container, {
+ value,
+ language: "newt",
+ fontFamily: "Comic Code, Menlo, Monaco, Courier New, sans",
+ automaticLayout: true,
+ acceptSuggestionOnEnter: "off",
+ unicodeHighlight: { ambiguousCharacters: false },
+ minimap: { enabled: false },
+ }));
+ monaco.languages.registerDefinitionProvider("newt", {
+ provideDefinition(model, position, token) {
+ const info = model.getWordAtPosition(position);
+ if (!info) return;
+ let entry = language.getEntry(
+ info.word,
+ position.lineNumber,
+ info.startColumn
+ );
+ if (!entry) return;
+
+ // HACK - we don't know our filename which was generated from `module` decl, so
+ // assume the last context entry is in our file.
+
+ let file = language.getFileName();
+ if (!entry || entry.fc.file !== file) return;
+ let lineNumber = entry.fc.line + 1;
+ let column = entry.fc.col + 1;
+ let word = model.getWordAtPosition({ lineNumber, column });
+ let range = new monaco.Range(lineNumber, column, lineNumber, column);
+ if (word) {
+ range = new monaco.Range(
+ lineNumber,
+ word.startColumn,
+ lineNumber,
+ word.endColumn
+ );
+ }
+ return { uri: model.uri, range };
+ },
+ });
+ monaco.languages.registerHoverProvider("newt", {
+ provideHover(model, position, token, context) {
+ const info = model.getWordAtPosition(position);
+ if (!info) return;
+ let entry = language.getEntry(
+ info.word,
+ position.lineNumber,
+ info.startColumn
+ );
+ if (!entry) return;
+ return {
+ range: new monaco.Range(
+ position.lineNumber,
+ info.startColumn,
+ position.lineNumber,
+ info.endColumn
+ ),
+ contents: [{ value: `${entry.name} : ${entry.type}` }],
+ };
+ },
+ });
+ editor.onDidChangeModelContent((ev) => {
+ this.delegate.onChange(editor.getValue());
+
+ let last = ev.changes[ev.changes.length - 1];
+ const model = editor.getModel();
+ // figure out heuristics here, what chars do we want to trigger?
+ // the lean one will only be active if it sees you type \
+ // and bail if it decides you've gone elsewhere
+ // it maintains an underline annotation, too.
+ if (model && last.text && " ')\\".includes(last.text)) {
+ console.log("LAST", last);
+ let { startLineNumber, startColumn } = last.range;
+ const text = model.getValueInRange(
+ new monaco.Range(
+ startLineNumber,
+ Math.max(1, startColumn - 10),
+ startLineNumber,
+ startColumn
+ )
+ );
+ const m = text.match(/(\\[^ ]+)$/);
+ if (m) {
+ let cand = m[0];
+ console.log("GOT", cand);
+ let text = ABBREV[cand];
+ if (text) {
+ const range = new monaco.Range(
+ startLineNumber,
+ startColumn - cand.length,
+ startLineNumber,
+ startColumn
+ );
+ editor.executeEdits("replaceSequence", [{ range, text: text }]);
+ }
+ }
+ }
+ });
+ }
+ setValue(value: string) {
+ this.editor.setValue(value);
+ }
+ getValue() {
+ return this.editor.getValue();
+ }
+ setMarkers(markers: Marker[]) {
+ let model = this.editor.getModel()!;
+ const mapMarker = (marker: Marker): monaco.editor.IMarkerData => {
+ let severity =
+ marker.severity === "ERROR"
+ ? monaco.MarkerSeverity.Error
+ : monaco.MarkerSeverity.Info;
+ // translate to surrounding word
+ // FIXME - we have `getWord` in monaco, but probably belongs to the delegate
+ // and eventually we have better FC
+ let { message, startColumn, endColumn, startLineNumber, endLineNumber } =
+ marker;
+ let word = model.getWordAtPosition({
+ column: startColumn,
+ lineNumber: startLineNumber,
+ });
+ if (word) endColumn = word.endColumn;
+ return {
+ message,
+ startColumn,
+ endColumn,
+ startLineNumber,
+ endLineNumber,
+ severity,
+ };
+ };
+ monaco.editor.setModelMarkers(model, "newt", markers.map(mapMarker));
+ }
+}
+
+// scratch
+const processOutput = (
+ editor: monaco.editor.IStandaloneCodeEditor,
+ output: string
+) => {
+ let model = editor.getModel()!;
+ let markers: monaco.editor.IMarkerData[] = [];
+ let lines = output.split("\n");
+ let m = lines[0].match(/.*Process (.*)/);
+ let fn = "";
+ if (m) fn = m[1];
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+ const match = line.match(/(INFO|ERROR) at (.*):\((\d+), (\d+)\):\s*(.*)/);
+ if (match) {
+ let [_full, kind, file, line, col, message] = match;
+ let lineNumber = +line + 1;
+ let column = +col + 1;
+ // FIXME - pass the real path in
+ if (fn && fn !== file) {
+ lineNumber = column = 0;
+ }
+ let start = { column, lineNumber };
+ // we don't have the full range, so grab the surrounding word
+ let endColumn = column + 1;
+ let word = model.getWordAtPosition(start);
+ if (word) endColumn = word.endColumn;
+
+ // heuristics to grab the entire message:
+ // anything indented
+ // Context:, or Goal: are part of PRINTME
+ // unexpected / expecting appear in parse errors
+ while (lines[i + 1]?.match(/^( )/)) {
+ message += "\n" + lines[++i];
+ }
+ const severity =
+ kind === "ERROR"
+ ? monaco.MarkerSeverity.Error
+ : monaco.MarkerSeverity.Info;
+ if (kind === "ERROR" || lineNumber)
+ markers.push({
+ severity,
+ message,
+ startLineNumber: lineNumber,
+ endLineNumber: lineNumber,
+ startColumn: column,
+ endColumn,
+ });
+ }
+ }
+ monaco.editor.setModelMarkers(model, "newt", markers);
+};
diff --git a/playground/src/types.ts b/playground/src/types.ts
index ec29e48..6cdd5f7 100644
--- a/playground/src/types.ts
+++ b/playground/src/types.ts
@@ -1,10 +1,15 @@
+import { EditorView } from "codemirror";
+import { linter, Diagnostic } from "@codemirror/lint";
+
export interface CompileReq {
+ id: string
type: "compileRequest";
fileName: string;
src: string;
}
export interface CompileRes {
+ id: string
type: "compileResult";
output: string;
javascript: string;
@@ -26,3 +31,44 @@ export interface ExecCode {
}
export type Message = CompileReq | CompileRes | ConsoleList | ConsoleItem | ExecCode
+// editor.(createModel / setModel / getModels) to switch files
+// TODO - remember files and allow switching?
+// static zip filesystem with user changes overlaid via localStorage
+// download individual files (we could use the cheap compression from the pdflib or no compression to make zip download)
+// would need way to reset/delete
+export interface FC {
+ file: string;
+ line: number;
+ col: number;
+}
+
+interface TopEntry {
+ fc: FC;
+ name: string;
+ type: string;
+}
+
+export interface TopData {
+ context: TopEntry[];
+}
+export interface EditorDelegate {
+ getEntry(word: string, row: number, col: number): TopEntry | undefined
+ onChange(value: string): unknown
+ getFileName(): string
+ lint(view: EditorView): Promise | Diagnostic[]
+}
+export interface Marker {
+ severity: 'error' | 'info' | 'warning'
+ message: string
+ startColumn: number
+ startLineNumber: number
+ endColumn: number
+ endLineNumber: number
+}
+export interface AbstractEditor {
+ setValue: (_: string) => unknown;
+ getValue: () => string;
+ setMarkers: (_: Marker[]) => unknown
+ setDark(isDark: boolean): unknown
+}
+
diff --git a/playground/src/worker.ts b/playground/src/worker.ts
index 4860821..1875ff2 100644
--- a/playground/src/worker.ts
+++ b/playground/src/worker.ts
@@ -10,7 +10,7 @@ const handleMessage = async function (ev: { data: CompileReq }) {
console.log("message", ev.data);
await preload;
shim.archive = archive;
- let { src, fileName } = ev.data;
+ let { id, src, fileName } = ev.data;
const outfile = "out.js";
shim.process.argv = ["browser", "newt", fileName, "-o", outfile, "--top"];
shim.files[fileName] = new TextEncoder().encode(src);
@@ -29,7 +29,7 @@ const handleMessage = async function (ev: { data: CompileReq }) {
console.log(`process ${fileName} in ${duration} ms`);
let javascript = new TextDecoder().decode(shim.files[outfile]);
let output = shim.stdout;
- sendResponse({ type: 'compileResult', javascript, output, duration });
+ sendResponse({ id, type: 'compileResult', javascript, output, duration });
};
// hooks for worker.html to override
diff --git a/playground/style.css b/playground/style.css
index 28c51eb..e5bdde2 100644
--- a/playground/style.css
+++ b/playground/style.css
@@ -1,4 +1,7 @@
-
+body {
+ overflow: hidden;
+ font-size: 12px;
+}
svg.icon path {
stroke: black;
fill: none;
@@ -87,3 +90,6 @@ svg.icon path {
white-space: pre;
padding: 5px;
}
+.tooltip {
+ padding: 5px;
+}
diff --git a/playground/tsconfig.json b/playground/tsconfig.json
index a0eb396..b0f79b8 100644
--- a/playground/tsconfig.json
+++ b/playground/tsconfig.json
@@ -5,7 +5,7 @@
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
-
+ "allowJs": true,
"outDir": "out",
"sourceMap": true,