vscode-eslint 0.0.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 (160) hide show
  1. package/$shared/customMessages.ts +113 -0
  2. package/$shared/settings.ts +189 -0
  3. package/.CodeQL.yml +5 -0
  4. package/.azure-pipelines.yml +27 -0
  5. package/.github/commands.yml +127 -0
  6. package/.github/locker.yml +6 -0
  7. package/.github/needs_more_info.yml +6 -0
  8. package/.github/workflows/npm-publish.yml +53 -0
  9. package/.github/workflows/release-please.yml +22 -0
  10. package/.lsifrc.json +4 -0
  11. package/.vscode/launch.json +20 -0
  12. package/.vscode/settings.json +52 -0
  13. package/.vscode/spellright.dict +8 -0
  14. package/.vscode/tasks.json +39 -0
  15. package/.vscodeignore +23 -0
  16. package/CHANGELOG.md +524 -0
  17. package/License.txt +17 -0
  18. package/README.md +517 -0
  19. package/SECURITY.md +41 -0
  20. package/agents.md +36 -0
  21. package/bin/vscode-eslint.js +56 -0
  22. package/build/azure-pipelines/linux/build.yml +14 -0
  23. package/build/azure-pipelines/pre-release.yml +42 -0
  24. package/build/azure-pipelines/release.yml +45 -0
  25. package/build/azure-pipelines/win32/build.yml +14 -0
  26. package/build/bin/all.js +29 -0
  27. package/build/bin/linking.js +102 -0
  28. package/build/bin/symlink.js +35 -0
  29. package/client/.mocharc.json +6 -0
  30. package/client/agents.md +5 -0
  31. package/client/package-lock.json +176 -0
  32. package/client/package.json +29 -0
  33. package/client/src/client.ts +992 -0
  34. package/client/src/extension.ts +180 -0
  35. package/client/src/node-utils.ts +393 -0
  36. package/client/src/settings.ts +379 -0
  37. package/client/src/tasks.ts +186 -0
  38. package/client/src/tests/glob.test.ts +31 -0
  39. package/client/src/vscode-utils.ts +28 -0
  40. package/client/test/mocha.opts +3 -0
  41. package/client/tsconfig.json +20 -0
  42. package/client/webpack.config.js +25 -0
  43. package/contributing.md +19 -0
  44. package/esbuild.js +62 -0
  45. package/eslint.config.js +129 -0
  46. package/eslint_icon.png +0 -0
  47. package/history/settings_1_9_x.md +110 -0
  48. package/images/2_1_10/eslint-dialog.png +0 -0
  49. package/images/2_1_10/eslint-status.png +0 -0
  50. package/package-json-schema.json +9 -0
  51. package/package.json +686 -0
  52. package/playgrounds/7.0/.eslintignore +1 -0
  53. package/playgrounds/7.0/.eslintrc.json +71 -0
  54. package/playgrounds/7.0/.vscode/settings.json +85 -0
  55. package/playgrounds/7.0/app.js +12 -0
  56. package/playgrounds/7.0/build/.eslintignore +1 -0
  57. package/playgrounds/7.0/build/.eslintrc.json +30 -0
  58. package/playgrounds/7.0/build/build.js +11 -0
  59. package/playgrounds/7.0/jsconfig.json +5 -0
  60. package/playgrounds/7.0/package-lock.json +2133 -0
  61. package/playgrounds/7.0/package.json +10 -0
  62. package/playgrounds/7.0/readme.md +0 -0
  63. package/playgrounds/7.0/subDir/sub.js +11 -0
  64. package/playgrounds/7.0/subDir/test.jsx +10 -0
  65. package/playgrounds/7.0/test.js +11 -0
  66. package/playgrounds/7.0/test.sh +1 -0
  67. package/playgrounds/7.0/test.vue +33 -0
  68. package/playgrounds/7.0/test2.html +8 -0
  69. package/playgrounds/8.0/.eslintignore +1 -0
  70. package/playgrounds/8.0/.eslintrc.json +71 -0
  71. package/playgrounds/8.0/.vscode/settings.json +91 -0
  72. package/playgrounds/8.0/app.js +12 -0
  73. package/playgrounds/8.0/build/.eslintignore +1 -0
  74. package/playgrounds/8.0/build/.eslintrc.json +30 -0
  75. package/playgrounds/8.0/build/build.js +11 -0
  76. package/playgrounds/8.0/jsconfig.json +5 -0
  77. package/playgrounds/8.0/package-lock.json +2321 -0
  78. package/playgrounds/8.0/package.json +10 -0
  79. package/playgrounds/8.0/readme.md +17 -0
  80. package/playgrounds/8.0/subDir/sub.js +11 -0
  81. package/playgrounds/8.0/subDir/test.jsx +10 -0
  82. package/playgrounds/8.0/test.ipynb +49 -0
  83. package/playgrounds/8.0/test.js +3 -0
  84. package/playgrounds/8.0/test.sh +1 -0
  85. package/playgrounds/8.0/test.vue +33 -0
  86. package/playgrounds/8.0/test2.html +8 -0
  87. package/playgrounds/9.0/flat/.vscode/settings.json +3 -0
  88. package/playgrounds/9.0/flat/app.js +12 -0
  89. package/playgrounds/9.0/flat/dist/ignore.js +12 -0
  90. package/playgrounds/9.0/flat/eslint.config.js +61 -0
  91. package/playgrounds/9.0/flat/package-lock.json +1053 -0
  92. package/playgrounds/9.0/flat/package.json +9 -0
  93. package/playgrounds/9.0/rc/.eslintrc.json +57 -0
  94. package/playgrounds/9.0/rc/.vscode/settings.json +3 -0
  95. package/playgrounds/9.0/rc/app.js +12 -0
  96. package/playgrounds/9.0/rc/package-lock.json +1345 -0
  97. package/playgrounds/9.0/rc/package.json +9 -0
  98. package/playgrounds/flat-config/.vscode/settings.json +22 -0
  99. package/playgrounds/flat-config/app.js +12 -0
  100. package/playgrounds/flat-config/eslint.config.js +51 -0
  101. package/playgrounds/flat-config/package-lock.json +2733 -0
  102. package/playgrounds/flat-config/package.json +12 -0
  103. package/playgrounds/flat-config/sub/sub.js +2 -0
  104. package/playgrounds/flat-config/test.ts +7 -0
  105. package/playgrounds/flat-config/tsconfig.json +11 -0
  106. package/playgrounds/flat-config-fail/f1/app.js +12 -0
  107. package/playgrounds/flat-config-fail/f1/eslint.config.js +51 -0
  108. package/playgrounds/flat-config-fail/package-lock.json +1683 -0
  109. package/playgrounds/flat-config-fail/package.json +11 -0
  110. package/playgrounds/flat-config-mjs/.vscode/settings.json +21 -0
  111. package/playgrounds/flat-config-mjs/app.js +12 -0
  112. package/playgrounds/flat-config-mjs/eslint.config.mjs +53 -0
  113. package/playgrounds/flat-config-mjs/package-lock.json +2860 -0
  114. package/playgrounds/flat-config-mjs/package.json +11 -0
  115. package/playgrounds/flat-config-mjs/sub/sub.js +2 -0
  116. package/playgrounds/flat-config-mjs/test.ts +7 -0
  117. package/playgrounds/flat-config-mjs/tsconfig.json +11 -0
  118. package/playgrounds/load-eslint/.vscode/settings.json +21 -0
  119. package/playgrounds/load-eslint/app.js +12 -0
  120. package/playgrounds/load-eslint/eslint.config.js +51 -0
  121. package/playgrounds/load-eslint/package-lock.json +2860 -0
  122. package/playgrounds/load-eslint/package.json +11 -0
  123. package/playgrounds/load-eslint/sub/sub.js +2 -0
  124. package/playgrounds/load-eslint/test.ts +7 -0
  125. package/playgrounds/load-eslint/tsconfig.json +11 -0
  126. package/playgrounds/noLib/test.js +22 -0
  127. package/playgrounds/noWD/.vscode/settings.json +2 -0
  128. package/playgrounds/noWD/src/.eslintrc.json +18 -0
  129. package/playgrounds/noWD/src/package-lock.json +2812 -0
  130. package/playgrounds/noWD/src/package.json +12 -0
  131. package/playgrounds/noWD/src/test.js +3 -0
  132. package/playgrounds/notebooks/notebook.ipynb +7072 -0
  133. package/playgrounds/notebooks/notebook2.ipynb +20 -0
  134. package/playgrounds/testing.code-workspace +28 -0
  135. package/playgrounds/ts/.eslintrc.base.json +23 -0
  136. package/playgrounds/ts/.eslintrc.json +191 -0
  137. package/playgrounds/ts/.vscode/settings.json +12 -0
  138. package/playgrounds/ts/package-lock.json +2687 -0
  139. package/playgrounds/ts/package.json +11 -0
  140. package/playgrounds/ts/test copy.ts +4 -0
  141. package/playgrounds/ts/test.ipynb +49 -0
  142. package/playgrounds/ts/test.ts +4 -0
  143. package/playgrounds/ts/test.tsx +14 -0
  144. package/playgrounds/ts/tsconfig.json +100 -0
  145. package/server/agents.md +9 -0
  146. package/server/package-lock.json +93 -0
  147. package/server/package.json +32 -0
  148. package/server/src/diff.ts +1079 -0
  149. package/server/src/eslint.ts +1471 -0
  150. package/server/src/eslintServer.ts +865 -0
  151. package/server/src/is.ts +18 -0
  152. package/server/src/languageDefaults.ts +40 -0
  153. package/server/src/linkedMap.ts +448 -0
  154. package/server/src/paths.ts +128 -0
  155. package/server/src/thenable.d.ts +5 -0
  156. package/server/tsconfig.json +21 -0
  157. package/server/webpack.config.js +25 -0
  158. package/shared.webpack.config.js +59 -0
  159. package/tsconfig.base.json +9 -0
  160. package/tsconfig.json +21 -0
