@vscode/diff 0.0.2-0 → 0.0.2-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,77 +2,8 @@
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  * Licensed under the MIT License. See License.txt in the project root for license information.
4
4
  *--------------------------------------------------------------------------------------------*/
5
- /**
6
- * A range of offsets (0-based).
7
- */
8
- class OffsetRange {
9
- start;
10
- endExclusive;
11
- static ofLength(length) {
12
- return new OffsetRange(0, length);
13
- }
14
- static ofStartAndLength(start, length) {
15
- return new OffsetRange(start, start + length);
16
- }
17
- constructor(start, endExclusive) {
18
- this.start = start;
19
- this.endExclusive = endExclusive;
20
- if (start > endExclusive) {
21
- throw new Error(`Invalid range: [${start}, ${endExclusive})`);
22
- }
23
- }
24
- get isEmpty() {
25
- return this.start === this.endExclusive;
26
- }
27
- get length() {
28
- return this.endExclusive - this.start;
29
- }
30
- delta(offset) {
31
- return new OffsetRange(this.start + offset, this.endExclusive + offset);
32
- }
33
- deltaStart(offset) {
34
- return new OffsetRange(this.start + offset, this.endExclusive);
35
- }
36
- deltaEnd(offset) {
37
- return new OffsetRange(this.start, this.endExclusive + offset);
38
- }
39
- equals(other) {
40
- return this.start === other.start && this.endExclusive === other.endExclusive;
41
- }
42
- containsRange(other) {
43
- return this.start <= other.start && other.endExclusive <= this.endExclusive;
44
- }
45
- contains(offset) {
46
- return this.start <= offset && offset < this.endExclusive;
47
- }
48
- join(other) {
49
- return new OffsetRange(Math.min(this.start, other.start), Math.max(this.endExclusive, other.endExclusive));
50
- }
51
- intersect(other) {
52
- const start = Math.max(this.start, other.start);
53
- const end = Math.min(this.endExclusive, other.endExclusive);
54
- if (start <= end) {
55
- return new OffsetRange(start, end);
56
- }
57
- return undefined;
58
- }
59
- intersects(other) {
60
- const start = Math.max(this.start, other.start);
61
- const end = Math.min(this.endExclusive, other.endExclusive);
62
- return start < end;
63
- }
64
- intersectsOrTouches(other) {
65
- const start = Math.max(this.start, other.start);
66
- const end = Math.min(this.endExclusive, other.endExclusive);
67
- return start <= end;
68
- }
69
- slice(arr) {
70
- return arr.slice(this.start, this.endExclusive);
71
- }
72
- toString() {
73
- return `[${this.start}, ${this.endExclusive})`;
74
- }
75
- }
5
+ import { O as OffsetRange, i as initWasm, g as getWasm, D as DefaultLinesDiffComputer, R as RangeMapping, a as Range, b as DetailedLineRangeMapping, L as LineRange, c as LinesDiff, w as wireToLinesDiff } from './wire.js';
6
+ export { d as _LineRangeMapping } from './wire.js';
76
7
 
