add syntax highlighting

This commit is contained in:
sfja 2024-12-17 03:46:12 +01:00
parent 77c01e12a5
commit fee5666971
2 changed files with 206 additions and 4 deletions

View File

@ -40,6 +40,200 @@ async function checkStatus(): Promise<"running" | "done"> {
return "done"; return "done";
} }
function syntaxHighlight(code: string): string {
const colors = {
colorBackground: "#282828",
colorForeground: "#fbf1c7",
colorKeyword: "#fb4934",
colorIdentifier: "#83a598",
colorOperator: "#fe8019",
colorSpecial: "#fe8019",
colorType: "#fabd2f",
colorBoolean: "#d3869b",
colorNumber: "#d3869b",
colorString: "#b8bb26",
colorComment: "#928374",
colorFunction: "#b8bb26",
colorLineNumber: "#7c6f64",
} as const;
/*
Keyword = { link = "GruvboxRed" },
Identifier = { link = "GruvboxBlue" },
Operator = { fg = colors.orange, italic = config.italic.operators },
Special = { link = "GruvboxOrange" },
Type = { link = "GruvboxYellow" },
Boolean = { link = "GruvboxPurple" },
Number = { link = "GruvboxPurple" },
String = { fg = colors.green, italic = config.italic.strings },
Comment = { fg = colors.gray, italic = config.italic.comments },
Function = { link = "GruvboxGreenBold" },
*/
let matches: {
index: number;
length: number;
color: string;
extra: string;
}[] = [];
function addMatches(color: string, re: RegExp, extra = "") {
for (const match of code.matchAll(re)) {
matches.push({
index: match.index,
length: match[1].length,
color,
extra,
});
}
}
function addKeywordMatches(color: string, keywords: string[]) {
addMatches(
color,
new RegExp(
`(?<!\\w)(${
keywords.map((kw) => `(?:${kw})`).join("|")
})(?!\\w)`,
"g",
),
);
}
for (let i = 0; i < code.length; ++i) {
if (code[i] !== '"') {
continue;
}
let last = code[i];
const index = i;
i += 1;
while (i < code.length && !(code[i] === '"' && last !== "\\")) {
last = code[i];
i += 1;
}
if (i < code.length) {
i += 1;
}
matches.push({
index,
length: i - index,
color: colors.colorString,
extra: "font-style: italic;",
});
}
{
let last = "";
for (let i = 0; i < code.length; ++i) {
if (last === "/" && code[i] === "/") {
const index = i - 1;
while (i < code.length && code[i] !== "\n") {
i += 1;
}
matches.push({
index,
length: i - index,
color: colors.colorComment,
extra: "font-style: italic;",
});
}
last = code[i];
}
}
addKeywordMatches(
colors.colorKeyword,
[
"break",
"return",
"let",
"fn",
"if",
"else",
"struct",
"import",
"or",
"and",
"not",
"while",
"for",
"in",
],
);
addKeywordMatches(colors.colorSpecial, ["null"]);
addKeywordMatches(colors.colorType, ["int", "string", "bool"]);
addKeywordMatches(colors.colorBoolean, ["false", "true"]);
addMatches(
colors.colorOperator,
new RegExp(
`(${
[
"\\+=",
"\\-=",
"\\+",
"\\->",
"\\-",
"\\*",
"/",
"==",
"!=",
"<=",
">=",
"=",
"<",
">",
"\\.",
"::<",
"::",
":",
].map((kw) => `(?:${kw})`).join("|")
})`,
"g",
),
);
addMatches(
colors.colorNumber,
/(0|(?:[1-9][0-9]*)|(?:0[0-7]+)|(?:0x[0-9a-fA-F]+)|(?:0b[01]+))/g,
);
addMatches(
colors.colorFunction,
/([a-zA-Z_]\w*(?=\())/g,
"font-weight: 700;",
);
addMatches(colors.colorIdentifier, /([a-z_]\w*)/g);
addMatches(colors.colorType, /([A-Z_]\w*)/g);
matches = matches.reduce<typeof matches>(
(acc, match) =>
acc.find((m) => m.index === match.index) === undefined
? (acc.push(match), acc)
: acc,
[],
);
matches.sort((a, b) => a.index - b.index);
let highlighted = "";
let i = 0;
for (const match of matches) {
if (match.index < i) {
continue;
}
while (i < match.index) {
highlighted += code[i];
i += 1;
}
const section = code.slice(match.index, match.index + match.length);
highlighted +=
`<span style="color: ${match.color};${match.extra}">${section}</span>`;
i += section.length;
}
while (i < code.length) {
highlighted += code[i];
i += 1;
}
return highlighted;
}
function sourceCode(view: Element, codeData: string) { function sourceCode(view: Element, codeData: string) {
const outerContainer = document.createElement("div"); const outerContainer = document.createElement("div");
outerContainer.classList.add("code-container"); outerContainer.classList.add("code-container");
@ -51,7 +245,7 @@ function sourceCode(view: Element, codeData: string) {
const code = document.createElement("pre"); const code = document.createElement("pre");
code.classList.add("code-source"); code.classList.add("code-source");
code.innerText = codeData; code.innerHTML = syntaxHighlight(codeData);
innerContainer.append(lines, code); innerContainer.append(lines, code);
outerContainer.append(innerContainer); outerContainer.append(innerContainer);
view.replaceChildren(outerContainer); view.replaceChildren(outerContainer);

View File

@ -11,6 +11,10 @@
--white: #ecebe9; --white: #ecebe9;
--white-transparent: #ecebe9aa; --white-transparent: #ecebe9aa;
--code-status: var(--white); --code-status: var(--white);
--code-bg: #282828;
--code-fg: #fbf1c7;
--code-linenr: #7c6f64;
} }
* { * {
@ -99,14 +103,19 @@ main #cover {
#view .code-container { #view .code-container {
max-height: 100%; max-height: 100%;
overflow: scroll; overflow: scroll;
background-color: rgba(255, 255, 255, 0.1); background-color: var(--code-bg);
padding: 0.5rem; padding: 0.5rem;
border-radius: 0.5rem; border-radius: 0.5rem;
} }
#view .code-container pre { #view .code-container pre {
font-family: "Roboto Mono", monospace; font-family: "Roboto Mono", monospace;
font-weight: 600; font-weight: 500;
color: var(--code-fg);
}
#view .code-container pre.code-lines {
color: var(--code-linenr);
} }
#view .code-container.code-coverage { #view .code-container.code-coverage {
@ -121,7 +130,6 @@ main #cover {
} }
#view .code-lines { #view .code-lines {
color: var(--white-transparent);
border-right: 1px solid currentcolor; border-right: 1px solid currentcolor;
padding-right: 0.5rem; padding-right: 0.5rem;
margin: 0; margin: 0;