@yuhufe/wtool-vdiff 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +140 -41
  2. package/dist/DiffFiles/DiffFiles.vue.d.ts +12 -0
  3. package/dist/DiffFiles/DiffList/DiffList.vue.d.ts +94 -0
  4. package/dist/DiffFiles/FileExplore/FileExplore.vue.d.ts +510 -0
  5. package/dist/DiffFiles/FileExplore/FileSearch.vue.d.ts +11 -0
  6. package/dist/DiffFiles/FileExplore/fileTree.d.ts +17 -0
  7. package/dist/DiffFiles/createDiffFiles.d.ts +13 -0
  8. package/dist/DiffFiles/index.d.ts +3 -0
  9. package/dist/DiffFiles/types.d.ts +12 -0
  10. package/dist/DiffFiles/useDiffFiles.d.ts +23 -0
  11. package/dist/DiffViewer/DiffViewer.vue.d.ts +15 -4
  12. package/dist/DiffViewer/MonacoDiffViewer.vue.d.ts +1 -1
  13. package/dist/DiffViewer/TopBar.vue.d.ts +2 -0
  14. package/dist/DiffViewer/const.d.ts +8 -0
  15. package/dist/DiffViewer/utils/autoHeight.d.ts +4 -1
  16. package/dist/DiffViewer/utils/patch2Pair.d.ts +9 -14
  17. package/dist/index.d.ts +1 -0
  18. package/dist/types.d.ts +16 -0
  19. package/dist/utils/patch.d.ts +20 -0
  20. package/dist/wtool-vdiff.cjs.js +45 -36
  21. package/dist/wtool-vdiff.es.js +9535 -5984
  22. package/package.json +10 -9
  23. package/src/DiffFiles/DiffFiles.vue +126 -0
  24. package/src/DiffFiles/DiffList/DiffList.vue +125 -0
  25. package/src/DiffFiles/FileExplore/FileExplore.vue +181 -0
  26. package/src/DiffFiles/FileExplore/FileSearch.vue +81 -0
  27. package/src/DiffFiles/FileExplore/fileTree.ts +102 -0
  28. package/src/DiffFiles/createDiffFiles.ts +70 -0
  29. package/src/DiffFiles/index.ts +11 -0
  30. package/src/DiffFiles/types.ts +15 -0
  31. package/src/DiffFiles/useDiffFiles.ts +56 -0
  32. package/src/DiffViewer/DiffViewer.vue +28 -7
  33. package/src/DiffViewer/MonacoDiffViewer.vue +21 -2
  34. package/src/DiffViewer/TopBar.vue +25 -6
  35. package/src/DiffViewer/const.ts +11 -0
  36. package/src/DiffViewer/utils/autoHeight.ts +159 -43
  37. package/src/DiffViewer/utils/patch2Pair.ts +37 -56
  38. package/src/assets/file.svg +4 -0
  39. package/src/assets/folder.svg +4 -0
  40. package/src/index.ts +1 -0
  41. package/src/types.ts +19 -0
  42. package/src/utils/patch.ts +74 -0
@@ -1,14 +1,75 @@
1
+ import { diffLines } from 'diff'
1
2
  import type { WtoolDiffViewerProps } from '@/types'
2
- import { parseHunks } from './patch2Pair'
3
+ import { HEIGHT_CODE_LINE, HEIGHT_HIDDEN_REGION, HEIGHT_HORIZONTAL_SCROLLBAR } from '../const'
4
+ import { patch2PairWithLayout } from './patch2Pair'
3
5
 
4
- const LINE_HEIGHT = 18
5
- const GAP_HEIGHT = 24
6
+ interface HeightCache {
7
+ patch?: string
8
+ origContent?: string | null
9
+ modContent?: string | null
10
+ minPx: number
11
+ maxPx: number
12
+ unchangedCtxLineNum: number
13
+ unchangedMinLineNum: number
14
+ heights: Partial<Record<'visible' | 'hidden', number>>
15
+ }
16
+
17
+ const heightMap = new Map<string, HeightCache>()
6
18
 
