v-code-diff 1.13.2 → 1.14.0

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.0",
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,8 @@
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": "vitest run"
58
60
  },
59
61
  "peerDependencies": {
60
62
  "@vue/composition-api": "^1.4.9",
@@ -87,6 +89,7 @@
87
89
  "typescript": "~4.7.4",
88
90
  "vite": "^5.4.2",
89
91
  "vite-plugin-css-injected-by-js": "^2.4.0",
92
+ "vitest": "^2.1.9",
90
93
  "vue": "^3.4.38",
91
94
  "vue-i18n": "^9.14.0",
92
95
  "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,112 @@ 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
+ let changes: Diff.Change[] | undefined
78
+ if (prev.length + current.length >= 100_000 && prev.includes('\n') && current.includes('\n')) {
79
+ const prevLines = prev ? prev.replace(/\n$/, '').split('\n') : []
80
+ const currentLines = current ? current.replace(/\n$/, '').split('\n') : []
81
+ const prevLineSet = new Set(prevLines)
82
+ const currentLineSet = new Set(currentLines)
83
+ const [smallerSet, largerSet] = prevLineSet.size < currentLineSet.size
84
+ ? [prevLineSet, currentLineSet]
85
+ : [currentLineSet, prevLineSet]
86
+ let sharedLines = 0
87
+ for (const line of smallerSet) {
88
+ if (largerSet.has(line))
89
+ sharedLines++
90
+ }
91
+ const overlap = smallerSet.size ? sharedLines / smallerSet.size : 0
92
+ const exceedsDmpLineLimit = prevLineSet.size >= 40_000 || prevLineSet.size + currentLineSet.size - sharedLines >= 65_535
93
+ const changedUniqueLines = prevLineSet.size + currentLineSet.size - sharedLines * 2
94
+
95
+ if (overlap < 0.01 || (exceedsDmpLineLimit && changedUniqueLines > 200)) {
96
+ // ponytail: complex large inputs use whole-file replacements; revisit if detailed move detection becomes necessary.
97
+ changes = []
98
+ if (prev)
99
+ changes.push({ count: prevLines.length, value: prev, removed: true })
100
+ if (current)
101
+ changes.push({ count: currentLines.length, value: current, added: true })
67
102
  }
68
- return change
69
- })
103
+ else if (exceedsDmpLineLimit) {
104
+ changes = Diff.diffLines(prev, current)
105
+ }
106
+ }
107
+
108
+ if (!changes) {
109
+ const dmp = new DiffMatchPatch()
110
+ const a = dmp.diff_linesToChars_(prev, current)
111
+ const linePrev = a.chars1
112
+ const lineCurrent = a.chars2
113
+ const lineArray = a.lineArray
114
+ const diffs: any[] = dmp.diff_main(linePrev, lineCurrent, false)
115
+ dmp.diff_charsToLines_(diffs, lineArray)
116
+ changes = diffs.map((x) => {
117
+ const [type, text] = x
118
+ const count = text.replace(/\n$/, '').split('\n').length
119
+ const change: Diff.Change = {
120
+ count,
121
+ value: text,
122
+ removed: type === DIFF_DELETE,
123
+ added: type === DIFF_INSERT,
124
+ }
125
+ return change
126
+ })
127
+ }
128
+
129
+ if (prevHasFinalNewline !== currentHasFinalNewline) {
130
+ changes.push({
131
+ count: 1,
132
+ value: '',
133
+ removed: prevHasFinalNewline,
134
+ added: currentHasFinalNewline,
135
+ })
136
+ }
137
+
138
+ return changes
70
139
  }
71
140
 
