diff --git a/apps/code/src/renderer/features/code-editor/components/CodeMirrorDiffEditor.tsx b/apps/code/src/renderer/features/code-editor/components/CodeMirrorDiffEditor.tsx index 8ecc682d5..abe820823 100644 --- a/apps/code/src/renderer/features/code-editor/components/CodeMirrorDiffEditor.tsx +++ b/apps/code/src/renderer/features/code-editor/components/CodeMirrorDiffEditor.tsx @@ -32,6 +32,12 @@ export function CodeMirrorDiffEditor({ const toggleLoadFullFiles = useDiffViewerStore((s) => s.toggleLoadFullFiles); const wordDiffs = useDiffViewerStore((s) => s.wordDiffs); const toggleWordDiffs = useDiffViewerStore((s) => s.toggleWordDiffs); + const hideWhitespaceChanges = useDiffViewerStore( + (s) => s.hideWhitespaceChanges, + ); + const toggleHideWhitespaceChanges = useDiffViewerStore( + (s) => s.toggleHideWhitespaceChanges, + ); const extensions = useEditorExtensions(filePath, !onContentChange, true); const options = useMemo( () => ({ @@ -41,6 +47,7 @@ export function CodeMirrorDiffEditor({ mode: viewMode, loadFullFiles, wordDiffs, + hideWhitespaceChanges, filePath, onContentChange, }), @@ -51,6 +58,7 @@ export function CodeMirrorDiffEditor({ viewMode, loadFullFiles, wordDiffs, + hideWhitespaceChanges, filePath, onContentChange, ], @@ -127,6 +135,11 @@ export function CodeMirrorDiffEditor({ {wordDiffs ? "Disable word diffs" : "Enable word diffs"} + + + {hideWhitespaceChanges ? "Show whitespace" : "Hide whitespace"} + + {onRefresh && ( <> diff --git a/apps/code/src/renderer/features/code-editor/hooks/useCodeMirror.ts b/apps/code/src/renderer/features/code-editor/hooks/useCodeMirror.ts index 9a35b149f..0c3178672 100644 --- a/apps/code/src/renderer/features/code-editor/hooks/useCodeMirror.ts +++ b/apps/code/src/renderer/features/code-editor/hooks/useCodeMirror.ts @@ -1,4 +1,8 @@ -import { MergeView, unifiedMergeView } from "@codemirror/merge"; +import { + diff as defaultDiff, + MergeView, + unifiedMergeView, +} from "@codemirror/merge"; import { EditorState, type Extension } from "@codemirror/state"; import { EditorView } from "@codemirror/view"; import { workspaceApi } from "@features/workspace/hooks/useWorkspace"; @@ -24,6 +28,7 @@ interface DiffOptions extends UseCodeMirrorOptions { mode: "split" | "unified"; loadFullFiles?: boolean; wordDiffs?: boolean; + hideWhitespaceChanges?: boolean; onContentChange?: (content: string) => void; } @@ -62,15 +67,28 @@ const createMergeControls = (onReject?: () => void) => { }; }; +const whitespaceIgnoringDiff = (a: string, b: string) => { + const changes = defaultDiff(a, b); + return changes.filter((change) => { + const textA = a.slice(change.fromA, change.toA); + const textB = b.slice(change.fromB, change.toB); + return textA.replace(/\s/g, "") !== textB.replace(/\s/g, ""); + }); +}; + const collapseExtension = (loadFullFiles?: boolean): Extension => loadFullFiles ? [] : gradualCollapseUnchanged({ margin: 3, minSize: 4 }); const getBaseDiffConfig = ( + hideWhitespaceChanges?: boolean, onReject?: () => void, ): Partial[0]> => ({ highlightChanges: false, gutter: true, mergeControls: createMergeControls(onReject), + diffConfig: hideWhitespaceChanges + ? { override: whitespaceIgnoringDiff } + : undefined, }); export function useCodeMirror(options: SingleDocOptions | DiffOptions) { @@ -93,6 +111,7 @@ export function useCodeMirror(options: SingleDocOptions | DiffOptions) { }); } else if (options.mode === "split") { const diffConfig = getBaseDiffConfig( + options.hideWhitespaceChanges, options.onContentChange ? () => { if (instanceRef.current instanceof MergeView) { @@ -143,6 +162,7 @@ export function useCodeMirror(options: SingleDocOptions | DiffOptions) { }); } else { const diffConfig = getBaseDiffConfig( + options.hideWhitespaceChanges, options.onContentChange ? () => { if (instanceRef.current instanceof EditorView) { diff --git a/apps/code/src/renderer/features/code-editor/stores/diffViewerStore.ts b/apps/code/src/renderer/features/code-editor/stores/diffViewerStore.ts index e0d45f662..b2f011820 100644 --- a/apps/code/src/renderer/features/code-editor/stores/diffViewerStore.ts +++ b/apps/code/src/renderer/features/code-editor/stores/diffViewerStore.ts @@ -8,6 +8,7 @@ interface DiffViewerStoreState { wordWrap: boolean; loadFullFiles: boolean; wordDiffs: boolean; + hideWhitespaceChanges: boolean; } interface DiffViewerStoreActions { @@ -16,6 +17,7 @@ interface DiffViewerStoreActions { toggleWordWrap: () => void; toggleLoadFullFiles: () => void; toggleWordDiffs: () => void; + toggleHideWhitespaceChanges: () => void; } type DiffViewerStore = DiffViewerStoreState & DiffViewerStoreActions; @@ -27,6 +29,7 @@ export const useDiffViewerStore = create()( wordWrap: true, loadFullFiles: false, wordDiffs: false, + hideWhitespaceChanges: false, setViewMode: (mode) => set({ viewMode: mode }), toggleViewMode: () => set((s) => ({ @@ -36,6 +39,8 @@ export const useDiffViewerStore = create()( toggleLoadFullFiles: () => set((s) => ({ loadFullFiles: !s.loadFullFiles })), toggleWordDiffs: () => set((s) => ({ wordDiffs: !s.wordDiffs })), + toggleHideWhitespaceChanges: () => + set((s) => ({ hideWhitespaceChanges: !s.hideWhitespaceChanges })), }), { name: "diff-viewer-storage",