Skip to content
File

Blob: src/shared/editor/highlight/code-highlight-runtime.js

javascript99 lines
1// Keep this runtime as JavaScript with a local .d.ts type facade. Importing
2// highlight.js types directly pulls in lib.dom, which breaks Worker no-DOM
3// typechecking for Sites static rendering.
4import { createLowlight } from "lowlight";
5import c from "highlight.js/lib/languages/c";
6import cpp from "highlight.js/lib/languages/cpp";
7import csharp from "highlight.js/lib/languages/csharp";
8import dockerfile from "highlight.js/lib/languages/dockerfile";
9import go from "highlight.js/lib/languages/go";
10import ini from "highlight.js/lib/languages/ini";
11import java from "highlight.js/lib/languages/java";
12import javascript from "highlight.js/lib/languages/javascript";
13import json from "highlight.js/lib/languages/json";
14import markdown from "highlight.js/lib/languages/markdown";
15import php from "highlight.js/lib/languages/php";
16import plaintext from "highlight.js/lib/languages/plaintext";
17import python from "highlight.js/lib/languages/python";
18import rust from "highlight.js/lib/languages/rust";
19import shell from "highlight.js/lib/languages/shell";
20import sql from "highlight.js/lib/languages/sql";
21import typescript from "highlight.js/lib/languages/typescript";
22import yaml from "highlight.js/lib/languages/yaml";
23import { CODE_LANGUAGES, resolveLanguage } from "../schema/code-block-model";
24 
25const PLAIN_TEXT_LANGUAGES = new Set(["text", "plaintext", "txt"]);
26 
27export const codeBlockLowlight = createLowlight({
28 text: plaintext,
29 plaintext,
30 c,
31 csharp,
32 cpp,
33 dockerfile,
34 go,
35 hcl: ini,
36 java,
37 javascript,
38 json,
39 jsonc: json,
40 markdown,
41 php,
42 python,
43 rust,
44 shell,
45 sql,
46 toml: ini,
47 typescript,
48 yaml,
49});
50 
51for (const [language, meta] of Object.entries(CODE_LANGUAGES)) {
52 const aliases = (meta.aliases ?? []).filter((alias) => alias !== language);
53 if (aliases.length > 0) {
54 codeBlockLowlight.registerAlias({ [language]: aliases });
55 }
56}
57 
58export function isPlainTextCodeLanguage(language) {
59 return language ? PLAIN_TEXT_LANGUAGES.has(language.trim().toLowerCase()) : false;
60}
61 
62export function highlightCodeToSegments(language, code) {
63 const normalizedLanguage = resolveLanguage(typeof language === "string" ? language : null);
64 if (isPlainTextCodeLanguage(normalizedLanguage)) {
65 return plainCodeSegments(code);
66 }
67 
68 try {
69 const result = codeBlockLowlight.highlight(normalizedLanguage, code);
70 const segments = flattenHighlightNodes(getHighlightNodes(result));
71 return segments.length > 0 ? segments : plainCodeSegments(code);
72 } catch {
73 return plainCodeSegments(code);
74 }
75}
76 
77function plainCodeSegments(code) {
78 return [{ text: code, classes: [] }];
79}
80 
81function getHighlightNodes(result) {
82 return result.value ?? result.children ?? [];
83}
84 
85function flattenHighlightNodes(nodes, inheritedClasses = []) {
86 return nodes.flatMap((node) => {
87 const classes = [...inheritedClasses, ...readClassNames(node.properties?.className)];
88 if (Array.isArray(node.children) && node.children.length > 0) {
89 return flattenHighlightNodes(node.children, classes);
90 }
91 return node.value ? [{ text: node.value, classes }] : [];
92 });
93}
94 
95function readClassNames(className) {
96 if (Array.isArray(className)) return className;
97 return className ? [className] : [];
98}