feat: ignore diff whitespace (#1067)
Add an option to ignore diff whitespace for diffs.
This commit is contained in:
@@ -25,7 +25,10 @@ import type { TaskAttempt } from 'shared/types';
|
|||||||
import { useReview, type ReviewDraft } from '@/contexts/ReviewProvider';
|
import { useReview, type ReviewDraft } from '@/contexts/ReviewProvider';
|
||||||
import { CommentWidgetLine } from '@/components/diff/CommentWidgetLine';
|
import { CommentWidgetLine } from '@/components/diff/CommentWidgetLine';
|
||||||
import { ReviewCommentRenderer } from '@/components/diff/ReviewCommentRenderer';
|
import { ReviewCommentRenderer } from '@/components/diff/ReviewCommentRenderer';
|
||||||
import { useDiffViewMode } from '@/stores/useDiffViewStore';
|
import {
|
||||||
|
useDiffViewMode,
|
||||||
|
useIgnoreWhitespaceDiff,
|
||||||
|
} from '@/stores/useDiffViewStore';
|
||||||
import { useProject } from '@/contexts/project-context';
|
import { useProject } from '@/contexts/project-context';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -76,6 +79,7 @@ export default function DiffCard({
|
|||||||
const theme = getActualTheme(config?.theme);
|
const theme = getActualTheme(config?.theme);
|
||||||
const { comments, drafts, setDraft } = useReview();
|
const { comments, drafts, setDraft } = useReview();
|
||||||
const globalMode = useDiffViewMode();
|
const globalMode = useDiffViewMode();
|
||||||
|
const ignoreWhitespace = useIgnoreWhitespaceDiff();
|
||||||
const { projectId } = useProject();
|
const { projectId } = useProject();
|
||||||
|
|
||||||
const oldName = diff.oldPath || undefined;
|
const oldName = diff.oldPath || undefined;
|
||||||
@@ -92,6 +96,11 @@ export default function DiffCard({
|
|||||||
const newContentSafe = diff.newContent || '';
|
const newContentSafe = diff.newContent || '';
|
||||||
const isContentEqual = oldContentSafe === newContentSafe;
|
const isContentEqual = oldContentSafe === newContentSafe;
|
||||||
|
|
||||||
|
const diffOptions = useMemo(
|
||||||
|
() => (ignoreWhitespace ? { ignoreWhitespace: true as const } : undefined),
|
||||||
|
[ignoreWhitespace]
|
||||||
|
);
|
||||||
|
|
||||||
const diffFile = useMemo(() => {
|
const diffFile = useMemo(() => {
|
||||||
if (isContentEqual || isOmitted) return null;
|
if (isContentEqual || isOmitted) return null;
|
||||||
try {
|
try {
|
||||||
@@ -103,7 +112,8 @@ export default function DiffCard({
|
|||||||
newFileName,
|
newFileName,
|
||||||
newContentSafe,
|
newContentSafe,
|
||||||
oldLang,
|
oldLang,
|
||||||
newLang
|
newLang,
|
||||||
|
diffOptions
|
||||||
);
|
);
|
||||||
file.initRaw();
|
file.initRaw();
|
||||||
return file;
|
return file;
|
||||||
@@ -120,6 +130,7 @@ export default function DiffCard({
|
|||||||
newLang,
|
newLang,
|
||||||
oldContentSafe,
|
oldContentSafe,
|
||||||
newContentSafe,
|
newContentSafe,
|
||||||
|
diffOptions,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const add = isOmitted
|
const add = isOmitted
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { Columns, FileText } from 'lucide-react';
|
import { Columns, FileText, Pilcrow } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useDiffViewMode, useDiffViewStore } from '@/stores/useDiffViewStore';
|
import {
|
||||||
|
useDiffViewMode,
|
||||||
|
useDiffViewStore,
|
||||||
|
useIgnoreWhitespaceDiff,
|
||||||
|
} from '@/stores/useDiffViewStore';
|
||||||
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
@@ -18,46 +22,77 @@ export default function DiffViewSwitch({ className }: Props) {
|
|||||||
const { t } = useTranslation('tasks');
|
const { t } = useTranslation('tasks');
|
||||||
const mode = useDiffViewMode();
|
const mode = useDiffViewMode();
|
||||||
const setMode = useDiffViewStore((s) => s.setMode);
|
const setMode = useDiffViewStore((s) => s.setMode);
|
||||||
|
const ignoreWhitespace = useIgnoreWhitespaceDiff();
|
||||||
|
const setIgnoreWhitespace = useDiffViewStore((s) => s.setIgnoreWhitespace);
|
||||||
|
|
||||||
|
const whitespaceValue = ignoreWhitespace ? ['ignoreWhitespace'] : [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<ToggleGroup
|
<div className={cn('inline-flex gap-4', className)}>
|
||||||
type="single"
|
<ToggleGroup
|
||||||
value={mode ?? ''}
|
type="single"
|
||||||
onValueChange={(v) => v && setMode(v as 'unified' | 'split')}
|
value={mode ?? ''}
|
||||||
className={cn('inline-flex gap-4', className)}
|
onValueChange={(v) => v && setMode(v as 'unified' | 'split')}
|
||||||
aria-label="Diff view mode"
|
className="inline-flex gap-4"
|
||||||
>
|
aria-label="Diff view mode"
|
||||||
<Tooltip>
|
>
|
||||||
<TooltipTrigger asChild>
|
<Tooltip>
|
||||||
<ToggleGroupItem
|
<TooltipTrigger asChild>
|
||||||
value="unified"
|
<ToggleGroupItem
|
||||||
aria-label="Inline view"
|
value="unified"
|
||||||
active={mode === 'unified'}
|
aria-label="Inline view"
|
||||||
>
|
active={mode === 'unified'}
|
||||||
<FileText className="h-4 w-4" />
|
>
|
||||||
</ToggleGroupItem>
|
<FileText className="h-4 w-4" />
|
||||||
</TooltipTrigger>
|
</ToggleGroupItem>
|
||||||
<TooltipContent side="bottom">
|
</TooltipTrigger>
|
||||||
{t('diff.viewModes.inline')}
|
<TooltipContent side="bottom">
|
||||||
</TooltipContent>
|
{t('diff.viewModes.inline')}
|
||||||
</Tooltip>
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<ToggleGroupItem
|
<ToggleGroupItem
|
||||||
value="split"
|
value="split"
|
||||||
aria-label="Split view"
|
aria-label="Split view"
|
||||||
active={mode === 'split'}
|
active={mode === 'split'}
|
||||||
>
|
>
|
||||||
<Columns className="h-4 w-4" />
|
<Columns className="h-4 w-4" />
|
||||||
</ToggleGroupItem>
|
</ToggleGroupItem>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="bottom">
|
<TooltipContent side="bottom">
|
||||||
{t('diff.viewModes.split')}
|
{t('diff.viewModes.split')}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ToggleGroup>
|
</ToggleGroup>
|
||||||
|
|
||||||
|
<ToggleGroup
|
||||||
|
type="multiple"
|
||||||
|
value={whitespaceValue}
|
||||||
|
onValueChange={(values) =>
|
||||||
|
setIgnoreWhitespace(values.includes('ignoreWhitespace'))
|
||||||
|
}
|
||||||
|
className="inline-flex gap-4"
|
||||||
|
aria-label={t('diff.ignoreWhitespace')}
|
||||||
|
>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<ToggleGroupItem
|
||||||
|
value="ignoreWhitespace"
|
||||||
|
aria-label={t('diff.ignoreWhitespace')}
|
||||||
|
active={ignoreWhitespace}
|
||||||
|
>
|
||||||
|
<Pilcrow className="h-4 w-4" />
|
||||||
|
</ToggleGroupItem>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom">
|
||||||
|
{t('diff.ignoreWhitespace')}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</ToggleGroup>
|
||||||
|
</div>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,7 @@
|
|||||||
"inline": "Inline view",
|
"inline": "Inline view",
|
||||||
"split": "Split view"
|
"split": "Split view"
|
||||||
},
|
},
|
||||||
|
"ignoreWhitespace": "Ignore whitespace changes",
|
||||||
"errorLoadingDiff": "Failed to load diff: {{error}}",
|
"errorLoadingDiff": "Failed to load diff: {{error}}",
|
||||||
"expandAll": "Expand all diffs",
|
"expandAll": "Expand all diffs",
|
||||||
"collapseAll": "Collapse all diffs",
|
"collapseAll": "Collapse all diffs",
|
||||||
|
|||||||
@@ -75,6 +75,7 @@
|
|||||||
"collapseAll": "Collapse all diffs",
|
"collapseAll": "Collapse all diffs",
|
||||||
"errorLoadingDiff": "Failed to load diff: {{error}}",
|
"errorLoadingDiff": "Failed to load diff: {{error}}",
|
||||||
"expandAll": "Expand all diffs",
|
"expandAll": "Expand all diffs",
|
||||||
|
"ignoreWhitespace": "Ignorar cambios de espacios en blanco",
|
||||||
"filesChanged_one": "{{count}} file changed",
|
"filesChanged_one": "{{count}} file changed",
|
||||||
"filesChanged_other": "{{count}} files changed",
|
"filesChanged_other": "{{count}} files changed",
|
||||||
"noChanges": "No changes have been made yet",
|
"noChanges": "No changes have been made yet",
|
||||||
|
|||||||
@@ -75,6 +75,7 @@
|
|||||||
"collapseAll": "Collapse all diffs",
|
"collapseAll": "Collapse all diffs",
|
||||||
"errorLoadingDiff": "Failed to load diff: {{error}}",
|
"errorLoadingDiff": "Failed to load diff: {{error}}",
|
||||||
"expandAll": "Expand all diffs",
|
"expandAll": "Expand all diffs",
|
||||||
|
"ignoreWhitespace": "空白の変更を無視",
|
||||||
"filesChanged_one": "{{count}} file changed",
|
"filesChanged_one": "{{count}} file changed",
|
||||||
"filesChanged_other": "{{count}} files changed",
|
"filesChanged_other": "{{count}} files changed",
|
||||||
"noChanges": "No changes have been made yet",
|
"noChanges": "No changes have been made yet",
|
||||||
|
|||||||
@@ -75,6 +75,7 @@
|
|||||||
"collapseAll": "Collapse all diffs",
|
"collapseAll": "Collapse all diffs",
|
||||||
"errorLoadingDiff": "Failed to load diff: {{error}}",
|
"errorLoadingDiff": "Failed to load diff: {{error}}",
|
||||||
"expandAll": "Expand all diffs",
|
"expandAll": "Expand all diffs",
|
||||||
|
"ignoreWhitespace": "공백 변경 무시",
|
||||||
"filesChanged_one": "{{count}} file changed",
|
"filesChanged_one": "{{count}} file changed",
|
||||||
"filesChanged_other": "{{count}} files changed",
|
"filesChanged_other": "{{count}} files changed",
|
||||||
"noChanges": "No changes have been made yet",
|
"noChanges": "No changes have been made yet",
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ type State = {
|
|||||||
mode: DiffViewMode;
|
mode: DiffViewMode;
|
||||||
setMode: (mode: DiffViewMode) => void;
|
setMode: (mode: DiffViewMode) => void;
|
||||||
toggle: () => void;
|
toggle: () => void;
|
||||||
|
ignoreWhitespace: boolean;
|
||||||
|
setIgnoreWhitespace: (value: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useDiffViewStore = create<State>((set) => ({
|
export const useDiffViewStore = create<State>((set) => ({
|
||||||
@@ -13,6 +15,10 @@ export const useDiffViewStore = create<State>((set) => ({
|
|||||||
setMode: (mode) => set({ mode }),
|
setMode: (mode) => set({ mode }),
|
||||||
toggle: () =>
|
toggle: () =>
|
||||||
set((s) => ({ mode: s.mode === 'unified' ? 'split' : 'unified' })),
|
set((s) => ({ mode: s.mode === 'unified' ? 'split' : 'unified' })),
|
||||||
|
ignoreWhitespace: true,
|
||||||
|
setIgnoreWhitespace: (value) => set({ ignoreWhitespace: value }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const useDiffViewMode = () => useDiffViewStore((s) => s.mode);
|
export const useDiffViewMode = () => useDiffViewStore((s) => s.mode);
|
||||||
|
export const useIgnoreWhitespaceDiff = () =>
|
||||||
|
useDiffViewStore((s) => s.ignoreWhitespace);
|
||||||
|
|||||||
Reference in New Issue
Block a user