Download desktop/src/components/chat/DiffViewer.tsx from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 6.69 kB
-
https://huggingface.co/chenbhao/codev/resolve/e40073cc959cd4729bf4009662bca374c5c4e266/desktop/src/components/chat/DiffViewer.tsx
- Command line
-
hf download hf://chenbhao/codev@e40073cc959cd4729bf4009662bca374c5c4e266/desktop/src/components/chat/DiffViewer.tsx
-
curl -L -o DiffViewer.tsx https://huggingface.co/chenbhao/codev/resolve/e40073cc959cd4729bf4009662bca374c5c4e266/desktop/src/components/chat/DiffViewer.tsx
6.69 kB
| import ReactDiffViewer, { DiffMethod } from 'react-diff-viewer-continued' | |
| import { Highlight, type PrismTheme } from 'prism-react-renderer' | |
| import { CopyButton } from '../shared/CopyButton' | |
| type Props = { | |
| filePath: string | |
| oldString: string | |
| newString: string | |
| } | |
| function inferLanguage(filePath: string): string { | |
| const ext = filePath.split('.').pop()?.toLowerCase() | |
| const langMap: Record<string, string> = { | |
| 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' | |
| } | |
| /** Shared warm syntax theme — must stay in sync with CodeViewer */ | |
| const warmSyntaxTheme: PrismTheme = { | |
| plain: { | |
| color: 'var(--color-code-fg)', | |
| backgroundColor: 'transparent', | |
| }, | |
| styles: [ | |
| { types: ['comment', 'prolog', 'doctype', 'cdata'], style: { color: 'var(--color-code-comment)', fontStyle: 'italic' as const } }, | |
| { types: ['string', 'attr-value', 'template-string'], style: { color: 'var(--color-code-string)' } }, | |
| { types: ['keyword', 'selector', 'important', 'atrule'], style: { color: 'var(--color-code-keyword)' } }, | |
| { types: ['function'], style: { color: 'var(--color-code-function)' } }, | |
| { types: ['tag'], style: { color: 'var(--color-code-keyword)' } }, | |
| { types: ['number', 'boolean'], style: { color: 'var(--color-code-number)' } }, | |
| { types: ['operator'], style: { color: 'var(--color-code-fg)' } }, | |
| { types: ['punctuation'], style: { color: 'var(--color-code-punctuation)' } }, | |
| { types: ['variable', 'parameter'], style: { color: 'var(--color-code-fg)' } }, | |
| { types: ['property', 'attr-name'], style: { color: 'var(--color-code-property)' } }, | |
| { types: ['builtin', 'class-name', 'constant', 'symbol'], style: { color: 'var(--color-code-type)' } }, | |
| { types: ['regex'], style: { color: 'var(--color-primary-container)' } }, | |
| { types: ['inserted'], style: { color: 'var(--color-code-inserted)' } }, | |
| { types: ['deleted'], style: { color: 'var(--color-code-deleted)' } }, | |
| ], | |
| } | |
| function highlightSyntax(str: string, language: string) { | |
| return ( | |
| <Highlight theme={warmSyntaxTheme} code={str} language={language}> | |
| {({ tokens, getTokenProps }) => ( | |
| <> | |
| {tokens.map((line, i) => ( | |
| <span key={i}> | |
| {line.map((token, key) => ( | |
| <span key={key} {...getTokenProps({ token })} /> | |
| ))} | |
| </span> | |
| ))} | |
| </> | |
| )} | |
| </Highlight> | |
| ) | |
| } | |
| const diffStyles = { | |
| variables: { | |
| light: { | |
| diffViewerBackground: 'var(--color-code-bg)', | |
| diffViewerColor: 'var(--color-code-fg)', | |
| addedBackground: 'var(--color-diff-added-bg)', | |
| addedColor: 'var(--color-code-fg)', | |
| removedBackground: 'var(--color-diff-removed-bg)', | |
| removedColor: 'var(--color-code-fg)', | |
| wordAddedBackground: 'var(--color-diff-added-word)', | |
| wordRemovedBackground: 'var(--color-diff-removed-word)', | |
| addedGutterBackground: 'var(--color-diff-added-gutter)', | |
| removedGutterBackground: 'var(--color-diff-removed-gutter)', | |
| gutterBackground: 'var(--color-surface-container-low)', | |
| gutterBackgroundDark: 'var(--color-surface-container)', | |
| highlightBackground: 'var(--color-diff-highlight-bg)', | |
| highlightGutterBackground: 'var(--color-diff-highlight-gutter)', | |
| codeFoldGutterBackground: 'var(--color-surface-container-high)', | |
| codeFoldBackground: 'var(--color-surface-container-highest)', | |
| emptyLineBackground: 'var(--color-surface-container-low)', | |
| gutterColor: 'var(--color-text-tertiary)', | |
| addedGutterColor: 'var(--color-diff-added-text)', | |
| removedGutterColor: 'var(--color-diff-removed-text)', | |
| codeFoldContentColor: 'var(--color-text-tertiary)', | |
| diffViewerTitleBackground: 'var(--color-diff-title-bg)', | |
| diffViewerTitleColor: 'var(--color-diff-title-color)', | |
| diffViewerTitleBorderColor: 'var(--color-diff-title-border)', | |
| }, | |
| }, | |
| diffContainer: { | |
| borderRadius: '0', | |
| fontSize: '12px', | |
| lineHeight: '1.45', | |
| 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 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 ( | |
| <div className="overflow-hidden rounded-[var(--radius-lg)] border border-[var(--color-outline-variant)]/50 bg-[var(--color-surface-container-low)]"> | |
| {/* Header */} | |
| <div className="flex items-center justify-between border-b border-[var(--color-outline-variant)]/40 bg-[var(--color-surface-container)] px-3 py-1.5"> | |
| <div className="min-w-0"> | |
| <div className="truncate font-[var(--font-mono)] text-[11px] text-[var(--color-text-tertiary)]"> | |
| {filePath} | |
| </div> | |
| <div className="mt-1 flex items-center gap-2 text-[10px] uppercase tracking-[0.14em]"> | |
| <span className="rounded-full bg-[var(--color-diff-added-bg)] px-2 py-0.5 text-[var(--color-diff-added-text)]">+{additions}</span> | |
| <span className="rounded-full bg-[var(--color-diff-removed-bg)] px-2 py-0.5 text-[var(--color-diff-removed-text)]">-{deletions}</span> | |
| </div> | |
| </div> | |
| <CopyButton | |
| text={`--- ${filePath}\n+++ ${filePath}`} | |
| label="Copy path" | |
| className="rounded-md border border-[var(--color-outline-variant)]/40 bg-[var(--color-surface-container-lowest)] px-2 py-1 text-[11px] text-[var(--color-text-tertiary)] transition-colors hover:bg-[var(--color-surface-container-high)] hover:text-[var(--color-text-primary)]" | |
| /> | |
| </div> | |
| {/* Diff area */} | |
| <div className="max-h-[400px] overflow-auto"> | |
| <ReactDiffViewer | |
| oldValue={oldString} | |
| newValue={newString} | |
| splitView={false} | |
| compareMethod={DiffMethod.WORDS} | |
| renderContent={(str) => highlightSyntax(str, language)} | |
| hideLineNumbers={false} | |
| styles={diffStyles} | |
| useDarkTheme={document.documentElement.getAttribute('data-theme') === 'dark'} | |
| /> | |
| </div> | |
| </div> | |
| ) | |
| } | |