@limetech/lime-elements 39.9.0 → 39.9.2

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.
Files changed (83) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cjs/{component-DC8AD-ZG.js → component-BrNWuGdj.js} +1 -1
  3. package/dist/cjs/{component-BflTavfP.js → component-CFk7Dfoi.js} +3 -3
  4. package/dist/cjs/{component--XvTU1MI.js → component-y8swlNNR.js} +1 -1
  5. package/dist/cjs/lime-elements.cjs.js +1 -1
  6. package/dist/cjs/limel-breadcrumbs_7.cjs.entry.js +4 -4
  7. package/dist/cjs/limel-button-group.cjs.entry.js +3 -7
  8. package/dist/cjs/limel-chip_2.cjs.entry.js +4 -4
  9. package/dist/cjs/limel-code-diff.cjs.entry.js +645 -711
  10. package/dist/cjs/limel-date-picker.cjs.entry.js +4 -4
  11. package/dist/cjs/limel-dialog.cjs.entry.js +2 -2
  12. package/dist/cjs/limel-portal_3.cjs.entry.js +3 -3
  13. package/dist/cjs/limel-prosemirror-adapter.cjs.entry.js +1 -1
  14. package/dist/cjs/limel-select.cjs.entry.js +2 -2
  15. package/dist/cjs/limel-slider.cjs.entry.js +2 -2
  16. package/dist/cjs/limel-switch.cjs.entry.js +18 -632
  17. package/dist/cjs/limel-tab-bar.cjs.entry.js +4 -4
  18. package/dist/cjs/limel-tab-panel.cjs.entry.js +1 -1
  19. package/dist/cjs/limel-table.cjs.entry.js +3 -3
  20. package/dist/cjs/limel-text-editor-link-menu.cjs.entry.js +3 -3
  21. package/dist/cjs/limel-text-editor.cjs.entry.js +1 -1
  22. package/dist/cjs/loader.cjs.js +1 -1
  23. package/dist/cjs/{ponyfill-CNdYytGF.js → ponyfill-BgIFyUG5.js} +0 -1
  24. package/dist/collection/components/button-group/button-group.css +38 -33
  25. package/dist/collection/components/button-group/button-group.js +3 -7
  26. package/dist/collection/components/switch/switch.css +69 -769
  27. package/dist/collection/components/switch/switch.js +18 -50
  28. package/dist/collection/components/tab-bar/tab-bar.js +2 -2
  29. package/dist/collection/components/tab-panel/tab-panel.js +1 -1
  30. package/dist/collection/components/table/table.js +3 -3
  31. package/dist/collection/components/text-editor/link-menu/editor-link-menu.js +3 -3
  32. package/dist/collection/components/text-editor/prosemirror-adapter/prosemirror-adapter.js +1 -1
  33. package/dist/collection/components/text-editor/text-editor.js +1 -1
  34. package/dist/collection/components/tooltip/tooltip-content.js +1 -1
  35. package/dist/collection/components/tooltip/tooltip.js +2 -2
  36. package/dist/esm/{component-CfpvJ0ln.js → component-7QB0OUSF.js} +1 -1
  37. package/dist/esm/{component-zxri0Bi3.js → component-D4qgwaTf.js} +1 -1
  38. package/dist/esm/{component-BsWKksqY.js → component-wGVqvUmL.js} +3 -3
  39. package/dist/esm/lime-elements.js +1 -1
  40. package/dist/esm/limel-breadcrumbs_7.entry.js +4 -4
  41. package/dist/esm/limel-button-group.entry.js +4 -8
  42. package/dist/esm/limel-chip_2.entry.js +4 -4
  43. package/dist/esm/limel-code-diff.entry.js +645 -711
  44. package/dist/esm/limel-date-picker.entry.js +4 -4
  45. package/dist/esm/limel-dialog.entry.js +2 -2
  46. package/dist/esm/limel-portal_3.entry.js +3 -3
  47. package/dist/esm/limel-prosemirror-adapter.entry.js +1 -1
  48. package/dist/esm/limel-select.entry.js +2 -2
  49. package/dist/esm/limel-slider.entry.js +2 -2
  50. package/dist/esm/limel-switch.entry.js +19 -633
  51. package/dist/esm/limel-tab-bar.entry.js +4 -4
  52. package/dist/esm/limel-tab-panel.entry.js +1 -1
  53. package/dist/esm/limel-table.entry.js +3 -3
  54. package/dist/esm/limel-text-editor-link-menu.entry.js +3 -3
  55. package/dist/esm/limel-text-editor.entry.js +1 -1
  56. package/dist/esm/loader.js +1 -1
  57. package/dist/esm/{ponyfill-D-I_3IxW.js → ponyfill-ChRGk668.js} +1 -1
  58. package/dist/lime-elements/lime-elements.esm.js +1 -1
  59. package/dist/lime-elements/{p-89dfbd4a.entry.js → p-13f4d39d.entry.js} +1 -1
  60. package/dist/lime-elements/{p-68ffd790.entry.js → p-1a3a7374.entry.js} +1 -1
  61. package/dist/lime-elements/p-266c228c.entry.js +1 -0
  62. package/dist/lime-elements/{p-90f8d2ef.entry.js → p-288aa326.entry.js} +1 -1
  63. package/dist/lime-elements/{p-6b05db4a.entry.js → p-2af214de.entry.js} +1 -1
  64. package/dist/lime-elements/{p-c6e9af7c.entry.js → p-37b41bad.entry.js} +1 -1
  65. package/dist/lime-elements/{p-ce22f3da.entry.js → p-74cd80a9.entry.js} +4 -4
  66. package/dist/lime-elements/{p-70e2e60c.entry.js → p-767935ac.entry.js} +1 -1
  67. package/dist/lime-elements/{p-b95a42ea.entry.js → p-86eebe44.entry.js} +1 -1
  68. package/dist/lime-elements/{p-D7ojrUFG.js → p-BN1-aIOw.js} +1 -1
  69. package/dist/lime-elements/{p-ClrXWM0F.js → p-C9yTLqR8.js} +1 -1
  70. package/dist/lime-elements/{p-D-I_3IxW.js → p-ChRGk668.js} +3 -3
  71. package/dist/lime-elements/{p-BDAjvVhN.js → p-DZkKQUDM.js} +3 -3
  72. package/dist/lime-elements/{p-dcf3cc71.entry.js → p-b2ea92be.entry.js} +1 -1
  73. package/dist/lime-elements/p-b3622713.entry.js +1 -0
  74. package/dist/lime-elements/{p-8ec4fdee.entry.js → p-cd722648.entry.js} +2 -2
  75. package/dist/lime-elements/{p-ee3afb60.entry.js → p-cdd5b814.entry.js} +9 -9
  76. package/dist/lime-elements/{p-8ec92025.entry.js → p-db51323c.entry.js} +1 -1
  77. package/dist/lime-elements/{p-64f664b0.entry.js → p-e2f1b070.entry.js} +1 -1
  78. package/dist/lime-elements/p-f10ca306.entry.js +1 -0
  79. package/dist/types/components/switch/switch.d.ts +4 -6
  80. package/package.json +3 -3
  81. package/dist/lime-elements/p-5be668d8.entry.js +0 -1
  82. package/dist/lime-elements/p-644911cc.entry.js +0 -68
  83. package/dist/lime-elements/p-965288d2.entry.js +0 -1
@@ -3,390 +3,402 @@
3
3
  var index = require('./index-BjHIBY-I.js');
4
4
  var translations = require('./translations-Bu_0fli7.js');
5
5
 
