@yuhufe/wtool-vdiff 0.0.1 → 0.0.3
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/dist/DiffViewer/DiffViewer.vue.d.ts +1 -1
- package/dist/DiffViewer/TopBar.vue.d.ts +2 -2
- package/dist/DiffViewer/utils/patch2Pair.d.ts +6 -3
- package/dist/types.d.ts +1 -1
- package/dist/wtool-vdiff.cjs.js +34 -34
- package/dist/wtool-vdiff.css +1 -1
- package/dist/wtool-vdiff.es.js +2805 -2777
- package/package.json +3 -2
- package/src/DiffViewer/DiffViewer.vue +2 -2
- package/src/DiffViewer/TopBar.vue +72 -6
- package/src/DiffViewer/utils/autoHeight.ts +2 -2
- package/src/DiffViewer/utils/patch2Pair.ts +25 -14
- package/src/types.ts +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yuhufe/wtool-vdiff",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Monaco diff viewer as Vue 3 Web Component",
|
|
7
7
|
"keywords": [
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"dev:lib": "vite build --watch --mode development",
|
|
17
17
|
"build:site": "vite build --mode production --config site/vite.config.ts",
|
|
18
18
|
"dev:site": "vite --config site/vite.config.ts",
|
|
19
|
-
"build": "vite build --mode production"
|
|
19
|
+
"build": "vite build --mode production",
|
|
20
|
+
"pub": "npm run build && npm publish"
|
|
20
21
|
},
|
|
21
22
|
"main": "./dist/wtool-vdiff.cjs.js",
|
|
22
23
|
"module": "./dist/wtool-vdiff.es.js",
|
|
@@ -56,8 +56,8 @@ const initDiff = function () {
|
|
|
56
56
|
initDiff()
|
|
57
57
|
console.log(`[DiffViewer] patch耗时: ${(performance.now() - _renderStart).toFixed(2)} ms`)
|
|
58
58
|
|
|
59
|
-
const originalCode = computed(() => diffPair.value[0]
|
|
60
|
-
const modifiedCode = computed(() => diffPair.value[1]
|
|
59
|
+
const originalCode = computed(() => diffPair.value[0]?.content ?? '')
|
|
60
|
+
const modifiedCode = computed(() => diffPair.value[1]?.content ?? '')
|
|
61
61
|
|
|
62
62
|
const { funcs, registerFunc } = useDiffViewer({ isMaster: true })
|
|
63
63
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="top-bar-wrap">
|
|
3
3
|
<div class="title-area">
|
|
4
|
-
<div class="filename">{{
|
|
4
|
+
<div class="filename">{{ filenameDisplay }}</div>
|
|
5
|
+
<span :class="['diff-type-tag', diffType]">{{ diffTypeLabel }}</span>
|
|
5
6
|
<div class="diff-line-num">
|
|
6
|
-
<div class="add">+{{ changed.added }}</div>
|
|
7
|
-
<div class="del">-{{ changed.removed }}</div>
|
|
7
|
+
<div class="add" v-if="diffType !== 'del'">+{{ changed.added }}</div>
|
|
8
|
+
<div class="del" v-if="diffType !== 'add'">-{{ changed.removed }}</div>
|
|
8
9
|
</div>
|
|
9
10
|
</div>
|
|
10
11
|
<div class="toolbar">
|
|
@@ -21,21 +22,53 @@
|
|
|
21
22
|
</template>
|
|
22
23
|
|
|
23
24
|
<script setup lang="ts">
|
|
24
|
-
import { computed, ref, onMounted
|
|
25
|
+
import { computed, ref, onMounted } from 'vue'
|
|
25
26
|
import { useDiffViewer } from './useDiffView'
|
|
26
27
|
|
|
27
28
|
const { funcs, registerFunc } = useDiffViewer()
|
|
28
29
|
|
|
29
30
|
const props = withDefaults(
|
|
30
31
|
defineProps<{
|
|
31
|
-
diffPair?: { filename: string; content: string }[]
|
|
32
|
+
diffPair?: { filename: string; content: string | null }[]
|
|
32
33
|
}>(),
|
|
33
34
|
{
|
|
34
35
|
diffPair: () => [],
|
|
35
36
|
}
|
|
36
37
|
)
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
type DiffType = 'changed' | 'add' | 'del' | 'rename'
|
|
40
|
+
|
|
41
|
+
const diffType = computed<DiffType>(() => {
|
|
42
|
+
const [original, modified] = props.diffPair
|
|
43
|
+
if (!original || !modified) return 'changed'
|
|
44
|
+
|
|
45
|
+
const originalNull = original.content === null
|
|
46
|
+
const modifiedNull = modified.content === null
|
|
47
|
+
|
|
48
|
+
if (originalNull && !modifiedNull) return 'add'
|
|
49
|
+
if (!originalNull && modifiedNull) return 'del'
|
|
50
|
+
if (original.filename !== modified.filename) return 'rename'
|
|
51
|
+
return 'changed'
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const diffTypeLabel = computed(() => {
|
|
55
|
+
const map: Record<DiffType, string> = {
|
|
56
|
+
changed: 'changed',
|
|
57
|
+
add: 'added',
|
|
58
|
+
del: 'deleted',
|
|
59
|
+
rename: 'renamed',
|
|
60
|
+
}
|
|
61
|
+
return map[diffType.value]
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const filenameDisplay = computed(() => {
|
|
65
|
+
const [original, modified] = props.diffPair
|
|
66
|
+
if (!original) return ''
|
|
67
|
+
if (diffType.value === 'rename') {
|
|
68
|
+
return `${original.filename} → ${modified.filename}`
|
|
69
|
+
}
|
|
70
|
+
return original.filename
|
|
71
|
+
})
|
|
39
72
|
|
|
40
73
|
const { viewed, rawed, canUnchangeVisible } = funcs
|
|
41
74
|
const onViewedChange = function (evt) {
|
|
@@ -79,12 +112,18 @@ registerFunc({
|
|
|
79
112
|
flex: 1;
|
|
80
113
|
display: flex;
|
|
81
114
|
align-items: center;
|
|
115
|
+
overflow: hidden;
|
|
82
116
|
|
|
83
117
|
.filename {
|
|
84
118
|
font-size: 14px;
|
|
119
|
+
line-height: 16px;
|
|
120
|
+
overflow: hidden;
|
|
121
|
+
text-overflow: ellipsis;
|
|
122
|
+
white-space: nowrap;
|
|
85
123
|
}
|
|
86
124
|
|
|
87
125
|
.diff-line-num {
|
|
126
|
+
flex-shrink: 0;
|
|
88
127
|
display: inline-flex;
|
|
89
128
|
font-size: 12px;
|
|
90
129
|
font-weight: bold;
|
|
@@ -118,4 +157,31 @@ registerFunc({
|
|
|
118
157
|
}
|
|
119
158
|
}
|
|
120
159
|
}
|
|
160
|
+
|
|
161
|
+
.diff-type-tag {
|
|
162
|
+
flex-shrink: 0;
|
|
163
|
+
display: block;
|
|
164
|
+
font-size: 10px;
|
|
165
|
+
font-weight: 600;
|
|
166
|
+
padding: 2px 4px;
|
|
167
|
+
border-radius: 2px;
|
|
168
|
+
margin-left: 4px;
|
|
169
|
+
|
|
170
|
+
&.changed {
|
|
171
|
+
background-color: #ddf4ff;
|
|
172
|
+
color: #0969da;
|
|
173
|
+
}
|
|
174
|
+
&.add {
|
|
175
|
+
background-color: #dafbe1;
|
|
176
|
+
color: #1a7f37;
|
|
177
|
+
}
|
|
178
|
+
&.del {
|
|
179
|
+
background-color: #ffebe9;
|
|
180
|
+
color: #d1242f;
|
|
181
|
+
}
|
|
182
|
+
&.rename {
|
|
183
|
+
background-color: #fff8c5;
|
|
184
|
+
color: #9a6700;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
121
187
|
</style>
|
|
@@ -94,8 +94,8 @@ const autoHeightPair = function ({
|
|
|
94
94
|
} & CommonParams): number {
|
|
95
95
|
if (!pair || pair.length < 2) return minLine
|
|
96
96
|
|
|
97
|
-
const origLines = pair[0].content
|
|
98
|
-
const modLines = pair[1].content
|
|
97
|
+
const origLines = pair[0].content?.split('\n') ?? []
|
|
98
|
+
const modLines = pair[1].content?.split('\n') ?? []
|
|
99
99
|
const totalLines = Math.max(origLines.length, modLines.length)
|
|
100
100
|
|
|
101
101
|
if (unchangedVisiable) {
|
|
@@ -56,9 +56,12 @@ export function parseHunks(patch: string): { origFilename: string; modFilename:
|
|
|
56
56
|
/**
|
|
57
57
|
* 将 unified diff patch 转换为 [original, modified] 文件对。
|
|
58
58
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
59
|
+
* 策略:
|
|
60
|
+
* - hunk 之外的行(patch 未提供内容):两侧各补一个空行撑开行号,
|
|
61
|
+
* Monaco hideUnchangedRegions 会将这些相同的空行折叠并显示正确的行号范围
|
|
62
|
+
* - context 行(' '):两侧均写入真实内容
|
|
63
|
+
* - 连续的删除('-')/ 新增('+')块:收集后成对对齐写入,
|
|
64
|
+
* 行数较少的一侧补空行,使 Monaco 能在同一视觉行渲染替换关系
|
|
62
65
|
*/
|
|
63
66
|
export const patch2Pair = function (patch: string): FilePair[] {
|
|
64
67
|
if (!patch) {
|
|
@@ -73,12 +76,24 @@ export const patch2Pair = function (patch: string): FilePair[] {
|
|
|
73
76
|
const origLines: string[] = []
|
|
74
77
|
const modLines: string[] = []
|
|
75
78
|
|
|
76
|
-
|
|
79
|
+
const pendingDel: string[] = []
|
|
80
|
+
const pendingAdd: string[] = []
|
|
81
|
+
|
|
82
|
+
const flushPending = () => {
|
|
83
|
+
const maxLen = Math.max(pendingDel.length, pendingAdd.length)
|
|
84
|
+
for (let i = 0; i < maxLen; i++) {
|
|
85
|
+
origLines.push(pendingDel[i] ?? '')
|
|
86
|
+
modLines.push(pendingAdd[i] ?? '')
|
|
87
|
+
}
|
|
88
|
+
pendingDel.length = 0
|
|
89
|
+
pendingAdd.length = 0
|
|
90
|
+
}
|
|
91
|
+
|
|
77
92
|
let origCursor = 1
|
|
78
93
|
let modCursor = 1
|
|
79
94
|
|
|
80
95
|
for (const hunk of hunks) {
|
|
81
|
-
// hunk
|
|
96
|
+
// hunk 之前(或两个 hunk 之间)的 gap:两侧各补空行对齐行号
|
|
82
97
|
while (origCursor < hunk.origStart) {
|
|
83
98
|
origLines.push('')
|
|
84
99
|
origCursor++
|
|
@@ -93,25 +108,21 @@ export const patch2Pair = function (patch: string): FilePair[] {
|
|
|
93
108
|
const content = line.slice(1)
|
|
94
109
|
|
|
95
110
|
if (prefix === ' ') {
|
|
96
|
-
|
|
111
|
+
flushPending()
|
|
97
112
|
origLines.push(content)
|
|
98
113
|
modLines.push(content)
|
|
99
114
|
origCursor++
|
|
100
115
|
modCursor++
|
|
101
116
|
} else if (prefix === '-') {
|
|
102
|
-
|
|
103
|
-
origLines.push(content)
|
|
104
|
-
modLines.push('')
|
|
117
|
+
pendingDel.push(content)
|
|
105
118
|
origCursor++
|
|
106
|
-
modCursor++
|
|
107
119
|
} else if (prefix === '+') {
|
|
108
|
-
|
|
109
|
-
origLines.push('')
|
|
110
|
-
modLines.push(content)
|
|
111
|
-
origCursor++
|
|
120
|
+
pendingAdd.push(content)
|
|
112
121
|
modCursor++
|
|
113
122
|
}
|
|
114
123
|
}
|
|
124
|
+
|
|
125
|
+
flushPending()
|
|
115
126
|
}
|
|
116
127
|
|
|
117
128
|
return [
|
package/src/types.ts
CHANGED
|
@@ -4,7 +4,7 @@ export type DiffEditorOptions = Monaco.editor.IStandaloneDiffEditorConstructionO
|
|
|
4
4
|
export type ModelOptions = Monaco.editor.ITextModelUpdateOptions
|
|
5
5
|
|
|
6
6
|
export interface WtoolDiffViewerProps {
|
|
7
|
-
diffPair?: { filename: string; content: string }[]
|
|
7
|
+
diffPair?: { filename: string; content: string | null }[]
|
|
8
8
|
diffPatch?: string
|
|
9
9
|
language?: string
|
|
10
10
|
options?: DiffEditorOptions
|