@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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yuhufe/wtool-vdiff",
3
3
  "private": false,
4
- "version": "0.0.1",
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].content)
60
- const modifiedCode = computed(() => diffPair.value[1].content)
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">{{ filename }}</div>
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, reactive } from 'vue'
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
- const filename = computed(() => props.diffPair[0].filename)
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.split('\n')
98
- const modLines = pair[1].content.split('\n')
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
- * Monaco diff editor 需要两个完整文件内容,而 patch 只记录变更片段。
60
- * 未改动的行(hunk 之外)用空行填充,使两侧行号与 hunk 声明的起始位置对齐,
61
- * 从而让 Monaco 能在正确的行号位置渲染增删差异。
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
- // 追踪两侧各自已写到的行号(1-based)
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 之前未覆盖的行用空行填充,使光标推进到 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
- // context 行:两侧都保留原文
111
+ flushPending()
97
112
  origLines.push(content)
98
113
  modLines.push(content)
99
114
  origCursor++
100
115
  modCursor++
101
116
  } else if (prefix === '-') {
102
- // 删除行:original 保留,modified 用空行占位
103
- origLines.push(content)
104
- modLines.push('')
117
+ pendingDel.push(content)
105
118
  origCursor++
106
- modCursor++
107
119
  } else if (prefix === '+') {
108
- // 新增行:modified 保留,original 用空行占位
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