119 lines
4.1 KiB
JavaScript
119 lines
4.1 KiB
JavaScript
/**
|
|||
|
|
* Farver til en kodeblok.
|
||
|
|
*
|
||
|
|
* Ikke et bibliotek: et fuldt syntaksbibliotek er hundredvis af kilobytes for at
|
||
|
|
* gøre nogle ord blå i en note. Det her er et lille sæt regler pr. sprog, der
|
||
|
|
* rammer det, øjet leder efter — strenge, tal, kommentarer, nøgleord — og som er
|
||
|
|
* ligeglad med resten. En farve for lidt er ingenting; en forkert er heller ikke
|
||
|
|
* meget, når teksten stadig står der, som den blev skrevet.
|
||
|
|
*
|
||
|
|
* Reglerne prøves i rækkefølge, og den første, der rammer, vinder. Strenge og
|
||
|
|
* kommentarer står derfor først: et nøgleord inde i en streng er ikke et nøgleord.
|
||
|
|
*/
|
||
|
|
|
||
|
|
const COMMON = {
|
||
|
|
string: /^("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)/,
|
||
|
|
number: /^\b\d+(\.\d+)?\b/
|
||
|
|
};
|
||
|
|
|
||
|
|
const LANGUAGES = {
|
||
|
|
yaml: [
|
||
|
|
['comment', /^#[^\n]*/],
|
||
|
|
['string', COMMON.string],
|
||
|
|
// Nøglen, som er det, man scanner efter i en YAML-fil.
|
||
|
|
['key', /^[ \t]*[-\w.]+(?=\s*:)/],
|
||
|
|
['punct', /^[:\-|>]/],
|
||
|
|
['number', COMMON.number],
|
||
|
|
['bool', /^\b(true|false|null|yes|no|on|off)\b/i]
|
||
|
|
],
|
||
|
|
json: [
|
||
|
|
['string', COMMON.string],
|
||
|
|
['punct', /^[{}[\],:]/],
|
||
|
|
['number', COMMON.number],
|
||
|
|
['bool', /^\b(true|false|null)\b/]
|
||
|
|
],
|
||
|
|
bash: [
|
||
|
|
['comment', /^#[^\n]*/],
|
||
|
|
['string', COMMON.string],
|
||
|
|
// Prompten er ikke kode, men den er det første øjet finder i en udskrift.
|
||
|
|
['prompt', /^\S+@\S+:[^$#]*[$#]/],
|
||
|
|
['variable', /^\$\{?\w+\}?/],
|
||
|
|
['keyword', /^\b(if|then|else|fi|for|in|do|done|while|case|esac|function|return|export|local|sudo)\b/],
|
||
|
|
['flag', /^\s-{1,2}[\w-]+/],
|
||
|
|
['number', COMMON.number]
|
||
|
|
],
|
||
|
|
go: [
|
||
|
|
['comment', /^\/\/[^\n]*/],
|
||
|
|
['string', COMMON.string],
|
||
|
|
['keyword', /^\b(func|package|import|var|const|type|struct|interface|if|else|for|range|return|defer|go|chan|select|switch|case|default|map|nil|true|false)\b/],
|
||
|
|
['number', COMMON.number]
|
||
|
|
],
|
||
|
|
js: [
|
||
|
|
['comment', /^\/\/[^\n]*/],
|
||
|
|
['string', COMMON.string],
|
||
|
|
['keyword', /^\b(const|let|var|function|return|if|else|for|while|import|export|from|class|new|await|async|try|catch|throw|typeof|null|undefined|true|false)\b/],
|
||
|
|
['number', COMMON.number]
|
||
|
|
],
|
||
|
|
sql: [
|
||
|
|
['comment', /^--[^\n]*/],
|
||
|
|
['string', COMMON.string],
|
||
|
|
['keyword', /^\b(select|from|where|insert|into|values|update|set|delete|create|table|index|join|left|inner|on|and|or|not|null|primary|key|references|order|by|group|limit)\b/i],
|
||
|
|
['number', COMMON.number]
|
||
|
|
]
|
||
|
|
};
|
||
|
|
|
||
|
|
// Det, folk skriver, mod det, filen hedder.
|
||
|
|
const ALIASES = {
|
||
|
|
sh: 'bash', shell: 'bash', zsh: 'bash', console: 'bash', terminal: 'bash',
|
||
|
|
yml: 'yaml', javascript: 'js', ts: 'js', typescript: 'js', golang: 'go'
|
||
|
|
};
|
||
|
|
|
||
|
|
const escape = (s) =>
|
||
|
|
s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Gætter sproget, når blokken ikke siger det.
|
||
|
|
*
|
||
|
|
* Med vilje forsigtigt: at gætte forkert farver de forkerte ord, og en
|
||
|
|
* terminaludskrift uden farver er stadig læselig. Kun mønstre, der er svære at
|
||
|
|
* forveksle, tæller med.
|
||
|
|
*/
|
||
|
|
export function guessLanguage(code) {
|
||
|
|
if (/^\s*[{[]/.test(code) && /["}\]]\s*$/.test(code.trim())) return 'json';
|
||
|
|
if (/^\s*\S+@\S+:.*[$#]/m.test(code)) return 'bash';
|
||
|
|
if (/^\s*(package|func)\s+\w/m.test(code)) return 'go';
|
||
|
|
if (/^\s*[-\w.]+:\s*(\S|$)/m.test(code) && !/[;{}]/.test(code)) return 'yaml';
|
||
|
|
if (/^\s*(SELECT|INSERT|UPDATE|CREATE TABLE)\b/im.test(code)) return 'sql';
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Kode til HTML med spans. Ukendt sprog giver teksten uændret, blot undsluppet. */
|
||
|
|
export function highlight(code, language) {
|
||
|
|
const rules = LANGUAGES[ALIASES[language] ?? language];
|
||
|
|
if (!rules) return escape(code);
|
||
|
|
|
||
|
|
let out = '';
|
||
|
|
let rest = code;
|
||
|
|
let guard = 0;
|
||
|
|
|
||
|
|
while (rest && guard++ < 20000) {
|
||
|
|
let matched = false;
|
||
|
|
for (const [kind, re] of rules) {
|
||
|
|
const m = re.exec(rest);
|
||
|
|
if (!m || !m[0]) continue;
|
||
|
|
out += `<span class="tok-${kind}">${escape(m[0])}</span>`;
|
||
|
|
rest = rest.slice(m[0].length);
|
||
|
|
matched = true;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (!matched) {
|
||
|
|
// Ét tegn frem. Langsommere end at springe til næste interessante sted og
|
||
|
|
// umuligt at få til at gå i stå, hvilket er den rigtige byttehandel i noget,
|
||
|
|
// der kører på hvert tastetryk i en note, nogen sidder og skriver i.
|
||
|
|
out += escape(rest[0]);
|
||
|
|
rest = rest.slice(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return out + escape(rest);
|
||
|
|
}
|