6
- function Diff() {}
7
- Diff.prototype = {
8
- diff: function diff(oldString, newString) {
9
- var _options$timeout;
10
- var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
11
- var callback = options.callback;
12
- if (typeof options === 'function') {
13
- callback = options;
14
- options = {};
15
- }
16
- var self = this;
17
- function done(value) {
18
- value = self.postProcess(value, options);
19
- if (callback) {
20
- setTimeout(function () {
21
- callback(value);
22
- }, 0);
23
- return true;
24
- } else {
25
- return value;
26
- }
6
+ class Diff {
7
+ diff(oldStr, newStr,
8
+ // Type below is not accurate/complete - see above for full possibilities - but it compiles
9
+ options = {}) {
10
+ let callback;
11
+ if (typeof options === 'function') {
12
+ callback = options;
13
+ options = {};
14
+ }
15
+ else if ('callback' in options) {
16
+ callback = options.callback;
17
+ }
18
+ // Allow subclasses to massage the input prior to running
19
+ const oldString = this.castInput(oldStr, options);
20
+ const newString = this.castInput(newStr, options);
21
+ const oldTokens = this.removeEmpty(this.tokenize(oldString, options));
22
+ const newTokens = this.removeEmpty(this.tokenize(newString, options));
23
+ return this.diffWithOptionsObj(oldTokens, newTokens, options, callback);
24
+ }
25
+ diffWithOptionsObj(oldTokens, newTokens, options, callback) {
26
+ var _a;
27
+ const done = (value) => {
28
+ value = this.postProcess(value, options);
29
+ if (callback) {
30
+ setTimeout(function () { callback(value); }, 0);
31
+ return undefined;
32
+ }
33
+ else {
34
+ return value;
35
+ }
36
+ };
37
+ const newLen = newTokens.length, oldLen = oldTokens.length;
38
+ let editLength = 1;
39
+ let maxEditLength = newLen + oldLen;
40
+ if (options.maxEditLength != null) {
41
+ maxEditLength = Math.min(maxEditLength, options.maxEditLength);
42
+ }
43
+ const maxExecutionTime = (_a = options.timeout) !== null && _a !== void 0 ? _a : Infinity;
44
+ const abortAfterTimestamp = Date.now() + maxExecutionTime;
45
+ const bestPath = [{ oldPos: -1, lastComponent: undefined }];
46
+ // Seed editLength = 0, i.e. the content starts with the same values
47
+ let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options);
48
+ if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
49
+ // Identity per the equality and tokenizer
50
+ return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens));
51
+ }
52
+ // Once we hit the right edge of the edit graph on some diagonal k, we can
53
+ // definitely reach the end of the edit graph in no more than k edits, so
54
+ // there's no point in considering any moves to diagonal k+1 any more (from
55
+ // which we're guaranteed to need at least k+1 more edits).
56
+ // Similarly, once we've reached the bottom of the edit graph, there's no
57
+ // point considering moves to lower diagonals.
58
+ // We record this fact by setting minDiagonalToConsider and
59
+ // maxDiagonalToConsider to some finite value once we've hit the edge of
60
+ // the edit graph.
61
+ // This optimization is not faithful to the original algorithm presented in
62
+ // Myers's paper, which instead pointlessly extends D-paths off the end of
63
+ // the edit graph - see page 7 of Myers's paper which notes this point
64
+ // explicitly and illustrates it with a diagram. This has major performance
65
+ // implications for some common scenarios. For instance, to compute a diff
66
+ // where the new text simply appends d characters on the end of the
67
+ // original text of length n, the true Myers algorithm will take O(n+d^2)
68
+ // time while this optimization needs only O(n+d) time.
69
+ let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;
70
+ // Main worker method. checks all permutations of a given edit length for acceptance.
71
+ const execEditLength = () => {
72
+ for (let diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
73
+ let basePath;
74
+ const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];
75
+ if (removePath) {
76
+ // No one else is going to attempt to use this value, clear it
77
+ // @ts-expect-error - perf optimisation. This type-violating value will never be read.
78
+ bestPath[diagonalPath - 1] = undefined;
79
+ }
80
+ let canAdd = false;
81
+ if (addPath) {
82
+ // what newPos will be after we do an insertion:
83
+ const addPathNewPos = addPath.oldPos - diagonalPath;
84
+ canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
85
+ }
86
+ const canRemove = removePath && removePath.oldPos + 1 < oldLen;
87
+ if (!canAdd && !canRemove) {
88
+ // If this path is a terminal then prune
89
+ // @ts-expect-error - perf optimisation. This type-violating value will never be read.
90
+ bestPath[diagonalPath] = undefined;
91
+ continue;
92
+ }
93
+ // Select the diagonal that we want to branch from. We select the prior
94
+ // path whose position in the old string is the farthest from the origin
95
+ // and does not pass the bounds of the diff graph
96
+ if (!canRemove || (canAdd && removePath.oldPos < addPath.oldPos)) {
97
+ basePath = this.addToPath(addPath, true, false, 0, options);
98
+ }
99
+ else {
100
+ basePath = this.addToPath(removePath, false, true, 1, options);
101
+ }
102
+ newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options);
103
+ if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
104
+ // If we have hit the end of both strings, then we are done
105
+ return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || true;
106
+ }
107
+ else {
108
+ bestPath[diagonalPath] = basePath;
109
+ if (basePath.oldPos + 1 >= oldLen) {
110
+ maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
111
+ }
112
+ if (newPos + 1 >= newLen) {
113
+ minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
114
+ }
115
+ }
116
+ }
117
+ editLength++;
118
+ };
119
+ // Performs the length of edit iteration. Is a bit fugly as this has to support the
120
+ // sync and async mode which is never fun. Loops over execEditLength until a value
121
+ // is produced, or until the edit length exceeds options.maxEditLength (if given),
122
+ // in which case it will return undefined.
123
+ if (callback) {
124
+ (function exec() {
125
+ setTimeout(function () {
126
+ if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
127
+ return callback(undefined);
128
+ }
129
+ if (!execEditLength()) {
130
+ exec();
131
+ }
132
+ }, 0);
133
+ }());
134
+ }
135
+ else {
136
+ while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
137
+ const ret = execEditLength();
138
+ if (ret) {
139
+ return ret;
140
+ }
141
+ }
142
+ }
27
143
  }
28
-
29
- // Allow subclasses to massage the input prior to running
30
- oldString = this.castInput(oldString, options);
31
- newString = this.castInput(newString, options);
32
- oldString = this.removeEmpty(this.tokenize(oldString, options));
33
- newString = this.removeEmpty(this.tokenize(newString, options));
34
- var newLen = newString.length,
35
- oldLen = oldString.length;
36
- var editLength = 1;
37
- var maxEditLength = newLen + oldLen;
38
- if (options.maxEditLength != null) {
39
- maxEditLength = Math.min(maxEditLength, options.maxEditLength);
40
- }
41
- var maxExecutionTime = (_options$timeout = options.timeout) !== null && _options$timeout !== void 0 ? _options$timeout : Infinity;
42
- var abortAfterTimestamp = Date.now() + maxExecutionTime;
43
- var bestPath = [{
44
- oldPos: -1,
45
- lastComponent: undefined
46
- }];
47
-
48
- // Seed editLength = 0, i.e. the content starts with the same values
49
- var newPos = this.extractCommon(bestPath[0], newString, oldString, 0, options);
50
- if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
51
- // Identity per the equality and tokenizer
52
- return done(buildValues(self, bestPath[0].lastComponent, newString, oldString, self.useLongestToken));
144
+ addToPath(path, added, removed, oldPosInc, options) {
145
+ const last = path.lastComponent;
146
+ if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
147
+ return {
148
+ oldPos: path.oldPos + oldPosInc,
149
+ lastComponent: { count: last.count + 1, added: added, removed: removed, previousComponent: last.previousComponent }
150
+ };
151
+ }
152
+ else {
153
+ return {
154
+ oldPos: path.oldPos + oldPosInc,
155
+ lastComponent: { count: 1, added: added, removed: removed, previousComponent: last }
156
+ };
157
+ }
53
158
  }
