import * as React from "react"; import { cn } from "@/lib/utils"; import { CopyButton } from "./copy-button"; export type CodeLang = "bash" | "json" | "javascript" | "typescript" | "python" | "go" | "php" | "ruby" | "java" | "csharp" | "html" | "text"; /** Tiny, dependency-free tokenizer good enough for docs and playground snippets. */ export function highlight(code: string, lang: CodeLang): React.ReactNode[] { const esc = (s: string) => s; if (lang === "text") return [esc(code)]; const rules: Array<[RegExp, string]> = []; if (lang === "json") { rules.push([/"(?:[^"\\]|\\.)*"(?=\s*:)/g, "tok-a"], [/"(?:[^"\\]|\\.)*"/g, "tok-s"], [/\b(?:true|false|null)\b/g, "tok-k"], [/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/gi, "tok-n"]); } else if (lang === "bash") { rules.push([/#.*$/gm, "tok-c"], [/'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/g, "tok-s"], [/^\s*(curl|export|echo|pip|npm|pnpm|yarn|go|python|node|fetcha)\b/gm, "tok-k"], [/\s(-{1,2}[a-zA-Z-]+)/g, "tok-a"]); } else if (lang === "html") { rules.push([//g, "tok-c"], [/<\/?[a-zA-Z][^\s>]*/g, "tok-t"], [/"[^"]*"/g, "tok-s"]); } else { rules.push( [/\/\/.*$|#.*$/gm, "tok-c"], [/\/\*[\s\S]*?\*\//g, "tok-c"], [/`(?:[^`\\]|\\.)*`|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g, "tok-s"], [/\b(?:import|from|export|const|let|var|function|async|await|return|new|class|def|if|else|elif|for|while|try|except|catch|finally|throw|raise|print|package|func|use|require|public|static|void|string|var|namespace|using|end|do|puts|nil|None|True|False|true|false|null|undefined|with|as|in|not|and|or|fmt|err)\b/g, "tok-k"], [/\b\d+(?:\.\d+)?\b/g, "tok-n"], [/\b(?:Fetcha|FetchaClient|fetch|json|text|sessions|create|Println|Fatal|NewRequest|Marshal|env|process|os|getenv|environ|std|Console|WriteLine)\b(?=\s*[\(\.])/g, "tok-a"], ); } type Span = { s: number; e: number; c: string }; const spans: Span[] = []; for (const [re, c] of rules) { re.lastIndex = 0; let m: RegExpExecArray | null; while ((m = re.exec(code))) { if (!m[0]) { re.lastIndex++; continue; } const s = m.index; const e = s + m[0].length; if (spans.some((x) => s < x.e && e > x.s)) continue; spans.push({ s, e, c }); } } spans.sort((a, b) => a.s - b.s); const out: React.ReactNode[] = []; let i = 0; for (const sp of spans) { if (sp.s > i) out.push(esc(code.slice(i, sp.s))); out.push( {code.slice(sp.s, sp.e)} , ); i = sp.e; } if (i < code.length) out.push(esc(code.slice(i))); return out; } export function CodeBlock({ code, lang = "bash", title, className, maxHeight, lineNumbers, copy = true, }: { code: string; lang?: CodeLang; title?: string; className?: string; maxHeight?: number | string; lineNumbers?: boolean; copy?: boolean; }) { const lines = code.replace(/\n$/, "").split("\n"); return (
{title ? (
{title} {copy ? : null}
) : copy ? (
) : null}
        
          {lineNumbers
            ? lines.map((l, i) => (
                
                  {i + 1}
                  {highlight(l, lang)}
                  {"\n"}
                
              ))
            : highlight(code, lang)}
        
      
); } export function InlineCode({ children, className }: { children: React.ReactNode; className?: string }) { return {children}; }