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];
}