aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/shikiCodeblocks.desktop/api/shiki.ts
blob: 91e179b72406e85822ec4ef6373fad93c554449c (plain)
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
/*
 * 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();
    }
};