v-code-diff 1.13.2 → 1.14.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "v-code-diff",
3
3
  "type": "module",
4
- "version": "1.13.2",
4
+ "version": "1.14.1",
5
5
  "packageManager": "pnpm@11.2.2",
6
6
  "description": "A code diff viewer for Vue 2.6, Vue 2.7, and Vue 3",
7
7
  "license": "MIT",
@@ -37,6 +37,7 @@
37
37
  "types"
38
38
  ],
39
39
  "scripts": {
40
+ "benchmark": "pnpm build:3 && vitest bench --run && node --expose-gc benchmarks/render.mjs",
40
41
  "build": "npm run clean && run-s build:**",
41
42
  "build:2": "vue-demi-switch 2 vue2 && pnpm --filter vue2-playground build",
42
43
  "build:2:umd": "vue-demi-switch 2 vue2 && format=umd pnpm --filter vue2-playground build",
@@ -54,7 +55,10 @@
54
55
  "lint:fix": "eslint . --ext .vue,.js,.jsx,.ts,.tsx,json --fix --ignore-path .gitignore",
55
56
  "postinstall": "node scripts/postinstall.cjs",
56
57
  "prepublishOnly": "npm run build",
57
- "release": "bumpp --commit --no-push --tag && npm publish"
58
+ "release": "bumpp --commit --no-push --tag && npm publish",
59
+ "test": "run-s test:unit test:types",
60
+ "test:types": "tsc --noEmit --skipLibCheck --module ESNext --moduleResolution node --target ES2020 tests/types.test-d.ts",
61
+ "test:unit": "vitest run"
58
62
  },
