@@ -8,6 +8,7 @@ library quill_delta;
88import 'dart:math' as math;
99
1010import 'package:collection/collection.dart' ;
11+ import 'package:diff_match_patch/diff_match_patch.dart' as dmp;
1112import 'package:quiver/core.dart' ;
1213
1314const _attributeEquality = DeepCollectionEquality ();
@@ -190,6 +191,9 @@ class Delta {
190191 factory Delta .from (Delta other) =>
191192 Delta ._(List <Operation >.from (other._operations));
192193
194+ // Placeholder char for embed in diff()
195+ static final String _kNullCharacter = String .fromCharCode (0 );
196+
193197 /// Transforms two attribute sets.
194198 static Map <String , dynamic >? transformAttributes (
195199 Map <String , dynamic >? a, Map <String , dynamic >? b, bool priority) {
@@ -248,6 +252,22 @@ class Delta {
248252 return inverted;
249253 }
250254
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+
251271 final List <Operation > _operations;
252272
253273 int _modificationCount = 0 ;
@@ -399,7 +419,7 @@ class Delta {
399419 if (thisIter.isNextDelete) return thisIter.next ();
400420
401421 final length = math.min (thisIter.peekLength (), otherIter.peekLength ());
402- final thisOp = thisIter.next (length as int );
422+ final thisOp = thisIter.next (length);
403423 final otherOp = otherIter.next (length);
404424 assert (thisOp.length == otherOp.length);
405425
@@ -442,6 +462,80 @@ class Delta {
442462 return result..trim ();
443463 }
444464
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+ ///
475+ /// Useful when one wishes to display difference between 2 documents
476+ Delta diff (Delta other) {
477+ if (_operations.equals (other._operations)) {
478+ return Delta ();
479+ }
480+ final stringThis = map ((op) {
481+ if (op.isInsert) {
482+ return op.data is String ? op.data : _kNullCharacter;
483+ }
484+ final prep = this == other ? 'on' : 'with' ;
485+ throw ArgumentError ('diff() call $prep non-document' );
486+ }).join ();
487+ final stringOther = other.map ((op) {
488+ if (op.isInsert) {
489+ return op.data is String ? op.data : _kNullCharacter;
490+ }
491+ final prep = this == other ? 'on' : 'with' ;
492+ throw ArgumentError ('diff() call $prep non-document' );
493+ }).join ();
494+
495+ final retDelta = Delta ();
496+ final diffResult = dmp.diff (stringThis, stringOther);
497+ final thisIter = DeltaIterator (this );
498+ final otherIter = DeltaIterator (other);
499+
500+ diffResult.forEach ((component) {
501+ var length = component.text.length;
502+ while (length > 0 ) {
503+ var opLength = 0 ;
504+ switch (component.operation) {
505+ case dmp.DIFF_INSERT :
506+ opLength = math.min (otherIter.peekLength (), length);
507+ retDelta.push (otherIter.next (opLength));
508+ break ;
509+ case dmp.DIFF_DELETE :
510+ opLength = math.min (length, thisIter.peekLength ());
511+ thisIter.next (opLength);
512+ retDelta.delete (opLength);
513+ break ;
514+ case dmp.DIFF_EQUAL :
515+ opLength = math.min (
516+ math.min (thisIter.peekLength (), otherIter.peekLength ()),
517+ length,
518+ );
519+ final thisOp = thisIter.next (opLength);
520+ final otherOp = otherIter.next (opLength);
521+ if (thisOp.data == otherOp.data) {
522+ retDelta.retain (
523+ opLength,
524+ diffAttributes (thisOp.attributes, otherOp.attributes),
525+ );
526+ } else {
527+ retDelta
528+ ..push (otherOp)
529+ ..delete (opLength);
530+ }
531+ break ;
532+ }
533+ length -= opLength;
534+ }
535+ });
536+ return retDelta..trim ();
537+ }
538+
445539 /// Transforms next operation from [otherIter] against next operation in
446540 /// [thisIter] .
447541 ///
@@ -455,7 +549,7 @@ class Delta {
455549 }
456550
457551 final length = math.min (thisIter.peekLength (), otherIter.peekLength ());
458- final thisOp = thisIter.next (length as int );
552+ final thisOp = thisIter.next (length);
459553 final otherOp = otherIter.next (length);
460554 assert (thisOp.length == otherOp.length);
461555
@@ -558,7 +652,7 @@ class Delta {
558652 if (index < start) {
559653 op = opIterator.next (start - index);
560654 } else {
561- op = opIterator.next (actualEnd - index as int );
655+ op = opIterator.next (actualEnd - index);
562656 delta.push (op);
563657 }
564658 index += op.length! ;
@@ -643,7 +737,8 @@ class DeltaIterator {
643737 ///
644738 /// If this iterator reached the end of the Delta then returns a retain
645739 /// operation with its length set to [maxLength] .
646- // TODO: Note that we used double.infinity as the default value for length here
740+ // TODO: Note that we used double.infinity as the default value
741+ // for length here
647742 // but this can now cause a type error since operation length is
648743 // expected to be an int. Changing default length to [maxLength] is
649744 // a workaround to avoid breaking changes.
@@ -686,7 +781,7 @@ class DeltaIterator {
686781 while (skipped < length && hasNext) {
687782 final opLength = peekLength ();
688783 final skip = math.min (length - skipped, opLength);
689- op = next (skip as int );
784+ op = next (skip);
690785 skipped += op.length! ;
691786 }
692787 return op;
0 commit comments