fix: use retain instead of insert/delete for same attr (#937)

* fix: use retain instead of insert/delete for same attr

* test: add delta diff test
This commit is contained in:
Lucas 2024-10-17 11:07:53 +08:00 • committed by GitHub
commit 450912305a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 73 additions and 2 deletions

View file

@ -52,6 +52,23 @@ Attributes invertAttributes(Attributes? from, Attributes? to) {
return attributes; return attributes;
} }
Attributes? diffAttributes(
Map<String, dynamic>? from,
Map<String, dynamic>? to,
) {
from ??= const {};
to ??= const {};
final attributes = Attributes.from({});
for (final key in (from.keys.toList()..addAll(to.keys))) {
if (from[key] != to[key]) {
attributes[key] = to.containsKey(key) ? to[key] : null;
}
}
return attributes;
}
int hashAttributes(Attributes base) => Object.hashAllUnordered( int hashAttributes(Attributes base) => Object.hashAllUnordered(
base.entries.map((e) => Object.hash(e.key, e.value)), base.entries.map((e) => Object.hash(e.key, e.value)),
); );

View file

@ -62,6 +62,10 @@ const int _maxInt = 9007199254740991;
sealed class TextOperation { sealed class TextOperation {
Attributes? get attributes; Attributes? get attributes;
// available for TextInsert, for TextDelete and TextRetain, it's null
Object? get data => null;
int get length; int get length;
bool get isEmpty => length == 0; bool get isEmpty => length == 0;
@ -81,6 +85,9 @@ class TextInsert extends TextOperation {
@override @override
int get length => text.length; int get length => text.length;
@override
Object? get data => text;
@override @override
Attributes? get attributes => _attributes != null ? {..._attributes} : null; Attributes? get attributes => _attributes != null ? {..._attributes} : null;
@ -402,10 +409,10 @@ class Delta extends Iterable<TextOperation> {
); );
final thisOp = thisIter.next(opLength); final thisOp = thisIter.next(opLength);
final otherOp = otherIter.next(opLength); final otherOp = otherIter.next(opLength);
if (isAttributesEqual(thisOp.attributes, otherOp.attributes)) { if (thisOp.data == otherOp.data) {
retDelta.retain( retDelta.retain(
opLength, opLength,
attributes: invertAttributes( attributes: diffAttributes(
thisOp.attributes, thisOp.attributes,
otherOp.attributes, otherOp.attributes,
), ),

View file

@ -519,6 +519,53 @@ void main() {
final diff = delta.diff(restored); final diff = delta.diff(restored);
expect(delta.compose(diff), restored); expect(delta.compose(diff), restored);
}); });
test('insert', () {
final a = Delta()..insert('Hello');
final b = Delta()..insert('Hello!');
final expected = Delta()
..retain(5)
..insert('!');
expect(a.diff(b), expected);
});
test('delete', () {
final a = Delta()..insert('Hello!');
final b = Delta()..insert('Hello');
final expected = Delta()
..retain(5)
..delete(1);
expect(a.diff(b), expected);
});
test('retain', () {
final a = Delta()..insert('A');
final b = Delta()..insert('A');
final expected = Delta();
expect(a.diff(b), expected);
});
test('retain with attributes - 1', () {
final a = Delta()..insert('Hello');
final b = Delta()..insert('Hello', attributes: {'bold': true});
final expected = Delta()
..retain(
5,
attributes: {'bold': true},
);
expect(a.diff(b), expected);
});
test('retain with attributes - 2', () {
final a = Delta()..insert('Hello', attributes: {'bold': true});
final b = Delta()..insert('Hello', attributes: {'italic': true});
final expected = Delta()
..retain(
5,
attributes: {'bold': null, 'italic': true},
);
expect(a.diff(b), expected);
});
}); });
}); });
} }