focus on diff when showing code

This commit is contained in:
Arjun 2026-06-11 23:22:44 +05:30
parent b2176435bd
commit 89621b8bf0
2 changed files with 36 additions and 3 deletions

View file

@ -53,6 +53,18 @@ export function cmBaseExtensions(isDark: boolean): Extension[] {
color: isDark ? '#6b7280' : '#9ca3af',
},
'&.cm-focused': { outline: 'none' },
// GitHub-style expander bar for folded unchanged regions (@codemirror/merge).
'.cm-collapsedLines': {
backgroundColor: isDark ? 'rgba(56, 139, 253, 0.15)' : 'rgba(9, 105, 218, 0.08)',
backgroundImage: 'none',
color: isDark ? '#79c0ff' : '#0969da',
padding: '4px 12px',
fontSize: '11px',
cursor: 'pointer',
},
'.cm-collapsedLines:hover': {
backgroundColor: isDark ? 'rgba(56, 139, 253, 0.25)' : 'rgba(9, 105, 218, 0.15)',
},
},
{ dark: isDark },
),

View file

@ -1,7 +1,7 @@
import { useEffect, useRef, useState } from 'react'
import { MergeView, unifiedMergeView } from '@codemirror/merge'
import { EditorView } from '@codemirror/view'
import { Columns2, Rows2, X } from 'lucide-react'
import { Columns2, FoldVertical, Rows2, UnfoldVertical, X } from 'lucide-react'
import { useTheme } from '@/contexts/theme-context'
import { Button } from '@/components/ui/button'
import { cmBaseExtensions, cmLanguageFor } from './cm'
@ -22,6 +22,9 @@ export function DiffViewer({
const isDark = resolvedTheme === 'dark'
const containerRef = useRef<HTMLDivElement>(null)
const [mode, setMode] = useState<'split' | 'unified'>('split')
// GitHub-style: unchanged regions fold into "⋯ N lines" bars (each clickable
// to reveal); "Expand all" rebuilds the view with nothing collapsed.
const [collapseUnchanged, setCollapseUnchanged] = useState(true)
const [diff, setDiff] = useState<{ oldText: string; newText: string; isBinary: boolean; tooLarge: boolean } | null>(null)
const [error, setError] = useState<string | null>(null)
@ -44,19 +47,27 @@ export function DiffViewer({
void cmLanguageFor(path).then((language) => {
if (cancelled || !containerRef.current) return
const extensions = [...cmBaseExtensions(isDark), ...(language ? [language] : [])]
// Same context margins GitHub uses: keep a few lines around each hunk,
// only fold stretches long enough to be worth hiding.
const collapse = collapseUnchanged ? { margin: 3, minSize: 6 } : undefined
if (mode === 'split') {
view = new MergeView({
a: { doc: diff.oldText, extensions },
b: { doc: diff.newText, extensions },
parent,
gutter: true,
...(collapse ? { collapseUnchanged: collapse } : {}),
})
} else {
view = new EditorView({
doc: diff.newText,
extensions: [
...extensions,
unifiedMergeView({ original: diff.oldText, mergeControls: false }),
unifiedMergeView({
original: diff.oldText,
mergeControls: false,
...(collapse ? { collapseUnchanged: collapse } : {}),
}),
],
parent,
})
@ -67,12 +78,22 @@ export function DiffViewer({
cancelled = true
view?.destroy()
}
}, [diff, mode, isDark, path])
}, [diff, mode, isDark, path, collapseUnchanged])
return (
<div className="flex h-full min-h-0 flex-col">
<div className="flex items-center gap-2 border-b px-3 py-1.5">
<span className="min-w-0 flex-1 truncate font-mono text-xs text-foreground/90" title={path}>{path}</span>
<Button
variant="ghost"
size="sm"
className="h-7 px-2 text-xs text-muted-foreground"
onClick={() => setCollapseUnchanged((c) => !c)}
title={collapseUnchanged ? 'Show the whole file' : 'Collapse unchanged regions'}
>
{collapseUnchanged ? <UnfoldVertical className="size-3.5" /> : <FoldVertical className="size-3.5" />}
{collapseUnchanged ? 'Expand all' : 'Collapse'}
</Button>
<Button
variant="ghost"
size="sm"