7
19
  interface CommonParams {
8
20
  minPx: number
9
21
  maxPx: number
10
22
  unchangedVisiable: boolean
11
23
  unchangedCtxLineNum: number
24
+ unchangedMinLineNum: number
25
+ }
26
+
27
+ interface ChangedLineBlock {
28
+ start: number
29
+ end: number
30
+ }
31
+
32
+ interface ChangedLineInfo {
33
+ blocks: ChangedLineBlock[]
34
+ totalLines: number
35
+ }
36
+
37
+ function getChangedLineInfo(origContent: string, modContent: string): ChangedLineInfo {
38
+ const changes = diffLines(origContent, modContent)
39
+ const blocks: ChangedLineBlock[] = []
40
+ let row = 1
41
+ let removed = 0
42
+ let added = 0
43
+
44
+ const flush = () => {
45
+ const len = Math.max(removed, added)
46
+ if (len > 0) {
47
+ blocks.push({ start: row, end: row + len - 1 })
48
+ row += len
49
+ removed = 0
50
+ added = 0
51
+ }
52
+ }
53
+
54
+ for (const part of changes) {
55
+ const count = part.count ?? 0
56
+
57
+ if (part.removed) {
58
+ removed += count
59
+ } else if (part.added) {
60
+ added += count
61
+ } else {
62
+ flush()
63
+ row += count
64
+ }
65
+ }
66
+
67
+ flush()
68
+
69
+ return {
70
+ blocks,
71
+ totalLines: Math.max(0, row - 1),
72
+ }
12
73
  }
13
74
 
14
75
  /**
@@ -16,15 +77,17 @@ interface CommonParams {
16
77
  * 区间必须按 start 升序喂入,每次 feed 后可读取 maxReached 判断是否达到像素上限。
17
78
  * @param totalLines 文件总行数,用于 clamp context 窗口上界及判断尾部是否有折叠 widget
18
79
  * @param ctx 每个变更块上下保留的 context 行数,对应 Monaco contextLineCount
80
+ * @param minHidden Monaco minimumLineCount,小于此值的未变更区不折叠
19
81
  * @param maxPx 像素上限,达到后 maxReached 为 true,调用方应立即停止 feed
20
82
  */
