-
Notifications
You must be signed in to change notification settings - Fork 678
/
highlighter.ts
82 lines (69 loc) · 2.1 KB
/
highlighter.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import { ok as assert } from "assert";
import * as shiki from "shiki";
import { Highlighter } from "shiki/dist/highlighter";
import { unique } from "./array";
export type ShikiTheme = Parameters<typeof import("shiki")["getTheme"]>[0];
// This is needed because Shiki includes some "fake" languages
// ts / js are expected by users to be equivalent to typescript / javascript
const aliases = new Map<string, string>([
["ts", "typescript"],
["js", "javascript"],
["bash", "shellscript"],
["sh", "shellscript"],
["shell", "shellscript"],
]);
const supportedLanguages = unique([
"text",
...aliases.keys(),
...shiki.BUNDLED_LANGUAGES.map((lang) => lang.id),
]).sort();
let highlighter: Highlighter | undefined;
export async function loadHighlighter(theme: ShikiTheme) {
if (highlighter) return;
highlighter = await shiki.getHighlighter({
theme,
});
}
export function isSupportedLanguage(lang: string) {
return getSupportedLanguages().includes(lang);
}
export function getSupportedLanguages(): string[] {
return supportedLanguages;
}
export function highlight(code: string, lang: string): string {
assert(highlighter, "Tried to highlight with an uninitialized highlighter");
if (!isSupportedLanguage(lang)) {
return code;
}
if (lang === "text") {
return escapeHtml(code);
}
lang = aliases.get(lang) ?? lang;
const result: string[] = [];
for (const line of highlighter.codeToThemedTokens(code, lang, {
includeExplanation: false,
})) {
for (const token of line) {
result.push(
`<span style="color: ${token.color ?? "#000"}">`,
escapeHtml(token.content),
"</span>"
);
}
result.push("\n");
}
return result.join("");
}
function escapeHtml(text: string) {
return text.replace(
/[&<>"']/g,
(match) =>
({
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'",
}[match as never])
);
}