54
-
55
- // Once we hit the right edge of the edit graph on some diagonal k, we can
56
- // definitely reach the end of the edit graph in no more than k edits, so
57
- // there's no point in considering any moves to diagonal k+1 any more (from
58
- // which we're guaranteed to need at least k+1 more edits).
59
- // Similarly, once we've reached the bottom of the edit graph, there's no
60
- // point considering moves to lower diagonals.
61
- // We record this fact by setting minDiagonalToConsider and
62
- // maxDiagonalToConsider to some finite value once we've hit the edge of
63
- // the edit graph.
64
- // This optimization is not faithful to the original algorithm presented in
65
- // Myers's paper, which instead pointlessly extends D-paths off the end of
66
- // the edit graph - see page 7 of Myers's paper which notes this point
67
- // explicitly and illustrates it with a diagram. This has major performance
68
- // implications for some common scenarios. For instance, to compute a diff
69
- // where the new text simply appends d characters on the end of the
70
- // original text of length n, the true Myers algorithm will take O(n+d^2)
71
- // time while this optimization needs only O(n+d) time.
72
- var minDiagonalToConsider = -Infinity,
73
- maxDiagonalToConsider = Infinity;
74
-
75
- // Main worker method. checks all permutations of a given edit length for acceptance.
76
- function execEditLength() {
77
- for (var diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
78
- var basePath = void 0;
79
- var removePath = bestPath[diagonalPath - 1],
80
- addPath = bestPath[diagonalPath + 1];
81
- if (removePath) {
82
- // No one else is going to attempt to use this value, clear it
83
- bestPath[diagonalPath - 1] = undefined;
84
- }
85
- var canAdd = false;
86
- if (addPath) {
87
- // what newPos will be after we do an insertion:
88
- var addPathNewPos = addPath.oldPos - diagonalPath;
89
- canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
90
- }
91
- var canRemove = removePath && removePath.oldPos + 1 < oldLen;
92
- if (!canAdd && !canRemove) {
93
- // If this path is a terminal then prune
94
- bestPath[diagonalPath] = undefined;
95
- continue;
159
+ extractCommon(basePath, newTokens, oldTokens, diagonalPath, options) {
160
+ const newLen = newTokens.length, oldLen = oldTokens.length;
161
+ let oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0;
162
+ while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) {
163
+ newPos++;
164
+ oldPos++;
165
+ commonCount++;
166
+ if (options.oneChangePerToken) {
167
+ basePath.lastComponent = { count: 1, previousComponent: basePath.lastComponent, added: false, removed: false };
168
+ }
96
169
  }
97
-
98
- // Select the diagonal that we want to branch from. We select the prior
99
- // path whose position in the old string is the farthest from the origin
100
- // and does not pass the bounds of the diff graph
101
- if (!canRemove || canAdd && removePath.oldPos < addPath.oldPos) {
102
- basePath = self.addToPath(addPath, true, false, 0, options);
103
- } else {
104
- basePath = self.addToPath(removePath, false, true, 1, options);
105
- }
106
- newPos = self.extractCommon(basePath, newString, oldString, diagonalPath, options);
107
- if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
108
- // If we have hit the end of both strings, then we are done
109
- return done(buildValues(self, basePath.lastComponent, newString, oldString, self.useLongestToken));
110
- } else {
111
- bestPath[diagonalPath] = basePath;
112
- if (basePath.oldPos + 1 >= oldLen) {
113
- maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
114
- }
115
- if (newPos + 1 >= newLen) {
116
- minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
117
- }
118
- }
119
- }
120
- editLength++;
170
+ if (commonCount && !options.oneChangePerToken) {
171
+ basePath.lastComponent = { count: commonCount, previousComponent: basePath.lastComponent, added: false, removed: false };
172
+ }
173
+ basePath.oldPos = oldPos;
174
+ return newPos;
175
+ }
176
+ equals(left, right, options) {
177
+ if (options.comparator) {
178
+ return options.comparator(left, right);
179
+ }
180
+ else {
181
+ return left === right
182
+ || (!!options.ignoreCase && left.toLowerCase() === right.toLowerCase());
183
+ }
184
+ }
185
+ removeEmpty(array) {
186
+ const ret = [];
187
+ for (let i = 0; i < array.length; i++) {
188
+ if (array[i]) {
189
+ ret.push(array[i]);
190
+ }
191
+ }
192
+ return ret;
193
+ }
194
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
195
+ castInput(value, options) {
196
+ return value;
197
+ }
198
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
199
+ tokenize(value, options) {
200
+ return Array.from(value);
201
+ }
202
+ join(chars) {
203
+ // Assumes ValueT is string, which is the case for most subclasses.
204
+ // When it's false, e.g. in diffArrays, this method needs to be overridden (e.g. with a no-op)
205
+ // Yes, the casts are verbose and ugly, because this pattern - of having the base class SORT OF
206
+ // assume tokens and values are strings, but not completely - is weird and janky.
207
+ return chars.join('');
208
+ }
209
+ postProcess(changeObjects,
210
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
211
+ options) {
212
+ return changeObjects;
213
+ }
214
+ get useLongestToken() {
215
+ return false;
216
+ }
217
+ buildValues(lastComponent, newTokens, oldTokens) {
218
+ // First we convert our linked list of components in reverse order to an
219
+ // array in the right order:
220
+ const components = [];
221
+ let nextComponent;
222
+ while (lastComponent) {
223
+ components.push(lastComponent);
224
+ nextComponent = lastComponent.previousComponent;
225
+ delete lastComponent.previousComponent;
226
+ lastComponent = nextComponent;
227
+ }
228
+ components.reverse();
229
+ const componentLen = components.length;
230
+ let componentPos = 0, newPos = 0, oldPos = 0;
231
+ for (; componentPos < componentLen; componentPos++) {
232
+ const component = components[componentPos];
233
+ if (!component.removed) {
234
+ if (!component.added && this.useLongestToken) {
235
+ let value = newTokens.slice(newPos, newPos + component.count);
236
+ value = value.map(function (value, i) {
237
+ const oldValue = oldTokens[oldPos + i];
238
+ return oldValue.length > value.length ? oldValue : value;
239
+ });
240
+ component.value = this.join(value);
241
+ }
242
+ else {
243
+ component.value = this.join(newTokens.slice(newPos, newPos + component.count));
244
+ }
245
+ newPos += component.count;
246
+ // Common case
247
+ if (!component.added) {
248
+ oldPos += component.count;
249
+ }
250
+ }
251
+ else {
252
+ component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count));
253
+ oldPos += component.count;
254
+ }
255
+ }
256
+ return components;
121
257
  }
122
-
123
- // Performs the length of edit iteration. Is a bit fugly as this has to support the
124
- // sync and async mode which is never fun. Loops over execEditLength until a value
125
- // is produced, or until the edit length exceeds options.maxEditLength (if given),
126
- // in which case it will return undefined.
127
- if (callback) {
128
- (function exec() {
129
- setTimeout(function () {
130
- if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
131
- return callback();
132
- }
133
- if (!execEditLength()) {
134
- exec();
135
- }
136
- }, 0);
137
- })();
138
- } else {
139
- while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
140
- var ret = execEditLength();
141
- if (ret) {
142
- return ret;
143
- }
144
- }
145
- }
146
- },
147
- addToPath: function addToPath(path, added, removed, oldPosInc, options) {
148
- var last = path.lastComponent;
149
- if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
150
- return {
151
- oldPos: path.oldPos + oldPosInc,
152
- lastComponent: {
153
- count: last.count + 1,
154
- added: added,
155
- removed: removed,
156
- previousComponent: last.previousComponent
157
- }
158
- };
159
- } else {
160
- return {
161
- oldPos: path.oldPos + oldPosInc,
162
- lastComponent: {
163
- count: 1,
164
- added: added,
165
- removed: removed,
166
- previousComponent: last
167
- }
168
- };
169
- }
170
- },
171
- extractCommon: function extractCommon(basePath, newString, oldString, diagonalPath, options) {
172
- var newLen = newString.length,
173
- oldLen = oldString.length,
174
- oldPos = basePath.oldPos,
175
- newPos = oldPos - diagonalPath,
176
- commonCount = 0;
177
- while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldString[oldPos + 1], newString[newPos + 1], options)) {
178
- newPos++;
179
- oldPos++;
180
- commonCount++;
181
- if (options.oneChangePerToken) {
182
- basePath.lastComponent = {
183
- count: 1,
184
- previousComponent: basePath.lastComponent,
185
- added: false,
186
- removed: false
187
- };
188
- }
189
- }
190
- if (commonCount && !options.oneChangePerToken) {
191
- basePath.lastComponent = {
192
- count: commonCount,
193
- previousComponent: basePath.lastComponent,
194
- added: false,
195
- removed: false
196
- };
197
- }
198
- basePath.oldPos = oldPos;
199
- return newPos;
200
- },
201
- equals: function equals(left, right, options) {
202
- if (options.comparator) {
203
- return options.comparator(left, right);
204
- } else {
205
- return left === right || options.ignoreCase && left.toLowerCase() === right.toLowerCase();
206
- }
207
- },
208
- removeEmpty: function removeEmpty(array) {
209
- var ret = [];
210
- for (var i = 0; i < array.length; i++) {
211
- if (array[i]) {
212
- ret.push(array[i]);
213
- }
214
- }
215
- return ret;
216
- },
217
- castInput: function castInput(value) {
218
- return value;
219
- },
220
- tokenize: function tokenize(value) {
221
- return Array.from(value);
222
- },
223
- join: function join(chars) {
224
- return chars.join('');
225
- },
226
- postProcess: function postProcess(changeObjects) {
227
- return changeObjects;
228
- }
229
- };
230
- function buildValues(diff, lastComponent, newString, oldString, useLongestToken) {
231
- // First we convert our linked list of components in reverse order to an
232
- // array in the right order:
233
- var components = [];
234
- var nextComponent;
235
- while (lastComponent) {
236
- components.push(lastComponent);
237
- nextComponent = lastComponent.previousComponent;
238
- delete lastComponent.previousComponent;
239
- lastComponent = nextComponent;
240
- }
241
- components.reverse();
242
- var componentPos = 0,
243
- componentLen = components.length,
244
- newPos = 0,
245
- oldPos = 0;
246
- for (; componentPos < componentLen; componentPos++) {
247
- var component = components[componentPos];
248
- if (!component.removed) {
249
- if (!component.added && useLongestToken) {
250
- var value = newString.slice(newPos, newPos + component.count);
251
- value = value.map(function (value, i) {
252
- var oldValue = oldString[oldPos + i];
253
- return oldValue.length > value.length ? oldValue : value;
254
- });
255
- component.value = diff.join(value);
256
- } else {
257
- component.value = diff.join(newString.slice(newPos, newPos + component.count));
258
- }
259
- newPos += component.count;
260
-
261
- // Common case
262
- if (!component.added) {
263
- oldPos += component.count;
264
- }
265
- } else {
266
- component.value = diff.join(oldString.slice(oldPos, oldPos + component.count));
267
- oldPos += component.count;
268
- }
269
- }
270
- return components;
271
258
  }
