@b9g/revise 0.1.2 → 0.1.4
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/LICENSE +7 -0
- package/README.md +147 -58
- package/package.json +59 -31
- package/{_subseq.d.ts → src/_subseq.d.ts} +18 -18
- package/src/contentarea.cjs +649 -0
- package/{contentarea.d.ts → src/contentarea.d.ts} +74 -57
- package/src/contentarea.js +624 -0
- package/src/edit.cjs +767 -0
- package/{edit.d.ts → src/edit.d.ts} +93 -99
- package/src/edit.js +741 -0
- package/src/history.cjs +100 -0
- package/{history.d.ts → src/history.d.ts} +13 -13
- package/src/history.js +76 -0
- package/src/keyer.cjs +93 -0
- package/{keyer.d.ts → src/keyer.d.ts} +8 -8
- package/src/keyer.js +69 -0
- package/src/state.cjs +142 -0
- package/src/state.d.ts +31 -0
- package/src/state.js +117 -0
- package/contentarea.cjs +0 -597
- package/contentarea.cjs.map +0 -1
- package/contentarea.js +0 -593
- package/contentarea.js.map +0 -1
- package/edit.cjs +0 -618
- package/edit.cjs.map +0 -1
- package/edit.js +0 -615
- package/edit.js.map +0 -1
- package/history.cjs +0 -81
- package/history.cjs.map +0 -1
- package/history.js +0 -78
- package/history.js.map +0 -1
- package/keyer.cjs +0 -42
- package/keyer.cjs.map +0 -1
- package/keyer.js +0 -39
- package/keyer.js.map +0 -1
package/edit.cjs
DELETED
|
@@ -1,618 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
-
|
|
5
|
-
function measure(subseq) {
|
|
6
|
-
let length = 0, includedLength = 0, excludedLength = 0;
|
|
7
|
-
for (let i = 0; i < subseq.length; i++) {
|
|
8
|
-
const s = subseq[i];
|
|
9
|
-
length += s;
|
|
10
|
-
if (i % 2 === 0) {
|
|
11
|
-
excludedLength += s;
|
|
12
|
-
}
|
|
13
|
-
else {
|
|
14
|
-
includedLength += s;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
return { length, includedLength, excludedLength };
|
|
18
|
-
}
|
|
19
|
-
function pushSegment(subseq, length, included) {
|
|
20
|
-
if (length < 0) {
|
|
21
|
-
throw new RangeError("Negative length");
|
|
22
|
-
}
|
|
23
|
-
else if (length === 0) {
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
else if (!subseq.length) {
|
|
27
|
-
if (included) {
|
|
28
|
-
subseq.push(0, length);
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
subseq.push(length);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
const included1 = subseq.length % 2 === 0;
|
|
36
|
-
if (included === included1) {
|
|
37
|
-
subseq[subseq.length - 1] += length;
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
subseq.push(length);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
function align(subseq1, subseq2) {
|
|
45
|
-
if (measure(subseq1).length !== measure(subseq2).length) {
|
|
46
|
-
throw new Error("Length mismatch");
|
|
47
|
-
}
|
|
48
|
-
const result = [];
|
|
49
|
-
for (let i1 = 0, i2 = 0, length1 = 0, length2 = 0, included1 = true, included2 = true; i1 < subseq1.length || i2 < subseq2.length;) {
|
|
50
|
-
if (length1 === 0) {
|
|
51
|
-
if (i1 >= subseq1.length) {
|
|
52
|
-
throw new Error("Length mismatch");
|
|
53
|
-
}
|
|
54
|
-
length1 = subseq1[i1++];
|
|
55
|
-
included1 = !included1;
|
|
56
|
-
}
|
|
57
|
-
if (length2 === 0) {
|
|
58
|
-
if (i2 >= subseq2.length) {
|
|
59
|
-
throw new Error("Size mismatch");
|
|
60
|
-
}
|
|
61
|
-
length2 = subseq2[i2++];
|
|
62
|
-
included2 = !included2;
|
|
63
|
-
}
|
|
64
|
-
if (length1 < length2) {
|
|
65
|
-
if (length1) {
|
|
66
|
-
result.push([length1, included1, included2]);
|
|
67
|
-
}
|
|
68
|
-
length2 = length2 - length1;
|
|
69
|
-
length1 = 0;
|
|
70
|
-
}
|
|
71
|
-
else if (length1 > length2) {
|
|
72
|
-
if (length2) {
|
|
73
|
-
result.push([length2, included1, included2]);
|
|
74
|
-
}
|
|
75
|
-
length1 = length1 - length2;
|
|
76
|
-
length2 = 0;
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
if (length1) {
|
|
80
|
-
result.push([length1, included1, included2]);
|
|
81
|
-
}
|
|
82
|
-
length1 = length2 = 0;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return result;
|
|
86
|
-
}
|
|
87
|
-
function union(subseq1, subseq2) {
|
|
88
|
-
const result = [];
|
|
89
|
-
for (const [length, included1, included2] of align(subseq1, subseq2)) {
|
|
90
|
-
pushSegment(result, length, included1 || included2);
|
|
91
|
-
}
|
|
92
|
-
return result;
|
|
93
|
-
}
|
|
94
|
-
function intersection(subseq1, subseq2) {
|
|
95
|
-
const result = [];
|
|
96
|
-
for (const [length, included1, included2] of align(subseq1, subseq2)) {
|
|
97
|
-
pushSegment(result, length, included1 && included2);
|
|
98
|
-
}
|
|
99
|
-
return result;
|
|
100
|
-
}
|
|
101
|
-
function shrink(subseq1, subseq2) {
|
|
102
|
-
if (measure(subseq1).length !== measure(subseq2).length) {
|
|
103
|
-
throw new Error("Length mismatch");
|
|
104
|
-
}
|
|
105
|
-
const result = [];
|
|
106
|
-
for (const [length, included1, included2] of align(subseq1, subseq2)) {
|
|
107
|
-
if (!included2) {
|
|
108
|
-
pushSegment(result, length, included1);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return result;
|
|
112
|
-
}
|
|
113
|
-
function expand(subseq1, subseq2) {
|
|
114
|
-
if (measure(subseq1).length !== measure(subseq2).excludedLength) {
|
|
115
|
-
throw new Error("Length mismatch");
|
|
116
|
-
}
|
|
117
|
-
const result = [];
|
|
118
|
-
for (let i1 = 0, i2 = 0, length1 = 0, included1 = true, included2 = true; i2 < subseq2.length; i2++) {
|
|
119
|
-
let length2 = subseq2[i2];
|
|
120
|
-
included2 = !included2;
|
|
121
|
-
if (included2) {
|
|
122
|
-
pushSegment(result, length2, false);
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
while (length2) {
|
|
126
|
-
if (length1 === 0) {
|
|
127
|
-
length1 = subseq1[i1++];
|
|
128
|
-
included1 = !included1;
|
|
129
|
-
}
|
|
130
|
-
const minLength = Math.min(length1, length2);
|
|
131
|
-
pushSegment(result, minLength, included1);
|
|
132
|
-
length1 -= minLength;
|
|
133
|
-
length2 -= minLength;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return result;
|
|
138
|
-
}
|
|
139
|
-
function interleave(subseq1, subseq2) {
|
|
140
|
-
if (measure(subseq1).excludedLength !== measure(subseq2).excludedLength) {
|
|
141
|
-
throw new Error("Length mismatch");
|
|
142
|
-
}
|
|
143
|
-
const result1 = [];
|
|
144
|
-
const result2 = [];
|
|
145
|
-
for (let i1 = 0, i2 = 0, length1 = 0, length2 = 0, included1 = true, included2 = true; i1 < subseq1.length || i2 < subseq2.length;) {
|
|
146
|
-
if (length1 === 0 && i1 < subseq1.length) {
|
|
147
|
-
length1 = subseq1[i1++];
|
|
148
|
-
included1 = !included1;
|
|
149
|
-
}
|
|
150
|
-
if (length2 === 0 && i2 < subseq2.length) {
|
|
151
|
-
length2 = subseq2[i2++];
|
|
152
|
-
included2 = !included2;
|
|
153
|
-
}
|
|
154
|
-
if (included1 && included2) {
|
|
155
|
-
pushSegment(result1, length1, true);
|
|
156
|
-
pushSegment(result1, length2, false);
|
|
157
|
-
pushSegment(result2, length1, false);
|
|
158
|
-
pushSegment(result2, length2, true);
|
|
159
|
-
length1 = length2 = 0;
|
|
160
|
-
}
|
|
161
|
-
else if (included1) {
|
|
162
|
-
pushSegment(result1, length1, true);
|
|
163
|
-
pushSegment(result2, length1, false);
|
|
164
|
-
length1 = 0;
|
|
165
|
-
}
|
|
166
|
-
else if (included2) {
|
|
167
|
-
pushSegment(result1, length2, false);
|
|
168
|
-
pushSegment(result2, length2, true);
|
|
169
|
-
length2 = 0;
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
const minLength = Math.min(length1, length2);
|
|
173
|
-
pushSegment(result1, minLength, false);
|
|
174
|
-
pushSegment(result2, minLength, false);
|
|
175
|
-
length1 -= minLength;
|
|
176
|
-
length2 -= minLength;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
return [result1, result2];
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/** A compact data structure for representing changes to strings. */
|
|
183
|
-
class Edit {
|
|
184
|
-
constructor(parts, deleted) {
|
|
185
|
-
this.parts = parts;
|
|
186
|
-
this.deleted = deleted;
|
|
187
|
-
}
|
|
188
|
-
/** A string which represents a concatenation of all insertions. */
|
|
189
|
-
get inserted() {
|
|
190
|
-
let text = "";
|
|
191
|
-
for (let i = 0; i < this.parts.length; i++) {
|
|
192
|
-
if (typeof this.parts[i] === "string") {
|
|
193
|
-
text += this.parts[i];
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
return text;
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Returns an array of operations, which is more readable than the parts
|
|
200
|
-
* array.
|
|
201
|
-
*
|
|
202
|
-
* new Edit([0, 1, " ", 2], "x").operations();
|
|
203
|
-
* [
|
|
204
|
-
* {type: "retain", start: 0, end: 1},
|
|
205
|
-
* {type: "insert", start: 1, value: " "},
|
|
206
|
-
* {type: "delete", start: 1, end: 2, value: "x"},
|
|
207
|
-
* ]
|
|
208
|
-
*
|
|
209
|
-
* When insertions and deletions happen at the same index, insertions will
|
|
210
|
-
* always appear before deletions in the operations array.
|
|
211
|
-
*/
|
|
212
|
-
operations() {
|
|
213
|
-
const operations = [];
|
|
214
|
-
let retaining = false;
|
|
215
|
-
let index = 0;
|
|
216
|
-
let deleteStart = 0;
|
|
217
|
-
for (let i = 0; i < this.parts.length; i++) {
|
|
218
|
-
const part = this.parts[i];
|
|
219
|
-
if (typeof part === "number") {
|
|
220
|
-
if (part < index) {
|
|
221
|
-
throw new TypeError("Malformed edit");
|
|
222
|
-
}
|
|
223
|
-
else if (part > index) {
|
|
224
|
-
if (retaining) {
|
|
225
|
-
operations.push({ type: "retain", start: index, end: part });
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
const value = typeof this.deleted === "undefined"
|
|
229
|
-
? undefined
|
|
230
|
-
: this.deleted.slice(deleteStart, part);
|
|
231
|
-
operations.push({
|
|
232
|
-
type: "delete",
|
|
233
|
-
start: index,
|
|
234
|
-
end: part,
|
|
235
|
-
value,
|
|
236
|
-
});
|
|
237
|
-
deleteStart = part;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
index = part;
|
|
241
|
-
retaining = !retaining;
|
|
242
|
-
}
|
|
243
|
-
else {
|
|
244
|
-
operations.push({ type: "insert", start: index, value: part });
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
return operations;
|
|
248
|
-
}
|
|
249
|
-
apply(text) {
|
|
250
|
-
let text1 = "";
|
|
251
|
-
const operations = this.operations();
|
|
252
|
-
for (let i = 0; i < operations.length; i++) {
|
|
253
|
-
const op = operations[i];
|
|
254
|
-
switch (op.type) {
|
|
255
|
-
case "retain":
|
|
256
|
-
text1 += text.slice(op.start, op.end);
|
|
257
|
-
break;
|
|
258
|
-
case "insert":
|
|
259
|
-
text1 += op.value;
|
|
260
|
-
break;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
return text1;
|
|
264
|
-
}
|
|
265
|
-
/** Composes two consecutive edits. */
|
|
266
|
-
compose(that) {
|
|
267
|
-
let [insertSeq1, inserted1, deleteSeq1, deleted1] = factor(this);
|
|
268
|
-
let [insertSeq2, inserted2, deleteSeq2, deleted2] = factor(that);
|
|
269
|
-
// Expand all subseqs so that they share the same coordinate space.
|
|
270
|
-
deleteSeq1 = expand(deleteSeq1, insertSeq1);
|
|
271
|
-
deleteSeq2 = expand(deleteSeq2, deleteSeq1);
|
|
272
|
-
[deleteSeq1, insertSeq2] = interleave(deleteSeq1, insertSeq2);
|
|
273
|
-
deleteSeq2 = expand(deleteSeq2, insertSeq2);
|
|
274
|
-
insertSeq1 = expand(insertSeq1, insertSeq2);
|
|
275
|
-
{
|
|
276
|
-
// Find insertions which have been deleted and remove them.
|
|
277
|
-
const toggleSeq = intersection(insertSeq1, deleteSeq2);
|
|
278
|
-
if (measure(toggleSeq).includedLength) {
|
|
279
|
-
deleteSeq1 = shrink(deleteSeq1, toggleSeq);
|
|
280
|
-
inserted1 = erase(insertSeq1, inserted1, toggleSeq);
|
|
281
|
-
insertSeq1 = shrink(insertSeq1, toggleSeq);
|
|
282
|
-
deleteSeq2 = shrink(deleteSeq2, toggleSeq);
|
|
283
|
-
insertSeq2 = shrink(insertSeq2, toggleSeq);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
const insertSeq = union(insertSeq1, insertSeq2);
|
|
287
|
-
const inserted = consolidate(insertSeq1, inserted1, insertSeq2, inserted2);
|
|
288
|
-
const deleteSeq = shrink(union(deleteSeq1, deleteSeq2), insertSeq);
|
|
289
|
-
const deleted = deleted1 != null && deleted2 != null
|
|
290
|
-
? consolidate(deleteSeq1, deleted1, deleteSeq2, deleted2)
|
|
291
|
-
: undefined;
|
|
292
|
-
return synthesize(insertSeq, inserted, deleteSeq, deleted).normalize();
|
|
293
|
-
}
|
|
294
|
-
invert() {
|
|
295
|
-
if (typeof this.deleted === "undefined") {
|
|
296
|
-
throw new Error("Edit is not invertible");
|
|
297
|
-
}
|
|
298
|
-
let [insertSeq, inserted, deleteSeq, deleted] = factor(this);
|
|
299
|
-
deleteSeq = expand(deleteSeq, insertSeq);
|
|
300
|
-
insertSeq = shrink(insertSeq, deleteSeq);
|
|
301
|
-
return synthesize(deleteSeq, deleted, insertSeq, inserted);
|
|
302
|
-
}
|
|
303
|
-
normalize() {
|
|
304
|
-
if (typeof this.deleted === "undefined") {
|
|
305
|
-
throw new Error("Edit is not normalizable");
|
|
306
|
-
}
|
|
307
|
-
const insertSeq = [];
|
|
308
|
-
const deleteSeq = [];
|
|
309
|
-
let inserted = "";
|
|
310
|
-
let deleted = "";
|
|
311
|
-
let insertion;
|
|
312
|
-
const operations = this.operations();
|
|
313
|
-
for (let i = 0; i < operations.length; i++) {
|
|
314
|
-
const op = operations[i];
|
|
315
|
-
switch (op.type) {
|
|
316
|
-
case "insert": {
|
|
317
|
-
insertion = op.value;
|
|
318
|
-
break;
|
|
319
|
-
}
|
|
320
|
-
case "retain": {
|
|
321
|
-
if (insertion !== undefined) {
|
|
322
|
-
pushSegment(insertSeq, insertion.length, true);
|
|
323
|
-
inserted += insertion;
|
|
324
|
-
insertion = undefined;
|
|
325
|
-
}
|
|
326
|
-
pushSegment(insertSeq, op.end - op.start, false);
|
|
327
|
-
pushSegment(deleteSeq, op.end - op.start, false);
|
|
328
|
-
break;
|
|
329
|
-
}
|
|
330
|
-
case "delete": {
|
|
331
|
-
const length = op.end - op.start;
|
|
332
|
-
const deletion = op.value;
|
|
333
|
-
let prefix = 0;
|
|
334
|
-
let suffix = 0;
|
|
335
|
-
if (insertion !== undefined) {
|
|
336
|
-
if (insertion === deletion) {
|
|
337
|
-
prefix = deletion.length;
|
|
338
|
-
}
|
|
339
|
-
else {
|
|
340
|
-
prefix = commonPrefixLength(insertion, deletion);
|
|
341
|
-
suffix = commonSuffixLength(insertion.slice(prefix), deletion.slice(prefix));
|
|
342
|
-
}
|
|
343
|
-
pushSegment(insertSeq, prefix, false);
|
|
344
|
-
pushSegment(insertSeq, insertion.length - prefix - suffix, true);
|
|
345
|
-
inserted += insertion.slice(prefix, insertion.length - suffix);
|
|
346
|
-
}
|
|
347
|
-
deleted += deletion.slice(prefix, deletion.length - suffix);
|
|
348
|
-
pushSegment(deleteSeq, prefix, false);
|
|
349
|
-
// TODO: This line is throwing for some reason
|
|
350
|
-
pushSegment(deleteSeq, length - prefix - suffix, true);
|
|
351
|
-
pushSegment(deleteSeq, suffix, false);
|
|
352
|
-
pushSegment(insertSeq, length - prefix - suffix, false);
|
|
353
|
-
pushSegment(insertSeq, suffix, false);
|
|
354
|
-
insertion = undefined;
|
|
355
|
-
break;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
if (insertion !== undefined) {
|
|
360
|
-
pushSegment(insertSeq, insertion.length, true);
|
|
361
|
-
inserted += insertion;
|
|
362
|
-
}
|
|
363
|
-
return synthesize(insertSeq, inserted, deleteSeq, deleted);
|
|
364
|
-
}
|
|
365
|
-
hasChangesBetween(start, end) {
|
|
366
|
-
const ops = this.operations();
|
|
367
|
-
for (const op of ops) {
|
|
368
|
-
switch (op.type) {
|
|
369
|
-
case "delete": {
|
|
370
|
-
if ((start <= op.start && op.start <= end) ||
|
|
371
|
-
(start <= op.end && op.end <= end)) {
|
|
372
|
-
return true;
|
|
373
|
-
}
|
|
374
|
-
break;
|
|
375
|
-
}
|
|
376
|
-
case "insert": {
|
|
377
|
-
if (start <= op.start && op.start <= end) {
|
|
378
|
-
return true;
|
|
379
|
-
}
|
|
380
|
-
break;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
return false;
|
|
385
|
-
}
|
|
386
|
-
static builder(value) {
|
|
387
|
-
let index = 0;
|
|
388
|
-
let inserted = "";
|
|
389
|
-
let deleted = undefined;
|
|
390
|
-
const insertSeq = [];
|
|
391
|
-
const deleteSeq = [];
|
|
392
|
-
return {
|
|
393
|
-
retain(length) {
|
|
394
|
-
if (value != null) {
|
|
395
|
-
length = Math.min(value.length - index, length);
|
|
396
|
-
}
|
|
397
|
-
index += length;
|
|
398
|
-
pushSegment(insertSeq, length, false);
|
|
399
|
-
pushSegment(deleteSeq, length, false);
|
|
400
|
-
return this;
|
|
401
|
-
},
|
|
402
|
-
delete(length) {
|
|
403
|
-
if (value != null) {
|
|
404
|
-
length = Math.min(value.length - index, length);
|
|
405
|
-
deleted = (deleted || "") + value.slice(index, index + length);
|
|
406
|
-
}
|
|
407
|
-
index += length;
|
|
408
|
-
pushSegment(insertSeq, length, false);
|
|
409
|
-
pushSegment(deleteSeq, length, true);
|
|
410
|
-
return this;
|
|
411
|
-
},
|
|
412
|
-
insert(value) {
|
|
413
|
-
pushSegment(insertSeq, value.length, true);
|
|
414
|
-
inserted += value;
|
|
415
|
-
return this;
|
|
416
|
-
},
|
|
417
|
-
concat(edit) {
|
|
418
|
-
const ops = edit.operations();
|
|
419
|
-
for (const op of ops) {
|
|
420
|
-
switch (op.type) {
|
|
421
|
-
case "delete":
|
|
422
|
-
this.delete(op.end - op.start);
|
|
423
|
-
break;
|
|
424
|
-
case "insert":
|
|
425
|
-
this.insert(op.value);
|
|
426
|
-
break;
|
|
427
|
-
case "retain":
|
|
428
|
-
this.retain(op.end - op.start);
|
|
429
|
-
break;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
if (value != null && index > value.length) {
|
|
433
|
-
throw new RangeError("Edit is longer than original value");
|
|
434
|
-
}
|
|
435
|
-
return this;
|
|
436
|
-
},
|
|
437
|
-
build() {
|
|
438
|
-
if (value != null) {
|
|
439
|
-
deleted = deleted || "";
|
|
440
|
-
if (index < value.length) {
|
|
441
|
-
pushSegment(insertSeq, value.length - index, false);
|
|
442
|
-
pushSegment(deleteSeq, value.length - index, false);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
return synthesize(insertSeq, inserted, deleteSeq, deleted);
|
|
446
|
-
},
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
/**
|
|
450
|
-
* Given two strings, this method finds an edit which can be applied to the
|
|
451
|
-
* first string to result in the second.
|
|
452
|
-
*
|
|
453
|
-
* @param startHint - An optional hint can be provided to disambiguate edits
|
|
454
|
-
* which cannot be inferred by comparing the text alone. For example,
|
|
455
|
-
* inserting "a" into the string "aaaa" to make it "aaaaa" could be an
|
|
456
|
-
* insertion at any index in the string. This value should be the smaller of
|
|
457
|
-
* the start indices of the selection from before and after the edit.
|
|
458
|
-
*/
|
|
459
|
-
static diff(text1, text2, startHint) {
|
|
460
|
-
let prefix = commonPrefixLength(text1, text2);
|
|
461
|
-
let suffix = commonSuffixLength(text1, text2);
|
|
462
|
-
// prefix and suffix overlap when edits are in runs of the same character.
|
|
463
|
-
if (prefix + suffix > Math.min(text1.length, text2.length)) {
|
|
464
|
-
if (startHint != null && startHint >= 0) {
|
|
465
|
-
prefix = Math.min(prefix, startHint);
|
|
466
|
-
}
|
|
467
|
-
// TODO: We can probably avoid the commonSuffixLength() call here in
|
|
468
|
-
// favor of arithmetic but I’m too dumb to figure it out.
|
|
469
|
-
suffix = commonSuffixLength(text1.slice(prefix), text2.slice(prefix));
|
|
470
|
-
}
|
|
471
|
-
return Edit.builder(text1)
|
|
472
|
-
.retain(prefix)
|
|
473
|
-
.insert(text2.slice(prefix, text2.length - suffix))
|
|
474
|
-
.delete(text1.length - prefix - suffix)
|
|
475
|
-
.retain(suffix)
|
|
476
|
-
.build();
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
function synthesize(insertSeq, inserted, deleteSeq, deleted) {
|
|
480
|
-
if (measure(insertSeq).includedLength !== inserted.length) {
|
|
481
|
-
throw new Error("insertSeq and inserted string do not match in length");
|
|
482
|
-
}
|
|
483
|
-
else if (deleted !== undefined &&
|
|
484
|
-
measure(deleteSeq).includedLength !== deleted.length) {
|
|
485
|
-
throw new Error("deleteSeq and deleted string do not match in length");
|
|
486
|
-
}
|
|
487
|
-
const parts = [];
|
|
488
|
-
let insertIndex = 0;
|
|
489
|
-
let retainIndex = 0;
|
|
490
|
-
let needsLength = true;
|
|
491
|
-
for (const [length, deleting, inserting] of align(expand(deleteSeq, insertSeq), insertSeq)) {
|
|
492
|
-
if (inserting) {
|
|
493
|
-
const insertion = inserted.slice(insertIndex, insertIndex + length);
|
|
494
|
-
if (parts.length && typeof parts[parts.length - 1] === "string") {
|
|
495
|
-
parts[parts.length - 1] += insertion;
|
|
496
|
-
}
|
|
497
|
-
else {
|
|
498
|
-
parts.push(insertion);
|
|
499
|
-
}
|
|
500
|
-
insertIndex += length;
|
|
501
|
-
}
|
|
502
|
-
else {
|
|
503
|
-
if (!deleting) {
|
|
504
|
-
parts.push(retainIndex, retainIndex + length);
|
|
505
|
-
}
|
|
506
|
-
retainIndex += length;
|
|
507
|
-
needsLength = deleting;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
if (needsLength) {
|
|
511
|
-
parts.push(retainIndex);
|
|
512
|
-
}
|
|
513
|
-
return new Edit(parts, deleted);
|
|
514
|
-
}
|
|
515
|
-
function factor(edit) {
|
|
516
|
-
const insertSeq = [];
|
|
517
|
-
const deleteSeq = [];
|
|
518
|
-
let inserted = "";
|
|
519
|
-
const operations = edit.operations();
|
|
520
|
-
for (let i = 0; i < operations.length; i++) {
|
|
521
|
-
const op = operations[i];
|
|
522
|
-
switch (op.type) {
|
|
523
|
-
case "retain": {
|
|
524
|
-
const length = op.end - op.start;
|
|
525
|
-
pushSegment(insertSeq, length, false);
|
|
526
|
-
pushSegment(deleteSeq, length, false);
|
|
527
|
-
break;
|
|
528
|
-
}
|
|
529
|
-
case "delete": {
|
|
530
|
-
const length = op.end - op.start;
|
|
531
|
-
pushSegment(insertSeq, length, false);
|
|
532
|
-
pushSegment(deleteSeq, length, true);
|
|
533
|
-
break;
|
|
534
|
-
}
|
|
535
|
-
case "insert":
|
|
536
|
-
pushSegment(insertSeq, op.value.length, true);
|
|
537
|
-
inserted += op.value;
|
|
538
|
-
break;
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
return [insertSeq, inserted, deleteSeq, edit.deleted];
|
|
542
|
-
}
|
|
543
|
-
/**
|
|
544
|
-
* Given two subseqs and strings which are represented by the included segments
|
|
545
|
-
* of the subseqs, this function combines the two strings so that they overlap
|
|
546
|
-
* according to the positions of the included segments of subseqs.
|
|
547
|
-
*
|
|
548
|
-
* The subseqs must have the same length, and the included segments of these
|
|
549
|
-
* subseqs may not overlap.
|
|
550
|
-
*/
|
|
551
|
-
function consolidate(subseq1, text1, subseq2, text2) {
|
|
552
|
-
let i1 = 0;
|
|
553
|
-
let i2 = 0;
|
|
554
|
-
let result = "";
|
|
555
|
-
for (const [length, included1, included2] of align(subseq1, subseq2)) {
|
|
556
|
-
if (included1 && included2) {
|
|
557
|
-
throw new Error("Overlapping subseqs");
|
|
558
|
-
}
|
|
559
|
-
else if (included1) {
|
|
560
|
-
result += text1.slice(i1, i1 + length);
|
|
561
|
-
i1 += length;
|
|
562
|
-
}
|
|
563
|
-
else if (included2) {
|
|
564
|
-
result += text2.slice(i2, i2 + length);
|
|
565
|
-
i2 += length;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
return result;
|
|
569
|
-
}
|
|
570
|
-
/**
|
|
571
|
-
* Given two subseqs as well a string which is represented by the included
|
|
572
|
-
* segments of the first subseq, this function returns the result of removing
|
|
573
|
-
* the included segments of the second subseq from the first subseq.
|
|
574
|
-
*
|
|
575
|
-
* The subseqs must have the same length, and the included segments of the second
|
|
576
|
-
* subseq must overlap with the first subseq’s included segments.
|
|
577
|
-
*/
|
|
578
|
-
function erase(subseq1, str, subseq2) {
|
|
579
|
-
let i = 0;
|
|
580
|
-
let result = "";
|
|
581
|
-
for (const [length, included1, included2] of align(subseq1, subseq2)) {
|
|
582
|
-
if (included1) {
|
|
583
|
-
if (!included2) {
|
|
584
|
-
result += str.slice(i, i + length);
|
|
585
|
-
}
|
|
586
|
-
i += length;
|
|
587
|
-
}
|
|
588
|
-
else if (included2) {
|
|
589
|
-
throw new Error("Non-overlapping subseqs");
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
return result;
|
|
593
|
-
}
|
|
594
|
-
/** @returns The length of the common prefix between two strings. */
|
|
595
|
-
function commonPrefixLength(text1, text2) {
|
|
596
|
-
const length = Math.min(text1.length, text2.length);
|
|
597
|
-
for (let i = 0; i < length; i++) {
|
|
598
|
-
if (text1[i] !== text2[i]) {
|
|
599
|
-
return i;
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
return length;
|
|
603
|
-
}
|
|
604
|
-
/** @returns The length of the common suffix between two strings. */
|
|
605
|
-
function commonSuffixLength(text1, text2) {
|
|
606
|
-
const length1 = text1.length;
|
|
607
|
-
const length2 = text2.length;
|
|
608
|
-
const length = Math.min(length1, length2);
|
|
609
|
-
for (let i = 0; i < length; i++) {
|
|
610
|
-
if (text1[length1 - i - 1] !== text2[length2 - i - 1]) {
|
|
611
|
-
return i;
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
return length;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
exports.Edit = Edit;
|
|
618
|
-
//# sourceMappingURL=edit.cjs.map
|