-
-
Notifications
You must be signed in to change notification settings - Fork 64
/
diff.js
147 lines (140 loc) · 4.66 KB
/
diff.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/**
* Efficient diffs.
*
* @module diff
*/
import { equalityStrict } from './function.js'
/**
* A SimpleDiff describes a change on a String.
*
* ```js
* console.log(a) // the old value
* console.log(b) // the updated value
* // Apply changes of diff (pseudocode)
* a.remove(diff.index, diff.remove) // Remove `diff.remove` characters
* a.insert(diff.index, diff.insert) // Insert `diff.insert`
* a === b // values match
* ```
*
* @typedef {Object} SimpleDiff
* @property {Number} index The index where changes were applied
* @property {Number} remove The number of characters to delete starting
* at `index`.
* @property {T} insert The new text to insert at `index` after applying
* `delete`
*
* @template T
*/
const highSurrogateRegex = /[\uD800-\uDBFF]/
const lowSurrogateRegex = /[\uDC00-\uDFFF]/
/**
* Create a diff between two strings. This diff implementation is highly
* efficient, but not very sophisticated.
*
* @function
*
* @param {string} a The old version of the string
* @param {string} b The updated version of the string
* @return {SimpleDiff<string>} The diff description.
*/
export const simpleDiffString = (a, b) => {
let left = 0 // number of same characters counting from left
let right = 0 // number of same characters counting from right
while (left < a.length && left < b.length && a[left] === b[left]) {
left++
}
// If the last same character is a high surrogate, we need to rollback to the previous character
if (left > 0 && highSurrogateRegex.test(a[left - 1])) left--
while (right + left < a.length && right + left < b.length && a[a.length - right - 1] === b[b.length - right - 1]) {
right++
}
// If the last same character is a low surrogate, we need to rollback to the previous character
if (right > 0 && lowSurrogateRegex.test(a[a.length - right])) right--
return {
index: left,
remove: a.length - left - right,
insert: b.slice(left, b.length - right)
}
}
/**
* @todo Remove in favor of simpleDiffString
* @deprecated
*/
export const simpleDiff = simpleDiffString
/**
* Create a diff between two arrays. This diff implementation is highly
* efficient, but not very sophisticated.
*
* Note: This is basically the same function as above. Another function was created so that the runtime
* can better optimize these function calls.
*
* @function
* @template T
*
* @param {Array<T>} a The old version of the array
* @param {Array<T>} b The updated version of the array
* @param {function(T, T):boolean} [compare]
* @return {SimpleDiff<Array<T>>} The diff description.
*/
export const simpleDiffArray = (a, b, compare = equalityStrict) => {
let left = 0 // number of same characters counting from left
let right = 0 // number of same characters counting from right
while (left < a.length && left < b.length && compare(a[left], b[left])) {
left++
}
while (right + left < a.length && right + left < b.length && compare(a[a.length - right - 1], b[b.length - right - 1])) {
right++
}
return {
index: left,
remove: a.length - left - right,
insert: b.slice(left, b.length - right)
}
}
/**
* Diff text and try to diff at the current cursor position.
*
* @param {string} a
* @param {string} b
* @param {number} cursor This should refer to the current left cursor-range position
*/
export const simpleDiffStringWithCursor = (a, b, cursor) => {
let left = 0 // number of same characters counting from left
let right = 0 // number of same characters counting from right
// Iterate left to the right until we find a changed character
// First iteration considers the current cursor position
while (
left < a.length &&
left < b.length &&
a[left] === b[left] &&
left < cursor
) {
left++
}
// If the last same character is a high surrogate, we need to rollback to the previous character
if (left > 0 && highSurrogateRegex.test(a[left - 1])) left--
// Iterate right to the left until we find a changed character
while (
right + left < a.length &&
right + left < b.length &&
a[a.length - right - 1] === b[b.length - right - 1]
) {
right++
}
// If the last same character is a low surrogate, we need to rollback to the previous character
if (right > 0 && lowSurrogateRegex.test(a[a.length - right])) right--
// Try to iterate left further to the right without caring about the current cursor position
while (
right + left < a.length &&
right + left < b.length &&
a[left] === b[left]
) {
left++
}
if (left > 0 && highSurrogateRegex.test(a[left - 1])) left--
return {
index: left,
remove: a.length - left - right,
insert: b.slice(left, b.length - right)
}
}