prosemirror-changeset 2.2.1 → 2.3.1
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/CHANGELOG.md +12 -0
- package/README.md +35 -2
- package/dist/index.cjs +126 -252
- package/dist/index.d.cts +42 -4
- package/dist/index.d.ts +42 -4
- package/dist/index.js +40 -19
- package/package.json +4 -3
- package/src/README.md +3 -1
- package/src/change.ts +1 -1
- package/src/changeset.ts +20 -5
- package/src/diff.ts +54 -16
- package/test/test-diff.ts +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## 2.3.1 (2025-05-28)
|
|
2
|
+
|
|
3
|
+
### Bug fixes
|
|
4
|
+
|
|
5
|
+
Improve diffing to not treat closing tokens of different node types as the same token.
|
|
6
|
+
|
|
7
|
+
## 2.3.0 (2025-05-05)
|
|
8
|
+
|
|
9
|
+
### New features
|
|
10
|
+
|
|
11
|
+
Change sets can now be passed a custom token encoder that controls the way changed content is diffed.
|
|
12
|
+
|
|
1
13
|
## 2.2.1 (2023-05-17)
|
|
2
14
|
|
|
3
15
|
### Bug fixes
|
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ A replaced range with metadata associated with it.
|
|
|
41
41
|
|
|
42
42
|
* **`inserted`**`: readonly Span[]`\
|
|
43
43
|
Data associated with the inserted content. Length adds up to
|
|
44
|
-
`this.toB - this.
|
|
44
|
+
`this.toB - this.fromB`.
|
|
45
45
|
|
|
46
46
|
* `static `**`merge`**`<Data>(x: readonly Change[], y: readonly Change[], combine: fn(dataA: Data, dataB: Data) → Data) → readonly Change[]`\
|
|
47
47
|
This merges two changesets (the end document of x should be the
|
|
@@ -96,12 +96,18 @@ partially undo themselves by comparing their content.
|
|
|
96
96
|
make sure the method is called on the old set and passed the new
|
|
97
97
|
set. The returned positions will be in new document coordinates.
|
|
98
98
|
|
|
99
|
-
* `static `**`create`**`<Data = any>(doc: Node, combine?: fn(dataA: Data, dataB: Data) → Data = (a, b) => a === b ? a : null as any) → ChangeSet`\
|
|
99
|
+
* `static `**`create`**`<Data = any>(doc: Node, combine?: fn(dataA: Data, dataB: Data) → Data = (a, b) => a === b ? a : null as any, tokenEncoder?: TokenEncoder = DefaultEncoder) → ChangeSet`\
|
|
100
100
|
Create a changeset with the given base object and configuration.
|
|
101
|
+
|
|
101
102
|
The `combine` function is used to compare and combine metadata—it
|
|
102
103
|
should return null when metadata isn't compatible, and a combined
|
|
103
104
|
version for a merged range when it is.
|
|
104
105
|
|
|
106
|
+
When given, a token encoder determines how document tokens are
|
|
107
|
+
serialized and compared when diffing the content produced by
|
|
108
|
+
changes. The default is to just compare nodes by name and text
|
|
109
|
+
by character, ignoring marks and attributes.
|
|
110
|
+
|
|
105
111
|
|
|
106
112
|
* **`simplifyChanges`**`(changes: readonly Change[], doc: Node) → Change[]`\
|
|
107
113
|
Simplifies a set of changes for presentation. This makes the
|
|
@@ -111,3 +117,30 @@ partially undo themselves by comparing their content.
|
|
|
111
117
|
words (in the new document) they touch. An exception is made for
|
|
112
118
|
single-character replacements.
|
|
113
119
|
|
|
120
|
+
|
|
121
|
+
### interface TokenEncoder`<T>`
|
|
122
|
+
|
|
123
|
+
A token encoder can be passed when creating a `ChangeSet` in order
|
|
124
|
+
to influence the way the library runs its diffing algorithm. The
|
|
125
|
+
encoder determines how document tokens (such as nodes and
|
|
126
|
+
characters) are encoded and compared.
|
|
127
|
+
|
|
128
|
+
Note that both the encoding and the comparison may run a lot, and
|
|
129
|
+
doing non-trivial work in these functions could impact
|
|
130
|
+
performance.
|
|
131
|
+
|
|
132
|
+
* **`encodeCharacter`**`(char: number, marks: readonly Mark[]) → T`\
|
|
133
|
+
Encode a given character, with the given marks applied.
|
|
134
|
+
|
|
135
|
+
* **`encodeNodeStart`**`(node: Node) → T`\
|
|
136
|
+
Encode the start of a node or, if this is a leaf node, the
|
|
137
|
+
entire node.
|
|
138
|
+
|
|
139
|
+
* **`encodeNodeEnd`**`(node: Node) → T`\
|
|
140
|
+
Encode the end token for the given node. It is valid to encode
|
|
141
|
+
every end token in the same way.
|
|
142
|
+
|
|
143
|
+
* **`compareTokens`**`(a: T, b: T) → boolean`\
|
|
144
|
+
Compare the given tokens. Should return true when they count as
|
|
145
|
+
equal.
|
|
146
|
+
|
package/dist/index.cjs
CHANGED
|
@@ -1,113 +1,96 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
|
|
4
|
-
|
|
5
4
|
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
6
|
-
|
|
7
5
|
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
8
|
-
|
|
9
6
|
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
|
|
10
|
-
|
|
11
7
|
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
|
|
12
|
-
|
|
13
|
-
function
|
|
14
|
-
|
|
8
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
|
|
9
|
+
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
15
10
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
16
|
-
|
|
17
|
-
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
|
|
18
|
-
|
|
11
|
+
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
|
|
19
12
|
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
|
|
20
|
-
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
13
|
+
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
|
|
14
|
+
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
|
15
|
+
function typeID(type) {
|
|
16
|
+
var cache = type.schema.cached.changeSetIDs || (type.schema.cached.changeSetIDs = Object.create(null));
|
|
17
|
+
var id = cache[type.name];
|
|
18
|
+
if (id == null) cache[type.name] = id = Object.keys(type.schema.nodes).indexOf(type.name) + 1;
|
|
19
|
+
return id;
|
|
20
|
+
}
|
|
21
|
+
var DefaultEncoder = {
|
|
22
|
+
encodeCharacter: function encodeCharacter(_char) {
|
|
23
|
+
return _char;
|
|
24
|
+
},
|
|
25
|
+
encodeNodeStart: function encodeNodeStart(node) {
|
|
26
|
+
return node.type.name;
|
|
27
|
+
},
|
|
28
|
+
encodeNodeEnd: function encodeNodeEnd(node) {
|
|
29
|
+
return -typeID(node.type);
|
|
30
|
+
},
|
|
31
|
+
compareTokens: function compareTokens(a, b) {
|
|
32
|
+
return a === b;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
function tokens(frag, encoder, start, end, target) {
|
|
28
36
|
for (var i = 0, off = 0; i < frag.childCount; i++) {
|
|
29
37
|
var child = frag.child(i),
|
|
30
|
-
|
|
38
|
+
endOff = off + child.nodeSize;
|
|
31
39
|
var from = Math.max(off, start),
|
|
32
|
-
|
|
33
|
-
|
|
40
|
+
to = Math.min(endOff, end);
|
|
34
41
|
if (from < to) {
|
|
35
42
|
if (child.isText) {
|
|
36
|
-
for (var j = from; j < to; j++)
|
|
37
|
-
target.push(child.text.charCodeAt(j - off));
|
|
38
|
-
}
|
|
43
|
+
for (var j = from; j < to; j++) target.push(encoder.encodeCharacter(child.text.charCodeAt(j - off), child.marks));
|
|
39
44
|
} else if (child.isLeaf) {
|
|
40
|
-
target.push(child
|
|
45
|
+
target.push(encoder.encodeNodeStart(child));
|
|
41
46
|
} else {
|
|
42
|
-
if (from == off) target.push(child
|
|
43
|
-
tokens(child.content, Math.max(off + 1, from) - off - 1, Math.min(endOff - 1, to) - off - 1, target);
|
|
44
|
-
if (to == endOff) target.push(
|
|
47
|
+
if (from == off) target.push(encoder.encodeNodeStart(child));
|
|
48
|
+
tokens(child.content, encoder, Math.max(off + 1, from) - off - 1, Math.min(endOff - 1, to) - off - 1, target);
|
|
49
|
+
if (to == endOff) target.push(encoder.encodeNodeEnd(child));
|
|
45
50
|
}
|
|
46
51
|
}
|
|
47
|
-
|
|
48
52
|
off = endOff;
|
|
49
53
|
}
|
|
50
|
-
|
|
51
54
|
return target;
|
|
52
55
|
}
|
|
53
|
-
|
|
54
56
|
var MAX_DIFF_SIZE = 5000;
|
|
55
|
-
|
|
56
57
|
function minUnchanged(sizeA, sizeB) {
|
|
57
58
|
return Math.min(15, Math.max(2, Math.floor(Math.max(sizeA, sizeB) / 10)));
|
|
58
59
|
}
|
|
59
|
-
|
|
60
60
|
function computeDiff(fragA, fragB, range) {
|
|
61
|
-
var
|
|
62
|
-
var
|
|
61
|
+
var encoder = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : DefaultEncoder;
|
|
62
|
+
var tokA = tokens(fragA, encoder, range.fromA, range.toA, []);
|
|
63
|
+
var tokB = tokens(fragB, encoder, range.fromB, range.toB, []);
|
|
63
64
|
var start = 0,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
while (start < tokA.length && start < tokB.length && tokA[start]
|
|
68
|
-
start++;
|
|
69
|
-
}
|
|
70
|
-
|
|
65
|
+
endA = tokA.length,
|
|
66
|
+
endB = tokB.length;
|
|
67
|
+
var cmp = encoder.compareTokens;
|
|
68
|
+
while (start < tokA.length && start < tokB.length && cmp(tokA[start], tokB[start])) start++;
|
|
71
69
|
if (start == tokA.length && start == tokB.length) return [];
|
|
72
|
-
|
|
73
|
-
while (endA > start && endB > start && tokA[endA - 1] === tokB[endB - 1]) {
|
|
74
|
-
endA--, endB--;
|
|
75
|
-
}
|
|
76
|
-
|
|
70
|
+
while (endA > start && endB > start && cmp(tokA[endA - 1], tokB[endB - 1])) endA--, endB--;
|
|
77
71
|
if (endA == start || endB == start || endA == endB && endA == start + 1) return [range.slice(start, endA, start, endB)];
|
|
78
72
|
var lenA = endA - start,
|
|
79
|
-
|
|
73
|
+
lenB = endB - start;
|
|
80
74
|
var max = Math.min(MAX_DIFF_SIZE, lenA + lenB),
|
|
81
|
-
|
|
75
|
+
off = max + 1;
|
|
82
76
|
var history = [];
|
|
83
77
|
var frontier = [];
|
|
84
|
-
|
|
85
|
-
for (var len = off * 2, i = 0; i < len; i++) {
|
|
86
|
-
frontier[i] = -1;
|
|
87
|
-
}
|
|
88
|
-
|
|
78
|
+
for (var len = off * 2, i = 0; i < len; i++) frontier[i] = -1;
|
|
89
79
|
for (var size = 0; size <= max; size++) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
prev = frontier[
|
|
93
|
-
|
|
94
|
-
y = x +
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
x
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
frontier[diag + max] = x;
|
|
101
|
-
|
|
102
|
-
if (x >= lenA && y >= lenB) {
|
|
103
|
-
var _ret = function () {
|
|
80
|
+
var _loop = function _loop(_diag) {
|
|
81
|
+
var next = frontier[_diag + 1 + max],
|
|
82
|
+
prev = frontier[_diag - 1 + max];
|
|
83
|
+
var x = next < prev ? prev : next + 1,
|
|
84
|
+
y = x + _diag;
|
|
85
|
+
while (x < lenA && y < lenB && cmp(tokA[start + x], tokB[start + y])) x++, y++;
|
|
86
|
+
frontier[_diag + max] = x;
|
|
87
|
+
if (x >= lenA && y >= lenB) {
|
|
104
88
|
var diff = [],
|
|
105
|
-
|
|
89
|
+
minSpan = minUnchanged(endA - start, endB - start);
|
|
106
90
|
var fromA = -1,
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
91
|
+
toA = -1,
|
|
92
|
+
fromB = -1,
|
|
93
|
+
toB = -1;
|
|
111
94
|
var add = function add(fA, tA, fB, tB) {
|
|
112
95
|
if (fromA > -1 && fromA < tA + minSpan) {
|
|
113
96
|
fromA = fA;
|
|
@@ -120,50 +103,44 @@ function computeDiff(fragA, fragB, range) {
|
|
|
120
103
|
toB = tB;
|
|
121
104
|
}
|
|
122
105
|
};
|
|
123
|
-
|
|
124
106
|
for (var _i = size - 1; _i >= 0; _i--) {
|
|
125
|
-
var _next = frontier[
|
|
126
|
-
|
|
127
|
-
|
|
107
|
+
var _next = frontier[_diag + 1 + max],
|
|
108
|
+
_prev = frontier[_diag - 1 + max];
|
|
128
109
|
if (_next < _prev) {
|
|
129
|
-
|
|
110
|
+
_diag--;
|
|
130
111
|
x = _prev + start;
|
|
131
|
-
y = x +
|
|
112
|
+
y = x + _diag;
|
|
132
113
|
add(x, x, y, y + 1);
|
|
133
114
|
} else {
|
|
134
|
-
|
|
115
|
+
_diag++;
|
|
135
116
|
x = _next + start;
|
|
136
|
-
y = x +
|
|
117
|
+
y = x + _diag;
|
|
137
118
|
add(x, x + 1, y, y);
|
|
138
119
|
}
|
|
139
|
-
|
|
140
120
|
frontier = history[_i >> 1];
|
|
141
121
|
}
|
|
142
|
-
|
|
143
122
|
if (fromA > -1) diff.push(range.slice(fromA, toA, fromB, toB));
|
|
144
123
|
return {
|
|
145
124
|
v: diff.reverse()
|
|
146
125
|
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
126
|
+
}
|
|
127
|
+
diag = _diag;
|
|
128
|
+
},
|
|
129
|
+
_ret;
|
|
130
|
+
for (var diag = -size; diag <= size; diag += 2) {
|
|
131
|
+
_ret = _loop(diag);
|
|
132
|
+
if (_ret) return _ret.v;
|
|
151
133
|
}
|
|
152
|
-
|
|
153
134
|
if (size % 2 == 0) history.push(frontier.slice());
|
|
154
135
|
}
|
|
155
|
-
|
|
156
136
|
return [range.slice(start, endA, start, endB)];
|
|
157
137
|
}
|
|
158
|
-
|
|
159
138
|
var Span = function () {
|
|
160
139
|
function Span(length, data) {
|
|
161
140
|
_classCallCheck(this, Span);
|
|
162
|
-
|
|
163
141
|
this.length = length;
|
|
164
142
|
this.data = data;
|
|
165
143
|
}
|
|
166
|
-
|
|
167
144
|
_createClass(Span, [{
|
|
168
145
|
key: "cut",
|
|
169
146
|
value: function cut(length) {
|
|
@@ -175,15 +152,13 @@ var Span = function () {
|
|
|
175
152
|
if (from == to) return Span.none;
|
|
176
153
|
if (from == 0 && to == Span.len(spans)) return spans;
|
|
177
154
|
var result = [];
|
|
178
|
-
|
|
179
155
|
for (var i = 0, off = 0; off < to; i++) {
|
|
180
156
|
var span = spans[i],
|
|
181
|
-
|
|
157
|
+
end = off + span.length;
|
|
182
158
|
var overlap = Math.min(to, end) - Math.max(from, off);
|
|
183
159
|
if (overlap > 0) result.push(span.cut(overlap));
|
|
184
160
|
off = end;
|
|
185
161
|
}
|
|
186
|
-
|
|
187
162
|
return result;
|
|
188
163
|
}
|
|
189
164
|
}, {
|
|
@@ -195,35 +170,23 @@ var Span = function () {
|
|
|
195
170
|
if (combined == null) return a.concat(b);
|
|
196
171
|
var result = a.slice(0, a.length - 1);
|
|
197
172
|
result.push(new Span(a[a.length - 1].length + b[0].length, combined));
|
|
198
|
-
|
|
199
|
-
for (var i = 1; i < b.length; i++) {
|
|
200
|
-
result.push(b[i]);
|
|
201
|
-
}
|
|
202
|
-
|
|
173
|
+
for (var i = 1; i < b.length; i++) result.push(b[i]);
|
|
203
174
|
return result;
|
|
204
175
|
}
|
|
205
176
|
}, {
|
|
206
177
|
key: "len",
|
|
207
178
|
value: function len(spans) {
|
|
208
179
|
var len = 0;
|
|
209
|
-
|
|
210
|
-
for (var i = 0; i < spans.length; i++) {
|
|
211
|
-
len += spans[i].length;
|
|
212
|
-
}
|
|
213
|
-
|
|
180
|
+
for (var i = 0; i < spans.length; i++) len += spans[i].length;
|
|
214
181
|
return len;
|
|
215
182
|
}
|
|
216
183
|
}]);
|
|
217
|
-
|
|
218
184
|
return Span;
|
|
219
185
|
}();
|
|
220
|
-
|
|
221
186
|
Span.none = [];
|
|
222
|
-
|
|
223
187
|
var Change = function () {
|
|
224
188
|
function Change(fromA, toA, fromB, toB, deleted, inserted) {
|
|
225
189
|
_classCallCheck(this, Change);
|
|
226
|
-
|
|
227
190
|
this.fromA = fromA;
|
|
228
191
|
this.toA = toA;
|
|
229
192
|
this.fromB = fromB;
|
|
@@ -231,7 +194,6 @@ var Change = function () {
|
|
|
231
194
|
this.deleted = deleted;
|
|
232
195
|
this.inserted = inserted;
|
|
233
196
|
}
|
|
234
|
-
|
|
235
197
|
_createClass(Change, [{
|
|
236
198
|
key: "lenA",
|
|
237
199
|
get: function get() {
|
|
@@ -254,7 +216,6 @@ var Change = function () {
|
|
|
254
216
|
if (x.length == 0) return y;
|
|
255
217
|
if (y.length == 0) return x;
|
|
256
218
|
var result = [];
|
|
257
|
-
|
|
258
219
|
for (var iX = 0, iY = 0, curX = x[0], curY = y[0];;) {
|
|
259
220
|
if (!curX && !curY) {
|
|
260
221
|
return result;
|
|
@@ -264,97 +225,79 @@ var Change = function () {
|
|
|
264
225
|
curX = iX++ == x.length ? null : x[iX];
|
|
265
226
|
} else if (curY && (!curX || curY.toA < curX.fromB)) {
|
|
266
227
|
var _off = iX ? x[iX - 1].toB - x[iX - 1].toA : 0;
|
|
267
|
-
|
|
268
228
|
result.push(_off == 0 ? curY : new Change(curY.fromA - _off, curY.toA - _off, curY.fromB, curY.toB, curY.deleted, curY.inserted));
|
|
269
229
|
curY = iY++ == y.length ? null : y[iY];
|
|
270
230
|
} else {
|
|
271
231
|
var pos = Math.min(curX.fromB, curY.fromA);
|
|
272
232
|
var fromA = Math.min(curX.fromA, curY.fromA - (iX ? x[iX - 1].toB - x[iX - 1].toA : 0)),
|
|
273
|
-
|
|
233
|
+
toA = fromA;
|
|
274
234
|
var fromB = Math.min(curY.fromB, curX.fromB + (iY ? y[iY - 1].toB - y[iY - 1].toA : 0)),
|
|
275
|
-
|
|
235
|
+
toB = fromB;
|
|
276
236
|
var deleted = Span.none,
|
|
277
|
-
|
|
237
|
+
inserted = Span.none;
|
|
278
238
|
var enteredX = false,
|
|
279
|
-
|
|
280
|
-
|
|
239
|
+
enteredY = false;
|
|
281
240
|
for (;;) {
|
|
282
241
|
var nextX = !curX ? 2e8 : pos >= curX.fromB ? curX.toB : curX.fromB;
|
|
283
242
|
var nextY = !curY ? 2e8 : pos >= curY.fromA ? curY.toA : curY.fromA;
|
|
284
243
|
var next = Math.min(nextX, nextY);
|
|
285
244
|
var inX = curX && pos >= curX.fromB,
|
|
286
|
-
|
|
245
|
+
inY = curY && pos >= curY.fromA;
|
|
287
246
|
if (!inX && !inY) break;
|
|
288
|
-
|
|
289
247
|
if (inX && pos == curX.fromB && !enteredX) {
|
|
290
248
|
deleted = Span.join(deleted, curX.deleted, combine);
|
|
291
249
|
toA += curX.lenA;
|
|
292
250
|
enteredX = true;
|
|
293
251
|
}
|
|
294
|
-
|
|
295
252
|
if (inX && !inY) {
|
|
296
253
|
inserted = Span.join(inserted, Span.slice(curX.inserted, pos - curX.fromB, next - curX.fromB), combine);
|
|
297
254
|
toB += next - pos;
|
|
298
255
|
}
|
|
299
|
-
|
|
300
256
|
if (inY && pos == curY.fromA && !enteredY) {
|
|
301
257
|
inserted = Span.join(inserted, curY.inserted, combine);
|
|
302
258
|
toB += curY.lenB;
|
|
303
259
|
enteredY = true;
|
|
304
260
|
}
|
|
305
|
-
|
|
306
261
|
if (inY && !inX) {
|
|
307
262
|
deleted = Span.join(deleted, Span.slice(curY.deleted, pos - curY.fromA, next - curY.fromA), combine);
|
|
308
263
|
toA += next - pos;
|
|
309
264
|
}
|
|
310
|
-
|
|
311
265
|
if (inX && next == curX.toB) {
|
|
312
266
|
curX = iX++ == x.length ? null : x[iX];
|
|
313
267
|
enteredX = false;
|
|
314
268
|
}
|
|
315
|
-
|
|
316
269
|
if (inY && next == curY.toA) {
|
|
317
270
|
curY = iY++ == y.length ? null : y[iY];
|
|
318
271
|
enteredY = false;
|
|
319
272
|
}
|
|
320
|
-
|
|
321
273
|
pos = next;
|
|
322
274
|
}
|
|
323
|
-
|
|
324
275
|
if (fromA < toA || fromB < toB) result.push(new Change(fromA, toA, fromB, toB, deleted, inserted));
|
|
325
276
|
}
|
|
326
277
|
}
|
|
327
278
|
}
|
|
328
279
|
}]);
|
|
329
|
-
|
|
330
280
|
return Change;
|
|
331
281
|
}();
|
|
332
|
-
|
|
333
282
|
var letter;
|
|
334
|
-
|
|
335
283
|
try {
|
|
336
284
|
letter = new RegExp("[\\p{Alphabetic}_]", "u");
|
|
337
285
|
} catch (_) {}
|
|
338
|
-
|
|
339
286
|
var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
|
|
340
|
-
|
|
341
287
|
function isLetter(code) {
|
|
342
288
|
if (code < 128) return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 79 && code <= 122;
|
|
343
289
|
var ch = String.fromCharCode(code);
|
|
344
290
|
if (letter) return letter.test(ch);
|
|
345
291
|
return ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch);
|
|
346
292
|
}
|
|
347
|
-
|
|
348
293
|
function getText(frag, start, end) {
|
|
349
294
|
var out = "";
|
|
350
|
-
|
|
351
295
|
function convert(frag, start, end) {
|
|
352
296
|
for (var i = 0, off = 0; i < frag.childCount; i++) {
|
|
353
297
|
var child = frag.child(i),
|
|
354
|
-
|
|
298
|
+
endOff = off + child.nodeSize;
|
|
355
299
|
var from = Math.max(off, start),
|
|
356
|
-
|
|
357
|
-
|
|
300
|
+
to = Math.min(endOff, end);
|
|
358
301
|
if (from < to) {
|
|
359
302
|
if (child.isText) {
|
|
360
303
|
out += child.text.slice(Math.max(0, start - off), Math.min(child.text.length, end - off));
|
|
@@ -366,104 +309,76 @@ function getText(frag, start, end) {
|
|
|
366
309
|
if (to == endOff) out += " ";
|
|
367
310
|
}
|
|
368
311
|
}
|
|
369
|
-
|
|
370
312
|
off = endOff;
|
|
371
313
|
}
|
|
372
314
|
}
|
|
373
|
-
|
|
374
315
|
convert(frag, start, end);
|
|
375
316
|
return out;
|
|
376
317
|
}
|
|
377
|
-
|
|
378
318
|
var MAX_SIMPLIFY_DISTANCE = 30;
|
|
379
|
-
|
|
380
319
|
function simplifyChanges(changes, doc) {
|
|
381
320
|
var result = [];
|
|
382
|
-
|
|
383
321
|
for (var i = 0; i < changes.length; i++) {
|
|
384
322
|
var end = changes[i].toB,
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
while (i < changes.length - 1 && changes[i + 1].fromB <= end + MAX_SIMPLIFY_DISTANCE) {
|
|
388
|
-
end = changes[++i].toB;
|
|
389
|
-
}
|
|
390
|
-
|
|
323
|
+
start = i;
|
|
324
|
+
while (i < changes.length - 1 && changes[i + 1].fromB <= end + MAX_SIMPLIFY_DISTANCE) end = changes[++i].toB;
|
|
391
325
|
simplifyAdjacentChanges(changes, start, i + 1, doc, result);
|
|
392
326
|
}
|
|
393
|
-
|
|
394
327
|
return result;
|
|
395
328
|
}
|
|
396
|
-
|
|
397
329
|
function simplifyAdjacentChanges(changes, from, to, doc, target) {
|
|
398
330
|
var start = Math.max(0, changes[from].fromB - MAX_SIMPLIFY_DISTANCE);
|
|
399
331
|
var end = Math.min(doc.content.size, changes[to - 1].toB + MAX_SIMPLIFY_DISTANCE);
|
|
400
332
|
var text = getText(doc.content, start, end);
|
|
401
|
-
|
|
402
333
|
for (var i = from; i < to; i++) {
|
|
403
334
|
var startI = i,
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
335
|
+
last = changes[i],
|
|
336
|
+
deleted = last.lenA,
|
|
337
|
+
inserted = last.lenB;
|
|
408
338
|
while (i < to - 1) {
|
|
409
339
|
var next = changes[i + 1],
|
|
410
|
-
|
|
340
|
+
boundary = false;
|
|
411
341
|
var prevLetter = last.toB == end ? false : isLetter(text.charCodeAt(last.toB - 1 - start));
|
|
412
|
-
|
|
413
342
|
for (var pos = last.toB; !boundary && pos < next.fromB; pos++) {
|
|
414
343
|
var nextLetter = pos == end ? false : isLetter(text.charCodeAt(pos - start));
|
|
415
344
|
if ((!prevLetter || !nextLetter) && pos != changes[startI].fromB) boundary = true;
|
|
416
345
|
prevLetter = nextLetter;
|
|
417
346
|
}
|
|
418
|
-
|
|
419
347
|
if (boundary) break;
|
|
420
348
|
deleted += next.lenA;
|
|
421
349
|
inserted += next.lenB;
|
|
422
350
|
last = next;
|
|
423
351
|
i++;
|
|
424
352
|
}
|
|
425
|
-
|
|
426
353
|
if (inserted > 0 && deleted > 0 && !(inserted == 1 && deleted == 1)) {
|
|
427
354
|
var _from = changes[startI].fromB,
|
|
428
|
-
|
|
429
|
-
if (_from < end && isLetter(text.charCodeAt(_from - start))) while (_from > start && isLetter(text.charCodeAt(_from - 1 - start)))
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
if (_to > start && isLetter(text.charCodeAt(_to - 1 - start))) while (_to < end && isLetter(text.charCodeAt(_to - start))) {
|
|
433
|
-
_to++;
|
|
434
|
-
}
|
|
355
|
+
_to = changes[i].toB;
|
|
356
|
+
if (_from < end && isLetter(text.charCodeAt(_from - start))) while (_from > start && isLetter(text.charCodeAt(_from - 1 - start))) _from--;
|
|
357
|
+
if (_to > start && isLetter(text.charCodeAt(_to - 1 - start))) while (_to < end && isLetter(text.charCodeAt(_to - start))) _to++;
|
|
435
358
|
var joined = fillChange(changes.slice(startI, i + 1), _from, _to);
|
|
436
|
-
|
|
437
359
|
var _last = target.length ? target[target.length - 1] : null;
|
|
438
|
-
|
|
439
360
|
if (_last && _last.toA == joined.fromA) target[target.length - 1] = new Change(_last.fromA, joined.toA, _last.fromB, joined.toB, _last.deleted.concat(joined.deleted), _last.inserted.concat(joined.inserted));else target.push(joined);
|
|
440
361
|
} else {
|
|
441
|
-
for (var j = startI; j <= i; j++)
|
|
442
|
-
target.push(changes[j]);
|
|
443
|
-
}
|
|
362
|
+
for (var j = startI; j <= i; j++) target.push(changes[j]);
|
|
444
363
|
}
|
|
445
364
|
}
|
|
446
|
-
|
|
447
365
|
return changes;
|
|
448
366
|
}
|
|
449
|
-
|
|
450
367
|
function combine(a, b) {
|
|
451
368
|
return a === b ? a : null;
|
|
452
369
|
}
|
|
453
|
-
|
|
454
370
|
function fillChange(changes, fromB, toB) {
|
|
455
371
|
var fromA = changes[0].fromA - (changes[0].fromB - fromB);
|
|
456
372
|
var last = changes[changes.length - 1];
|
|
457
373
|
var toA = last.toA + (toB - last.toB);
|
|
458
374
|
var deleted = Span.none,
|
|
459
|
-
|
|
375
|
+
inserted = Span.none;
|
|
460
376
|
var delData = (changes[0].deleted.length ? changes[0].deleted : changes[0].inserted)[0].data;
|
|
461
377
|
var insData = (changes[0].inserted.length ? changes[0].inserted : changes[0].deleted)[0].data;
|
|
462
|
-
|
|
463
378
|
for (var posA = fromA, posB = fromB, i = 0;; i++) {
|
|
464
379
|
var next = i == changes.length ? null : changes[i];
|
|
465
380
|
var endA = next ? next.fromA : toA,
|
|
466
|
-
|
|
381
|
+
endB = next ? next.fromB : toB;
|
|
467
382
|
if (endA > posA) deleted = Span.join(deleted, [new Span(endA - posA, delData)], combine);
|
|
468
383
|
if (endB > posB) inserted = Span.join(inserted, [new Span(endB - posB, insData)], combine);
|
|
469
384
|
if (!next) break;
|
|
@@ -474,26 +389,20 @@ function fillChange(changes, fromB, toB) {
|
|
|
474
389
|
posA = next.toA;
|
|
475
390
|
posB = next.toB;
|
|
476
391
|
}
|
|
477
|
-
|
|
478
392
|
return new Change(fromA, toA, fromB, toB, deleted, inserted);
|
|
479
393
|
}
|
|
480
|
-
|
|
481
394
|
var ChangeSet = function () {
|
|
482
395
|
function ChangeSet(config, changes) {
|
|
483
396
|
_classCallCheck(this, ChangeSet);
|
|
484
|
-
|
|
485
397
|
this.config = config;
|
|
486
398
|
this.changes = changes;
|
|
487
399
|
}
|
|
488
|
-
|
|
489
400
|
_createClass(ChangeSet, [{
|
|
490
401
|
key: "addSteps",
|
|
491
402
|
value: function addSteps(newDoc, maps, data) {
|
|
492
403
|
var _this = this;
|
|
493
|
-
|
|
494
404
|
var stepChanges = [];
|
|
495
|
-
|
|
496
|
-
var _loop = function _loop(i) {
|
|
405
|
+
var _loop2 = function _loop2() {
|
|
497
406
|
var d = Array.isArray(data) ? data[i] : data;
|
|
498
407
|
var off = 0;
|
|
499
408
|
maps[i].forEach(function (fromA, toA, fromB, toB) {
|
|
@@ -501,54 +410,41 @@ var ChangeSet = function () {
|
|
|
501
410
|
off = toB - fromB - (toA - fromA);
|
|
502
411
|
});
|
|
503
412
|
};
|
|
504
|
-
|
|
505
413
|
for (var i = 0; i < maps.length; i++) {
|
|
506
|
-
|
|
414
|
+
_loop2();
|
|
507
415
|
}
|
|
508
|
-
|
|
509
416
|
if (stepChanges.length == 0) return this;
|
|
510
417
|
var newChanges = mergeAll(stepChanges, this.config.combine);
|
|
511
418
|
var changes = Change.merge(this.changes, newChanges, this.config.combine);
|
|
512
419
|
var updated = changes;
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
420
|
+
var _loop3 = function _loop3(_i3) {
|
|
421
|
+
var change = updated[_i3];
|
|
422
|
+
if (change.fromA == change.toA || change.fromB == change.toB || !newChanges.some(function (r) {
|
|
423
|
+
return r.toB > change.fromB && r.fromB < change.toB;
|
|
424
|
+
})) {
|
|
425
|
+
_i2 = _i3;
|
|
426
|
+
return 0;
|
|
427
|
+
}
|
|
428
|
+
var diff = computeDiff(_this.config.doc.content, newDoc.content, change, _this.config.encoder);
|
|
429
|
+
if (diff.length == 1 && diff[0].fromB == 0 && diff[0].toB == change.toB - change.fromB) {
|
|
430
|
+
_i2 = _i3;
|
|
431
|
+
return 0;
|
|
432
|
+
}
|
|
433
|
+
if (updated == changes) updated = changes.slice();
|
|
434
|
+
if (diff.length == 1) {
|
|
435
|
+
updated[_i3] = diff[0];
|
|
436
|
+
} else {
|
|
437
|
+
var _updated;
|
|
438
|
+
(_updated = updated).splice.apply(_updated, [_i3, 1].concat(_toConsumableArray(diff)));
|
|
439
|
+
_i3 += diff.length - 1;
|
|
440
|
+
}
|
|
527
441
|
_i2 = _i3;
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
if (updated == changes) updated = changes.slice();
|
|
532
|
-
|
|
533
|
-
if (diff.length == 1) {
|
|
534
|
-
updated[_i3] = diff[0];
|
|
535
|
-
} else {
|
|
536
|
-
var _updated;
|
|
537
|
-
|
|
538
|
-
(_updated = updated).splice.apply(_updated, [_i3, 1].concat(_toConsumableArray(diff)));
|
|
539
|
-
|
|
540
|
-
_i3 += diff.length - 1;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
_i2 = _i3;
|
|
544
|
-
};
|
|
545
|
-
|
|
442
|
+
},
|
|
443
|
+
_ret2;
|
|
546
444
|
for (var _i2 = 0; _i2 < updated.length; _i2++) {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
if (_ret2 === "continue") continue;
|
|
445
|
+
_ret2 = _loop3(_i2);
|
|
446
|
+
if (_ret2 === 0) continue;
|
|
550
447
|
}
|
|
551
|
-
|
|
552
448
|
return new ChangeSet(this.config, updated);
|
|
553
449
|
}
|
|
554
450
|
}, {
|
|
@@ -563,7 +459,6 @@ var ChangeSet = function () {
|
|
|
563
459
|
var newData = f(span);
|
|
564
460
|
return newData === span.data ? span : new Span(span.length, newData);
|
|
565
461
|
};
|
|
566
|
-
|
|
567
462
|
return new ChangeSet(this.config, this.changes.map(function (ch) {
|
|
568
463
|
return new Change(ch.fromA, ch.toA, ch.fromB, ch.toB, ch.deleted.map(mapSpan), ch.inserted.map(mapSpan));
|
|
569
464
|
}));
|
|
@@ -574,27 +469,21 @@ var ChangeSet = function () {
|
|
|
574
469
|
if (b == this) return null;
|
|
575
470
|
var touched = maps && touchedRange(maps);
|
|
576
471
|
var moved = touched ? touched.toB - touched.fromB - (touched.toA - touched.fromA) : 0;
|
|
577
|
-
|
|
578
472
|
function map(p) {
|
|
579
473
|
return !touched || p <= touched.fromA ? p : p + moved;
|
|
580
474
|
}
|
|
581
|
-
|
|
582
475
|
var from = touched ? touched.fromB : 2e8,
|
|
583
|
-
|
|
584
|
-
|
|
476
|
+
to = touched ? touched.toB : -2e8;
|
|
585
477
|
function add(start) {
|
|
586
478
|
var end = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : start;
|
|
587
479
|
from = Math.min(start, from);
|
|
588
480
|
to = Math.max(end, to);
|
|
589
481
|
}
|
|
590
|
-
|
|
591
482
|
var rA = this.changes,
|
|
592
|
-
|
|
593
|
-
|
|
483
|
+
rB = b.changes;
|
|
594
484
|
for (var iA = 0, iB = 0; iA < rA.length && iB < rB.length;) {
|
|
595
485
|
var rangeA = rA[iA],
|
|
596
|
-
|
|
597
|
-
|
|
486
|
+
rangeB = rB[iB];
|
|
598
487
|
if (rangeA && rangeB && sameRanges(rangeA, rangeB, map)) {
|
|
599
488
|
iA++;
|
|
600
489
|
iB++;
|
|
@@ -606,7 +495,6 @@ var ChangeSet = function () {
|
|
|
606
495
|
iA++;
|
|
607
496
|
}
|
|
608
497
|
}
|
|
609
|
-
|
|
610
498
|
return from <= to ? {
|
|
611
499
|
from: from,
|
|
612
500
|
to: to
|
|
@@ -618,18 +506,17 @@ var ChangeSet = function () {
|
|
|
618
506
|
var combine = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function (a, b) {
|
|
619
507
|
return a === b ? a : null;
|
|
620
508
|
};
|
|
509
|
+
var tokenEncoder = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DefaultEncoder;
|
|
621
510
|
return new ChangeSet({
|
|
622
511
|
combine: combine,
|
|
623
|
-
doc: doc
|
|
512
|
+
doc: doc,
|
|
513
|
+
encoder: tokenEncoder
|
|
624
514
|
}, []);
|
|
625
515
|
}
|
|
626
516
|
}]);
|
|
627
|
-
|
|
628
517
|
return ChangeSet;
|
|
629
518
|
}();
|
|
630
|
-
|
|
631
519
|
ChangeSet.computeDiff = computeDiff;
|
|
632
|
-
|
|
633
520
|
function mergeAll(ranges, combine) {
|
|
634
521
|
var start = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
|
|
635
522
|
var end = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : ranges.length;
|
|
@@ -637,31 +524,25 @@ function mergeAll(ranges, combine) {
|
|
|
637
524
|
var mid = start + end >> 1;
|
|
638
525
|
return Change.merge(mergeAll(ranges, combine, start, mid), mergeAll(ranges, combine, mid, end), combine);
|
|
639
526
|
}
|
|
640
|
-
|
|
641
527
|
function endRange(maps) {
|
|
642
528
|
var from = 2e8,
|
|
643
|
-
|
|
644
|
-
|
|
529
|
+
to = -2e8;
|
|
645
530
|
for (var i = 0; i < maps.length; i++) {
|
|
646
531
|
var map = maps[i];
|
|
647
|
-
|
|
648
532
|
if (from != 2e8) {
|
|
649
533
|
from = map.map(from, -1);
|
|
650
534
|
to = map.map(to, 1);
|
|
651
535
|
}
|
|
652
|
-
|
|
653
536
|
map.forEach(function (_s, _e, start, end) {
|
|
654
537
|
from = Math.min(from, start);
|
|
655
538
|
to = Math.max(to, end);
|
|
656
539
|
});
|
|
657
540
|
}
|
|
658
|
-
|
|
659
541
|
return from == 2e8 ? null : {
|
|
660
542
|
from: from,
|
|
661
543
|
to: to
|
|
662
544
|
};
|
|
663
545
|
}
|
|
664
|
-
|
|
665
546
|
function touchedRange(maps) {
|
|
666
547
|
var b = endRange(maps);
|
|
667
548
|
if (!b) return null;
|
|
@@ -675,21 +556,14 @@ function touchedRange(maps) {
|
|
|
675
556
|
toB: b.to
|
|
676
557
|
};
|
|
677
558
|
}
|
|
678
|
-
|
|
679
559
|
function sameRanges(a, b, map) {
|
|
680
560
|
return map(a.fromB) == b.fromB && map(a.toB) == b.toB && sameSpans(a.deleted, b.deleted) && sameSpans(a.inserted, b.inserted);
|
|
681
561
|
}
|
|
682
|
-
|
|
683
562
|
function sameSpans(a, b) {
|
|
684
563
|
if (a.length != b.length) return false;
|
|
685
|
-
|
|
686
|
-
for (var i = 0; i < a.length; i++) {
|
|
687
|
-
if (a[i].length != b[i].length || a[i].data !== b[i].data) return false;
|
|
688
|
-
}
|
|
689
|
-
|
|
564
|
+
for (var i = 0; i < a.length; i++) if (a[i].length != b[i].length || a[i].data !== b[i].data) return false;
|
|
690
565
|
return true;
|
|
691
566
|
}
|
|
692
|
-
|
|
693
567
|
exports.Change = Change;
|
|
694
568
|
exports.ChangeSet = ChangeSet;
|
|
695
569
|
exports.Span = Span;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Node } from 'prosemirror-model';
|
|
1
|
+
import { Mark, Node } from 'prosemirror-model';
|
|
2
2
|
import { StepMap } from 'prosemirror-transform';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -41,7 +41,7 @@ declare class Change<Data = any> {
|
|
|
41
41
|
readonly deleted: readonly Span<Data>[];
|
|
42
42
|
/**
|
|
43
43
|
Data associated with the inserted content. Length adds up to
|
|
44
|
-
`this.toB - this.
|
|
44
|
+
`this.toB - this.fromB`.
|
|
45
45
|
*/
|
|
46
46
|
readonly inserted: readonly Span<Data>[];
|
|
47
47
|
/**
|
|
@@ -52,6 +52,38 @@ declare class Change<Data = any> {
|
|
|
52
52
|
static merge<Data>(x: readonly Change<Data>[], y: readonly Change<Data>[], combine: (dataA: Data, dataB: Data) => Data): readonly Change<Data>[];
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
A token encoder can be passed when creating a `ChangeSet` in order
|
|
57
|
+
to influence the way the library runs its diffing algorithm. The
|
|
58
|
+
encoder determines how document tokens (such as nodes and
|
|
59
|
+
characters) are encoded and compared.
|
|
60
|
+
|
|
61
|
+
Note that both the encoding and the comparison may run a lot, and
|
|
62
|
+
doing non-trivial work in these functions could impact
|
|
63
|
+
performance.
|
|
64
|
+
*/
|
|
65
|
+
interface TokenEncoder<T> {
|
|
66
|
+
/**
|
|
67
|
+
Encode a given character, with the given marks applied.
|
|
68
|
+
*/
|
|
69
|
+
encodeCharacter(char: number, marks: readonly Mark[]): T;
|
|
70
|
+
/**
|
|
71
|
+
Encode the start of a node or, if this is a leaf node, the
|
|
72
|
+
entire node.
|
|
73
|
+
*/
|
|
74
|
+
encodeNodeStart(node: Node): T;
|
|
75
|
+
/**
|
|
76
|
+
Encode the end token for the given node. It is valid to encode
|
|
77
|
+
every end token in the same way.
|
|
78
|
+
*/
|
|
79
|
+
encodeNodeEnd(node: Node): T;
|
|
80
|
+
/**
|
|
81
|
+
Compare the given tokens. Should return true when they count as
|
|
82
|
+
equal.
|
|
83
|
+
*/
|
|
84
|
+
compareTokens(a: T, b: T): boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
55
87
|
/**
|
|
56
88
|
Simplifies a set of changes for presentation. This makes the
|
|
57
89
|
assumption that having both insertions and deletions within a word
|
|
@@ -108,11 +140,17 @@ declare class ChangeSet<Data = any> {
|
|
|
108
140
|
} | null;
|
|
109
141
|
/**
|
|
110
142
|
Create a changeset with the given base object and configuration.
|
|
143
|
+
|
|
111
144
|
The `combine` function is used to compare and combine metadata—it
|
|
112
145
|
should return null when metadata isn't compatible, and a combined
|
|
113
146
|
version for a merged range when it is.
|
|
147
|
+
|
|
148
|
+
When given, a token encoder determines how document tokens are
|
|
149
|
+
serialized and compared when diffing the content produced by
|
|
150
|
+
changes. The default is to just compare nodes by name and text
|
|
151
|
+
by character, ignoring marks and attributes.
|
|
114
152
|
*/
|
|
115
|
-
static create<Data = any>(doc: Node, combine?: (dataA: Data, dataB: Data) => Data): ChangeSet<Data>;
|
|
153
|
+
static create<Data = any>(doc: Node, combine?: (dataA: Data, dataB: Data) => Data, tokenEncoder?: TokenEncoder<any>): ChangeSet<Data>;
|
|
116
154
|
}
|
|
117
155
|
|
|
118
|
-
export { Change, ChangeSet, Span, simplifyChanges };
|
|
156
|
+
export { Change, ChangeSet, Span, type TokenEncoder, simplifyChanges };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Node } from 'prosemirror-model';
|
|
1
|
+
import { Mark, Node } from 'prosemirror-model';
|
|
2
2
|
import { StepMap } from 'prosemirror-transform';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -41,7 +41,7 @@ declare class Change<Data = any> {
|
|
|
41
41
|
readonly deleted: readonly Span<Data>[];
|
|
42
42
|
/**
|
|
43
43
|
Data associated with the inserted content. Length adds up to
|
|
44
|
-
`this.toB - this.
|
|
44
|
+
`this.toB - this.fromB`.
|
|
45
45
|
*/
|
|
46
46
|
readonly inserted: readonly Span<Data>[];
|
|
47
47
|
/**
|
|
@@ -52,6 +52,38 @@ declare class Change<Data = any> {
|
|
|
52
52
|
static merge<Data>(x: readonly Change<Data>[], y: readonly Change<Data>[], combine: (dataA: Data, dataB: Data) => Data): readonly Change<Data>[];
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
A token encoder can be passed when creating a `ChangeSet` in order
|
|
57
|
+
to influence the way the library runs its diffing algorithm. The
|
|
58
|
+
encoder determines how document tokens (such as nodes and
|
|
59
|
+
characters) are encoded and compared.
|
|
60
|
+
|
|
61
|
+
Note that both the encoding and the comparison may run a lot, and
|
|
62
|
+
doing non-trivial work in these functions could impact
|
|
63
|
+
performance.
|
|
64
|
+
*/
|
|
65
|
+
interface TokenEncoder<T> {
|
|
66
|
+
/**
|
|
67
|
+
Encode a given character, with the given marks applied.
|
|
68
|
+
*/
|
|
69
|
+
encodeCharacter(char: number, marks: readonly Mark[]): T;
|
|
70
|
+
/**
|
|
71
|
+
Encode the start of a node or, if this is a leaf node, the
|
|
72
|
+
entire node.
|
|
73
|
+
*/
|
|
74
|
+
encodeNodeStart(node: Node): T;
|
|
75
|
+
/**
|
|
76
|
+
Encode the end token for the given node. It is valid to encode
|
|
77
|
+
every end token in the same way.
|
|
78
|
+
*/
|
|
79
|
+
encodeNodeEnd(node: Node): T;
|
|
80
|
+
/**
|
|
81
|
+
Compare the given tokens. Should return true when they count as
|
|
82
|
+
equal.
|
|
83
|
+
*/
|
|
84
|
+
compareTokens(a: T, b: T): boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
55
87
|
/**
|
|
56
88
|
Simplifies a set of changes for presentation. This makes the
|
|
57
89
|
assumption that having both insertions and deletions within a word
|
|
@@ -108,11 +140,17 @@ declare class ChangeSet<Data = any> {
|
|
|
108
140
|
} | null;
|
|
109
141
|
/**
|
|
110
142
|
Create a changeset with the given base object and configuration.
|
|
143
|
+
|
|
111
144
|
The `combine` function is used to compare and combine metadata—it
|
|
112
145
|
should return null when metadata isn't compatible, and a combined
|
|
113
146
|
version for a merged range when it is.
|
|
147
|
+
|
|
148
|
+
When given, a token encoder determines how document tokens are
|
|
149
|
+
serialized and compared when diffing the content produced by
|
|
150
|
+
changes. The default is to just compare nodes by name and text
|
|
151
|
+
by character, ignoring marks and attributes.
|
|
114
152
|
*/
|
|
115
|
-
static create<Data = any>(doc: Node, combine?: (dataA: Data, dataB: Data) => Data): ChangeSet<Data>;
|
|
153
|
+
static create<Data = any>(doc: Node, combine?: (dataA: Data, dataB: Data) => Data, tokenEncoder?: TokenEncoder<any>): ChangeSet<Data>;
|
|
116
154
|
}
|
|
117
155
|
|
|
118
|
-
export { Change, ChangeSet, Span, simplifyChanges };
|
|
156
|
+
export { Change, ChangeSet, Span, type TokenEncoder, simplifyChanges };
|
package/dist/index.js
CHANGED
|
@@ -1,24 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
function typeID(type) {
|
|
2
|
+
let cache = type.schema.cached.changeSetIDs || (type.schema.cached.changeSetIDs = Object.create(null));
|
|
3
|
+
let id = cache[type.name];
|
|
4
|
+
if (id == null)
|
|
5
|
+
cache[type.name] = id = Object.keys(type.schema.nodes).indexOf(type.name) + 1;
|
|
6
|
+
return id;
|
|
7
|
+
}
|
|
8
|
+
// The default token encoder, which encodes node open tokens are
|
|
9
|
+
// encoded as strings holding the node name, characters as their
|
|
10
|
+
// character code, and node close tokens as negative numbers.
|
|
11
|
+
const DefaultEncoder = {
|
|
12
|
+
encodeCharacter: char => char,
|
|
13
|
+
encodeNodeStart: node => node.type.name,
|
|
14
|
+
encodeNodeEnd: node => -typeID(node.type),
|
|
15
|
+
compareTokens: (a, b) => a === b
|
|
16
|
+
};
|
|
17
|
+
// Convert the given range of a fragment to tokens.
|
|
18
|
+
function tokens(frag, encoder, start, end, target) {
|
|
5
19
|
for (let i = 0, off = 0; i < frag.childCount; i++) {
|
|
6
20
|
let child = frag.child(i), endOff = off + child.nodeSize;
|
|
7
21
|
let from = Math.max(off, start), to = Math.min(endOff, end);
|
|
8
22
|
if (from < to) {
|
|
9
23
|
if (child.isText) {
|
|
10
24
|
for (let j = from; j < to; j++)
|
|
11
|
-
target.push(child.text.charCodeAt(j - off));
|
|
25
|
+
target.push(encoder.encodeCharacter(child.text.charCodeAt(j - off), child.marks));
|
|
12
26
|
}
|
|
13
27
|
else if (child.isLeaf) {
|
|
14
|
-
target.push(child
|
|
28
|
+
target.push(encoder.encodeNodeStart(child));
|
|
15
29
|
}
|
|
16
30
|
else {
|
|
17
31
|
if (from == off)
|
|
18
|
-
target.push(child
|
|
19
|
-
tokens(child.content, Math.max(off + 1, from) - off - 1, Math.min(endOff - 1, to) - off - 1, target);
|
|
32
|
+
target.push(encoder.encodeNodeStart(child));
|
|
33
|
+
tokens(child.content, encoder, Math.max(off + 1, from) - off - 1, Math.min(endOff - 1, to) - off - 1, target);
|
|
20
34
|
if (to == endOff)
|
|
21
|
-
target.push(
|
|
35
|
+
target.push(encoder.encodeNodeEnd(child));
|
|
22
36
|
}
|
|
23
37
|
}
|
|
24
38
|
off = endOff;
|
|
@@ -37,16 +51,17 @@ const MAX_DIFF_SIZE = 5000;
|
|
|
37
51
|
function minUnchanged(sizeA, sizeB) {
|
|
38
52
|
return Math.min(15, Math.max(2, Math.floor(Math.max(sizeA, sizeB) / 10)));
|
|
39
53
|
}
|
|
40
|
-
function computeDiff(fragA, fragB, range) {
|
|
41
|
-
let tokA = tokens(fragA, range.fromA, range.toA, []);
|
|
42
|
-
let tokB = tokens(fragB, range.fromB, range.toB, []);
|
|
54
|
+
function computeDiff(fragA, fragB, range, encoder = DefaultEncoder) {
|
|
55
|
+
let tokA = tokens(fragA, encoder, range.fromA, range.toA, []);
|
|
56
|
+
let tokB = tokens(fragB, encoder, range.fromB, range.toB, []);
|
|
43
57
|
// Scan from both sides to cheaply eliminate work
|
|
44
58
|
let start = 0, endA = tokA.length, endB = tokB.length;
|
|
45
|
-
|
|
59
|
+
let cmp = encoder.compareTokens;
|
|
60
|
+
while (start < tokA.length && start < tokB.length && cmp(tokA[start], tokB[start]))
|
|
46
61
|
start++;
|
|
47
62
|
if (start == tokA.length && start == tokB.length)
|
|
48
63
|
return [];
|
|
49
|
-
while (endA > start && endB > start && tokA[endA - 1]
|
|
64
|
+
while (endA > start && endB > start && cmp(tokA[endA - 1], tokB[endB - 1]))
|
|
50
65
|
endA--, endB--;
|
|
51
66
|
// If the result is simple _or_ too big to cheaply compute, return
|
|
52
67
|
// the remaining region as the diff
|
|
@@ -65,7 +80,7 @@ function computeDiff(fragA, fragB, range) {
|
|
|
65
80
|
for (let diag = -size; diag <= size; diag += 2) {
|
|
66
81
|
let next = frontier[diag + 1 + max], prev = frontier[diag - 1 + max];
|
|
67
82
|
let x = next < prev ? prev : next + 1, y = x + diag;
|
|
68
|
-
while (x < lenA && y < lenB && tokA[start + x]
|
|
83
|
+
while (x < lenA && y < lenB && cmp(tokA[start + x], tokB[start + y]))
|
|
69
84
|
x++, y++;
|
|
70
85
|
frontier[diag + max] = x;
|
|
71
86
|
// Found a match
|
|
@@ -225,7 +240,7 @@ class Change {
|
|
|
225
240
|
deleted,
|
|
226
241
|
/**
|
|
227
242
|
Data associated with the inserted content. Length adds up to
|
|
228
|
-
`this.toB - this.
|
|
243
|
+
`this.toB - this.fromB`.
|
|
229
244
|
*/
|
|
230
245
|
inserted) {
|
|
231
246
|
this.fromA = fromA;
|
|
@@ -549,7 +564,7 @@ class ChangeSet {
|
|
|
549
564
|
// Only look at changes that touch newly added changed ranges
|
|
550
565
|
!newChanges.some(r => r.toB > change.fromB && r.fromB < change.toB))
|
|
551
566
|
continue;
|
|
552
|
-
let diff = computeDiff(this.config.doc.content, newDoc.content, change);
|
|
567
|
+
let diff = computeDiff(this.config.doc.content, newDoc.content, change, this.config.encoder);
|
|
553
568
|
// Fast path: If they are completely different, don't do anything
|
|
554
569
|
if (diff.length == 1 && diff[0].fromB == 0 && diff[0].toB == change.toB - change.fromB)
|
|
555
570
|
continue;
|
|
@@ -622,12 +637,18 @@ class ChangeSet {
|
|
|
622
637
|
}
|
|
623
638
|
/**
|
|
624
639
|
Create a changeset with the given base object and configuration.
|
|
640
|
+
|
|
625
641
|
The `combine` function is used to compare and combine metadata—it
|
|
626
642
|
should return null when metadata isn't compatible, and a combined
|
|
627
643
|
version for a merged range when it is.
|
|
644
|
+
|
|
645
|
+
When given, a token encoder determines how document tokens are
|
|
646
|
+
serialized and compared when diffing the content produced by
|
|
647
|
+
changes. The default is to just compare nodes by name and text
|
|
648
|
+
by character, ignoring marks and attributes.
|
|
628
649
|
*/
|
|
629
|
-
static create(doc, combine = (a, b) => a === b ? a : null) {
|
|
630
|
-
return new ChangeSet({ combine, doc }, []);
|
|
650
|
+
static create(doc, combine = (a, b) => a === b ? a : null, tokenEncoder = DefaultEncoder) {
|
|
651
|
+
return new ChangeSet({ combine, doc, encoder: tokenEncoder }, []);
|
|
631
652
|
}
|
|
632
653
|
}
|
|
633
654
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prosemirror-changeset",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Distills a series of editing steps into deleted and added ranges",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -29,11 +29,12 @@
|
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@prosemirror/buildhelper": "^0.1.5",
|
|
31
31
|
"prosemirror-model": "^1.0.0",
|
|
32
|
-
"prosemirror-test-builder": "^1.0.0"
|
|
32
|
+
"prosemirror-test-builder": "^1.0.0",
|
|
33
|
+
"builddocs": "^1.0.8"
|
|
33
34
|
},
|
|
34
35
|
"scripts": {
|
|
35
36
|
"test": "pm-runtests",
|
|
36
37
|
"prepare": "pm-buildhelper src/changeset.ts",
|
|
37
|
-
"build-readme": "
|
|
38
|
+
"build-readme": "builddocs --format markdown --main src/README.md src/changeset.ts > README.md"
|
|
38
39
|
}
|
|
39
40
|
}
|
package/src/README.md
CHANGED
package/src/change.ts
CHANGED
|
@@ -66,7 +66,7 @@ export class Change<Data = any> {
|
|
|
66
66
|
/// spans adds up to `this.toA - this.fromA`.
|
|
67
67
|
readonly deleted: readonly Span<Data>[],
|
|
68
68
|
/// Data associated with the inserted content. Length adds up to
|
|
69
|
-
/// `this.toB - this.
|
|
69
|
+
/// `this.toB - this.fromB`.
|
|
70
70
|
readonly inserted: readonly Span<Data>[]
|
|
71
71
|
) {}
|
|
72
72
|
|
package/src/changeset.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {Node} from "prosemirror-model"
|
|
2
2
|
import {StepMap} from "prosemirror-transform"
|
|
3
|
-
import {computeDiff} from "./diff"
|
|
3
|
+
import {computeDiff, TokenEncoder, DefaultEncoder} from "./diff"
|
|
4
4
|
import {Change, Span} from "./change"
|
|
5
5
|
export {Change, Span}
|
|
6
6
|
export {simplifyChanges} from "./simplify"
|
|
7
|
+
export {TokenEncoder}
|
|
7
8
|
|
|
8
9
|
/// A change set tracks the changes to a document from a given point
|
|
9
10
|
/// in the past. It condenses a number of step maps down to a flat
|
|
@@ -13,7 +14,11 @@ export class ChangeSet<Data = any> {
|
|
|
13
14
|
/// @internal
|
|
14
15
|
constructor(
|
|
15
16
|
/// @internal
|
|
16
|
-
readonly config: {
|
|
17
|
+
readonly config: {
|
|
18
|
+
doc: Node,
|
|
19
|
+
combine: (dataA: Data, dataB: Data) => Data,
|
|
20
|
+
encoder: TokenEncoder<any>
|
|
21
|
+
},
|
|
17
22
|
/// Replaced regions.
|
|
18
23
|
readonly changes: readonly Change<Data>[]
|
|
19
24
|
) {}
|
|
@@ -69,7 +74,7 @@ export class ChangeSet<Data = any> {
|
|
|
69
74
|
if (change.fromA == change.toA || change.fromB == change.toB ||
|
|
70
75
|
// Only look at changes that touch newly added changed ranges
|
|
71
76
|
!newChanges.some(r => r.toB > change.fromB && r.fromB < change.toB)) continue
|
|
72
|
-
let diff = computeDiff(this.config.doc.content, newDoc.content, change)
|
|
77
|
+
let diff = computeDiff(this.config.doc.content, newDoc.content, change, this.config.encoder)
|
|
73
78
|
|
|
74
79
|
// Fast path: If they are completely different, don't do anything
|
|
75
80
|
if (diff.length == 1 && diff[0].fromB == 0 && diff[0].toB == change.toB - change.fromB)
|
|
@@ -132,11 +137,21 @@ export class ChangeSet<Data = any> {
|
|
|
132
137
|
}
|
|
133
138
|
|
|
134
139
|
/// Create a changeset with the given base object and configuration.
|
|
140
|
+
///
|
|
135
141
|
/// The `combine` function is used to compare and combine metadata—it
|
|
136
142
|
/// should return null when metadata isn't compatible, and a combined
|
|
137
143
|
/// version for a merged range when it is.
|
|
138
|
-
|
|
139
|
-
|
|
144
|
+
///
|
|
145
|
+
/// When given, a token encoder determines how document tokens are
|
|
146
|
+
/// serialized and compared when diffing the content produced by
|
|
147
|
+
/// changes. The default is to just compare nodes by name and text
|
|
148
|
+
/// by character, ignoring marks and attributes.
|
|
149
|
+
static create<Data = any>(
|
|
150
|
+
doc: Node,
|
|
151
|
+
combine: (dataA: Data, dataB: Data) => Data = (a, b) => a === b ? a : null as any,
|
|
152
|
+
tokenEncoder: TokenEncoder<any> = DefaultEncoder
|
|
153
|
+
) {
|
|
154
|
+
return new ChangeSet({combine, doc, encoder: tokenEncoder}, [])
|
|
140
155
|
}
|
|
141
156
|
|
|
142
157
|
/// Exported for testing @internal
|
package/src/diff.ts
CHANGED
|
@@ -1,22 +1,59 @@
|
|
|
1
|
-
import {Fragment} from "prosemirror-model"
|
|
1
|
+
import {Fragment, Node, NodeType, Mark} from "prosemirror-model"
|
|
2
2
|
import {Change} from "./change"
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
/// A token encoder can be passed when creating a `ChangeSet` in order
|
|
5
|
+
/// to influence the way the library runs its diffing algorithm. The
|
|
6
|
+
/// encoder determines how document tokens (such as nodes and
|
|
7
|
+
/// characters) are encoded and compared.
|
|
8
|
+
///
|
|
9
|
+
/// Note that both the encoding and the comparison may run a lot, and
|
|
10
|
+
/// doing non-trivial work in these functions could impact
|
|
11
|
+
/// performance.
|
|
12
|
+
export interface TokenEncoder<T> {
|
|
13
|
+
/// Encode a given character, with the given marks applied.
|
|
14
|
+
encodeCharacter(char: number, marks: readonly Mark[]): T
|
|
15
|
+
/// Encode the start of a node or, if this is a leaf node, the
|
|
16
|
+
/// entire node.
|
|
17
|
+
encodeNodeStart(node: Node): T
|
|
18
|
+
/// Encode the end token for the given node. It is valid to encode
|
|
19
|
+
/// every end token in the same way.
|
|
20
|
+
encodeNodeEnd(node: Node): T
|
|
21
|
+
/// Compare the given tokens. Should return true when they count as
|
|
22
|
+
/// equal.
|
|
23
|
+
compareTokens(a: T, b: T): boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function typeID(type: NodeType) {
|
|
27
|
+
let cache: Record<string, number> = type.schema.cached.changeSetIDs || (type.schema.cached.changeSetIDs = Object.create(null))
|
|
28
|
+
let id = cache[type.name]
|
|
29
|
+
if (id == null) cache[type.name] = id = Object.keys(type.schema.nodes).indexOf(type.name) + 1
|
|
30
|
+
return id
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// The default token encoder, which encodes node open tokens are
|
|
34
|
+
// encoded as strings holding the node name, characters as their
|
|
35
|
+
// character code, and node close tokens as negative numbers.
|
|
36
|
+
export const DefaultEncoder: TokenEncoder<number | string> = {
|
|
37
|
+
encodeCharacter: char => char,
|
|
38
|
+
encodeNodeStart: node => node.type.name,
|
|
39
|
+
encodeNodeEnd: node => -typeID(node.type),
|
|
40
|
+
compareTokens: (a, b) => a === b
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Convert the given range of a fragment to tokens.
|
|
44
|
+
function tokens<T>(frag: Fragment, encoder: TokenEncoder<T>, start: number, end: number, target: T[]) {
|
|
8
45
|
for (let i = 0, off = 0; i < frag.childCount; i++) {
|
|
9
46
|
let child = frag.child(i), endOff = off + child.nodeSize
|
|
10
47
|
let from = Math.max(off, start), to = Math.min(endOff, end)
|
|
11
48
|
if (from < to) {
|
|
12
49
|
if (child.isText) {
|
|
13
|
-
for (let j = from; j < to; j++) target.push(child.text!.charCodeAt(j - off))
|
|
50
|
+
for (let j = from; j < to; j++) target.push(encoder.encodeCharacter(child.text!.charCodeAt(j - off), child.marks))
|
|
14
51
|
} else if (child.isLeaf) {
|
|
15
|
-
target.push(child
|
|
52
|
+
target.push(encoder.encodeNodeStart(child))
|
|
16
53
|
} else {
|
|
17
|
-
if (from == off) target.push(child
|
|
18
|
-
tokens(child.content, Math.max(off + 1, from) - off - 1, Math.min(endOff - 1, to) - off - 1, target)
|
|
19
|
-
if (to == endOff) target.push(
|
|
54
|
+
if (from == off) target.push(encoder.encodeNodeStart(child))
|
|
55
|
+
tokens(child.content, encoder, Math.max(off + 1, from) - off - 1, Math.min(endOff - 1, to) - off - 1, target)
|
|
56
|
+
if (to == endOff) target.push(encoder.encodeNodeEnd(child))
|
|
20
57
|
}
|
|
21
58
|
}
|
|
22
59
|
off = endOff
|
|
@@ -38,15 +75,16 @@ function minUnchanged(sizeA: number, sizeB: number) {
|
|
|
38
75
|
return Math.min(15, Math.max(2, Math.floor(Math.max(sizeA, sizeB) / 10)))
|
|
39
76
|
}
|
|
40
77
|
|
|
41
|
-
export function computeDiff(fragA: Fragment, fragB: Fragment, range: Change) {
|
|
42
|
-
let tokA = tokens(fragA, range.fromA, range.toA, [])
|
|
43
|
-
let tokB = tokens(fragB, range.fromB, range.toB, [])
|
|
78
|
+
export function computeDiff(fragA: Fragment, fragB: Fragment, range: Change, encoder: TokenEncoder<any> = DefaultEncoder) {
|
|
79
|
+
let tokA = tokens(fragA, encoder, range.fromA, range.toA, [])
|
|
80
|
+
let tokB = tokens(fragB, encoder, range.fromB, range.toB, [])
|
|
44
81
|
|
|
45
82
|
// Scan from both sides to cheaply eliminate work
|
|
46
83
|
let start = 0, endA = tokA.length, endB = tokB.length
|
|
47
|
-
|
|
84
|
+
let cmp = encoder.compareTokens
|
|
85
|
+
while (start < tokA.length && start < tokB.length && cmp(tokA[start], tokB[start])) start++
|
|
48
86
|
if (start == tokA.length && start == tokB.length) return []
|
|
49
|
-
while (endA > start && endB > start && tokA[endA - 1]
|
|
87
|
+
while (endA > start && endB > start && cmp(tokA[endA - 1], tokB[endB - 1])) endA--, endB--
|
|
50
88
|
// If the result is simple _or_ too big to cheaply compute, return
|
|
51
89
|
// the remaining region as the diff
|
|
52
90
|
if (endA == start || endB == start || (endA == endB && endA == start + 1))
|
|
@@ -66,7 +104,7 @@ export function computeDiff(fragA: Fragment, fragB: Fragment, range: Change) {
|
|
|
66
104
|
for (let diag = -size; diag <= size; diag += 2) {
|
|
67
105
|
let next = frontier[diag + 1 + max], prev = frontier[diag - 1 + max]
|
|
68
106
|
let x = next < prev ? prev : next + 1, y = x + diag
|
|
69
|
-
while (x < lenA && y < lenB && tokA[start + x]
|
|
107
|
+
while (x < lenA && y < lenB && cmp(tokA[start + x], tokB[start + y])) x++, y++
|
|
70
108
|
frontier[diag + max] = x
|
|
71
109
|
// Found a match
|
|
72
110
|
if (x >= lenA && y >= lenB) {
|
package/test/test-diff.ts
CHANGED