diff --git a/desktop/src/components/chat/DiffViewer.tsx b/desktop/src/components/chat/DiffViewer.tsx index 2e6e36bb..9de6ec09 100644 --- a/desktop/src/components/chat/DiffViewer.tsx +++ b/desktop/src/components/chat/DiffViewer.tsx @@ -1,5 +1,5 @@ -import { useMemo } from 'react' -import { createPatch } from 'diff' +import ReactDiffViewer, { DiffMethod } from 'react-diff-viewer-continued' +import { Highlight, themes } from 'prism-react-renderer' import { CopyButton } from '../shared/CopyButton' type Props = { @@ -8,21 +8,92 @@ type Props = { newString: string } -type DiffLine = { - oldLineNo: number | null - newLineNo: number | null - type: 'added' | 'removed' | 'context' - content: string +function inferLanguage(filePath: string): string { + const ext = filePath.split('.').pop()?.toLowerCase() + const langMap: Record = { + ts: 'typescript', tsx: 'tsx', js: 'javascript', jsx: 'jsx', + py: 'python', rs: 'rust', go: 'go', rb: 'ruby', + json: 'json', yaml: 'yaml', yml: 'yaml', toml: 'toml', + md: 'markdown', css: 'css', html: 'markup', xml: 'markup', + sql: 'sql', sh: 'bash', bash: 'bash', zsh: 'bash', + } + return langMap[ext ?? ''] || 'text' +} + +function highlightSyntax(str: string, language: string) { + return ( + + {({ tokens, getTokenProps }) => ( + <> + {tokens.map((line, i) => ( + + {line.map((token, key) => ( + + ))} + + ))} + + )} + + ) +} + +const diffStyles = { + variables: { + light: { + diffViewerBackground: '#ffffff', + diffViewerColor: '#24292f', + addedBackground: '#dafbe1', + addedColor: '#24292f', + removedBackground: '#ffebe9', + removedColor: '#24292f', + wordAddedBackground: '#abf2bc', + wordRemovedBackground: '#ff818266', + addedGutterBackground: '#ccffd8', + removedGutterBackground: '#ffd7d5', + gutterBackground: '#f6f8fa', + gutterBackgroundDark: '#f0f1f3', + highlightBackground: '#fffbdd', + highlightGutterBackground: '#fff5b1', + codeFoldGutterBackground: '#dbedff', + codeFoldBackground: '#f1f8ff', + emptyLineBackground: '#fafbfc', + gutterColor: '#8b949e', + addedGutterColor: '#1a7f37', + removedGutterColor: '#cf222e', + codeFoldContentColor: '#57606a', + diffViewerTitleBackground: '#fafbfc', + diffViewerTitleColor: '#57606a', + diffViewerTitleBorderColor: '#d0d7de', + }, + }, + diffContainer: { + borderRadius: '0', + fontSize: '12px', + lineHeight: '1.3', + fontFamily: 'var(--font-mono)', + }, + line: { + padding: '1px 0', + }, + gutter: { + padding: '1px 8px', + minWidth: '40px', + fontSize: '11px', + }, + wordDiff: { + padding: '1px 2px', + borderRadius: '2px', + }, } 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 + const language = inferLanguage(filePath) + + const oldLines = oldString.split('\n') + const newLines = newString.split('\n') + const additions = newLines.filter((l, i) => l !== (oldLines[i] ?? null)).length + const deletions = oldLines.filter((l, i) => l !== (newLines[i] ?? null)).length return (
@@ -36,99 +107,25 @@ export function DiffViewer({ filePath, oldString, newString }: Props) { -{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} -
- ) - })} -
+
+ highlightSyntax(str, language)} + hideLineNumbers={false} + styles={diffStyles} + useDarkTheme={false} + />
) } - -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 -}