272
259
 
273
260
  function longestCommonPrefix(str1, str2) {
274
- var i;
275
- for (i = 0; i < str1.length && i < str2.length; i++) {
276
- if (str1[i] != str2[i]) {
277
- return str1.slice(0, i);
261
+ let i;
262
+ for (i = 0; i < str1.length && i < str2.length; i++) {
263
+ if (str1[i] != str2[i]) {
264
+ return str1.slice(0, i);
265
+ }
278
266
  }
279
- }
280
- return str1.slice(0, i);
267
+ return str1.slice(0, i);
281
268
  }
282
269
  function longestCommonSuffix(str1, str2) {
283
- var i;
284
-
285
- // Unlike longestCommonPrefix, we need a special case to handle all scenarios
286
- // where we return the empty string since str1.slice(-0) will return the
287
- // entire string.
288
- if (!str1 || !str2 || str1[str1.length - 1] != str2[str2.length - 1]) {
289
- return '';
290
- }
291
- for (i = 0; i < str1.length && i < str2.length; i++) {
292
- if (str1[str1.length - (i + 1)] != str2[str2.length - (i + 1)]) {
293
- return str1.slice(-i);
294
- }
295
- }
296
- return str1.slice(-i);
270
+ let i;
271
+ // Unlike longestCommonPrefix, we need a special case to handle all scenarios
272
+ // where we return the empty string since str1.slice(-0) will return the
273
+ // entire string.
274
+ if (!str1 || !str2 || str1[str1.length - 1] != str2[str2.length - 1]) {
275
+ return '';
276
+ }
277
+ for (i = 0; i < str1.length && i < str2.length; i++) {
278
+ if (str1[str1.length - (i + 1)] != str2[str2.length - (i + 1)]) {
279
+ return str1.slice(-i);
280
+ }
281
+ }
282
+ return str1.slice(-i);
297
283
  }
298
284
  function replacePrefix(string, oldPrefix, newPrefix) {
299
- if (string.slice(0, oldPrefix.length) != oldPrefix) {
300
- throw Error("string ".concat(JSON.stringify(string), " doesn't start with prefix ").concat(JSON.stringify(oldPrefix), "; this is a bug"));
301
- }
302
- return newPrefix + string.slice(oldPrefix.length);
285
+ if (string.slice(0, oldPrefix.length) != oldPrefix) {
286
+ throw Error(`string ${JSON.stringify(string)} doesn't start with prefix ${JSON.stringify(oldPrefix)}; this is a bug`);
287
+ }
288
+ return newPrefix + string.slice(oldPrefix.length);
303
289
  }
304
290
  function replaceSuffix(string, oldSuffix, newSuffix) {
305
- if (!oldSuffix) {
306
- return string + newSuffix;
307
- }
308
- if (string.slice(-oldSuffix.length) != oldSuffix) {
309
- throw Error("string ".concat(JSON.stringify(string), " doesn't end with suffix ").concat(JSON.stringify(oldSuffix), "; this is a bug"));
310
- }
311
- return string.slice(0, -oldSuffix.length) + newSuffix;
291
+ if (!oldSuffix) {
292
+ return string + newSuffix;
293
+ }
294
+ if (string.slice(-oldSuffix.length) != oldSuffix) {
295
+ throw Error(`string ${JSON.stringify(string)} doesn't end with suffix ${JSON.stringify(oldSuffix)}; this is a bug`);
296
+ }
297
+ return string.slice(0, -oldSuffix.length) + newSuffix;
312
298
  }
313
299
  function removePrefix(string, oldPrefix) {
314
- return replacePrefix(string, oldPrefix, '');
300
+ return replacePrefix(string, oldPrefix, '');
315
301
  }
316
302
  function removeSuffix(string, oldSuffix) {
317
- return replaceSuffix(string, oldSuffix, '');
303
+ return replaceSuffix(string, oldSuffix, '');
318
304
  }
319
305
  function maximumOverlap(string1, string2) {
320
- return string2.slice(0, overlapCount(string1, string2));
306
+ return string2.slice(0, overlapCount(string1, string2));
321
307
  }
322
-
323
308
  // Nicked from https://stackoverflow.com/a/60422853/1709587
324
309
  function overlapCount(a, b) {
325
- // Deal with cases where the strings differ in length
326
- var startA = 0;
327
- if (a.length > b.length) {
328
- startA = a.length - b.length;
329
- }
330
- var endB = b.length;
331
- if (a.length < b.length) {
332
- endB = a.length;
333
- }
334
- // Create a back-reference for each index
335
- // that should be followed in case of a mismatch.
336
- // We only need B to make these references:
337
- var map = Array(endB);
338
- var k = 0; // Index that lags behind j
339
- map[0] = 0;
340
- for (var j = 1; j < endB; j++) {
341
- if (b[j] == b[k]) {
342
- map[j] = map[k]; // skip over the same character (optional optimisation)
343
- } else {
344
- map[j] = k;
345
- }
346
- while (k > 0 && b[j] != b[k]) {
347
- k = map[k];
348
- }
349
- if (b[j] == b[k]) {
350
- k++;
351
- }
352
- }
353
- // Phase 2: use these references while iterating over A
354
- k = 0;
355
- for (var i = startA; i < a.length; i++) {
356
- while (k > 0 && a[i] != b[k]) {
357
- k = map[k];
358
- }
359
- if (a[i] == b[k]) {
360
- k++;
361
- }
362
- }
363
- return k;
310
+ // Deal with cases where the strings differ in length
311
+ let startA = 0;
312
+ if (a.length > b.length) {
313
+ startA = a.length - b.length;
314
+ }
315
+ let endB = b.length;
316
+ if (a.length < b.length) {
317
+ endB = a.length;
318
+ }
319
+ // Create a back-reference for each index
320
+ // that should be followed in case of a mismatch.
321
+ // We only need B to make these references:
322
+ const map = Array(endB);
323
+ let k = 0; // Index that lags behind j
324
+ map[0] = 0;
325
+ for (let j = 1; j < endB; j++) {
326
+ if (b[j] == b[k]) {
327
+ map[j] = map[k]; // skip over the same character (optional optimisation)
328
+ }
329
+ else {
330
+ map[j] = k;
331
+ }
332
+ while (k > 0 && b[j] != b[k]) {
333
+ k = map[k];
334
+ }
335
+ if (b[j] == b[k]) {
336
+ k++;
337
+ }
338
+ }
339
+ // Phase 2: use these references while iterating over A
340
+ k = 0;
341
+ for (let i = startA; i < a.length; i++) {
342
+ while (k > 0 && a[i] != b[k]) {
343
+ k = map[k];
344
+ }
345
+ if (a[i] == b[k]) {
346
+ k++;
347
+ }
348
+ }
349
+ return k;
350
+ }
351
+ function trailingWs(string) {
352
+ // Yes, this looks overcomplicated and dumb - why not replace the whole function with
353
+ // return string.match(/\s*$/)[0]
354
+ // you ask? Because:
355
+ // 1. the trap described at https://markamery.com/blog/quadratic-time-regexes/ would mean doing
356
+ // this would cause this function to take O(n²) time in the worst case (specifically when
357
+ // there is a massive run of NON-TRAILING whitespace in `string`), and
358
+ // 2. the fix proposed in the same blog post, of using a negative lookbehind, is incompatible
359
+ // with old Safari versions that we'd like to not break if possible (see
360
+ // https://github.com/kpdecker/jsdiff/pull/550)
361
+ // It feels absurd to do this with an explicit loop instead of a regex, but I really can't see a
362
+ // better way that doesn't result in broken behaviour.
363
+ let i;
364
+ for (i = string.length - 1; i >= 0; i--) {
365
+ if (!string[i].match(/\s/)) {
366
+ break;
367
+ }
368
+ }
369
+ return string.substring(i + 1);
370
+ }
371
+ function leadingWs(string) {
372
+ // Thankfully the annoying considerations described in trailingWs don't apply here:
373
+ const match = string.match(/^\s*/);
374
+ return match ? match[0] : '';
364
375
  }
365
376
 
366
377
  // Based on https://en.wikipedia.org/wiki/Latin_script_in_Unicode
367
378
  //
368
- // Ranges and exceptions:
369
- // Latin-1 Supplement, 0080–00FF
370
- // - U+00D7 × Multiplication sign
371
- // - U+00F7 ÷ Division sign
372
- // Latin Extended-A, 0100–017F
373
- // Latin Extended-B, 0180–024F
374
- // IPA Extensions, 025002AF
375
- // Spacing Modifier Letters, 02B002FF
376
- // - U+02C7 ˇ &#711; Caron
377
- // - U+02D8 ˘ &#728; Breve
378
- // - U+02D9 ˙ &#729; Dot Above
379
- // - U+02DA ˚ &#730; Ring Above
380
- // - U+02DB ˛ &#731; Ogonek
381
- // - U+02DC ˜ &#732; Small Tilde
382
- // - U+02DD ˝ &#733; Double Acute Accent
383
- // Latin Extended Additional, 1E00–1EFF
384
- var extendedWordChars = "a-zA-Z0-9_\\u{C0}-\\u{FF}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}";
385
-
379
+ // Chars/ranges counted as "word" characters by this regex are as follows:
380
+ //
381
+ // + U+00AD Soft hyphen
382
+ // + 00C0–00FF (letters with diacritics from the Latin-1 Supplement), except:
383
+ // - U+00D7 × Multiplication sign
384
+ // - U+00F7 ÷ Division sign
385
+ // + Latin Extended-A, 0100017F
386
+ // + Latin Extended-B, 0180024F
387
+ // + IPA Extensions, 0250–02AF
388
+ // + Spacing Modifier Letters, 02B0–02FF, except:
389
+ // - U+02C7 ˇ &#711; Caron
390
+ // - U+02D8 ˘ &#728; Breve
391
+ // - U+02D9 ˙ &#729; Dot Above
392
+ // - U+02DA ˚ &#730; Ring Above
393
+ // - U+02DB ˛ &#731; Ogonek
394
+ // - U+02DC ˜ &#732; Small Tilde
395
+ // - U+02DD ˝ &#733; Double Acute Accent
396
+ // + Latin Extended Additional, 1E00–1EFF
397
+ const extendedWordChars = 'a-zA-Z0-9_\\u{AD}\\u{C0}-\\u{D6}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}';
386
398
  // Each token is one of the following:
387
399
  // - A punctuation mark plus the surrounding whitespace
388
400
  // - A word plus the surrounding whitespace
389
- // - Pure whitespace (but only in the special case where this the entire text
401
+ // - Pure whitespace (but only in the special case where the entire text
390
402
  // is just whitespace)
391
403
  //
392
404
  // We have to include surrounding whitespace in the tokens because the two
@@ -403,376 +415,298 @@ var extendedWordChars = "a-zA-Z0-9_\\u{C0}-\\u{FF}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6
403
415
  //
404
416
  // Keeping the surrounding whitespace of course has implications for .equals
405
417
  // and .join, not just .tokenize.
406
-
407
418
  // This regex does NOT fully implement the tokenization rules described above.
408
419
  // Instead, it gives runs of whitespace their own "token". The tokenize method
409
420
  // then handles stitching whitespace tokens onto adjacent word or punctuation
410
421
  // tokens.
411
- var tokenizeIncludingWhitespace = new RegExp("[".concat(extendedWordChars, "]+|\\s+|[^").concat(extendedWordChars, "]"), 'ug');
412
- var wordDiff = new Diff();
413
- wordDiff.equals = function (left, right, options) {
414
- if (options.ignoreCase) {
415
- left = left.toLowerCase();
416
- right = right.toLowerCase();
417
- }
418
- return left.trim() === right.trim();
419
- };
420
- wordDiff.tokenize = function (value) {
421
- var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
422
- var parts;
423
- if (options.intlSegmenter) {
424
- if (options.intlSegmenter.resolvedOptions().granularity != 'word') {
425
- throw new Error('The segmenter passed must have a granularity of "word"');
426
- }
427
- parts = Array.from(options.intlSegmenter.segment(value), function (segment) {
428
- return segment.segment;
429
- });
430
- } else {
431
- parts = value.match(tokenizeIncludingWhitespace) || [];
432
- }
433
- var tokens = [];
434
- var prevPart = null;
435
- parts.forEach(function (part) {
436
- if (/\s/.test(part)) {
437
- if (prevPart == null) {
438
- tokens.push(part);
439
- } else {
440
- tokens.push(tokens.pop() + part);
441
- }
442
- } else if (/\s/.test(prevPart)) {
443
- if (tokens[tokens.length - 1] == prevPart) {
444
- tokens.push(tokens.pop() + part);
445
- } else {
446
- tokens.push(prevPart + part);
447
- }
448
- } else {
449
- tokens.push(part);
450
- }
451
- prevPart = part;
452
- });
453
- return tokens;
454
- };
455
- wordDiff.join = function (tokens) {
456
- // Tokens being joined here will always have appeared consecutively in the
457
- // same text, so we can simply strip off the leading whitespace from all the
458
- // tokens except the first (and except any whitespace-only tokens - but such
459
- // a token will always be the first and only token anyway) and then join them
460
- // and the whitespace around words and punctuation will end up correct.
461
- return tokens.map(function (token, i) {
462
- if (i == 0) {
463
- return token;
464
- } else {
465
- return token.replace(/^\s+/, '');
466
- }
467
- }).join('');
468
- };
469
- wordDiff.postProcess = function (changes, options) {
470
- if (!changes || options.oneChangePerToken) {
471
- return changes;
472
- }
473
- var lastKeep = null;
474
- // Change objects representing any insertion or deletion since the last
475
- // "keep" change object. There can be at most one of each.
476
- var insertion = null;
477
- var deletion = null;
478
- changes.forEach(function (change) {
479
- if (change.added) {
480
- insertion = change;
481
- } else if (change.removed) {
482
- deletion = change;
483
- } else {
484
- if (insertion || deletion) {
485
- // May be false at start of text
486
- dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, change);
487
- }
488
- lastKeep = change;
489
- insertion = null;
490
- deletion = null;
491
- }
492
- });
493
- if (insertion || deletion) {
494
- dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, null);
495
- }
496
- return changes;
497
- };
422
+ const tokenizeIncludingWhitespace = new RegExp(`[${extendedWordChars}]+|\\s+|[^${extendedWordChars}]`, 'ug');
423
+ class WordDiff extends Diff {
424
+ equals(left, right, options) {
425
+ if (options.ignoreCase) {
426
+ left = left.toLowerCase();
427
+ right = right.toLowerCase();
428
+ }
429
+ return left.trim() === right.trim();
430
+ }
431
+ tokenize(value, options = {}) {
432
+ let parts;
433
+ if (options.intlSegmenter) {
434
+ const segmenter = options.intlSegmenter;
435
+ if (segmenter.resolvedOptions().granularity != 'word') {
436
+ throw new Error('The segmenter passed must have a granularity of "word"');
437
+ }
438
+ // We want `parts` to be an array whose elements alternate between being
439
+ // pure whitespace and being pure non-whitespace. This is ALMOST what the
440
+ // segments returned by a word-based Intl.Segmenter already look like,
441
+ // and therefore we can ALMOST get what we want by simply doing...
442
+ // parts = Array.from(segmenter.segment(value), segment => segment.segment);
443
+ // ... but not QUITE, because there's of one annoying special case: every
444
+ // newline character gets its own segment, instead of sharing a segment
445
+ // with other surrounding whitespace. We therefore need to manually merge
446
+ // consecutive segments of whitespace into a single part:
447
+ parts = [];
448
+ for (const segmentObj of Array.from(segmenter.segment(value))) {
449
+ const segment = segmentObj.segment;
450
+ if (parts.length && (/\s/).test(parts[parts.length - 1]) && (/\s/).test(segment)) {
451
+ parts[parts.length - 1] += segment;
452
+ }
453
+ else {
454
+ parts.push(segment);
455
+ }
456
+ }
457
+ }
458
+ else {
459
+ parts = value.match(tokenizeIncludingWhitespace) || [];
460
+ }
461
+ const tokens = [];
462
+ let prevPart = null;
463
+ parts.forEach(part => {
464
+ if ((/\s/).test(part)) {
465
+ if (prevPart == null) {
466
+ tokens.push(part);
467
+ }
468
+ else {
469
+ tokens.push(tokens.pop() + part);
470
+ }
471
+ }
472
+ else if (prevPart != null && (/\s/).test(prevPart)) {
473
+ if (tokens[tokens.length - 1] == prevPart) {
474
+ tokens.push(tokens.pop() + part);
475
+ }
476
+ else {
477
+ tokens.push(prevPart + part);
478
+ }
479
+ }
480
+ else {
481
+ tokens.push(part);
482
+ }
483
+ prevPart = part;
484
+ });
485
+ return tokens;
486
+ }
487
+ join(tokens) {
488
+ // Tokens being joined here will always have appeared consecutively in the
489
+ // same text, so we can simply strip off the leading whitespace from all the
490
+ // tokens except the first (and except any whitespace-only tokens - but such
491
+ // a token will always be the first and only token anyway) and then join them
492
+ // and the whitespace around words and punctuation will end up correct.
493
+ return tokens.map((token, i) => {
494
+ if (i == 0) {
495
+ return token;
496
+ }
497
+ else {
498
+ return token.replace((/^\s+/), '');
499
+ }
500
+ }).join('');
501
+ }
502
+ postProcess(changes, options) {
503
+ if (!changes || options.oneChangePerToken) {
504
+ return changes;
505
+ }
506
+ let lastKeep = null;
507
+ // Change objects representing any insertion or deletion since the last
508
+ // "keep" change object. There can be at most one of each.
509
+ let insertion = null;
510
+ let deletion = null;
511
+ changes.forEach(change => {
512
+ if (change.added) {
513
+ insertion = change;
514
+ }
515
+ else if (change.removed) {
516
+ deletion = change;
517
+ }
518
+ else {
519
+ if (insertion || deletion) { // May be false at start of text
520
+ dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, change);
521
+ }
522
+ lastKeep = change;
523
+ insertion = null;
524
+ deletion = null;
525
+ }
526
+ });
527
+ if (insertion || deletion) {
528
+ dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, null);
529
+ }
530
+ return changes;
531
+ }
532
+ }
533
+ const wordDiff = new WordDiff();
498
534
  function diffWords(oldStr, newStr, options) {
499
- return wordDiff.diff(oldStr, newStr, options);
535
+ return wordDiff.diff(oldStr, newStr, options);
500
536
  }
