v-code-diff 0.3.11 → 1.0.0-alpha.1
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/LICENSE +1 -1
- package/README.md +152 -94
- package/dist/v2/index.cjs.js +9 -0
- package/dist/v2/index.es.js +4526 -0
- package/dist/v2.7/index.cjs.js +9 -0
- package/dist/v2.7/index.es.js +4337 -0
- package/dist/v3/index.cjs.js +9 -0
- package/dist/v3/index.es.js +4315 -0
- package/package.json +44 -87
- package/scripts/postinstall.js +18 -0
- package/scripts/utils.js +36 -0
- package/src/CodeDiff.vue +57 -0
- package/src/highlight.ts +23 -0
- package/src/index.ts +15 -0
- package/src/split/SplitLine.vue +66 -0
- package/src/split/SplitViewer.vue +26 -0
- package/src/style.scss +114 -0
- package/src/types.ts +29 -0
- package/src/unified/UnifiedLine.vue +62 -0
- package/src/unified/UnifiedViewer.vue +20 -0
- package/src/utils.ts +418 -0
- package/src/var.scss +439 -0
- package/types/index.d.ts +14 -0
- package/dist/index.d.ts +0 -6
- package/dist/v-code-diff/index.d.ts +0 -2
- package/dist/v-code-diff/styles/index.d.ts +0 -3
- package/dist/v-code-diff/util.d.ts +0 -24
- package/dist/v-code-diff/v-code-diff.d.ts +0 -93
- package/dist/v-code-diff.cjs.js +0 -1
- package/dist/v-code-diff.esm.js +0 -1
- package/dist/v-code-diff.umd.js +0 -1
- package/src/lib/index.ts +0 -18
- package/src/lib/v-code-diff/index.ts +0 -3
- package/src/lib/v-code-diff/styles/highlight.scss +0 -84
- package/src/lib/v-code-diff/styles/index.ts +0 -3
- package/src/lib/v-code-diff/styles/style.scss +0 -36
- package/src/lib/v-code-diff/util.ts +0 -122
- package/src/lib/v-code-diff/v-code-diff.ts +0 -108
package/src/utils.ts
ADDED
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import * as Diff from 'diff'
|
|
2
|
+
import type { Change } from 'diff'
|
|
3
|
+
import hljs from './highlight'
|
|
4
|
+
import { DiffType } from './types'
|
|
5
|
+
import type { DiffLine, SplitViewerChange, UnifiedViewerChange } from './types'
|
|
6
|
+
|
|
7
|
+
const MODIFIED_START_TAG = '<code-diff-modified>'
|
|
8
|
+
const MODIFIED_CLOSE_TAG = '</code-diff-modified>'
|
|
9
|
+
|
|
10
|
+
const startEntity = MODIFIED_START_TAG.replace('<', '<').replace('>', '>')
|
|
11
|
+
const closeEntity = MODIFIED_CLOSE_TAG.replace('<', '<').replace('>', '>')
|
|
12
|
+
|
|
13
|
+
const lineType = (diff: Diff.Change): DiffType => {
|
|
14
|
+
if (diff === undefined)
|
|
15
|
+
return DiffType.EQUAL
|
|
16
|
+
if (diff.added)
|
|
17
|
+
return DiffType.ADD
|
|
18
|
+
if (diff.removed)
|
|
19
|
+
return DiffType.DELETE
|
|
20
|
+
return DiffType.EQUAL
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const renderWords = (prev?: string, current?: string, diffStyle = 'word'): string => {
|
|
24
|
+
if (typeof prev === 'undefined')
|
|
25
|
+
return current!
|
|
26
|
+
if (typeof current === 'undefined')
|
|
27
|
+
return prev!
|
|
28
|
+
|
|
29
|
+
type RenderFunctionType = (prev: string, old: string) => Change[]
|
|
30
|
+
const func: RenderFunctionType = diffStyle === 'char' ? Diff.diffChars : Diff.diffWords
|
|
31
|
+
return func(prev, current)
|
|
32
|
+
.filter(word => lineType(word) !== DiffType.DELETE)
|
|
33
|
+
.map(word =>
|
|
34
|
+
lineType(word) === DiffType.ADD ? `${MODIFIED_START_TAG}${word.value}${MODIFIED_CLOSE_TAG}` : word.value,
|
|
35
|
+
)
|
|
36
|
+
.join('')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const getHighlightCode = (language: string, code: string) => {
|
|
40
|
+
const hasModifiedTags = code.match(new RegExp(`(${MODIFIED_START_TAG}|${MODIFIED_CLOSE_TAG})`, 'g'))
|
|
41
|
+
|
|
42
|
+
if (!hasModifiedTags)
|
|
43
|
+
return hljs.highlight(code, { language }).value
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Explore highlight DOM extracted from pure code and compare the text with the original code to generate the highlight code
|
|
47
|
+
*/
|
|
48
|
+
let originalCode = code // original code with modified tags
|
|
49
|
+
const pureCode = code.replace(new RegExp(`(${MODIFIED_START_TAG}|${MODIFIED_CLOSE_TAG})`, 'g'), '') // Without modified tags
|
|
50
|
+
const pureElement = document.createElement('div')
|
|
51
|
+
pureElement.innerHTML = hljs.highlight(pureCode, { language }).value // Highlight DOM without modified tags
|
|
52
|
+
|
|
53
|
+
// Modified span is created per highlight operator and causes it to continue
|
|
54
|
+
let innerModifiedTag = false
|
|
55
|
+
|
|
56
|
+
const diffElements = (node: HTMLElement) => {
|
|
57
|
+
node.childNodes.forEach((child) => {
|
|
58
|
+
if (child.nodeType === Node.ELEMENT_NODE)
|
|
59
|
+
diffElements(child as HTMLElement)
|
|
60
|
+
|
|
61
|
+
// Compare text nodes and check changed text
|
|
62
|
+
if (child.nodeType === Node.TEXT_NODE) {
|
|
63
|
+
if (!child.textContent)
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
let oldContent = child.textContent
|
|
67
|
+
let newContent = ''
|
|
68
|
+
|
|
69
|
+
if (innerModifiedTag) {
|
|
70
|
+
// If it continues within the modified range
|
|
71
|
+
newContent = newContent + MODIFIED_START_TAG
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
while (oldContent.length) {
|
|
75
|
+
if (originalCode.startsWith(MODIFIED_START_TAG)) {
|
|
76
|
+
// Add modified start tag
|
|
77
|
+
originalCode = originalCode.slice(MODIFIED_START_TAG.length)
|
|
78
|
+
newContent = newContent + MODIFIED_START_TAG
|
|
79
|
+
innerModifiedTag = true // Start modified
|
|
80
|
+
continue
|
|
81
|
+
}
|
|
82
|
+
if (originalCode.startsWith(MODIFIED_CLOSE_TAG)) {
|
|
83
|
+
// Add modified close tag
|
|
84
|
+
originalCode = originalCode.slice(MODIFIED_CLOSE_TAG.length)
|
|
85
|
+
newContent = newContent + MODIFIED_CLOSE_TAG
|
|
86
|
+
innerModifiedTag = false // End modified
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Add words before modified tag
|
|
91
|
+
const hasModifiedTag = originalCode.match(new RegExp(`(${MODIFIED_START_TAG}|${MODIFIED_CLOSE_TAG})`))
|
|
92
|
+
const originalCodeDiffLength = (hasModifiedTag && hasModifiedTag.index) ? hasModifiedTag.index : originalCode.length
|
|
93
|
+
const nextDiffsLength = Math.min(originalCodeDiffLength, oldContent.length)
|
|
94
|
+
|
|
95
|
+
newContent = newContent + originalCode.substring(0, nextDiffsLength)
|
|
96
|
+
originalCode = originalCode.slice(nextDiffsLength)
|
|
97
|
+
oldContent = oldContent.slice(nextDiffsLength)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (innerModifiedTag) {
|
|
101
|
+
// If the loop is finished without a modified close, it is still within the modified range.
|
|
102
|
+
newContent = newContent + MODIFIED_CLOSE_TAG
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
child.textContent = newContent // put as entity code because change textContent
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
diffElements(pureElement)
|
|
110
|
+
|
|
111
|
+
return pureElement.innerHTML
|
|
112
|
+
.replace(new RegExp(startEntity, 'g'), '<span class="x">')
|
|
113
|
+
.replace(new RegExp(closeEntity, 'g'), '</span>')
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function createSplitDiff(
|
|
117
|
+
oldString: string,
|
|
118
|
+
newString: string,
|
|
119
|
+
language = 'plaintext',
|
|
120
|
+
diffStyle = 'word',
|
|
121
|
+
context = 10,
|
|
122
|
+
) {
|
|
123
|
+
const newEmptySplitDiff = (): DiffLine => ({ type: DiffType.EMPTY })
|
|
124
|
+
const newSplitDiff = (type: DiffType, num: number, code: string): DiffLine => ({ type, num, code })
|
|
125
|
+
|
|
126
|
+
const changes = Diff.diffLines(oldString, newString)
|
|
127
|
+
|
|
128
|
+
let delNum = 0
|
|
129
|
+
let addNum = 0
|
|
130
|
+
let skip = false
|
|
131
|
+
|
|
132
|
+
const result: SplitViewerChange = []
|
|
133
|
+
for (let i = 0; i < changes.length; i++) {
|
|
134
|
+
if (skip) {
|
|
135
|
+
skip = false
|
|
136
|
+
continue
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const [cur, next] = [changes[i], changes[i + 1]]
|
|
140
|
+
const [curType, nextType] = [lineType(cur), lineType(next)]
|
|
141
|
+
|
|
142
|
+
const curLines = cur.value.replace(/\n$/, '').split('\n')
|
|
143
|
+
|
|
144
|
+
// 最后一处 diff 的特殊处理
|
|
145
|
+
if (next === undefined) {
|
|
146
|
+
for (const line of curLines) {
|
|
147
|
+
let left: DiffLine = newEmptySplitDiff()
|
|
148
|
+
let right: DiffLine = newEmptySplitDiff()
|
|
149
|
+
|
|
150
|
+
const highlightCode = getHighlightCode(language, line)
|
|
151
|
+
if (curType === DiffType.EQUAL) {
|
|
152
|
+
delNum++
|
|
153
|
+
addNum++
|
|
154
|
+
|
|
155
|
+
left = newSplitDiff(DiffType.EQUAL, delNum, highlightCode)
|
|
156
|
+
right = newSplitDiff(DiffType.EQUAL, addNum, highlightCode)
|
|
157
|
+
}
|
|
158
|
+
if (curType === DiffType.DELETE) {
|
|
159
|
+
delNum++
|
|
160
|
+
|
|
161
|
+
left = newSplitDiff(DiffType.DELETE, delNum, highlightCode)
|
|
162
|
+
right = newEmptySplitDiff()
|
|
163
|
+
}
|
|
164
|
+
if (curType === DiffType.ADD) {
|
|
165
|
+
addNum++
|
|
166
|
+
|
|
167
|
+
left = newEmptySplitDiff()
|
|
168
|
+
right = newSplitDiff(DiffType.ADD, addNum, highlightCode)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
result.push({ left, right })
|
|
172
|
+
}
|
|
173
|
+
break
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 正常逻辑
|
|
177
|
+
// 处理当前 diff 为相等的情况
|
|
178
|
+
if (curType === DiffType.EQUAL) {
|
|
179
|
+
for (const line of curLines) {
|
|
180
|
+
delNum++
|
|
181
|
+
addNum++
|
|
182
|
+
|
|
183
|
+
const highlightCode = getHighlightCode(language, line)
|
|
184
|
+
result.push({
|
|
185
|
+
left: newSplitDiff(DiffType.EQUAL, delNum, highlightCode),
|
|
186
|
+
right: newSplitDiff(DiffType.EQUAL, addNum, highlightCode),
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const nextLines = next.value.replace(/\n$/, '').split('\n')
|
|
192
|
+
// 处理当前 diff 为删除的情况
|
|
193
|
+
if (curType === DiffType.DELETE) {
|
|
194
|
+
if (nextType === DiffType.EQUAL) {
|
|
195
|
+
for (const line of curLines) {
|
|
196
|
+
delNum++
|
|
197
|
+
|
|
198
|
+
result.push({
|
|
199
|
+
left: newSplitDiff(DiffType.DELETE, delNum, getHighlightCode(language, line)),
|
|
200
|
+
right: newEmptySplitDiff(),
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (nextType === DiffType.ADD) {
|
|
205
|
+
skip = true
|
|
206
|
+
const maxCount = Math.max(cur.count!, next.count!)
|
|
207
|
+
for (let j = 0; j < maxCount; j++) {
|
|
208
|
+
if (j < cur.count!)
|
|
209
|
+
delNum++
|
|
210
|
+
|
|
211
|
+
if (j < next.count!)
|
|
212
|
+
addNum++
|
|
213
|
+
|
|
214
|
+
const [curLine, nextLine] = [curLines[j], nextLines[j]]
|
|
215
|
+
|
|
216
|
+
const leftLine = curLines.length === nextLines.length ? renderWords(nextLine, curLine, diffStyle) : curLine
|
|
217
|
+
const rightLine = curLines.length === nextLines.length ? renderWords(curLine, nextLine, diffStyle) : nextLine
|
|
218
|
+
|
|
219
|
+
const left
|
|
220
|
+
= j < cur.count!
|
|
221
|
+
? newSplitDiff(DiffType.DELETE, delNum, getHighlightCode(language, leftLine))
|
|
222
|
+
: newEmptySplitDiff()
|
|
223
|
+
const right
|
|
224
|
+
= j < next.count!
|
|
225
|
+
? newSplitDiff(DiffType.ADD, addNum, getHighlightCode(language, rightLine))
|
|
226
|
+
: newEmptySplitDiff()
|
|
227
|
+
|
|
228
|
+
result.push({ left, right })
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// 处理当前 diff 为添加的情况
|
|
233
|
+
if (curType === DiffType.ADD) {
|
|
234
|
+
for (const line of curLines) {
|
|
235
|
+
addNum++
|
|
236
|
+
result.push({
|
|
237
|
+
left: newEmptySplitDiff(),
|
|
238
|
+
right: newSplitDiff(DiffType.ADD, addNum, getHighlightCode(language, line)),
|
|
239
|
+
})
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (oldString === newString) {
|
|
245
|
+
for (let i = 0; i < result.length; i++)
|
|
246
|
+
result[i].fold = false
|
|
247
|
+
|
|
248
|
+
return result
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
for (let i = 0; i < result.length; i++) {
|
|
252
|
+
const line = result[i]
|
|
253
|
+
if (line.left.type === DiffType.DELETE || line.right.type === DiffType.ADD) {
|
|
254
|
+
const [start, end] = [Math.max(i - context, 0), Math.min(i + context + 1, result.length)]
|
|
255
|
+
for (let j = start; j < end; j++)
|
|
256
|
+
result[j].fold = false
|
|
257
|
+
}
|
|
258
|
+
if (line.fold === undefined)
|
|
259
|
+
line.fold = true
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const realResult = []
|
|
263
|
+
for (let i = 0; i < result.length; i++) {
|
|
264
|
+
const line = result[i]
|
|
265
|
+
if (line.fold === false) {
|
|
266
|
+
realResult.push(line)
|
|
267
|
+
continue
|
|
268
|
+
}
|
|
269
|
+
if (line.fold === true) {
|
|
270
|
+
if (realResult[realResult.length - 1]?.fold !== true)
|
|
271
|
+
realResult.push(line)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return realResult
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function createUnifiedDiff(
|
|
279
|
+
oldString: string,
|
|
280
|
+
newString: string,
|
|
281
|
+
language = 'plaintext',
|
|
282
|
+
diffStyle = 'word',
|
|
283
|
+
context = 2,
|
|
284
|
+
) {
|
|
285
|
+
const changes = Diff.diffLines(oldString, newString)
|
|
286
|
+
|
|
287
|
+
let delNum = 0
|
|
288
|
+
let addNum = 0
|
|
289
|
+
let skip = false
|
|
290
|
+
|
|
291
|
+
const result: UnifiedViewerChange = []
|
|
292
|
+
for (let i = 0; i < changes.length; i++) {
|
|
293
|
+
if (skip) {
|
|
294
|
+
skip = false
|
|
295
|
+
continue
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const [cur, next] = [changes[i], changes[i + 1]]
|
|
299
|
+
const [curType, nextType] = [lineType(cur), lineType(next)]
|
|
300
|
+
|
|
301
|
+
const curLines = cur.value.replace(/\n$/, '').split('\n')
|
|
302
|
+
|
|
303
|
+
// 最后一行的特殊处理
|
|
304
|
+
if (next === undefined) {
|
|
305
|
+
for (const line of curLines) {
|
|
306
|
+
if (curType === DiffType.EQUAL) {
|
|
307
|
+
delNum++
|
|
308
|
+
addNum++
|
|
309
|
+
}
|
|
310
|
+
if (curType === DiffType.DELETE)
|
|
311
|
+
delNum++
|
|
312
|
+
|
|
313
|
+
if (curType === DiffType.ADD)
|
|
314
|
+
addNum++
|
|
315
|
+
|
|
316
|
+
const code = getHighlightCode(language, line)
|
|
317
|
+
|
|
318
|
+
result.push({
|
|
319
|
+
type: curType,
|
|
320
|
+
code,
|
|
321
|
+
addNum: curType === DiffType.DELETE ? undefined : addNum,
|
|
322
|
+
delNum: curType === DiffType.ADD ? undefined : delNum,
|
|
323
|
+
})
|
|
324
|
+
}
|
|
325
|
+
break
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// 正常逻辑
|
|
329
|
+
// 处理当前 diff 为相等的情况
|
|
330
|
+
if (curType === DiffType.EQUAL) {
|
|
331
|
+
for (const line of curLines) {
|
|
332
|
+
delNum++
|
|
333
|
+
addNum++
|
|
334
|
+
const code = getHighlightCode(language, line)
|
|
335
|
+
|
|
336
|
+
result.push({ type: DiffType.EQUAL, code, delNum, addNum })
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const nextLines = next.value.replace(/\n$/, '').split('\n')
|
|
341
|
+
// 处理当前 diff 为删除的情况
|
|
342
|
+
if (curType === DiffType.DELETE) {
|
|
343
|
+
// 下一处差异为新增,且删除与新增行数相同时,对每行依次 diff
|
|
344
|
+
if (nextType === DiffType.ADD && curLines.length === nextLines.length) {
|
|
345
|
+
for (let j = 0; j < curLines.length; j++) {
|
|
346
|
+
const curLine = curLines[j]
|
|
347
|
+
const nextLine = nextLines[j]
|
|
348
|
+
delNum++
|
|
349
|
+
|
|
350
|
+
const code = getHighlightCode(language, renderWords(nextLine, curLine, diffStyle))
|
|
351
|
+
result.push({ type: DiffType.DELETE, code, delNum })
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
for (let j = 0; j < nextLines.length; j++) {
|
|
355
|
+
const curLine = curLines[j]
|
|
356
|
+
const nextLine = nextLines[j]
|
|
357
|
+
addNum++
|
|
358
|
+
|
|
359
|
+
const code = getHighlightCode(language, renderWords(curLine, nextLine, diffStyle))
|
|
360
|
+
result.push({ type: DiffType.ADD, code, addNum })
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
skip = true
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
// 否则单独渲染每行
|
|
367
|
+
for (const line of curLines) {
|
|
368
|
+
delNum++
|
|
369
|
+
|
|
370
|
+
const code = getHighlightCode(language, line)
|
|
371
|
+
result.push({ type: DiffType.DELETE, code, delNum })
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// 处理当前 diff 为添加的情况
|
|
376
|
+
if (curType === DiffType.ADD) {
|
|
377
|
+
for (const line of curLines) {
|
|
378
|
+
addNum++
|
|
379
|
+
const code = getHighlightCode(language, line)
|
|
380
|
+
|
|
381
|
+
result.push({ type: DiffType.ADD, code, addNum })
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
for (let i = 0; i < result.length; i++) {
|
|
387
|
+
const line = result[i]
|
|
388
|
+
if (line.type === DiffType.DELETE || line.type === DiffType.ADD) {
|
|
389
|
+
const [start, end] = [Math.max(i - context, 0), Math.min(i + context + 1, result.length)]
|
|
390
|
+
for (let j = start; j < end; j++)
|
|
391
|
+
result[j].fold = false
|
|
392
|
+
}
|
|
393
|
+
if (line.fold === undefined)
|
|
394
|
+
line.fold = true
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (oldString === newString) {
|
|
398
|
+
for (let i = 0; i < result.length; i++)
|
|
399
|
+
result[i].fold = false
|
|
400
|
+
|
|
401
|
+
return result
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const realResult = []
|
|
405
|
+
for (let i = 0; i < result.length; i++) {
|
|
406
|
+
const line = result[i]
|
|
407
|
+
if (line.fold === false) {
|
|
408
|
+
realResult.push(line)
|
|
409
|
+
continue
|
|
410
|
+
}
|
|
411
|
+
if (line.fold === true) {
|
|
412
|
+
if (i === 0 || realResult[realResult.length - 1]?.fold !== true)
|
|
413
|
+
realResult.push(line)
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return realResult
|
|
418
|
+
}
|