|
| 1 | +import { browser } from "$app/environment"; |
| 2 | +import posthog from "posthog-js"; |
| 3 | +import type { ShikiTransformer, SpecialLanguage } from "shiki"; |
| 4 | + |
| 5 | +/** |
| 6 | + * Detects the programming or markup language based on the given code snippet. |
| 7 | + * |
| 8 | + * @param code the code snippet to analyze and detect the language from. |
| 9 | + * @returns The detected language as a string, or undefined if no language |
| 10 | + * could be determined. |
| 11 | + */ |
| 12 | +export function detectLanguage(code: string): (SpecialLanguage | (string & {})) | undefined { |
| 13 | + const match = code |
| 14 | + .split("\n", 1)[0] |
| 15 | + ?.trim() |
| 16 | + ?.match(/^(?:\/\/|#) ?[^ !]+?\.([A-Za-z0-9]{1,10})$/); |
| 17 | + if (match) return match[1]; |
| 18 | + |
| 19 | + const hasHTML = /<\/[a-zA-Z0-9-]+>/.test(code); |
| 20 | + const hasJS = / (let|var|const|=) /.test(code); |
| 21 | + |
| 22 | + if (hasHTML && hasJS) return "svelte"; |
| 23 | + if (hasHTML) return "html"; |
| 24 | + if (hasJS) return /(: [A-Z]|type |interface )/.test(code) ? "ts" : "js"; |
| 25 | + if (/[a-z-]+: \S+/.test(code)) return "css"; |
| 26 | +} |
| 27 | + |
| 28 | +/** |
| 29 | + * A Shiki transformer used for language detection and setting the appropriate language metadata |
| 30 | + * in code blocks. Useful for handling code snippets with "diff" language and converting them |
| 31 | + * to a detected programming language. |
| 32 | + */ |
| 33 | +export const transformerLanguageDetection: ShikiTransformer = { |
| 34 | + preprocess(code, options) { |
| 35 | + if (options.lang === "diff") { |
| 36 | + const cleanedCode = code |
| 37 | + .split("\n") |
| 38 | + .map(line => line.replace(/^[+-]/, "")) |
| 39 | + .join("\n"); |
| 40 | + const detectedLanguage = detectLanguage(cleanedCode); |
| 41 | + if (!detectedLanguage) { |
| 42 | + if (browser) |
| 43 | + posthog.captureException(new Error("Failed to determine diff language"), { |
| 44 | + code |
| 45 | + }); |
| 46 | + return; |
| 47 | + } |
| 48 | + options.lang = detectedLanguage; |
| 49 | + return code; |
| 50 | + } |
| 51 | + }, |
| 52 | + pre(node) { |
| 53 | + node.properties["data-language"] = this.options.lang |
| 54 | + .toLowerCase() |
| 55 | + .replace(/^js$/, "javascript") |
| 56 | + .replace(/^ts$/, "typescript"); |
| 57 | + } |
| 58 | +}; |
| 59 | + |
| 60 | +/** |
| 61 | + * Replicate the behavior of Shiki's `transformerNotationDiff`, |
| 62 | + * but in-house and without needing any code transformation. |
| 63 | + * |
| 64 | + * @see https://shiki.style/packages/transformers#transformernotationdiff |
| 65 | + */ |
| 66 | +export const transformerDiffMarking: ShikiTransformer = { |
| 67 | + pre(node) { |
| 68 | + const isDiff = this.tokens.some(line => { |
| 69 | + return line.some(token => { |
| 70 | + if (token.offset > 0) return false; |
| 71 | + const content = token.content.trim(); |
| 72 | + return content === "+" || content === "-"; |
| 73 | + }); |
| 74 | + }); |
| 75 | + if (isDiff) this.addClassToHast(node, "has-diff"); |
| 76 | + }, |
| 77 | + line(node) { |
| 78 | + const firstChild = node.children[0]; |
| 79 | + if (!firstChild || firstChild.type !== "element") return; |
| 80 | + const firstToken = firstChild.children[0]; |
| 81 | + if (!firstToken || firstToken.type !== "text") return; |
| 82 | + if (firstToken.value.startsWith("+")) { |
| 83 | + this.addClassToHast(node, ["diff", "add"]); |
| 84 | + if (firstToken.value.length === 1) { |
| 85 | + node.children.shift(); |
| 86 | + } else { |
| 87 | + firstToken.value = firstToken.value.slice(1); |
| 88 | + } |
| 89 | + } else if (firstToken.value.startsWith("-")) { |
| 90 | + this.addClassToHast(node, ["diff", "remove"]); |
| 91 | + if (firstToken.value.length === 1) { |
| 92 | + node.children.shift(); |
| 93 | + } else { |
| 94 | + firstToken.value = firstToken.value.slice(1); |
| 95 | + } |
| 96 | + } |
| 97 | + } |
| 98 | +}; |
0 commit comments