@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.
- package/README.md +140 -41
- package/dist/DiffFiles/DiffFiles.vue.d.ts +12 -0
- package/dist/DiffFiles/DiffList/DiffList.vue.d.ts +94 -0
- package/dist/DiffFiles/FileExplore/FileExplore.vue.d.ts +510 -0
- package/dist/DiffFiles/FileExplore/FileSearch.vue.d.ts +11 -0
- package/dist/DiffFiles/FileExplore/fileTree.d.ts +17 -0
- package/dist/DiffFiles/createDiffFiles.d.ts +13 -0
- package/dist/DiffFiles/index.d.ts +3 -0
- package/dist/DiffFiles/types.d.ts +12 -0
- package/dist/DiffFiles/useDiffFiles.d.ts +23 -0
- package/dist/DiffViewer/DiffViewer.vue.d.ts +15 -4
- package/dist/DiffViewer/MonacoDiffViewer.vue.d.ts +1 -1
- package/dist/DiffViewer/TopBar.vue.d.ts +2 -0
- package/dist/DiffViewer/const.d.ts +8 -0
- package/dist/DiffViewer/utils/autoHeight.d.ts +4 -1
- package/dist/DiffViewer/utils/patch2Pair.d.ts +9 -14
- package/dist/index.d.ts +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/utils/patch.d.ts +20 -0
- package/dist/wtool-vdiff.cjs.js +45 -36
- package/dist/wtool-vdiff.es.js +9535 -5984
- package/package.json +10 -9
- package/src/DiffFiles/DiffFiles.vue +126 -0
- package/src/DiffFiles/DiffList/DiffList.vue +125 -0
- package/src/DiffFiles/FileExplore/FileExplore.vue +181 -0
- package/src/DiffFiles/FileExplore/FileSearch.vue +81 -0
- package/src/DiffFiles/FileExplore/fileTree.ts +102 -0
- package/src/DiffFiles/createDiffFiles.ts +70 -0
- package/src/DiffFiles/index.ts +11 -0
- package/src/DiffFiles/types.ts +15 -0
- package/src/DiffFiles/useDiffFiles.ts +56 -0
- package/src/DiffViewer/DiffViewer.vue +28 -7
- package/src/DiffViewer/MonacoDiffViewer.vue +21 -2
- package/src/DiffViewer/TopBar.vue +25 -6
- package/src/DiffViewer/const.ts +11 -0
- package/src/DiffViewer/utils/autoHeight.ts +159 -43
- package/src/DiffViewer/utils/patch2Pair.ts +37 -56
- package/src/assets/file.svg +4 -0
- package/src/assets/folder.svg +4 -0
- package/src/index.ts +1 -0
- package/src/types.ts +19 -0
- 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 {
|
|
3
|
+
import { HEIGHT_CODE_LINE, HEIGHT_HIDDEN_REGION, HEIGHT_HORIZONTAL_SCROLLBAR } from '../const'
|
|
4
|
+
import { patch2PairWithLayout } from './patch2Pair'
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
23
|
-
let gaps = 0
|
|
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 = () =>
|
|
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
|
-
|
|
102
|
+
let winStart = Math.max(1, s - ctx)
|
|
40
103
|
const winEnd = Math.min(totalLines, e + ctx)
|
|
41
|
-
if (pendingEnd === -1
|
|
42
|
-
|
|
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
|
-
|
|
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 {
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
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
|
|
106
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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
|
-
|
|
198
|
+
let result = counter.flush()
|
|
199
|
+
result = Math.max(minPx, result)
|
|
200
|
+
result = Math.min(maxPx, result)
|
|
122
201
|
|
|
123
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
29
|
+
export const patch2PairWithLayout = function (patch: string): PatchPairLayout {
|
|
67
30
|
if (!patch) {
|
|
68
|
-
return
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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
|
-
|
|
130
|
-
|
|
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
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
|
+
}
|