import { loader, type Monaco } from "@monaco-editor/react"; import * as monaco from "monaco-editor"; import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker"; let initialized = false; let monacoPromise: Promise | null = null; // A Monarch tokenizer for Typst covering the common surface (headings, // script keywords, math, comments, emphasis, strings, raw blocks). This is // syntax-only; the LSP additionally provides diagnostics and (optionally) // semantic tokens. const typstLanguageDefinition: monaco.languages.IMonarchLanguage = { defaultToken: "", tokenPostfix: ".typst", brackets: [ { open: "(", close: ")", token: "delimiter.parenthesis" }, { open: "[", close: "]", token: "delimiter.square" }, { open: "{", close: "}", token: "delimiter.curly" }, ], tokenizer: { root: [ [/^=+\s.*$/, "keyword.heading"], [/\*[^*]+\*/, "string.emphasis"], [/_[^_]+_/, "string.emphasis"], [/\/\/.*$/, "comment"], [/\/\*/, "comment", "@comment"], [/#\w+/, "keyword"], // #set, #let, #show, #if, ... [/\$/, { token: "string", next: "@math" }], [/"/, "string", "@string"], [/[{}()\[\]]/, "@brackets"], ], comment: [ [/\*\//, "comment", "@pop"], [/[^*]+/, "comment"], [/\*/, "comment"], ], math: [ [/\$/, { token: "string", next: "@pop" }], [/\\[a-zA-Z]+/, "keyword"], [/[a-zA-Z]+/, "variable"], [/[^\$]/, "string"], ], string: [ [/[^"]/, "string"], [/"/, "string", "@pop"], ], }, }; function initEnvironment(): void { if (initialized) return; // Vite bundles the editor worker via `?worker`. Monaco only needs the base // editor worker for plaintext/typst (no language-specific workers required). self.MonacoEnvironment = { getWorker: () => new editorWorker(), }; loader.config({ monaco }); initialized = true; } export function loadMonaco(): Promise { if (!monacoPromise) { initEnvironment(); // loader.init() resolves to the same `monaco` instance we configured above. monacoPromise = loader.init().then((m) => { m.languages.register({ id: "typst" }); m.languages.setMonarchTokensProvider("typst", typstLanguageDefinition); return m; }); } return monacoPromise; } export function getMonaco(): Monaco { return monaco as unknown as Monaco; }