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