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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { shikiOnigasmSrc, shikiWorkerSrc } from "@utils/dependencies";
import { WorkerClient } from "@vap/core/ipc";
import type { IShikiTheme, IThemedToken } from "@vap/shiki";
import { dispatchTheme } from "../hooks/useTheme";
import type { ShikiSpec } from "../types";
import { getGrammar, languages, loadLanguages, resolveLang } from "./languages";
import { themes } from "./themes";
const themeUrls = Object.values(themes);
let resolveClient: (client: WorkerClient<ShikiSpec>) => void;
export const shiki = {
client: null as WorkerClient<ShikiSpec> | null,
currentTheme: null as IShikiTheme | null,
currentThemeUrl: null as string | null,
timeoutMs: 10000,
languages,
themes,
loadedThemes: new Set<string>(),
loadedLangs: new Set<string>(),
clientPromise: new Promise<WorkerClient<ShikiSpec>>(resolve => resolveClient = resolve),
init: async (initThemeUrl: string | undefined) => {
/** https://stackoverflow.com/q/58098143 */
const workerBlob = await fetch(shikiWorkerSrc).then(res => res.blob());
const client = shiki.client = new WorkerClient<ShikiSpec>(
"shiki-client",
"shiki-host",
workerBlob,
{ name: "ShikiWorker" },
);
await client.init();
const themeUrl = initThemeUrl || themeUrls[0];
await loadLanguages();
await client.run("setOnigasm", { wasm: shikiOnigasmSrc });
await client.run("setHighlighter", { theme: themeUrl, langs: [] });
shiki.loadedThemes.add(themeUrl);
await shiki._setTheme(themeUrl);
resolveClient(client);
},
_setTheme: async (themeUrl: string) => {
shiki.currentThemeUrl = themeUrl;
const { themeData } = await shiki.client!.run("getTheme", { theme: themeUrl });
shiki.currentTheme = JSON.parse(themeData);
dispatchTheme({ id: themeUrl, theme: shiki.currentTheme });
},
loadTheme: async (themeUrl: string) => {
const client = await shiki.clientPromise;
if (shiki.loadedThemes.has(themeUrl)) return;
await client.run("loadTheme", { theme: themeUrl });
shiki.loadedThemes.add(themeUrl);
},
setTheme: async (themeUrl: string) => {
await shiki.clientPromise;
themeUrl ||= themeUrls[0];
if (!shiki.loadedThemes.has(themeUrl)) await shiki.loadTheme(themeUrl);
await shiki._setTheme(themeUrl);
},
loadLang: async (langId: string) => {
const client = await shiki.clientPromise;
const lang = resolveLang(langId);
if (!lang || shiki.loadedLangs.has(lang.id)) return;
await client.run("loadLanguage", {
lang: {
...lang,
grammar: lang.grammar ?? await getGrammar(lang),
}
});
shiki.loadedLangs.add(lang.id);
},
tokenizeCode: async (code: string, langId: string): Promise<IThemedToken[][]> => {
const client = await shiki.clientPromise;
const lang = resolveLang(langId);
if (!lang) return [];
if (!shiki.loadedLangs.has(lang.id)) await shiki.loadLang(lang.id);
return await client.run("codeToThemedTokens", {
code,
lang: langId,
theme: shiki.currentThemeUrl ?? themeUrls[0],
});
},
destroy() {
shiki.currentTheme = null;
shiki.currentThemeUrl = null;
dispatchTheme({ id: null, theme: null });
shiki.client?.destroy();
}
};
|