Spaces:
Running
Running
File size: 4,316 Bytes
ce2d6ca b38d231 ce2d6ca b38d231 ce2d6ca b38d231 ce2d6ca b38d231 ce2d6ca b38d231 ce2d6ca b38d231 860406b ce2d6ca 70d86da ce2d6ca b38d231 ce2d6ca | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | import { useMemo } from 'react';
interface SyntaxHighlightProps {
code: string;
language: string;
}
// Apply a regex replacement only on text outside of HTML tags
function replaceOutsideTags(text: string, regex: RegExp, fn: (match: string, ...args: any[]) => string): string {
let result = '';
let lastIndex = 0;
const tagRe = /<[^>]*>/g;
let tagMatch: RegExpExecArray | null;
while ((tagMatch = tagRe.exec(text)) !== null) {
result += text.substring(lastIndex, tagMatch.index).replace(regex, fn as any);
result += tagMatch[0];
lastIndex = tagMatch.index + tagMatch[0].length;
}
result += text.substring(lastIndex).replace(regex, fn as any);
return result;
}
// A lightweight syntax highlighter without heavy dependencies
export function SyntaxHighlight({ code, language }: SyntaxHighlightProps) {
const highlighted = useMemo(() => {
const escaped = code
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>');
const lines = escaped.split('\n');
return lines.map((line, i) => {
// Step 1: Highlight strings (adds HTML markup)
let processed = line.replace(
/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)/g,
'<span class="text-green-400">$1</span>'
);
// Step 2: Highlight numbers (must skip inside HTML tags added by steps above)
processed = replaceOutsideTags(processed, /\b(\d+\.?\d*)\b/g, (_, num) =>
`<span class="text-amber-400">${num}</span>`
);
// Step 3: Highlight comments (skip inside HTML tags)
processed = replaceOutsideTags(processed, /(\/\/.*$|\/\*[\s\S]*?\*\/)/g, (_, comment) =>
`<span class="text-surface-500 italic">${comment}</span>`
);
// Step 4: Highlight keywords (skip inside HTML tags)
const keywords = [
'\\bconst\\b', '\\blet\\b', '\\bvar\\b', '\\bfunction\\b', '\\breturn\\b',
'\\bif\\b', '\\belse\\b', '\\bfor\\b', '\\bwhile\\b', '\\bdo\\b',
'\\bswitch\\b', '\\bcase\\b', '\\bbreak\\b', '\\bcontinue\\b',
'\\btry\\b', '\\bcatch\\b', '\\bfinally\\b', '\\bthrow\\b',
'\\bnew\\b', '\\bclass\\b', '\\bextends\\b', '\\bsuper\\b',
'\\bimport\\b', '\\bexport\\b', '\\bfrom\\b', '\\brequire\\b',
'\\bmodule\\b', '\\bexports\\b', '\\bdefault\\b', '\\btypeof\\b',
'\\binstanceof\\b', '\\bthis\\b', '\\btrue\\b', '\\bfalse\\b',
'\\bnull\\b', '\\bundefined\\b', '\\bawait\\b', '\\basync\\b',
'\\byield\\b', '\\bdelete\\b', '\\bin\\b', '\\bof\\b',
'\\bpublic\\b', '\\bprivate\\b', '\\bprotected\\b', '\\binternal\\b',
'\\bclass\\b', '\\bstruct\\b', '\\binterface\\b', '\\benum\\b',
'\\bnamespace\\b', '\\busing\\b', '\\basync\\b', '\\bawait\\b',
'\\bvoid\\b', '\\bint\\b', '\\bstring\\b', '\\bbool\\b',
'\\bvar\\b', '\\bget\\b', '\\bset\\b', '\\bvalue\\b',
'\\bpartial\\b', '\\bstatic\\b', '\\breadonly\\b', '\\bvirtual\\b',
'\\boverride\\b', '\\babstract\\b', '\\bsealed\\b',
];
const keywordPattern = new RegExp(keywords.join('|'), 'g');
processed = replaceOutsideTags(processed, keywordPattern, (match) =>
`<span class="text-purple-400">${match}</span>`
);
// Step 5: Highlight HTML tags/attributes (only for html/xml languages)
if (language === 'html' || language === 'xml') {
processed = processed.replace(
/(<\/?)([\w-]+)/g,
'$1<span class="text-blue-400">$2</span>'
);
processed = processed.replace(
/(\s)([\w-]+)(=)/g,
'$1<span class="text-amber-300">$2</span>$3'
);
}
const lineNum = String(i + 1).padStart(4, ' ');
return `<div class="flex">
<span class="text-surface-600 select-none text-right w-12 pr-4 flex-shrink-0 text-xs leading-6">${lineNum}</span>
<span class="flex-1 text-xs leading-6" style="white-space: pre-wrap; word-break: break-all; overflow-wrap: break-word;">${processed || ' '}</span>
</div>`;
}).join('\n');
}, [code, language]);
return (
<div className="p-0 font-mono text-sm">
<div className="bg-[#0a0e1a] p-0">
<div
className="overflow-x-auto"
dangerouslySetInnerHTML={{ __html: highlighted }}
/>
</div>
</div>
);
}
|