dartlangeditorflutterflutter-appsflutter-examplesflutter-packageflutter-widgetquillquill-deltaquilljsreactquillrich-textrich-text-editorwysiwygwysiwyg-editor
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
100 lines
3.2 KiB
100 lines
3.2 KiB
import 'dart:math' as math; |
|
|
|
import '../operation/operation.dart'; |
|
import 'delta.dart'; |
|
|
|
/// Specialized iterator for [Delta]s. |
|
class DeltaIterator { |
|
DeltaIterator(this.delta) : _modificationCount = delta.modificationCount; |
|
|
|
static const int maxLength = 1073741824; |
|
|
|
final Delta delta; |
|
final int _modificationCount; |
|
int _index = 0; |
|
int _offset = 0; |
|
|
|
bool get isNextInsert => nextOperationKey == Operation.insertKey; |
|
|
|
bool get isNextDelete => nextOperationKey == Operation.deleteKey; |
|
|
|
bool get isNextRetain => nextOperationKey == Operation.retainKey; |
|
|
|
String? get nextOperationKey { |
|
if (_index < delta.length) { |
|
return delta.elementAt(_index).key; |
|
} else { |
|
return null; |
|
} |
|
} |
|
|
|
bool get hasNext => peekLength() < maxLength; |
|
|
|
/// Returns length of next operation without consuming it. |
|
/// |
|
/// Returns [maxLength] if there is no more operations left to iterate. |
|
int peekLength() { |
|
if (_index < delta.length) { |
|
final operation = delta.operations[_index]; |
|
return operation.length! - _offset; |
|
} |
|
return maxLength; |
|
} |
|
|
|
/// Consumes and returns next operation. |
|
/// |
|
/// Optional [length] specifies maximum length of operation to return. Note |
|
/// that actual length of returned operation may be less than specified value. |
|
/// |
|
/// 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 |
|
// 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. |
|
Operation next([int length = maxLength]) { |
|
if (_modificationCount != delta.modificationCount) { |
|
throw ConcurrentModificationError(delta); |
|
} |
|
|
|
if (_index < delta.length) { |
|
final op = delta.elementAt(_index); |
|
final opKey = op.key; |
|
final opAttributes = op.attributes; |
|
final currentOffset = _offset; |
|
final actualLength = math.min(op.length! - currentOffset, length); |
|
if (actualLength == op.length! - currentOffset) { |
|
_index++; |
|
_offset = 0; |
|
} else { |
|
_offset += actualLength; |
|
} |
|
final opData = op.isInsert && op.data is String |
|
? (op.data as String) |
|
.substring(currentOffset, currentOffset + actualLength) |
|
: op.data; |
|
final opIsNotEmpty = |
|
opData is String ? opData.isNotEmpty : true; // embeds are never empty |
|
final opLength = opData is String ? opData.length : 1; |
|
final opActualLength = opIsNotEmpty ? opLength : actualLength; |
|
return Operation(opKey, opActualLength, opData, opAttributes); |
|
} |
|
return Operation.retain(length); |
|
} |
|
|
|
/// Skips [length] characters in source delta. |
|
/// |
|
/// Returns last skipped operation, or `null` if there was nothing to skip. |
|
Operation? skip(int length) { |
|
var skipped = 0; |
|
Operation? op; |
|
while (skipped < length && hasNext) { |
|
final opLength = peekLength(); |
|
final skip = math.min(length - skipped, opLength); |
|
op = next(skip); |
|
skipped += op.length!; |
|
} |
|
return op; |
|
} |
|
}
|
|
|