Add fast diff

pull/332/head
Xin Yao 4 years ago
parent 3b4e5d8a0a
commit ad57c6bab6
  1. 105
      lib/src/models/quill_delta.dart
  2. 2
      lib/src/utils/diff_delta.dart
  3. 1
      pubspec.yaml

@ -8,6 +8,7 @@ library quill_delta;
import 'dart:math' as math;
import 'package:collection/collection.dart';
import 'package:diff_match_patch/diff_match_patch.dart' as dmp;
import 'package:quiver/core.dart';
const _attributeEquality = DeepCollectionEquality();
@ -190,6 +191,9 @@ class Delta {
factory Delta.from(Delta other) =>
Delta._(List<Operation>.from(other._operations));
// Placeholder char for embed in diff()
static final String _kNullCharacter = String.fromCharCode(0);
/// Transforms two attribute sets.
static Map<String, dynamic>? transformAttributes(
Map<String, dynamic>? a, Map<String, dynamic>? b, bool priority) {
@ -248,6 +252,22 @@ class Delta {
return inverted;
}
/// Returns diff between two attribute sets
static Map<String, dynamic>? diffAttributes(
Map<String, dynamic>? a, Map<String, dynamic>? b) {
a ??= const {};
b ??= const {};
final attributes = <String, dynamic>{};
(a.keys.toList()..addAll(b.keys)).forEach((key) {
if (a![key] != b![key]) {
attributes[key] = b.containsKey(key) ? b[key] : null;
}
});
return attributes.keys.isNotEmpty ? attributes : null;
}
final List<Operation> _operations;
int _modificationCount = 0;
@ -399,7 +419,7 @@ class Delta {
if (thisIter.isNextDelete) return thisIter.next();
final length = math.min(thisIter.peekLength(), otherIter.peekLength());
final thisOp = thisIter.next(length as int);
final thisOp = thisIter.next(length);
final otherOp = otherIter.next(length);
assert(thisOp.length == otherOp.length);
@ -442,6 +462,80 @@ class Delta {
return result..trim();
}
/// Returns a new lazy Iterable with elements that are created by calling
/// f on each element of this Iterable in iteration order.
///
/// Convenience method
Iterable<T> map<T>(T Function(Operation) f) {
return _operations.map<T>(f);
}
/// Returns a [Delta] containing differences between 2 [Delta]s
///
/// Useful when one wishes to display difference between 2 documents
Delta diff(Delta other) {
if (_operations.equals(other._operations)) {
return Delta();
}
final stringThis = map((op) {
if (op.isInsert) {
return op.data is String ? op.data : _kNullCharacter;
}
final prep = this == other ? 'on' : 'with';
throw ArgumentError('diff() call $prep non-document');
}).join();
final stringOther = other.map((op) {
if (op.isInsert) {
return op.data is String ? op.data : _kNullCharacter;
}
final prep = this == other ? 'on' : 'with';
throw ArgumentError('diff() call $prep non-document');
}).join();
final retDelta = Delta();
final diffResult = dmp.diff(stringThis, stringOther);
final thisIter = DeltaIterator(this);
final otherIter = DeltaIterator(other);
diffResult.forEach((component) {
var length = component.text.length;
while (length > 0) {
var opLength = 0;
switch (component.operation) {
case dmp.DIFF_INSERT:
opLength = math.min(otherIter.peekLength(), length);
retDelta.push(otherIter.next(opLength));
break;
case dmp.DIFF_DELETE:
opLength = math.min(length, thisIter.peekLength());
thisIter.next(opLength);
retDelta.delete(opLength);
break;
case dmp.DIFF_EQUAL:
opLength = math.min(
math.min(thisIter.peekLength(), otherIter.peekLength()),
length,
);
final thisOp = thisIter.next(opLength);
final otherOp = otherIter.next(opLength);
if (thisOp.data == otherOp.data) {
retDelta.retain(
opLength,
diffAttributes(thisOp.attributes, otherOp.attributes),
);
} else {
retDelta
..push(otherOp)
..delete(opLength);
}
break;
}
length -= opLength;
}
});
return retDelta..trim();
}
/// Transforms next operation from [otherIter] against next operation in
/// [thisIter].
///
@ -455,7 +549,7 @@ class Delta {
}
final length = math.min(thisIter.peekLength(), otherIter.peekLength());
final thisOp = thisIter.next(length as int);
final thisOp = thisIter.next(length);
final otherOp = otherIter.next(length);
assert(thisOp.length == otherOp.length);
@ -558,7 +652,7 @@ class Delta {
if (index < start) {
op = opIterator.next(start - index);
} else {
op = opIterator.next(actualEnd - index as int);
op = opIterator.next(actualEnd - index);
delta.push(op);
}
index += op.length!;
@ -643,7 +737,8 @@ class DeltaIterator {
///
/// If this iterator reached the end of the Delta then returns a retain
/// operation with its length set to [maxLength].
// TODO: Note that we used double.infinity as the default value for length here
// TODO: Note that we used double.infinity as the default value
// for length here
// but this can now cause a type error since operation length is
// expected to be an int. Changing default length to [maxLength] is
// a workaround to avoid breaking changes.
@ -686,7 +781,7 @@ class DeltaIterator {
while (skipped < length && hasNext) {
final opLength = peekLength();
final skip = math.min(length - skipped, opLength);
op = next(skip as int);
op = next(skip);
skipped += op.length!;
}
return op;

@ -76,7 +76,7 @@ int getPositionDelta(Delta user, Delta actual) {
var diff = 0;
while (userItr.hasNext || actualItr.hasNext) {
final length = math.min(userItr.peekLength(), actualItr.peekLength());
final userOperation = userItr.next(length as int);
final userOperation = userItr.next(length);
final actualOperation = actualItr.next(length);
if (userOperation.length != actualOperation.length) {
throw 'userOp ${userOperation.length} does not match actualOp '

@ -25,6 +25,7 @@ dependencies:
video_player: ^2.1.10
characters: ^1.1.0
youtube_player_flutter: ^8.0.0
diff_match_patch: ^0.4.1
dev_dependencies:
flutter_test:

Loading…
Cancel
Save