21
- function makeVisibleLineCounter(totalLines: number, ctx: number, maxPx: number) {
22
- let visible = 0 // 已 commit 的确定可见行数(不含当前 pending 块)
23
- let gaps = 0 // gap widget 数量(每个折叠区域占 GAP_HEIGHT,非 LINE_HEIGHT)
83
+ function makeVisibleLineCounter(totalLines: number, ctx: number, minHidden: number, maxPx: number) {
84
+ let visible = 0 // 已 commit 的确定可见行数(不含当前 pending 块)
85
+ let gaps = 0 // gap widget 数量(每个折叠区域占固定高度,非代码行高)
24
86
  let pendingStart = -1 // 当前待合并窗口的起始行(-1 表示无 pending)
25
- let pendingEnd = -1 // 当前待合并窗口的结束行
87
+ let pendingEnd = -1 // 当前待合并窗口的结束行
26
88
 
27
- const usedPx = () => visible * LINE_HEIGHT + gaps * GAP_HEIGHT
89
+ const usedPx = () =>
90
+ visible * HEIGHT_CODE_LINE + gaps * HEIGHT_HIDDEN_REGION + HEIGHT_HORIZONTAL_SCROLLBAR
28
91
 
29
92
  const commitPending = () => {
30
93
  if (pendingStart === -1) return
@@ -36,10 +99,18 @@ function makeVisibleLineCounter(totalLines: number, ctx: number, maxPx: number)
36
99
 
37
100
  return {
38
101
  feed(s: number, e: number) {
39
- const winStart = Math.max(1, s - ctx)
102
+ let winStart = Math.max(1, s - ctx)
40
103
  const winEnd = Math.min(totalLines, e + ctx)
41
- if (pendingEnd === -1 || winStart > pendingEnd + 1) {
42
- commitPending() // 新窗口与 pending 有间隙:先提交 pending,再开新窗口
104
+ if (pendingEnd === -1) {
105
+ if (winStart - 1 < minHidden) winStart = 1
106
+ pendingStart = winStart
107
+ pendingEnd = winEnd
108
+ return
109
+ }
110
+
111
+ const hiddenLineCount = winStart - pendingEnd - 1
112
+ if (hiddenLineCount > 0 && hiddenLineCount >= minHidden) {
113
+ commitPending() // 新窗口与 pending 有间隙:先提交 pending,再开新窗口
43
114
  pendingStart = winStart
44
115
  pendingEnd = winEnd
45
116
  } else {
@@ -49,7 +120,14 @@ function makeVisibleLineCounter(totalLines: number, ctx: number, maxPx: number)
49
120
  flush() {
50
121
  const lastEnd = pendingEnd
51
122
  commitPending()
52
- if (visible > 0 && lastEnd !== totalLines) gaps += 1
123
+ const trailingLineCount = totalLines - lastEnd
124
+ if (visible > 0 && trailingLineCount > 0) {
125
+ if (trailingLineCount >= minHidden) {
126
+ gaps += 1
127
+ } else {
128
+ visible += trailingLineCount
129
+ }
130
+ }
53
131
  return usedPx()
54
132
  },
55
133
  get usedPx() {
@@ -69,26 +147,20 @@ const autoHeightPatch = function ({
69
147
  minPx,
70
148
  maxPx,
71
149
  unchangedCtxLineNum,
150
+ unchangedMinLineNum,
72
151
  }: {
73
152
  patch: string
74
153
  } & CommonParams): number {
75
- const { hunks } = parseHunks(patch)
76
- if (hunks.length === 0) return minPx
77
-
78
- const lastHunk = hunks[hunks.length - 1]
79
- const totalLines = Math.max(
80
- lastHunk.origStart + lastHunk.origCount - 1,
81
- lastHunk.modStart + lastHunk.modCount - 1,
82
- )
83
- const counter = makeVisibleLineCounter(totalLines, unchangedCtxLineNum, maxPx)
84
-
85
- for (const h of hunks) {
86
- const hunkEnd = Math.max(h.origStart + h.origCount - 1, h.modStart + h.modCount - 1)
87
- counter.feed(h.origStart, hunkEnd)
154
+ const { changedLineBlocks, totalLines } = patch2PairWithLayout(patch)
155
+ if (changedLineBlocks.length === 0) return minPx
156
+
157
+ const counter = makeVisibleLineCounter(totalLines, unchangedCtxLineNum, unchangedMinLineNum, maxPx)
158
+ for (const block of changedLineBlocks) {
159
+ counter.feed(block.start, block.end)
88
160
  if (counter.maxReached) return maxPx
89
161
  }
90
162
 
91
- return Math.max(minPx, counter.flush())
163
+ return Math.min(maxPx, Math.max(minPx, counter.flush()))
92
164
  }
93
165
 
94
166
  const autoHeightPair = function ({
@@ -97,35 +169,40 @@ const autoHeightPair = function ({
97
169
  maxPx,
98
170
  unchangedVisiable,
99
171
  unchangedCtxLineNum,
172
+ unchangedMinLineNum,
100
173
  }: {
101
174
  pair: WtoolDiffViewerProps['diffPair']
102
175
  } & CommonParams): number {
103
176
  if (!pair || pair.length < 2) return minPx
104
177
 
105
- const origLines = pair[0].content?.split('\n') ?? []
106
- const modLines = pair[1].content?.split('\n') ?? []
178
+ const origContent = pair[0].content ?? ''
179
+ const modContent = pair[1].content ?? ''
180
+ const origLines = origContent.split('\n')
181
+ const modLines = modContent.split('\n')
107
182
  const totalLines = Math.max(origLines.length, modLines.length)
108
183
 
109
184
  if (unchangedVisiable) {
110
- return Math.max(minPx, Math.min(totalLines * LINE_HEIGHT, maxPx))
185
+ const contentHeight = totalLines * HEIGHT_CODE_LINE + HEIGHT_HORIZONTAL_SCROLLBAR
186
+ return Math.max(minPx, Math.min(contentHeight, maxPx))
111
187
  }
112
188
 
113
- // 剥前缀相同行
114
- let lo = 0
115
- while (lo < origLines.length && lo < modLines.length && origLines[lo] === modLines[lo]) lo++
189
+ const { blocks, totalLines: diffTotalLines } = getChangedLineInfo(origContent, modContent)
190
+ if (blocks.length === 0) return minPx
116
191
 
117
- // 剥后缀相同行
118
- let origHi = origLines.length, modHi = modLines.length
119
- while (origHi > lo && modHi > lo && origLines[origHi - 1] === modLines[modHi - 1]) { origHi--; modHi-- }
192
+ const counter = makeVisibleLineCounter(diffTotalLines, unchangedCtxLineNum, unchangedMinLineNum, maxPx)
193
+ for (const block of blocks) {
194
+ counter.feed(block.start, block.end)
195
+ if (counter.maxReached) return maxPx
196
+ }
120
197
 
121
- if (origHi <= lo && modHi <= lo) return minPx
198
+ let result = counter.flush()
199
+ result = Math.max(minPx, result)
200
+ result = Math.min(maxPx, result)
122
201
 
123
- const counter = makeVisibleLineCounter(totalLines, unchangedCtxLineNum, maxPx)
124
- counter.feed(lo + 1, Math.max(origHi, modHi))
125
- return Math.max(minPx, counter.flush())
202
+ return result
126
203
  }
127
204
 
128
- const height2Num = (heightStr: string): number => {
205
+ export const height2Num = (heightStr: string): number => {
129
206
  if (heightStr.endsWith('vh')) {
130
207
  const vh = parseFloat(heightStr)
131
208
  return Math.round((vh / 100) * document.documentElement.clientHeight)
@@ -134,23 +211,62 @@ const height2Num = (heightStr: string): number => {
134
211
  }
135
212
 
136
213
  export const autoHeight = function ({
214
+ id,
137
215
  patch,
138
216
  pair,
139
217
  maxHeight,
140
218
  minHeight,
141
219
  unchangedVisiable,
142
220
  unchangedCtxLineNum,
221
+ unchangedMinLineNum = 1,
143
222
  }: {
223
+ id: string
144
224
  patch?: string
145
225
  pair?: WtoolDiffViewerProps['diffPair']
146
226
  maxHeight: string
147
227
  minHeight: string
148
228
  unchangedVisiable: boolean
149
229
  unchangedCtxLineNum: number
230
+ unchangedMinLineNum?: number
150
231
  }): number {
232
+ const cacheKey = unchangedVisiable ? 'visible' : 'hidden'
151
233
  const [minPx, maxPx] = [minHeight, maxHeight].map(height2Num)
152
- const commonParams: CommonParams = { minPx, maxPx, unchangedVisiable, unchangedCtxLineNum }
234
+ const origContent = pair?.[0]?.content
235
+ const modContent = pair?.[1]?.content
236
+ const cache = heightMap.get(id)
237
+ const cacheMatched =
238
+ cache?.patch === patch &&
239
+ cache?.origContent === origContent &&
240
+ cache?.modContent === modContent &&
241
+ cache?.minPx === minPx &&
242
+ cache?.maxPx === maxPx &&
243
+ cache?.unchangedCtxLineNum === unchangedCtxLineNum &&
244
+ cache?.unchangedMinLineNum === unchangedMinLineNum
245
+ const cachedHeight = cacheMatched ? cache.heights[cacheKey] : undefined
246
+ if (cachedHeight !== undefined) return cachedHeight
247
+
248
+ const commonParams: CommonParams = {
249
+ minPx,
250
+ maxPx,
251
+ unchangedVisiable,
252
+ unchangedCtxLineNum,
253
+ unchangedMinLineNum,
254
+ }
255
+ const height = patch ? autoHeightPatch({ patch, ...commonParams }) : autoHeightPair({ pair, ...commonParams })
256
+
257
+ heightMap.set(id, {
258
+ patch,
259
+ origContent,
260
+ modContent,
261
+ minPx,
262
+ maxPx,
263
+ unchangedCtxLineNum,
264
+ unchangedMinLineNum,
265
+ heights: {
266
+ ...(cacheMatched ? cache.heights : {}),
267
+ [cacheKey]: height,
268
+ },
269
+ })
153
270
 
154
- if (patch) return autoHeightPatch({ patch, ...commonParams })
155
- return autoHeightPair({ pair, ...commonParams })
271
+ return height
156
272
  }
@@ -1,56 +1,19 @@
1
+ import { parsePatchFilenames, parsePatchHunks } from '@/utils/patch'
2
+
1
3
  export interface FilePair {
2
4
  filename: string
3
5
  content: string
4
6
  }
5
7
 
6
- export interface Hunk {
7
- origStart: number // original 起始行号(1-based)
8
- origCount: number // original 行数
9
- modStart: number // modified 起始行号(1-based)
10
- modCount: number // modified 行数
11
- lines: string[] // hunk 原始行(含前缀字符)
8
+ export interface PatchChangedLineBlock {
9
+ start: number
10
+ end: number
12
11
  }
13
12
 
14
- /**
15
- * 解析 unified diff patch,提取文件名与所有 hunk
16
- */
17
- export function parseHunks(patch: string): { origFilename: string; modFilename: string; hunks: Hunk[] } {
18
- const lines = patch.split('\n')
19
-
20
- let origFilename = ''
21
- let modFilename = ''
22
- const hunks: Hunk[] = []
23
- let currentHunk: Hunk | null = null
24
-
25
- for (const line of lines) {
26
- if (line.startsWith('--- ')) {
27
- origFilename = line.slice(4).trim()
28
- continue
29
- }
30
- if (line.startsWith('+++ ')) {
31
- modFilename = line.slice(4).trim()
32
- continue
33
- }
34
- // @@ -origStart,origCount +modStart,modCount @@
35
- const hunkMatch = line.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/)
36
- if (hunkMatch) {
37
- currentHunk = {
38
- origStart: parseInt(hunkMatch[1], 10),
39
- origCount: hunkMatch[2] !== undefined ? parseInt(hunkMatch[2], 10) : 1,
40
- modStart: parseInt(hunkMatch[3], 10),
41
- modCount: hunkMatch[4] !== undefined ? parseInt(hunkMatch[4], 10) : 1,
42
- lines: [],
43
- }
44
- hunks.push(currentHunk)
45
- continue
46
- }
47
- if (currentHunk) {
48
- if (line.startsWith('\\')) continue // ""
49
- currentHunk.lines.push(line)
50
- }
51
- }
52
-
53
- return { origFilename, modFilename, hunks }
13
+ export interface PatchPairLayout {
14
+ pair: FilePair[]
15
+ changedLineBlocks: PatchChangedLineBlock[]
16
+ totalLines: number
54
17
  }
55
18
 
56
19
  /**
@@ -63,24 +26,34 @@ export function parseHunks(patch: string): { origFilename: string; modFilename:
63
26
  * - 连续的删除('-')/ 新增('+')块:收集后成对对齐写入,
64
27
  * 行数较少的一侧补空行,使 Monaco 能在同一视觉行渲染替换关系
65
28
  */
66
- export const patch2Pair = function (patch: string): FilePair[] {
29
+ export const patch2PairWithLayout = function (patch: string): PatchPairLayout {
67
30
  if (!patch) {
68
- return [
69
- { filename: '', content: '' },
70
- { filename: '', content: '' },
71
- ]
31
+ return {
32
+ pair: [
33
+ { filename: '', content: '' },
34
+ { filename: '', content: '' },
35
+ ],
36
+ changedLineBlocks: [],
37
+ totalLines: 1,
38
+ }
72
39
  }
73
40
 
74
- const { origFilename, modFilename, hunks } = parseHunks(patch)
41
+ const { origFilename, modFilename } = parsePatchFilenames(patch)
42
+ const hunks = parsePatchHunks(patch)
75
43
 
76
44
  const origLines: string[] = []
77
45
  const modLines: string[] = []
46
+ const changedLineBlocks: PatchChangedLineBlock[] = []
78
47
 
79
48
  const pendingDel: string[] = []
80
49
  const pendingAdd: string[] = []
81
50
 
82
51
  const flushPending = () => {
83
52
  const maxLen = Math.max(pendingDel.length, pendingAdd.length)
53
+ if (maxLen > 0) {
54
+ const start = Math.max(origLines.length, modLines.length) + 1
55
+ changedLineBlocks.push({ start, end: start + maxLen - 1 })
56
+ }
84
57
  for (let i = 0; i < maxLen; i++) {
85
58
  origLines.push(pendingDel[i] ?? '')
86
59
  modLines.push(pendingAdd[i] ?? '')
@@ -125,8 +98,16 @@ export const patch2Pair = function (patch: string): FilePair[] {
125
98
  flushPending()
126
99
  }
127
100
 
128
- return [
129
- { filename: origFilename, content: origLines.join('\n') },
130
- { filename: modFilename, content: modLines.join('\n') },
131
- ]
101
+ return {
102
+ pair: [
103
+ { filename: origFilename, content: origLines.join('\n') },
104
+ { filename: modFilename, content: modLines.join('\n') },
105
+ ],
106
+ changedLineBlocks,
107
+ totalLines: Math.max(origLines.length, modLines.length),
108
+ }
109
+ }
110
+
111
+ export const patch2Pair = function (patch: string): FilePair[] {
112
+ return patch2PairWithLayout(patch).pair
132
113
  }
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
2
+ <svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
3
+ <path d="M13 3L13.7071 2.29289C13.5196 2.10536 13.2652 2 13 2V3ZM19 9H20C20 8.73478 19.8946 8.48043 19.7071 8.29289L19 9ZM13.109 8.45399L14 8V8L13.109 8.45399ZM13.546 8.89101L14 8L13.546 8.89101ZM10 13C10 12.4477 9.55228 12 9 12C8.44772 12 8 12.4477 8 13H10ZM8 16C8 16.5523 8.44772 17 9 17C9.55228 17 10 16.5523 10 16H8ZM8.5 9C7.94772 9 7.5 9.44772 7.5 10C7.5 10.5523 7.94772 11 8.5 11V9ZM9.5 11C10.0523 11 10.5 10.5523 10.5 10C10.5 9.44772 10.0523 9 9.5 9V11ZM8.5 6C7.94772 6 7.5 6.44772 7.5 7C7.5 7.55228 7.94772 8 8.5 8V6ZM9.5 8C10.0523 8 10.5 7.55228 10.5 7C10.5 6.44772 10.0523 6 9.5 6V8ZM17.908 20.782L17.454 19.891L17.454 19.891L17.908 20.782ZM18.782 19.908L19.673 20.362L18.782 19.908ZM5.21799 19.908L4.32698 20.362H4.32698L5.21799 19.908ZM6.09202 20.782L6.54601 19.891L6.54601 19.891L6.09202 20.782ZM6.09202 3.21799L5.63803 2.32698L5.63803 2.32698L6.09202 3.21799ZM5.21799 4.09202L4.32698 3.63803L4.32698 3.63803L5.21799 4.09202ZM12 3V7.4H14V3H12ZM14.6 10H19V8H14.6V10ZM12 7.4C12 7.66353 11.9992 7.92131 12.0169 8.13823C12.0356 8.36682 12.0797 8.63656 12.218 8.90798L14 8C14.0293 8.05751 14.0189 8.08028 14.0103 7.97537C14.0008 7.85878 14 7.69653 14 7.4H12ZM14.6 8C14.3035 8 14.1412 7.99922 14.0246 7.9897C13.9197 7.98113 13.9425 7.9707 14 8L13.092 9.78201C13.3634 9.92031 13.6332 9.96438 13.8618 9.98305C14.0787 10.0008 14.3365 10 14.6 10V8ZM12.218 8.90798C12.4097 9.2843 12.7157 9.59027 13.092 9.78201L14 8V8L12.218 8.90798ZM8 13V16H10V13H8ZM8.5 11H9.5V9H8.5V11ZM8.5 8H9.5V6H8.5V8ZM13 2H8.2V4H13V2ZM4 6.2V17.8H6V6.2H4ZM8.2 22H15.8V20H8.2V22ZM20 17.8V9H18V17.8H20ZM19.7071 8.29289L13.7071 2.29289L12.2929 3.70711L18.2929 9.70711L19.7071 8.29289ZM15.8 22C16.3436 22 16.8114 22.0008 17.195 21.9694C17.5904 21.9371 17.9836 21.8658 18.362 21.673L17.454 19.891C17.4045 19.9162 17.3038 19.9539 17.0322 19.9761C16.7488 19.9992 16.3766 20 15.8 20V22ZM18 17.8C18 18.3766 17.9992 18.7488 17.9761 19.0322C17.9539 19.3038 17.9162 19.4045 17.891 19.454L19.673 20.362C19.8658 19.9836 19.9371 19.5904 19.9694 19.195C20.0008 18.8114 20 18.3436 20 17.8H18ZM18.362 21.673C18.9265 21.3854 19.3854 20.9265 19.673 20.362L17.891 19.454C17.7951 19.6422 17.6422 19.7951 17.454 19.891L18.362 21.673ZM4 17.8C4 18.3436 3.99922 18.8114 4.03057 19.195C4.06287 19.5904 4.13419 19.9836 4.32698 20.362L6.10899 19.454C6.0838 19.4045 6.04612 19.3038 6.02393 19.0322C6.00078 18.7488 6 18.3766 6 17.8H4ZM8.2 20C7.62345 20 7.25117 19.9992 6.96784 19.9761C6.69617 19.9539 6.59545 19.9162 6.54601 19.891L5.63803 21.673C6.01641 21.8658 6.40963 21.9371 6.80497 21.9694C7.18864 22.0008 7.65645 22 8.2 22V20ZM4.32698 20.362C4.6146 20.9265 5.07354 21.3854 5.63803 21.673L6.54601 19.891C6.35785 19.7951 6.20487 19.6422 6.10899 19.454L4.32698 20.362ZM8.2 2C7.65645 2 7.18864 1.99922 6.80497 2.03057C6.40963 2.06287 6.01641 2.13419 5.63803 2.32698L6.54601 4.10899C6.59545 4.0838 6.69617 4.04612 6.96784 4.02393C7.25117 4.00078 7.62345 4 8.2 4V2ZM6 6.2C6 5.62345 6.00078 5.25117 6.02393 4.96784C6.04612 4.69617 6.0838 4.59545 6.10899 4.54601L4.32698 3.63803C4.13419 4.01641 4.06287 4.40963 4.03057 4.80497C3.99922 5.18864 4 5.65645 4 6.2H6ZM5.63803 2.32698C5.07354 2.6146 4.6146 3.07354 4.32698 3.63803L6.10899 4.54601C6.20487 4.35785 6.35785 4.20487 6.54601 4.10899L5.63803 2.32698Z" fill="#000000"/>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
2
+ <svg width="800px" height="800px" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
3
+ <path d="M0 1H6L9 4H16V14H0V1Z" fill="#54aeff"/>
4
+ </svg>
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './DiffViewer'
2
+ export * from './DiffFiles'
2
3
  export { default as loader } from '@monaco-editor/loader'
package/src/types.ts CHANGED
@@ -4,6 +4,7 @@ export type DiffEditorOptions = Monaco.editor.IStandaloneDiffEditorConstructionO
4
4
  export type ModelOptions = Monaco.editor.ITextModelUpdateOptions
5
5
 
6
6
  export interface WtoolDiffViewerProps {
7
+ fileId?: string
7
8
  diffPair?: { filename: string; content: string | null }[]
8
9
  diffPatch?: string
9
10
  language?: string
@@ -17,4 +18,22 @@ export interface WtoolDiffViewerStyle {
17
18
  height?: string
18
19
  minHeight?: string
19
20
  maxHeight?: string
21
+ viewed?: boolean
22
+ rawed?: boolean
23
+ }
24
+
25
+ export interface DiffFile {
26
+ fullPath?: string
27
+ diffPair?: WtoolDiffViewerProps['diffPair']
28
+ diffPatch?: string
29
+ viewerStyle?: WtoolDiffViewerStyle
30
+ }
31
+
32
+ /** @deprecated Use DiffFile. Nested directory input is no longer supported. */
33
+ export type FileTree = DiffFile
34
+
35
+ export interface WtoolDiffFilesProps {
36
+ diffFiles?: DiffFile[]
37
+ fileOverScan?: number
38
+ viewerStyle?: WtoolDiffViewerStyle
20
39
  }
@@ -0,0 +1,74 @@
1
+ export interface Hunk {
2
+ origStart: number // original 起始行号(1-based)
3
+ origCount: number // original 行数
4
+ modStart: number // modified 起始行号(1-based)
5
+ modCount: number // modified 行数
6
+ lines: string[] // hunk 原始行(含前缀字符)
7
+ }
8
+
9
+ export interface PatchFilenames {
10
+ origFilename: string
11
+ modFilename: string
12
+ }
13
+
14
+ /**
15
+ * 从 unified diff patch 解析 --- / +++ 行中的文件名。
16
+ * 文件名位于 patch 头部,遇到首个 @@ hunk 或已解析到两侧文件名后即停止扫描。
17
+ */
18
+ export function parsePatchFilenames(patch: string): PatchFilenames {
19
+ let origFilename = ''
20
+ let modFilename = ''
21
+
22
+ let start = 0
23
+ while (start < patch.length) {
24
+ const nl = patch.indexOf('\n', start)
25
+ const line = nl === -1 ? patch.slice(start) : patch.slice(start, nl)
26
+ start = nl === -1 ? patch.length : nl + 1
27
+
28
+ if (line.startsWith('--- ')) {
29
+ origFilename = line.slice(4).trim()
30
+ if (origFilename && modFilename) break
31
+ continue
32
+ }
33
+ if (line.startsWith('+++ ')) {
34
+ modFilename = line.slice(4).trim()
35
+ if (origFilename && modFilename) break
36
+ continue
37
+ }
38
+ if (line.startsWith('@@')) break
39
+ }
40
+
41
+ return { origFilename, modFilename }
42
+ }
43
+
44
+ /**
45
+ * 从 unified diff patch 解析所有 hunk(@@ 块)
46
+ */
47
+ export function parsePatchHunks(patch: string): Hunk[] {
48
+ const lines = patch.split('\n')
49
+
50
+ const hunks: Hunk[] = []
51
+ let currentHunk: Hunk | null = null
52
+
53
+ for (const line of lines) {
54
+ // @@ -origStart,origCount +modStart,modCount @@
55
+ const hunkMatch = line.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/)
56
+ if (hunkMatch) {
57
+ currentHunk = {
58
+ origStart: parseInt(hunkMatch[1], 10),
59
+ origCount: hunkMatch[2] !== undefined ? parseInt(hunkMatch[2], 10) : 1,
60
+ modStart: parseInt(hunkMatch[3], 10),
61
+ modCount: hunkMatch[4] !== undefined ? parseInt(hunkMatch[4], 10) : 1,
62
+ lines: [],
63
+ }
64
+ hunks.push(currentHunk)
65
+ continue
66
+ }
67
+ if (currentHunk) {
68
+ if (line.startsWith('\\')) continue // ""
69
+ currentHunk.lines.push(line)
70
+ }
71
+ }
72
+
73
+ return hunks
74
+ }