Skip to content

Commit 75d4dcd

Browse files
adboiojonathanlab
authored andcommitted
feat(code): replace in-chat diffs with pierre/diffs
1 parent e3e00f0 commit 75d4dcd

File tree

6 files changed

+118
-217
lines changed

6 files changed

+118
-217
lines changed

apps/code/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@
109109
"@codemirror/lang-xml": "^6.1.0",
110110
"@codemirror/lang-yaml": "^6.1.2",
111111
"@codemirror/language": "^6.12.2",
112-
"@codemirror/merge": "^6.12.0",
113112
"@codemirror/search": "^6.6.0",
114113
"@codemirror/state": "^6.5.4",
115114
"@codemirror/view": "^6.39.17",

apps/code/src/renderer/features/code-editor/hooks/useEditorExtensions.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,35 +12,27 @@ import {
1212
} from "@codemirror/view";
1313
import { useThemeStore } from "@stores/themeStore";
1414
import { useMemo } from "react";
15-
import { useDiffViewerStore } from "../stores/diffViewerStore";
16-
import { mergeViewTheme, oneDark, oneLight } from "../theme/editorTheme";
15+
import { oneDark, oneLight } from "../theme/editorTheme";
1716
import { getLanguageExtension } from "../utils/languages";
1817

19-
export function useEditorExtensions(
20-
filePath?: string,
21-
readOnly = false,
22-
isDiff = false,
23-
) {
18+
export function useEditorExtensions(filePath?: string, readOnly = false) {
2419
const isDarkMode = useThemeStore((state) => state.isDarkMode);
25-
const wordWrap = useDiffViewerStore((state) => state.wordWrap);
2620

2721
return useMemo(() => {
2822
const languageExtension = filePath ? getLanguageExtension(filePath) : null;
2923
const theme = isDarkMode ? oneDark : oneLight;
30-
const shouldWrap = isDiff ? wordWrap : true;
3124

3225
return [
3326
lineNumbers(),
3427
highlightActiveLineGutter(),
3528
search(),
3629
highlightSelectionMatches(),
3730
keymap.of(searchKeymap),
38-
...(shouldWrap ? [EditorView.lineWrapping] : []),
31+
EditorView.lineWrapping,
3932
theme,
40-
mergeViewTheme,
4133
EditorView.editable.of(!readOnly),
42-
...(readOnly && !isDiff ? [EditorState.readOnly.of(true)] : []),
34+
...(readOnly ? [EditorState.readOnly.of(true)] : []),
4335
...(languageExtension ? [languageExtension] : []),
4436
];
45-
}, [filePath, isDarkMode, readOnly, isDiff, wordWrap]);
37+
}, [filePath, isDarkMode, readOnly]);
4638
}

apps/code/src/renderer/features/code-editor/theme/editorTheme.ts