72
141
  function getHighlightCode(language: string, code: string) {
73
- if (typeof document === 'undefined') {
142
+ if (typeof document === 'undefined' || language === 'plaintext') {
74
143
  return escapeHtml(code)
75
144
  .replace(new RegExp(startEntity, 'g'), '<span class="x">')
76
145
  .replace(new RegExp(closeEntity, 'g'), '</span>')
@@ -152,6 +221,28 @@ function getHighlightCode(language: string, code: string) {
152
221
  .replace(new RegExp(closeEntity, 'g'), '</span>')
153
222
  }
154
223
 
224
+ export function highlightUnifiedLine(line: UnifiedLineChange, language: string) {
225
+ if (line.highlighted)
226
+ return
227
+ line.code = getHighlightCode(language, line.code)
228
+ line.highlighted = true
229
+ }
230
+
231
+ export function highlightSplitLine(line: SplitLineChange, language: string) {
232
+ if (line.highlighted)
233
+ return
234
+ const leftCode = line.left.code
235
+ if (leftCode !== undefined)
236
+ line.left.code = getHighlightCode(language, leftCode)
237
+
238
+ if (line.right.code !== undefined) {
239
+ line.right.code = line.left.type === DiffType.EQUAL && line.right.type === DiffType.EQUAL && line.right.code === leftCode
240
+ ? line.left.code
241
+ : getHighlightCode(language, line.right.code)
242
+ }
243
+ line.highlighted = true
244
+ }
245
+
155
246
  function calcDiffStat(changes: Change[], ignoreRegex?: RegExp): DiffStat {
156
247
  const count = (s: string, c: string) => (s.match(new RegExp(c, 'g')) || []).length
157
248
  const ignoreCount = (lines: string[]) => lines.filter(line => ignoreRegex?.test(line)).length
@@ -162,12 +253,20 @@ function calcDiffStat(changes: Change[], ignoreRegex?: RegExp): DiffStat {
162
253
  let ignoreDeletionsNum = 0
163
254
  for (const change of changes) {
164
255
  if (change.added) {
256
+ if (!ignoreRegex) {
257
+ additionsNum += change.count ?? 0
258
+ continue
259
+ }
165
260
  const ignoreNum = ignoreCount(change.value.trim().split('\n'))
166
261
  additionsNum += count(change.value.trim(), '\n') + 1 - ignoreNum
167
262
  ignoreAdditionsNum += ignoreNum
168
263
  continue
169
264
  }
170
265
  if (change.removed) {
266
+ if (!ignoreRegex) {
267
+ deletionsNum += change.count ?? 0
268
+ continue
269
+ }
171
270
  const ignoreNum = ignoreCount(change.value.trim().split('\n'))
172
271
  deletionsNum += count(change.value.trim(), '\n') + 1 - ignoreNum
173
272
  ignoreDeletionsNum += ignoreNum
@@ -224,25 +323,24 @@ export function createSplitDiff(
224
323
  let left: DiffLine = newEmptySplitDiff()
225
324
  let right: DiffLine = newEmptySplitDiff()
226
325
 
227
- const highlightCode = getHighlightCode(language, line)
228
326
  if (curType === DiffType.EQUAL) {
229
327
  delNum++
230
328
  addNum++
231
329
 
232
- left = newSplitDiff(DiffType.EQUAL, delNum, highlightCode)
233
- right = newSplitDiff(DiffType.EQUAL, addNum, highlightCode)
330
+ left = newSplitDiff(DiffType.EQUAL, delNum, line)
331
+ right = newSplitDiff(DiffType.EQUAL, addNum, line)
234
332
  }
235
333
  if (curType === DiffType.DELETE) {
236
334
  delNum++
237
335
 
238
- left = newSplitDiff(DiffType.DELETE, delNum, highlightCode)
336
+ left = newSplitDiff(DiffType.DELETE, delNum, line)
239
337
  right = newEmptySplitDiff()
240
338
  }
241
339
  if (curType === DiffType.ADD) {
242
340
  addNum++
243
341
 
244
342
  left = newEmptySplitDiff()
245
- right = newSplitDiff(DiffType.ADD, addNum, highlightCode)
343
+ right = newSplitDiff(DiffType.ADD, addNum, line)
246
344
  }
247
345
  rawChanges.push({ left, right })
248
346
  }
@@ -256,10 +354,9 @@ export function createSplitDiff(
256
354
  delNum++
257
355
  addNum++
258
356
 
259
- const highlightCode = getHighlightCode(language, line)
260
357
  rawChanges.push({
261
- left: newSplitDiff(DiffType.EQUAL, delNum, highlightCode),
262
- right: newSplitDiff(DiffType.EQUAL, addNum, highlightCode),
358
+ left: newSplitDiff(DiffType.EQUAL, delNum, line),
359
+ right: newSplitDiff(DiffType.EQUAL, addNum, line),
263
360
  })
264
361
  }
265
362
  }
@@ -272,7 +369,7 @@ export function createSplitDiff(
272
369
  delNum++
273
370
 
274
371
  rawChanges.push({
275
- left: newSplitDiff(DiffType.DELETE, delNum, getHighlightCode(language, line)),
372
+ left: newSplitDiff(DiffType.DELETE, delNum, line),
276
373
  right: newEmptySplitDiff(),
277
374
  })
278
375
  }
@@ -289,8 +386,9 @@ export function createSplitDiff(
289
386
 
290
387
  const [curLine, nextLine] = [curLines[j], nextLines[j]]
291
388
  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
389
+ const [leftLine, rightLine] = shouldRenderWords
390
+ ? renderChangedLines(curLine, nextLine, diffStyle, forceInlineComparison)
391
+ : [curLine, nextLine]
294
392
 
295
393
  // 忽略匹配的行等价于相等
296
394
  const leftDiffType = ignoreRegex?.test(curLine) ? DiffType.EQUAL : DiffType.DELETE
@@ -298,11 +396,11 @@ export function createSplitDiff(
298
396
 
299
397
  const left
300
398
  = j < cur.count!
301
- ? newSplitDiff(leftDiffType, delNum, getHighlightCode(language, leftLine))
399
+ ? newSplitDiff(leftDiffType, delNum, leftLine!)
302
400
  : newEmptySplitDiff()
303
401
  const right
304
402
  = j < next.count!
305
- ? newSplitDiff(rightDiffType, addNum, getHighlightCode(language, rightLine))
403
+ ? newSplitDiff(rightDiffType, addNum, rightLine!)
306
404
  : newEmptySplitDiff()
307
405
 
308
406
  rawChanges.push({ left, right })
@@ -315,7 +413,7 @@ export function createSplitDiff(
315
413
  addNum++
316
414
  rawChanges.push({
317
415
  left: newEmptySplitDiff(),
318
- right: newSplitDiff(DiffType.ADD, addNum, getHighlightCode(language, line)),
416
+ right: newSplitDiff(DiffType.ADD, addNum, line),
319
417
  })
320
418
  }
321
419
  }
@@ -324,6 +422,7 @@ export function createSplitDiff(
324
422
  if (oldString === newString) {
325
423
  for (let i = 0; i < rawChanges.length; i++)
326
424
  rawChanges[i].fold = false
425
+ rawChanges.slice(0, RENDER_BATCH_SIZE).forEach(line => highlightSplitLine(line, language))
327
426
 
328
427
  return result
329
428
  }
@@ -370,6 +469,7 @@ export function createSplitDiff(
370
469
  unchanges = []
371
470
  }
372
471
  result.changes = processedChanges
472
+ result.changes.filter(line => !line.hide).slice(0, RENDER_BATCH_SIZE).forEach(line => highlightSplitLine(line, language))
373
473
 
374
474
  return result
375
475
  }
@@ -421,11 +521,9 @@ export function createUnifiedDiff(
421
521
  if (curType === DiffType.ADD)
422
522
  addNum++
423
523
 
424
- const code = getHighlightCode(language, line)
425
-
426
524
  rawChanges.push({
427
525
  type: curType,
428
- code,
526
+ code: line,
429
527
  addNum: curType === DiffType.DELETE ? undefined : addNum,
430
528
  delNum: curType === DiffType.ADD ? undefined : delNum,
431
529
  })
@@ -439,9 +537,7 @@ export function createUnifiedDiff(
439
537
  for (const line of curLines) {
440
538
  delNum++
441
539
  addNum++
442
- const code = getHighlightCode(language, line)
443
-
444
- rawChanges.push({ type: DiffType.EQUAL, code, delNum, addNum })
540
+ rawChanges.push({ type: DiffType.EQUAL, code: line, delNum, addNum })
445
541
  }
446
542
  }
447
543
 
@@ -450,28 +546,26 @@ export function createUnifiedDiff(
450
546
  if (curType === DiffType.DELETE) {
451
547
  // 下一处差异为新增,且删除与新增行数相同时,对每行依次 diff
452
548
  if (nextType === DiffType.ADD && (curLines.length === nextLines.length || forceInlineComparison)) {
549
+ const maxCount = Math.max(curLines.length, nextLines.length)
550
+ const renderedLines = Array.from({ length: maxCount }, (_, index) => renderChangedLines(curLines[index], nextLines[index], diffStyle, forceInlineComparison))
453
551
  for (let j = 0; j < curLines.length; j++) {
454
552
  const curLine = curLines[j]
455
- const nextLine = nextLines[j]
456
553
  delNum++
457
554
 
458
- const code = getHighlightCode(language, renderWords(nextLine, curLine, diffStyle))
459
555
  rawChanges.push({
460
556
  type: ignoreRegex?.test(curLine) ? DiffType.EQUAL : DiffType.DELETE,
461
- code,
557
+ code: renderedLines[j][0]!,
462
558
  delNum,
463
559
  })
464
560
  }
465
561
 
466
562
  for (let j = 0; j < nextLines.length; j++) {
467
- const curLine = curLines[j]
468
563
  const nextLine = nextLines[j]
469
564
  addNum++
470
565
 
471
- const code = getHighlightCode(language, renderWords(curLine, nextLine, diffStyle))
472
566
  rawChanges.push({
473
567
  type: ignoreRegex?.test(nextLine) ? DiffType.EQUAL : DiffType.ADD,
474
- code,
568
+ code: renderedLines[j][1]!,
475
569
  addNum,
476
570
  })
477
571
  }
@@ -483,8 +577,7 @@ export function createUnifiedDiff(
483
577
  for (const line of curLines) {
484
578
  delNum++
485
579
 
486
- const code = getHighlightCode(language, line)
487
- rawChanges.push({ type: DiffType.DELETE, code, delNum })
580
+ rawChanges.push({ type: DiffType.DELETE, code: line, delNum })
488
581
  }
489
582
  }
490
583
  }
@@ -492,9 +585,7 @@ export function createUnifiedDiff(
492
585
  if (curType === DiffType.ADD) {
493
586
  for (const line of curLines) {
494
587
  addNum++
495
- const code = getHighlightCode(language, line)
496
-
497
- rawChanges.push({ type: DiffType.ADD, code, addNum })
588
+ rawChanges.push({ type: DiffType.ADD, code: line, addNum })
498
589
  }
499
590
  }
500
591
  }
@@ -513,6 +604,7 @@ export function createUnifiedDiff(
513
604
  if (oldString === newString) {
514
605
  for (let i = 0; i < rawChanges.length; i++)
515
606
  rawChanges[i].fold = false
607
+ rawChanges.slice(0, RENDER_BATCH_SIZE).forEach(line => highlightUnifiedLine(line, language))
516
608
 
517
609
  return result
518
610
  }
@@ -551,6 +643,7 @@ export function createUnifiedDiff(
551
643
  unchanges = []
552
644
  }
553
645
  result.changes = processedChanges
646
+ result.changes.filter(line => !line.hide).slice(0, RENDER_BATCH_SIZE).forEach(line => highlightUnifiedLine(line, language))
554
647
 
555
648
  return result
556
649
  }