From 75d4dcdc1b23a65c9bb28ccdb8412e85f0e4aa6f Mon Sep 17 00:00:00 2001 From: Adam Bowker Date: Tue, 31 Mar 2026 20:38:53 -0700 Subject: [PATCH] feat(code): replace in-chat diffs with pierre/diffs --- apps/code/package.json | 1 - .../code-editor/hooks/useEditorExtensions.ts | 18 +- .../features/code-editor/theme/editorTheme.ts | 158 ------------------ .../components/session-update/CodePreview.tsx | 134 ++++++++++++--- .../useCodePreviewExtensions.ts | 10 +- pnpm-lock.yaml | 14 -- 6 files changed, 118 insertions(+), 217 deletions(-) diff --git a/apps/code/package.json b/apps/code/package.json index 36d34a521..1f7e51e4f 100644 --- a/apps/code/package.json +++ b/apps/code/package.json @@ -109,7 +109,6 @@ "@codemirror/lang-xml": "^6.1.0", "@codemirror/lang-yaml": "^6.1.2", "@codemirror/language": "^6.12.2", - "@codemirror/merge": "^6.12.0", "@codemirror/search": "^6.6.0", "@codemirror/state": "^6.5.4", "@codemirror/view": "^6.39.17", diff --git a/apps/code/src/renderer/features/code-editor/hooks/useEditorExtensions.ts b/apps/code/src/renderer/features/code-editor/hooks/useEditorExtensions.ts index b9e29ab2b..14a02c07d 100644 --- a/apps/code/src/renderer/features/code-editor/hooks/useEditorExtensions.ts +++ b/apps/code/src/renderer/features/code-editor/hooks/useEditorExtensions.ts @@ -12,22 +12,15 @@ import { } from "@codemirror/view"; import { useThemeStore } from "@stores/themeStore"; import { useMemo } from "react"; -import { useDiffViewerStore } from "../stores/diffViewerStore"; -import { mergeViewTheme, oneDark, oneLight } from "../theme/editorTheme"; +import { oneDark, oneLight } from "../theme/editorTheme"; import { getLanguageExtension } from "../utils/languages"; -export function useEditorExtensions( - filePath?: string, - readOnly = false, - isDiff = false, -) { +export function useEditorExtensions(filePath?: string, readOnly = false) { const isDarkMode = useThemeStore((state) => state.isDarkMode); - const wordWrap = useDiffViewerStore((state) => state.wordWrap); return useMemo(() => { const languageExtension = filePath ? getLanguageExtension(filePath) : null; const theme = isDarkMode ? oneDark : oneLight; - const shouldWrap = isDiff ? wordWrap : true; return [ lineNumbers(), @@ -35,12 +28,11 @@ export function useEditorExtensions( search(), highlightSelectionMatches(), keymap.of(searchKeymap), - ...(shouldWrap ? [EditorView.lineWrapping] : []), + EditorView.lineWrapping, theme, - mergeViewTheme, EditorView.editable.of(!readOnly), - ...(readOnly && !isDiff ? [EditorState.readOnly.of(true)] : []), + ...(readOnly ? [EditorState.readOnly.of(true)] : []), ...(languageExtension ? [languageExtension] : []), ]; - }, [filePath, isDarkMode, readOnly, isDiff, wordWrap]); + }, [filePath, isDarkMode, readOnly]); } diff --git a/apps/code/src/renderer/features/code-editor/theme/editorTheme.ts b/apps/code/src/renderer/features/code-editor/theme/editorTheme.ts index 8b5b8725a..99dfab538 100644 --- a/apps/code/src/renderer/features/code-editor/theme/editorTheme.ts +++ b/apps/code/src/renderer/features/code-editor/theme/editorTheme.ts @@ -246,161 +246,3 @@ export const oneLight: Extension = [ createEditorTheme(light, false), syntaxHighlighting(createHighlightStyle(light)), ]; - -export const mergeViewTheme = EditorView.baseTheme({ - ".cm-mergeView": { - height: "100%", - overflowY: "auto", - }, - ".cm-mergeView .cm-content": { - padding: "0", - }, - ".cm-mergeViewEditors": { - display: "flex", - alignItems: "stretch", - minHeight: "100%", - }, - ".cm-mergeViewEditor": { - flexGrow: "1", - flexBasis: "0", - overflow: "hidden", - }, - ".cm-merge-revert button": { - opacity: "0", - transition: "opacity 0.15s ease", - }, - ".cm-merge-revert:hover button": { - opacity: "1", - }, - // Light mode - line-level diffs (subtle) - "&light.cm-merge-a .cm-changedLine, &light .cm-deletedChunk": { - backgroundColor: "rgba(220, 80, 80, 0.15)", - }, - "&light.cm-merge-b .cm-changedLine, &light .cm-inlineChangedLine": { - backgroundColor: "rgba(80, 180, 100, 0.15)", - }, - // Dark mode - line-level diffs (subtle) - "&dark.cm-merge-a .cm-changedLine, &dark .cm-deletedChunk": { - backgroundColor: "rgba(220, 80, 80, 0.15)", - }, - "&dark.cm-merge-b .cm-changedLine, &dark .cm-inlineChangedLine": { - backgroundColor: "rgba(80, 180, 100, 0.15)", - }, - ".cm-changedText": { - backgroundImage: "none !important", - backgroundSize: "0 0 !important", - backgroundPosition: "0 0 !important", - borderRadius: "0", - padding: "1px 0", - }, - "&light.cm-merge-a .cm-changedText, &light .cm-deletedChunk .cm-deletedText": - { - backgroundColor: "rgba(220, 80, 80, 0.35)", - backgroundImage: "none", - }, - "&dark.cm-merge-a .cm-changedText, &dark .cm-deletedChunk .cm-deletedText": { - backgroundColor: "rgba(220, 80, 80, 0.35)", - backgroundImage: "none", - }, - "&light.cm-merge-b .cm-changedText": { - backgroundColor: "rgba(80, 180, 100, 0.35)", - backgroundImage: "none", - }, - "&dark.cm-merge-b .cm-changedText": { - backgroundColor: "rgba(80, 180, 100, 0.35)", - backgroundImage: "none", - }, - "&.cm-merge-b .cm-deletedText": { - backgroundColor: "rgba(220, 80, 80, 0.35)", - backgroundImage: "none", - }, - ".cm-insertedLine, .cm-deletedLine, .cm-deletedLine del": { - textDecoration: "none", - }, - ".cm-deletedChunk": { - paddingLeft: "6px", - "& .cm-chunkButtons": { - position: "absolute", - insetInlineEnd: "5px", - opacity: "0", - transition: "opacity 0.15s ease", - }, - "&:hover .cm-chunkButtons": { - opacity: "1", - }, - "& button": { - cursor: "pointer", - margin: "0 2px", - "&[name=accept]": { - border: "none", - background: "#2a2", - color: "white", - borderRadius: "3px", - }, - }, - }, - ".cm-collapsed-context": { - display: "flex", - alignItems: "center", - justifyContent: "flex-start", - gap: "4px", - padding: "0 8px", - borderTop: "1px solid var(--gray-6)", - borderBottom: "1px solid var(--gray-6)", - fontSize: "12px", - lineHeight: "1", - userSelect: "none", - minHeight: "26px", - }, - "&light .cm-collapsed-context": { - background: "#e8e9e3", - color: "#3a4036", - }, - "&dark .cm-collapsed-context": { - background: "#1a1a24", - color: "#9898b6", - }, - ".cm-collapsed-gutter-el": { - borderTop: "1px solid var(--gray-6)", - borderBottom: "1px solid var(--gray-6)", - }, - "&light .cm-collapsed-gutter-el": { - background: "#e8e9e3", - }, - "&dark .cm-collapsed-gutter-el": { - background: "#1a1a24", - }, - ".cm-collapsed-expand-btn": { - display: "inline-flex", - alignItems: "center", - gap: "4px", - border: "none", - borderRadius: "3px", - cursor: "pointer", - padding: "3px 6px", - lineHeight: "0", - background: "transparent", - color: "inherit", - fontFamily: "inherit", - fontSize: "11px", - opacity: "0.6", - transition: "opacity 0.15s ease, background 0.15s ease", - "& span": { - lineHeight: "1", - }, - "&:hover": { - opacity: "1", - background: "var(--gray-a4)", - }, - }, - ".cm-changeGutter": { width: "3px", paddingLeft: "0px" }, - "&light.cm-merge-a .cm-changedLineGutter, &light .cm-deletedLineGutter": { - background: "#e43", - }, - "&dark.cm-merge-a .cm-changedLineGutter, &dark .cm-deletedLineGutter": { - background: "#fa9", - }, - "&light.cm-merge-b .cm-changedLineGutter": { background: "#2b2" }, - "&dark.cm-merge-b .cm-changedLineGutter": { background: "#8f8" }, - ".cm-inlineChangedLineGutter": { background: "#75d" }, -}); diff --git a/apps/code/src/renderer/features/sessions/components/session-update/CodePreview.tsx b/apps/code/src/renderer/features/sessions/components/session-update/CodePreview.tsx index 62d2175b8..b7cbc4d28 100644 --- a/apps/code/src/renderer/features/sessions/components/session-update/CodePreview.tsx +++ b/apps/code/src/renderer/features/sessions/components/session-update/CodePreview.tsx @@ -1,9 +1,10 @@ -import { unifiedMergeView } from "@codemirror/merge"; -import type { Extension } from "@codemirror/state"; import { EditorView } from "@codemirror/view"; +import { MultiFileDiff, WorkerPoolContextProvider } from "@pierre/diffs/react"; +import WorkerUrl from "@pierre/diffs/worker/worker.js?worker&url"; import { Code } from "@radix-ui/themes"; +import { useThemeStore } from "@stores/themeStore"; import { compactHomePath } from "@utils/path"; -import { useEffect, useRef } from "react"; +import { useEffect, useMemo, useRef } from "react"; import { CODE_PREVIEW_CONTAINER_STYLE, CODE_PREVIEW_EDITOR_STYLE, @@ -11,6 +12,10 @@ import { useCodePreviewExtensions, } from "./useCodePreviewExtensions"; +function workerFactory(): Worker { + return new Worker(WorkerUrl, { type: "module" }); +} + interface CodePreviewProps { content: string; filePath?: string; @@ -28,35 +33,118 @@ export function CodePreview({ firstLineNumber = 1, maxHeight, }: CodePreviewProps) { - const containerRef = useRef(null); - const editorRef = useRef(null); const isDiff = oldContent !== undefined && oldContent !== null; - const extensions = useCodePreviewExtensions( - filePath, - isDiff, - firstLineNumber, + + if (isDiff) { + return ( + + ); + } + + return ( + + ); +} + +function DiffPreview({ + content, + filePath, + showPath, + oldContent, + maxHeight, +}: { + content: string; + filePath?: string; + showPath?: boolean; + oldContent: string; + maxHeight?: string; +}) { + const isDarkMode = useThemeStore((s) => s.isDarkMode); + const fileName = filePath?.split("/").pop() ?? "file"; + + const oldFile = useMemo( + () => ({ name: fileName, contents: oldContent }), + [fileName, oldContent], + ); + const newFile = useMemo( + () => ({ name: fileName, contents: content }), + [fileName, content], + ); + const options = useMemo( + () => ({ + diffStyle: "unified" as const, + overflow: "wrap" as const, + themeType: (isDarkMode ? "dark" : "light") as "dark" | "light", + theme: { dark: "github-dark" as const, light: "github-light" as const }, + disableFileHeader: true, + }), + [isDarkMode], + ); + + return ( +
+ {showPath && filePath && ( +
+ + {compactHomePath(filePath)} + +
+ )} +
+ + + +
+
); +} + +function PlainCodePreview({ + content, + filePath, + showPath, + firstLineNumber, + maxHeight, +}: { + content: string; + filePath?: string; + showPath?: boolean; + firstLineNumber: number; + maxHeight?: string; +}) { + const containerRef = useRef(null); + const editorRef = useRef(null); + const extensions = useCodePreviewExtensions(filePath, firstLineNumber); useEffect(() => { if (!containerRef.current) return; editorRef.current?.destroy(); - const diffExtension: Extension[] = isDiff - ? [ - unifiedMergeView({ - original: oldContent, - highlightChanges: false, - gutter: false, - mergeControls: false, - collapseUnchanged: { margin: 3, minSize: 4 }, - }), - ] - : []; - editorRef.current = new EditorView({ doc: content, - extensions: [...extensions, ...diffExtension], + extensions, parent: containerRef.current, }); @@ -64,7 +152,7 @@ export function CodePreview({ editorRef.current?.destroy(); editorRef.current = null; }; - }, [content, oldContent, extensions, isDiff]); + }, [content, extensions]); return (
diff --git a/apps/code/src/renderer/features/sessions/components/session-update/useCodePreviewExtensions.ts b/apps/code/src/renderer/features/sessions/components/session-update/useCodePreviewExtensions.ts index f78b9745d..81aa20bba 100644 --- a/apps/code/src/renderer/features/sessions/components/session-update/useCodePreviewExtensions.ts +++ b/apps/code/src/renderer/features/sessions/components/session-update/useCodePreviewExtensions.ts @@ -1,17 +1,12 @@ import { EditorState } from "@codemirror/state"; import { EditorView, lineNumbers } from "@codemirror/view"; -import { - mergeViewTheme, - oneDark, - oneLight, -} from "@features/code-editor/theme/editorTheme"; +import { oneDark, oneLight } from "@features/code-editor/theme/editorTheme"; import { getLanguageExtension } from "@features/code-editor/utils/languages"; import { useThemeStore } from "@stores/themeStore"; import { useMemo } from "react"; export function useCodePreviewExtensions( filePath: string | undefined, - includeMergeTheme = false, firstLineNumber = 1, ) { const isDarkMode = useThemeStore((state) => state.isDarkMode); @@ -25,7 +20,6 @@ export function useCodePreviewExtensions( return [ theme, - ...(includeMergeTheme ? [mergeViewTheme] : []), lineNumbers({ formatNumber: (n) => String(n + firstLineNumber - 1) }), EditorView.editable.of(false), EditorState.readOnly.of(true), @@ -33,7 +27,7 @@ export function useCodePreviewExtensions( ...(languageExtension ? [languageExtension] : []), compactPadding, ]; - }, [filePath, isDarkMode, includeMergeTheme, firstLineNumber]); + }, [filePath, isDarkMode, firstLineNumber]); } export const CODE_PREVIEW_CONTAINER_STYLE: React.CSSProperties = { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 24e62d15f..214b4c5b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -103,9 +103,6 @@ importers: '@codemirror/language': specifier: ^6.12.2 version: 6.12.2 - '@codemirror/merge': - specifier: ^6.12.0 - version: 6.12.0 '@codemirror/search': specifier: ^6.6.0 version: 6.6.0 @@ -1469,9 +1466,6 @@ packages: '@codemirror/lint@6.9.3': resolution: {integrity: sha512-y3YkYhdnhjDBAe0VIA0c4wVoFOvnp8CnAvfLqi0TqotIv92wIlAAP7HELOpLBsKwjAX6W92rSflA6an/2zBvXw==} - '@codemirror/merge@6.12.0': - resolution: {integrity: sha512-o+36bbapcEHf4Ux75pZ4CKjMBUd14parA0uozvWVlacaT+uxaA3DDefEvWYjngsKU+qsrDe/HOOfsw0Q72pLjA==} - '@codemirror/search@6.6.0': resolution: {integrity: sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==} @@ -12039,14 +12033,6 @@ snapshots: '@codemirror/view': 6.39.17 crelt: 1.0.6 - '@codemirror/merge@6.12.0': - dependencies: - '@codemirror/language': 6.12.2 - '@codemirror/state': 6.5.4 - '@codemirror/view': 6.39.17 - '@lezer/highlight': 1.2.3 - style-mod: 4.1.3 - '@codemirror/search@6.6.0': dependencies: '@codemirror/state': 6.5.4