77
8
  /**
78
9
  * A pair of ranges, one in `original`, one in `modified`.
@@ -376,2208 +307,282 @@ class DiffResult {
376
307
  }
377
308
  }
378
309
 
379
- class InfiniteTimeout {
380
- static instance = new InfiniteTimeout();
381
- isValid() {
382
- return true;
310
+ async function createDiffComputer(options = {}) {
311
+ if (options.useWasm) {
312
+ await initWasm();
313
+ return new _WasmDiffComputer(options.algorithm);
383
314
  }
315
+ return new _TsDiffComputer(options.algorithm);
384
316
  }
385
- class DateTimeout {
386
- timeout;
387
- startTime = Date.now();
388
- valid = true;
389
- constructor(timeout) {
390
- this.timeout = timeout;
391
- if (timeout <= 0) {
392
- throw new Error('timeout must be positive');
393
- }
317
+ // ============================================================================
318
+ // TS-backed computer
319
+ // ============================================================================
320
+ class _TsDiffComputer {
321
+ _algorithm;
322
+ _impl = new DefaultLinesDiffComputer();
323
+ constructor(_algorithm) {
324
+ this._algorithm = _algorithm;
394
325
  }
395
- isValid() {
396
- const valid = Date.now() - this.startTime < this.timeout;
397
- if (!valid && this.valid) {
398
- this.valid = false;
399
- }
400
- return this.valid;
326
+ computeDiff(original, modified, options) {
327
+ const originalLines = original.split('\n');
328
+ const modifiedLines = modified.split('\n');
329
+ const internal = {
330
+ maxComputationTimeMs: options?.maxComputationTimeMs ?? 0,
331
+ ignoreTrimWhitespace: options?.ignoreTrimWhitespace ?? false,
332
+ computeMoves: options?.computeMoves ?? false,
333
+ extendToSubwords: options?.extendToSubwords ?? false,
334
+ };
335
+ const linesDiff = this._impl.computeDiff(originalLines, modifiedLines, internal);
336
+ void this._algorithm; // currently a hint; auto-selection lives inside DefaultLinesDiffComputer
337
+ return _adaptLinesDiff(linesDiff, original, modified, originalLines, modifiedLines);
401
338
  }
402
339
  }
403
- /**
404
- * Represents an offset pair for two sequences.
405
- */
406
- class OffsetPair {
407
- offset1;
408
- offset2;
409
- static zero = new OffsetPair(0, 0);
410
- static max = new OffsetPair(Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER);
411
- constructor(offset1, offset2) {
412
- this.offset1 = offset1;
413
- this.offset2 = offset2;
414
- }
415
- toString() {
416
- return `${this.offset1} <-> ${this.offset2}`;
417
- }
418
- delta(offset) {
419
- if (offset === 0) {
420
- return this;
421
- }
422
- return new OffsetPair(this.offset1 + offset, this.offset2 + offset);
340
+ // ============================================================================
341
+ // WASM-backed computer
342
+ // ============================================================================
343
+ class _WasmDiffComputer {
344
+ _algorithm;
345
+ constructor(_algorithm) {
346
+ this._algorithm = _algorithm;
423
347
  }
424
- equals(other) {
425
- return this.offset1 === other.offset1 && this.offset2 === other.offset2;
348
+ computeDiff(original, modified, options) {
349
+ const originalLines = original.split('\n');
350
+ const modifiedLines = modified.split('\n');
351
+ const wasmOptions = {};
352
+ if (options?.maxComputationTimeMs !== undefined)
353
+ wasmOptions.maxComputationTimeMs = options.maxComputationTimeMs;
354
+ if (options?.ignoreTrimWhitespace !== undefined)
355
+ wasmOptions.ignoreTrimWhitespace = options.ignoreTrimWhitespace;
356
+ if (options?.computeMoves !== undefined)
357
+ wasmOptions.computeMoves = options.computeMoves;
358
+ if (options?.extendToSubwords !== undefined)
359
+ wasmOptions.extendToSubwords = options.extendToSubwords;
360
+ const wasm = getWasm();
361
+ const wasmResult = wasm.computeDiffLines(originalLines, modifiedLines, wasmOptions);
362
+ void this._algorithm;
363
+ const linesDiff = _wasmToLinesDiff(wasmResult);
364
+ return _adaptLinesDiff(linesDiff, original, modified, originalLines, modifiedLines);
426
365
  }
427
366
  }
428
- /**
429
- * Represents a diff between two sequences.
430
- */
431
- class SequenceDiff {
432
- seq1Range;
433
- seq2Range;
434
- static invert(sequenceDiffs, doc1Length) {
435
- const result = [];
436
- forEachAdjacent(sequenceDiffs, (a, b) => {
437
- result.push(SequenceDiff.fromOffsetPairs(a ? a.getEndExclusives() : OffsetPair.zero, b ? b.getStarts() : new OffsetPair(doc1Length, (a ? a.seq2Range.endExclusive - a.seq1Range.endExclusive : 0) + doc1Length)));
438
- });
439
- return result;
440
- }
441
- static fromOffsetPairs(start, endExclusive) {
442
- return new SequenceDiff(new OffsetRange(start.offset1, endExclusive.offset1), new OffsetRange(start.offset2, endExclusive.offset2));
443
- }
444
- static assertSorted(sequenceDiffs) {
445
- let last = undefined;
446
- for (const cur of sequenceDiffs) {
447
- if (last) {
448
- if (!(last.seq1Range.endExclusive <= cur.seq1Range.start && last.seq2Range.endExclusive <= cur.seq2Range.start)) {
449
- throw new Error('Sequence diffs must be sorted');
450
- }
451
- }
452
- last = cur;
453
- }
454
- }
455
- constructor(seq1Range, seq2Range) {
456
- this.seq1Range = seq1Range;
457
- this.seq2Range = seq2Range;
458
- }
459
- swap() {
460
- return new SequenceDiff(this.seq2Range, this.seq1Range);
461
- }
462
- toString() {
463
- return `${this.seq1Range} <-> ${this.seq2Range}`;
464
- }
465
- join(other) {
466
- return new SequenceDiff(this.seq1Range.join(other.seq1Range), this.seq2Range.join(other.seq2Range));
467
- }
468
- delta(offset) {
469
- if (offset === 0) {
470
- return this;
471
- }
472
- return new SequenceDiff(this.seq1Range.delta(offset), this.seq2Range.delta(offset));
367
+ function _wasmToLinesDiff(wasmResult) {
368
+ const changes = wasmResult.changes.map((c) => {
369
+ const inner = c.innerChanges?.map((ic) => new RangeMapping(new Range(ic.originalStartLine, ic.originalStartColumn, ic.originalEndLine, ic.originalEndColumn), new Range(ic.modifiedStartLine, ic.modifiedStartColumn, ic.modifiedEndLine, ic.modifiedEndColumn)));
370
+ return new DetailedLineRangeMapping(new LineRange(c.original.startLineNumber, c.original.endLineNumberExclusive), new LineRange(c.modified.startLineNumber, c.modified.endLineNumberExclusive), inner);
371
+ });
372
+ return new LinesDiff(changes, [], wasmResult.hitTimeout);
373
+ }
374
+ // ============================================================================
375
+ // LinesDiff -> DiffResult adapter
376
+ // ============================================================================
377
+ /** @internal — exported for the worker-side client. */
378
+ function _adaptLinesDiff(linesDiff, original, modified, originalLines, modifiedLines) {
379
+ const originalLineStarts = _computeLineStarts(originalLines);
380
+ const modifiedLineStarts = _computeLineStarts(modifiedLines);
381
+ const replacements = [];
382
+ for (const change of linesDiff.changes) {
383
+ _appendChangeReplacements(change, original, modified, originalLines, modifiedLines, originalLineStarts, modifiedLineStarts, replacements);
473
384
  }
474
- deltaStart(offset) {
475
- if (offset === 0) {
476
- return this;
385
+ const edits = AnnotatedStringEdit.of(replacements);
386
+ const moves = linesDiff.moves.map(m => _adaptMove(m, original, modified, originalLines, modifiedLines, originalLineStarts, modifiedLineStarts));
387
+ return new DiffResult(edits, moves, linesDiff.hitTimeout);
388
+ }
389
+ function _appendChangeReplacements(change, original, modified, originalLines, modifiedLines, originalLineStarts, modifiedLineStarts, out) {
390
+ if (change.innerChanges && change.innerChanges.length > 0) {
391
+ for (const ic of change.innerChanges) {
392
+ const origRange = _rangeToOffsetRange(ic.originalRange, original, originalLines, originalLineStarts);
393
+ const modRange = _rangeToOffsetRange(ic.modifiedRange, modified, modifiedLines, modifiedLineStarts);
394
+ const newText = modified.substring(modRange.start, modRange.endExclusive);
395
+ out.push(new AnnotatedStringReplacement(origRange, newText, DiffAnnotation.change));
477
396
  }
478
- return new SequenceDiff(this.seq1Range.deltaStart(offset), this.seq2Range.deltaStart(offset));
397
+ return;
479
398
  }
480
- deltaEnd(offset) {
481
- if (offset === 0) {
482
- return this;
399
+ // Fallback: no inner detail — replace the whole line range.
400
+ const origRange = _lineRangeToOffsetRange(change.original.startLineNumber, change.original.endLineNumberExclusive, original, originalLineStarts);
401
+ const modRange = _lineRangeToOffsetRange(change.modified.startLineNumber, change.modified.endLineNumberExclusive, modified, modifiedLineStarts);
402
+ const newText = modified.substring(modRange.start, modRange.endExclusive);
403
+ out.push(new AnnotatedStringReplacement(origRange, newText, DiffAnnotation.change));
404
+ }
405
+ function _adaptMove(move, original, modified, originalLines, modifiedLines, originalLineStarts, modifiedLineStarts) {
406
+ const origRange = _lineRangeToOffsetRange(move.lineRangeMapping.original.startLineNumber, move.lineRangeMapping.original.endLineNumberExclusive, original, originalLineStarts);
407
+ const modRange = _lineRangeToOffsetRange(move.lineRangeMapping.modified.startLineNumber, move.lineRangeMapping.modified.endLineNumberExclusive, modified, modifiedLineStarts);
408
+ // Build inner edit with offsets local to the moved slices.
409
+ const inner = [];
410
+ for (const change of move.changes) {
411
+ if (change.innerChanges && change.innerChanges.length > 0) {
412
+ for (const ic of change.innerChanges) {
413
+ const globalOrig = _rangeToOffsetRange(ic.originalRange, original, originalLines, originalLineStarts);
414
+ const globalMod = _rangeToOffsetRange(ic.modifiedRange, modified, modifiedLines, modifiedLineStarts);
415
+ inner.push(new StringReplacement(new OffsetRange(globalOrig.start - origRange.start, globalOrig.endExclusive - origRange.start), modified.substring(globalMod.start, globalMod.endExclusive)));
416
+ }
483
417
  }
484
- return new SequenceDiff(this.seq1Range.deltaEnd(offset), this.seq2Range.deltaEnd(offset));
485
- }
486
- intersectsOrTouches(other) {
487
- return this.seq1Range.intersectsOrTouches(other.seq1Range) || this.seq2Range.intersectsOrTouches(other.seq2Range);
488
- }
489
- intersect(other) {
490
- const i1 = this.seq1Range.intersect(other.seq1Range);
491
- const i2 = this.seq2Range.intersect(other.seq2Range);
492
- if (!i1 || !i2) {
493
- return undefined;
418
+ else {
419
+ const globalOrig = _lineRangeToOffsetRange(change.original.startLineNumber, change.original.endLineNumberExclusive, original, originalLineStarts);
420
+ const globalMod = _lineRangeToOffsetRange(change.modified.startLineNumber, change.modified.endLineNumberExclusive, modified, modifiedLineStarts);
421
+ inner.push(new StringReplacement(new OffsetRange(globalOrig.start - origRange.start, globalOrig.endExclusive - origRange.start), modified.substring(globalMod.start, globalMod.endExclusive)));
494
422
  }
495
- return new SequenceDiff(i1, i2);
496
- }
497
- getStarts() {
498
- return new OffsetPair(this.seq1Range.start, this.seq2Range.start);
499
- }
500
- getEndExclusives() {
501
- return new OffsetPair(this.seq1Range.endExclusive, this.seq2Range.endExclusive);
502
423
  }
424
+ return new Move(new OffsetRangeMapping(origRange, modRange), StringEdit.of(inner));
503
425
  }
504
- /**
505
- * Result of a diff algorithm computation.
506
- */
507
- class DiffAlgorithmResult {
508
- diffs;
509
- hitTimeout;
510
- static trivial(seq1, seq2) {
511
- return new DiffAlgorithmResult([new SequenceDiff(OffsetRange.ofLength(seq1.length), OffsetRange.ofLength(seq2.length))], false);
512
- }
513
- static trivialTimedOut(seq1, seq2) {
514
- return new DiffAlgorithmResult([new SequenceDiff(OffsetRange.ofLength(seq1.length), OffsetRange.ofLength(seq2.length))], true);
515
- }
516
- constructor(diffs, hitTimeout) {
517
- this.diffs = diffs;
518
- this.hitTimeout = hitTimeout;
426
+ // ----- offset/line helpers -----
427
+ function _computeLineStarts(lines) {
428
+ // Offsets correspond to `lines.join('\n')`, which equals the input string
429
+ // when the input was split by `\n`.
430
+ const starts = new Array(lines.length + 1);
431
+ starts[0] = 0;
432
+ for (let i = 0; i < lines.length; i++) {
433
+ starts[i + 1] = starts[i] + lines[i].length + 1; // +1 for the '\n'
519
434
  }
435
+ // `starts[lines.length]` is one past the synthetic trailing newline; the
436
+ // real text ends one earlier when there is no trailing newline (which is
437
+ // what `split('\n')` produces for non-newline-terminated input).
438
+ return starts;
520
439
  }
521
- // Helper function for iterating with neighbors
522
- function forEachAdjacent(items, callback) {
523
- let prev = undefined;
524
- for (const item of items) {
525
- callback(prev, item);
526
- prev = item;
527
- }
528
- callback(prev, undefined);
440
+ function _lineRangeToOffsetRange(startLineNumber, endLineNumberExclusive, text, lineStarts) {
441
+ const start = _lineColToOffset(startLineNumber, 1, text, lineStarts);
442
+ const end = _lineColToOffset(endLineNumberExclusive, 1, text, lineStarts);
443
+ return new OffsetRange(start, end);
529
444
  }
530
-
531
- /**
532
- * A 2D array implementation.
533
- */
534
- class Array2D {
535
- width;
536
- height;
537
- array = [];
538
- constructor(width, height) {
539
- this.width = width;
540
- this.height = height;
541
- this.array = new Array(width * height);
542
- }
543
- get(x, y) {
544
- return this.array[x + y * this.width];
545
- }
546
- set(x, y, value) {
547
- this.array[x + y * this.width] = value;
548
- }
445
+ function _rangeToOffsetRange(range, text, lines, lineStarts) {
446
+ const start = _lineColToOffset(range.startLineNumber, range.startColumn, text, lineStarts);
447
+ const end = _lineColToOffset(range.endLineNumber, range.endColumn, text, lineStarts);
448
+ return new OffsetRange(start, end);
549
449
  }
550
- /**
551
- * A O(MN) diffing algorithm that supports a score function.
552
- * The algorithm can be improved by processing the 2d array diagonally.
553
- */
554
- class DynamicProgrammingDiffing {
555
- compute(sequence1, sequence2, timeout = InfiniteTimeout.instance, equalityScore) {
556
- if (sequence1.length === 0 || sequence2.length === 0) {
557
- return DiffAlgorithmResult.trivial(sequence1, sequence2);
558
- }
559
- /**
560
- * lcsLengths.get(i, j): Length of the longest common subsequence of
561
- * sequence1.substring(0, i + 1) and sequence2.substring(0, j + 1).
562
- */
563
- const lcsLengths = new Array2D(sequence1.length, sequence2.length);
564
- const directions = new Array2D(sequence1.length, sequence2.length);
565
- const lengths = new Array2D(sequence1.length, sequence2.length);
566
- // Initialize lcsLengths
567
- for (let s1 = 0; s1 < sequence1.length; s1++) {
568
- for (let s2 = 0; s2 < sequence2.length; s2++) {
569
- if (!timeout.isValid()) {
570
- return DiffAlgorithmResult.trivialTimedOut(sequence1, sequence2);
571
- }
572
- const horizontalLen = s1 === 0 ? 0 : lcsLengths.get(s1 - 1, s2);
573
- const verticalLen = s2 === 0 ? 0 : lcsLengths.get(s1, s2 - 1);
574
- let extendedSeqScore;
575
- if (sequence1.getElement(s1) === sequence2.getElement(s2)) {
576
- if (s1 === 0 || s2 === 0) {
577
- extendedSeqScore = 0;
578
- }
579
- else {
580
- extendedSeqScore = lcsLengths.get(s1 - 1, s2 - 1);
581
- }
582
- if (s1 > 0 && s2 > 0 && directions.get(s1 - 1, s2 - 1) === 3) {
583
- // Prefer consecutive diagonals
584
- extendedSeqScore += lengths.get(s1 - 1, s2 - 1);
585
- }
586
- extendedSeqScore += (equalityScore ? equalityScore(s1, s2) : 1);
587
- }
588
- else {
589
- extendedSeqScore = -1;
590
- }
591
- const newValue = Math.max(horizontalLen, verticalLen, extendedSeqScore);
592
- if (newValue === extendedSeqScore) {
593
- // Prefer diagonals
594
- const prevLen = s1 > 0 && s2 > 0 ? lengths.get(s1 - 1, s2 - 1) : 0;
595
- lengths.set(s1, s2, prevLen + 1);
596
- directions.set(s1, s2, 3);
597
- }
598
- else if (newValue === horizontalLen) {
599
- lengths.set(s1, s2, 0);
600
- directions.set(s1, s2, 1);
601
- }
602
- else if (newValue === verticalLen) {
603
- lengths.set(s1, s2, 0);
604
- directions.set(s1, s2, 2);
605
- }
606
- lcsLengths.set(s1, s2, newValue);
607
- }
608
- }
609
- // Backtracking
610
- const result = [];
611
- let lastAligningPosS1 = sequence1.length;
612
- let lastAligningPosS2 = sequence2.length;
613
- function reportDecreasingAligningPositions(s1, s2) {
614
- if (s1 + 1 !== lastAligningPosS1 || s2 + 1 !== lastAligningPosS2) {
615
- result.push(new SequenceDiff(new OffsetRange(s1 + 1, lastAligningPosS1), new OffsetRange(s2 + 1, lastAligningPosS2)));
616
- }
617
- lastAligningPosS1 = s1;
618
- lastAligningPosS2 = s2;
619
- }
620
- let s1 = sequence1.length - 1;
621
- let s2 = sequence2.length - 1;
622
- while (s1 >= 0 && s2 >= 0) {
623
- if (directions.get(s1, s2) === 3) {
624
- reportDecreasingAligningPositions(s1, s2);
625
- s1--;
626
- s2--;
627
- }
628
- else {
629
- if (directions.get(s1, s2) === 1) {
630
- s1--;
631
- }
632
- else {
633
- s2--;
634
- }
635
- }
636
- }
637
- reportDecreasingAligningPositions(-1, -1);
638
- result.reverse();
639
- return new DiffAlgorithmResult(result, false);
450
+ function _lineColToOffset(lineNumber, column, text, lineStarts) {
451
+ // `lineNumber` and `column` are 1-based; `endLineNumberExclusive` can be
452
+ // `lines.length + 1` (one past the last line).
453
+ if (lineNumber <= 0) {
454
+ return 0;
455
+ }
456
+ if (lineNumber > lineStarts.length) {
457
+ return text.length;
640
458
  }
459
+ const base = lineStarts[lineNumber - 1];
460
+ const offset = base + (column - 1);
461
+ return Math.min(Math.max(offset, 0), text.length);
641
462
  }
642
463
 
643
464
  /**
644
- * An O(ND) diff algorithm that has a quadratic space worst-case complexity.
465
+ * Main-thread client for the diff worker. Exposes an async `IAsyncDiffComputer`.
466
+ *
467
+ * Inputs travel as `string[]` via structured-clone postMessage. The response
468
+ * is a small JSON-friendly `WireLinesDiff`; the main thread reconstructs the
469
+ * full `LinesDiff` and runs `_adaptLinesDiff` locally (it already has the
470
+ * input strings).
645
471
  */
646
- class MyersDiffAlgorithm {
647
- compute(seq1, seq2, timeout = InfiniteTimeout.instance) {
648
- // Common special cases - early return improves performance dramatically
649
- if (seq1.length === 0 || seq2.length === 0) {
650
- return DiffAlgorithmResult.trivial(seq1, seq2);
651
- }
652
- const seqX = seq1; // Text on the x axis
653
- const seqY = seq2; // Text on the y axis
654
- function getXAfterSnake(x, y) {
655
- while (x < seqX.length && y < seqY.length && seqX.getElement(x) === seqY.getElement(y)) {
656
- x++;
657
- y++;
658
- }
659
- return x;
660
- }
661
- let d = 0;
662
- // V[k]: X value of longest d-line that ends in diagonal k.
663
- // d-line: path from (0,0) to (x,y) that uses exactly d non-diagonals.
664
- // diagonal k: Set of points (x,y) with x-y = k.
665
- const V = new FastInt32Array();
666
- V.set(0, getXAfterSnake(0, 0));
667
- const paths = new FastArrayNegativeIndices();
668
- paths.set(0, V.get(0) === 0 ? null : new SnakePath(null, 0, 0, V.get(0)));
669
- let k = 0;
670
- loop: while (true) {
671
- d++;
672
- if (!timeout.isValid()) {
673
- return DiffAlgorithmResult.trivialTimedOut(seqX, seqY);
674
- }
675
- // The paper has `for (k = -d; k <= d; k += 2)`, but we can ignore diagonals that cannot influence the result.
676
- const lowerBound = -Math.min(d, seqY.length + (d % 2));
677
- const upperBound = Math.min(d, seqX.length + (d % 2));
678
- for (k = lowerBound; k <= upperBound; k += 2) {
679
- // We can use the X values of (d-1)-lines to compute X value of the longest d-lines.
680
- const maxXofDLineTop = k === upperBound ? -1 : V.get(k + 1); // Vertical non-diagonal (add symbol in seqX)
681
- const maxXofDLineLeft = k === lowerBound ? -1 : V.get(k - 1) + 1; // Horizontal non-diagonal (delete symbol in seqX)
682
- const x = Math.min(Math.max(maxXofDLineTop, maxXofDLineLeft), seqX.length);
683
- const y = x - k;
684
- if (x > seqX.length || y > seqY.length) {
685
- continue;
686
- }
687
- const newMaxX = getXAfterSnake(x, y);
688
- V.set(k, newMaxX);
689
- const lastPath = x === maxXofDLineTop ? paths.get(k + 1) : paths.get(k - 1);
690
- paths.set(k, newMaxX !== x ? new SnakePath(lastPath, x, y, newMaxX - x) : lastPath);
691
- if (V.get(k) === seqX.length && V.get(k) - k === seqY.length) {
692
- break loop;
693
- }
694
- }
695
- }
696
- let path = paths.get(k);
697
- const result = [];
698
- let lastAligningPosS1 = seqX.length;
699
- let lastAligningPosS2 = seqY.length;
700
- while (true) {
701
- const endX = path ? path.x + path.length : 0;
702
- const endY = path ? path.y + path.length : 0;
703
- if (endX !== lastAligningPosS1 || endY !== lastAligningPosS2) {
704
- result.push(new SequenceDiff(new OffsetRange(endX, lastAligningPosS1), new OffsetRange(endY, lastAligningPosS2)));
705
- }
706
- if (!path) {
707
- break;
708
- }
709
- lastAligningPosS1 = path.x;
710
- lastAligningPosS2 = path.y;
711
- path = path.prev;
712
- }
713
- result.reverse();
714
- return new DiffAlgorithmResult(result, false);
472
+ async function createWorkerDiffComputer(opts = {}) {
473
+ const worker = opts.workerFactory ? opts.workerFactory() : defaultWorkerFactory();
474
+ const backend = opts.useWasm ? 'wasm' : 'ts';
475
+ const computer = new _WorkerDiffComputer(worker, backend);
476
+ await computer.init();
477
+ return computer;
478
+ }
479
+ function defaultWorkerFactory() {
480
+ // The `?esm` marker is auto-discovered by `@vscode/*-plugin-esm-url`
481
+ // bundler plugins; they emit `worker.ts` as a sibling `worker.js` bundle
482
+ // and rewrite this URL accordingly.
483
+ return new Worker(new URL('worker.js?esm', import.meta.url), { type: 'module' });
484
+ }
485
+ class _WorkerDiffComputer {
486
+ _worker;
487
+ _backend;
488
+ _nextId = 1;
489
+ _pending = new Map();
490
+ _pendingInit = null;
491
+ _disposed = false;
492
+ constructor(_worker, _backend) {
493
+ this._worker = _worker;
494
+ this._backend = _backend;
495
+ this._worker.addEventListener('message', (ev) => {
496
+ this._onMessage(ev.data);
497
+ });
498
+ this._worker.addEventListener('error', (ev) => {
499
+ this._failAll(new Error(`Worker error: ${ev.message ?? 'unknown'}`));
500
+ });
715
501
  }
716
- }
717
- class SnakePath {
718
- prev;
719
- x;
720
- y;
721
- length;
722
- constructor(prev, x, y, length) {
723
- this.prev = prev;
724
- this.x = x;
725
- this.y = y;
726
- this.length = length;
502
+ init() {
503
+ const id = this._nextId++;
504
+ const req = { kind: 'init', id, backend: this._backend };
505
+ return new Promise((resolve, reject) => {
506
+ this._pendingInit = { resolve, reject };
507
+ this._worker.postMessage(req);
508
+ });
727
509
  }
728
- }
729
- /**
730
- * An array that supports fast negative indices.
731
- */
732
- class FastInt32Array {
733
- positiveArr = new Int32Array(10);
734
- negativeArr = new Int32Array(10);
735
- get(idx) {
736
- if (idx < 0) {
737
- idx = -idx - 1;
738
- return this.negativeArr[idx];
739
- }
740
- else {
741
- return this.positiveArr[idx];
510
+ computeDiff(original, modified, options = {}) {
511
+ if (this._disposed) {
512
+ return Promise.reject(new Error('Worker computer disposed'));
742
513
  }
514
+ const id = this._nextId++;
515
+ const originalLines = original.split('\n');
516
+ const modifiedLines = modified.split('\n');
517
+ const msg = {
518
+ kind: 'computeDiff',
519
+ id,
520
+ backend: this._backend,
521
+ originalLines,
522
+ modifiedLines,
523
+ options,
524
+ };
525
+ return new Promise((resolve, reject) => {
526
+ this._pending.set(id, {
527
+ resolve,
528
+ reject,
529
+ original,
530
+ modified,
531
+ originalLines,
532
+ modifiedLines,
533
+ });
534
+ this._worker.postMessage(msg);
535
+ });
743
536
  }
744
- set(idx, value) {
745
- if (idx < 0) {
746
- idx = -idx - 1;
747
- if (idx >= this.negativeArr.length) {
748
- const arr = this.negativeArr;
749
- this.negativeArr = new Int32Array(arr.length * 2);
750
- this.negativeArr.set(arr);
751
- }
752
- this.negativeArr[idx] = value;
537
+ dispose() {
538
+ if (this._disposed)
539
+ return;
540
+ this._disposed = true;
541
+ this._failAll(new Error('Worker computer disposed'));
542
+ this._worker.terminate();
543
+ }
544
+ _onMessage(msg) {
545
+ if (msg.kind === 'init') {
546
+ const pending = this._pendingInit;
547
+ this._pendingInit = null;
548
+ pending?.resolve();
549
+ return;
753
550
  }
754
- else {
755
- if (idx >= this.positiveArr.length) {
756
- const arr = this.positiveArr;
757
- this.positiveArr = new Int32Array(arr.length * 2);
758
- this.positiveArr.set(arr);
551
+ const entry = this._pending.get(msg.id);
552
+ if (!entry) {
553
+ // Init failure is reported on the init id with `kind === 'computeDiff'`
554
+ // and `ok === false`; route to the init promise if it's still pending.
555
+ if (!msg.ok && this._pendingInit) {
556
+ const p = this._pendingInit;
557
+ this._pendingInit = null;
558
+ p.reject(new Error(msg.error));
759
559
  }
760
- this.positiveArr[idx] = value;
560
+ return;
761
561
  }
762
- }
763
- }
764
- /**
765
- * An array that supports fast negative indices for objects.
766
- */
767
- class FastArrayNegativeIndices {
768
- positiveArr = [];
769
- negativeArr = [];
770
- get(idx) {
771
- if (idx < 0) {
772
- idx = -idx - 1;
773
- return this.negativeArr[idx];
562
+ this._pending.delete(msg.id);
563
+ if (!msg.ok) {
564
+ entry.reject(new Error(msg.error));
565
+ return;
774
566
  }
775
- else {
776
- return this.positiveArr[idx];
567
+ try {
568
+ const linesDiff = wireToLinesDiff(msg.result);
569
+ const result = _adaptLinesDiff(linesDiff, entry.original, entry.modified, entry.originalLines, entry.modifiedLines);
570
+ entry.resolve(result);
571
+ }
572
+ catch (err) {
573
+ entry.reject(err instanceof Error ? err : new Error(String(err)));
777
574
  }
778
575
  }
779
- set(idx, value) {
780
- if (idx < 0) {
781
- idx = -idx - 1;
782
- this.negativeArr[idx] = value;
576
+ _failAll(err) {
577
+ for (const p of this._pending.values())
578
+ p.reject(err);
579
+ this._pending.clear();
580
+ if (this._pendingInit) {
581
+ this._pendingInit.reject(err);
582
+ this._pendingInit = null;
783
583
  }
784
- else {
785
- this.positiveArr[idx] = value;
786
- }
787
- }
788
- }
789
-
790
- function log(msg) {
791
- }
792
- function optimizeSequenceDiffs(sequence1, sequence2, sequenceDiffs) {
793
- let result = sequenceDiffs;
794
- result = joinSequenceDiffsByShifting(sequence1, sequence2, result);
795
- // Sometimes, calling this function twice improves the result.
796
- result = joinSequenceDiffsByShifting(sequence1, sequence2, result);
797
- result = shiftSequenceDiffs(sequence1, sequence2, result);
798
- return result;
799
- }
800
- /**
801
- * This function fixes issues like this:
802
- * ```
803
- * import { Baz, Bar } from "foo";
804
- * ```
805
- * <->
806
- * ```
807
- * import { Baz, Bar, Foo } from "foo";
808
- * ```
809
- * Computed diff: [ {Add "," after Bar}, {Add "Foo " after space} }
810
- * Improved diff: [{Add ", Foo" after Bar}]
811
- */
812
- function joinSequenceDiffsByShifting(sequence1, sequence2, sequenceDiffs) {
813
- if (sequenceDiffs.length === 0) {
814
- return sequenceDiffs;
815
- }
816
- const result = [];
817
- result.push(sequenceDiffs[0]);
818
- // First move them all to the left as much as possible and join them if possible
819
- for (let i = 1; i < sequenceDiffs.length; i++) {
820
- const prevResult = result[result.length - 1];
821
- let cur = sequenceDiffs[i];
822
- if (cur.seq1Range.isEmpty || cur.seq2Range.isEmpty) {
823
- const length = cur.seq1Range.start - prevResult.seq1Range.endExclusive;
824
- let d;
825
- for (d = 1; d <= length; d++) {
826
- if (sequence1.getElement(cur.seq1Range.start - d) !== sequence1.getElement(cur.seq1Range.endExclusive - d) ||
827
- sequence2.getElement(cur.seq2Range.start - d) !== sequence2.getElement(cur.seq2Range.endExclusive - d)) {
828
- break;
829
- }
830
- }
831
- d--;
832
- if (d === length) {
833
- // Merge previous and current diff
834
- result[result.length - 1] = new SequenceDiff(new OffsetRange(prevResult.seq1Range.start, cur.seq1Range.endExclusive - length), new OffsetRange(prevResult.seq2Range.start, cur.seq2Range.endExclusive - length));
835
- continue;
836
- }
837
- cur = cur.delta(-d);
838
- }
839
- result.push(cur);
840
- }
841
- const result2 = [];
842
- // Then move them all to the right and join them again if possible
843
- for (let i = 0; i < result.length - 1; i++) {
844
- const nextResult = result[i + 1];
845
- let cur = result[i];
846
- if (cur.seq1Range.isEmpty || cur.seq2Range.isEmpty) {
847
- const length = nextResult.seq1Range.start - cur.seq1Range.endExclusive;
848
- let d;
849
- for (d = 0; d < length; d++) {
850
- if (!sequence1.isStronglyEqual(cur.seq1Range.start + d, cur.seq1Range.endExclusive + d) ||
851
- !sequence2.isStronglyEqual(cur.seq2Range.start + d, cur.seq2Range.endExclusive + d)) {
852
- break;
853
- }
854
- }
855
- if (d === length) {
856
- // Merge previous and current diff
857
- result[i + 1] = new SequenceDiff(new OffsetRange(cur.seq1Range.start + length, nextResult.seq1Range.endExclusive), new OffsetRange(cur.seq2Range.start + length, nextResult.seq2Range.endExclusive));
858
- continue;
859
- }
860
- if (d > 0) {
861
- cur = cur.delta(d);
862
- }
863
- }
864
- result2.push(cur);
865
- }
866
- if (result.length > 0) {
867
- result2.push(result[result.length - 1]);
868
- }
869
- return result2;
870
- }
871
- function shiftSequenceDiffs(sequence1, sequence2, sequenceDiffs) {
872
- if (!sequence1.getBoundaryScore || !sequence2.getBoundaryScore) {
873
- return sequenceDiffs;
874
- }
875
- for (let i = 0; i < sequenceDiffs.length; i++) {
876
- const prevDiff = (i > 0 ? sequenceDiffs[i - 1] : undefined);
877
- const diff = sequenceDiffs[i];
878
- const nextDiff = (i + 1 < sequenceDiffs.length ? sequenceDiffs[i + 1] : undefined);
879
- const seq1ValidRange = new OffsetRange(prevDiff ? prevDiff.seq1Range.endExclusive + 1 : 0, nextDiff ? nextDiff.seq1Range.start - 1 : sequence1.length);
880
- const seq2ValidRange = new OffsetRange(prevDiff ? prevDiff.seq2Range.endExclusive + 1 : 0, nextDiff ? nextDiff.seq2Range.start - 1 : sequence2.length);
881
- if (diff.seq1Range.isEmpty) {
882
- sequenceDiffs[i] = shiftDiffToBetterPosition(diff, sequence1, sequence2, seq1ValidRange, seq2ValidRange);
883
- }
884
- else if (diff.seq2Range.isEmpty) {
885
- sequenceDiffs[i] = shiftDiffToBetterPosition(diff.swap(), sequence2, sequence1, seq2ValidRange, seq1ValidRange).swap();
886
- }
887
- }
888
- return sequenceDiffs;
889
- }
890
- function shiftDiffToBetterPosition(diff, sequence1, sequence2, seq1ValidRange, seq2ValidRange) {
891
- const maxShiftLimit = 100; // To prevent performance issues
892
- let deltaBefore = 1;
893
- while (diff.seq1Range.start - deltaBefore >= seq1ValidRange.start &&
894
- diff.seq2Range.start - deltaBefore >= seq2ValidRange.start &&
895
- sequence2.isStronglyEqual(diff.seq2Range.start - deltaBefore, diff.seq2Range.endExclusive - deltaBefore) && deltaBefore < maxShiftLimit) {
896
- deltaBefore++;
897
- }
898
- deltaBefore--;
899
- let deltaAfter = 0;
900
- while (diff.seq1Range.start + deltaAfter < seq1ValidRange.endExclusive &&
901
- diff.seq2Range.endExclusive + deltaAfter < seq2ValidRange.endExclusive &&
902
- sequence2.isStronglyEqual(diff.seq2Range.start + deltaAfter, diff.seq2Range.endExclusive + deltaAfter) && deltaAfter < maxShiftLimit) {
903
- deltaAfter++;
904
- }
905
- if (deltaBefore === 0 && deltaAfter === 0) {
906
- return diff;
907
- }
908
- let bestDelta = 0;
909
- let bestScore = -1;
910
- // Find best scored delta
911
- for (let delta = -deltaBefore; delta <= deltaAfter; delta++) {
912
- const seq2OffsetStart = diff.seq2Range.start + delta;
913
- const seq2OffsetEndExclusive = diff.seq2Range.endExclusive + delta;
914
- const seq1Offset = diff.seq1Range.start + delta;
915
- const score = sequence1.getBoundaryScore(seq1Offset) + sequence2.getBoundaryScore(seq2OffsetStart) + sequence2.getBoundaryScore(seq2OffsetEndExclusive);
916
- if (score > bestScore) {
917
- bestScore = score;
918
- bestDelta = delta;
919
- }
920
- }
921
- return diff.delta(bestDelta);
922
- }
923
- function removeShortMatches(sequence1, sequence2, sequenceDiffs) {
924
- const result = [];
925
- for (const s of sequenceDiffs) {
926
- const last = result[result.length - 1];
927
- if (!last) {
928
- result.push(s);
929
- continue;
930
- }
931
- if (s.seq1Range.start - last.seq1Range.endExclusive <= 2 || s.seq2Range.start - last.seq2Range.endExclusive <= 2) {
932
- result[result.length - 1] = new SequenceDiff(last.seq1Range.join(s.seq1Range), last.seq2Range.join(s.seq2Range));
933
- }
934
- else {
935
- result.push(s);
936
- }
937
- }
938
- return result;
939
- }
940
- function extendDiffsToEntireWordIfAppropriate(sequence1, sequence2, sequenceDiffs, findParent, force = false) {
941
- const equalMappings = SequenceDiff.invert(sequenceDiffs, sequence1.length);
942
- const additional = [];
943
- let lastPoint = new OffsetPair(0, 0);
944
- function scanWord(pair, equalMapping) {
945
- if (pair.offset1 < lastPoint.offset1 || pair.offset2 < lastPoint.offset2) {
946
- return;
947
- }
948
- const w1 = findParent(sequence1, pair.offset1);
949
- const w2 = findParent(sequence2, pair.offset2);
950
- if (!w1 || !w2) {
951
- return;
952
- }
953
- let w = new SequenceDiff(w1, w2);
954
- const equalPart = w.intersect(equalMapping);
955
- let equalChars1 = equalPart.seq1Range.length;
956
- let equalChars2 = equalPart.seq2Range.length;
957
- while (equalMappings.length > 0) {
958
- const next = equalMappings[0];
959
- const intersects = next.seq1Range.intersects(w.seq1Range) || next.seq2Range.intersects(w.seq2Range);
960
- if (!intersects) {
961
- break;
962
- }
963
- const v1 = findParent(sequence1, next.seq1Range.start);
964
- const v2 = findParent(sequence2, next.seq2Range.start);
965
- const v = new SequenceDiff(v1, v2);
966
- const equalPartV = v.intersect(next);
967
- equalChars1 += equalPartV.seq1Range.length;
968
- equalChars2 += equalPartV.seq2Range.length;
969
- w = w.join(v);
970
- if (w.seq1Range.endExclusive >= next.seq1Range.endExclusive) {
971
- equalMappings.shift();
972
- }
973
- else {
974
- break;
975
- }
976
- }
977
- if ((force && equalChars1 + equalChars2 < w.seq1Range.length + w.seq2Range.length) || equalChars1 + equalChars2 < (w.seq1Range.length + w.seq2Range.length) * 2 / 3) {
978
- additional.push(w);
979
- }
980
- lastPoint = w.getEndExclusives();
981
- }
982
- while (equalMappings.length > 0) {
983
- const next = equalMappings.shift();
984
- if (next.seq1Range.isEmpty) {
985
- continue;
986
- }
987
- scanWord(next.getStarts(), next);
988
- scanWord(next.getEndExclusives().delta(-1), next);
989
- }
990
- const merged = mergeSequenceDiffs(sequenceDiffs, additional);
991
- return merged;
992
- }
993
- function mergeSequenceDiffs(sequenceDiffs1, sequenceDiffs2) {
994
- const result = [];
995
- while (sequenceDiffs1.length > 0 || sequenceDiffs2.length > 0) {
996
- const sd1 = sequenceDiffs1[0];
997
- const sd2 = sequenceDiffs2[0];
998
- let next;
999
- if (sd1 && (!sd2 || sd1.seq1Range.start < sd2.seq1Range.start)) {
1000
- next = sequenceDiffs1.shift();
1001
- }
1002
- else {
1003
- next = sequenceDiffs2.shift();
1004
- }
1005
- if (result.length > 0 && result[result.length - 1].seq1Range.endExclusive >= next.seq1Range.start) {
1006
- result[result.length - 1] = result[result.length - 1].join(next);
1007
- }
1008
- else {
1009
- result.push(next);
1010
- }
1011
- }
1012
- return result;
1013
- }
1014
- function removeVeryShortMatchingLinesBetweenDiffs(sequence1, _sequence2, sequenceDiffs) {
1015
- let diffs = sequenceDiffs;
1016
- if (diffs.length === 0) {
1017
- return diffs;
1018
- }
1019
- let counter = 0;
1020
- let shouldRepeat;
1021
- do {
1022
- shouldRepeat = false;
1023
- const result = [diffs[0]];
1024
- for (let i = 1; i < diffs.length; i++) {
1025
- const cur = diffs[i];
1026
- const lastResult = result[result.length - 1];
1027
- function shouldJoinDiffs(before, after) {
1028
- const unchangedRange = new OffsetRange(lastResult.seq1Range.endExclusive, cur.seq1Range.start);
1029
- const unchangedText = sequence1.getText(unchangedRange);
1030
- const unchangedTextWithoutWs = unchangedText.replace(/\s/g, '');
1031
- if (unchangedTextWithoutWs.length <= 4
1032
- && (before.seq1Range.length + before.seq2Range.length > 5 || after.seq1Range.length + after.seq2Range.length > 5)) {
1033
- return true;
1034
- }
1035
- return false;
1036
- }
1037
- const shouldJoin = shouldJoinDiffs(lastResult, cur);
1038
- if (shouldJoin) {
1039
- shouldRepeat = true;
1040
- result[result.length - 1] = result[result.length - 1].join(cur);
1041
- }
1042
- else {
1043
- result.push(cur);
1044
- }
1045
- }
1046
- diffs = result;
1047
- } while (counter++ < 10 && shouldRepeat);
1048
- return diffs;
1049
- }
1050
- function removeVeryShortMatchingTextBetweenLongDiffs(sequence1, sequence2, sequenceDiffs) {
1051
- log(`[removeVeryShortMatchingTextBetweenLongDiffs] input diffs: ${JSON.stringify(sequenceDiffs.map(d => d.toString()))}`);
1052
- let diffs = sequenceDiffs;
1053
- if (diffs.length === 0) {
1054
- return diffs;
1055
- }
1056
- let counter = 0;
1057
- let shouldRepeat;
1058
- do {
1059
- shouldRepeat = false;
1060
- const result = [diffs[0]];
1061
- for (let i = 1; i < diffs.length; i++) {
1062
- const cur = diffs[i];
1063
- const lastResult = result[result.length - 1];
1064
- function shouldJoinDiffs(before, after) {
1065
- const unchangedRange = new OffsetRange(lastResult.seq1Range.endExclusive, cur.seq1Range.start);
1066
- const unchangedLineCount = sequence1.countLinesIn(unchangedRange);
1067
- if (unchangedLineCount > 5 || unchangedRange.length > 500) {
1068
- return false;
1069
- }
1070
- const unchangedText = sequence1.getText(unchangedRange).trim();
1071
- if (unchangedText.length > 20 || unchangedText.split(/\r\n|\r|\n/).length > 1) {
1072
- return false;
1073
- }
1074
- const beforeLineCount1 = sequence1.countLinesIn(before.seq1Range);
1075
- const beforeSeq1Length = before.seq1Range.length;
1076
- const beforeLineCount2 = sequence2.countLinesIn(before.seq2Range);
1077
- const beforeSeq2Length = before.seq2Range.length;
1078
- const afterLineCount1 = sequence1.countLinesIn(after.seq1Range);
1079
- const afterSeq1Length = after.seq1Range.length;
1080
- const afterLineCount2 = sequence2.countLinesIn(after.seq2Range);
1081
- const afterSeq2Length = after.seq2Range.length;
1082
- const max = 2 * 40 + 50;
1083
- function cap(v) {
1084
- return Math.min(v, max);
1085
- }
1086
- if (Math.pow(Math.pow(cap(beforeLineCount1 * 40 + beforeSeq1Length), 1.5) + Math.pow(cap(beforeLineCount2 * 40 + beforeSeq2Length), 1.5), 1.5)
1087
- + Math.pow(Math.pow(cap(afterLineCount1 * 40 + afterSeq1Length), 1.5) + Math.pow(cap(afterLineCount2 * 40 + afterSeq2Length), 1.5), 1.5) > ((max ** 1.5) ** 1.5) * 1.3) {
1088
- return true;
1089
- }
1090
- return false;
1091
- }
1092
- const shouldJoin = shouldJoinDiffs(lastResult, cur);
1093
- if (shouldJoin) {
1094
- shouldRepeat = true;
1095
- result[result.length - 1] = result[result.length - 1].join(cur);
1096
- }
1097
- else {
1098
- result.push(cur);
1099
- }
1100
- }
1101
- diffs = result;
1102
- } while (counter++ < 10 && shouldRepeat);
1103
- log(`[removeVeryShortMatchingTextBetweenLongDiffs] after join loop, diffs: ${JSON.stringify(diffs.map(d => d.toString()))}`);
1104
- const newDiffs = [];
1105
- // Remove short suffixes/prefixes
1106
- forEachWithNeighbors(diffs, (prev, cur, next) => {
1107
- log(`[forEachWithNeighbors] cur: ${cur.toString()}, prev: ${prev?.toString()}, next: ${next?.toString()}`);
1108
- let newDiff = cur;
1109
- function shouldMarkAsChanged(text) {
1110
- return text.length > 0 && text.trim().length <= 3 && cur.seq1Range.length + cur.seq2Range.length > 100;
1111
- }
1112
- const fullRange1 = sequence1.extendToFullLines(cur.seq1Range);
1113
- log(`[fullRange1] ${fullRange1.toString()} for cur.seq1Range: ${cur.seq1Range.toString()}`);
1114
- const prefix = sequence1.getText(new OffsetRange(fullRange1.start, cur.seq1Range.start));
1115
- log(`[prefix] "${prefix}" len=${prefix.length}, shouldMark=${prefix.length > 0 && prefix.trim().length <= 3 && cur.seq1Range.length + cur.seq2Range.length > 100}`);
1116
- if (shouldMarkAsChanged(prefix)) {
1117
- log(`[deltaStart] before: ${newDiff.toString()}, delta: ${-prefix.length}`);
1118
- newDiff = newDiff.deltaStart(-prefix.length);
1119
- log(`[deltaStart] after: ${newDiff.toString()}`);
1120
- }
1121
- const suffix = sequence1.getText(new OffsetRange(cur.seq1Range.endExclusive, fullRange1.endExclusive));
1122
- log(`[suffix] "${suffix}" len=${suffix.length}, shouldMark=${suffix.length > 0 && suffix.trim().length <= 3 && cur.seq1Range.length + cur.seq2Range.length > 100}`);
1123
- if (shouldMarkAsChanged(suffix)) {
1124
- log(`[deltaEnd] before: ${newDiff.toString()}, delta: ${suffix.length}`);
1125
- newDiff = newDiff.deltaEnd(suffix.length);
1126
- log(`[deltaEnd] after: ${newDiff.toString()}`);
1127
- }
1128
- const availableSpace = SequenceDiff.fromOffsetPairs(prev ? prev.getEndExclusives() : OffsetPair.zero, next ? next.getStarts() : OffsetPair.max);
1129
- log(`[availableSpace] ${availableSpace.toString()}, newDiff before intersect: ${newDiff.toString()}`);
1130
- const intersected = newDiff.intersect(availableSpace);
1131
- log(`[intersected] ${intersected?.toString()}`);
1132
- if (newDiffs.length > 0 && intersected.getStarts().equals(newDiffs[newDiffs.length - 1].getEndExclusives())) {
1133
- newDiffs[newDiffs.length - 1] = newDiffs[newDiffs.length - 1].join(intersected);
1134
- }
1135
- else {
1136
- newDiffs.push(intersected);
1137
- }
1138
- });
1139
- return newDiffs;
1140
- }
1141
- function forEachWithNeighbors(items, callback) {
1142
- for (let i = 0; i < items.length; i++) {
1143
- callback(items[i - 1], items[i], items[i + 1]);
1144
- }
1145
- }
1146
-
1147
- const CharCode$1 = {
1148
- Space: 32,
1149
- Tab: 9,
1150
- };
1151
- class LineSequence {
1152
- trimmedHash;
1153
- lines;
1154
- constructor(trimmedHash, lines) {
1155
- this.trimmedHash = trimmedHash;
1156
- this.lines = lines;
1157
- }
1158
- getElement(offset) {
1159
- return this.trimmedHash[offset];
1160
- }
1161
- get length() {
1162
- return this.trimmedHash.length;
1163
- }
1164
- getBoundaryScore(length) {
1165
- const indentationBefore = length === 0 ? 0 : getIndentation(this.lines[length - 1]);
1166
- const indentationAfter = length === this.lines.length ? 0 : getIndentation(this.lines[length]);
1167
- return 1000 - (indentationBefore + indentationAfter);
1168
- }
1169
- getText(range) {
1170
- return this.lines.slice(range.start, range.endExclusive).join('\n');
1171
- }
1172
- isStronglyEqual(offset1, offset2) {
1173
- return this.lines[offset1] === this.lines[offset2];
1174
- }
1175
- }
1176
- function getIndentation(str) {
1177
- let i = 0;
1178
- while (i < str.length && (str.charCodeAt(i) === CharCode$1.Space || str.charCodeAt(i) === CharCode$1.Tab)) {
1179
- i++;
1180
- }
1181
- return i;
1182
- }
1183
-
1184
- const CharCode = {
1185
- LineFeed: 10,
1186
- CarriageReturn: 13,
1187
- Space: 32,
1188
- Tab: 9,
1189
- a: 97,
1190
- z: 122,
1191
- A: 65,
1192
- Z: 90,
1193
- Digit0: 48,
1194
- Digit9: 57,
1195
- Comma: 44,
1196
- Semicolon: 59,
1197
- };
1198
- function isSpace(charCode) {
1199
- return charCode === CharCode.Space || charCode === CharCode.Tab;
1200
- }
1201
- class Range {
1202
- startLineNumber;
1203
- startColumn;
1204
- endLineNumber;
1205
- endColumn;
1206
- constructor(startLineNumber, startColumn, endLineNumber, endColumn) {
1207
- this.startLineNumber = startLineNumber;
1208
- this.startColumn = startColumn;
1209
- this.endLineNumber = endLineNumber;
1210
- this.endColumn = endColumn;
1211
- }
1212
- static fromPositions(start, end) {
1213
- return new Range(start.lineNumber, start.column, end.lineNumber, end.column);
1214
- }
1215
- }
1216
- class Position {
1217
- lineNumber;
1218
- column;
1219
- constructor(lineNumber, column) {
1220
- this.lineNumber = lineNumber;
1221
- this.column = column;
1222
- }
1223
- isBefore(other) {
1224
- if (this.lineNumber < other.lineNumber) {
1225
- return true;
1226
- }
1227
- if (other.lineNumber < this.lineNumber) {
1228
- return false;
1229
- }
1230
- return this.column < other.column;
1231
- }
1232
- }
1233
- class LinesSliceCharSequence {
1234
- lines;
1235
- considerWhitespaceChanges;
1236
- elements = [];
1237
- firstElementOffsetByLineIdx = [];
1238
- lineStartOffsets = [];
1239
- trimmedWsLengthsByLineIdx = [];
1240
- _range;
1241
- constructor(lines, range, considerWhitespaceChanges) {
1242
- this.lines = lines;
1243
- this.considerWhitespaceChanges = considerWhitespaceChanges;
1244
- this._range = range;
1245
- this.firstElementOffsetByLineIdx.push(0);
1246
- for (let lineNumber = this._range.startLineNumber; lineNumber <= this._range.endLineNumber; lineNumber++) {
1247
- let line = lines[lineNumber - 1];
1248
- let lineStartOffset = 0;
1249
- if (lineNumber === this._range.startLineNumber && this._range.startColumn > 1) {
1250
- lineStartOffset = this._range.startColumn - 1;
1251
- line = line.substring(lineStartOffset);
1252
- }
1253
- this.lineStartOffsets.push(lineStartOffset);
1254
- let trimmedWsLength = 0;
1255
- if (!considerWhitespaceChanges) {
1256
- const trimmedStartLine = line.trimStart();
1257
- trimmedWsLength = line.length - trimmedStartLine.length;
1258
- line = trimmedStartLine.trimEnd();
1259
- }
1260
- this.trimmedWsLengthsByLineIdx.push(trimmedWsLength);
1261
- const lineLength = lineNumber === this._range.endLineNumber ? Math.min(this._range.endColumn - 1 - lineStartOffset - trimmedWsLength, line.length) : line.length;
1262
- for (let i = 0; i < lineLength; i++) {
1263
- this.elements.push(line.charCodeAt(i));
1264
- }
1265
- if (lineNumber < this._range.endLineNumber) {
1266
- this.elements.push('\n'.charCodeAt(0));
1267
- this.firstElementOffsetByLineIdx.push(this.elements.length);
1268
- }
1269
- }
1270
- }
1271
- toString() {
1272
- return `Slice: "${this.text}"`;
1273
- }
1274
- get text() {
1275
- return this.getText(new OffsetRange(0, this.length));
1276
- }
1277
- getText(range) {
1278
- return this.elements.slice(range.start, range.endExclusive).map(e => String.fromCharCode(e)).join('');
1279
- }
1280
- getElement(offset) {
1281
- return this.elements[offset];
1282
- }
1283
- get length() {
1284
- return this.elements.length;
1285
- }
1286
- getBoundaryScore(length) {
1287
- const prevCategory = getCategory(length > 0 ? this.elements[length - 1] : -1);
1288
- const nextCategory = getCategory(length < this.elements.length ? this.elements[length] : -1);
1289
- if (prevCategory === 7 /* CharBoundaryCategory.LineBreakCR */ && nextCategory === 8 /* CharBoundaryCategory.LineBreakLF */) {
1290
- // don't break between \r and \n
1291
- return 0;
1292
- }
1293
- if (prevCategory === 8 /* CharBoundaryCategory.LineBreakLF */) {
1294
- // prefer the linebreak before the change
1295
- return 150;
1296
- }
1297
- let score = 0;
1298
- if (prevCategory !== nextCategory) {
1299
- score += 10;
1300
- if (prevCategory === 0 /* CharBoundaryCategory.WordLower */ && nextCategory === 1 /* CharBoundaryCategory.WordUpper */) {
1301
- score += 1;
1302
- }
1303
- }
1304
- score += getCategoryBoundaryScore(prevCategory);
1305
- score += getCategoryBoundaryScore(nextCategory);
1306
- return score;
1307
- }
1308
- translateOffset(offset, preference = 'right') {
1309
- const i = findLastIdxMonotonous(this.firstElementOffsetByLineIdx, (value) => value <= offset);
1310
- const lineOffset = offset - this.firstElementOffsetByLineIdx[i];
1311
- return new Position(this._range.startLineNumber + i, 1 + this.lineStartOffsets[i] + lineOffset + ((lineOffset === 0 && preference === 'left') ? 0 : this.trimmedWsLengthsByLineIdx[i]));
1312
- }
1313
- translateRange(range) {
1314
- const pos1 = this.translateOffset(range.start, 'right');
1315
- const pos2 = this.translateOffset(range.endExclusive, 'left');
1316
- if (pos2.isBefore(pos1)) {
1317
- return Range.fromPositions(pos2, pos2);
1318
- }
1319
- return Range.fromPositions(pos1, pos2);
1320
- }
1321
- /**
1322
- * Finds the word that contains the character at the given offset
1323
- */
1324
- findWordContaining(offset) {
1325
- if (offset < 0 || offset >= this.elements.length) {
1326
- return undefined;
1327
- }
1328
- if (!isWordChar(this.elements[offset])) {
1329
- return undefined;
1330
- }
1331
- // find start
1332
- let start = offset;
1333
- while (start > 0 && isWordChar(this.elements[start - 1])) {
1334
- start--;
1335
- }
1336
- // find end
1337
- let end = offset;
1338
- while (end < this.elements.length && isWordChar(this.elements[end])) {
1339
- end++;
1340
- }
1341
- return new OffsetRange(start, end);
1342
- }
1343
- /** fooBar has the two sub-words foo and bar */
1344
- findSubWordContaining(offset) {
1345
- if (offset < 0 || offset >= this.elements.length) {
1346
- return undefined;
1347
- }
1348
- if (!isWordChar(this.elements[offset])) {
1349
- return undefined;
1350
- }
1351
- // find start
1352
- let start = offset;
1353
- while (start > 0 && isWordChar(this.elements[start - 1]) && !isUpperCase(this.elements[start])) {
1354
- start--;
1355
- }
1356
- // find end
1357
- let end = offset;
1358
- while (end < this.elements.length && isWordChar(this.elements[end]) && !isUpperCase(this.elements[end])) {
1359
- end++;
1360
- }
1361
- return new OffsetRange(start, end);
1362
- }
1363
- countLinesIn(range) {
1364
- return this.translateOffset(range.endExclusive).lineNumber - this.translateOffset(range.start).lineNumber;
1365
- }
1366
- isStronglyEqual(offset1, offset2) {
1367
- return this.elements[offset1] === this.elements[offset2];
1368
- }
1369
- extendToFullLines(range) {
1370
- const start = findLastMonotonous(this.firstElementOffsetByLineIdx, x => x <= range.start) ?? 0;
1371
- const end = findFirstMonotonous(this.firstElementOffsetByLineIdx, x => range.endExclusive <= x) ?? this.elements.length;
1372
- return new OffsetRange(start, end);
1373
- }
1374
- }
1375
- function isWordChar(charCode) {
1376
- return charCode >= CharCode.a && charCode <= CharCode.z
1377
- || charCode >= CharCode.A && charCode <= CharCode.Z
1378
- || charCode >= CharCode.Digit0 && charCode <= CharCode.Digit9;
1379
- }
1380
- function isUpperCase(charCode) {
1381
- return charCode >= CharCode.A && charCode <= CharCode.Z;
1382
- }
1383
- const categoryScore = {
1384
- [0 /* CharBoundaryCategory.WordLower */]: 0,
1385
- [1 /* CharBoundaryCategory.WordUpper */]: 0,
1386
- [2 /* CharBoundaryCategory.WordNumber */]: 0,
1387
- [3 /* CharBoundaryCategory.End */]: 10,
1388
- [4 /* CharBoundaryCategory.Other */]: 2,
1389
- [5 /* CharBoundaryCategory.Separator */]: 30,
1390
- [6 /* CharBoundaryCategory.Space */]: 3,
1391
- [7 /* CharBoundaryCategory.LineBreakCR */]: 10,
1392
- [8 /* CharBoundaryCategory.LineBreakLF */]: 10,
1393
- };
1394
- function getCategoryBoundaryScore(category) {
1395
- return categoryScore[category];
1396
- }
1397
- function getCategory(charCode) {
1398
- if (charCode === CharCode.LineFeed) {
1399
- return 8 /* CharBoundaryCategory.LineBreakLF */;
1400
- }
1401
- else if (charCode === CharCode.CarriageReturn) {
1402
- return 7 /* CharBoundaryCategory.LineBreakCR */;
1403
- }
1404
- else if (isSpace(charCode)) {
1405
- return 6 /* CharBoundaryCategory.Space */;
1406
- }
1407
- else if (charCode >= CharCode.a && charCode <= CharCode.z) {
1408
- return 0 /* CharBoundaryCategory.WordLower */;
1409
- }
1410
- else if (charCode >= CharCode.A && charCode <= CharCode.Z) {
1411
- return 1 /* CharBoundaryCategory.WordUpper */;
1412
- }
1413
- else if (charCode >= CharCode.Digit0 && charCode <= CharCode.Digit9) {
1414
- return 2 /* CharBoundaryCategory.WordNumber */;
1415
- }
1416
- else if (charCode === -1) {
1417
- return 3 /* CharBoundaryCategory.End */;
1418
- }
1419
- else if (charCode === CharCode.Comma || charCode === CharCode.Semicolon) {
1420
- return 5 /* CharBoundaryCategory.Separator */;
1421
- }
1422
- else {
1423
- return 4 /* CharBoundaryCategory.Other */;
1424
- }
1425
- }
1426
- // Binary search helpers
1427
- function findLastIdxMonotonous(array, predicate) {
1428
- let low = 0;
1429
- let high = array.length - 1;
1430
- while (low <= high) {
1431
- const mid = Math.floor((low + high) / 2);
1432
- if (predicate(array[mid])) {
1433
- low = mid + 1;
1434
- }
1435
- else {
1436
- high = mid - 1;
1437
- }
1438
- }
1439
- return high;
1440
- }
1441
- function findLastMonotonous(array, predicate) {
1442
- const idx = findLastIdxMonotonous(array, predicate);
1443
- return idx >= 0 ? array[idx] : undefined;
1444
- }
1445
- function findFirstMonotonous(array, predicate) {
1446
- let low = 0;
1447
- let high = array.length - 1;
1448
- while (low <= high) {
1449
- const mid = Math.floor((low + high) / 2);
1450
- if (predicate(array[mid])) {
1451
- high = mid - 1;
1452
- }
1453
- else {
1454
- low = mid + 1;
1455
- }
1456
- }
1457
- return low < array.length ? array[low] : undefined;
1458
- }
1459
-
1460
- /**
1461
- * A range of lines (1-based, end is exclusive).
1462
- */
1463
- class LineRange {
1464
- startLineNumber;
1465
- endLineNumberExclusive;
1466
- constructor(startLineNumber, endLineNumberExclusive) {
1467
- this.startLineNumber = startLineNumber;
1468
- this.endLineNumberExclusive = endLineNumberExclusive;
1469
- if (startLineNumber > endLineNumberExclusive) {
1470
- throw new Error(`Invalid range: ${startLineNumber} > ${endLineNumberExclusive}`);
1471
- }
1472
- }
1473
- get isEmpty() {
1474
- return this.startLineNumber === this.endLineNumberExclusive;
1475
- }
1476
- get length() {
1477
- return this.endLineNumberExclusive - this.startLineNumber;
1478
- }
1479
- join(other) {
1480
- return new LineRange(Math.min(this.startLineNumber, other.startLineNumber), Math.max(this.endLineNumberExclusive, other.endLineNumberExclusive));
1481
- }
1482
- intersectsOrTouches(other) {
1483
- return this.startLineNumber <= other.endLineNumberExclusive
1484
- && other.startLineNumber <= this.endLineNumberExclusive;
1485
- }
1486
- toOffsetRange() {
1487
- return new OffsetRange(this.startLineNumber - 1, this.endLineNumberExclusive - 1);
1488
- }
1489
- toString() {
1490
- return `[${this.startLineNumber}, ${this.endLineNumberExclusive})`;
1491
- }
1492
- }
1493
- /**
1494
- * A mapping between two ranges.
1495
- */
1496
- class RangeMapping {
1497
- originalRange;
1498
- modifiedRange;
1499
- constructor(originalRange, modifiedRange) {
1500
- this.originalRange = originalRange;
1501
- this.modifiedRange = modifiedRange;
1502
- }
1503
- toString() {
1504
- return `${rangeToString(this.originalRange)} <-> ${rangeToString(this.modifiedRange)}`;
1505
- }
1506
- static assertSorted(rangeMappings) {
1507
- for (let i = 1; i < rangeMappings.length; i++) {
1508
- const prev = rangeMappings[i - 1];
1509
- const cur = rangeMappings[i];
1510
- if (!(prev.originalRange.endLineNumber <= cur.originalRange.startLineNumber ||
1511
- (prev.originalRange.endLineNumber === cur.originalRange.startLineNumber && prev.originalRange.endColumn <= cur.originalRange.startColumn))) {
1512
- throw new Error('RangeMappings must be sorted');
1513
- }
1514
- }
1515
- }
1516
- }
1517
- function rangeToString(range) {
1518
- return `(${range.startLineNumber}:${range.startColumn})-(${range.endLineNumber}:${range.endColumn})`;
1519
- }
1520
- /**
1521
- * A mapping between two line ranges.
1522
- */
1523
- class LineRangeMapping {
1524
- original;
1525
- modified;
1526
- constructor(original, modified) {
1527
- this.original = original;
1528
- this.modified = modified;
1529
- }
1530
- toString() {
1531
- return `${this.original} <-> ${this.modified}`;
1532
- }
1533
- /**
1534
- * This method assumes that the LineRangeMapping describes a valid diff!
1535
- * I.e. if one range is empty, the other range cannot be the entire document.
1536
- * It avoids various problems when the line range points to non-existing line-numbers.
1537
- */
1538
- toRangeMapping2(originalLines, modifiedLines) {
1539
- const isValidLineNumber = (lineNumber, lines) => {
1540
- return lineNumber >= 1 && lineNumber <= lines.length;
1541
- };
1542
- const normalizePosition = (lineNumber, column, lines) => {
1543
- if (lineNumber < 1) {
1544
- return new Position(1, 1);
1545
- }
1546
- if (lineNumber > lines.length) {
1547
- return new Position(lines.length, lines[lines.length - 1].length + 1);
1548
- }
1549
- const line = lines[lineNumber - 1];
1550
- if (column > line.length + 1) {
1551
- return new Position(lineNumber, line.length + 1);
1552
- }
1553
- return new Position(lineNumber, column);
1554
- };
1555
- if (isValidLineNumber(this.original.endLineNumberExclusive, originalLines)
1556
- && isValidLineNumber(this.modified.endLineNumberExclusive, modifiedLines)) {
1557
- return new RangeMapping(new Range(this.original.startLineNumber, 1, this.original.endLineNumberExclusive, 1), new Range(this.modified.startLineNumber, 1, this.modified.endLineNumberExclusive, 1));
1558
- }
1559
- if (!this.original.isEmpty && !this.modified.isEmpty) {
1560
- return new RangeMapping(Range.fromPositions(new Position(this.original.startLineNumber, 1), normalizePosition(this.original.endLineNumberExclusive - 1, Number.MAX_SAFE_INTEGER, originalLines)), Range.fromPositions(new Position(this.modified.startLineNumber, 1), normalizePosition(this.modified.endLineNumberExclusive - 1, Number.MAX_SAFE_INTEGER, modifiedLines)));
1561
- }
1562
- if (this.original.startLineNumber > 1 && this.modified.startLineNumber > 1) {
1563
- return new RangeMapping(Range.fromPositions(normalizePosition(this.original.startLineNumber - 1, Number.MAX_SAFE_INTEGER, originalLines), normalizePosition(this.original.endLineNumberExclusive - 1, Number.MAX_SAFE_INTEGER, originalLines)), Range.fromPositions(normalizePosition(this.modified.startLineNumber - 1, Number.MAX_SAFE_INTEGER, modifiedLines), normalizePosition(this.modified.endLineNumberExclusive - 1, Number.MAX_SAFE_INTEGER, modifiedLines)));
1564
- }
1565
- // Situation now: one range is empty and one range touches the last line and one range starts at line 1.
1566
- // This shouldn't happen for valid diffs.
1567
- throw new Error('Invalid diff: cannot convert to range mapping');
1568
- }
1569
- }
1570
- /**
1571
- * A detailed mapping between two line ranges, including inner changes.
1572
- */
1573
- class DetailedLineRangeMapping extends LineRangeMapping {
1574
- innerChanges;
1575
- constructor(original, modified, innerChanges) {
1576
- super(original, modified);
1577
- this.innerChanges = innerChanges;
1578
- }
1579
- }
1580
- /**
1581
- * Result of a lines diff computation.
1582
- */
1583
- class LinesDiff {
1584
- changes;
1585
- moves;
1586
- hitTimeout;
1587
- constructor(changes, moves, hitTimeout) {
1588
- this.changes = changes;
1589
- this.moves = moves;
1590
- this.hitTimeout = hitTimeout;
1591
- }
1592
- }
1593
- /**
1594
- * Helper to convert from range mappings to line range mappings.
1595
- */
1596
- function lineRangeMappingFromRangeMappings(alignments, originalLines, modifiedLines, dontAssertStartLine = false) {
1597
- const changes = [];
1598
- for (const g of groupAdjacentBy(alignments.map(a => getLineRangeMapping(a, originalLines, modifiedLines)), (a1, a2) => a1.original.intersectsOrTouches(a2.original)
1599
- || a1.modified.intersectsOrTouches(a2.modified))) {
1600
- const first = g[0];
1601
- const last = g[g.length - 1];
1602
- changes.push(new DetailedLineRangeMapping(first.original.join(last.original), first.modified.join(last.modified), g.map(a => a.innerChanges[0])));
1603
- }
1604
- if (!dontAssertStartLine) {
1605
- // Assert that the changes are sorted and non-overlapping (but don't throw - just log in debug)
1606
- for (let i = 1; i < changes.length; i++) {
1607
- const prev = changes[i - 1];
1608
- const cur = changes[i];
1609
- if (prev.original.endLineNumberExclusive > cur.original.startLineNumber ||
1610
- prev.modified.endLineNumberExclusive > cur.modified.startLineNumber) {
1611
- // Silently skip validation issue - may be expected in some edge cases
1612
- break;
1613
- }
1614
- }
1615
- }
1616
- return changes;
1617
- }
1618
- function getLineRangeMapping(rangeMapping, originalLines, modifiedLines) {
1619
- let lineStartDelta = 0;
1620
- let lineEndDelta = 0;
1621
- // rangeMapping describes the edit that replaces `rangeMapping.originalRange` with `newText := getText(modifiedLines, rangeMapping.modifiedRange)`.
1622
- // original: ]xxx \n <- this line is not modified
1623
- // modified: ]xx \n
1624
- if (rangeMapping.modifiedRange.endColumn === 1 && rangeMapping.originalRange.endColumn === 1
1625
- && rangeMapping.originalRange.startLineNumber + lineStartDelta <= rangeMapping.originalRange.endLineNumber
1626
- && rangeMapping.modifiedRange.startLineNumber + lineStartDelta <= rangeMapping.modifiedRange.endLineNumber) {
1627
- // We can only do this if the range is not empty yet
1628
- lineEndDelta = -1;
1629
- }
1630
- // original: xxx[ \n <- this line is not modified
1631
- // modified: xxx[ \n
1632
- if (rangeMapping.modifiedRange.startColumn - 1 >= (modifiedLines[rangeMapping.modifiedRange.startLineNumber - 1]?.length ?? 0)
1633
- && rangeMapping.originalRange.startColumn - 1 >= (originalLines[rangeMapping.originalRange.startLineNumber - 1]?.length ?? 0)
1634
- && rangeMapping.originalRange.startLineNumber <= rangeMapping.originalRange.endLineNumber + lineEndDelta
1635
- && rangeMapping.modifiedRange.startLineNumber <= rangeMapping.modifiedRange.endLineNumber + lineEndDelta) {
1636
- // We can only do this if the range is not empty yet
1637
- lineStartDelta = 1;
1638
- }
1639
- const originalLineRange = new LineRange(rangeMapping.originalRange.startLineNumber + lineStartDelta, rangeMapping.originalRange.endLineNumber + 1 + lineEndDelta);
1640
- const modifiedLineRange = new LineRange(rangeMapping.modifiedRange.startLineNumber + lineStartDelta, rangeMapping.modifiedRange.endLineNumber + 1 + lineEndDelta);
1641
- return new DetailedLineRangeMapping(originalLineRange, modifiedLineRange, [rangeMapping]);
1642
- }
1643
- function* groupAdjacentBy(items, shouldBeGrouped) {
1644
- let currentGroup;
1645
- let last;
1646
- for (const item of items) {
1647
- if (last !== undefined && shouldBeGrouped(last, item)) {
1648
- currentGroup.push(item);
1649
- }
1650
- else {
1651
- if (currentGroup) {
1652
- yield currentGroup;
1653
- }
1654
- currentGroup = [item];
1655
- }
1656
- last = item;
1657
- }
1658
- if (currentGroup) {
1659
- yield currentGroup;
1660
- }
1661
- }
1662
- class ArrayText {
1663
- lines;
1664
- constructor(lines) {
1665
- this.lines = lines;
1666
- }
1667
- get lineCount() {
1668
- return this.lines.length;
1669
- }
1670
- getLineContent(lineNumber) {
1671
- return this.lines[lineNumber - 1];
1672
- }
1673
- }
1674
-
1675
- /**
1676
- * The main diff computer that computes detailed line diffs.
1677
- */
1678
- class DefaultLinesDiffComputer {
1679
- _dynamicProgrammingDiffing = new DynamicProgrammingDiffing();
1680
- _myersDiffingAlgorithm = new MyersDiffAlgorithm();
1681
- computeDiff(originalLines, modifiedLines, options) {
1682
- // Handle identical single line
1683
- if (originalLines.length <= 1 && arraysEqual(originalLines, modifiedLines)) {
1684
- return new LinesDiff([], [], false);
1685
- }
1686
- // Handle empty single line - treat as full replacement
1687
- if (originalLines.length === 1 && originalLines[0].length === 0 || modifiedLines.length === 1 && modifiedLines[0].length === 0) {
1688
- return new LinesDiff([
1689
- new DetailedLineRangeMapping(new LineRange(1, originalLines.length + 1), new LineRange(1, modifiedLines.length + 1), [
1690
- new RangeMapping(new Range(1, 1, originalLines.length, originalLines[originalLines.length - 1].length + 1), new Range(1, 1, modifiedLines.length, modifiedLines[modifiedLines.length - 1].length + 1))
1691
- ])
1692
- ], [], false);
1693
- }
1694
- const timeout = options.maxComputationTimeMs === 0 ? InfiniteTimeout.instance : new DateTimeout(options.maxComputationTimeMs);
1695
- const considerWhitespaceChanges = !options.ignoreTrimWhitespace;
1696
- // Create perfect hash for lines (trimmed)
1697
- const perfectHashes = new Map();
1698
- function getOrCreateHash(text) {
1699
- let hash = perfectHashes.get(text);
1700
- if (hash === undefined) {
1701
- hash = perfectHashes.size;
1702
- perfectHashes.set(text, hash);
1703
- }
1704
- return hash;
1705
- }
1706
- const originalLinesHashes = originalLines.map((l) => getOrCreateHash(l.trim()));
1707
- const modifiedLinesHashes = modifiedLines.map((l) => getOrCreateHash(l.trim()));
1708
- const sequence1 = new LineSequence(originalLinesHashes, originalLines);
1709
- const sequence2 = new LineSequence(modifiedLinesHashes, modifiedLines);
1710
- // Choose algorithm based on size
1711
- const lineAlignmentResult = (() => {
1712
- if (sequence1.length + sequence2.length < 1700) {
1713
- // Use the improved algorithm for small files
1714
- return this._dynamicProgrammingDiffing.compute(sequence1, sequence2, timeout, (offset1, offset2) => originalLines[offset1] === modifiedLines[offset2]
1715
- ? modifiedLines[offset2].length === 0
1716
- ? 0.1
1717
- : 1 + Math.log(1 + modifiedLines[offset2].length)
1718
- : 0.99);
1719
- }
1720
- return this._myersDiffingAlgorithm.compute(sequence1, sequence2, timeout);
1721
- })();
1722
- let lineAlignments = lineAlignmentResult.diffs;
1723
- let hitTimeout = lineAlignmentResult.hitTimeout;
1724
- // Apply heuristics
1725
- lineAlignments = optimizeSequenceDiffs(sequence1, sequence2, lineAlignments);
1726
- lineAlignments = removeVeryShortMatchingLinesBetweenDiffs(sequence1, sequence2, lineAlignments);
1727
- const alignments = [];
1728
- const scanForWhitespaceChanges = (equalLinesCount) => {
1729
- if (!considerWhitespaceChanges) {
1730
- return;
1731
- }
1732
- for (let i = 0; i < equalLinesCount; i++) {
1733
- const seq1Offset = seq1LastStart + i;
1734
- const seq2Offset = seq2LastStart + i;
1735
- if (originalLines[seq1Offset] !== modifiedLines[seq2Offset]) {
1736
- // This is because of whitespace changes, diff these lines
1737
- const characterDiffs = this._refineDiff(originalLines, modifiedLines, new SequenceDiff(new OffsetRange(seq1Offset, seq1Offset + 1), new OffsetRange(seq2Offset, seq2Offset + 1)), timeout, considerWhitespaceChanges, options);
1738
- for (const a of characterDiffs.mappings) {
1739
- alignments.push(a);
1740
- }
1741
- if (characterDiffs.hitTimeout) {
1742
- hitTimeout = true;
1743
- }
1744
- }
1745
- }
1746
- };
1747
- let seq1LastStart = 0;
1748
- let seq2LastStart = 0;
1749
- for (const diff of lineAlignments) {
1750
- const equalLinesCount = diff.seq1Range.start - seq1LastStart;
1751
- scanForWhitespaceChanges(equalLinesCount);
1752
- seq1LastStart = diff.seq1Range.endExclusive;
1753
- seq2LastStart = diff.seq2Range.endExclusive;
1754
- const characterDiffs = this._refineDiff(originalLines, modifiedLines, diff, timeout, considerWhitespaceChanges, options);
1755
- if (characterDiffs.hitTimeout) {
1756
- hitTimeout = true;
1757
- }
1758
- for (const a of characterDiffs.mappings) {
1759
- alignments.push(a);
1760
- }
1761
- }
1762
- scanForWhitespaceChanges(originalLines.length - seq1LastStart);
1763
- const original = new ArrayText(originalLines);
1764
- const modified = new ArrayText(modifiedLines);
1765
- const changes = lineRangeMappingFromRangeMappings(alignments, original.lines, modified.lines);
1766
- // TODO: Compute moves if options.computeMoves is true
1767
- const moves = [];
1768
- return new LinesDiff(changes, moves, hitTimeout);
1769
- }
1770
- _refineDiff(originalLines, modifiedLines, diff, timeout, considerWhitespaceChanges, options) {
1771
- const lineRangeMapping = toLineRangeMapping(diff);
1772
- const rangeMapping = lineRangeMapping.toRangeMapping2(originalLines, modifiedLines);
1773
- const slice1 = new LinesSliceCharSequence(originalLines, rangeMapping.originalRange, considerWhitespaceChanges);
1774
- const slice2 = new LinesSliceCharSequence(modifiedLines, rangeMapping.modifiedRange, considerWhitespaceChanges);
1775
- const diffResult = slice1.length + slice2.length < 500
1776
- ? this._dynamicProgrammingDiffing.compute(slice1, slice2, timeout)
1777
- : this._myersDiffingAlgorithm.compute(slice1, slice2, timeout);
1778
- let diffs = diffResult.diffs;
1779
- diffs = optimizeSequenceDiffs(slice1, slice2, diffs);
1780
- diffs = extendDiffsToEntireWordIfAppropriate(slice1, slice2, diffs, (seq, idx) => seq.findWordContaining(idx));
1781
- if (options.extendToSubwords) {
1782
- diffs = extendDiffsToEntireWordIfAppropriate(slice1, slice2, diffs, (seq, idx) => seq.findSubWordContaining(idx), true);
1783
- }
1784
- diffs = removeShortMatches(slice1, slice2, diffs);
1785
- diffs = removeVeryShortMatchingTextBetweenLongDiffs(slice1, slice2, diffs);
1786
- const result = diffs.map((d) => new RangeMapping(slice1.translateRange(d.seq1Range), slice2.translateRange(d.seq2Range)));
1787
- return {
1788
- mappings: result,
1789
- hitTimeout: diffResult.hitTimeout,
1790
- };
1791
- }
1792
- }
1793
- function toLineRangeMapping(sequenceDiff) {
1794
- return new LineRangeMapping(new LineRange(sequenceDiff.seq1Range.start + 1, sequenceDiff.seq1Range.endExclusive + 1), new LineRange(sequenceDiff.seq2Range.start + 1, sequenceDiff.seq2Range.endExclusive + 1));
1795
- }
1796
- function arraysEqual(a, b) {
1797
- if (a.length !== b.length) {
1798
- return false;
1799
- }
1800
- for (let i = 0; i < a.length; i++) {
1801
- if (a[i] !== b[i]) {
1802
- return false;
1803
- }
1804
- }
1805
- return true;
1806
- }
1807
-
1808
- /* @ts-self-types="./diff_wasm.d.ts" */
1809
-
1810
- /**
1811
- * Compute a diff between two texts.
1812
- *
1813
- * # Arguments
1814
- * * `original` - The original text (lines separated by newlines)
1815
- * * `modified` - The modified text (lines separated by newlines)
1816
- * * `options` - Optional diff options (as a JavaScript object)
1817
- *
1818
- * # Returns
1819
- * A `WasmLinesDiff` object containing the changes.
1820
- * @param {string} original
1821
- * @param {string} modified
1822
- * @param {any} options
1823
- * @returns {any}
1824
- */
1825
- function computeDiff(original, modified, options) {
1826
- const ptr0 = passStringToWasm0(original, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1827
- const len0 = WASM_VECTOR_LEN;
1828
- const ptr1 = passStringToWasm0(modified, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1829
- const len1 = WASM_VECTOR_LEN;
1830
- const ret = wasm.computeDiff(ptr0, len0, ptr1, len1, options);
1831
- if (ret[2]) {
1832
- throw takeFromExternrefTable0(ret[1]);
1833
- }
1834
- return takeFromExternrefTable0(ret[0]);
1835
- }
1836
-
1837
- /**
1838
- * Compute a diff between two arrays of lines.
1839
- *
1840
- * # Arguments
1841
- * * `original_lines` - The original lines (as a JavaScript array of strings)
1842
- * * `modified_lines` - The modified lines (as a JavaScript array of strings)
1843
- * * `options` - Optional diff options (as a JavaScript object)
1844
- *
1845
- * # Returns
1846
- * A `WasmLinesDiff` object containing the changes.
1847
- * @param {any} original_lines
1848
- * @param {any} modified_lines
1849
- * @param {any} options
1850
- * @returns {any}
1851
- */
1852
- function computeDiffLines(original_lines, modified_lines, options) {
1853
- const ret = wasm.computeDiffLines(original_lines, modified_lines, options);
1854
- if (ret[2]) {
1855
- throw takeFromExternrefTable0(ret[1]);
1856
- }
1857
- return takeFromExternrefTable0(ret[0]);
1858
- }
1859
-
1860
- /**
1861
- * Initialize panic hook for better error messages in the browser console.
1862
- */
1863
- function init() {
1864
- wasm.init();
1865
- }
1866
- function __wbg_get_imports() {
1867
- const import0 = {
1868
- __proto__: null,
1869
- __wbg_Error_bce6d499ff0a4aff: function(arg0, arg1) {
1870
- const ret = Error(getStringFromWasm0(arg0, arg1));
1871
- return ret;
1872
- },
1873
- __wbg_Number_b7972a139bfbfdf0: function(arg0) {
1874
- const ret = Number(arg0);
1875
- return ret;
1876
- },
1877
- __wbg_String_8564e559799eccda: function(arg0, arg1) {
1878
- const ret = String(arg1);
1879
- const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1880
- const len1 = WASM_VECTOR_LEN;
1881
- getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
1882
- getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
1883
- },
1884
- __wbg___wbindgen_bigint_get_as_i64_410e28c7b761ad83: function(arg0, arg1) {
1885
- const v = arg1;
1886
- const ret = typeof(v) === 'bigint' ? v : undefined;
1887
- getDataViewMemory0().setBigInt64(arg0 + 8 * 1, isLikeNone(ret) ? BigInt(0) : ret, true);
1888
- getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true);
1889
- },
1890
- __wbg___wbindgen_boolean_get_2304fb8c853028c8: function(arg0) {
1891
- const v = arg0;
1892
- const ret = typeof(v) === 'boolean' ? v : undefined;
1893
- return isLikeNone(ret) ? 0xFFFFFF : ret ? 1 : 0;
1894
- },
1895
- __wbg___wbindgen_debug_string_edece8177ad01481: function(arg0, arg1) {
1896
- const ret = debugString(arg1);
1897
- const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1898
- const len1 = WASM_VECTOR_LEN;
1899
- getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
1900
- getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
1901
- },
1902
- __wbg___wbindgen_in_07056af4f902c445: function(arg0, arg1) {
1903
- const ret = arg0 in arg1;
1904
- return ret;
1905
- },
1906
- __wbg___wbindgen_is_bigint_aeae3893f30ed54e: function(arg0) {
1907
- const ret = typeof(arg0) === 'bigint';
1908
- return ret;
1909
- },
1910
- __wbg___wbindgen_is_function_5cd60d5cf78b4eef: function(arg0) {
1911
- const ret = typeof(arg0) === 'function';
1912
- return ret;
1913
- },
1914
- __wbg___wbindgen_is_null_2042690d351e14f0: function(arg0) {
1915
- const ret = arg0 === null;
1916
- return ret;
1917
- },
1918
- __wbg___wbindgen_is_object_b4593df85baada48: function(arg0) {
1919
- const val = arg0;
1920
- const ret = typeof(val) === 'object' && val !== null;
1921
- return ret;
1922
- },
1923
- __wbg___wbindgen_is_undefined_35bb9f4c7fd651d5: function(arg0) {
1924
- const ret = arg0 === undefined;
1925
- return ret;
1926
- },
1927
- __wbg___wbindgen_jsval_eq_c0ed08b3e0f393b9: function(arg0, arg1) {
1928
- const ret = arg0 === arg1;
1929
- return ret;
1930
- },
1931
- __wbg___wbindgen_jsval_loose_eq_0ad77b7717db155c: function(arg0, arg1) {
1932
- const ret = arg0 == arg1;
1933
- return ret;
1934
- },
1935
- __wbg___wbindgen_number_get_f73a1244370fcc2c: function(arg0, arg1) {
1936
- const obj = arg1;
1937
- const ret = typeof(obj) === 'number' ? obj : undefined;
1938
- getDataViewMemory0().setFloat64(arg0 + 8 * 1, isLikeNone(ret) ? 0 : ret, true);
1939
- getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true);
1940
- },
1941
- __wbg___wbindgen_string_get_d109740c0d18f4d7: function(arg0, arg1) {
1942
- const obj = arg1;
1943
- const ret = typeof(obj) === 'string' ? obj : undefined;
1944
- var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1945
- var len1 = WASM_VECTOR_LEN;
1946
- getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
1947
- getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
1948
- },
1949
- __wbg___wbindgen_throw_9c31b086c2b26051: function(arg0, arg1) {
1950
- throw new Error(getStringFromWasm0(arg0, arg1));
1951
- },
1952
- __wbg_call_13665d9f14390edc: function() { return handleError(function (arg0, arg1) {
1953
- const ret = arg0.call(arg1);
1954
- return ret;
1955
- }, arguments); },
1956
- __wbg_done_54b8da57023b7ed2: function(arg0) {
1957
- const ret = arg0.done;
1958
- return ret;
1959
- },
1960
- __wbg_error_a6fa202b58aa1cd3: function(arg0, arg1) {
1961
- let deferred0_0;
1962
- let deferred0_1;
1963
- try {
1964
- deferred0_0 = arg0;
1965
- deferred0_1 = arg1;
1966
- console.error(getStringFromWasm0(arg0, arg1));
1967
- } finally {
1968
- wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
1969
- }
1970
- },
1971
- __wbg_get_3e9a707ab7d352eb: function() { return handleError(function (arg0, arg1) {
1972
- const ret = Reflect.get(arg0, arg1);
1973
- return ret;
1974
- }, arguments); },
1975
- __wbg_get_unchecked_1dfe6d05ad91d9b7: function(arg0, arg1) {
1976
- const ret = arg0[arg1 >>> 0];
1977
- return ret;
1978
- },
1979
- __wbg_get_with_ref_key_6412cf3094599694: function(arg0, arg1) {
1980
- const ret = arg0[arg1];
1981
- return ret;
1982
- },
1983
- __wbg_instanceof_ArrayBuffer_53db37b06f6b9afe: function(arg0) {
1984
- let result;
1985
- try {
1986
- result = arg0 instanceof ArrayBuffer;
1987
- } catch (_) {
1988
- result = false;
1989
- }
1990
- const ret = result;
1991
- return ret;
1992
- },
1993
- __wbg_instanceof_Uint8Array_abd07d4bd221d50b: function(arg0) {
1994
- let result;
1995
- try {
1996
- result = arg0 instanceof Uint8Array;
1997
- } catch (_) {
1998
- result = false;
1999
- }
2000
- const ret = result;
2001
- return ret;
2002
- },
2003
- __wbg_isArray_94898ed3aad6947b: function(arg0) {
2004
- const ret = Array.isArray(arg0);
2005
- return ret;
2006
- },
2007
- __wbg_isSafeInteger_01e964d144ad3a55: function(arg0) {
2008
- const ret = Number.isSafeInteger(arg0);
2009
- return ret;
2010
- },
2011
- __wbg_iterator_1441b47f341dc34f: function() {
2012
- const ret = Symbol.iterator;
2013
- return ret;
2014
- },
2015
- __wbg_length_2591a0f4f659a55c: function(arg0) {
2016
- const ret = arg0.length;
2017
- return ret;
2018
- },
2019
- __wbg_length_56fcd3e2b7e0299d: function(arg0) {
2020
- const ret = arg0.length;
2021
- return ret;
2022
- },
2023
- __wbg_new_02d162bc6cf02f60: function() {
2024
- const ret = new Object();
2025
- return ret;
2026
- },
2027
- __wbg_new_227d7c05414eb861: function() {
2028
- const ret = new Error();
2029
- return ret;
2030
- },
2031
- __wbg_new_310879b66b6e95e1: function() {
2032
- const ret = new Array();
2033
- return ret;
2034
- },
2035
- __wbg_new_7ddec6de44ff8f5d: function(arg0) {
2036
- const ret = new Uint8Array(arg0);
2037
- return ret;
2038
- },
2039
- __wbg_next_2a4e19f4f5083b0f: function(arg0) {
2040
- const ret = arg0.next;
2041
- return ret;
2042
- },
2043
- __wbg_next_6429a146bf756f93: function() { return handleError(function (arg0) {
2044
- const ret = arg0.next();
2045
- return ret;
2046
- }, arguments); },
2047
- __wbg_prototypesetcall_5f9bdc8d75e07276: function(arg0, arg1, arg2) {
2048
- Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
2049
- },
2050
- __wbg_set_6be42768c690e380: function(arg0, arg1, arg2) {
2051
- arg0[arg1] = arg2;
2052
- },
2053
- __wbg_set_78ea6a19f4818587: function(arg0, arg1, arg2) {
2054
- arg0[arg1 >>> 0] = arg2;
2055
- },
2056
- __wbg_stack_3b0d974bbf31e44f: function(arg0, arg1) {
2057
- const ret = arg1.stack;
2058
- const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
2059
- const len1 = WASM_VECTOR_LEN;
2060
- getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
2061
- getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
2062
- },
2063
- __wbg_value_9cc0518af87a489c: function(arg0) {
2064
- const ret = arg0.value;
2065
- return ret;
2066
- },
2067
- __wbindgen_cast_0000000000000001: function(arg0) {
2068
- // Cast intrinsic for `F64 -> Externref`.
2069
- const ret = arg0;
2070
- return ret;
2071
- },
2072
- __wbindgen_cast_0000000000000002: function(arg0, arg1) {
2073
- // Cast intrinsic for `Ref(String) -> Externref`.
2074
- const ret = getStringFromWasm0(arg0, arg1);
2075
- return ret;
2076
- },
2077
- __wbindgen_cast_0000000000000003: function(arg0) {
2078
- // Cast intrinsic for `U64 -> Externref`.
2079
- const ret = BigInt.asUintN(64, arg0);
2080
- return ret;
2081
- },
2082
- __wbindgen_init_externref_table: function() {
2083
- const table = wasm.__wbindgen_externrefs;
2084
- const offset = table.grow(4);
2085
- table.set(0, undefined);
2086
- table.set(offset + 0, undefined);
2087
- table.set(offset + 1, null);
2088
- table.set(offset + 2, true);
2089
- table.set(offset + 3, false);
2090
- },
2091
- };
2092
- return {
2093
- __proto__: null,
2094
- "./diff_wasm_bg.js": import0,
2095
- };
2096
- }
2097
-
2098
- function addToExternrefTable0(obj) {
2099
- const idx = wasm.__externref_table_alloc();
2100
- wasm.__wbindgen_externrefs.set(idx, obj);
2101
- return idx;
2102
- }
2103
-
2104
- function debugString(val) {
2105
- // primitive types
2106
- const type = typeof val;
2107
- if (type == 'number' || type == 'boolean' || val == null) {
2108
- return `${val}`;
2109
584
  }
2110
- if (type == 'string') {
2111
- return `"${val}"`;
2112
- }
2113
- if (type == 'symbol') {
2114
- const description = val.description;
2115
- if (description == null) {
2116
- return 'Symbol';
2117
- } else {
2118
- return `Symbol(${description})`;
2119
- }
2120
- }
2121
- if (type == 'function') {
2122
- const name = val.name;
2123
- if (typeof name == 'string' && name.length > 0) {
2124
- return `Function(${name})`;
2125
- } else {
2126
- return 'Function';
2127
- }
2128
- }
2129
- // objects
2130
- if (Array.isArray(val)) {
2131
- const length = val.length;
2132
- let debug = '[';
2133
- if (length > 0) {
2134
- debug += debugString(val[0]);
2135
- }
2136
- for(let i = 1; i < length; i++) {
2137
- debug += ', ' + debugString(val[i]);
2138
- }
2139
- debug += ']';
2140
- return debug;
2141
- }
2142
- // Test for built-in
2143
- const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
2144
- let className;
2145
- if (builtInMatches && builtInMatches.length > 1) {
2146
- className = builtInMatches[1];
2147
- } else {
2148
- // Failed to match the standard '[object ClassName]'
2149
- return toString.call(val);
2150
- }
2151
- if (className == 'Object') {
2152
- // we're a user defined class or Object
2153
- // JSON.stringify avoids problems with cycles, and is generally much
2154
- // easier than looping through ownProperties of `val`.
2155
- try {
2156
- return 'Object(' + JSON.stringify(val) + ')';
2157
- } catch (_) {
2158
- return 'Object';
2159
- }
2160
- }
2161
- // errors
2162
- if (val instanceof Error) {
2163
- return `${val.name}: ${val.message}\n${val.stack}`;
2164
- }
2165
- // TODO we could test for more things here, like `Set`s and `Map`s.
2166
- return className;
2167
- }
2168
-
2169
- function getArrayU8FromWasm0(ptr, len) {
2170
- ptr = ptr >>> 0;
2171
- return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
2172
- }
2173
-
2174
- let cachedDataViewMemory0 = null;
2175
- function getDataViewMemory0() {
2176
- if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
2177
- cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
2178
- }
2179
- return cachedDataViewMemory0;
2180
- }
2181
-
2182
- function getStringFromWasm0(ptr, len) {
2183
- return decodeText(ptr >>> 0, len);
2184
- }
2185
-
2186
- let cachedUint8ArrayMemory0 = null;
2187
- function getUint8ArrayMemory0() {
2188
- if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
2189
- cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
2190
- }
2191
- return cachedUint8ArrayMemory0;
2192
- }
2193
-
2194
- function handleError(f, args) {
2195
- try {
2196
- return f.apply(this, args);
2197
- } catch (e) {
2198
- const idx = addToExternrefTable0(e);
2199
- wasm.__wbindgen_exn_store(idx);
2200
- }
2201
- }
2202
-
2203
- function isLikeNone(x) {
2204
- return x === undefined || x === null;
2205
- }
2206
-
2207
- function passStringToWasm0(arg, malloc, realloc) {
2208
- if (realloc === undefined) {
2209
- const buf = cachedTextEncoder.encode(arg);
2210
- const ptr = malloc(buf.length, 1) >>> 0;
2211
- getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
2212
- WASM_VECTOR_LEN = buf.length;
2213
- return ptr;
2214
- }
2215
-
2216
- let len = arg.length;
2217
- let ptr = malloc(len, 1) >>> 0;
2218
-
2219
- const mem = getUint8ArrayMemory0();
2220
-
2221
- let offset = 0;
2222
-
2223
- for (; offset < len; offset++) {
2224
- const code = arg.charCodeAt(offset);
2225
- if (code > 0x7F) break;
2226
- mem[ptr + offset] = code;
2227
- }
2228
- if (offset !== len) {
2229
- if (offset !== 0) {
2230
- arg = arg.slice(offset);
2231
- }
2232
- ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
2233
- const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
2234
- const ret = cachedTextEncoder.encodeInto(arg, view);
2235
-
2236
- offset += ret.written;
2237
- ptr = realloc(ptr, len, offset, 1) >>> 0;
2238
- }
2239
-
2240
- WASM_VECTOR_LEN = offset;
2241
- return ptr;
2242
- }
2243
-
2244
- function takeFromExternrefTable0(idx) {
2245
- const value = wasm.__wbindgen_externrefs.get(idx);
2246
- wasm.__externref_table_dealloc(idx);
2247
- return value;
2248
- }
2249
-
2250
- let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
2251
- cachedTextDecoder.decode();
2252
- const MAX_SAFARI_DECODE_BYTES = 2146435072;
2253
- let numBytesDecoded = 0;
2254
- function decodeText(ptr, len) {
2255
- numBytesDecoded += len;
2256
- if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
2257
- cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
2258
- cachedTextDecoder.decode();
2259
- numBytesDecoded = len;
2260
- }
2261
- return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
2262
- }
2263
-
2264
- const cachedTextEncoder = new TextEncoder();
2265
-
2266
- if (!('encodeInto' in cachedTextEncoder)) {
2267
- cachedTextEncoder.encodeInto = function (arg, view) {
2268
- const buf = cachedTextEncoder.encode(arg);
2269
- view.set(buf);
2270
- return {
2271
- read: arg.length,
2272
- written: buf.length
2273
- };
2274
- };
2275
- }
2276
-
2277
- let WASM_VECTOR_LEN = 0;
2278
-
2279
- let wasm;
2280
- function __wbg_finalize_init(instance, module) {
2281
- wasm = instance.exports;
2282
- cachedDataViewMemory0 = null;
2283
- cachedUint8ArrayMemory0 = null;
2284
- wasm.__wbindgen_start();
2285
- return wasm;
2286
- }
2287
-
2288
- async function __wbg_load(module, imports) {
2289
- if (typeof Response === 'function' && module instanceof Response) {
2290
- if (typeof WebAssembly.instantiateStreaming === 'function') {
2291
- try {
2292
- return await WebAssembly.instantiateStreaming(module, imports);
2293
- } catch (e) {
2294
- const validResponse = module.ok && expectedResponseType(module.type);
2295
-
2296
- if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') {
2297
- console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
2298
-
2299
- } else { throw e; }
2300
- }
2301
- }
2302
-
2303
- const bytes = await module.arrayBuffer();
2304
- return await WebAssembly.instantiate(bytes, imports);
2305
- } else {
2306
- const instance = await WebAssembly.instantiate(module, imports);
2307
-
2308
- if (instance instanceof WebAssembly.Instance) {
2309
- return { instance, module };
2310
- } else {
2311
- return instance;
2312
- }
2313
- }
2314
-
2315
- function expectedResponseType(type) {
2316
- switch (type) {
2317
- case 'basic': case 'cors': case 'default': return true;
2318
- }
2319
- return false;
2320
- }
2321
- }
2322
-
2323
- function initSync(module) {
2324
- if (wasm !== undefined) return wasm;
2325
-
2326
-
2327
- if (module !== undefined) {
2328
- if (Object.getPrototypeOf(module) === Object.prototype) {
2329
- ({module} = module);
2330
- } else {
2331
- console.warn('using deprecated parameters for `initSync()`; pass a single object instead');
2332
- }
2333
- }
2334
-
2335
- const imports = __wbg_get_imports();
2336
- if (!(module instanceof WebAssembly.Module)) {
2337
- module = new WebAssembly.Module(module);
2338
- }
2339
- const instance = new WebAssembly.Instance(module, imports);
2340
- return __wbg_finalize_init(instance);
2341
- }
2342
-
2343
- async function __wbg_init(module_or_path) {
2344
- if (wasm !== undefined) return wasm;
2345
-
2346
-
2347
- if (module_or_path !== undefined) {
2348
- if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
2349
- ({module_or_path} = module_or_path);
2350
- } else {
2351
- console.warn('using deprecated parameters for the initialization function; pass a single object instead');
2352
- }
2353
- }
2354
-
2355
- if (module_or_path === undefined) {
2356
- module_or_path = new URL(new URL('diff_wasm_bg-BNcsPO7p.wasm', import.meta.url).href);
2357
- }
2358
- const imports = __wbg_get_imports();
2359
-
2360
- if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
2361
- module_or_path = fetch(module_or_path);
2362
- }
2363
-
2364
- const { instance, module } = await __wbg_load(await module_or_path, imports);
2365
-
2366
- return __wbg_finalize_init(instance);
2367
- }
2368
-
2369
- var wasm$1 = /*#__PURE__*/Object.freeze({
2370
- __proto__: null,
2371
- computeDiff: computeDiff,
2372
- computeDiffLines: computeDiffLines,
2373
- default: __wbg_init,
2374
- init: init,
2375
- initSync: initSync
2376
- });
2377
-
2378
- /**
2379
- * WASM module loader.
2380
- *
2381
- * Handles async initialization of the WASM module.
2382
- */
2383
- // @ts-ignore -- generated at build time by `npm run build:wasm` (wasm-pack --target web).
2384
- let wasmModule = null;
2385
- let initPromise = null;
2386
- /**
2387
- * Initialize the WASM module.
2388
- *
2389
- * This must be called before using any WASM-based diff functions.
2390
- * It's safe to call multiple times — subsequent calls are no-ops.
2391
- *
2392
- * @returns Promise that resolves when WASM is ready
2393
- */
2394
- async function initWasm() {
2395
- if (wasmModule) {
2396
- return wasmModule;
2397
- }
2398
- if (initPromise) {
2399
- return initPromise;
2400
- }
2401
- initPromise = (async () => {
2402
- const wasmUrl = new URL(new URL('diff_wasm_bg-BNcsPO7p.wasm', import.meta.url).href);
2403
- // Node's undici fetch does not support file:// URLs, so read bytes ourselves.
2404
- let source = wasmUrl;
2405
- if (typeof process !== 'undefined' && process.versions?.node) {
2406
- const { readFile } = await import('node:fs/promises');
2407
- source = await readFile(wasmUrl);
2408
- }
2409
- await __wbg_init({ module_or_path: source });
2410
- init();
2411
- wasmModule = wasm$1;
2412
- return wasmModule;
2413
- })();
2414
- return initPromise;
2415
- }
2416
- /**
2417
- * Get the WASM module synchronously.
2418
- *
2419
- * @throws Error if WASM hasn't been initialized yet
2420
- * @returns The WASM module
2421
- */
2422
- function getWasm() {
2423
- if (!wasmModule) {
2424
- throw new Error('WASM not initialized. Call initWasm() first and await it.');
2425
- }
2426
- return wasmModule;
2427
- }
2428
-
2429
- async function createDiffComputer(options = {}) {
2430
- if (options.useWasm) {
2431
- await initWasm();
2432
- return new _WasmDiffComputer(options.algorithm);
2433
- }
2434
- return new _TsDiffComputer(options.algorithm);
2435
- }
2436
- // ============================================================================
2437
- // TS-backed computer
2438
- // ============================================================================
2439
- class _TsDiffComputer {
2440
- _algorithm;
2441
- _impl = new DefaultLinesDiffComputer();
2442
- constructor(_algorithm) {
2443
- this._algorithm = _algorithm;
2444
- }
2445
- computeDiff(original, modified, options) {
2446
- const originalLines = original.split('\n');
2447
- const modifiedLines = modified.split('\n');
2448
- const internal = {
2449
- maxComputationTimeMs: options?.maxComputationTimeMs ?? 0,
2450
- ignoreTrimWhitespace: options?.ignoreTrimWhitespace ?? false,
2451
- computeMoves: options?.computeMoves ?? false,
2452
- extendToSubwords: options?.extendToSubwords ?? false,
2453
- };
2454
- const linesDiff = this._impl.computeDiff(originalLines, modifiedLines, internal);
2455
- void this._algorithm; // currently a hint; auto-selection lives inside DefaultLinesDiffComputer
2456
- return _adaptLinesDiff(linesDiff, original, modified, originalLines, modifiedLines);
2457
- }
2458
- }
2459
- // ============================================================================
2460
- // WASM-backed computer
2461
- // ============================================================================
2462
- class _WasmDiffComputer {
2463
- _algorithm;
2464
- constructor(_algorithm) {
2465
- this._algorithm = _algorithm;
2466
- }
2467
- computeDiff(original, modified, options) {
2468
- const originalLines = original.split('\n');
2469
- const modifiedLines = modified.split('\n');
2470
- const wasmOptions = {};
2471
- if (options?.maxComputationTimeMs !== undefined)
2472
- wasmOptions.maxComputationTimeMs = options.maxComputationTimeMs;
2473
- if (options?.ignoreTrimWhitespace !== undefined)
2474
- wasmOptions.ignoreTrimWhitespace = options.ignoreTrimWhitespace;
2475
- if (options?.computeMoves !== undefined)
2476
- wasmOptions.computeMoves = options.computeMoves;
2477
- if (options?.extendToSubwords !== undefined)
2478
- wasmOptions.extendToSubwords = options.extendToSubwords;
2479
- const wasm = getWasm();
2480
- const wasmResult = wasm.computeDiffLines(originalLines, modifiedLines, wasmOptions);
2481
- void this._algorithm;
2482
- const linesDiff = _wasmToLinesDiff(wasmResult);
2483
- return _adaptLinesDiff(linesDiff, original, modified, originalLines, modifiedLines);
2484
- }
2485
- }
2486
- function _wasmToLinesDiff(wasmResult) {
2487
- const changes = wasmResult.changes.map((c) => {
2488
- const inner = c.innerChanges?.map((ic) => new RangeMapping(new Range(ic.originalStartLine, ic.originalStartColumn, ic.originalEndLine, ic.originalEndColumn), new Range(ic.modifiedStartLine, ic.modifiedStartColumn, ic.modifiedEndLine, ic.modifiedEndColumn)));
2489
- return new DetailedLineRangeMapping(new LineRange(c.original.startLineNumber, c.original.endLineNumberExclusive), new LineRange(c.modified.startLineNumber, c.modified.endLineNumberExclusive), inner);
2490
- });
2491
- return new LinesDiff(changes, [], wasmResult.hitTimeout);
2492
- }
2493
- // ============================================================================
2494
- // LinesDiff -> DiffResult adapter
2495
- // ============================================================================
2496
- function _adaptLinesDiff(linesDiff, original, modified, originalLines, modifiedLines) {
2497
- const originalLineStarts = _computeLineStarts(originalLines);
2498
- const modifiedLineStarts = _computeLineStarts(modifiedLines);
2499
- const replacements = [];
2500
- for (const change of linesDiff.changes) {
2501
- _appendChangeReplacements(change, original, modified, originalLines, modifiedLines, originalLineStarts, modifiedLineStarts, replacements);
2502
- }
2503
- const edits = AnnotatedStringEdit.of(replacements);
2504
- const moves = linesDiff.moves.map(m => _adaptMove(m, original, modified, originalLines, modifiedLines, originalLineStarts, modifiedLineStarts));
2505
- return new DiffResult(edits, moves, linesDiff.hitTimeout);
2506
- }
2507
- function _appendChangeReplacements(change, original, modified, originalLines, modifiedLines, originalLineStarts, modifiedLineStarts, out) {
2508
- if (change.innerChanges && change.innerChanges.length > 0) {
2509
- for (const ic of change.innerChanges) {
2510
- const origRange = _rangeToOffsetRange(ic.originalRange, original, originalLines, originalLineStarts);
2511
- const modRange = _rangeToOffsetRange(ic.modifiedRange, modified, modifiedLines, modifiedLineStarts);
2512
- const newText = modified.substring(modRange.start, modRange.endExclusive);
2513
- out.push(new AnnotatedStringReplacement(origRange, newText, DiffAnnotation.change));
2514
- }
2515
- return;
2516
- }
2517
- // Fallback: no inner detail — replace the whole line range.
2518
- const origRange = _lineRangeToOffsetRange(change.original.startLineNumber, change.original.endLineNumberExclusive, original, originalLineStarts);
2519
- const modRange = _lineRangeToOffsetRange(change.modified.startLineNumber, change.modified.endLineNumberExclusive, modified, modifiedLineStarts);
2520
- const newText = modified.substring(modRange.start, modRange.endExclusive);
2521
- out.push(new AnnotatedStringReplacement(origRange, newText, DiffAnnotation.change));
2522
- }
2523
- function _adaptMove(move, original, modified, originalLines, modifiedLines, originalLineStarts, modifiedLineStarts) {
2524
- const origRange = _lineRangeToOffsetRange(move.lineRangeMapping.original.startLineNumber, move.lineRangeMapping.original.endLineNumberExclusive, original, originalLineStarts);
2525
- const modRange = _lineRangeToOffsetRange(move.lineRangeMapping.modified.startLineNumber, move.lineRangeMapping.modified.endLineNumberExclusive, modified, modifiedLineStarts);
2526
- // Build inner edit with offsets local to the moved slices.
2527
- const inner = [];
2528
- for (const change of move.changes) {
2529
- if (change.innerChanges && change.innerChanges.length > 0) {
2530
- for (const ic of change.innerChanges) {
2531
- const globalOrig = _rangeToOffsetRange(ic.originalRange, original, originalLines, originalLineStarts);
2532
- const globalMod = _rangeToOffsetRange(ic.modifiedRange, modified, modifiedLines, modifiedLineStarts);
2533
- inner.push(new StringReplacement(new OffsetRange(globalOrig.start - origRange.start, globalOrig.endExclusive - origRange.start), modified.substring(globalMod.start, globalMod.endExclusive)));
2534
- }
2535
- }
2536
- else {
2537
- const globalOrig = _lineRangeToOffsetRange(change.original.startLineNumber, change.original.endLineNumberExclusive, original, originalLineStarts);
2538
- const globalMod = _lineRangeToOffsetRange(change.modified.startLineNumber, change.modified.endLineNumberExclusive, modified, modifiedLineStarts);
2539
- inner.push(new StringReplacement(new OffsetRange(globalOrig.start - origRange.start, globalOrig.endExclusive - origRange.start), modified.substring(globalMod.start, globalMod.endExclusive)));
2540
- }
2541
- }
2542
- return new Move(new OffsetRangeMapping(origRange, modRange), StringEdit.of(inner));
2543
- }
2544
- // ----- offset/line helpers -----
2545
- function _computeLineStarts(lines) {
2546
- // Offsets correspond to `lines.join('\n')`, which equals the input string
2547
- // when the input was split by `\n`.
2548
- const starts = new Array(lines.length + 1);
2549
- starts[0] = 0;
2550
- for (let i = 0; i < lines.length; i++) {
2551
- starts[i + 1] = starts[i] + lines[i].length + 1; // +1 for the '\n'
2552
- }
2553
- // `starts[lines.length]` is one past the synthetic trailing newline; the
2554
- // real text ends one earlier when there is no trailing newline (which is
2555
- // what `split('\n')` produces for non-newline-terminated input).
2556
- return starts;
2557
- }
2558
- function _lineRangeToOffsetRange(startLineNumber, endLineNumberExclusive, text, lineStarts) {
2559
- const start = _lineColToOffset(startLineNumber, 1, text, lineStarts);
2560
- const end = _lineColToOffset(endLineNumberExclusive, 1, text, lineStarts);
2561
- return new OffsetRange(start, end);
2562
- }
2563
- function _rangeToOffsetRange(range, text, lines, lineStarts) {
2564
- const start = _lineColToOffset(range.startLineNumber, range.startColumn, text, lineStarts);
2565
- const end = _lineColToOffset(range.endLineNumber, range.endColumn, text, lineStarts);
2566
- return new OffsetRange(start, end);
2567
- }
2568
- function _lineColToOffset(lineNumber, column, text, lineStarts) {
2569
- // `lineNumber` and `column` are 1-based; `endLineNumberExclusive` can be
2570
- // `lines.length + 1` (one past the last line).
2571
- if (lineNumber <= 0) {
2572
- return 0;
2573
- }
2574
- if (lineNumber > lineStarts.length) {
2575
- return text.length;
2576
- }
2577
- const base = lineStarts[lineNumber - 1];
2578
- const offset = base + (column - 1);
2579
- return Math.min(Math.max(offset, 0), text.length);
2580
585
  }
2581
586
 
2582
- export { AnnotatedStringEdit, AnnotatedStringReplacement, DiffAnnotation, DiffResult, Move, OffsetRange, OffsetRangeMapping, StringEdit, StringReplacement, createDiffComputer };
587
+ export { AnnotatedStringEdit, AnnotatedStringReplacement, DiffAnnotation, DiffResult, Move, OffsetRange, OffsetRangeMapping, StringEdit, StringReplacement, DefaultLinesDiffComputer as _DefaultLinesDiffComputer, DetailedLineRangeMapping as _DetailedLineRangeMapping, LineRange as _LineRange, LinesDiff as _LinesDiff, RangeMapping as _RangeMapping, getWasm as _getWasm, initWasm as _initWasm, createDiffComputer, createWorkerDiffComputer };
2583
588
  //# sourceMappingURL=index.js.map