Switch for diff split vs inline view (#653)

This commit is contained in:
Solomon
2025-09-09 18:17:49 +01:00
committed by GitHub
parent cec320cf8b
commit 465f14ce7e
5 changed files with 113 additions and 10 deletions

View File

@@ -24,6 +24,7 @@ import type { TaskAttempt } from 'shared/types';
import { useReview, type ReviewDraft } from '@/contexts/ReviewProvider';
import { CommentWidgetLine } from '@/components/diff/CommentWidgetLine';
import { ReviewCommentRenderer } from '@/components/diff/ReviewCommentRenderer';
import { useDiffViewMode } from '@/stores/useDiffViewStore';
type Props = {
diff: Diff;
@@ -53,6 +54,7 @@ export default function DiffCard({
const { config } = useUserSystem();
const theme = getActualTheme(config?.theme);
const { comments, drafts, setDraft } = useReview();
const globalMode = useDiffViewMode();
const oldName = diff.oldPath || undefined;
const newName = diff.newPath || oldName || 'unknown';
@@ -247,7 +249,9 @@ export default function DiffCard({
diffViewWrap={false}
diffViewTheme={theme}
diffViewHighlight
diffViewMode={DiffModeEnum.Unified}
diffViewMode={
globalMode === 'split' ? DiffModeEnum.Split : DiffModeEnum.Unified
}
diffViewFontSize={12}
diffViewAddWidget
onAddWidgetClick={handleAddWidgetClick}

View File

@@ -11,6 +11,8 @@ import { getHighLightLanguageFromPath } from '@/utils/extToLanguage';
import { getActualTheme } from '@/utils/theme';
import '@/styles/diff-style-overrides.css';
import '@/styles/edit-diff-overrides.css';
import { useDiffViewMode } from '@/stores/useDiffViewStore';
import DiffViewSwitch from '@/components/diff-view-switch';
type Props = {
path: string;
@@ -67,6 +69,7 @@ function EditDiffRenderer({
const [expanded, setExpanded] = useExpandable(expansionKey, false);
const theme = getActualTheme(config?.theme);
const globalMode = useDiffViewMode();
const { hunks, hideLineNumbers, additions, deletions, isValidDiff } = useMemo(
() => processUnifiedDiff(unifiedDiff, hasLineNumbers),
@@ -104,13 +107,20 @@ function EditDiffRenderer({
{expanded && (
<div className={'mt-2 border ' + hideLineNumbersClass}>
<div className="flex items-center justify-end border-b px-2 py-1">
<DiffViewSwitch />
</div>
{isValidDiff ? (
<DiffView
data={diffData}
diffViewWrap={false}
diffViewTheme={theme}
diffViewHighlight
diffViewMode={DiffModeEnum.Unified}
diffViewMode={
globalMode === 'split'
? DiffModeEnum.Split
: DiffModeEnum.Unified
}
diffViewFontSize={12}
/>
) : (

View File

@@ -0,0 +1,66 @@
import { Columns, FileText } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { useDiffViewMode, useDiffViewStore } from '@/stores/useDiffViewStore';
type Props = {
className?: string;
size?: 'xs' | 'sm';
};
/**
* Segmented switch for Inline vs Split diff modes.
* - Left segment: Inline (Unified)
* - Right segment: Split
* Uses global Zustand store so changing here updates all diffs.
*/
export default function DiffViewSwitch({ className, size = 'xs' }: Props) {
const mode = useDiffViewMode();
const setMode = useDiffViewStore((s) => s.setMode);
const isUnified = mode === 'unified';
return (
<div
className={cn(
'inline-flex rounded-md border border-input overflow-hidden',
className
)}
role="group"
aria-label="Diff view mode"
>
<Button
variant={isUnified ? 'default' : 'outline'}
size={size}
className={cn(
'rounded-none rounded-l-md h-6',
!isUnified && 'bg-background',
'gap-1',
// Highlight the inner divider when right side is active
!isUnified && 'border-r-foreground'
)}
aria-pressed={isUnified}
onClick={() => setMode('unified')}
>
<FileText className="h-3 w-3" />
<span className="text-[11px]">Inline</span>
</Button>
<Button
variant={!isUnified ? 'default' : 'outline'}
size={size}
className={cn(
'rounded-none rounded-r-md -ml-px h-6',
isUnified && 'bg-background',
'gap-1',
// Ensure inner divider reflects active left side
isUnified && 'border-l-foreground'
)}
aria-pressed={!isUnified}
onClick={() => setMode('split')}
>
<Columns className="h-3 w-3" />
<span className="text-[11px]">Split</span>
</Button>
</div>
);
}

View File

@@ -2,6 +2,7 @@ import { useDiffEntries } from '@/hooks/useDiffEntries';
import { useMemo, useCallback, useState, useEffect } from 'react';
import { Loader } from '@/components/ui/loader';
import { Button } from '@/components/ui/button';
import DiffViewSwitch from '@/components/diff-view-switch';
import DiffCard from '@/components/DiffCard';
import { useDiffSummary } from '@/hooks/useDiffSummary';
import type { TaskAttempt } from 'shared/types';
@@ -131,14 +132,17 @@ function DiffTabContent({
-{deleted}
</span>
</span>
<Button
variant="outline"
size="xs"
onClick={handleCollapseAll}
className="shrink-0"
>
{allCollapsed ? 'Expand All' : 'Collapse All'}
</Button>
<div className="flex items-center gap-2">
<DiffViewSwitch />
<Button
variant="outline"
size="xs"
onClick={handleCollapseAll}
className="shrink-0"
>
{allCollapsed ? 'Expand All' : 'Collapse All'}
</Button>
</div>
</div>
</div>
)}

View File

@@ -0,0 +1,19 @@
import { create } from 'zustand';
export type DiffViewMode = 'unified' | 'split';
type State = {
mode: DiffViewMode;
setMode: (mode: DiffViewMode) => void;
toggle: () => void;
};
export const useDiffViewStore = create<State>((set) => ({
mode: 'unified',
setMode: (mode) => set({ mode }),
toggle: () =>
set((s) => ({ mode: s.mode === 'unified' ? 'split' : 'unified' })),
}));
export const useDiffViewMode = () => useDiffViewStore((s) => s.mode);
export const useToggleDiffViewMode = () => useDiffViewStore((s) => s.toggle);