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
}