Skip to content

Commit dd4a544

Browse files
committed
feat: Expanding folded lines
1 parent c324796 commit dd4a544

7 files changed

Lines changed: 127 additions & 54 deletions

File tree

src/CodeDiff.vue

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { computed } from 'vue-demi'
2+
import { computed, ref, watch } from 'vue-demi'
33
import { createSplitDiff, createUnifiedDiff } from './utils'
44
import UnifiedViewer from './unified/UnifiedViewer.vue'
55
import SplitViewer from './split/SplitViewer.vue'
@@ -42,11 +42,15 @@ const newString = computed(() => {
4242
return props.noDiffLineFeed ? value.replace(/(\r\n)/g, '\n') : value
4343
})
4444
45-
const diffChange = computed(() =>
45+
const raw = computed(() =>
4646
isUnifiedViewer.value
4747
? createUnifiedDiff(oldString.value, newString.value, props.language, props.diffStyle, props.context)
4848
: createSplitDiff(oldString.value, newString.value, props.language, props.diffStyle, props.context),
4949
)
50+
const diffChange = ref(raw.value)
51+
watch(() => props, () => {
52+
diffChange.value = raw.value
53+
}, { deep: true })
5054
</script>
5155

5256
<template>
@@ -60,11 +64,9 @@ const diffChange = computed(() =>
6064
</span>
6165
</div>
6266
</div>
63-
<UnifiedViewer v-if="isUnifiedViewer" :diff-change="diffChange.changes" />
64-
<SplitViewer v-else :diff-change="diffChange.changes" />
67+
<UnifiedViewer v-if="isUnifiedViewer" :diff-change="diffChange" />
68+
<SplitViewer v-else :diff-change="diffChange" />
6569
</div>
6670
</template>
6771

68-
<style lang="scss">
69-
70-
</style>
72+
<style lang="scss"></style>

src/split/SplitLine.vue

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { SplitLineChange } from '../types'
55
const props = defineProps<{
66
splitLine: SplitLineChange
77
}>()
8+
const emit = defineEmits(['expand'])
89
910
const getCodeMarker = (type: DiffType) => {
1011
if (type === DiffType.DELETE)
@@ -16,51 +17,45 @@ const getCodeMarker = (type: DiffType) => {
1617
</script>
1718

1819
<template>
19-
<tr v-if="splitLine.fold">
20-
<td class="blob-num blob-num-hunk" colspan="1">
20+
<tr v-if="splitLine.hideIndex !== undefined && splitLine.hide">
21+
<td class="blob-num blob-num-hunk" colspan="1" @click="emit('expand', splitLine)">
2122
>
2223
</td>
2324
<td class="blob-code blob-code-inner blob-code-hunk" colspan="3" align="left">
2425
2526
</td>
2627
</tr>
27-
<tr v-else>
28+
<tr v-else-if="!splitLine.hide">
2829
<template v-for="line in [splitLine.left, splitLine.right]">
2930
<!-- eslint-disable -->
3031
<template v-if="line.type === DiffType.EMPTY">
3132
<td class="blob-num blob-num-empty empty-cell" />
3233
<td class="blob-code blob-code-empty empty-cell" />
3334
</template>
3435
<template v-else>
35-
<td
36-
class="blob-num"
37-
:class="{
38-
'blob-num-deletion': line.type === DiffType.DELETE,
39-
'blob-num-addition': line.type === DiffType.ADD,
40-
'blob-num-context': line.type === DiffType.EQUAL,
41-
}"
42-
>
36+
<td class="blob-num" :class="{
37+
'blob-num-deletion': line.type === DiffType.DELETE,
38+
'blob-num-addition': line.type === DiffType.ADD,
39+
'blob-num-context': line.type === DiffType.EQUAL,
40+
'blob-num-hunk': splitLine.hide !== undefined,
41+
42+
}">
4343
{{ line.num }}
4444
</td>
45-
<td
46-
class="blob-code"
47-
:class="{
48-
'blob-code-deletion': line.type === DiffType.DELETE,
49-
'blob-code-addition': line.type === DiffType.ADD,
50-
'blob-code-context': line.type === DiffType.EQUAL,
51-
}"
52-
>
53-
<span
54-
class="blob-code-inner blob-code-marker"
55-
:data-code-marker="getCodeMarker(line.type)"
56-
v-html="line.code"
57-
/>
45+
<td class="blob-code" :class="{
46+
'blob-code-deletion': line.type === DiffType.DELETE,
47+
'blob-code-addition': line.type === DiffType.ADD,
48+
'blob-code-context': line.type === DiffType.EQUAL,
49+
'blob-code-hunk': splitLine.hide !== undefined,
50+
51+
}">
52+
<span class="blob-code-inner blob-code-marker" :data-code-marker="getCodeMarker(line.type)"
53+
v-html="line.code" />
5854
</td>
5955
</template>
6056
<!-- eslint-enable -->
6157
</template>
6258
</tr>
6359
</template>
6460

65-
<style lang="scss">
66-
</style>
61+
<style lang="scss"></style>

