import { Suspense, lazy, type ReactNode } from "react"; import type { SyntaxHighlighterProps } from "react-syntax-highlighter"; import { useThemeValue } from "@/hooks/useTheme"; import type { RenderableFileDiffLine } from "@/lib/file-diff"; import { cn } from "@/lib/utils"; interface DiffSyntaxHighlightProps { language: string; lines: RenderableFileDiffLine[]; } interface LoadedDiffSyntaxHighlightProps extends DiffSyntaxHighlightProps { isDark: boolean; } type RendererArgs = Parameters>[0]; type SyntaxNode = RendererArgs["rows"][number]; const CODE_FONT_STACK = [ '"JetBrains Mono"', '"SFMono-Regular"', '"SF Mono"', '"Fira Code"', '"Cascadia Code"', '"Source Code Pro"', "Menlo", "Consolas", "monospace", ].join(", "); const LazyDiffSyntaxHighlight = lazy(async () => { const [ { default: SyntaxHighlighter }, { default: createSyntaxElement }, { default: oneDark }, { default: oneLight }, ] = await Promise.all([ import("react-syntax-highlighter/dist/esm/prism-async-light"), import("react-syntax-highlighter/dist/esm/create-element"), import("react-syntax-highlighter/dist/esm/styles/prism/one-dark"), import("react-syntax-highlighter/dist/esm/styles/prism/one-light"), ]); return { default: function LoadedDiffSyntaxHighlight({ language, lines, isDark, }: LoadedDiffSyntaxHighlightProps) { const theme = isDark ? oneDark : oneLight; const code = lines.map((line) => line.content || " ").join("\n"); return ( ( { const node = rows[index]; if (!node) return line.content || " "; return createSyntaxElement({ node: stripConflictingTableClass(trimTrailingLineBreak(node)), stylesheet, useInlineStyles, key: `diff-code-${index}`, }); }} /> )} > {code} ); }, }; }); export function DiffSyntaxHighlight({ language, lines }: DiffSyntaxHighlightProps) { const isDark = useThemeValue() === "dark"; return ( }> ); } function PlainDiffLines({ lines }: { lines: RenderableFileDiffLine[] }) { return (
line.content || " "} />
); } function DiffLineTable({ lines, renderCode, }: { lines: RenderableFileDiffLine[]; renderCode: (line: RenderableFileDiffLine, index: number) => ReactNode; }) { return ( {lines.map((line, index) => ( {renderCode(line, index)} ))}
); } function DiffLineRow({ line, children, }: { line: RenderableFileDiffLine; children: ReactNode; }) { const kind = line.kind === "add" || line.kind === "delete" ? line.kind : "context"; const marker = kind === "add" ? "+" : kind === "delete" ? "-" : " "; return ( {line.old_lineno ?? ""} {line.new_lineno ?? ""} {marker} {children} ); } function trimTrailingLineBreak(node: SyntaxNode): SyntaxNode { if (node.type === "text" && typeof node.value === "string") { return { ...node, value: node.value.replace(/\n$/, "") }; } if (!node.children?.length) return node; const children = [...node.children]; children[children.length - 1] = trimTrailingLineBreak(children[children.length - 1]!); return { ...node, children }; } function stripConflictingTableClass(node: SyntaxNode): SyntaxNode { const className = node.properties?.className; const children = node.children?.map(stripConflictingTableClass); const hasTableClass = Array.isArray(className) && className.includes("table"); if (!hasTableClass && !children) return node; return { ...node, ...(hasTableClass ? { properties: { ...node.properties, // Tailwind's global `.table` utility changes Prism's inline Markdown // table tokens into CSS tables, splitting a single diff line vertically. className: className.filter((name) => name !== "table"), }, } : {}), ...(children ? { children } : {}), }; }