From 8191c6bb3c5aeac64abfacd52350fb6dcd3192ac Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 20 Jul 2026 00:16:37 -0500 Subject: [PATCH] syntax highlighting --- package-lock.json | 383 ++++++++++++++++++++++- package.json | 5 +- src/webpage/highlighter/clike/langs.json | 20 ++ src/webpage/highlighter/clike/lex.ts | 105 +++++++ src/webpage/highlighter/colors.ts | 8 + src/webpage/highlighter/highlight.ts | 132 ++++++++ src/webpage/highlighter/index.ts | 34 ++ src/webpage/highlighter/python/lex.ts | 71 +++++ src/webpage/highlighter/xml/lex.ts | 35 +++ src/webpage/markdown.ts | 4 + src/webpage/style.css | 19 ++ tsconfig.json | 4 +- 12 files changed, 806 insertions(+), 14 deletions(-) create mode 100644 src/webpage/highlighter/clike/langs.json create mode 100644 src/webpage/highlighter/clike/lex.ts create mode 100644 src/webpage/highlighter/colors.ts create mode 100644 src/webpage/highlighter/highlight.ts create mode 100644 src/webpage/highlighter/index.ts create mode 100644 src/webpage/highlighter/python/lex.ts create mode 100644 src/webpage/highlighter/xml/lex.ts diff --git a/package-lock.json b/package-lock.json index d3dc8da..1507cbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "jankclient", - "version": "0.2.0", + "name": "fermi", + "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "jankclient", - "version": "0.2.0", + "name": "fermi", + "version": "0.0.1", "license": "GPL-3.0", "dependencies": { "@swc/core": "1.11.24" @@ -16,7 +16,7 @@ "@types/node": "^25.5.0", "@types/node-fetch": "^2.6.13", "prettier": "^3.7.4", - "typescript": "5.9.3" + "typescript": "^7.0.2" } }, "node_modules/@swc/core": { @@ -272,6 +272,346 @@ "form-data": "^4.0.4" } }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -551,17 +891,38 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", "dev": true, "license": "Apache-2.0", "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "tsc": "bin/tsc" }, "engines": { - "node": ">=14.17" + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" } }, "node_modules/undici-types": { diff --git a/package.json b/package.json index 30da0a5..63566be 100644 --- a/package.json +++ b/package.json @@ -20,12 +20,15 @@ "@types/node": "^25.5.0", "@types/node-fetch": "^2.6.13", "prettier": "^3.7.4", - "typescript": "5.9.3" + "typescript": "^7.0.2" }, "prettier": { "useTabs": true, "printWidth": 100, "semi": true, "bracketSpacing": false + }, + "allowScripts": { + "@swc/core@1.11.24": true } } diff --git a/src/webpage/highlighter/clike/langs.json b/src/webpage/highlighter/clike/langs.json new file mode 100644 index 0000000..462bcb3 --- /dev/null +++ b/src/webpage/highlighter/clike/langs.json @@ -0,0 +1,20 @@ +[ +{ + "names":["js","ts","javascript"], + "keywords": ["break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete", "do","else", "export", "extends", "finally", "for", "function", "if", "import", "in", "instanceof", "new", "return", "super", "switch", "this", "throw", "try", "typeof", "var", "void", "while", "with", "yield", "let", "await", "true", "false", "null", "undefined"], + "firstLineShabang": true, + "hashComments": false, + "doubleSlashComments": true, + "multilineSlashComments": true, + "JSLikeTemplateStrings": true +}, +{ + "names":["h","c"], + "keywords":["auto", "break", "case", "char", "continue", "do", "default", "const", "double", "else", "enum", "extern", "for", "if", "goto", "float", "int", "long", "register", "return", "signed", "static", "sizeof", "short", "struct", "switch", "typedef", "union", "void", "while", "volatile", "unsigned"], + "firstLineShabang": false, + "hashComments": true, + "doubleSlashComments": true, + "multilineSlashComments": false, + "JSLikeTemplateStrings": false +} +] diff --git a/src/webpage/highlighter/clike/lex.ts b/src/webpage/highlighter/clike/lex.ts new file mode 100644 index 0000000..5398c11 --- /dev/null +++ b/src/webpage/highlighter/clike/lex.ts @@ -0,0 +1,105 @@ +import {hcolors} from "../colors.js"; + +interface clikeConf { + names: string[]; + keywords: string[]; + firstLineShabang: boolean; + hashComments: boolean; + doubleSlashComments: boolean; + multilineSlashComments: boolean; + JSLikeTemplateStrings: boolean; +} +function regex(config: clikeConf): RegExp { + const conds = [] as string[]; + if (config.hashComments) { + conds.push("#.*"); + } + if (config.doubleSlashComments) { + conds.push("\\/\\/.*"); + } + if (config.multilineSlashComments) { + conds.push("\\/\\*([^*]|\\*([^\\/]|$))*(\\*\\/)?"); + } + if (config.multilineSlashComments) { + conds.push("\\/\\*([^*]|\\*([^\\/]|$))*(\\*\\/)?"); + } + if (config.JSLikeTemplateStrings) { + conds.push("`(\\\\.|[^`])*`?"); + } + conds.push("(.|\n)"); + return new RegExp( + '"(\\\\(.|\\n)|[^"\\n\\\\])*"?|\'(\\\\(.|\\n)|[^"\\n\\\\])*\'?|\\s+|[a-zA-Z][a-zA-Z0-9]*|[0-9][a-zA-Z0-9]*\\.?[a-zA-Z0-9]*|' + + conds.join("|"), + "gm", + ); +} +let j: undefined | clikeConf[] = undefined; +export async function canLex(lang: string) { + if (!j) { + j = await (await fetch("/highlighter/clike/langs.json")).json(); + } + const conf = j!.find((_) => _.names.includes(lang)); + if (conf) { + return (code: string) => { + return lex(code, conf); + }; + } + return undefined; +} +function* lex(code: string, config: clikeConf) { + const r = regex(config); + const keywords = new Set(config.keywords); + if (config.firstLineShabang) { + const m = code.match(/^#!.*\n?/gm); + if (m) { + code = code.replace(/^#!.*\n?/gm, ""); + yield { + type: hcolors.comment, + content: m[0], + }; + } + } + for (const [lex] of code.matchAll(r)) { + if (lex.startsWith("//") && config.doubleSlashComments) { + yield { + type: hcolors.comment, + content: lex, + }; + } else if (lex.startsWith("/*") && config.multilineSlashComments) { + yield { + type: hcolors.comment, + content: lex, + }; + } else if (lex.startsWith("#") && config.hashComments) { + yield { + type: hcolors.comment, + content: lex, + }; + } else if (lex.startsWith("`") && config.JSLikeTemplateStrings) { + yield { + type: hcolors.string, + content: lex, + }; + } else if (lex.match(/^("|')/)) { + yield { + type: hcolors.string, + content: lex, + }; + } else if (lex.match(/^[0-9]/)) { + yield { + type: hcolors.number, + content: lex, + }; + } else if (lex.match(/^[a-zA-Z]/)) { + yield { + type: keywords.has(lex) ? hcolors.keyword : hcolors.identifier, + content: lex, + }; + } else { + yield { + type: hcolors.symbol, + content: lex, + }; + } + } +} diff --git a/src/webpage/highlighter/colors.ts b/src/webpage/highlighter/colors.ts new file mode 100644 index 0000000..3c2a5a1 --- /dev/null +++ b/src/webpage/highlighter/colors.ts @@ -0,0 +1,8 @@ +export const enum hcolors { + keyword, + number, + string, + symbol, + comment, + identifier, +} diff --git a/src/webpage/highlighter/highlight.ts b/src/webpage/highlighter/highlight.ts new file mode 100644 index 0000000..655a3c1 --- /dev/null +++ b/src/webpage/highlighter/highlight.ts @@ -0,0 +1,132 @@ +import {hcolors} from "./colors"; +function ifCSSAllows() { + if (!("CSS" in globalThis && CSS.highlights && globalThis.Highlight)) return () => {}; + const strLen = (str: string) => { + return [...str].length; + }; + const highkeyword = new Highlight(); + CSS.highlights.set("highkeyword", highkeyword); + + const highnumber = new Highlight(); + CSS.highlights.set("highnumber", highnumber); + + const highstring = new Highlight(); + CSS.highlights.set("highstring", highstring); + + const highsymbol = new Highlight(); + CSS.highlights.set("highsymbol", highsymbol); + const highcomment = new Highlight(); + CSS.highlights.set("highcomment", highcomment); + + const highidentifier = new Highlight(); + CSS.highlights.set("highidentifier", highidentifier); + const f = new FinalizationRegistry((id) => { + eliMap.delete(id); + }); + const eliMap = new Map(); + function addToWeakRange(r: Range, elm: HTMLElement, h: Highlight) { + let prop = elm.getAttribute("UUID"); + if (!prop) { + prop = Math.random() + ""; + elm.setAttribute("UUID", prop); + f.register(elm, prop); + } + let r2: [Highlight, Range][] | undefined = eliMap.get(prop); + if (!r2) { + r2 = [] as [Highlight, Range][]; + eliMap.set(prop, r2); + } + r2.push([h, r]); + } + function clearHighlights(elm: HTMLElement) { + let prop = elm.getAttribute("UUID"); + if (!prop) return; + const m = eliMap.get(prop); + if (!m) return; + eliMap.delete(prop); + for (const [h, r] of m) { + h.delete(r); + } + } + + function highlight(r: Range, color: hcolors, elm: HTMLElement) { + switch (color) { + case hcolors.keyword: + highkeyword.add(r); + addToWeakRange(r, elm, highkeyword); + break; + case hcolors.number: + highnumber.add(r); + addToWeakRange(r, elm, highnumber); + break; + case hcolors.comment: + highcomment.add(r); + addToWeakRange(r, elm, highcomment); + break; + case hcolors.identifier: + highidentifier.add(r); + addToWeakRange(r, elm, highidentifier); + break; + case hcolors.string: + highstring.add(r); + addToWeakRange(r, elm, highstring); + break; + case hcolors.symbol: + highsymbol.add(r); + addToWeakRange(r, elm, highsymbol); + break; + } + } + return function highlightFromLex( + elm: HTMLElement, + lex: Iterable<{ + type: hcolors; + content: string; + }>, + skipStart: number, + ) { + clearHighlights(elm); + const nodes = Array.from(elm.childNodes) as Text[]; + let r = new Range(); + const lexed = Array.from(lex); + if (skipStart) { + lexed.unshift({type: hcolors.symbol, content: " ".repeat(skipStart)}); + } + if (lexed.length === 0) return; + let i = 1; + let lenToGo = strLen(lexed[0].content); + let t = lexed[0].type; + r.setStart(nodes[0], 0); + function getNextNode() { + if (!lexed[i]) { + const last = nodes.at(-1) as Text; + r.setEnd(last, strLen(last.textContent)); + highlight(r, t, elm); + return -1; + } else highlight(r, t, elm); + lenToGo = strLen(lexed[i].content); + t = lexed[i].type; + i++; + return 0; + } + + for (const node of nodes) { + let len = strLen(node.textContent); + let start = 0; + while (len) { + if (len < lenToGo) { + lenToGo -= len; + break; + } else { + start += lenToGo; + r.setEnd(node, start); + len -= lenToGo; + if (getNextNode() === -1) return; + r = new Range(); + r.setStart(node, start); + } + } + } + }; +} +export const highlightFromLex = ifCSSAllows(); diff --git a/src/webpage/highlighter/index.ts b/src/webpage/highlighter/index.ts new file mode 100644 index 0000000..f790e49 --- /dev/null +++ b/src/webpage/highlighter/index.ts @@ -0,0 +1,34 @@ +import {canLex} from "./clike/lex.js"; +import {highlightFromLex} from "./highlight.js"; +import {lex as pylex} from "./python/lex.js"; +import {lex as xmlex} from "./xml/lex.js"; +export async function highlight( + elm: HTMLElement, + lang: string, + skipStart: number = 0, + skipEnd: number = 0, +) { + await new Promise((res) => setTimeout(res)); + lang = lang.trim(); + let content = elm.textContent; + if (skipStart) { + content = content.slice(skipStart); + } + if (skipEnd) { + content = content.slice(0, content.length - skipEnd); + } + if (lang === "py" || lang === "python") { + highlightFromLex(elm, pylex(content), skipStart); + } else if ( + lang === "xml" || + lang === "html" || + lang === "htmlx" || + lang === "qml" || + lang === "gmx" + ) { + highlightFromLex(elm, xmlex(content), skipStart); + } else { + const clike = await canLex(lang); + if (clike) highlightFromLex(elm, clike(content), skipStart); + } +} diff --git a/src/webpage/highlighter/python/lex.ts b/src/webpage/highlighter/python/lex.ts new file mode 100644 index 0000000..f1449a7 --- /dev/null +++ b/src/webpage/highlighter/python/lex.ts @@ -0,0 +1,71 @@ +import {hcolors} from "../colors.js"; + +const r = + /"""((\\(.|\n)|[^"\\]|"(?!")|""(?!"))*)(""")?|"(\\(.|\n)|[^"\n\\])*"?|'(\\(.|\n)|[^"\n\\])*'?|\s+|#.*|[~()\[\]|:;`=+\-*\/\\&^%$.]|[a-zA-Z][a-zA-Z0-9]*|[0-9][a-zA-Z0-9]*\.?[a-zA-Z0-9]*|./gm; +const keywords = new Set([ + "False", + "None", + "True", + "and", + "as", + "assert", + "async", + "await", + "break", + "class", + "continue", + "def", + "del", + "elif", + "else", + "except", + "finally", + "for", + "from", + "global", + "if", + "import", + "in", + "is", + "lambda", + "nonlocal", + "not", + "or", + "pass", + "raise", + "return", + "try", + "while", + "with", + "yield", +]); +export function* lex(code: string) { + for (const [lex] of code.matchAll(r)) { + if (lex.startsWith("#")) { + yield { + type: hcolors.comment, + content: lex, + }; + } else if (lex.match(/^("|')/)) { + yield { + type: hcolors.string, + content: lex, + }; + } else if (lex.match(/^[0-9]/)) { + yield { + type: hcolors.number, + content: lex, + }; + } else if (lex.match(/^[a-zA-Z]/)) { + yield { + type: keywords.has(lex) ? hcolors.keyword : hcolors.identifier, + content: lex, + }; + } else { + yield { + type: hcolors.symbol, + content: lex, + }; + } + } +} diff --git a/src/webpage/highlighter/xml/lex.ts b/src/webpage/highlighter/xml/lex.ts new file mode 100644 index 0000000..cea7c03 --- /dev/null +++ b/src/webpage/highlighter/xml/lex.ts @@ -0,0 +1,35 @@ +import {hcolors} from "../colors.js"; + +const r = + /"(\\(.|\n)|[^"\n\\])*"?|)?|[a-zA-Z][a-zA-Z0-9]*\s*=|<[\s\/]*[^<> ]+|.|\n/gm; + +export function* lex(code: string) { + for (const [lex] of code.matchAll(r)) { + if (lex.startsWith("