501
537
  function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep) {
502
- // Before returning, we tidy up the leading and trailing whitespace of the
503
- // change objects to eliminate cases where trailing whitespace in one object
504
- // is repeated as leading whitespace in the next.
505
- // Below are examples of the outcomes we want here to explain the code.
506
- // I=insert, K=keep, D=delete
507
- // 1. diffing 'foo bar baz' vs 'foo baz'
508
- // Prior to cleanup, we have K:'foo ' D:' bar ' K:' baz'
509
- // After cleanup, we want: K:'foo ' D:'bar ' K:'baz'
510
- //
511
- // 2. Diffing 'foo bar baz' vs 'foo qux baz'
512
- // Prior to cleanup, we have K:'foo ' D:' bar ' I:' qux ' K:' baz'
513
- // After cleanup, we want K:'foo ' D:'bar' I:'qux' K:' baz'
514
- //
515
- // 3. Diffing 'foo\nbar baz' vs 'foo baz'
516
- // Prior to cleanup, we have K:'foo ' D:'\nbar ' K:' baz'
517
- // After cleanup, we want K'foo' D:'\nbar' K:' baz'
518
- //
519
- // 4. Diffing 'foo baz' vs 'foo\nbar baz'
520
- // Prior to cleanup, we have K:'foo\n' I:'\nbar ' K:' baz'
521
- // After cleanup, we ideally want K'foo' I:'\nbar' K:' baz'
522
- // but don't actually manage this currently (the pre-cleanup change
523
- // objects don't contain enough information to make it possible).
524
- //
525
- // 5. Diffing 'foo bar baz' vs 'foo baz'
526
- // Prior to cleanup, we have K:'foo ' D:' bar ' K:' baz'
527
- // After cleanup, we want K:'foo ' D:' bar ' K:'baz'
528
- //
529
- // Our handling is unavoidably imperfect in the case where there's a single
530
- // indel between keeps and the whitespace has changed. For instance, consider
531
- // diffing 'foo\tbar\nbaz' vs 'foo baz'. Unless we create an extra change
532
- // object to represent the insertion of the space character (which isn't even
533
- // a token), we have no way to avoid losing information about the texts'
534
- // original whitespace in the result we return. Still, we do our best to
535
- // output something that will look sensible if we e.g. print it with
536
- // insertions in green and deletions in red.
537
-
538
- // Between two "keep" change objects (or before the first or after the last
539
- // change object), we can have either:
540
- // * A "delete" followed by an "insert"
541
- // * Just an "insert"
542
- // * Just a "delete"
543
- // We handle the three cases separately.
544
- if (deletion && insertion) {
545
- var oldWsPrefix = deletion.value.match(/^\s*/)[0];
546
- var oldWsSuffix = deletion.value.match(/\s*$/)[0];
547
- var newWsPrefix = insertion.value.match(/^\s*/)[0];
548
- var newWsSuffix = insertion.value.match(/\s*$/)[0];
549
- if (startKeep) {
550
- var commonWsPrefix = longestCommonPrefix(oldWsPrefix, newWsPrefix);
551
- startKeep.value = replaceSuffix(startKeep.value, newWsPrefix, commonWsPrefix);
552
- deletion.value = removePrefix(deletion.value, commonWsPrefix);
553
- insertion.value = removePrefix(insertion.value, commonWsPrefix);
554
- }
555
- if (endKeep) {
556
- var commonWsSuffix = longestCommonSuffix(oldWsSuffix, newWsSuffix);
557
- endKeep.value = replacePrefix(endKeep.value, newWsSuffix, commonWsSuffix);
558
- deletion.value = removeSuffix(deletion.value, commonWsSuffix);
559
- insertion.value = removeSuffix(insertion.value, commonWsSuffix);
560
- }
561
- } else if (insertion) {
562
- // The whitespaces all reflect what was in the new text rather than
563
- // the old, so we essentially have no information about whitespace
564
- // insertion or deletion. We just want to dedupe the whitespace.
565
- // We do that by having each change object keep its trailing
566
- // whitespace and deleting duplicate leading whitespace where
567
- // present.
568
- if (startKeep) {
569
- insertion.value = insertion.value.replace(/^\s*/, '');
570
- }
571
- if (endKeep) {
572
- endKeep.value = endKeep.value.replace(/^\s*/, '');
573
- }
574
- // otherwise we've got a deletion and no insertion
575
- } else if (startKeep && endKeep) {
576
- var newWsFull = endKeep.value.match(/^\s*/)[0],
577
- delWsStart = deletion.value.match(/^\s*/)[0],
578
- delWsEnd = deletion.value.match(/\s*$/)[0];
579
-
580
- // Any whitespace that comes straight after startKeep in both the old and
581
- // new texts, assign to startKeep and remove from the deletion.
582
- var newWsStart = longestCommonPrefix(newWsFull, delWsStart);
583
- deletion.value = removePrefix(deletion.value, newWsStart);
584
-
585
- // Any whitespace that comes straight before endKeep in both the old and
586
- // new texts, and hasn't already been assigned to startKeep, assign to
587
- // endKeep and remove from the deletion.
588
- var newWsEnd = longestCommonSuffix(removePrefix(newWsFull, newWsStart), delWsEnd);
589
- deletion.value = removeSuffix(deletion.value, newWsEnd);
590
- endKeep.value = replacePrefix(endKeep.value, newWsFull, newWsEnd);
591
-
592
- // If there's any whitespace from the new text that HASN'T already been
593
- // assigned, assign it to the start:
594
- startKeep.value = replaceSuffix(startKeep.value, newWsFull, newWsFull.slice(0, newWsFull.length - newWsEnd.length));
595
- } else if (endKeep) {
596
- // We are at the start of the text. Preserve all the whitespace on
597
- // endKeep, and just remove whitespace from the end of deletion to the
598
- // extent that it overlaps with the start of endKeep.
599
- var endKeepWsPrefix = endKeep.value.match(/^\s*/)[0];
600
- var deletionWsSuffix = deletion.value.match(/\s*$/)[0];
601
- var overlap = maximumOverlap(deletionWsSuffix, endKeepWsPrefix);
602
- deletion.value = removeSuffix(deletion.value, overlap);
603
- } else if (startKeep) {
604
- // We are at the END of the text. Preserve all the whitespace on
605
- // startKeep, and just remove whitespace from the start of deletion to
606
- // the extent that it overlaps with the end of startKeep.
607
- var startKeepWsSuffix = startKeep.value.match(/\s*$/)[0];
608
- var deletionWsPrefix = deletion.value.match(/^\s*/)[0];
609
- var _overlap = maximumOverlap(startKeepWsSuffix, deletionWsPrefix);
610
- deletion.value = removePrefix(deletion.value, _overlap);
611
- }
538
+ // Before returning, we tidy up the leading and trailing whitespace of the
539
+ // change objects to eliminate cases where trailing whitespace in one object
540
+ // is repeated as leading whitespace in the next.
541
+ // Below are examples of the outcomes we want here to explain the code.
542
+ // I=insert, K=keep, D=delete
543
+ // 1. diffing 'foo bar baz' vs 'foo baz'
544
+ // Prior to cleanup, we have K:'foo ' D:' bar ' K:' baz'
545
+ // After cleanup, we want: K:'foo ' D:'bar ' K:'baz'
546
+ //
547
+ // 2. Diffing 'foo bar baz' vs 'foo qux baz'
548
+ // Prior to cleanup, we have K:'foo ' D:' bar ' I:' qux ' K:' baz'
549
+ // After cleanup, we want K:'foo ' D:'bar' I:'qux' K:' baz'
550
+ //
551
+ // 3. Diffing 'foo\nbar baz' vs 'foo baz'
552
+ // Prior to cleanup, we have K:'foo ' D:'\nbar ' K:' baz'
553
+ // After cleanup, we want K'foo' D:'\nbar' K:' baz'
554
+ //
555
+ // 4. Diffing 'foo baz' vs 'foo\nbar baz'
556
+ // Prior to cleanup, we have K:'foo\n' I:'\nbar ' K:' baz'
557
+ // After cleanup, we ideally want K'foo' I:'\nbar' K:' baz'
558
+ // but don't actually manage this currently (the pre-cleanup change
559
+ // objects don't contain enough information to make it possible).
560
+ //
561
+ // 5. Diffing 'foo bar baz' vs 'foo baz'
562
+ // Prior to cleanup, we have K:'foo ' D:' bar ' K:' baz'
563
+ // After cleanup, we want K:'foo ' D:' bar ' K:'baz'
564
+ //
565
+ // Our handling is unavoidably imperfect in the case where there's a single
566
+ // indel between keeps and the whitespace has changed. For instance, consider
567
+ // diffing 'foo\tbar\nbaz' vs 'foo baz'. Unless we create an extra change
568
+ // object to represent the insertion of the space character (which isn't even
569
+ // a token), we have no way to avoid losing information about the texts'
570
+ // original whitespace in the result we return. Still, we do our best to
571
+ // output something that will look sensible if we e.g. print it with
572
+ // insertions in green and deletions in red.
573
+ // Between two "keep" change objects (or before the first or after the last
574
+ // change object), we can have either:
575
+ // * A "delete" followed by an "insert"
576
+ // * Just an "insert"
577
+ // * Just a "delete"
578
+ // We handle the three cases separately.
579
+ if (deletion && insertion) {
580
+ const oldWsPrefix = leadingWs(deletion.value);
581
+ const oldWsSuffix = trailingWs(deletion.value);
582
+ const newWsPrefix = leadingWs(insertion.value);
583
+ const newWsSuffix = trailingWs(insertion.value);
584
+ if (startKeep) {
585
+ const commonWsPrefix = longestCommonPrefix(oldWsPrefix, newWsPrefix);
586
+ startKeep.value = replaceSuffix(startKeep.value, newWsPrefix, commonWsPrefix);
587
+ deletion.value = removePrefix(deletion.value, commonWsPrefix);
588
+ insertion.value = removePrefix(insertion.value, commonWsPrefix);
589
+ }
590
+ if (endKeep) {
591
+ const commonWsSuffix = longestCommonSuffix(oldWsSuffix, newWsSuffix);
592
+ endKeep.value = replacePrefix(endKeep.value, newWsSuffix, commonWsSuffix);
593
+ deletion.value = removeSuffix(deletion.value, commonWsSuffix);
594
+ insertion.value = removeSuffix(insertion.value, commonWsSuffix);
595
+ }
596
+ }
597
+ else if (insertion) {
598
+ // The whitespaces all reflect what was in the new text rather than
599
+ // the old, so we essentially have no information about whitespace
600
+ // insertion or deletion. We just want to dedupe the whitespace.
601
+ // We do that by having each change object keep its trailing
602
+ // whitespace and deleting duplicate leading whitespace where
603
+ // present.
604
+ if (startKeep) {
605
+ const ws = leadingWs(insertion.value);
606
+ insertion.value = insertion.value.substring(ws.length);
607
+ }
608
+ if (endKeep) {
609
+ const ws = leadingWs(endKeep.value);
610
+ endKeep.value = endKeep.value.substring(ws.length);
611
+ }
612
+ // otherwise we've got a deletion and no insertion
613
+ }
614
+ else if (startKeep && endKeep) {
615
+ const newWsFull = leadingWs(endKeep.value), delWsStart = leadingWs(deletion.value), delWsEnd = trailingWs(deletion.value);
616
+ // Any whitespace that comes straight after startKeep in both the old and
617
+ // new texts, assign to startKeep and remove from the deletion.
618
+ const newWsStart = longestCommonPrefix(newWsFull, delWsStart);
619
+ deletion.value = removePrefix(deletion.value, newWsStart);
620
+ // Any whitespace that comes straight before endKeep in both the old and
621
+ // new texts, and hasn't already been assigned to startKeep, assign to
622
+ // endKeep and remove from the deletion.
623
+ const newWsEnd = longestCommonSuffix(removePrefix(newWsFull, newWsStart), delWsEnd);
624
+ deletion.value = removeSuffix(deletion.value, newWsEnd);
625
+ endKeep.value = replacePrefix(endKeep.value, newWsFull, newWsEnd);
626
+ // If there's any whitespace from the new text that HASN'T already been
627
+ // assigned, assign it to the start:
628
+ startKeep.value = replaceSuffix(startKeep.value, newWsFull, newWsFull.slice(0, newWsFull.length - newWsEnd.length));
629
+ }
630
+ else if (endKeep) {
631
+ // We are at the start of the text. Preserve all the whitespace on
632
+ // endKeep, and just remove whitespace from the end of deletion to the
633
+ // extent that it overlaps with the start of endKeep.
634
+ const endKeepWsPrefix = leadingWs(endKeep.value);
635
+ const deletionWsSuffix = trailingWs(deletion.value);
636
+ const overlap = maximumOverlap(deletionWsSuffix, endKeepWsPrefix);
637
+ deletion.value = removeSuffix(deletion.value, overlap);
638
+ }
639
+ else if (startKeep) {
640
+ // We are at the END of the text. Preserve all the whitespace on
641
+ // startKeep, and just remove whitespace from the start of deletion to
642
+ // the extent that it overlaps with the end of startKeep.
643
+ const startKeepWsSuffix = trailingWs(startKeep.value);
644
+ const deletionWsPrefix = leadingWs(deletion.value);
645
+ const overlap = maximumOverlap(startKeepWsSuffix, deletionWsPrefix);
646
+ deletion.value = removePrefix(deletion.value, overlap);
647
+ }
612
648
  }
