import { useMemo } from 'react' import { createPatch } from 'diff' import { CopyButton } from '../shared/CopyButton' type Props = { filePath: string oldString: string newString: string } type DiffLine = { oldLineNo: number | null newLineNo: number | null type: 'added' | 'removed' | 'context' content: string } export function DiffViewer({ filePath, oldString, newString }: Props) { const patch = useMemo( () => createPatch(filePath, oldString, newString, 'before', 'after', { context: 3 }), [filePath, newString, oldString], ) const lines = useMemo(() => parsePatchLines(patch), [patch]) const additions = lines.filter((line) => line.type === 'added').length const deletions = lines.filter((line) => line.type === 'removed').length return (
{filePath}
+{additions} -{deletions}
{lines.map((line, index) => { const rowClass = line.type === 'added' ? 'bg-[#dafbe1]/60 border-l-2 border-l-[#1a7f37]' : line.type === 'removed' ? 'bg-[#ffebe9]/60 border-l-2 border-l-[#cf222e]' : 'bg-white border-l-2 border-l-transparent' const prefix = line.type === 'added' ? '+' : line.type === 'removed' ? '-' : ' ' const prefixColor = line.type === 'added' ? 'text-[#1a7f37]' : line.type === 'removed' ? 'text-[#cf222e]' : 'text-[#57606a]' return (
{line.oldLineNo ?? ''} {line.newLineNo ?? ''} {prefix} {line.content}
) })}
) } function parsePatchLines(patch: string): DiffLine[] { const output: DiffLine[] = [] const patchLines = patch.split('\n') let oldLineNo = 0 let newLineNo = 0 for (const line of patchLines) { if ( line.startsWith('Index:') || line.startsWith('===') || line.startsWith('---') || line.startsWith('+++') ) { continue } if (line.startsWith('@@')) { const match = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)/) if (match?.[1]) oldLineNo = parseInt(match[1], 10) - 1 if (match?.[2]) newLineNo = parseInt(match[2], 10) - 1 continue } if (line.startsWith('\\ No newline')) continue if (line.startsWith('+')) { newLineNo += 1 output.push({ oldLineNo: null, newLineNo, type: 'added', content: line.slice(1) }) continue } if (line.startsWith('-')) { oldLineNo += 1 output.push({ oldLineNo, newLineNo: null, type: 'removed', content: line.slice(1) }) continue } oldLineNo += 1 newLineNo += 1 output.push({ oldLineNo, newLineNo, type: 'context', content: line.startsWith(' ') ? line.slice(1) : line, }) } return output }