@@ -0,0 +1,1079 @@
1
+ /*---------------------------------------------------------------------------------------------
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License. See License.txt in the project root for license information.
4
+ *--------------------------------------------------------------------------------------------*/
5
+
6
+ export interface IDiffChange {
7
+ /**
8
+ * The position of the first element in the original sequence which
9
+ * this change affects.
10
+ */
11
+ originalStart: number;
12
+
13
+ /**
14
+ * The number of elements from the original sequence which were
15
+ * affected.
16
+ */
17
+ originalLength: number;
18
+
19
+ /**
20
+ * The position of the first element in the modified sequence which
21
+ * this change affects.
22
+ */
23
+ modifiedStart: number;
24
+
25
+ /**
26
+ * The number of elements from the modified sequence which were
27
+ * affected (added).
28
+ */
29
+ modifiedLength: number;
30
+ }
31
+
32
+ export function stringDiff(original: string, modified: string, pretty: boolean): IDiffChange[] {
33
+ return new LcsDiff(new StringDiffSequence(original), new StringDiffSequence(modified)).ComputeDiff(pretty).changes;
34
+ }
35
+
36
+ interface ISequence {
37
+ getElements(): Int32Array;
38
+ }
39
+
40
+ class StringDiffSequence implements ISequence {
41
+
42
+ constructor(private source: string) { }
43
+
44
+ getElements(): Int32Array {
45
+ const source = this.source;
46
+ const characters = new Int32Array(source.length);
47
+ for (let i = 0, len = source.length; i < len; i++) {
48
+ characters[i] = source.charCodeAt(i);
49
+ }
50
+ return characters;
51
+ }
52
+ }
53
+
54
+ interface IContinueProcessingPredicate {
55
+ (furthestOriginalIndex: number, matchLengthOfLongest: number): boolean;
56
+ }
57
+
58
+ interface IDiffResult {
59
+ quitEarly: boolean;
60
+ changes: IDiffChange[];
61
+ }
62
+
63
+ /**
64
+ * Represents information about a specific difference between two sequences.
65
+ */
66
+ class DiffChange {
67
+
68
+ /**
69
+ * The position of the first element in the original sequence which
70
+ * this change affects.
71
+ */
72
+ public originalStart: number;
73
+
74
+ /**
75
+ * The number of elements from the original sequence which were
76
+ * affected.
77
+ */
78
+ public originalLength: number;
79
+
80
+ /**
81
+ * The position of the first element in the modified sequence which
82
+ * this change affects.
83
+ */
84
+ public modifiedStart: number;
85
+
86
+ /**
87
+ * The number of elements from the modified sequence which were
88
+ * affected (added).
89
+ */
90
+ public modifiedLength: number;
91
+
92
+ /**
93
+ * Constructs a new DiffChange with the given sequence information
94
+ * and content.
95
+ */
96
+ constructor(originalStart: number, originalLength: number, modifiedStart: number, modifiedLength: number) {
97
+ //Debug.Assert(originalLength > 0 || modifiedLength > 0, "originalLength and modifiedLength cannot both be <= 0");
98
+ this.originalStart = originalStart;
99
+ this.originalLength = originalLength;
100
+ this.modifiedStart = modifiedStart;
101
+ this.modifiedLength = modifiedLength;
102
+ }
103
+
104
+ /**
105
+ * The end point (exclusive) of the change in the original sequence.
106
+ */
107
+ public getOriginalEnd() {
108
+ return this.originalStart + this.originalLength;
109
+ }
110
+
111
+ /**
112
+ * The end point (exclusive) of the change in the modified sequence.
113
+ */
114
+ public getModifiedEnd() {
115
+ return this.modifiedStart + this.modifiedLength;
116
+ }
117
+ }
118
+
119
+ export class Debug {
120
+ public static Assert(condition: boolean, message: string): void {
121
+ if (!condition) {
122
+ throw new Error(message);
123
+ }
124
+ }
125
+ }
126
+
127
+ const enum Constants {
128
+ /**
129
+ * MAX SMI (SMall Integer) as defined in v8.
130
+ * one bit is lost for boxing/unboxing flag.
131
+ * one bit is lost for sign flag.
132
+ * See https://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/#tagged-values
133
+ */
134
+ MAX_SAFE_SMALL_INTEGER = 1 << 30,
135
+
136
+ /**
137
+ * MIN SMI (SMall Integer) as defined in v8.
138
+ * one bit is lost for boxing/unboxing flag.
139
+ * one bit is lost for sign flag.
140
+ * See https://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/#tagged-values
141
+ */
142
+ MIN_SAFE_SMALL_INTEGER = -(1 << 30),
143
+
144
+ /**
145
+ * Max unsigned integer that fits on 8 bits.
146
+ */
147
+ MAX_UINT_8 = 255, // 2^8 - 1
148
+
149
+ /**
150
+ * Max unsigned integer that fits on 16 bits.
151
+ */
152
+ MAX_UINT_16 = 65535, // 2^16 - 1
153
+
154
+ /**
155
+ * Max unsigned integer that fits on 32 bits.
156
+ */
157
+ MAX_UINT_32 = 4294967295, // 2^32 - 1
158
+
159
+ UNICODE_SUPPLEMENTARY_PLANE_BEGIN = 0x010000
160
+ }
161
+
162
+ const enum LocalConstants {
163
+ MaxDifferencesHistory = 1447
164
+ }
165
+
166
+ class MyArray {
167
+ /**
168
+ * Copies a range of elements from an Array starting at the specified source index and pastes
169
+ * them to another Array starting at the specified destination index. The length and the indexes
170
+ * are specified as 64-bit integers.
171
+ * sourceArray:
172
+ * The Array that contains the data to copy.
173
+ * sourceIndex:
174
+ * A 64-bit integer that represents the index in the sourceArray at which copying begins.
175
+ * destinationArray:
176
+ * The Array that receives the data.
177
+ * destinationIndex:
178
+ * A 64-bit integer that represents the index in the destinationArray at which storing begins.
179
+ * length:
180
+ * A 64-bit integer that represents the number of elements to copy.
181
+ */
182
+ public static Copy(sourceArray: any[], sourceIndex: number, destinationArray: any[], destinationIndex: number, length: number) {
183
+ for (let i = 0; i < length; i++) {
184
+ destinationArray[destinationIndex + i] = sourceArray[sourceIndex + i];
185
+ }
186
+ }
187
+ public static Copy2(sourceArray: Int32Array, sourceIndex: number, destinationArray: Int32Array, destinationIndex: number, length: number) {
188
+ for (let i = 0; i < length; i++) {
189
+ destinationArray[destinationIndex + i] = sourceArray[sourceIndex + i];
190
+ }
191
+ }
192
+ }
193
+
194
+ class DiffChangeHelper {
195
+
196
+ private m_changes: DiffChange[];
197
+ private m_originalStart: number;
198
+ private m_modifiedStart: number;
199
+ private m_originalCount: number;
200
+ private m_modifiedCount: number;
201
+
202
+ /**
203
+ * Constructs a new DiffChangeHelper for the given DiffSequences.
204
+ */
205
+ constructor() {
206
+ this.m_changes = [];
207
+ this.m_originalStart = Constants.MAX_SAFE_SMALL_INTEGER;
208
+ this.m_modifiedStart = Constants.MAX_SAFE_SMALL_INTEGER;
209
+ this.m_originalCount = 0;
210
+ this.m_modifiedCount = 0;
211
+ }
212
+
213
+ /**
214
+ * Marks the beginning of the next change in the set of differences.
215
+ */
216
+ public MarkNextChange(): void {
217
+ // Only add to the list if there is something to add
218
+ if (this.m_originalCount > 0 || this.m_modifiedCount > 0) {
219
+ // Add the new change to our list
220
+ this.m_changes.push(new DiffChange(this.m_originalStart, this.m_originalCount,
221
+ this.m_modifiedStart, this.m_modifiedCount));
222
+ }
223
+
224
+ // Reset for the next change
225
+ this.m_originalCount = 0;
226
+ this.m_modifiedCount = 0;
227
+ this.m_originalStart = Constants.MAX_SAFE_SMALL_INTEGER;
228
+ this.m_modifiedStart = Constants.MAX_SAFE_SMALL_INTEGER;
229
+ }
230
+
231
+ /**
232
+ * Adds the original element at the given position to the elements
233
+ * affected by the current change. The modified index gives context
234
+ * to the change position with respect to the original sequence.
235
+ * @param originalIndex The index of the original element to add.
236
+ * @param modifiedIndex The index of the modified element that provides corresponding position in the modified sequence.
237
+ */
238
+ public AddOriginalElement(originalIndex: number, modifiedIndex: number) {
239
+ // The 'true' start index is the smallest of the ones we've seen
240
+ this.m_originalStart = Math.min(this.m_originalStart, originalIndex);
241
+ this.m_modifiedStart = Math.min(this.m_modifiedStart, modifiedIndex);
242
+
243
+ this.m_originalCount++;
244
+ }
245
+
246
+ /**
247
+ * Adds the modified element at the given position to the elements
248
+ * affected by the current change. The original index gives context
249
+ * to the change position with respect to the modified sequence.
250
+ * @param originalIndex The index of the original element that provides corresponding position in the original sequence.
251
+ * @param modifiedIndex The index of the modified element to add.
252
+ */
253
+ public AddModifiedElement(originalIndex: number, modifiedIndex: number): void {
254
+ // The 'true' start index is the smallest of the ones we've seen
255
+ this.m_originalStart = Math.min(this.m_originalStart, originalIndex);
256
+ this.m_modifiedStart = Math.min(this.m_modifiedStart, modifiedIndex);
257
+
258
+ this.m_modifiedCount++;
259
+ }
260
+
261
+ /**
262
+ * Retrieves all of the changes marked by the class.
263
+ */
264
+ public getChanges(): DiffChange[] {
265
+ if (this.m_originalCount > 0 || this.m_modifiedCount > 0) {
266
+ // Finish up on whatever is left
267
+ this.MarkNextChange();
268
+ }
269
+
270
+ return this.m_changes;
271
+ }
272
+
273
+ /**
274
+ * Retrieves all of the changes marked by the class in the reverse order
275
+ */
276
+ public getReverseChanges(): DiffChange[] {
277
+ if (this.m_originalCount > 0 || this.m_modifiedCount > 0) {
278
+ // Finish up on whatever is left
279
+ this.MarkNextChange();
280
+ }
281
+
282
+ this.m_changes.reverse();
283
+ return this.m_changes;
284
+ }
285
+
286
+ }
287
+
288
+ /**
289
+ * An implementation of the difference algorithm described in
290
+ * "An O(ND) Difference Algorithm and its variations" by Eugene W. Myers
291
+ */
292
+ export class LcsDiff {
293
+
294
+ private readonly ContinueProcessingPredicate: IContinueProcessingPredicate | null;
295
+
296
+ private readonly _hasStrings: boolean;
297
+ private readonly _originalStringElements: string[];
298
+ private readonly _originalElementsOrHash: Int32Array;
299
+ private readonly _modifiedStringElements: string[];
300
+ private readonly _modifiedElementsOrHash: Int32Array;
301
+
302
+ private m_forwardHistory: Int32Array[];
303
+ private m_reverseHistory: Int32Array[];
304
+
305
+ /**
306
+ * Constructs the DiffFinder
307
+ */
308
+ constructor(originalSequence: ISequence, modifiedSequence: ISequence, continueProcessingPredicate: IContinueProcessingPredicate | null = null) {
309
+ this.ContinueProcessingPredicate = continueProcessingPredicate;
310
+
311
+ const [originalStringElements, originalElementsOrHash, originalHasStrings] = LcsDiff._getElements(originalSequence);
312
+ const [modifiedStringElements, modifiedElementsOrHash, modifiedHasStrings] = LcsDiff._getElements(modifiedSequence);
313
+
314
+ this._hasStrings = (originalHasStrings && modifiedHasStrings);
315
+ this._originalStringElements = originalStringElements;
316
+ this._originalElementsOrHash = originalElementsOrHash;
317
+ this._modifiedStringElements = modifiedStringElements;
318
+ this._modifiedElementsOrHash = modifiedElementsOrHash;
319
+
320
+ this.m_forwardHistory = [];
321
+ this.m_reverseHistory = [];
322
+ }
323
+
324
+ private static _getElements(sequence: ISequence): [string[], Int32Array, boolean] {
325
+ const elements = sequence.getElements();
326
+
327
+ if (elements instanceof Int32Array) {
328
+ return [[], elements, false];
329
+ }
330
+
331
+ return [[], new Int32Array(elements), false];
332
+ }
333
+
334
+ private ElementsAreEqual(originalIndex: number, newIndex: number): boolean {
335
+ if (this._originalElementsOrHash[originalIndex] !== this._modifiedElementsOrHash[newIndex]) {
336
+ return false;
337
+ }
338
+ return (this._hasStrings ? this._originalStringElements[originalIndex] === this._modifiedStringElements[newIndex] : true);
339
+ }
340
+
341
+ private OriginalElementsAreEqual(index1: number, index2: number): boolean {
342
+ if (this._originalElementsOrHash[index1] !== this._originalElementsOrHash[index2]) {
343
+ return false;
344
+ }
345
+ return (this._hasStrings ? this._originalStringElements[index1] === this._originalStringElements[index2] : true);
346
+ }
347
+
348
+ private ModifiedElementsAreEqual(index1: number, index2: number): boolean {
349
+ if (this._modifiedElementsOrHash[index1] !== this._modifiedElementsOrHash[index2]) {
350
+ return false;
351
+ }
352
+ return (this._hasStrings ? this._modifiedStringElements[index1] === this._modifiedStringElements[index2] : true);
353
+ }
354
+
355
+ public ComputeDiff(pretty: boolean): IDiffResult {
356
+ return this._ComputeDiff(0, this._originalElementsOrHash.length - 1, 0, this._modifiedElementsOrHash.length - 1, pretty);
357
+ }
358
+
359
+ /**
360
+ * Computes the differences between the original and modified input
361
+ * sequences on the bounded range.
362
+ * @returns An array of the differences between the two input sequences.
363
+ */
364
+ private _ComputeDiff(originalStart: number, originalEnd: number, modifiedStart: number, modifiedEnd: number, pretty: boolean): IDiffResult {
365
+ const quitEarlyArr = [false];
366
+ let changes = this.ComputeDiffRecursive(originalStart, originalEnd, modifiedStart, modifiedEnd, quitEarlyArr);
367
+
368
+ if (pretty) {
369
+ // We have to clean up the computed diff to be more intuitive
370
+ // but it turns out this cannot be done correctly until the entire set
371
+ // of diffs have been computed
372
+ changes = this.PrettifyChanges(changes);
373
+ }
374
+
375
+ return {
376
+ quitEarly: quitEarlyArr[0],
377
+ changes: changes
378
+ };
379
+ }
380
+
381
+ /**
382
+ * Private helper method which computes the differences on the bounded range
383
+ * recursively.
384
+ * @returns An array of the differences between the two input sequences.
385
+ */
386
+ private ComputeDiffRecursive(originalStart: number, originalEnd: number, modifiedStart: number, modifiedEnd: number, quitEarlyArr: boolean[]): DiffChange[] {
387
+ quitEarlyArr[0] = false;
388
+
389
+ // Find the start of the differences
390
+ while (originalStart <= originalEnd && modifiedStart <= modifiedEnd && this.ElementsAreEqual(originalStart, modifiedStart)) {
391
+ originalStart++;
392
+ modifiedStart++;
393
+ }
394
+
395
+ // Find the end of the differences
396
+ while (originalEnd >= originalStart && modifiedEnd >= modifiedStart && this.ElementsAreEqual(originalEnd, modifiedEnd)) {
397
+ originalEnd--;
398
+ modifiedEnd--;
399
+ }
400
+
401
+ // In the special case where we either have all insertions or all deletions or the sequences are identical
402
+ if (originalStart > originalEnd || modifiedStart > modifiedEnd) {
403
+ let changes: DiffChange[];
404
+
405
+ if (modifiedStart <= modifiedEnd) {
406
+ Debug.Assert(originalStart === originalEnd + 1, 'originalStart should only be one more than originalEnd');
407
+
408
+ // All insertions
409
+ changes = [
410
+ new DiffChange(originalStart, 0, modifiedStart, modifiedEnd - modifiedStart + 1)
411
+ ];
412
+ } else if (originalStart <= originalEnd) {
413
+ Debug.Assert(modifiedStart === modifiedEnd + 1, 'modifiedStart should only be one more than modifiedEnd');
414
+
415
+ // All deletions
416
+ changes = [
417
+ new DiffChange(originalStart, originalEnd - originalStart + 1, modifiedStart, 0)
418
+ ];
419
+ } else {
420
+ Debug.Assert(originalStart === originalEnd + 1, 'originalStart should only be one more than originalEnd');
421
+ Debug.Assert(modifiedStart === modifiedEnd + 1, 'modifiedStart should only be one more than modifiedEnd');
422
+
423
+ // Identical sequences - No differences
424
+ changes = [];
425
+ }
426
+
427
+ return changes;
428
+ }
429
+
430
+ // This problem can be solved using the Divide-And-Conquer technique.
431
+ const midOriginalArr = [0];
432
+ const midModifiedArr = [0];
433
+ const result = this.ComputeRecursionPoint(originalStart, originalEnd, modifiedStart, modifiedEnd, midOriginalArr, midModifiedArr, quitEarlyArr);
434
+
435
+ const midOriginal = midOriginalArr[0];
436
+ const midModified = midModifiedArr[0];
437
+
438
+ if (result !== null) {
439
+ // Result is not-null when there was enough memory to compute the changes while
440
+ // searching for the recursion point
441
+ return result;
442
+ } else if (!quitEarlyArr[0]) {
443
+ // We can break the problem down recursively by finding the changes in the
444
+ // First Half: (originalStart, modifiedStart) to (midOriginal, midModified)
445
+ // Second Half: (midOriginal + 1, minModified + 1) to (originalEnd, modifiedEnd)
446
+ // NOTE: ComputeDiff() is inclusive, therefore the second range starts on the next point
447
+
448
+ const leftChanges = this.ComputeDiffRecursive(originalStart, midOriginal, modifiedStart, midModified, quitEarlyArr);
449
+ let rightChanges: DiffChange[] = [];
450
+
451
+ if (!quitEarlyArr[0]) {
452
+ rightChanges = this.ComputeDiffRecursive(midOriginal + 1, originalEnd, midModified + 1, modifiedEnd, quitEarlyArr);
453
+ } else {
454
+ // We did't have time to finish the first half, so we don't have time to compute this half.
455
+ // Consider the entire rest of the sequence different.
456
+ rightChanges = [
457
+ new DiffChange(midOriginal + 1, originalEnd - (midOriginal + 1) + 1, midModified + 1, modifiedEnd - (midModified + 1) + 1)
458
+ ];
459
+ }
460
+
461
+ return this.ConcatenateChanges(leftChanges, rightChanges);
462
+ }
463
+
464
+ // If we hit here, we quit early, and so can't return anything meaningful
465
+ return [
466
+ new DiffChange(originalStart, originalEnd - originalStart + 1, modifiedStart, modifiedEnd - modifiedStart + 1)
467
+ ];
468
+ }
469
+
470
+ private WALKTRACE(diagonalForwardBase: number, diagonalForwardStart: number, diagonalForwardEnd: number, diagonalForwardOffset: number,
471
+ diagonalReverseBase: number, diagonalReverseStart: number, diagonalReverseEnd: number, diagonalReverseOffset: number,
472
+ forwardPoints: Int32Array, reversePoints: Int32Array,
473
+ originalIndex: number, originalEnd: number, midOriginalArr: number[],
474
+ modifiedIndex: number, modifiedEnd: number, midModifiedArr: number[],
475
+ deltaIsEven: boolean, quitEarlyArr: boolean[]
476
+ ): DiffChange[] {
477
+ let forwardChanges: DiffChange[] | null = null;
478
+ let reverseChanges: DiffChange[] | null = null;
479
+
480
+ // First, walk backward through the forward diagonals history
481
+ let changeHelper = new DiffChangeHelper();
482
+ let diagonalMin = diagonalForwardStart;
483
+ let diagonalMax = diagonalForwardEnd;
484
+ let diagonalRelative = (midOriginalArr[0] - midModifiedArr[0]) - diagonalForwardOffset;
485
+ let lastOriginalIndex = Constants.MIN_SAFE_SMALL_INTEGER;
486
+ let historyIndex = this.m_forwardHistory.length - 1;
487
+
488
+ do {
489
+ // Get the diagonal index from the relative diagonal number
490
+ const diagonal = diagonalRelative + diagonalForwardBase;
491
+
492
+ // Figure out where we came from
493
+ if (diagonal === diagonalMin || (diagonal < diagonalMax && forwardPoints[diagonal - 1] < forwardPoints[diagonal + 1])) {
494
+ // Vertical line (the element is an insert)
495
+ originalIndex = forwardPoints[diagonal + 1];
496
+ modifiedIndex = originalIndex - diagonalRelative - diagonalForwardOffset;
497
+ if (originalIndex < lastOriginalIndex) {
498
+ changeHelper.MarkNextChange();
499
+ }
500
+ lastOriginalIndex = originalIndex;
501
+ changeHelper.AddModifiedElement(originalIndex + 1, modifiedIndex);
502
+ diagonalRelative = (diagonal + 1) - diagonalForwardBase; //Setup for the next iteration
503
+ } else {
504
+ // Horizontal line (the element is a deletion)
505
+ originalIndex = forwardPoints[diagonal - 1] + 1;
506
+ modifiedIndex = originalIndex - diagonalRelative - diagonalForwardOffset;
507
+ if (originalIndex < lastOriginalIndex) {
508
+ changeHelper.MarkNextChange();
509
+ }
510
+ lastOriginalIndex = originalIndex - 1;
511
+ changeHelper.AddOriginalElement(originalIndex, modifiedIndex + 1);
512
+ diagonalRelative = (diagonal - 1) - diagonalForwardBase; //Setup for the next iteration
513
+ }
514
+
515
+ if (historyIndex >= 0) {
516
+ forwardPoints = this.m_forwardHistory[historyIndex];
517
+ diagonalForwardBase = forwardPoints[0]; //We stored this in the first spot
518
+ diagonalMin = 1;
519
+ diagonalMax = forwardPoints.length - 1;
520
+ }
521
+ } while (--historyIndex >= -1);
522
+
523
+ // Ironically, we get the forward changes as the reverse of the
524
+ // order we added them since we technically added them backwards
525
+ forwardChanges = changeHelper.getReverseChanges();
526
+
527
+ if (quitEarlyArr[0]) {
528
+ // TODO: Calculate a partial from the reverse diagonals.
529
+ // For now, just assume everything after the midOriginal/midModified point is a diff
530
+
531
+ let originalStartPoint = midOriginalArr[0] + 1;
532
+ let modifiedStartPoint = midModifiedArr[0] + 1;
533
+
534
+ if (forwardChanges !== null && forwardChanges.length > 0) {
535
+ const lastForwardChange = forwardChanges[forwardChanges.length - 1];
536
+ originalStartPoint = Math.max(originalStartPoint, lastForwardChange.getOriginalEnd());
537
+ modifiedStartPoint = Math.max(modifiedStartPoint, lastForwardChange.getModifiedEnd());
538
+ }
539
+
540
+ reverseChanges = [
541
+ new DiffChange(originalStartPoint, originalEnd - originalStartPoint + 1,
542
+ modifiedStartPoint, modifiedEnd - modifiedStartPoint + 1)
543
+ ];
544
+ } else {
545
+ // Now walk backward through the reverse diagonals history
546
+ changeHelper = new DiffChangeHelper();
547
+ diagonalMin = diagonalReverseStart;
548
+ diagonalMax = diagonalReverseEnd;
549
+ diagonalRelative = (midOriginalArr[0] - midModifiedArr[0]) - diagonalReverseOffset;
550
+ lastOriginalIndex = Constants.MAX_SAFE_SMALL_INTEGER;
551
+ historyIndex = (deltaIsEven) ? this.m_reverseHistory.length - 1 : this.m_reverseHistory.length - 2;
552
+
553
+ do {
554
+ // Get the diagonal index from the relative diagonal number
555
+ const diagonal = diagonalRelative + diagonalReverseBase;
556
+
557
+ // Figure out where we came from
558
+ if (diagonal === diagonalMin || (diagonal < diagonalMax && reversePoints[diagonal - 1] >= reversePoints[diagonal + 1])) {
559
+ // Horizontal line (the element is a deletion))
560
+ originalIndex = reversePoints[diagonal + 1] - 1;
561
+ modifiedIndex = originalIndex - diagonalRelative - diagonalReverseOffset;
562
+ if (originalIndex > lastOriginalIndex) {
563
+ changeHelper.MarkNextChange();
564
+ }
565
+ lastOriginalIndex = originalIndex + 1;
566
+ changeHelper.AddOriginalElement(originalIndex + 1, modifiedIndex + 1);
567
+ diagonalRelative = (diagonal + 1) - diagonalReverseBase; //Setup for the next iteration
568
+ } else {
569
+ // Vertical line (the element is an insertion)
570
+ originalIndex = reversePoints[diagonal - 1];
571
+ modifiedIndex = originalIndex - diagonalRelative - diagonalReverseOffset;
572
+ if (originalIndex > lastOriginalIndex) {
573
+ changeHelper.MarkNextChange();
574
+ }
575
+ lastOriginalIndex = originalIndex;
576
+ changeHelper.AddModifiedElement(originalIndex + 1, modifiedIndex + 1);
577
+ diagonalRelative = (diagonal - 1) - diagonalReverseBase; //Setup for the next iteration
578
+ }
579
+
580
+ if (historyIndex >= 0) {
581
+ reversePoints = this.m_reverseHistory[historyIndex];
582
+ diagonalReverseBase = reversePoints[0]; //We stored this in the first spot
583
+ diagonalMin = 1;
584
+ diagonalMax = reversePoints.length - 1;
585
+ }
586
+ } while (--historyIndex >= -1);
587
+
588
+ // There are cases where the reverse history will find diffs that
589
+ // are correct, but not intuitive, so we need shift them.
590
+ reverseChanges = changeHelper.getChanges();
591
+ }
592
+
593
+ return this.ConcatenateChanges(forwardChanges, reverseChanges);
594
+ }
595
+
596
+ /**
597
+ * Given the range to compute the diff on, this method finds the point:
598
+ * (midOriginal, midModified)
599
+ * that exists in the middle of the LCS of the two sequences and
600
+ * is the point at which the LCS problem may be broken down recursively.
601
+ * This method will try to keep the LCS trace in memory. If the LCS recursion
602
+ * point is calculated and the full trace is available in memory, then this method
603
+ * will return the change list.
604
+ * @param originalStart The start bound of the original sequence range
605
+ * @param originalEnd The end bound of the original sequence range
606
+ * @param modifiedStart The start bound of the modified sequence range
607
+ * @param modifiedEnd The end bound of the modified sequence range
608
+ * @param midOriginal The middle point of the original sequence range
609
+ * @param midModified The middle point of the modified sequence range
610
+ * @returns The diff changes, if available, otherwise null
611
+ */
612
+ private ComputeRecursionPoint(originalStart: number, originalEnd: number, modifiedStart: number, modifiedEnd: number, midOriginalArr: number[], midModifiedArr: number[], quitEarlyArr: boolean[]) {
613
+ let originalIndex = 0, modifiedIndex = 0;
614
+ let diagonalForwardStart = 0, diagonalForwardEnd = 0;
615
+ let diagonalReverseStart = 0, diagonalReverseEnd = 0;
616
+
617
+ // To traverse the edit graph and produce the proper LCS, our actual
618
+ // start position is just outside the given boundary
619
+ originalStart--;
620
+ modifiedStart--;
621
+
622
+ // We set these up to make the compiler happy, but they will
623
+ // be replaced before we return with the actual recursion point
624
+ midOriginalArr[0] = 0;
625
+ midModifiedArr[0] = 0;
626
+
627
+ // Clear out the history
628
+ this.m_forwardHistory = [];
629
+ this.m_reverseHistory = [];
630
+
631
+ // Each cell in the two arrays corresponds to a diagonal in the edit graph.
632
+ // The integer value in the cell represents the originalIndex of the furthest
633
+ // reaching point found so far that ends in that diagonal.
634
+ // The modifiedIndex can be computed mathematically from the originalIndex and the diagonal number.
635
+ const maxDifferences = (originalEnd - originalStart) + (modifiedEnd - modifiedStart);
636
+ const numDiagonals = maxDifferences + 1;
637
+ const forwardPoints = new Int32Array(numDiagonals);
638
+ const reversePoints = new Int32Array(numDiagonals);
639
+ // diagonalForwardBase: Index into forwardPoints of the diagonal which passes through (originalStart, modifiedStart)
640
+ // diagonalReverseBase: Index into reversePoints of the diagonal which passes through (originalEnd, modifiedEnd)
641
+ const diagonalForwardBase = (modifiedEnd - modifiedStart);
642
+ const diagonalReverseBase = (originalEnd - originalStart);
643
+ // diagonalForwardOffset: Geometric offset which allows modifiedIndex to be computed from originalIndex and the
644
+ // diagonal number (relative to diagonalForwardBase)
645
+ // diagonalReverseOffset: Geometric offset which allows modifiedIndex to be computed from originalIndex and the
646
+ // diagonal number (relative to diagonalReverseBase)
647
+ const diagonalForwardOffset = (originalStart - modifiedStart);
648
+ const diagonalReverseOffset = (originalEnd - modifiedEnd);
649
+
650
+ // delta: The difference between the end diagonal and the start diagonal. This is used to relate diagonal numbers
651
+ // relative to the start diagonal with diagonal numbers relative to the end diagonal.
652
+ // The Even/Oddn-ness of this delta is important for determining when we should check for overlap
653
+ const delta = diagonalReverseBase - diagonalForwardBase;
654
+ const deltaIsEven = (delta % 2 === 0);
655
+
656
+ // Here we set up the start and end points as the furthest points found so far
657
+ // in both the forward and reverse directions, respectively
658
+ forwardPoints[diagonalForwardBase] = originalStart;
659
+ reversePoints[diagonalReverseBase] = originalEnd;
660
+
661
+ // Remember if we quit early, and thus need to do a best-effort result instead of a real result.
662
+ quitEarlyArr[0] = false;
663
+
664
+
665
+
666
+ // A couple of points:
667
+ // --With this method, we iterate on the number of differences between the two sequences.
668
+ // The more differences there actually are, the longer this will take.
669
+ // --Also, as the number of differences increases, we have to search on diagonals further
670
+ // away from the reference diagonal (which is diagonalForwardBase for forward, diagonalReverseBase for reverse).
671
+ // --We extend on even diagonals (relative to the reference diagonal) only when numDifferences
672
+ // is even and odd diagonals only when numDifferences is odd.
673
+ for (let numDifferences = 1; numDifferences <= (maxDifferences / 2) + 1; numDifferences++) {
674
+ let furthestOriginalIndex = 0;
675
+ let furthestModifiedIndex = 0;
676
+
677
+ // Run the algorithm in the forward direction
678
+ diagonalForwardStart = this.ClipDiagonalBound(diagonalForwardBase - numDifferences, numDifferences, diagonalForwardBase, numDiagonals);
679
+ diagonalForwardEnd = this.ClipDiagonalBound(diagonalForwardBase + numDifferences, numDifferences, diagonalForwardBase, numDiagonals);
680
+ for (let diagonal = diagonalForwardStart; diagonal <= diagonalForwardEnd; diagonal += 2) {
681
+ // STEP 1: We extend the furthest reaching point in the present diagonal
682
+ // by looking at the diagonals above and below and picking the one whose point
683
+ // is further away from the start point (originalStart, modifiedStart)
684
+ if (diagonal === diagonalForwardStart || (diagonal < diagonalForwardEnd && forwardPoints[diagonal - 1] < forwardPoints[diagonal + 1])) {
685
+ originalIndex = forwardPoints[diagonal + 1];
686
+ } else {
687
+ originalIndex = forwardPoints[diagonal - 1] + 1;
688
+ }
689
+ modifiedIndex = originalIndex - (diagonal - diagonalForwardBase) - diagonalForwardOffset;
690
+
691
+ // Save the current originalIndex so we can test for false overlap in step 3
692
+ const tempOriginalIndex = originalIndex;
693
+
694
+ // STEP 2: We can continue to extend the furthest reaching point in the present diagonal
695
+ // so long as the elements are equal.
696
+ while (originalIndex < originalEnd && modifiedIndex < modifiedEnd && this.ElementsAreEqual(originalIndex + 1, modifiedIndex + 1)) {
697
+ originalIndex++;
698
+ modifiedIndex++;
699
+ }
700
+ forwardPoints[diagonal] = originalIndex;
701
+
702
+ if (originalIndex + modifiedIndex > furthestOriginalIndex + furthestModifiedIndex) {
703
+ furthestOriginalIndex = originalIndex;
704
+ furthestModifiedIndex = modifiedIndex;
705
+ }
706
+
707
+ // STEP 3: If delta is odd (overlap first happens on forward when delta is odd)
708
+ // and diagonal is in the range of reverse diagonals computed for numDifferences-1
709
+ // (the previous iteration; we haven't computed reverse diagonals for numDifferences yet)
710
+ // then check for overlap.
711
+ if (!deltaIsEven && Math.abs(diagonal - diagonalReverseBase) <= (numDifferences - 1)) {
712
+ if (originalIndex >= reversePoints[diagonal]) {
713
+ midOriginalArr[0] = originalIndex;
714
+ midModifiedArr[0] = modifiedIndex;
715
+
716
+ if (tempOriginalIndex <= reversePoints[diagonal] && LocalConstants.MaxDifferencesHistory > 0 && numDifferences <= (LocalConstants.MaxDifferencesHistory + 1)) {
717
+ // BINGO! We overlapped, and we have the full trace in memory!
718
+ return this.WALKTRACE(diagonalForwardBase, diagonalForwardStart, diagonalForwardEnd, diagonalForwardOffset,
719
+ diagonalReverseBase, diagonalReverseStart, diagonalReverseEnd, diagonalReverseOffset,
720
+ forwardPoints, reversePoints,
721
+ originalIndex, originalEnd, midOriginalArr,
722
+ modifiedIndex, modifiedEnd, midModifiedArr,
723
+ deltaIsEven, quitEarlyArr
724
+ );
725
+ } else {
726
+ // Either false overlap, or we didn't have enough memory for the full trace
727
+ // Just return the recursion point
728
+ return null;
729
+ }
730
+ }
731
+ }
732
+ }
733
+
734
+ // Check to see if we should be quitting early, before moving on to the next iteration.
735
+ const matchLengthOfLongest = ((furthestOriginalIndex - originalStart) + (furthestModifiedIndex - modifiedStart) - numDifferences) / 2;
736
+
737
+ if (this.ContinueProcessingPredicate !== null && !this.ContinueProcessingPredicate(furthestOriginalIndex, matchLengthOfLongest)) {
738
+ // We can't finish, so skip ahead to generating a result from what we have.
739
+ quitEarlyArr[0] = true;
740
+
741
+ // Use the furthest distance we got in the forward direction.
742
+ midOriginalArr[0] = furthestOriginalIndex;
743
+ midModifiedArr[0] = furthestModifiedIndex;
744
+
745
+ if (matchLengthOfLongest > 0 && LocalConstants.MaxDifferencesHistory > 0 && numDifferences <= (LocalConstants.MaxDifferencesHistory + 1)) {
746
+ // Enough of the history is in memory to walk it backwards
747
+ return this.WALKTRACE(diagonalForwardBase, diagonalForwardStart, diagonalForwardEnd, diagonalForwardOffset,
748
+ diagonalReverseBase, diagonalReverseStart, diagonalReverseEnd, diagonalReverseOffset,
749
+ forwardPoints, reversePoints,
750
+ originalIndex, originalEnd, midOriginalArr,
751
+ modifiedIndex, modifiedEnd, midModifiedArr,
752
+ deltaIsEven, quitEarlyArr
753
+ );
754
+ } else {
755
+ // We didn't actually remember enough of the history.
756
+
757
+ //Since we are quitting the diff early, we need to shift back the originalStart and modified start
758
+ //back into the boundary limits since we decremented their value above beyond the boundary limit.
759
+ originalStart++;
760
+ modifiedStart++;
761
+
762
+ return [
763
+ new DiffChange(originalStart, originalEnd - originalStart + 1,
764
+ modifiedStart, modifiedEnd - modifiedStart + 1)
765
+ ];
766
+ }
767
+ }
768
+
769
+ // Run the algorithm in the reverse direction
770
+ diagonalReverseStart = this.ClipDiagonalBound(diagonalReverseBase - numDifferences, numDifferences, diagonalReverseBase, numDiagonals);
771
+ diagonalReverseEnd = this.ClipDiagonalBound(diagonalReverseBase + numDifferences, numDifferences, diagonalReverseBase, numDiagonals);
772
+ for (let diagonal = diagonalReverseStart; diagonal <= diagonalReverseEnd; diagonal += 2) {
773
+ // STEP 1: We extend the furthest reaching point in the present diagonal
774
+ // by looking at the diagonals above and below and picking the one whose point
775
+ // is further away from the start point (originalEnd, modifiedEnd)
776
+ if (diagonal === diagonalReverseStart || (diagonal < diagonalReverseEnd && reversePoints[diagonal - 1] >= reversePoints[diagonal + 1])) {
777
+ originalIndex = reversePoints[diagonal + 1] - 1;
778
+ } else {
779
+ originalIndex = reversePoints[diagonal - 1];
780
+ }
781
+ modifiedIndex = originalIndex - (diagonal - diagonalReverseBase) - diagonalReverseOffset;
782
+
783
+ // Save the current originalIndex so we can test for false overlap
784
+ const tempOriginalIndex = originalIndex;
785
+
786
+ // STEP 2: We can continue to extend the furthest reaching point in the present diagonal
787
+ // as long as the elements are equal.
788
+ while (originalIndex > originalStart && modifiedIndex > modifiedStart && this.ElementsAreEqual(originalIndex, modifiedIndex)) {
789
+ originalIndex--;
790
+ modifiedIndex--;
791
+ }
792
+ reversePoints[diagonal] = originalIndex;
793
+
794
+ // STEP 4: If delta is even (overlap first happens on reverse when delta is even)
795
+ // and diagonal is in the range of forward diagonals computed for numDifferences
796
+ // then check for overlap.
797
+ if (deltaIsEven && Math.abs(diagonal - diagonalForwardBase) <= numDifferences) {
798
+ if (originalIndex <= forwardPoints[diagonal]) {
799
+ midOriginalArr[0] = originalIndex;
800
+ midModifiedArr[0] = modifiedIndex;
801
+
802
+ if (tempOriginalIndex >= forwardPoints[diagonal] && LocalConstants.MaxDifferencesHistory > 0 && numDifferences <= (LocalConstants.MaxDifferencesHistory + 1)) {
803
+ // BINGO! We overlapped, and we have the full trace in memory!
804
+ return this.WALKTRACE(diagonalForwardBase, diagonalForwardStart, diagonalForwardEnd, diagonalForwardOffset,
805
+ diagonalReverseBase, diagonalReverseStart, diagonalReverseEnd, diagonalReverseOffset,
806
+ forwardPoints, reversePoints,
807
+ originalIndex, originalEnd, midOriginalArr,
808
+ modifiedIndex, modifiedEnd, midModifiedArr,
809
+ deltaIsEven, quitEarlyArr
810
+ );
811
+ } else {
812
+ // Either false overlap, or we didn't have enough memory for the full trace
813
+ // Just return the recursion point
814
+ return null;
815
+ }
816
+ }
817
+ }
818
+ }
819
+
820
+ // Save current vectors to history before the next iteration
821
+ if (numDifferences <= LocalConstants.MaxDifferencesHistory) {
822
+ // We are allocating space for one extra int, which we fill with
823
+ // the index of the diagonal base index
824
+ let temp = new Int32Array(diagonalForwardEnd - diagonalForwardStart + 2);
825
+ temp[0] = diagonalForwardBase - diagonalForwardStart + 1;
826
+ MyArray.Copy2(forwardPoints, diagonalForwardStart, temp, 1, diagonalForwardEnd - diagonalForwardStart + 1);
827
+ this.m_forwardHistory.push(temp);
828
+
829
+ temp = new Int32Array(diagonalReverseEnd - diagonalReverseStart + 2);
830
+ temp[0] = diagonalReverseBase - diagonalReverseStart + 1;
831
+ MyArray.Copy2(reversePoints, diagonalReverseStart, temp, 1, diagonalReverseEnd - diagonalReverseStart + 1);
832
+ this.m_reverseHistory.push(temp);
833
+ }
834
+
835
+ }
836
+
837
+ // If we got here, then we have the full trace in history. We just have to convert it to a change list
838
+ // NOTE: This part is a bit messy
839
+ return this.WALKTRACE(diagonalForwardBase, diagonalForwardStart, diagonalForwardEnd, diagonalForwardOffset,
840
+ diagonalReverseBase, diagonalReverseStart, diagonalReverseEnd, diagonalReverseOffset,
841
+ forwardPoints, reversePoints,
842
+ originalIndex, originalEnd, midOriginalArr,
843
+ modifiedIndex, modifiedEnd, midModifiedArr,
844
+ deltaIsEven, quitEarlyArr
845
+ );
846
+ }
847
+
848
+ /**
849
+ * Shifts the given changes to provide a more intuitive diff.
850
+ * While the first element in a diff matches the first element after the diff,
851
+ * we shift the diff down.
852
+ *
853
+ * @param changes The list of changes to shift
854
+ * @returns The shifted changes
855
+ */
856
+ private PrettifyChanges(changes: DiffChange[]): DiffChange[] {
857
+
858
+ // Shift all the changes down first
859
+ for (let i = 0; i < changes.length; i++) {
860
+ const change = changes[i];
861
+ const originalStop = (i < changes.length - 1) ? changes[i + 1].originalStart : this._originalElementsOrHash.length;
862
+ const modifiedStop = (i < changes.length - 1) ? changes[i + 1].modifiedStart : this._modifiedElementsOrHash.length;
863
+ const checkOriginal = change.originalLength > 0;
864
+ const checkModified = change.modifiedLength > 0;
865
+
866
+ while (change.originalStart + change.originalLength < originalStop &&
867
+ change.modifiedStart + change.modifiedLength < modifiedStop &&
868
+ (!checkOriginal || this.OriginalElementsAreEqual(change.originalStart, change.originalStart + change.originalLength)) &&
869
+ (!checkModified || this.ModifiedElementsAreEqual(change.modifiedStart, change.modifiedStart + change.modifiedLength))) {
870
+ change.originalStart++;
871
+ change.modifiedStart++;
872
+ }
873
+
874
+ const mergedChangeArr: Array<DiffChange | null> = [null];
875
+ if (i < changes.length - 1 && this.ChangesOverlap(changes[i], changes[i + 1], mergedChangeArr)) {
876
+ changes[i] = mergedChangeArr[0]!;
877
+ changes.splice(i + 1, 1);
878
+ i--;
879
+ continue;
880
+ }
881
+ }
882
+
883
+ // Shift changes back up until we hit empty or whitespace-only lines
884
+ for (let i = changes.length - 1; i >= 0; i--) {
885
+ const change = changes[i];
886
+
887
+ let originalStop = 0;
888
+ let modifiedStop = 0;
889
+ if (i > 0) {
890
+ const prevChange = changes[i - 1];
891
+ if (prevChange.originalLength > 0) {
892
+ originalStop = prevChange.originalStart + prevChange.originalLength;
893
+ }
894
+ if (prevChange.modifiedLength > 0) {
895
+ modifiedStop = prevChange.modifiedStart + prevChange.modifiedLength;
896
+ }
897
+ }
898
+
899
+ const checkOriginal = change.originalLength > 0;
900
+ const checkModified = change.modifiedLength > 0;
901
+
902
+ let bestDelta = 0;
903
+ let bestScore = this._boundaryScore(change.originalStart, change.originalLength, change.modifiedStart, change.modifiedLength);
904
+
905
+ for (let delta = 1; ; delta++) {
906
+ const originalStart = change.originalStart - delta;
907
+ const modifiedStart = change.modifiedStart - delta;
908
+
909
+ if (originalStart < originalStop || modifiedStart < modifiedStop) {
910
+ break;
911
+ }
912
+
913
+ if (checkOriginal && !this.OriginalElementsAreEqual(originalStart, originalStart + change.originalLength)) {
914
+ break;
915
+ }
916
+
917
+ if (checkModified && !this.ModifiedElementsAreEqual(modifiedStart, modifiedStart + change.modifiedLength)) {
918
+ break;
919
+ }
920
+
921
+ const score = this._boundaryScore(originalStart, change.originalLength, modifiedStart, change.modifiedLength);
922
+
923
+ if (score > bestScore) {
924
+ bestScore = score;
925
+ bestDelta = delta;
926
+ }
927
+ }
928
+
929
+ change.originalStart -= bestDelta;
930
+ change.modifiedStart -= bestDelta;
931
+ }
932
+
933
+ return changes;
934
+ }
935
+
936
+ private _OriginalIsBoundary(index: number): boolean {
937
+ if (index <= 0 || index >= this._originalElementsOrHash.length - 1) {
938
+ return true;
939
+ }
940
+ return (this._hasStrings && /^\s*$/.test(this._originalStringElements[index]));
941
+ }
942
+
943
+ private _OriginalRegionIsBoundary(originalStart: number, originalLength: number): boolean {
944
+ if (this._OriginalIsBoundary(originalStart) || this._OriginalIsBoundary(originalStart - 1)) {
945
+ return true;
946
+ }
947
+ if (originalLength > 0) {
948
+ const originalEnd = originalStart + originalLength;
949
+ if (this._OriginalIsBoundary(originalEnd - 1) || this._OriginalIsBoundary(originalEnd)) {
950
+ return true;
951
+ }
952
+ }
953
+ return false;
954
+ }
955
+
956
+ private _ModifiedIsBoundary(index: number): boolean {
957
+ if (index <= 0 || index >= this._modifiedElementsOrHash.length - 1) {
958
+ return true;
959
+ }
960
+ return (this._hasStrings && /^\s*$/.test(this._modifiedStringElements[index]));
961
+ }
962
+
963
+ private _ModifiedRegionIsBoundary(modifiedStart: number, modifiedLength: number): boolean {
964
+ if (this._ModifiedIsBoundary(modifiedStart) || this._ModifiedIsBoundary(modifiedStart - 1)) {
965
+ return true;
966
+ }
967
+ if (modifiedLength > 0) {
968
+ const modifiedEnd = modifiedStart + modifiedLength;
969
+ if (this._ModifiedIsBoundary(modifiedEnd - 1) || this._ModifiedIsBoundary(modifiedEnd)) {
970
+ return true;
971
+ }
972
+ }
973
+ return false;
974
+ }
975
+
976
+ private _boundaryScore(originalStart: number, originalLength: number, modifiedStart: number, modifiedLength: number): number {
977
+ const originalScore = (this._OriginalRegionIsBoundary(originalStart, originalLength) ? 1 : 0);
978
+ const modifiedScore = (this._ModifiedRegionIsBoundary(modifiedStart, modifiedLength) ? 1 : 0);
979
+ return (originalScore + modifiedScore);
980
+ }
981
+
982
+ /**
983
+ * Concatenates the two input DiffChange lists and returns the resulting
984
+ * list.
985
+ * @param The left changes
986
+ * @param The right changes
987
+ * @returns The concatenated list
988
+ */
989
+ private ConcatenateChanges(left: DiffChange[], right: DiffChange[]): DiffChange[] {
990
+ const mergedChangeArr: DiffChange[] = [];
991
+
992
+ if (left.length === 0 || right.length === 0) {
993
+ return (right.length > 0) ? right : left;
994
+ } else if (this.ChangesOverlap(left[left.length - 1], right[0], mergedChangeArr)) {
995
+ // Since we break the problem down recursively, it is possible that we
996
+ // might recurse in the middle of a change thereby splitting it into
997
+ // two changes. Here in the combining stage, we detect and fuse those
998
+ // changes back together
999
+ const result = new Array<DiffChange>(left.length + right.length - 1);
1000
+ MyArray.Copy(left, 0, result, 0, left.length - 1);
1001
+ result[left.length - 1] = mergedChangeArr[0];
1002
+ MyArray.Copy(right, 1, result, left.length, right.length - 1);
1003
+
1004
+ return result;
1005
+ } else {
1006
+ const result = new Array<DiffChange>(left.length + right.length);
1007
+ MyArray.Copy(left, 0, result, 0, left.length);
1008
+ MyArray.Copy(right, 0, result, left.length, right.length);
1009
+
1010
+ return result;
1011
+ }
1012
+ }
1013
+
1014
+ /**
1015
+ * Returns true if the two changes overlap and can be merged into a single
1016
+ * change
1017
+ * @param left The left change
1018
+ * @param right The right change
1019
+ * @param mergedChange The merged change if the two overlap, null otherwise
1020
+ * @returns True if the two changes overlap
1021
+ */
1022
+ private ChangesOverlap(left: DiffChange, right: DiffChange, mergedChangeArr: Array<DiffChange | null>): boolean {
1023
+ Debug.Assert(left.originalStart <= right.originalStart, 'Left change is not less than or equal to right change');
1024
+ Debug.Assert(left.modifiedStart <= right.modifiedStart, 'Left change is not less than or equal to right change');
1025
+
1026
+ if (left.originalStart + left.originalLength >= right.originalStart || left.modifiedStart + left.modifiedLength >= right.modifiedStart) {
1027
+ const originalStart = left.originalStart;
1028
+ let originalLength = left.originalLength;
1029
+ const modifiedStart = left.modifiedStart;
1030
+ let modifiedLength = left.modifiedLength;
1031
+
1032
+ if (left.originalStart + left.originalLength >= right.originalStart) {
1033
+ originalLength = right.originalStart + right.originalLength - left.originalStart;
1034
+ }
1035
+ if (left.modifiedStart + left.modifiedLength >= right.modifiedStart) {
1036
+ modifiedLength = right.modifiedStart + right.modifiedLength - left.modifiedStart;
1037
+ }
1038
+
1039
+ mergedChangeArr[0] = new DiffChange(originalStart, originalLength, modifiedStart, modifiedLength);
1040
+ return true;
1041
+ } else {
1042
+ mergedChangeArr[0] = null;
1043
+ return false;
1044
+ }
1045
+ }
1046
+
1047
+ /**
1048
+ * Helper method used to clip a diagonal index to the range of valid
1049
+ * diagonals. This also decides whether or not the diagonal index,
1050
+ * if it exceeds the boundary, should be clipped to the boundary or clipped
1051
+ * one inside the boundary depending on the Even/Odd status of the boundary
1052
+ * and numDifferences.
1053
+ * @param diagonal The index of the diagonal to clip.
1054
+ * @param numDifferences The current number of differences being iterated upon.
1055
+ * @param diagonalBaseIndex The base reference diagonal.
1056
+ * @param numDiagonals The total number of diagonals.
1057
+ * @returns The clipped diagonal index.
1058
+ */
1059
+ private ClipDiagonalBound(diagonal: number, numDifferences: number, diagonalBaseIndex: number, numDiagonals: number): number {
1060
+ if (diagonal >= 0 && diagonal < numDiagonals) {
1061
+ // Nothing to clip, its in range
1062
+ return diagonal;
1063
+ }
1064
+
1065
+ // diagonalsBelow: The number of diagonals below the reference diagonal
1066
+ // diagonalsAbove: The number of diagonals above the reference diagonal
1067
+ const diagonalsBelow = diagonalBaseIndex;
1068
+ const diagonalsAbove = numDiagonals - diagonalBaseIndex - 1;
1069
+ const diffEven = (numDifferences % 2 === 0);
1070
+
1071
+ if (diagonal < 0) {
1072
+ const lowerBoundEven = (diagonalsBelow % 2 === 0);
1073
+ return (diffEven === lowerBoundEven) ? 0 : 1;
1074
+ } else {
1075
+ const upperBoundEven = (diagonalsAbove % 2 === 0);
1076
+ return (diffEven === upperBoundEven) ? numDiagonals - 1 : numDiagonals - 2;
1077
+ }
1078
+ }
1079
+ }