From cf3434b8e5ec9f15c635f332bfccfaf17bbcb237 Mon Sep 17 00:00:00 2001 From: Richard Solomou Date: Tue, 31 Mar 2026 13:29:38 +0300 Subject: [PATCH 1/2] feat(code): add hide whitespace changes toggle to diff viewer Adds a "Hide whitespace changes" option to the diff viewer dropdown menu. When enabled, whitespace-only differences are filtered out of the diff using a custom override of the @codemirror/merge diff algorithm, similar to git diff -w. --- .../components/CodeMirrorDiffEditor.tsx | 15 ++++++++ .../code-editor/hooks/useCodeMirror.ts | 37 +++++++++++++++++-- .../code-editor/stores/diffViewerStore.ts | 5 +++ 3 files changed, 53 insertions(+), 4 deletions(-) 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..6ba41e92b 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,13 @@ export function CodeMirrorDiffEditor({ {wordDiffs ? "Disable word diffs" : "Enable word diffs"} + + + {hideWhitespaceChanges + ? "Show whitespace changes" + : "Hide whitespace changes"} + + {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 e4f9610dc..1fc7933cf 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"; @@ -23,6 +27,7 @@ interface DiffOptions extends UseCodeMirrorOptions { mode: "split" | "unified"; loadFullFiles?: boolean; wordDiffs?: boolean; + hideWhitespaceChanges?: boolean; onContentChange?: (content: string) => void; } @@ -61,8 +66,21 @@ 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 getBaseDiffConfig = ( - options?: { loadFullFiles?: boolean; wordDiffs?: boolean }, + options?: { + loadFullFiles?: boolean; + wordDiffs?: boolean; + hideWhitespaceChanges?: boolean; + }, onReject?: () => void, ): Partial[0]> => ({ collapseUnchanged: options?.loadFullFiles @@ -71,6 +89,9 @@ const getBaseDiffConfig = ( highlightChanges: false, gutter: true, mergeControls: createMergeControls(onReject), + diffConfig: options?.hideWhitespaceChanges + ? { override: whitespaceIgnoringDiff } + : undefined, }); export function useCodeMirror(options: SingleDocOptions | DiffOptions) { @@ -93,7 +114,11 @@ export function useCodeMirror(options: SingleDocOptions | DiffOptions) { }); } else if (options.mode === "split") { const diffConfig = getBaseDiffConfig( - { loadFullFiles: options.loadFullFiles, wordDiffs: options.wordDiffs }, + { + loadFullFiles: options.loadFullFiles, + wordDiffs: options.wordDiffs, + hideWhitespaceChanges: options.hideWhitespaceChanges, + }, options.onContentChange ? () => { if (instanceRef.current instanceof MergeView) { @@ -140,7 +165,11 @@ export function useCodeMirror(options: SingleDocOptions | DiffOptions) { }); } else { const diffConfig = getBaseDiffConfig( - { loadFullFiles: options.loadFullFiles, wordDiffs: options.wordDiffs }, + { + loadFullFiles: options.loadFullFiles, + wordDiffs: options.wordDiffs, + hideWhitespaceChanges: 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", From 5b517ae6631b75d72dc9c4d48d21327bcb9fcd6b Mon Sep 17 00:00:00 2001 From: Richard Solomou Date: Tue, 31 Mar 2026 13:38:22 +0300 Subject: [PATCH 2/2] fix(code): shorten whitespace toggle labels --- .../features/code-editor/components/CodeMirrorDiffEditor.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 6ba41e92b..abe820823 100644 --- a/apps/code/src/renderer/features/code-editor/components/CodeMirrorDiffEditor.tsx +++ b/apps/code/src/renderer/features/code-editor/components/CodeMirrorDiffEditor.tsx @@ -137,9 +137,7 @@ export function CodeMirrorDiffEditor({ - {hideWhitespaceChanges - ? "Show whitespace changes" - : "Hide whitespace changes"} + {hideWhitespaceChanges ? "Show whitespace" : "Hide whitespace"}