Skip to content
This repository was archived by the owner on Feb 10, 2025. It is now read-only.

Commit fc37241

Browse files
authored
Merge pull request #3 from hoiheart/develop
Develop
2 parents 8fcb5e5 + 875659c commit fc37241

File tree

5 files changed

+77
-43
lines changed

5 files changed

+77
-43
lines changed

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,22 @@ Vue diff viewer plugin
1515
* [props](#props)
1616
- [Custom theme](#custom-theme)
1717
- [Extend languages](#extend-languages)
18+
- [Large text comparison](#large-text-comparison)
1819

1920
## Introduction
2021

2122
<img src="https://user-images.githubusercontent.com/25652218/104784360-7520e600-57cb-11eb-8abc-ce81dd309e05.png" alt="screenshot" style="max-width:100%;">
2223

2324
You can see the difference between the two codes with the `vue-diff` plugin.
24-
This plugin dependent on <a href="https://github.com/kpdecker/jsdiff">diff</a> and <a href="https://github.com/highlightjs/highlight.js/">highlight.js</a>, shows similar results to other diff viewers (e.g., Github Desktop).
25+
This plugin dependent on <a href="https://github.com/JackuB/diff-match-patch">diff-match-patch</a> and <a href="https://github.com/highlightjs/highlight.js/">highlight.js</a>, shows similar results to other diff viewers (e.g., Github Desktop).
2526
Here is the <a href="https://hoiheart.github.io/vue-diff/demo/index.html" target="_blank" style="font-size: 1.2em; text-decoration: underline;">demo</a>
2627

2728
## Features
2829

2930
* [x] Support split / unified mode
3031
* [x] Support multiple languages and can be extended
3132
* [X] Support two themes (dark / light) and can be customized
33+
* [ ] Virtual scroll for large text comparison
3234
* [ ] Support IE11 (IE 11 support for Vue@3 is still pending)
3335

3436
## Install plugin
@@ -145,4 +147,8 @@ VueDiff.hljs.registerLanguage('yaml', yaml)
145147
app.use(VueDiff)
146148
```
147149

148-
> <a href="https://github.com/highlightjs/highlight.js/blob/master/SUPPORTED_LANGUAGES.md">Check supported languages of Highlight.js</a>
150+
> <a href="https://github.com/highlightjs/highlight.js/blob/master/SUPPORTED_LANGUAGES.md">Check supported languages of Highlight.js</a>
151+
152+
## Large text comparison
153+
154+
⚠️ It's still hard to compare large texts. Virtual scroll for Vue3 must be created or found.

package-lock.json

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"vue": "^3.0.0"
3636
},
3737
"dependencies": {
38-
"diff": "^5.0.0",
38+
"diff-match-patch": "^1.0.5",
3939
"highlight.js": "^10.5.0"
4040
},
4141
"devDependencies": {
@@ -46,7 +46,7 @@
4646
"@rollup/plugin-node-resolve": "^10.0.0",
4747
"@rollup/plugin-typescript": "^6.1.0",
4848
"@semantic-release/git": "^9.0.0",
49-
"@types/diff": "^5.0.0",
49+
"@types/diff-match-patch": "^1.0.32",
5050
"@types/highlight.js": "^10.1.0",
5151
"@types/jest": "^24.0.19",
5252
"@typescript-eslint/eslint-plugin": "^2.33.0",

src/Diff.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,13 @@ export default defineComponent({
5757
() => props.prev,
5858
() => props.current
5959
], () => {
60-
lines.value = renderLines(props.mode, props.prev, props.current)
60+
const render = renderLines(props.mode, props.prev, props.current)
61+
62+
if (render.length > 1000) {
63+
console.warn('Comparison of many lines is not recommended because rendering delays occur.')
64+
}
65+
66+
lines.value = render
6167
}, { immediate: true })
6268
6369
return { lines }

src/utils.ts

Lines changed: 52 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
1-
import * as Diff from 'diff'
1+
import { diff_match_patch as DiffMatchPatch } from 'diff-match-patch'
22
import hljs from './highlight'
33

44
import type { Ref } from 'vue'
5-
import type { Change } from 'diff'
5+
import type { Diff } from 'diff-match-patch'
66

77
type Mode = 'split' | 'unified'
88
type Theme = 'dark' | 'light' | 'custom'
99
type Role = 'prev' | 'current' | 'unified'
1010

11+
enum Type {
12+
removed = -1,
13+
equal = 0,
14+
added = 1,
15+
disabled = 2
16+
}
17+
1118
interface Line {
12-
type: 'added' | 'removed' | 'equal' | 'disabled';
19+
type: string;
1320
lineNum?: number;
1421
value?: string;
1522
chkWords?: boolean;
1623
}
1724

1825
type Lines = Array<Line>
19-
type Diffs = Array<Change>
26+
type Diffs = Array<Diff>
2027

2128
const MODIFIED_START_TAG = '<vue-diff-modified>'
2229
const MODIFIED_CLOSE_TAG = '</vue-diff-modified>'
@@ -25,9 +32,9 @@ const MODIFIED_CLOSE_TAG = '</vue-diff-modified>'
2532
* Get diff type
2633
* @param diff
2734
*/
28-
const getDiffType = (diff: Change) => {
29-
if (!diff.count) return 'disabled'
30-
return diff.added ? 'added' : diff.removed ? 'removed' : 'equal'
35+
const getDiffType = (type: Type) => {
36+
if (!Type[type]) return 'disabled'
37+
return Type[type]
3138
}
3239

3340
/**
@@ -42,28 +49,28 @@ const getSplitLines = (diffsMap: Array<Diffs>): Array<Lines> => {
4249
}
4350

4451
diffsMap.map((diffs) => {
45-
const prevLines = diffs[0].value.replace(/\n$/, '').split('\n')
46-
const currentLines = diffs[1].value.replace(/\n$/, '').split('\n')
52+
const prevLines = diffs[0][1].replace(/\n$/, '').split('\n')
53+
const currentLines = diffs[1][1].replace(/\n$/, '').split('\n')
4754
const loopCount = Math.max(prevLines.length, currentLines.length)
4855

4956
for (let i = 0; i < loopCount; i++) {
50-
const hasPrevLine = getDiffType(diffs[0]) !== 'disabled'
51-
const hasCurrentLine = getDiffType(diffs[1]) !== 'disabled'
57+
const hasPrevLine = getDiffType(diffs[0][0]) !== 'disabled' && typeof prevLines[i] !== 'undefined'
58+
const hasCurrentLine = getDiffType(diffs[1][0]) !== 'disabled' && typeof currentLines[i] !== 'undefined'
5259

5360
if (hasPrevLine) lineNum.prev = lineNum.prev + 1
5461
if (hasCurrentLine) lineNum.current = lineNum.current + 1
5562

56-
const chkWords = Boolean(diffs[0].count === diffs[1].count && getDiffType(diffs[0]).match(/added|removed/) && getDiffType(diffs[1]).match(/added|removed/))
63+
const chkWords = Boolean(getDiffType(diffs[0][0]).match(/added|removed/) && getDiffType(diffs[1][0]).match(/added|removed/))
5764

5865
result.push([
5966
{
60-
type: getDiffType(diffs[0]),
67+
type: hasPrevLine ? getDiffType(diffs[0][0]) : 'disabled',
6168
lineNum: hasPrevLine ? lineNum.prev : undefined,
6269
value: hasPrevLine ? prevLines[i] : undefined,
6370
chkWords
6471
},
6572
{
66-
type: getDiffType(diffs[1]),
73+
type: hasCurrentLine ? getDiffType(diffs[1][0]) : 'disabled',
6774
lineNum: hasCurrentLine ? lineNum.current : undefined,
6875
value: hasCurrentLine ? currentLines[i] : undefined,
6976
chkWords
@@ -84,33 +91,33 @@ const getUnifiedLines = (diffsMap: Array<Diffs>): Array<Lines> => {
8491
let lineNum = 0
8592

8693
diffsMap.map((diffs) => {
87-
const prevLines = diffs[0].value.replace(/\n$/, '').split('\n')
88-
const currentLines = diffs[1].value.replace(/\n$/, '').split('\n')
94+
const prevLines = diffs[0][1].replace(/\n$/, '').split('\n')
95+
const currentLines = diffs[1][1].replace(/\n$/, '').split('\n')
8996

9097
prevLines.map(value => {
91-
const type = getDiffType(diffs[0])
98+
const type = getDiffType(diffs[0][0])
9299

93100
if (type !== 'removed') return
94101

95102
result.push([
96103
{
97-
type: getDiffType(diffs[0]),
104+
type: getDiffType(diffs[0][0]),
98105
lineNum: undefined,
99106
value: value
100107
}
101108
])
102109
})
103110

104111
currentLines.map(value => {
105-
const type = getDiffType(diffs[1])
112+
const type = getDiffType(diffs[1][0])
106113

107114
if (type === 'disabled') return
108115

109116
lineNum = lineNum + 1
110117

111118
result.push([
112119
{
113-
type: getDiffType(diffs[1]),
120+
type: getDiffType(diffs[1][0]),
114121
lineNum,
115122
value: value
116123
}
@@ -128,11 +135,22 @@ const getUnifiedLines = (diffsMap: Array<Diffs>): Array<Lines> => {
128135
* @param current
129136
*/
130137
const renderLines = (mode: Mode, prev: string, current: string): Array<Lines> => {
138+
function diffLines (prev: string, current: string) {
139+
const dmp = new DiffMatchPatch()
140+
const a = dmp.diff_linesToChars_(prev, current)
141+
const linePrev = a.chars1
142+
const lineCurrent = a.chars2
143+
const lineArray = a.lineArray
144+
const diffs = dmp.diff_main(linePrev, lineCurrent, false)
145+
dmp.diff_charsToLines_(diffs, lineArray)
146+
return diffs
147+
}
148+
131149
/**
132150
* stacked prev, current data
133151
*/
134-
const diffsMap = Diff.diffLines(prev, current).reduce((acc: Array<Diffs>, curr) => {
135-
const type = getDiffType(curr)
152+
const diffsMap = diffLines(prev, current).reduce((acc: Array<Diffs>, curr) => {
153+
const type = getDiffType(curr[0])
136154

137155
if (type === 'equal') {
138156
acc.push([curr]) // Push index 0
@@ -143,7 +161,8 @@ const renderLines = (mode: Mode, prev: string, current: string): Array<Lines> =>
143161
}
144162

145163
if (type === 'added') {
146-
if (acc.length && acc[acc.length - 1][0] && acc[acc.length - 1][0].removed) {
164+
const prev = acc.length && acc[acc.length - 1][0] ? acc[acc.length - 1][0] : null
165+
if (prev && getDiffType(prev[0]) === 'removed') {
147166
acc[acc.length - 1].push(curr) // Push index 1 if index 0 has removed data in last array
148167
} else {
149168
acc.push([curr]) // Push index 0
@@ -159,14 +178,14 @@ const renderLines = (mode: Mode, prev: string, current: string): Array<Lines> =>
159178
diffsMap.map((diffs) => {
160179
if (diffs.length > 1) return // Return if has index 0, 1
161180

162-
const type = getDiffType(diffs[0])
181+
const type = getDiffType(diffs[0][0])
163182

164183
if (type === 'added') {
165-
diffs.unshift({ value: '' }) // Set empty data
184+
diffs.unshift([2, '']) // Set empty data
166185
} else if (type === 'removed') {
167-
diffs.push({ value: '' }) // Set empty data
186+
diffs.push([2, '']) // Set empty data
168187
} else if (type === 'equal') {
169-
diffs.push({ ...diffs[0] }) // Set same data
188+
diffs.push([...diffs[0]]) // Set same data
170189
}
171190
})
172191

@@ -191,8 +210,11 @@ const renderWords = (prev: string, current: string) => {
191210
/**
192211
* Set modified tags in changed words (removed -> added)
193212
*/
194-
return Diff.diffWords(prev, current).filter(word => getDiffType(word) !== 'removed').map(word => {
195-
return getDiffType(word) === 'added' ? `${MODIFIED_START_TAG}${word.value}${MODIFIED_CLOSE_TAG}` : word.value
213+
const dmp = new DiffMatchPatch()
214+
const diff = dmp.diff_main(prev, current)
215+
dmp.diff_cleanupSemantic(diff)
216+
return diff.filter(result => getDiffType(result[0]) !== 'removed').map(result => {
217+
return getDiffType(result[0]) === 'added' ? `${MODIFIED_START_TAG}${result[1]}${MODIFIED_CLOSE_TAG}` : result[1]
196218
}).join('')
197219
}
198220

@@ -269,4 +291,4 @@ const setHighlightCode = ({ highlightCode, language, code }: { highlightCode: Re
269291
}
270292

271293
export { MODIFIED_START_TAG, MODIFIED_CLOSE_TAG, getDiffType, getSplitLines, getUnifiedLines, renderLines, renderWords, setHighlightCode }
272-
export type { Mode, Theme, Role, Change, Lines, Line }
294+
export type { Mode, Theme, Role, Lines, Line }

0 commit comments

Comments
 (0)