Skip to content

Commit a5f24d5

Browse files
committed
code mirror cleanup
1 parent 37a0e4a commit a5f24d5

File tree

4 files changed

+117
-137
lines changed

4 files changed

+117
-137
lines changed

apps/array/src/renderer/features/code-editor/components/CodeMirrorDiffEditor.tsx

Lines changed: 15 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { MergeView, unifiedMergeView } from "@codemirror/merge";
2-
import { EditorView } from "@codemirror/view";
31
import { Box, Flex, SegmentedControl } from "@radix-ui/themes";
4-
import { useEffect, useRef, useState } from "react";
2+
import { useMemo, useState } from "react";
3+
import { useCodeMirror } from "../hooks/useCodeMirror";
54
import { useEditorExtensions } from "../hooks/useEditorExtensions";
65

76
type ViewMode = "split" | "unified";
@@ -17,65 +16,25 @@ export function CodeMirrorDiffEditor({
1716
modifiedContent,
1817
filePath,
1918
}: CodeMirrorDiffEditorProps) {
20-
const editorRef = useRef<HTMLDivElement>(null);
21-
const mergeViewRef = useRef<MergeView | null>(null);
22-
const editorViewRef = useRef<EditorView | null>(null);
2319
const [viewMode, setViewMode] = useState<ViewMode>("split");
24-
const baseExtensions = useEditorExtensions(filePath, true);
25-
26-
useEffect(() => {
27-
if (!editorRef.current) return;
28-
29-
mergeViewRef.current?.destroy();
30-
editorViewRef.current?.destroy();
31-
mergeViewRef.current = null;
32-
editorViewRef.current = null;
33-
34-
if (viewMode === "split") {
35-
mergeViewRef.current = new MergeView({
36-
a: {
37-
doc: originalContent,
38-
extensions: baseExtensions,
39-
},
40-
b: {
41-
doc: modifiedContent,
42-
extensions: baseExtensions,
43-
},
44-
parent: editorRef.current,
45-
});
46-
} else {
47-
editorViewRef.current = new EditorView({
48-
doc: modifiedContent,
49-
extensions: [
50-
...baseExtensions,
51-
unifiedMergeView({
52-
original: originalContent,
53-
highlightChanges: true,
54-
gutter: true,
55-
mergeControls: false,
56-
}),
57-
],
58-
parent: editorRef.current,
59-
});
60-
}
61-
62-
return () => {
63-
mergeViewRef.current?.destroy();
64-
editorViewRef.current?.destroy();
65-
mergeViewRef.current = null;
66-
editorViewRef.current = null;
67-
};
68-
}, [originalContent, modifiedContent, baseExtensions, viewMode]);
20+
const extensions = useEditorExtensions(filePath, true);
21+
const options = useMemo(
22+
() => ({
23+
original: originalContent,
24+
modified: modifiedContent,
25+
extensions,
26+
mode: viewMode,
27+
}),
28+
[originalContent, modifiedContent, extensions, viewMode],
29+
);
30+
const containerRef = useCodeMirror(options);
6931

7032
return (
7133
<Flex direction="column" height="100%">
7234
<Box
7335
px="3"
7436
py="2"
75-
style={{
76-
borderBottom: "1px solid var(--gray-6)",
77-
flexShrink: 0,
78-
}}
37+
style={{ borderBottom: "1px solid var(--gray-6)", flexShrink: 0 }}
7938
>
8039
<SegmentedControl.Root
8140
size="1"
@@ -87,7 +46,7 @@ export function CodeMirrorDiffEditor({
8746
</SegmentedControl.Root>
8847
</Box>
8948
<Box style={{ flex: 1, overflow: "auto" }}>
90-
<div ref={editorRef} style={{ height: "100%", width: "100%" }} />
49+
<div ref={containerRef} style={{ height: "100%", width: "100%" }} />
9150
</Box>
9251
</Flex>
9352
);
Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { EditorState } from "@codemirror/state";
2-
import { EditorView } from "@codemirror/view";
3-
import { useEffect, useRef } from "react";
1+
import { useMemo } from "react";
2+
import { useCodeMirror } from "../hooks/useCodeMirror";
43
import { useEditorExtensions } from "../hooks/useEditorExtensions";
54

65
interface CodeMirrorEditorProps {
@@ -14,28 +13,12 @@ export function CodeMirrorEditor({
1413
filePath,
1514
readOnly = false,
1615
}: CodeMirrorEditorProps) {
17-
const editorRef = useRef<HTMLDivElement>(null);
18-
const viewRef = useRef<EditorView | null>(null);
1916
const extensions = useEditorExtensions(filePath, readOnly);
17+
const options = useMemo(
18+
() => ({ doc: content, extensions }),
19+
[content, extensions],
20+
);
21+
const containerRef = useCodeMirror(options);
2022

21-
useEffect(() => {
22-
if (!editorRef.current) return;
23-
24-
const state = EditorState.create({
25-
doc: content,
26-
extensions,
27-
});
28-
29-
viewRef.current = new EditorView({
30-
state,
31-
parent: editorRef.current,
32-
});
33-
34-
return () => {
35-
viewRef.current?.destroy();
36-
viewRef.current = null;
37-
};
38-
}, [content, extensions]);
39-
40-
return <div ref={editorRef} style={{ height: "100%", width: "100%" }} />;
23+
return <div ref={containerRef} style={{ height: "100%", width: "100%" }} />;
4124
}

apps/array/src/renderer/features/code-editor/components/DiffEditorPanel.tsx

Lines changed: 25 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { CodeMirrorDiffEditor } from "@features/code-editor/components/CodeMirro
33
import { CodeMirrorEditor } from "@features/code-editor/components/CodeMirrorEditor";
44
import { useTaskData } from "@features/task-detail/hooks/useTaskData";
55
import { Box } from "@radix-ui/themes";
6-
import type { ChangedFile, Task } from "@shared/types";
6+
import type { Task } from "@shared/types";
77
import { useQuery } from "@tanstack/react-query";
88

99
interface DiffEditorPanelProps {
@@ -17,94 +17,63 @@ export function DiffEditorPanel({
1717
task,
1818
filePath,
1919
}: DiffEditorPanelProps) {
20-
const taskData = useTaskData({ taskId, initialTask: task });
21-
const repoPath = taskData.repoPath;
20+
const { repoPath } = useTaskData({ taskId, initialTask: task });
2221

23-
// Fetch changed files to get status information
2422
const { data: changedFiles = [] } = useQuery({
2523
queryKey: ["changed-files-head", repoPath],
24+
queryFn: () => window.electronAPI.getChangedFilesHead(repoPath!),
2625
enabled: !!repoPath,
2726
staleTime: Infinity,
28-
queryFn: async () => {
29-
if (!window.electronAPI || !repoPath) return [];
30-
return window.electronAPI.getChangedFilesHead(repoPath);
31-
},
3227
});
3328

34-
// Find the file info to get status and originalPath (for renames)
35-
const fileInfo = changedFiles.find((f: ChangedFile) => f.path === filePath);
29+
const fileInfo = changedFiles.find((f) => f.path === filePath);
3630
const status = fileInfo?.status ?? "modified";
3731
const originalPath = fileInfo?.originalPath ?? filePath;
32+
const isDeleted = status === "deleted";
33+
const isNew = status === "untracked" || status === "added";
3834

39-
// Determine what to fetch based on status
40-
const skipModified = status === "deleted";
41-
const skipOriginal = status === "untracked" || status === "added";
42-
43-
// Fetch modified content (current working directory)
44-
const { data: modifiedContent, isLoading: isLoadingModified } = useQuery({
35+
const { data: modifiedContent, isLoading: loadingModified } = useQuery({
4536
queryKey: ["repo-file", repoPath, filePath],
46-
enabled: !!repoPath && !!filePath && !skipModified,
37+
queryFn: () => window.electronAPI.readRepoFile(repoPath!, filePath),
38+
enabled: !!repoPath && !isDeleted,
4739
staleTime: Infinity,
48-
queryFn: async () => {
49-
if (!window.electronAPI || !repoPath || !filePath) return null;
50-
return window.electronAPI.readRepoFile(repoPath, filePath);
51-
},
5240
});
5341

54-
// Fetch original content (HEAD) - use originalPath for renames
55-
const { data: originalContent, isLoading: isLoadingOriginal } = useQuery({
42+
const { data: originalContent, isLoading: loadingOriginal } = useQuery({
5643
queryKey: ["file-at-head", repoPath, originalPath],
57-
enabled: !!repoPath && !!originalPath && !skipOriginal,
44+
queryFn: () => window.electronAPI.getFileAtHead(repoPath!, originalPath),
45+
enabled: !!repoPath && !isNew,
5846
staleTime: Infinity,
59-
queryFn: async () => {
60-
if (!window.electronAPI || !repoPath || !originalPath) return null;
61-
return window.electronAPI.getFileAtHead(repoPath, originalPath);
62-
},
6347
});
6448

6549
if (!repoPath) {
6650
return <PanelMessage>No repository path available</PanelMessage>;
6751
}
6852

6953
const isLoading =
70-
(!skipModified && isLoadingModified) ||
71-
(!skipOriginal && isLoadingOriginal);
72-
54+
(!isDeleted && loadingModified) || (!isNew && loadingOriginal);
7355
if (isLoading) {
7456
return <PanelMessage>Loading diff...</PanelMessage>;
7557
}
7658

77-
if (skipModified) {
78-
return (
79-
<Box height="100%" style={{ overflow: "hidden" }}>
80-
<CodeMirrorEditor
81-
content={originalContent ?? ""}
59+
const showDiff = !isDeleted && !isNew;
60+
const content = isDeleted ? originalContent : modifiedContent;
61+
62+
return (
63+
<Box height="100%" style={{ overflow: "hidden" }}>
64+
{showDiff ? (
65+
<CodeMirrorDiffEditor
66+
originalContent={originalContent ?? ""}
67+
modifiedContent={modifiedContent ?? ""}
8268
filePath={filePath}
83-
readOnly
8469
/>
85-
</Box>
86-
);
87-
}
88-
89-
if (skipOriginal) {
90-
return (
91-
<Box height="100%" style={{ overflow: "hidden" }}>
70+
) : (
9271
<CodeMirrorEditor
93-
content={modifiedContent ?? ""}
72+
content={content ?? ""}
9473
filePath={filePath}
9574
readOnly
9675
/>
97-
</Box>
98-
);
99-
}
100-
101-
return (
102-
<Box height="100%" style={{ overflow: "hidden" }}>
103-
<CodeMirrorDiffEditor
104-
originalContent={originalContent ?? ""}
105-
modifiedContent={modifiedContent ?? ""}
106-
filePath={filePath}
107-
/>
76+
)}
10877
</Box>
10978
);
11079
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { MergeView, unifiedMergeView } from "@codemirror/merge";
2+
import { EditorState, type Extension } from "@codemirror/state";
3+
import { EditorView } from "@codemirror/view";
4+
import { useEffect, useRef } from "react";
5+
6+
type EditorInstance = EditorView | MergeView;
7+
8+
interface UseCodeMirrorOptions {
9+
extensions: Extension[];
10+
}
11+
12+
interface SingleDocOptions extends UseCodeMirrorOptions {
13+
doc: string;
14+
}
15+
16+
interface DiffOptions extends UseCodeMirrorOptions {
17+
original: string;
18+
modified: string;
19+
mode: "split" | "unified";
20+
}
21+
22+
export function useCodeMirror(options: SingleDocOptions | DiffOptions) {
23+
const containerRef = useRef<HTMLDivElement>(null);
24+
const instanceRef = useRef<EditorInstance | null>(null);
25+
26+
useEffect(() => {
27+
if (!containerRef.current) return;
28+
29+
instanceRef.current?.destroy();
30+
instanceRef.current = null;
31+
32+
if ("doc" in options) {
33+
instanceRef.current = new EditorView({
34+
state: EditorState.create({
35+
doc: options.doc,
36+
extensions: options.extensions,
37+
}),
38+
parent: containerRef.current,
39+
});
40+
} else if (options.mode === "split") {
41+
instanceRef.current = new MergeView({
42+
a: { doc: options.original, extensions: options.extensions },
43+
b: { doc: options.modified, extensions: options.extensions },
44+
parent: containerRef.current,
45+
});
46+
} else {
47+
instanceRef.current = new EditorView({
48+
doc: options.modified,
49+
extensions: [
50+
...options.extensions,
51+
unifiedMergeView({
52+
original: options.original,
53+
highlightChanges: true,
54+
gutter: true,
55+
mergeControls: false,
56+
}),
57+
],
58+
parent: containerRef.current,
59+
});
60+
}
61+
62+
return () => {
63+
instanceRef.current?.destroy();
64+
instanceRef.current = null;
65+
};
66+
}, [options]);
67+
68+
return containerRef;
69+
}

0 commit comments

Comments
 (0)