src/split/SplitViewer.vue

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
<script setup lang="ts">
2-
import type { SplitViewerChange } from '../types'
2+
import type { SplitLineChange, SplitViewerChange } from '../types'
33
import SplitLine from './SplitLine.vue'
44
55
const props = defineProps<{
66
diffChange: SplitViewerChange
77
}>()
8+
9+
function expandHandler({ hideIndex }: SplitLineChange) {
10+
if (hideIndex === undefined)
11+
return
12+
props.diffChange.collector[hideIndex!].lines.forEach((line) => {
13+
line.hide = false
14+
line.fold = false
15+
})
16+
}
817
</script>
918

1019
<template>
@@ -16,7 +25,7 @@ const props = defineProps<{
1625
<col>
1726
</colgroup>
1827
<tbody>
19-
<SplitLine v-for="(item, index) in diffChange" :key="index" :split-line="item" />
28+
<SplitLine v-for="(item, index) in diffChange?.changes" :key="index" :split-line="item" @expand="expandHandler" />
2029
</tbody>
2130
</table>
2231
</template>

src/types.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ export const enum DiffType {
55
EMPTY = 'empty',
66
}
77

8+
export interface UnifiedLineUnchanges {
9+
lines: UnifiedLineChange[]
10+
fold: boolean
11+
}
12+
13+
export interface SplitLineUnchanges {
14+
lines: SplitLineChange[]
15+
fold: boolean
16+
}
17+
818
export interface DiffLine {
919
type: DiffType
1020
code?: string
@@ -15,6 +25,8 @@ export interface SplitLineChange {
1525
fold?: boolean
1626
left: DiffLine
1727
right: DiffLine
28+
hide?: boolean
29+
hideIndex?: number
1830
}
1931

2032
export interface UnifiedLineChange {
@@ -23,18 +35,23 @@ export interface UnifiedLineChange {
2335
code: string
2436
delNum?: number
2537
addNum?: number
38+
hide?: boolean
39+
hideIndex?: number
2640
}
2741

2842
export interface DiffStat {
2943
additionsNum: number
3044
deletionsNum: number
3145
}
46+
3247
export interface SplitViewerChange {
3348
changes: SplitLineChange[]
49+
collector: SplitLineUnchanges[]
3450
stat: DiffStat
3551
}
3652

3753
export interface UnifiedViewerChange {
3854
changes: UnifiedLineChange[]
55+
collector: UnifiedLineUnchanges[]
3956
stat: DiffStat
4057
}

src/unified/UnifiedLine.vue

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { DiffType } from '../types'
55
const props = defineProps<{
66
line: UnifiedLineChange
77
}>()
8+
const emit = defineEmits(['expand'])
89
910
const getCodeMarker = (type: DiffType) => {
1011
if (type === DiffType.DELETE)
@@ -16,24 +17,22 @@ const getCodeMarker = (type: DiffType) => {
1617
</script>
1718

1819
<template>
19-
<tr v-if="line.fold">
20-
<td class="blob-num blob-num-hunk">
21-
>
22-
</td>
23-
<td class="blob-num blob-num-hunk">
20+
<tr v-if="line.hideIndex !== undefined && line.hide">
21+
<td class="blob-num blob-num-hunk text-center" colspan="2" @click="emit('expand', line)">
2422
>
2523
</td>
2624
<td class="blob-code blob-code-hunk" align="left">
2725
2826
</td>
2927
</tr>
30-
<tr v-else>
28+
<tr v-else-if="!line.hide">
3129
<td
3230
class="blob-num"
3331
:class="{
3432
'blob-num-deletion': line.type === DiffType.DELETE,
3533
'blob-num-addition': line.type === DiffType.ADD,
3634
'blob-num-context': line.type === DiffType.EQUAL,
35+
'blob-num-hunk': line.hide !== undefined,
3736
}"
3837
>
3938
{{ line.delNum }}
@@ -44,6 +43,7 @@ const getCodeMarker = (type: DiffType) => {
4443
'blob-num-deletion': line.type === DiffType.DELETE,
4544
'blob-num-addition': line.type === DiffType.ADD,
4645
'blob-num-context': line.type === DiffType.EQUAL,
46+
'blob-num-hunk': line.hide !== undefined,
4747
}"
4848
>
4949
{{ line.addNum }}
@@ -54,6 +54,7 @@ const getCodeMarker = (type: DiffType) => {
5454
'blob-code-deletion': line.type === DiffType.DELETE,
5555
'blob-code-addition': line.type === DiffType.ADD,
5656
'blob-code-context': line.type === DiffType.EQUAL,
57+
'blob-code-hunk': line.hide !== undefined,
5758
}"
5859
>
5960
<span class="blob-code-inner blob-code-marker" :data-code-marker="getCodeMarker(line.type)" v-html="line.code" />

src/unified/UnifiedViewer.vue

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
<script setup lang="ts">
2-
import type { UnifiedLineChange } from '../types'
2+
import type { UnifiedLineChange, UnifiedViewerChange } from '../types'
33
import UnifiedLine from './UnifiedLine.vue'
44
55
const props = defineProps<{
6-
diffChange: UnifiedLineChange[]
6+
diffChange: UnifiedViewerChange
77
}>()
8+
9+
function expandHandler({ hideIndex }: UnifiedLineChange) {
10+
if (hideIndex === undefined)
11+
return
12+
props.diffChange.collector[hideIndex!].lines.forEach((line) => {
13+
line.hide = false
14+
line.fold = false
15+
})
16+
}
817
</script>
918

1019
<template>
1120
<table class="diff-table">
1221
<tbody>
13-
<UnifiedLine v-for="(item, index) in diffChange" :key="index" :line="item" />
22+
<UnifiedLine v-for="(item, index) in diffChange?.changes" :key="index" :line="item" @expand="expandHandler" />
1423
</tbody>
1524
</table>
1625
</template>

src/utils.ts

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Change } from 'diff'
33
import { DIFF_DELETE, DIFF_INSERT, diff_match_patch as DiffMatchPatch } from 'diff-match-patch'
44
import hljs from './highlight'
55
import { DiffType } from './types'
6-
import type { DiffLine, DiffStat, SplitLineChange, SplitViewerChange, UnifiedLineChange, UnifiedViewerChange } from './types'
6+
import type { DiffLine, DiffStat, SplitLineChange, SplitLineUnchanges, SplitViewerChange, UnifiedLineChange, UnifiedLineUnchanges, UnifiedViewerChange } from './types'
77

88
const MODIFIED_START_TAG = '<code-diff-modified>'
99
const MODIFIED_CLOSE_TAG = '</code-diff-modified>'
@@ -168,6 +168,7 @@ export function createSplitDiff(
168168
const rawChanges: SplitLineChange[] = []
169169
const result: SplitViewerChange = {
170170
changes: rawChanges,
171+
collector: [],
171172
stat: calcDiffStat(changes),
172173
}
173174

@@ -208,7 +209,6 @@ export function createSplitDiff(
208209
left = newEmptySplitDiff()
209210
right = newSplitDiff(DiffType.ADD, addNum, highlightCode)
210211
}
211-
212212
rawChanges.push({ left, right })
213213
}
214214
break
@@ -300,17 +300,35 @@ export function createSplitDiff(
300300
line.fold = true
301301
}
302302

303-
const processedChanges = []
303+
const processedChanges: SplitViewerChange['changes'] = []
304+
let unchanges: SplitLineUnchanges['lines'] = [] // collector for unchanged lines.
305+
304306
for (let i = 0; i < rawChanges.length; i++) {
305307
const line = rawChanges[i]
306308
if (line.fold === false) {
309+
if (unchanges.length) {
310+
unchanges[0].hideIndex = result.collector.length
311+
result.collector.push({
312+
lines: unchanges,
313+
fold: true,
314+
})
315+
unchanges = []
316+
}
307317
processedChanges.push(line)
308318
continue
309319
}
310-
if (line.fold === true) {
311-
if (processedChanges[processedChanges.length - 1]?.fold !== true)
312-
processedChanges.push(line)
313-
}
320+
321+
line.hide = true
322+
unchanges.push(line)
323+
processedChanges.push(line)
324+
}
325+
if (unchanges.length) {
326+
unchanges[0].hideIndex = result.collector.length
327+
result.collector.push({
328+
lines: unchanges,
329+
fold: true,
330+
})
331+
unchanges = []
314332
}
315333
result.changes = processedChanges
316334

@@ -333,6 +351,7 @@ export function createUnifiedDiff(
333351
const rawChanges: UnifiedLineChange[] = []
334352
const result: UnifiedViewerChange = {
335353
changes: rawChanges,
354+
collector: [],
336355
stat: calcDiffStat(changes),
337356
}
338357

@@ -449,16 +468,37 @@ export function createUnifiedDiff(
449468
}
450469

451470
const processedChanges = []
471+
let unchanges: UnifiedLineUnchanges['lines'] = [] // collector for unchanged lines.
472+
452473
for (let i = 0; i < rawChanges.length; i++) {
453474
const line = rawChanges[i]
454475
if (line.fold === false) {
476+
if (unchanges.length) {
477+
unchanges[0].hideIndex = result.collector.length
478+
// Keeps "hideIndex" in first element of collector
479+
// for delegating lines to expand.
480+
result.collector.push({
481+
lines: unchanges,
482+
fold: true,
483+
})
484+
unchanges = []
485+
}
455486
processedChanges.push(line)
456487
continue
457488
}
458-
if (line.fold === true) {
459-
if (i === 0 || processedChanges[processedChanges.length - 1]?.fold !== true)
460-
processedChanges.push(line)
489+
if (line.type === 'equal') {
490+
line.hide = true
491+
unchanges.push(line)
461492
}
493+
processedChanges.push(line)
494+
}
495+
if (unchanges.length) {
496+
unchanges[0].hideIndex = result.collector.length
497+
result.collector.push({
498+
lines: unchanges,
499+
fold: true,
500+
})
501+
unchanges = []
462502
}
463503
result.changes = processedChanges
464504

0 commit comments

Comments
 (0)