import hljs from "highlight.js"; import React from "react"; import { readFileSync } from "fs"; import { Span } from "paintcannon-react"; import { SYNTAX_ATTRIBUTE_COLOR, SYNTAX_BUILT_IN_COLOR, SYNTAX_COMMENT_COLOR, SYNTAX_KEYWORD_COLOR, SYNTAX_LITERAL_COLOR, SYNTAX_NAME_COLOR, SYNTAX_NUMBER_COLOR, SYNTAX_PROPERTY_COLOR, SYNTAX_SELECTOR_CLASS_COLOR, SYNTAX_SELECTOR_ID_COLOR, SYNTAX_SELECTOR_TAG_COLOR, SYNTAX_STRING_COLOR, SYNTAX_TITLE_COLOR, SYNTAX_TYPE_COLOR, SYNTAX_VALUE_COLOR, SYNTAX_VARIABLE_COLOR, } from "../theme.ts"; export function HighlightedCode({ code, language }: { code: string; language?: string }) { try { let result; if (language && hljs.getLanguage(language)) { result = hljs.highlight(code, { language, }); } else { result = hljs.highlightAuto(code); } const segments = parseHighlightedHTML(result.value); return ; } catch (error) { // If highlighting fails, return plain text lines return ( <> {code.split("\n").map((line, index) => ( {line} ))} ); } } type CodeSegment = { text: string; className?: string; }; function parseHighlightedHTML(html: string): CodeSegment[] { const segments: CodeSegment[] = []; let currentIndex = 0; // Simple state machine to parse HTML while (currentIndex < html.length) { const nextOpenTag = html.indexOf('", classEnd); if (classEnd === -1 || tagEnd === -1) break; const className = html.substring(classStart, classEnd); // Find the closing tag const closingTag = ""; const contentStart = tagEnd + 1; let closingTagStart = html.indexOf(closingTag, contentStart); // Handle nested spans by counting open/close tags let openCount = 1; let searchFrom = contentStart; while (openCount > 0 && closingTagStart !== -1) { const nextOpen = html.indexOf(" 0) { searchFrom = closingTagStart + closingTag.length; closingTagStart = html.indexOf(closingTag, searchFrom); } } } if (closingTagStart === -1) break; const content = html.substring(contentStart, closingTagStart); // Recursively parse content for nested spans if (content.includes(" { const linesInSegment = segment.text.split("\n"); linesInSegment.forEach((lineText, lineIndex) => { if (lineIndex > 0) { // This is a new line, push current line and start fresh lines.push(currentLine); currentLine = []; } if (lineText || lineIndex === linesInSegment.length - 1) { currentLine.push({ text: lineText, className: segment.className, }); } }); }); // Push the last line if it has content if (currentLine.length > 0) { lines.push(currentLine); } return ( <> {lines.map((lineSegments, index) => ( ))} ); } function CodeLine({ segments }: { segments: CodeSegment[] }) { return ( {segments.map((segment, index) => { const color = segment.className ? getColorForClass(segment.className) : undefined; return ( {segment.text} ); })} ); } function decodeHtmlEntities(text: string): string { return text .replace(/"/g, '"') .replace(/'/g, "'") .replace(/'/g, "'") .replace(/'/g, "'") .replace(/</g, "<") .replace(/>/g, ">") .replace(/&/g, "&"); } function getColorForClass(className: string): string | undefined { const colorMap: Record = { "hljs-keyword": SYNTAX_KEYWORD_COLOR, "hljs-string": SYNTAX_STRING_COLOR, "hljs-comment": SYNTAX_COMMENT_COLOR, "hljs-number": SYNTAX_NUMBER_COLOR, "hljs-title": SYNTAX_TITLE_COLOR, "hljs-title function_": SYNTAX_TITLE_COLOR, "hljs-variable": SYNTAX_VARIABLE_COLOR, "hljs-type": SYNTAX_TYPE_COLOR, "hljs-attr": SYNTAX_ATTRIBUTE_COLOR, "hljs-built_in": SYNTAX_BUILT_IN_COLOR, "hljs-literal": SYNTAX_LITERAL_COLOR, "hljs-name": SYNTAX_NAME_COLOR, "hljs-selector-tag": SYNTAX_SELECTOR_TAG_COLOR, "hljs-selector-class": SYNTAX_SELECTOR_CLASS_COLOR, "hljs-selector-id": SYNTAX_SELECTOR_ID_COLOR, "hljs-property": SYNTAX_PROPERTY_COLOR, "hljs-value": SYNTAX_VALUE_COLOR, }; return colorMap[className]; }