@@ -8,6 +8,7 @@ library quill_delta;
8
8
import 'dart:math' as math;
9
9
10
10
import 'package:collection/collection.dart' ;
11
+ import 'package:diff_match_patch/diff_match_patch.dart' as dmp;
11
12
import 'package:quiver/core.dart' ;
12
13
13
14
const _attributeEquality = DeepCollectionEquality ();
@@ -190,6 +191,9 @@ class Delta {
190
191
factory Delta .from (Delta other) =>
191
192
Delta ._(List <Operation >.from (other._operations));
192
193
194
+ // Placeholder char for embed in diff()
195
+ static final String _kNullCharacter = String .fromCharCode (0 );
196
+
193
197
/// Transforms two attribute sets.
194
198
static Map <String , dynamic >? transformAttributes (
195
199
Map <String , dynamic >? a, Map <String , dynamic >? b, bool priority) {
@@ -248,6 +252,22 @@ class Delta {
248
252
return inverted;
249
253
}
250
254
255
+ /// Returns diff between two attribute sets
256
+ static Map <String , dynamic >? diffAttributes (
257
+ Map <String , dynamic >? a, Map <String , dynamic >? b) {
258
+ a ?? = const {};
259
+ b ?? = const {};
260
+
261
+ final attributes = < String , dynamic > {};
262
+ (a.keys.toList ()..addAll (b.keys)).forEach ((key) {
263
+ if (a! [key] != b! [key]) {
264
+ attributes[key] = b.containsKey (key) ? b[key] : null ;
265
+ }
266
+ });
267
+
268
+ return attributes.keys.isNotEmpty ? attributes : null ;
269
+ }
270
+
251
271
final List <Operation > _operations;
252
272
253
273
int _modificationCount = 0 ;
@@ -399,7 +419,7 @@ class Delta {
399
419
if (thisIter.isNextDelete) return thisIter.next ();
400
420
401
421
final length = math.min (thisIter.peekLength (), otherIter.peekLength ());
402
- final thisOp = thisIter.next (length as int );
422
+ final thisOp = thisIter.next (length);
403
423
final otherOp = otherIter.next (length);
404
424
assert (thisOp.length == otherOp.length);
405
425
@@ -442,6 +462,94 @@ class Delta {
442
462
return result..trim ();
443
463
}
444
464
465
+ /// Returns a new lazy Iterable with elements that are created by calling
466
+ /// f on each element of this Iterable in iteration order.
467
+ ///
468
+ /// Convenience method
469
+ Iterable <T > map <T >(T Function (Operation ) f) {
470
+ return _operations.map <T >(f);
471
+ }
472
+
473
+ /// Returns a [Delta] containing differences between 2 [Delta] s.
474
+ /// If [cleanupSemantic] is `true` (default), applies the following:
475
+ ///
476
+ /// The diff of "mouse" and "sofas" is
477
+ /// [delete(1), insert("s"), retain(1),
478
+ /// delete("u"), insert("fa"), retain(1), delete(1)].
479
+ /// While this is the optimum diff, it is difficult for humans to understand.
480
+ /// Semantic cleanup rewrites the diff,
481
+ /// expanding it into a more intelligible format.
482
+ /// The above example would become: [(-1, "mouse"), (1, "sofas")] .
483
+ /// (source: https://github.com/google/diff-match-patch/wiki/API)
484
+ ///
485
+ /// Useful when one wishes to display difference between 2 documents
486
+ Delta diff (Delta other, {bool cleanupSemantic = true }) {
487
+ if (_operations.equals (other._operations)) {
488
+ return Delta ();
489
+ }
490
+ final stringThis = map ((op) {
491
+ if (op.isInsert) {
492
+ return op.data is String ? op.data : _kNullCharacter;
493
+ }
494
+ final prep = this == other ? 'on' : 'with' ;
495
+ throw ArgumentError ('diff() call $prep non-document' );
496
+ }).join ();
497
+ final stringOther = other.map ((op) {
498
+ if (op.isInsert) {
499
+ return op.data is String ? op.data : _kNullCharacter;
500
+ }
501
+ final prep = this == other ? 'on' : 'with' ;
502
+ throw ArgumentError ('diff() call $prep non-document' );
503
+ }).join ();
504
+
505
+ final retDelta = Delta ();
506
+ final diffResult = dmp.diff (stringThis, stringOther);
507
+ if (cleanupSemantic) {
508
+ dmp.DiffMatchPatch ().diffCleanupSemantic (diffResult);
509
+ }
510
+
511
+ final thisIter = DeltaIterator (this );
512
+ final otherIter = DeltaIterator (other);
513
+
514
+ diffResult.forEach ((component) {
515
+ var length = component.text.length;
516
+ while (length > 0 ) {
517
+ var opLength = 0 ;
518
+ switch (component.operation) {
519
+ case dmp.DIFF_INSERT :
520
+ opLength = math.min (otherIter.peekLength (), length);
521
+ retDelta.push (otherIter.next (opLength));
522
+ break ;
523
+ case dmp.DIFF_DELETE :
524
+ opLength = math.min (length, thisIter.peekLength ());
525
+ thisIter.next (opLength);
526
+ retDelta.delete (opLength);
527
+ break ;
528
+ case dmp.DIFF_EQUAL :
529
+ opLength = math.min (
530
+ math.min (thisIter.peekLength (), otherIter.peekLength ()),
531
+ length,
532
+ );
533
+ final thisOp = thisIter.next (opLength);
534
+ final otherOp = otherIter.next (opLength);
535
+ if (thisOp.data == otherOp.data) {
536
+ retDelta.retain (
537
+ opLength,
538
+ diffAttributes (thisOp.attributes, otherOp.attributes),
539
+ );
540
+ } else {
541
+ retDelta
542
+ ..push (otherOp)
543
+ ..delete (opLength);
544
+ }
545
+ break ;
546
+ }
547
+ length -= opLength;
548
+ }
549
+ });
550
+ return retDelta..trim ();
551
+ }
552
+
445
553
/// Transforms next operation from [otherIter] against next operation in
446
554
/// [thisIter] .
447
555
///
@@ -455,7 +563,7 @@ class Delta {
455
563
}
456
564
457
565
final length = math.min (thisIter.peekLength (), otherIter.peekLength ());
458
- final thisOp = thisIter.next (length as int );
566
+ final thisOp = thisIter.next (length);
459
567
final otherOp = otherIter.next (length);
460
568
assert (thisOp.length == otherOp.length);
461
569
@@ -551,14 +659,14 @@ class Delta {
551
659
var index = 0 ;
552
660
final opIterator = DeltaIterator (this );
553
661
554
- final actualEnd = end ?? double .infinity ;
662
+ final actualEnd = end ?? DeltaIterator .maxLength ;
555
663
556
664
while (index < actualEnd && opIterator.hasNext) {
557
665
Operation op;
558
666
if (index < start) {
559
667
op = opIterator.next (start - index);
560
668
} else {
561
- op = opIterator.next (actualEnd - index as int );
669
+ op = opIterator.next (actualEnd - index);
562
670
delta.push (op);
563
671
}
564
672
index += op.length! ;
@@ -602,10 +710,12 @@ class Delta {
602
710
class DeltaIterator {
603
711
DeltaIterator (this .delta) : _modificationCount = delta._modificationCount;
604
712
713
+ static const int maxLength = 1073741824 ;
714
+
605
715
final Delta delta;
606
716
final int _modificationCount;
607
717
int _index = 0 ;
608
- num _offset = 0 ;
718
+ int _offset = 0 ;
609
719
610
720
bool get isNextInsert => nextOperationKey == Operation .insertKey;
611
721
@@ -621,24 +731,32 @@ class DeltaIterator {
621
731
}
622
732
}
623
733
624
- bool get hasNext => peekLength () < double .infinity ;
734
+ bool get hasNext => peekLength () < maxLength ;
625
735
626
736
/// Returns length of next operation without consuming it.
627
737
///
628
- /// Returns [double.infinity ] if there is no more operations left to iterate.
629
- num peekLength () {
738
+ /// Returns [maxLength ] if there is no more operations left to iterate.
739
+ int peekLength () {
630
740
if (_index < delta.length) {
631
741
final operation = delta._operations[_index];
632
742
return operation.length! - _offset;
633
743
}
634
- return double .infinity ;
744
+ return maxLength ;
635
745
}
636
746
637
747
/// Consumes and returns next operation.
638
748
///
639
749
/// Optional [length] specifies maximum length of operation to return. Note
640
750
/// that actual length of returned operation may be less than specified value.
641
- Operation next ([int length = 4294967296 ]) {
751
+ ///
752
+ /// If this iterator reached the end of the Delta then returns a retain
753
+ /// operation with its length set to [maxLength] .
754
+ // TODO: Note that we used double.infinity as the default value
755
+ // for length here
756
+ // but this can now cause a type error since operation length is
757
+ // expected to be an int. Changing default length to [maxLength] is
758
+ // a workaround to avoid breaking changes.
759
+ Operation next ([int length = maxLength]) {
642
760
if (_modificationCount != delta._modificationCount) {
643
761
throw ConcurrentModificationError (delta);
644
762
}
@@ -656,13 +774,13 @@ class DeltaIterator {
656
774
_offset += actualLength;
657
775
}
658
776
final opData = op.isInsert && op.data is String
659
- ? (op.data as String ). substring (
660
- _currentOffset as int , _currentOffset + ( actualLength as int ) )
777
+ ? (op.data as String )
778
+ . substring ( _currentOffset, _currentOffset + actualLength)
661
779
: op.data;
662
780
final opIsNotEmpty =
663
781
opData is String ? opData.isNotEmpty : true ; // embeds are never empty
664
782
final opLength = opData is String ? opData.length : 1 ;
665
- final opActualLength = opIsNotEmpty ? opLength : actualLength as int ;
783
+ final opActualLength = opIsNotEmpty ? opLength : actualLength;
666
784
return Operation ._(opKey, opActualLength, opData, opAttributes);
667
785
}
668
786
return Operation .retain (length);
@@ -677,7 +795,7 @@ class DeltaIterator {
677
795
while (skipped < length && hasNext) {
678
796
final opLength = peekLength ();
679
797
final skip = math.min (length - skipped, opLength);
680
- op = next (skip as int );
798
+ op = next (skip);
681
799
skipped += op.length! ;
682
800
}
683
801
return op;
0 commit comments