Lines changed: 0 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -246,161 +246,3 @@ export const oneLight: Extension = [
246246
createEditorTheme(light, false),
247247
syntaxHighlighting(createHighlightStyle(light)),
248248
];
249-
250-
export const mergeViewTheme = EditorView.baseTheme({
251-
".cm-mergeView": {
252-
height: "100%",
253-
overflowY: "auto",
254-
},
255-
".cm-mergeView .cm-content": {
256-
padding: "0",
257-
},
258-
".cm-mergeViewEditors": {
259-
display: "flex",
260-
alignItems: "stretch",
261-
minHeight: "100%",
262-
},
263-
".cm-mergeViewEditor": {
264-
flexGrow: "1",
265-
flexBasis: "0",
266-
overflow: "hidden",
267-
},
268-
".cm-merge-revert button": {
269-
opacity: "0",
270-
transition: "opacity 0.15s ease",
271-
},
272-
".cm-merge-revert:hover button": {
273-
opacity: "1",
274-
},
275-
// Light mode - line-level diffs (subtle)
276-
"&light.cm-merge-a .cm-changedLine, &light .cm-deletedChunk": {
277-
backgroundColor: "rgba(220, 80, 80, 0.15)",
278-
},
279-
"&light.cm-merge-b .cm-changedLine, &light .cm-inlineChangedLine": {
280-
backgroundColor: "rgba(80, 180, 100, 0.15)",
281-
},
282-
// Dark mode - line-level diffs (subtle)
283-
"&dark.cm-merge-a .cm-changedLine, &dark .cm-deletedChunk": {
284-
backgroundColor: "rgba(220, 80, 80, 0.15)",
285-
},
286-
"&dark.cm-merge-b .cm-changedLine, &dark .cm-inlineChangedLine": {
287-
backgroundColor: "rgba(80, 180, 100, 0.15)",
288-
},
289-
".cm-changedText": {
290-
backgroundImage: "none !important",
291-
backgroundSize: "0 0 !important",
292-
backgroundPosition: "0 0 !important",
293-
borderRadius: "0",
294-
padding: "1px 0",
295-
},
296-
"&light.cm-merge-a .cm-changedText, &light .cm-deletedChunk .cm-deletedText":
297-
{
298-
backgroundColor: "rgba(220, 80, 80, 0.35)",
299-
backgroundImage: "none",
300-
},
301-
"&dark.cm-merge-a .cm-changedText, &dark .cm-deletedChunk .cm-deletedText": {
302-
backgroundColor: "rgba(220, 80, 80, 0.35)",
303-
backgroundImage: "none",
304-
},
305-
"&light.cm-merge-b .cm-changedText": {
306-
backgroundColor: "rgba(80, 180, 100, 0.35)",
307-
backgroundImage: "none",
308-
},
309-
"&dark.cm-merge-b .cm-changedText": {
310-
backgroundColor: "rgba(80, 180, 100, 0.35)",
311-
backgroundImage: "none",
312-
},
313-
"&.cm-merge-b .cm-deletedText": {
314-
backgroundColor: "rgba(220, 80, 80, 0.35)",
315-
backgroundImage: "none",
316-
},
317-
".cm-insertedLine, .cm-deletedLine, .cm-deletedLine del": {
318-
textDecoration: "none",
319-
},
320-
".cm-deletedChunk": {
321-
paddingLeft: "6px",
322-
"& .cm-chunkButtons": {
323-
position: "absolute",
324-
insetInlineEnd: "5px",
325-
opacity: "0",
326-
transition: "opacity 0.15s ease",
327-
},
328-
"&:hover .cm-chunkButtons": {
329-
opacity: "1",
330-
},
331-
"& button": {
332-
cursor: "pointer",
333-
margin: "0 2px",
334-
"&[name=accept]": {
335-
border: "none",
336-
background: "#2a2",
337-
color: "white",
338-
borderRadius: "3px",
339-
},
340-
},
341-
},
342-
".cm-collapsed-context": {
343-
display: "flex",
344-
alignItems: "center",
345-
justifyContent: "flex-start",
346-
gap: "4px",
347-
padding: "0 8px",
348-
borderTop: "1px solid var(--gray-6)",
349-
borderBottom: "1px solid var(--gray-6)",
350-
fontSize: "12px",
351-
lineHeight: "1",
352-
userSelect: "none",
353-
minHeight: "26px",
354-
},
355-
"&light .cm-collapsed-context": {
356-
background: "#e8e9e3",
357-
color: "#3a4036",
358-
},
359-
"&dark .cm-collapsed-context": {
360-
background: "#1a1a24",
361-
color: "#9898b6",
362-
},
363-
".cm-collapsed-gutter-el": {
364-
borderTop: "1px solid var(--gray-6)",
365-
borderBottom: "1px solid var(--gray-6)",
366-
},
367-
"&light .cm-collapsed-gutter-el": {
368-
background: "#e8e9e3",
369-
},
370-
"&dark .cm-collapsed-gutter-el": {
371-
background: "#1a1a24",
372-
},
373-
".cm-collapsed-expand-btn": {
374-
display: "inline-flex",
375-
alignItems: "center",
376-
gap: "4px",
377-
border: "none",
378-
borderRadius: "3px",
379-
cursor: "pointer",
380-
padding: "3px 6px",
381-
lineHeight: "0",
382-
background: "transparent",
383-
color: "inherit",
384-
fontFamily: "inherit",
385-
fontSize: "11px",
386-
opacity: "0.6",
387-
transition: "opacity 0.15s ease, background 0.15s ease",
388-
"& span": {
389-
lineHeight: "1",
390-
},
391-
"&:hover": {
392-
opacity: "1",
393-
background: "var(--gray-a4)",
394-
},
395-
},
396-
".cm-changeGutter": { width: "3px", paddingLeft: "0px" },
397-
"&light.cm-merge-a .cm-changedLineGutter, &light .cm-deletedLineGutter": {
398-
background: "#e43",
399-
},
400-
"&dark.cm-merge-a .cm-changedLineGutter, &dark .cm-deletedLineGutter": {
401-
background: "#fa9",
402-
},
403-
"&light.cm-merge-b .cm-changedLineGutter": { background: "#2b2" },
404-
"&dark.cm-merge-b .cm-changedLineGutter": { background: "#8f8" },
405-
".cm-inlineChangedLineGutter": { background: "#75d" },
406-
});

apps/code/src/renderer/features/sessions/components/session-update/CodePreview.tsx

Lines changed: 111 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
1-
import { unifiedMergeView } from "@codemirror/merge";
2-
import type { Extension } from "@codemirror/state";
31
import { EditorView } from "@codemirror/view";
2+
import { MultiFileDiff, WorkerPoolContextProvider } from "@pierre/diffs/react";
3+
import WorkerUrl from "@pierre/diffs/worker/worker.js?worker&url";
44
import { Code } from "@radix-ui/themes";
5+
import { useThemeStore } from "@stores/themeStore";
56
import { compactHomePath } from "@utils/path";
6-
import { useEffect, useRef } from "react";
7+
import { useEffect, useMemo, useRef } from "react";
78
import {
89
CODE_PREVIEW_CONTAINER_STYLE,
910
CODE_PREVIEW_EDITOR_STYLE,
1011
CODE_PREVIEW_PATH_STYLE,
1112
useCodePreviewExtensions,
1213
} from "./useCodePreviewExtensions";
1314

