Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/diff/legacyLinesDiffComputer.ts
3294 views
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
import { CharCode } from '../../../base/common/charCode.js';
7
import { IDiffChange, ISequence, LcsDiff, IDiffResult } from '../../../base/common/diff/diff.js';
8
import { ILinesDiffComputer, ILinesDiffComputerOptions, LinesDiff } from './linesDiffComputer.js';
9
import { RangeMapping, DetailedLineRangeMapping } from './rangeMapping.js';
10
import * as strings from '../../../base/common/strings.js';
11
import { Range } from '../core/range.js';
12
import { assertFn, checkAdjacentItems } from '../../../base/common/assert.js';
13
import { LineRange } from '../core/ranges/lineRange.js';
14
15
const MINIMUM_MATCHING_CHARACTER_LENGTH = 3;
16
17
export class LegacyLinesDiffComputer implements ILinesDiffComputer {
18
computeDiff(originalLines: string[], modifiedLines: string[], options: ILinesDiffComputerOptions): LinesDiff {
19
const diffComputer = new DiffComputer(originalLines, modifiedLines, {
20
maxComputationTime: options.maxComputationTimeMs,
21
shouldIgnoreTrimWhitespace: options.ignoreTrimWhitespace,
22
shouldComputeCharChanges: true,
23
shouldMakePrettyDiff: true,
24
shouldPostProcessCharChanges: true,
25
});
26
const result = diffComputer.computeDiff();
27
const changes: DetailedLineRangeMapping[] = [];
28
let lastChange: DetailedLineRangeMapping | null = null;
29
30
31
for (const c of result.changes) {
32
let originalRange: LineRange;
33
if (c.originalEndLineNumber === 0) {
34
// Insertion
35
originalRange = new LineRange(c.originalStartLineNumber + 1, c.originalStartLineNumber + 1);
36
} else {
37
originalRange = new LineRange(c.originalStartLineNumber, c.originalEndLineNumber + 1);
38
}
39
40
let modifiedRange: LineRange;
41
if (c.modifiedEndLineNumber === 0) {
42
// Deletion
43
modifiedRange = new LineRange(c.modifiedStartLineNumber + 1, c.modifiedStartLineNumber + 1);
44
} else {
45
modifiedRange = new LineRange(c.modifiedStartLineNumber, c.modifiedEndLineNumber + 1);
46
}
47
48
let change = new DetailedLineRangeMapping(originalRange, modifiedRange, c.charChanges?.map(c => new RangeMapping(
49
new Range(c.originalStartLineNumber, c.originalStartColumn, c.originalEndLineNumber, c.originalEndColumn),
50
new Range(c.modifiedStartLineNumber, c.modifiedStartColumn, c.modifiedEndLineNumber, c.modifiedEndColumn),
51
)));
52
if (lastChange) {
53
if (lastChange.modified.endLineNumberExclusive === change.modified.startLineNumber
54
|| lastChange.original.endLineNumberExclusive === change.original.startLineNumber) {
55
// join touching diffs. Probably moving diffs up/down in the algorithm causes touching diffs.
56
change = new DetailedLineRangeMapping(
57
lastChange.original.join(change.original),
58
lastChange.modified.join(change.modified),
59
lastChange.innerChanges && change.innerChanges ?
60
lastChange.innerChanges.concat(change.innerChanges) : undefined
61
);
62
changes.pop();
63
}
64
}
65
66
changes.push(change);
67
lastChange = change;
68
}
69
70
assertFn(() => {
71
return checkAdjacentItems(changes,
72
(m1, m2) => m2.original.startLineNumber - m1.original.endLineNumberExclusive === m2.modified.startLineNumber - m1.modified.endLineNumberExclusive &&
73
// There has to be an unchanged line in between (otherwise both diffs should have been joined)
74
m1.original.endLineNumberExclusive < m2.original.startLineNumber &&
75
m1.modified.endLineNumberExclusive < m2.modified.startLineNumber,
76
);
77
});
78
79
return new LinesDiff(changes, [], result.quitEarly);
80
}
81
}
82
83
export interface IDiffComputationResult {
84
quitEarly: boolean;
85
identical: boolean;
86
87
/**
88
* The changes as (legacy) line change array.
89
* @deprecated Use `changes2` instead.
90
*/
91
changes: ILineChange[];
92
93
/**
94
* The changes as (modern) line range mapping array.
95
*/
96
changes2: readonly DetailedLineRangeMapping[];
97
}
98
99
/**
100
* A change
101
*/
102
export interface IChange {
103
readonly originalStartLineNumber: number;
104
readonly originalEndLineNumber: number;
105
readonly modifiedStartLineNumber: number;
106
readonly modifiedEndLineNumber: number;
107
}
108
109
/**
110
* A character level change.
111
*/
112
export interface ICharChange extends IChange {
113
readonly originalStartColumn: number;
114
readonly originalEndColumn: number;
115
readonly modifiedStartColumn: number;
116
readonly modifiedEndColumn: number;
117
}
118
119
/**
120
* A line change
121
*/
122
export interface ILineChange extends IChange {
123
readonly charChanges: ICharChange[] | undefined;
124
}
125
126
export interface IDiffComputerResult {
127
quitEarly: boolean;
128
changes: ILineChange[];
129
}
130
131
function computeDiff(originalSequence: ISequence, modifiedSequence: ISequence, continueProcessingPredicate: () => boolean, pretty: boolean): IDiffResult {
132
const diffAlgo = new LcsDiff(originalSequence, modifiedSequence, continueProcessingPredicate);
133
return diffAlgo.ComputeDiff(pretty);
134
}
135
136
class LineSequence implements ISequence {
137
138
public readonly lines: string[];
139
private readonly _startColumns: number[];
140
private readonly _endColumns: number[];
141
142
constructor(lines: string[]) {
143
const startColumns: number[] = [];
144
const endColumns: number[] = [];
145
for (let i = 0, length = lines.length; i < length; i++) {
146
startColumns[i] = getFirstNonBlankColumn(lines[i], 1);
147
endColumns[i] = getLastNonBlankColumn(lines[i], 1);
148
}
149
this.lines = lines;
150
this._startColumns = startColumns;
151
this._endColumns = endColumns;
152
}
153
154
public getElements(): Int32Array | number[] | string[] {
155
const elements: string[] = [];
156
for (let i = 0, len = this.lines.length; i < len; i++) {
157
elements[i] = this.lines[i].substring(this._startColumns[i] - 1, this._endColumns[i] - 1);
158
}
159
return elements;
160
}
161
162
public getStrictElement(index: number): string {
163
return this.lines[index];
164
}
165
166
public getStartLineNumber(i: number): number {
167
return i + 1;
168
}
169
170
public getEndLineNumber(i: number): number {
171
return i + 1;
172
}
173
174
public createCharSequence(shouldIgnoreTrimWhitespace: boolean, startIndex: number, endIndex: number): CharSequence {
175
const charCodes: number[] = [];
176
const lineNumbers: number[] = [];
177
const columns: number[] = [];
178
let len = 0;
179
for (let index = startIndex; index <= endIndex; index++) {
180
const lineContent = this.lines[index];
181
const startColumn = (shouldIgnoreTrimWhitespace ? this._startColumns[index] : 1);
182
const endColumn = (shouldIgnoreTrimWhitespace ? this._endColumns[index] : lineContent.length + 1);
183
for (let col = startColumn; col < endColumn; col++) {
184
charCodes[len] = lineContent.charCodeAt(col - 1);
185
lineNumbers[len] = index + 1;
186
columns[len] = col;
187
len++;
188
}
189
if (!shouldIgnoreTrimWhitespace && index < endIndex) {
190
// Add \n if trim whitespace is not ignored
191
charCodes[len] = CharCode.LineFeed;
192
lineNumbers[len] = index + 1;
193
columns[len] = lineContent.length + 1;
194
len++;
195
}
196
}
197
return new CharSequence(charCodes, lineNumbers, columns);
198
}
199
}
200
201
class CharSequence implements ISequence {
202
203
private readonly _charCodes: number[];
204
private readonly _lineNumbers: number[];
205
private readonly _columns: number[];
206
207
constructor(charCodes: number[], lineNumbers: number[], columns: number[]) {
208
this._charCodes = charCodes;
209
this._lineNumbers = lineNumbers;
210
this._columns = columns;
211
}
212
213
public toString() {
214
return (
215
'[' + this._charCodes.map((s, idx) => (s === CharCode.LineFeed ? '\\n' : String.fromCharCode(s)) + `-(${this._lineNumbers[idx]},${this._columns[idx]})`).join(', ') + ']'
216
);
217
}
218
219
private _assertIndex(index: number, arr: number[]): void {
220
if (index < 0 || index >= arr.length) {
221
throw new Error(`Illegal index`);
222
}
223
}
224
225
public getElements(): Int32Array | number[] | string[] {
226
return this._charCodes;
227
}
228
229
public getStartLineNumber(i: number): number {
230
if (i > 0 && i === this._lineNumbers.length) {
231
// the start line number of the element after the last element
232
// is the end line number of the last element
233
return this.getEndLineNumber(i - 1);
234
}
235
this._assertIndex(i, this._lineNumbers);
236
237
return this._lineNumbers[i];
238
}
239
240
public getEndLineNumber(i: number): number {
241
if (i === -1) {
242
// the end line number of the element before the first element
243
// is the start line number of the first element
244
return this.getStartLineNumber(i + 1);
245
}
246
this._assertIndex(i, this._lineNumbers);
247
248
if (this._charCodes[i] === CharCode.LineFeed) {
249
return this._lineNumbers[i] + 1;
250
}
251
return this._lineNumbers[i];
252
}
253
254
public getStartColumn(i: number): number {
255
if (i > 0 && i === this._columns.length) {
256
// the start column of the element after the last element
257
// is the end column of the last element
258
return this.getEndColumn(i - 1);
259
}
260
this._assertIndex(i, this._columns);
261
return this._columns[i];
262
}
263
264
public getEndColumn(i: number): number {
265
if (i === -1) {
266
// the end column of the element before the first element
267
// is the start column of the first element
268
return this.getStartColumn(i + 1);
269
}
270
this._assertIndex(i, this._columns);
271
272
if (this._charCodes[i] === CharCode.LineFeed) {
273
return 1;
274
}
275
return this._columns[i] + 1;
276
}
277
}
278
279
class CharChange implements ICharChange {
280
281
public originalStartLineNumber: number;
282
public originalStartColumn: number;
283
public originalEndLineNumber: number;
284
public originalEndColumn: number;
285
286
public modifiedStartLineNumber: number;
287
public modifiedStartColumn: number;
288
public modifiedEndLineNumber: number;
289
public modifiedEndColumn: number;
290
291
constructor(
292
originalStartLineNumber: number,
293
originalStartColumn: number,
294
originalEndLineNumber: number,
295
originalEndColumn: number,
296
modifiedStartLineNumber: number,
297
modifiedStartColumn: number,
298
modifiedEndLineNumber: number,
299
modifiedEndColumn: number
300
) {
301
this.originalStartLineNumber = originalStartLineNumber;
302
this.originalStartColumn = originalStartColumn;
303
this.originalEndLineNumber = originalEndLineNumber;
304
this.originalEndColumn = originalEndColumn;
305
this.modifiedStartLineNumber = modifiedStartLineNumber;
306
this.modifiedStartColumn = modifiedStartColumn;
307
this.modifiedEndLineNumber = modifiedEndLineNumber;
308
this.modifiedEndColumn = modifiedEndColumn;
309
}
310
311
public static createFromDiffChange(diffChange: IDiffChange, originalCharSequence: CharSequence, modifiedCharSequence: CharSequence): CharChange {
312
const originalStartLineNumber = originalCharSequence.getStartLineNumber(diffChange.originalStart);
313
const originalStartColumn = originalCharSequence.getStartColumn(diffChange.originalStart);
314
const originalEndLineNumber = originalCharSequence.getEndLineNumber(diffChange.originalStart + diffChange.originalLength - 1);
315
const originalEndColumn = originalCharSequence.getEndColumn(diffChange.originalStart + diffChange.originalLength - 1);
316
317
const modifiedStartLineNumber = modifiedCharSequence.getStartLineNumber(diffChange.modifiedStart);
318
const modifiedStartColumn = modifiedCharSequence.getStartColumn(diffChange.modifiedStart);
319
const modifiedEndLineNumber = modifiedCharSequence.getEndLineNumber(diffChange.modifiedStart + diffChange.modifiedLength - 1);
320
const modifiedEndColumn = modifiedCharSequence.getEndColumn(diffChange.modifiedStart + diffChange.modifiedLength - 1);
321
322
return new CharChange(
323
originalStartLineNumber, originalStartColumn, originalEndLineNumber, originalEndColumn,
324
modifiedStartLineNumber, modifiedStartColumn, modifiedEndLineNumber, modifiedEndColumn,
325
);
326
}
327
}
328
329
function postProcessCharChanges(rawChanges: IDiffChange[]): IDiffChange[] {
330
if (rawChanges.length <= 1) {
331
return rawChanges;
332
}
333
334
const result = [rawChanges[0]];
335
let prevChange = result[0];
336
337
for (let i = 1, len = rawChanges.length; i < len; i++) {
338
const currChange = rawChanges[i];
339
340
const originalMatchingLength = currChange.originalStart - (prevChange.originalStart + prevChange.originalLength);
341
const modifiedMatchingLength = currChange.modifiedStart - (prevChange.modifiedStart + prevChange.modifiedLength);
342
// Both of the above should be equal, but the continueProcessingPredicate may prevent this from being true
343
const matchingLength = Math.min(originalMatchingLength, modifiedMatchingLength);
344
345
if (matchingLength < MINIMUM_MATCHING_CHARACTER_LENGTH) {
346
// Merge the current change into the previous one
347
prevChange.originalLength = (currChange.originalStart + currChange.originalLength) - prevChange.originalStart;
348
prevChange.modifiedLength = (currChange.modifiedStart + currChange.modifiedLength) - prevChange.modifiedStart;
349
} else {
350
// Add the current change
351
result.push(currChange);
352
prevChange = currChange;
353
}
354
}
355
356
return result;
357
}
358
359
class LineChange implements ILineChange {
360
public originalStartLineNumber: number;
361
public originalEndLineNumber: number;
362
public modifiedStartLineNumber: number;
363
public modifiedEndLineNumber: number;
364
public charChanges: CharChange[] | undefined;
365
366
constructor(
367
originalStartLineNumber: number,
368
originalEndLineNumber: number,
369
modifiedStartLineNumber: number,
370
modifiedEndLineNumber: number,
371
charChanges: CharChange[] | undefined
372
) {
373
this.originalStartLineNumber = originalStartLineNumber;
374
this.originalEndLineNumber = originalEndLineNumber;
375
this.modifiedStartLineNumber = modifiedStartLineNumber;
376
this.modifiedEndLineNumber = modifiedEndLineNumber;
377
this.charChanges = charChanges;
378
}
379
380
public static createFromDiffResult(shouldIgnoreTrimWhitespace: boolean, diffChange: IDiffChange, originalLineSequence: LineSequence, modifiedLineSequence: LineSequence, continueCharDiff: () => boolean, shouldComputeCharChanges: boolean, shouldPostProcessCharChanges: boolean): LineChange {
381
let originalStartLineNumber: number;
382
let originalEndLineNumber: number;
383
let modifiedStartLineNumber: number;
384
let modifiedEndLineNumber: number;
385
let charChanges: CharChange[] | undefined = undefined;
386
387
if (diffChange.originalLength === 0) {
388
originalStartLineNumber = originalLineSequence.getStartLineNumber(diffChange.originalStart) - 1;
389
originalEndLineNumber = 0;
390
} else {
391
originalStartLineNumber = originalLineSequence.getStartLineNumber(diffChange.originalStart);
392
originalEndLineNumber = originalLineSequence.getEndLineNumber(diffChange.originalStart + diffChange.originalLength - 1);
393
}
394
395
if (diffChange.modifiedLength === 0) {
396
modifiedStartLineNumber = modifiedLineSequence.getStartLineNumber(diffChange.modifiedStart) - 1;
397
modifiedEndLineNumber = 0;
398
} else {
399
modifiedStartLineNumber = modifiedLineSequence.getStartLineNumber(diffChange.modifiedStart);
400
modifiedEndLineNumber = modifiedLineSequence.getEndLineNumber(diffChange.modifiedStart + diffChange.modifiedLength - 1);
401
}
402
403
if (shouldComputeCharChanges && diffChange.originalLength > 0 && diffChange.originalLength < 20 && diffChange.modifiedLength > 0 && diffChange.modifiedLength < 20 && continueCharDiff()) {
404
// Compute character changes for diff chunks of at most 20 lines...
405
const originalCharSequence = originalLineSequence.createCharSequence(shouldIgnoreTrimWhitespace, diffChange.originalStart, diffChange.originalStart + diffChange.originalLength - 1);
406
const modifiedCharSequence = modifiedLineSequence.createCharSequence(shouldIgnoreTrimWhitespace, diffChange.modifiedStart, diffChange.modifiedStart + diffChange.modifiedLength - 1);
407
408
if (originalCharSequence.getElements().length > 0 && modifiedCharSequence.getElements().length > 0) {
409
let rawChanges = computeDiff(originalCharSequence, modifiedCharSequence, continueCharDiff, true).changes;
410
411
if (shouldPostProcessCharChanges) {
412
rawChanges = postProcessCharChanges(rawChanges);
413
}
414
415
charChanges = [];
416
for (let i = 0, length = rawChanges.length; i < length; i++) {
417
charChanges.push(CharChange.createFromDiffChange(rawChanges[i], originalCharSequence, modifiedCharSequence));
418
}
419
}
420
}
421
422
return new LineChange(originalStartLineNumber, originalEndLineNumber, modifiedStartLineNumber, modifiedEndLineNumber, charChanges);
423
}
424
}
425
426
export interface IDiffComputerOpts {
427
shouldComputeCharChanges: boolean;
428
shouldPostProcessCharChanges: boolean;
429
shouldIgnoreTrimWhitespace: boolean;
430
shouldMakePrettyDiff: boolean;
431
maxComputationTime: number;
432
}
433
434
export class DiffComputer {
435
436
private readonly shouldComputeCharChanges: boolean;
437
private readonly shouldPostProcessCharChanges: boolean;
438
private readonly shouldIgnoreTrimWhitespace: boolean;
439
private readonly shouldMakePrettyDiff: boolean;
440
private readonly originalLines: string[];
441
private readonly modifiedLines: string[];
442
private readonly original: LineSequence;
443
private readonly modified: LineSequence;
444
private readonly continueLineDiff: () => boolean;
445
private readonly continueCharDiff: () => boolean;
446
447
constructor(originalLines: string[], modifiedLines: string[], opts: IDiffComputerOpts) {
448
this.shouldComputeCharChanges = opts.shouldComputeCharChanges;
449
this.shouldPostProcessCharChanges = opts.shouldPostProcessCharChanges;
450
this.shouldIgnoreTrimWhitespace = opts.shouldIgnoreTrimWhitespace;
451
this.shouldMakePrettyDiff = opts.shouldMakePrettyDiff;
452
this.originalLines = originalLines;
453
this.modifiedLines = modifiedLines;
454
this.original = new LineSequence(originalLines);
455
this.modified = new LineSequence(modifiedLines);
456
457
this.continueLineDiff = createContinueProcessingPredicate(opts.maxComputationTime);
458
this.continueCharDiff = createContinueProcessingPredicate(opts.maxComputationTime === 0 ? 0 : Math.min(opts.maxComputationTime, 5000)); // never run after 5s for character changes...
459
}
460
461
public computeDiff(): IDiffComputerResult {
462
463
if (this.original.lines.length === 1 && this.original.lines[0].length === 0) {
464
// empty original => fast path
465
if (this.modified.lines.length === 1 && this.modified.lines[0].length === 0) {
466
return {
467
quitEarly: false,
468
changes: []
469
};
470
}
471
472
return {
473
quitEarly: false,
474
changes: [{
475
originalStartLineNumber: 1,
476
originalEndLineNumber: 1,
477
modifiedStartLineNumber: 1,
478
modifiedEndLineNumber: this.modified.lines.length,
479
charChanges: undefined
480
}]
481
};
482
}
483
484
if (this.modified.lines.length === 1 && this.modified.lines[0].length === 0) {
485
// empty modified => fast path
486
return {
487
quitEarly: false,
488
changes: [{
489
originalStartLineNumber: 1,
490
originalEndLineNumber: this.original.lines.length,
491
modifiedStartLineNumber: 1,
492
modifiedEndLineNumber: 1,
493
charChanges: undefined
494
}]
495
};
496
}
497
498
const diffResult = computeDiff(this.original, this.modified, this.continueLineDiff, this.shouldMakePrettyDiff);
499
const rawChanges = diffResult.changes;
500
const quitEarly = diffResult.quitEarly;
501
502
// The diff is always computed with ignoring trim whitespace
503
// This ensures we get the prettiest diff
504
505
if (this.shouldIgnoreTrimWhitespace) {
506
const lineChanges: LineChange[] = [];
507
for (let i = 0, length = rawChanges.length; i < length; i++) {
508
lineChanges.push(LineChange.createFromDiffResult(this.shouldIgnoreTrimWhitespace, rawChanges[i], this.original, this.modified, this.continueCharDiff, this.shouldComputeCharChanges, this.shouldPostProcessCharChanges));
509
}
510
return {
511
quitEarly: quitEarly,
512
changes: lineChanges
513
};
514
}
515
516
// Need to post-process and introduce changes where the trim whitespace is different
517
// Note that we are looping starting at -1 to also cover the lines before the first change
518
const result: LineChange[] = [];
519
520
let originalLineIndex = 0;
521
let modifiedLineIndex = 0;
522
for (let i = -1 /* !!!! */, len = rawChanges.length; i < len; i++) {
523
const nextChange = (i + 1 < len ? rawChanges[i + 1] : null);
524
const originalStop = (nextChange ? nextChange.originalStart : this.originalLines.length);
525
const modifiedStop = (nextChange ? nextChange.modifiedStart : this.modifiedLines.length);
526
527
while (originalLineIndex < originalStop && modifiedLineIndex < modifiedStop) {
528
const originalLine = this.originalLines[originalLineIndex];
529
const modifiedLine = this.modifiedLines[modifiedLineIndex];
530
531
if (originalLine !== modifiedLine) {
532
// These lines differ only in trim whitespace
533
534
// Check the leading whitespace
535
{
536
let originalStartColumn = getFirstNonBlankColumn(originalLine, 1);
537
let modifiedStartColumn = getFirstNonBlankColumn(modifiedLine, 1);
538
while (originalStartColumn > 1 && modifiedStartColumn > 1) {
539
const originalChar = originalLine.charCodeAt(originalStartColumn - 2);
540
const modifiedChar = modifiedLine.charCodeAt(modifiedStartColumn - 2);
541
if (originalChar !== modifiedChar) {
542
break;
543
}
544
originalStartColumn--;
545
modifiedStartColumn--;
546
}
547
548
if (originalStartColumn > 1 || modifiedStartColumn > 1) {
549
this._pushTrimWhitespaceCharChange(result,
550
originalLineIndex + 1, 1, originalStartColumn,
551
modifiedLineIndex + 1, 1, modifiedStartColumn
552
);
553
}
554
}
555
556
// Check the trailing whitespace
557
{
558
let originalEndColumn = getLastNonBlankColumn(originalLine, 1);
559
let modifiedEndColumn = getLastNonBlankColumn(modifiedLine, 1);
560
const originalMaxColumn = originalLine.length + 1;
561
const modifiedMaxColumn = modifiedLine.length + 1;
562
while (originalEndColumn < originalMaxColumn && modifiedEndColumn < modifiedMaxColumn) {
563
const originalChar = originalLine.charCodeAt(originalEndColumn - 1);
564
const modifiedChar = originalLine.charCodeAt(modifiedEndColumn - 1);
565
if (originalChar !== modifiedChar) {
566
break;
567
}
568
originalEndColumn++;
569
modifiedEndColumn++;
570
}
571
572
if (originalEndColumn < originalMaxColumn || modifiedEndColumn < modifiedMaxColumn) {
573
this._pushTrimWhitespaceCharChange(result,
574
originalLineIndex + 1, originalEndColumn, originalMaxColumn,
575
modifiedLineIndex + 1, modifiedEndColumn, modifiedMaxColumn
576
);
577
}
578
}
579
}
580
originalLineIndex++;
581
modifiedLineIndex++;
582
}
583
584
if (nextChange) {
585
// Emit the actual change
586
result.push(LineChange.createFromDiffResult(this.shouldIgnoreTrimWhitespace, nextChange, this.original, this.modified, this.continueCharDiff, this.shouldComputeCharChanges, this.shouldPostProcessCharChanges));
587
588
originalLineIndex += nextChange.originalLength;
589
modifiedLineIndex += nextChange.modifiedLength;
590
}
591
}
592
593
return {
594
quitEarly: quitEarly,
595
changes: result
596
};
597
}
598
599
private _pushTrimWhitespaceCharChange(
600
result: LineChange[],
601
originalLineNumber: number, originalStartColumn: number, originalEndColumn: number,
602
modifiedLineNumber: number, modifiedStartColumn: number, modifiedEndColumn: number
603
): void {
604
if (this._mergeTrimWhitespaceCharChange(result, originalLineNumber, originalStartColumn, originalEndColumn, modifiedLineNumber, modifiedStartColumn, modifiedEndColumn)) {
605
// Merged into previous
606
return;
607
}
608
609
let charChanges: CharChange[] | undefined = undefined;
610
if (this.shouldComputeCharChanges) {
611
charChanges = [new CharChange(
612
originalLineNumber, originalStartColumn, originalLineNumber, originalEndColumn,
613
modifiedLineNumber, modifiedStartColumn, modifiedLineNumber, modifiedEndColumn
614
)];
615
}
616
result.push(new LineChange(
617
originalLineNumber, originalLineNumber,
618
modifiedLineNumber, modifiedLineNumber,
619
charChanges
620
));
621
}
622
623
private _mergeTrimWhitespaceCharChange(
624
result: LineChange[],
625
originalLineNumber: number, originalStartColumn: number, originalEndColumn: number,
626
modifiedLineNumber: number, modifiedStartColumn: number, modifiedEndColumn: number
627
): boolean {
628
const len = result.length;
629
if (len === 0) {
630
return false;
631
}
632
633
const prevChange = result[len - 1];
634
635
if (prevChange.originalEndLineNumber === 0 || prevChange.modifiedEndLineNumber === 0) {
636
// Don't merge with inserts/deletes
637
return false;
638
}
639
640
if (prevChange.originalEndLineNumber === originalLineNumber && prevChange.modifiedEndLineNumber === modifiedLineNumber) {
641
if (this.shouldComputeCharChanges && prevChange.charChanges) {
642
prevChange.charChanges.push(new CharChange(
643
originalLineNumber, originalStartColumn, originalLineNumber, originalEndColumn,
644
modifiedLineNumber, modifiedStartColumn, modifiedLineNumber, modifiedEndColumn
645
));
646
}
647
return true;
648
}
649
650
if (prevChange.originalEndLineNumber + 1 === originalLineNumber && prevChange.modifiedEndLineNumber + 1 === modifiedLineNumber) {
651
prevChange.originalEndLineNumber = originalLineNumber;
652
prevChange.modifiedEndLineNumber = modifiedLineNumber;
653
if (this.shouldComputeCharChanges && prevChange.charChanges) {
654
prevChange.charChanges.push(new CharChange(
655
originalLineNumber, originalStartColumn, originalLineNumber, originalEndColumn,
656
modifiedLineNumber, modifiedStartColumn, modifiedLineNumber, modifiedEndColumn
657
));
658
}
659
return true;
660
}
661
662
return false;
663
}
664
}
665
666
function getFirstNonBlankColumn(txt: string, defaultValue: number): number {
667
const r = strings.firstNonWhitespaceIndex(txt);
668
if (r === -1) {
669
return defaultValue;
670
}
671
return r + 1;
672
}
673
674
function getLastNonBlankColumn(txt: string, defaultValue: number): number {
675
const r = strings.lastNonWhitespaceIndex(txt);
676
if (r === -1) {
677
return defaultValue;
678
}
679
return r + 2;
680
}
681
682
function createContinueProcessingPredicate(maximumRuntime: number): () => boolean {
683
if (maximumRuntime === 0) {
684
return () => true;
685
}
686
687
const startTime = Date.now();
688
return () => {
689
return Date.now() - startTime < maximumRuntime;
690
};
691
}
692
693