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",