613
- var wordWithSpaceDiff = new Diff();
614
- wordWithSpaceDiff.tokenize = function (value) {
615
- // Slightly different to the tokenizeIncludingWhitespace regex used above in
616
- // that this one treats each individual newline as a distinct tokens, rather
617
- // than merging them into other surrounding whitespace. This was requested
618
- // in https://github.com/kpdecker/jsdiff/issues/180 &
619
- // https://github.com/kpdecker/jsdiff/issues/211
620
- var regex = new RegExp("(\\r?\\n)|[".concat(extendedWordChars, "]+|[^\\S\\n\\r]+|[^").concat(extendedWordChars, "]"), 'ug');
621
- return value.match(regex) || [];
622
- };
623
-
624
- var lineDiff = new Diff();
625
- lineDiff.tokenize = function (value, options) {
626
- if (options.stripTrailingCr) {
627
- // remove one \r before \n to match GNU diff's --strip-trailing-cr behavior
628
- value = value.replace(/\r\n/g, '\n');
629
- }
630
- var retLines = [],
631
- linesAndNewlines = value.split(/(\n|\r\n)/);
632
649
 
633
- // Ignore the final empty token that occurs if the string ends with a new line
634
- if (!linesAndNewlines[linesAndNewlines.length - 1]) {
635
- linesAndNewlines.pop();
636
- }
637
-
638
- // Merge the content and line separators into single tokens
639
- for (var i = 0; i < linesAndNewlines.length; i++) {
640
- var line = linesAndNewlines[i];
641
- if (i % 2 && !options.newlineIsToken) {
642
- retLines[retLines.length - 1] += line;
643
- } else {
644
- retLines.push(line);
645
- }
646
- }
647
- return retLines;
648
- };
649
- lineDiff.equals = function (left, right, options) {
650
- // If we're ignoring whitespace, we need to normalise lines by stripping
651
- // whitespace before checking equality. (This has an annoying interaction
652
- // with newlineIsToken that requires special handling: if newlines get their
653
- // own token, then we DON'T want to trim the *newline* tokens down to empty
654
- // strings, since this would cause us to treat whitespace-only line content
655
- // as equal to a separator between lines, which would be weird and
656
- // inconsistent with the documented behavior of the options.)
657
- if (options.ignoreWhitespace) {
658
- if (!options.newlineIsToken || !left.includes('\n')) {
659
- left = left.trim();
660
- }
661
- if (!options.newlineIsToken || !right.includes('\n')) {
662
- right = right.trim();
663
- }
664
- } else if (options.ignoreNewlineAtEof && !options.newlineIsToken) {
665
- if (left.endsWith('\n')) {
666
- left = left.slice(0, -1);
667
- }
668
- if (right.endsWith('\n')) {
669
- right = right.slice(0, -1);
670
- }
671
- }
672
- return Diff.prototype.equals.call(this, left, right, options);
673
- };
674
- function diffLines(oldStr, newStr, callback) {
675
- return lineDiff.diff(oldStr, newStr, callback);
650
+ class LineDiff extends Diff {
651
+ constructor() {
652
+ super(...arguments);
653
+ this.tokenize = tokenize$1;
654
+ }
655
+ equals(left, right, options) {
656
+ // If we're ignoring whitespace, we need to normalise lines by stripping
657
+ // whitespace before checking equality. (This has an annoying interaction
658
+ // with newlineIsToken that requires special handling: if newlines get their
659
+ // own token, then we DON'T want to trim the *newline* tokens down to empty
660
+ // strings, since this would cause us to treat whitespace-only line content
661
+ // as equal to a separator between lines, which would be weird and
662
+ // inconsistent with the documented behavior of the options.)
663
+ if (options.ignoreWhitespace) {
664
+ if (!options.newlineIsToken || !left.includes('\n')) {
665
+ left = left.trim();
666
+ }
667
+ if (!options.newlineIsToken || !right.includes('\n')) {
668
+ right = right.trim();
669
+ }
670
+ }
671
+ else if (options.ignoreNewlineAtEof && !options.newlineIsToken) {
672
+ if (left.endsWith('\n')) {
673
+ left = left.slice(0, -1);
674
+ }
675
+ if (right.endsWith('\n')) {
676
+ right = right.slice(0, -1);
677
+ }
678
+ }
679
+ return super.equals(left, right, options);
680
+ }
676
681
  }
677
-
678
- var sentenceDiff = new Diff();
679
- sentenceDiff.tokenize = function (value) {
680
- return value.split(/(\S.+?[.!?])(?=\s+|$)/);
681
- };
682
-
683
- var cssDiff = new Diff();
684
- cssDiff.tokenize = function (value) {
685
- return value.split(/([{}:;,]|\s+)/);
686
- };
687
- function _typeof(o) {
688
- "@babel/helpers - typeof";
689
-
690
- return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
691
- return typeof o;
692
- } : function (o) {
693
- return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
694
- }, _typeof(o);
682
+ const lineDiff = new LineDiff();
683
+ function diffLines(oldStr, newStr, options) {
684
+ return lineDiff.diff(oldStr, newStr, options);
695
685
  }
696
-
697
- var jsonDiff = new Diff();
698
- // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a
699
- // dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output:
700
- jsonDiff.useLongestToken = true;
701
- jsonDiff.tokenize = lineDiff.tokenize;
702
- jsonDiff.castInput = function (value, options) {
703
- var undefinedReplacement = options.undefinedReplacement,
704
- _options$stringifyRep = options.stringifyReplacer,
705
- stringifyReplacer = _options$stringifyRep === void 0 ? function (k, v) {
706
- return typeof v === 'undefined' ? undefinedReplacement : v;
707
- } : _options$stringifyRep;
708
- return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), stringifyReplacer, ' ');
709
- };
710
- jsonDiff.equals = function (left, right, options) {
711
- return Diff.prototype.equals.call(jsonDiff, left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'), options);
712
- };
713
-
714
- // This function handles the presence of circular references by bailing out when encountering an
715
- // object that is already on the "stack" of items being processed. Accepts an optional replacer
716
- function canonicalize(obj, stack, replacementStack, replacer, key) {
717
- stack = stack || [];
718
- replacementStack = replacementStack || [];
719
- if (replacer) {
720
- obj = replacer(key, obj);
721
- }
722
- var i;
723
- for (i = 0; i < stack.length; i += 1) {
724
- if (stack[i] === obj) {
725
- return replacementStack[i];
726
- }
727
- }
728
- var canonicalizedObj;
729
- if ('[object Array]' === Object.prototype.toString.call(obj)) {
730
- stack.push(obj);
731
- canonicalizedObj = new Array(obj.length);
732
- replacementStack.push(canonicalizedObj);
733
- for (i = 0; i < obj.length; i += 1) {
734
- canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, key);
735
- }
736
- stack.pop();
737
- replacementStack.pop();
738
- return canonicalizedObj;
739
- }
740
- if (obj && obj.toJSON) {
741
- obj = obj.toJSON();
742
- }
743
- if (_typeof(obj) === 'object' && obj !== null) {
744
- stack.push(obj);
745
- canonicalizedObj = {};
746
- replacementStack.push(canonicalizedObj);
747
- var sortedKeys = [],
748
- _key;
749
- for (_key in obj) {
750
- /* istanbul ignore else */
751
- if (Object.prototype.hasOwnProperty.call(obj, _key)) {
752
- sortedKeys.push(_key);
753
- }
754
- }
755
- sortedKeys.sort();
756
- for (i = 0; i < sortedKeys.length; i += 1) {
757
- _key = sortedKeys[i];
758
- canonicalizedObj[_key] = canonicalize(obj[_key], stack, replacementStack, replacer, _key);
759
- }
760
- stack.pop();
761
- replacementStack.pop();
762
- } else {
763
- canonicalizedObj = obj;
764
- }
765
- return canonicalizedObj;
686
+ // Exported standalone so it can be used from jsonDiff too.
687
+ function tokenize$1(value, options) {
688
+ if (options.stripTrailingCr) {
689
+ // remove one \r before \n to match GNU diff's --strip-trailing-cr behavior
690
+ value = value.replace(/\r\n/g, '\n');
691
+ }
692
+ const retLines = [], linesAndNewlines = value.split(/(\n|\r\n)/);
693
+ // Ignore the final empty token that occurs if the string ends with a new line
694
+ if (!linesAndNewlines[linesAndNewlines.length - 1]) {
695
+ linesAndNewlines.pop();
696
+ }
697
+ // Merge the content and line separators into single tokens
698
+ for (let i = 0; i < linesAndNewlines.length; i++) {
699
+ const line = linesAndNewlines[i];
700
+ if (i % 2 && !options.newlineIsToken) {
701
+ retLines[retLines.length - 1] += line;
702
+ }
703
+ else {
704
+ retLines.push(line);
705
+ }
706
+ }
707
+ return retLines;
766
708
  }
767
709
 
768
- var arrayDiff = new Diff();
769
- arrayDiff.tokenize = function (value) {
770
- return value.slice();
771
- };
772
- arrayDiff.join = arrayDiff.removeEmpty = function (value) {
773
- return value;
774
- };
775
-
776
710
  /**
777
711
  * Compute a structured diff between two strings.
778
712
  *