Skip to content

Commit cbfbbee

Browse files
update
1 parent 2965fb3 commit cbfbbee

File tree

5 files changed

+286
-4
lines changed

5 files changed

+286
-4
lines changed

ui/react-example/src/components/MainContent/MainContentDiffAdvance.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,24 @@ const transform = (str: string) =>
1616
.replace(/\t/g, '<span class="diff-symbol diff-symbol-tab">\t</span>');
1717

1818
export const MainContentDiffAdvance = () => {
19-
const { tabSpace, setFastDiff, setTabSpace, fastDiff, shadowDOM, setShadowDOM } = useDiffConfig((s) => ({
19+
const {
20+
tabSpace,
21+
setFastDiff,
22+
setTabSpace,
23+
fastDiff,
24+
shadowDOM,
25+
setShadowDOM,
26+
autoExpandCommentLine,
27+
setAutoExpandCommentLine,
28+
} = useDiffConfig((s) => ({
2029
tabSpace: s.tabSpace,
2130
setTabSpace: s.setTabSpace,
2231
fastDiff: s.fastDiff,
2332
setFastDiff: s.setFastDiff,
2433
shadowDOM: s.shadowDOM,
2534
setShadowDOM: s.setShadowDOM,
35+
autoExpandCommentLine: s.autoExpandCommentLine,
36+
setAutoExpandCommentLine: s.setAutoExpandCommentLine,
2637
}));
2738

2839
useEffect(() => {
@@ -57,6 +68,9 @@ export const MainContentDiffAdvance = () => {
5768
{fastDiff ? "disable fast diff template" : "enable fast diff template"}
5869
</Button>
5970
<Button onClick={() => setShadowDOM(!shadowDOM)}>{shadowDOM ? "disable shadow DOM" : "enable shadow DOM"}</Button>
71+
<Button onClick={() => setAutoExpandCommentLine(!autoExpandCommentLine)}>
72+
{autoExpandCommentLine ? "disable auto expand comment line" : "enable auto expand comment line"}
73+
</Button>
6074
</Group>
6175
);
6276
};

ui/react-example/src/components/MainContent/MainContentDiffExample.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,39 @@ import { useCallbackRef, useDisclosure, useMounted } from "@mantine/hooks";
44
import { IconCode, IconPlayerPlay, IconRefresh, IconBrandReact, IconBrandVue } from "@tabler/icons-react";
55
import { useState, startTransition, useCallback, forwardRef, useMemo, useEffect } from "react";
66

7+
import { useDiffConfig } from "../../hooks/useDiffConfig";
78
import { useDiffHighlighter } from "../../hooks/useDiffHighlighter";
9+
import { generateDynamicHunks, parseHunk } from "../../utils/hunk";
810

911
import { MainContentDiffExampleCode } from "./MainContentDiffExampleCode";
1012
import { temp1, temp2 } from "./MainContentDiffExampleData";
1113
import { MainContentDiffExampleViewWrapper } from "./MainContentDiffExampleViewWrapper";
1214

15+
import type { DiffViewProps } from "@git-diff-view/react";
16+
1317
const _diffFile = generateDiffFile("temp1.tsx", temp1, "temp2.tsx", temp2, "tsx", "tsx");
1418

19+
export const defaultComment: DiffViewProps<string[]>["extendData"] = {
20+
oldFile: {
21+
2: { data: ['console.log("hello world");'] },
22+
},
23+
newFile: {},
24+
};
25+
1526
const getNewDiffFile = () => {
27+
const isEnableAutoExpandCommentLine = useDiffConfig.getReadonlyState().autoExpandCommentLine;
28+
1629
const instance = DiffFile.createInstance({
1730
oldFile: { content: temp1, fileName: "temp1.tsx" },
1831
newFile: { content: temp2, fileName: "temp2.tsx" },
19-
hunks: _diffFile._diffList,
32+
hunks: isEnableAutoExpandCommentLine
33+
? generateDynamicHunks({
34+
hunks: parseHunk(_diffFile._diffList[0]),
35+
comments: defaultComment,
36+
oldFile: temp1,
37+
newFile: temp2,
38+
}).map((i) => i.patchContent)
39+
: _diffFile._diffList,
2040
});
2141
instance.initRaw();
2242
return instance;

ui/react-example/src/components/MainContent/MainContentDiffExampleView.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { useDiffConfig } from "../../hooks/useDiffConfig";
77
import { DiffViewWithScrollBar } from "../DiffViewWithScrollBar";
88
import { Textarea } from "../TextArea";
99

10+
import { defaultComment } from "./MainContentDiffExample";
11+
1012
import type { DiffFile, DiffViewProps } from "@git-diff-view/react";
1113

1214
// disable diffFile Cache
@@ -27,18 +29,20 @@ export const MainContentDiffExampleView = memo(
2729
const [str, setStr] = useState("");
2830

2931
const [extend, setExtend] = useState<DiffViewProps<string[]>["extendData"]>({
30-
oldFile: {},
32+
oldFile: { 2: { data: ['console.log("hello world");'] } },
3133
newFile: {},
3234
});
3335

34-
const { highlight, mode, wrap, engine, tabSpace, fastDiff } = useDiffConfig();
36+
const { highlight, mode, wrap, engine, tabSpace, fastDiff, autoExpandCommentLine } = useDiffConfig();
3537

3638
const prevEngine = usePrevious(engine);
3739

3840
const prevTabSpace = usePrevious(tabSpace);
3941

4042
const prevFastDiff = usePrevious(fastDiff);
4143

44+
const prevAutoExpandCommentLine = usePrevious(autoExpandCommentLine);
45+
4246
// because of the cache, switch the highlighter engine will not work, need a new diffFile instance to avoid this
4347
// see packages/core/src/file.ts:172 getFile
4448
// TODO fix this in the future
@@ -48,6 +52,17 @@ export const MainContentDiffExampleView = memo(
4852
}
4953
}, [engine, prevEngine, tabSpace, prevTabSpace, fastDiff, prevFastDiff, refreshDiffFile]);
5054

55+
useEffect(() => {
56+
if (autoExpandCommentLine !== prevAutoExpandCommentLine) {
57+
if (autoExpandCommentLine) {
58+
setExtend(defaultComment);
59+
refreshDiffFile();
60+
} else {
61+
refreshDiffFile();
62+
}
63+
}
64+
}, [autoExpandCommentLine, prevAutoExpandCommentLine]);
65+
5166
return (
5267
<Box className="h-full overflow-auto">
5368
<DiffViewWithScrollBar

ui/react-example/src/hooks/useDiffConfig.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ type DiffConfig = {
1010
tabSpace: boolean;
1111
fastDiff: boolean;
1212
shadowDOM: boolean;
13+
autoExpandCommentLine: boolean;
1314
};
1415

1516
export const useDiffConfig = createState(
@@ -23,6 +24,7 @@ export const useDiffConfig = createState(
2324
tabSpace: false,
2425
fastDiff: false,
2526
shadowDOM: false,
27+
autoExpandCommentLine: false,
2628
}) as DiffConfig,
2729
{
2830
withActions(state) {
@@ -67,6 +69,11 @@ export const useDiffConfig = createState(
6769
state.shadowDOM = v;
6870
}
6971
},
72+
setAutoExpandCommentLine: (v: boolean) => {
73+
if (v !== state.autoExpandCommentLine) {
74+
state.autoExpandCommentLine = v;
75+
}
76+
},
7077
};
7178
},
7279
withNamespace: "diffConfig",

ui/react-example/src/utils/hunk.ts

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
import findLastIndex from "lodash/findLastIndex";
2+
import sortBy from "lodash/sortBy";
3+
4+
import type { DiffViewProps } from "@git-diff-view/react";
5+
6+
type HunkItem = {
7+
oldStartLine: number;
8+
oldLineCount: number;
9+
newStartLine: number;
10+
newLineCount: number;
11+
patchContent: string;
12+
};
13+
14+
const checkHasExpand = (hunks: HunkItem[], line: number, side: "L" | "R") => {
15+
return hunks.some((hunk) => {
16+
if (side === "L") {
17+
return hunk.oldStartLine <= line && line <= hunk.oldStartLine + hunk.oldLineCount - 1;
18+
}
19+
if (side === "R") {
20+
return hunk.newStartLine <= line && line <= hunk.newStartLine + hunk.newLineCount - 1;
21+
}
22+
return false;
23+
});
24+
};
25+
26+
const getHunk = (hunks: HunkItem[], line: number, side: "L" | "R") => {
27+
// 1 表示取后一个hunk,2表示取前一个hunk
28+
let type: 1 | 2 = 1;
29+
// 默认取后一个hunk,计算更简单
30+
let hunkIndex = hunks.findIndex((hunk) => {
31+
if (side === "L") {
32+
return hunk.oldStartLine > line;
33+
}
34+
if (side === "R") {
35+
return hunk.newStartLine > line;
36+
}
37+
return false;
38+
});
39+
// 如果后一个不存在,取前一个hunk
40+
if (hunkIndex === -1) {
41+
type = 2;
42+
43+
hunkIndex = findLastIndex(hunks, (hunk) => {
44+
if (side === "L") {
45+
return hunk.oldStartLine + hunk.oldLineCount < line;
46+
}
47+
if (side === "R") {
48+
return hunk.newStartLine + hunk.newLineCount < line;
49+
}
50+
return false;
51+
});
52+
}
53+
54+
return { hunkIndex, type };
55+
};
56+
57+
const insertHunk = (hunks: HunkItem[], line: number, side: "L" | "R", hunkIndex: number, type: 1 | 2) => {
58+
if (hunkIndex === -1) return { newHunks: hunks, hunk: null };
59+
60+
const existHunk = hunks[hunkIndex];
61+
62+
let hunk: HunkItem | null = null;
63+
64+
// 根据旧行号得出新行号
65+
if (side === "L") {
66+
const newLineNumber =
67+
type === 1
68+
? existHunk.newStartLine - (existHunk.oldStartLine - line)
69+
: existHunk.newStartLine + existHunk.newLineCount + (line - existHunk.oldStartLine - existHunk.oldLineCount);
70+
hunk = {
71+
oldStartLine: line,
72+
oldLineCount: 1,
73+
newStartLine: newLineNumber,
74+
newLineCount: 1,
75+
patchContent: "",
76+
};
77+
} else {
78+
const oldLineNumber =
79+
type === 1
80+
? existHunk.oldStartLine - (existHunk.newStartLine - line)
81+
: existHunk.oldStartLine + existHunk.oldLineCount + (line - existHunk.newStartLine - existHunk.newLineCount);
82+
hunk = {
83+
oldStartLine: oldLineNumber,
84+
oldLineCount: 1,
85+
newStartLine: line,
86+
newLineCount: 1,
87+
patchContent: "",
88+
};
89+
}
90+
91+
let newHunks = Array.from(hunks);
92+
93+
if (hunk) {
94+
if (type === 1) {
95+
// 插入到后面
96+
newHunks.splice(hunkIndex + 1, 0, hunk);
97+
} else {
98+
// 插入到前面
99+
newHunks.splice(hunkIndex, 0, hunk);
100+
}
101+
}
102+
103+
newHunks = sortBy(newHunks, (hunk) => hunk.oldStartLine);
104+
105+
return { newHunks, hunk };
106+
};
107+
108+
const generatePatchContent = (hunk: HunkItem, oldFile?: string, newFile?: string) => {
109+
const oldLine = hunk.oldStartLine;
110+
const newLine = hunk.newStartLine;
111+
const oldLineContent = (oldFile || "")
112+
.split("\n")
113+
.slice(oldLine - 1, oldLine + hunk.oldLineCount - 1)
114+
.join("\n");
115+
const newLineContent = (newFile || "")
116+
.split("\n")
117+
.slice(newLine - 1, newLine + hunk.newLineCount - 1)
118+
.join("\n");
119+
// 动态生成的hunk有误
120+
// TODO 给出提示
121+
if (oldLineContent !== newLineContent) {
122+
return false;
123+
}
124+
// 确保是正确的hunk格式
125+
const patchContent = `--- a.txt
126+
+++ b.txt
127+
@@ -${oldLine},${hunk.oldLineCount} +${newLine},${hunk.newLineCount} @@
128+
${oldLineContent}
129+
`;
130+
hunk.patchContent = patchContent;
131+
return true;
132+
};
133+
134+
export const parseHunk = (patchContent: string) => {
135+
const res: HunkItem[] = [];
136+
const lines = patchContent.split("\n");
137+
let str = "";
138+
let header = "";
139+
let headerLine = "";
140+
lines.forEach((line, index) => {
141+
if (line.startsWith("@@")) {
142+
if (str) {
143+
const headerMatch = headerLine.match(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/);
144+
res.push({
145+
oldStartLine: headerMatch ? Number(headerMatch[1]) : 0,
146+
oldLineCount: headerMatch ? Number(headerMatch[2]) : 0,
147+
newStartLine: headerMatch ? Number(headerMatch[3]) : 0,
148+
newLineCount: headerMatch ? Number(headerMatch[4]) : 0,
149+
patchContent: header + str,
150+
});
151+
}
152+
headerLine = line;
153+
str = "";
154+
}
155+
if (index < lines.length - 1) {
156+
str += `${line}\n`;
157+
} else {
158+
str += line;
159+
}
160+
if (line.startsWith("+++")) {
161+
header = str;
162+
str = "";
163+
}
164+
});
165+
if (str) {
166+
const headerMatch = headerLine.match(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/);
167+
res.push({
168+
oldStartLine: headerMatch ? Number(headerMatch[1]) : 0,
169+
oldLineCount: headerMatch ? Number(headerMatch[2]) : 0,
170+
newStartLine: headerMatch ? Number(headerMatch[3]) : 0,
171+
newLineCount: headerMatch ? Number(headerMatch[4]) : 0,
172+
patchContent: header + str,
173+
});
174+
}
175+
return res;
176+
};
177+
178+
export const generateDynamicHunks = ({
179+
hunks,
180+
comments,
181+
oldFile,
182+
newFile,
183+
}: {
184+
hunks: HunkItem[];
185+
comments: DiffViewProps<string[]>["extendData"];
186+
oldFile?: string;
187+
newFile?: string;
188+
}) => {
189+
const allNeedExpandLineNumbers: { old: number[]; new: number[] } = { old: [], new: [] };
190+
191+
Object.keys(comments?.oldFile || {}).forEach((lineNumber) => {
192+
if (comments?.oldFile?.[lineNumber]?.data?.length) {
193+
allNeedExpandLineNumbers.old.push(Number(lineNumber));
194+
}
195+
});
196+
197+
Object.keys(comments?.newFile || {}).forEach((lineNumber) => {
198+
if (comments?.newFile?.[lineNumber]?.data?.length) {
199+
allNeedExpandLineNumbers.new.push(Number(lineNumber));
200+
}
201+
});
202+
203+
allNeedExpandLineNumbers.new.forEach((line) => {
204+
// check current line has expand
205+
if (!checkHasExpand(hunks, line, "R")) {
206+
const { type, hunkIndex } = getHunk(hunks, line, "R");
207+
const { newHunks, hunk } = insertHunk(hunks, line, "R", hunkIndex, type);
208+
if (hunk && generatePatchContent(hunk, oldFile, newFile)) {
209+
hunks = newHunks;
210+
}
211+
}
212+
});
213+
214+
allNeedExpandLineNumbers.old.forEach((line) => {
215+
// check current line has expand
216+
if (!checkHasExpand(hunks, line, "L")) {
217+
const { type, hunkIndex } = getHunk(hunks, line, "L");
218+
const { newHunks, hunk } = insertHunk(hunks, line, "L", hunkIndex, type);
219+
if (hunk && generatePatchContent(hunk, oldFile, newFile)) {
220+
hunks = newHunks;
221+
}
222+
}
223+
});
224+
225+
return sortBy(hunks, (hunk) => hunk.oldStartLine);
226+
};

0 commit comments

Comments
 (0)