import type { BundledLanguage, BundledTheme, HighlighterCore } from "shiki"; import { getCustomLanguageGrammars } from "dxUtils/shikiGrammars"; export interface ShikiModule { createHighlighter: (options: { themes: string[]; langs: BundledLanguage[]; }) => Promise; } export interface BracketsModule { transformerColorizedBrackets: () => any; } export interface ShikiState { highlighter: HighlighterCore | null; initialized: boolean; initPromise: Promise | null; } export const THEME_MAP: Record = { light: "light-plus", dark: "material-theme-darker" }; export const THEME_MAP_COLOR_REPLACEMENTS: Record< string, { colorReplacements: Record } > = { light: { colorReplacements: {} }, dark: { colorReplacements: { "#545454": "#8A8A8A" } } }; export const LANGUAGE_MAP: Record = { ssjs: "javascript", gtl: "handlebars", lwc: "jsx", sql_docs_template: "sql", visualforce: "html", js: "javascript", ts: "typescript", yml: "yaml", md: "markdown", sh: "bash" }; export const CORE_LANGUAGES: BundledLanguage[] = [ "apex", "javascript", "html", "css", "json", "sql", "bash", "xml", "graphql", "jsx", getCustomLanguageGrammars().ampscript ]; export const OPTIONAL_LANGUAGES: Record = { // @ts-ignore typescript: "typescript", // @ts-ignore markdown: "markdown", // @ts-ignore python: "python", // @ts-ignore java: "java", // @ts-ignore yaml: "yaml", // @ts-ignore php: "php", // @ts-ignore swift: "swift", // @ts-ignore kotlin: "kotlin", // @ts-ignore handlebars: "handlebars", // @ts-ignore dataweave: getCustomLanguageGrammars().dataweave, // @ts-ignore agentscript: getCustomLanguageGrammars().agentscript }; export async function loadLanguageIfNeeded( highlighter: HighlighterCore, language: string ): Promise { const loadedLanguages = highlighter.getLoadedLanguages(); if (!loadedLanguages.includes(language as BundledLanguage)) { const languageLoader = OPTIONAL_LANGUAGES[language]; if (languageLoader) { try { await highlighter.loadLanguage(languageLoader); } catch (error) { console.warn( `Failed to load optional language ${language}:`, error ); } } } } export function getMappedLanguage(language: string): BundledLanguage { const mapped = LANGUAGE_MAP[language.toLowerCase()] || language.toLowerCase(); return mapped as BundledLanguage; } export function escapeHtml(text: string): string { const div = document.createElement("div"); div.textContent = text; // eslint-disable-next-line @lwc/lwc/no-inner-html return div.innerHTML; } export function createShikiState(): ShikiState { return { highlighter: null, initialized: false, initPromise: null }; } export async function initializeHighlighter( shikiState: ShikiState, shikiModule: ShikiModule ): Promise { if (shikiState.highlighter) { return shikiState.highlighter; } if (shikiState.initPromise) { return shikiState.initPromise; } shikiState.initPromise = (async () => { const highlighter = await shikiModule.createHighlighter({ themes: ["light-plus", "material-theme-darker"], langs: CORE_LANGUAGES }); shikiState.highlighter = highlighter; shikiState.initialized = true; return highlighter; })(); try { return await shikiState.initPromise; } catch (error) { console.error("Failed to initialize Shiki:", error); shikiState.initPromise = null; throw error; } } export async function highlightCodeWithShiki( code: string, language: string, theme: "light" | "dark", highlighter: HighlighterCore, transformerColorizedBrackets: () => any ): Promise { let mappedLanguage = getMappedLanguage(language); await loadLanguageIfNeeded(highlighter, mappedLanguage); const loadedLanguages = highlighter.getLoadedLanguages(); if (!loadedLanguages.includes(mappedLanguage)) { mappedLanguage = "text" as BundledLanguage; } return highlighter.codeToHtml(code, { lang: mappedLanguage, theme: THEME_MAP[theme], transformers: [transformerColorizedBrackets()], colorReplacements: THEME_MAP_COLOR_REPLACEMENTS[theme].colorReplacements }); }