syntax highlighting

This commit is contained in:
MathMan05
2026-07-20 00:16:37 -05:00
parent bc44ddb145
commit 8191c6bb3c
12 changed files with 806 additions and 14 deletions
+372 -11
View File
@@ -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": {
+4 -1
View File
@@ -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
}
}
+20
View File
@@ -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
}
]
+105
View File
@@ -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,
};
}
}
}
+8
View File
@@ -0,0 +1,8 @@
export const enum hcolors {
keyword,
number,
string,
symbol,
comment,
identifier,
}
+132
View File
@@ -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<string>((id) => {
eliMap.delete(id);
});
const eliMap = new Map<string, [Highlight, Range][]>();
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();
+34
View File
@@ -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);
}
}
+71
View File
@@ -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,
};
}
}
}
+35
View File
@@ -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("<!--")) {
yield {
type: hcolors.comment,
content: lex,
};
} else if (lex.startsWith('"')) {
yield {
type: hcolors.string,
content: lex,
};
} else if (lex.startsWith("<")) {
yield {
type: hcolors.identifier,
content: lex,
};
} else if (lex.match(/[a-zA-Z][a-zA-Z0-9]*\s*=/)) {
yield {
type: hcolors.number,
content: lex,
};
} else {
yield {
type: hcolors.symbol,
content: lex,
};
}
}
}
+4
View File
@@ -5,6 +5,7 @@ import {Guild} from "./guild.js";
import {I18n} from "./i18n.js";
import {Dialog} from "./settings.js";
import {Contextmenu} from "./contextmenu.js";
import {highlight} from "./highlighter/index.js";
const linkMenu = new Contextmenu<string, void>("copyLink", true);
linkMenu.addButton(
() => I18n.copyRegLink(),
@@ -324,6 +325,7 @@ class MarkDown {
let find = 0;
let j = i + count;
let init = true;
let lang = "";
for (; txt[j] !== undefined && (txt[j] !== "\n" || count === 3) && find !== count; j++) {
if (txt[j] === "`") {
find++;
@@ -336,6 +338,7 @@ class MarkDown {
if (txt[j] === " " || txt[j] === "\n") {
init = false;
}
lang += txt[j];
if (keep) {
build += txt[j];
}
@@ -367,6 +370,7 @@ class MarkDown {
}
pre.textContent = build;
span.appendChild(pre);
if (count === 3) highlight(pre, lang, keep ? 3 + lang.length : 0, keep ? 3 : 0);
}
i--;
continue;
+19
View File
@@ -3366,6 +3366,25 @@ img.bigembedimg {
display: block;
}
}
::highlight(highkeyword) {
color: yellow;
}
::highlight(highnumber) {
color: orange;
}
::highlight(highstring) {
color: red;
}
::highlight(highsymbol) {
color: white;
font-weight: bold;
}
::highlight(highcomment) {
color: green;
}
::highlight(highidentifier) {
color: white;
}
#memberlisttoggle.svgicon {
-moz-appearance: initial;
&::before {
+2 -2
View File
@@ -8,7 +8,7 @@
"importHelpers": false,
"incremental": true,
"lib": ["esnext", "DOM", "webworker", "DOM.AsyncIterable"],
"module": "ESNext",
"module": "EsNext",
"moduleResolution": "Bundler",
"newLine": "lf",
"noEmitHelpers": false,
@@ -22,7 +22,7 @@
"resolveJsonModule": true,
"sourceMap": true,
"strict": true,
"target": "es2022",
"target": "es2024",
"useDefineForClassFields": true,
"resolvePackageJsonImports": true,
"skipLibCheck": true,