59
63
  "peerDependencies": {
60
64
  "@vue/composition-api": "^1.4.9",
@@ -87,6 +91,7 @@
87
91
  "typescript": "~4.7.4",
88
92
  "vite": "^5.4.2",
89
93
  "vite-plugin-css-injected-by-js": "^2.4.0",
94
+ "vitest": "^2.1.9",
90
95
  "vue": "^3.4.38",
91
96
  "vue-i18n": "^9.14.0",
92
97
  "vue-tsc": "^0.40.13",
package/src/CodeDiff.vue CHANGED
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { computed, ref, watch } from 'vue-demi'
2
+ import { computed, ref, shallowRef, watch } from 'vue-demi'
3
3
  import { createSplitDiff, createUnifiedDiff } from './utils'
4
4
  import UnifiedViewer from './unified/UnifiedViewer.vue'
5
5
  import SplitViewer from './split/SplitViewer.vue'
@@ -77,7 +77,7 @@ const raw = computed(() =>
77
77
  ? createUnifiedDiff(oldString.value, newString.value, props.language, props.diffStyle, props.forceInlineComparison, props.context, props.ignoreMatchingLines)
78
78
  : createSplitDiff(oldString.value, newString.value, props.language, props.diffStyle, props.forceInlineComparison, props.context, props.ignoreMatchingLines),
79
79
  )
80
- const diffChange = ref(raw.value)
80
+ const diffChange = shallowRef(raw.value)
81
81
  const isNotChanged = computed(() => diffChange.value.stat.additionsNum === 0 && diffChange.value.stat.deletionsNum === 0)
82
82
 
83
83
  const currentDiffIndex = ref(-1)
@@ -169,8 +169,8 @@ watch(() => props, () => {
169
169
  </span>
170
170
  </div>
171
171
  </div>
172
- <UnifiedViewer v-if="isUnifiedViewer" :diff-change="diffChange" />
173
- <SplitViewer v-else :diff-change="diffChange" />
172
+ <UnifiedViewer v-if="isUnifiedViewer" :diff-change="diffChange" :language="language" />
173
+ <SplitViewer v-else :diff-change="diffChange" :language="language" />
174
174
  </div>
175
175
  </template>
176
176
 
@@ -1,18 +1,43 @@
1
1
  <script setup lang="ts">
2
+ import { computed, ref, shallowRef, watch } from 'vue-demi'
2
3
  import type { SplitLineChange, SplitViewerChange } from '../types'
4
+ import { RENDER_BATCH_SIZE, highlightSplitLine } from '../utils'
3
5
  import SplitLine from './SplitLine.vue'
4
6
 
5
7
  const props = defineProps<{
6
8
  diffChange: SplitViewerChange
9
+ language: string
7
10
  }>()
8
11
 
12
+ const renderLimit = ref(RENDER_BATCH_SIZE)
13
+ const visibleChanges = shallowRef(props.diffChange.changes.filter(line => !line.hide || line.hideIndex !== undefined))
14
+ const renderedChanges = computed(() => visibleChanges.value.slice(0, renderLimit.value))
15
+ const remainingLines = computed(() => visibleChanges.value.length - renderedChanges.value.length)
16
+ const nextBatchSize = computed(() => Math.min(remainingLines.value, RENDER_BATCH_SIZE))
17
+
18
+ watch(() => props.diffChange, (diffChange) => {
19
+ renderLimit.value = RENDER_BATCH_SIZE
20
+ visibleChanges.value = diffChange.changes.filter(line => !line.hide || line.hideIndex !== undefined)
21
+ })
22
+
23
+ function highlightRenderedChanges() {
24
+ renderedChanges.value.forEach(line => highlightSplitLine(line, props.language))
25
+ }
26
+
9
27
  function expandHandler({ hideIndex }: SplitLineChange) {
10
28
  if (hideIndex === undefined)
11
29
  return
12
- props.diffChange.collector[hideIndex!].lines.forEach((line) => {
30
+ props.diffChange.collector[hideIndex].lines.forEach((line) => {
13
31
  line.hide = false
14
32
  line.fold = false
15
33
  })
34
+ visibleChanges.value = props.diffChange.changes.filter(line => !line.hide || line.hideIndex !== undefined)
35
+ highlightRenderedChanges()
36
+ }
37
+
38
+ function loadMore() {
39
+ renderLimit.value += RENDER_BATCH_SIZE
40
+ highlightRenderedChanges()
16
41
  }
17
42
  </script>
18
43
 
@@ -25,7 +50,14 @@ function expandHandler({ hideIndex }: SplitLineChange) {
25
50
  <col>
26
51
  </colgroup>
27
52
  <tbody>
28
- <SplitLine v-for="(item, index) in diffChange?.changes" :key="index" :split-line="item" @expand="expandHandler" />
53
+ <SplitLine v-for="(item, index) in renderedChanges" :key="index" :split-line="item" @expand="expandHandler" />
54
+ <tr v-if="remainingLines">
55
+ <td class="blob-code blob-code-hunk load-more" colspan="4">
56
+ <button class="load-more-button" type="button" @click="loadMore">
57
+ Show next {{ nextBatchSize }} lines ({{ remainingLines }} remaining)
58
+ </button>
59
+ </td>
60
+ </tr>
29
61
  </tbody>
30
62
  </table>
31
63
  </template>
package/src/style.scss CHANGED
@@ -168,6 +168,24 @@
168
168
  .blob-code-hunk {
169
169
  background-color: var(--color-accent-subtle);
170
170
  }
171
+
172
+ .load-more {
173
+ padding: 8px;
174
+ text-align: center;
175
+ }
176
+
177
+ .load-more-button {
178
+ padding: 4px 12px;
179
+ color: var(--color-accent-fg);
180
+ background: transparent;
181
+ border: 1px solid var(--color-border-default);
182
+ border-radius: 4px;
183
+ cursor: pointer;
184
+ }
185
+
186
+ .load-more-button:hover {
187
+ background-color: var(--color-accent-muted);
188
+ }
171
189
  }
172
190
 
173
191
  .file-diff-split {
package/src/types.ts CHANGED
@@ -23,6 +23,7 @@ export interface DiffLine {
23
23
 
24
24
  export interface SplitLineChange {
25
25
  fold?: boolean
26
+ highlighted?: boolean
26
27
  left: DiffLine
27
28
  right: DiffLine
28
29
  hide?: boolean
@@ -31,6 +32,7 @@ export interface SplitLineChange {
31
32
 
32
33
  export interface UnifiedLineChange {
33
34
  fold?: boolean
35
+ highlighted?: boolean
34
36
  type: DiffType
35
37
  code: string
36
38
  delNum?: number
@@ -1,25 +1,57 @@
1
1
  <script setup lang="ts">
2
+ import { computed, ref, shallowRef, watch } from 'vue-demi'
2
3
  import type { UnifiedLineChange, UnifiedViewerChange } from '../types'
4
+ import { RENDER_BATCH_SIZE, highlightUnifiedLine } from '../utils'
3
5
  import UnifiedLine from './UnifiedLine.vue'
4
6
 
5
7
  const props = defineProps<{
6
8
  diffChange: UnifiedViewerChange
9
+ language: string
7
10
  }>()
8
11
 
12
+ const renderLimit = ref(RENDER_BATCH_SIZE)
13
+ const visibleChanges = shallowRef(props.diffChange.changes.filter(line => !line.hide || line.hideIndex !== undefined))
14
+ const renderedChanges = computed(() => visibleChanges.value.slice(0, renderLimit.value))
15
+ const remainingLines = computed(() => visibleChanges.value.length - renderedChanges.value.length)
16
+ const nextBatchSize = computed(() => Math.min(remainingLines.value, RENDER_BATCH_SIZE))
17
+
18
+ watch(() => props.diffChange, (diffChange) => {
19
+ renderLimit.value = RENDER_BATCH_SIZE
20
+ visibleChanges.value = diffChange.changes.filter(line => !line.hide || line.hideIndex !== undefined)
21
+ })
22
+
23
+ function highlightRenderedChanges() {
24
+ renderedChanges.value.forEach(line => highlightUnifiedLine(line, props.language))
25
+ }
26
+
9
27
  function expandHandler({ hideIndex }: UnifiedLineChange) {
10
28
  if (hideIndex === undefined)
11
29
  return
12
- props.diffChange.collector[hideIndex!].lines.forEach((line) => {
30
+ props.diffChange.collector[hideIndex].lines.forEach((line) => {
13
31
  line.hide = false
14
32
  line.fold = false
15
33
  })
34
+ visibleChanges.value = props.diffChange.changes.filter(line => !line.hide || line.hideIndex !== undefined)
35
+ highlightRenderedChanges()
36
+ }
37
+
38
+ function loadMore() {
39
+ renderLimit.value += RENDER_BATCH_SIZE
40
+ highlightRenderedChanges()
16
41
  }
17
42
  </script>
18
43
 
19
44
  <template>
20
45
  <table class="diff-table">
21
46
  <tbody>
22
- <UnifiedLine v-for="(item, index) in diffChange?.changes" :key="index" :line="item" @expand="expandHandler" />
47
+ <UnifiedLine v-for="(item, index) in renderedChanges" :key="index" :line="item" @expand="expandHandler" />
48
+ <tr v-if="remainingLines">
49
+ <td class="blob-code blob-code-hunk load-more" colspan="3">
50
+ <button class="load-more-button" type="button" @click="loadMore">
51
+ Show next {{ nextBatchSize }} lines ({{ remainingLines }} remaining)
52
+ </button>
53
+ </td>
54
+ </tr>
23
55
  </tbody>
24
56
  </table>
25
57
  </template>
package/src/utils.ts CHANGED
@@ -7,6 +7,8 @@ import type { DiffLine, DiffStat, SplitLineChange, SplitLineUnchanges, SplitView
7
7
 
8
8
  const MODIFIED_START_TAG = '<code-diff-modified>'
9
9
  const MODIFIED_CLOSE_TAG = '</code-diff-modified>'
10
+ const MAX_INLINE_DIFF_LENGTH = 10_000
11
+ export const RENDER_BATCH_SIZE = 1_000
10
12
 
11
13
  const startEntity = MODIFIED_START_TAG.replace('<', '&lt;').replace('>', '&gt;')
12
14
  const closeEntity = MODIFIED_CLOSE_TAG.replace('<', '&lt;').replace('>', '&gt;')
@@ -32,45 +34,117 @@ function lineType(diff: Diff.Change): DiffType {
32
34
  return DiffType.EQUAL
33
35
  }
34
36
 
35
- function renderWords(prev?: string, current?: string, diffStyle = 'word'): string {
36
- if (typeof prev === 'undefined')
37
- return current!
38
- if (typeof current === 'undefined')
39
- return prev!
37
+ function renderChangedLines(prev?: string, current?: string, diffStyle = 'word', force = false): [string | undefined, string | undefined] {
38
+ if (typeof prev === 'undefined' || typeof current === 'undefined')
39
+ return [prev, current]
40
+ if (!force && prev.length + current.length > MAX_INLINE_DIFF_LENGTH)
41
+ return [prev, current]
40
42
 
41
43
  type RenderFunctionType = (prev: string, old: string) => Change[]
42
44
  const func: RenderFunctionType = diffStyle === 'char' ? Diff.diffChars : Diff.diffWords
43
- return func(prev, current)
45
+ const changes = func(prev, current)
46
+ const oldLine = changes
47
+ .filter(word => lineType(word) !== DiffType.ADD)
48
+ .map(word =>
49
+ lineType(word) === DiffType.DELETE ? `${MODIFIED_START_TAG}${word.value}${MODIFIED_CLOSE_TAG}` : word.value,
50
+ )
51
+ .join('')
52
+ const newLine = changes
44
53
  .filter(word => lineType(word) !== DiffType.DELETE)
45
54
  .map(word =>
46
55
  lineType(word) === DiffType.ADD ? `${MODIFIED_START_TAG}${word.value}${MODIFIED_CLOSE_TAG}` : word.value,
47
56
  )
48
57
  .join('')
58
+ return [oldLine, newLine]
49
59
  }
50
60
 
51
61
  function diffLines(prev: string, current: string) {
52
- const dmp = new DiffMatchPatch()
53
- const a = dmp.diff_linesToChars_(prev, current)
54
- const linePrev = a.chars1
55
- const lineCurrent = a.chars2
56
- const lineArray = a.lineArray
57
- const diffs: any[] = dmp.diff_main(linePrev, lineCurrent, false)
58
- dmp.diff_charsToLines_(diffs, lineArray)
59
- return diffs.map((x) => {
60
- const [type, text] = x
61
- const count = text.replace(/\n$/, '').split('\n').length
62
- const change: Diff.Change = {
63
- count,
64
- value: text,
65
- removed: type === DIFF_DELETE,
66
- added: type === DIFF_INSERT,
62
+ if (prev === current) {
63
+ return prev
64
+ ? [{ count: prev.replace(/\n$/, '').split('\n').length, value: prev }]
65
+ : []
66
+ }
67
+
68
+ const prevHasFinalNewline = prev.endsWith('\n')
69
+ const currentHasFinalNewline = current.endsWith('\n')
70
+ if (prevHasFinalNewline !== currentHasFinalNewline) {
71
+ if (prevHasFinalNewline)
72
+ prev = prev.slice(0, -1)
73
+ else
74
+ current = current.slice(0, -1)
75
+ }
76
+
77
+ if (prev)
78
+ prev += '\n'
79
+ if (current)
80
+ current += '\n'
81
+
82
+ let changes: Diff.Change[] | undefined
83
+ if (prev.length + current.length >= 100_000 && prev.includes('\n') && current.includes('\n')) {
84
+ const prevLines = prev ? prev.replace(/\n$/, '').split('\n') : []
85
+ const currentLines = current ? current.replace(/\n$/, '').split('\n') : []
86
+ const prevLineSet = new Set(prevLines)
87
+ const currentLineSet = new Set(currentLines)
88
+ const [smallerSet, largerSet] = prevLineSet.size < currentLineSet.size
89
+ ? [prevLineSet, currentLineSet]
90
+ : [currentLineSet, prevLineSet]
91
+ let sharedLines = 0
92
+ for (const line of smallerSet) {
93
+ if (largerSet.has(line))
94
+ sharedLines++
95
+ }
96
+ const overlap = smallerSet.size ? sharedLines / smallerSet.size : 0
97
+ const exceedsDmpLineLimit = prevLineSet.size >= 40_000 || prevLineSet.size + currentLineSet.size - sharedLines >= 65_535
98
+ const changedUniqueLines = prevLineSet.size + currentLineSet.size - sharedLines * 2
99
+
100
+ if (overlap < 0.01 || (exceedsDmpLineLimit && changedUniqueLines > 200)) {
101
+ // ponytail: complex large inputs use whole-file replacements; revisit if detailed move detection becomes necessary.
102
+ changes = []
103
+ if (prev)
104
+ changes.push({ count: prevLines.length, value: prev, removed: true })
105
+ if (current)
106
+ changes.push({ count: currentLines.length, value: current, added: true })
67
107
  }
68
- return change
69
- })
108
+ else if (exceedsDmpLineLimit) {
109
+ changes = Diff.diffLines(prev, current)
110
+ }
111
+ }
112
+
113
+ if (!changes) {
114
+ const dmp = new DiffMatchPatch()
115
+ const a = dmp.diff_linesToChars_(prev, current)
116
+ const linePrev = a.chars1
117
+ const lineCurrent = a.chars2
118
+ const lineArray = a.lineArray
119
+ const diffs: any[] = dmp.diff_main(linePrev, lineCurrent, false)
120
+ dmp.diff_charsToLines_(diffs, lineArray)
121
+ changes = diffs.map((x) => {
122
+ const [type, text] = x
123
+ const count = text.replace(/\n$/, '').split('\n').length
124
+ const change: Diff.Change = {
125
+ count,
126
+ value: text,
127
+ removed: type === DIFF_DELETE,
128
+ added: type === DIFF_INSERT,
129
+ }
130
+ return change
131
+ })
132
+ }
133
+
134
+ if (prevHasFinalNewline !== currentHasFinalNewline) {
135
+ changes.push({
136
+ count: 1,
137
+ value: '',
138
+ removed: prevHasFinalNewline,
139
+ added: currentHasFinalNewline,
140
+ })
141
+ }
142
+
143
+ return changes
70
144
  }
71
145
 
72
146
  function getHighlightCode(language: string, code: string) {
73
- if (typeof document === 'undefined') {
147
+ if (typeof document === 'undefined' || language === 'plaintext') {
74
148
  return escapeHtml(code)
75
149
  .replace(new RegExp(startEntity, 'g'), '<span class="x">')
76
150
  .replace(new RegExp(closeEntity, 'g'), '</span>')
@@ -152,6 +226,28 @@ function getHighlightCode(language: string, code: string) {
152
226
  .replace(new RegExp(closeEntity, 'g'), '</span>')
153
227
  }
154
228
 
229
+ export function highlightUnifiedLine(line: UnifiedLineChange, language: string) {
230
+ if (line.highlighted)
231
+ return
232
+ line.code = getHighlightCode(language, line.code)
233
+ line.highlighted = true
234
+ }
235
+
236
+ export function highlightSplitLine(line: SplitLineChange, language: string) {
237
+ if (line.highlighted)
238
+ return
239
+ const leftCode = line.left.code
240
+ if (leftCode !== undefined)
241
+ line.left.code = getHighlightCode(language, leftCode)
242
+
243
+ if (line.right.code !== undefined) {
244
+ line.right.code = line.left.type === DiffType.EQUAL && line.right.type === DiffType.EQUAL && line.right.code === leftCode
245
+ ? line.left.code
246
+ : getHighlightCode(language, line.right.code)
247
+ }
248
+ line.highlighted = true
249
+ }
250
+
155
251
  function calcDiffStat(changes: Change[], ignoreRegex?: RegExp): DiffStat {
156
252
  const count = (s: string, c: string) => (s.match(new RegExp(c, 'g')) || []).length
157
253
  const ignoreCount = (lines: string[]) => lines.filter(line => ignoreRegex?.test(line)).length
@@ -162,12 +258,20 @@ function calcDiffStat(changes: Change[], ignoreRegex?: RegExp): DiffStat {
162
258
  let ignoreDeletionsNum = 0
163
259
  for (const change of changes) {
164
260
  if (change.added) {
261
+ if (!ignoreRegex) {
262
+ additionsNum += change.count ?? 0
263
+ continue
264
+ }
165
265
  const ignoreNum = ignoreCount(change.value.trim().split('\n'))
166
266
  additionsNum += count(change.value.trim(), '\n') + 1 - ignoreNum
167
267
  ignoreAdditionsNum += ignoreNum
168
268
  continue
169
269
  }
170
270
  if (change.removed) {
271
+ if (!ignoreRegex) {
272
+ deletionsNum += change.count ?? 0
273
+ continue
274
+ }
171
275
  const ignoreNum = ignoreCount(change.value.trim().split('\n'))
172
276
  deletionsNum += count(change.value.trim(), '\n') + 1 - ignoreNum
173
277
  ignoreDeletionsNum += ignoreNum
@@ -224,25 +328,24 @@ export function createSplitDiff(
224
328
  let left: DiffLine = newEmptySplitDiff()
225
329
  let right: DiffLine = newEmptySplitDiff()
226
330
 
227
- const highlightCode = getHighlightCode(language, line)
228
331
  if (curType === DiffType.EQUAL) {
229
332
  delNum++
230
333
  addNum++
231
334
 
232
- left = newSplitDiff(DiffType.EQUAL, delNum, highlightCode)
233
- right = newSplitDiff(DiffType.EQUAL, addNum, highlightCode)
335
+ left = newSplitDiff(DiffType.EQUAL, delNum, line)
336
+ right = newSplitDiff(DiffType.EQUAL, addNum, line)
234
337
  }
235
338
  if (curType === DiffType.DELETE) {
236
339
  delNum++
237
340
 
238
- left = newSplitDiff(DiffType.DELETE, delNum, highlightCode)
341
+ left = newSplitDiff(DiffType.DELETE, delNum, line)
239
342
  right = newEmptySplitDiff()
240
343
  }
241
344
  if (curType === DiffType.ADD) {
242
345
  addNum++
243
346
 
244
347
  left = newEmptySplitDiff()
245
- right = newSplitDiff(DiffType.ADD, addNum, highlightCode)
348
+ right = newSplitDiff(DiffType.ADD, addNum, line)
246
349
  }
247
350
  rawChanges.push({ left, right })
248
351
  }
@@ -256,10 +359,9 @@ export function createSplitDiff(
256
359
  delNum++
257
360
  addNum++
258
361
 
259
- const highlightCode = getHighlightCode(language, line)
260
362
  rawChanges.push({
261
- left: newSplitDiff(DiffType.EQUAL, delNum, highlightCode),
262
- right: newSplitDiff(DiffType.EQUAL, addNum, highlightCode),
363
+ left: newSplitDiff(DiffType.EQUAL, delNum, line),
364
+ right: newSplitDiff(DiffType.EQUAL, addNum, line),
263
365
  })
264
366
  }
265
367
  }
@@ -272,7 +374,7 @@ export function createSplitDiff(
272
374
  delNum++
273
375
 
274
376
  rawChanges.push({
275
- left: newSplitDiff(DiffType.DELETE, delNum, getHighlightCode(language, line)),
377
+ left: newSplitDiff(DiffType.DELETE, delNum, line),
276
378
  right: newEmptySplitDiff(),
277
379
  })
278
380
  }
@@ -289,8 +391,9 @@ export function createSplitDiff(
289
391
 
290
392
  const [curLine, nextLine] = [curLines[j], nextLines[j]]
291
393
  const shouldRenderWords = forceInlineComparison || curLines.length === nextLines.length
292
- const leftLine = shouldRenderWords ? renderWords(nextLine, curLine, diffStyle) : curLine
293
- const rightLine = shouldRenderWords ? renderWords(curLine, nextLine, diffStyle) : nextLine
394
+ const [leftLine, rightLine] = shouldRenderWords
395
+ ? renderChangedLines(curLine, nextLine, diffStyle, forceInlineComparison)
396
+ : [curLine, nextLine]
294
397
 
295
398
  // 忽略匹配的行等价于相等
296
399
  const leftDiffType = ignoreRegex?.test(curLine) ? DiffType.EQUAL : DiffType.DELETE
@@ -298,11 +401,11 @@ export function createSplitDiff(
298
401
 
299
402
  const left
300
403
  = j < cur.count!
301
- ? newSplitDiff(leftDiffType, delNum, getHighlightCode(language, leftLine))
404
+ ? newSplitDiff(leftDiffType, delNum, leftLine!)
302
405
  : newEmptySplitDiff()
303
406
  const right
304
407
  = j < next.count!
305
- ? newSplitDiff(rightDiffType, addNum, getHighlightCode(language, rightLine))
408
+ ? newSplitDiff(rightDiffType, addNum, rightLine!)
306
409
  : newEmptySplitDiff()
307
410
 
308
411
  rawChanges.push({ left, right })
@@ -315,7 +418,7 @@ export function createSplitDiff(
315
418
  addNum++
316
419
  rawChanges.push({
317
420
  left: newEmptySplitDiff(),
318
- right: newSplitDiff(DiffType.ADD, addNum, getHighlightCode(language, line)),
421
+ right: newSplitDiff(DiffType.ADD, addNum, line),
319
422
  })
320
423
  }
321
424
  }
@@ -324,6 +427,7 @@ export function createSplitDiff(
324
427
  if (oldString === newString) {
325
428
  for (let i = 0; i < rawChanges.length; i++)
326
429
  rawChanges[i].fold = false
430
+ rawChanges.slice(0, RENDER_BATCH_SIZE).forEach(line => highlightSplitLine(line, language))
327
431
 
328
432
  return result
329
433
  }
@@ -370,6 +474,7 @@ export function createSplitDiff(
370
474
  unchanges = []
371
475
  }
372
476
  result.changes = processedChanges
477
+ result.changes.filter(line => !line.hide).slice(0, RENDER_BATCH_SIZE).forEach(line => highlightSplitLine(line, language))
373
478
 
374
479
  return result
375
480
  }
@@ -421,11 +526,9 @@ export function createUnifiedDiff(
421
526
  if (curType === DiffType.ADD)
422
527
  addNum++
423
528
 
424
- const code = getHighlightCode(language, line)
425
-
426
529
  rawChanges.push({
427
530
  type: curType,
428
- code,
531
+ code: line,
429
532
  addNum: curType === DiffType.DELETE ? undefined : addNum,
430
533
  delNum: curType === DiffType.ADD ? undefined : delNum,
431
534
  })
@@ -439,9 +542,7 @@ export function createUnifiedDiff(
439
542
  for (const line of curLines) {
440
543
  delNum++
441
544
  addNum++
442
- const code = getHighlightCode(language, line)
443
-
444
- rawChanges.push({ type: DiffType.EQUAL, code, delNum, addNum })
545
+ rawChanges.push({ type: DiffType.EQUAL, code: line, delNum, addNum })
445
546
  }
446
547
  }
447
548
 
@@ -450,28 +551,26 @@ export function createUnifiedDiff(
450
551
  if (curType === DiffType.DELETE) {
451
552
  // 下一处差异为新增,且删除与新增行数相同时,对每行依次 diff
452
553
  if (nextType === DiffType.ADD && (curLines.length === nextLines.length || forceInlineComparison)) {
554
+ const maxCount = Math.max(curLines.length, nextLines.length)
555
+ const renderedLines = Array.from({ length: maxCount }, (_, index) => renderChangedLines(curLines[index], nextLines[index], diffStyle, forceInlineComparison))
453
556
  for (let j = 0; j < curLines.length; j++) {
454
557
  const curLine = curLines[j]
455
- const nextLine = nextLines[j]
456
558
  delNum++
457
559
 
458
- const code = getHighlightCode(language, renderWords(nextLine, curLine, diffStyle))
459
560
  rawChanges.push({
460
561
  type: ignoreRegex?.test(curLine) ? DiffType.EQUAL : DiffType.DELETE,
461
- code,
562
+ code: renderedLines[j][0]!,
462
563
  delNum,
463
564
  })
464
565
  }
465
566
 
466
567
  for (let j = 0; j < nextLines.length; j++) {
467
- const curLine = curLines[j]
468
568
  const nextLine = nextLines[j]
469
569
  addNum++
470
570
 
471
- const code = getHighlightCode(language, renderWords(curLine, nextLine, diffStyle))
472
571
  rawChanges.push({
473
572
  type: ignoreRegex?.test(nextLine) ? DiffType.EQUAL : DiffType.ADD,
474
- code,
573
+ code: renderedLines[j][1]!,
475
574
  addNum,
476
575
  })
477
576
  }
@@ -483,8 +582,7 @@ export function createUnifiedDiff(
483
582
  for (const line of curLines) {
484
583
  delNum++
485
584
 
486
- const code = getHighlightCode(language, line)
487
- rawChanges.push({ type: DiffType.DELETE, code, delNum })
585
+ rawChanges.push({ type: DiffType.DELETE, code: line, delNum })
488
586
  }
489
587
  }
490
588
  }
@@ -492,9 +590,7 @@ export function createUnifiedDiff(
492
590
  if (curType === DiffType.ADD) {
493
591
  for (const line of curLines) {
494
592
  addNum++
495
- const code = getHighlightCode(language, line)
496
-
497
- rawChanges.push({ type: DiffType.ADD, code, addNum })
593
+ rawChanges.push({ type: DiffType.ADD, code: line, addNum })
498
594
  }
499
595
  }
500
596
  }
@@ -513,6 +609,7 @@ export function createUnifiedDiff(
513
609
  if (oldString === newString) {
514
610
  for (let i = 0; i < rawChanges.length; i++)
515
611
  rawChanges[i].fold = false
612
+ rawChanges.slice(0, RENDER_BATCH_SIZE).forEach(line => highlightUnifiedLine(line, language))
516
613
 
517
614
  return result
518
615
  }
@@ -551,6 +648,7 @@ export function createUnifiedDiff(
551
648
  unchanges = []
552
649
  }
553
650
  result.changes = processedChanges
651
+ result.changes.filter(line => !line.hide).slice(0, RENDER_BATCH_SIZE).forEach(line => highlightUnifiedLine(line, language))
554
652
 
555
653
  return result
556
654
  }