15+
function workerFactory(): Worker {
16+
return new Worker(WorkerUrl, { type: "module" });
17+
}
18+
1419
interface CodePreviewProps {
1520
content: string;
1621
filePath?: string;
@@ -28,43 +33,126 @@ export function CodePreview({
2833
firstLineNumber = 1,
2934
maxHeight,
3035
}: CodePreviewProps) {
31-
const containerRef = useRef<HTMLDivElement>(null);
32-
const editorRef = useRef<EditorView | null>(null);
3336
const isDiff = oldContent !== undefined && oldContent !== null;
34-
const extensions = useCodePreviewExtensions(
35-
filePath,
36-
isDiff,
37-
firstLineNumber,
37+
38+
if (isDiff) {
39+
return (
40+
<DiffPreview
41+
content={content}
42+
filePath={filePath}
43+
showPath={showPath}
44+
oldContent={oldContent}
45+
maxHeight={maxHeight}
46+
/>
47+
);
48+
}
49+
50+
return (
51+
<PlainCodePreview
52+
content={content}
53+
filePath={filePath}
54+
showPath={showPath}
55+
firstLineNumber={firstLineNumber}
56+
maxHeight={maxHeight}
57+
/>
58+
);
59+
}
60+
61+
function DiffPreview({
62+
content,
63+
filePath,
64+
showPath,
65+
oldContent,
66+
maxHeight,
67+
}: {
68+
content: string;
69+
filePath?: string;
70+
showPath?: boolean;
71+
oldContent: string;
72+
maxHeight?: string;
73+
}) {
74+
const isDarkMode = useThemeStore((s) => s.isDarkMode);
75+
const fileName = filePath?.split("/").pop() ?? "file";
76+
77+
const oldFile = useMemo(
78+
() => ({ name: fileName, contents: oldContent }),
79+
[fileName, oldContent],
80+
);
81+
const newFile = useMemo(
82+
() => ({ name: fileName, contents: content }),
83+
[fileName, content],
84+
);
85+
const options = useMemo(
86+
() => ({
87+
diffStyle: "unified" as const,
88+
overflow: "wrap" as const,
89+
themeType: (isDarkMode ? "dark" : "light") as "dark" | "light",
90+
theme: { dark: "github-dark" as const, light: "github-light" as const },
91+
disableFileHeader: true,
92+
}),
93+
[isDarkMode],
94+
);
95+
96+
return (
97+
<div style={CODE_PREVIEW_CONTAINER_STYLE}>
98+
{showPath && filePath && (
99+
<div style={CODE_PREVIEW_PATH_STYLE} title={filePath}>
100+
<Code variant="ghost" size="1" className="truncate">
101+
{compactHomePath(filePath)}
102+
</Code>
103+
</div>
104+
)}
105+
<div style={maxHeight ? { maxHeight, overflow: "auto" } : undefined}>
106+
<WorkerPoolContextProvider
107+
poolOptions={{ workerFactory }}
108+
highlighterOptions={{
109+
theme: { dark: "github-dark", light: "github-light" },
110+
}}
111+
>
112+
<MultiFileDiff
113+
oldFile={oldFile}
114+
newFile={newFile}
115+
options={options}
116+
/>
117+
</WorkerPoolContextProvider>
118+
</div>
119+
</div>
38120
);
121+
}
122+
123+
function PlainCodePreview({
124+
content,
125+
filePath,
126+
showPath,
127+
firstLineNumber,
128+
maxHeight,
129+
}: {
130+
content: string;
131+
filePath?: string;
132+
showPath?: boolean;
133+
firstLineNumber: number;
134+
maxHeight?: string;
135+
}) {
136+
const containerRef = useRef<HTMLDivElement>(null);
137+
const editorRef = useRef<EditorView | null>(null);
138+
const extensions = useCodePreviewExtensions(filePath, firstLineNumber);
39139

40140
useEffect(() => {
41141
if (!containerRef.current) return;
42142

43143
editorRef.current?.destroy();
44144

45-
const diffExtension: Extension[] = isDiff
46-
? [
47-
unifiedMergeView({
48-
original: oldContent,
49-
highlightChanges: false,
50-
gutter: false,
51-
mergeControls: false,
52-
collapseUnchanged: { margin: 3, minSize: 4 },
53-
}),
54-
]
55-
: [];
56-
57145
editorRef.current = new EditorView({
58146
doc: content,
59-
extensions: [...extensions, ...diffExtension],
147+
extensions,
60148
parent: containerRef.current,
61149
});
62150

63151
return () => {
64152
editorRef.current?.destroy();
65153
editorRef.current = null;
66154
};
67-
}, [content, oldContent, extensions, isDiff]);
155+
}, [content, extensions]);
68156

69157
return (
70158
<div style={CODE_PREVIEW_CONTAINER_STYLE}>

0 commit comments

Comments
 (0)