quill_delta.dart 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. // Copyright (c) 2018, Anatoly Pulyaevskiy. All rights reserved. Use of this source code
  2. // is governed by a BSD-style license that can be found in the LICENSE file.
  3. /// Implementation of Quill Delta format in Dart.
  4. library quill_delta;
  5. import 'dart:math' as math;
  6. import 'package:collection/collection.dart';
  7. import 'package:quiver/core.dart';
  8. const _attributeEquality = DeepCollectionEquality();
  9. const _valueEquality = DeepCollectionEquality();
  10. /// Decoder function to convert raw `data` object into a user-defined data type.
  11. ///
  12. /// Useful with embedded content.
  13. typedef DataDecoder = Object? Function(Object data);
  14. /// Default data decoder which simply passes through the original value.
  15. Object? _passThroughDataDecoder(Object? data) => data;
  16. /// Operation performed on a rich-text document.
  17. class Operation {
  18. Operation._(this.key, this.length, this.data, Map? attributes)
  19. : assert(_validKeys.contains(key), 'Invalid operation key "$key".'),
  20. assert(() {
  21. if (key != Operation.insertKey) return true;
  22. return data is String ? data.length == length : length == 1;
  23. }(), 'Length of insert operation must be equal to the data length.'),
  24. _attributes =
  25. attributes != null ? Map<String, dynamic>.from(attributes) : null;
  26. /// Creates operation which deletes [length] of characters.
  27. factory Operation.delete(int length) =>
  28. Operation._(Operation.deleteKey, length, '', null);
  29. /// Creates operation which inserts [text] with optional [attributes].
  30. factory Operation.insert(dynamic data, [Map<String, dynamic>? attributes]) =>
  31. Operation._(Operation.insertKey, data is String ? data.length : 1, data,
  32. attributes);
  33. /// Creates operation which retains [length] of characters and optionally
  34. /// applies attributes.
  35. factory Operation.retain(int? length, [Map<String, dynamic>? attributes]) =>
  36. Operation._(Operation.retainKey, length, '', attributes);
  37. /// Key of insert operations.
  38. static const String insertKey = 'insert';
  39. /// Key of delete operations.
  40. static const String deleteKey = 'delete';
  41. /// Key of retain operations.
  42. static const String retainKey = 'retain';
  43. /// Key of attributes collection.
  44. static const String attributesKey = 'attributes';
  45. static const List<String> _validKeys = [insertKey, deleteKey, retainKey];
  46. /// Key of this operation, can be "insert", "delete" or "retain".
  47. final String key;
  48. /// Length of this operation.
  49. final int? length;
  50. /// Payload of "insert" operation, for other types is set to empty string.
  51. final Object? data;
  52. /// Rich-text attributes set by this operation, can be `null`.
  53. Map<String, dynamic>? get attributes =>
  54. _attributes == null ? null : Map<String, dynamic>.from(_attributes!);
  55. final Map<String, dynamic>? _attributes;
  56. /// Creates new [Operation] from JSON payload.
  57. ///
  58. /// If `dataDecoder` parameter is not null then it is used to additionally
  59. /// decode the operation's data object. Only applied to insert operations.
  60. static Operation fromJson(Map data, {DataDecoder? dataDecoder}) {
  61. dataDecoder ??= _passThroughDataDecoder;
  62. final map = Map<String, dynamic>.from(data);
  63. if (map.containsKey(Operation.insertKey)) {
  64. final data = dataDecoder(map[Operation.insertKey]);
  65. final dataLength = data is String ? data.length : 1;
  66. return Operation._(
  67. Operation.insertKey, dataLength, data, map[Operation.attributesKey]);
  68. } else if (map.containsKey(Operation.deleteKey)) {
  69. final int? length = map[Operation.deleteKey];
  70. return Operation._(Operation.deleteKey, length, '', null);
  71. } else if (map.containsKey(Operation.retainKey)) {
  72. final int? length = map[Operation.retainKey];
  73. return Operation._(
  74. Operation.retainKey, length, '', map[Operation.attributesKey]);
  75. }
  76. throw ArgumentError.value(data, 'Invalid data for Delta operation.');
  77. }
  78. /// Returns JSON-serializable representation of this operation.
  79. Map<String, dynamic> toJson() {
  80. final json = {key: value};
  81. if (_attributes != null) json[Operation.attributesKey] = attributes;
  82. return json;
  83. }
  84. /// Returns value of this operation.
  85. ///
  86. /// For insert operations this returns text, for delete and retain - length.
  87. dynamic get value => (key == Operation.insertKey) ? data : length;
  88. /// Returns `true` if this is a delete operation.
  89. bool get isDelete => key == Operation.deleteKey;
  90. /// Returns `true` if this is an insert operation.
  91. bool get isInsert => key == Operation.insertKey;
  92. /// Returns `true` if this is a retain operation.
  93. bool get isRetain => key == Operation.retainKey;
  94. /// Returns `true` if this operation has no attributes, e.g. is plain text.
  95. bool get isPlain => _attributes == null || _attributes!.isEmpty;
  96. /// Returns `true` if this operation sets at least one attribute.
  97. bool get isNotPlain => !isPlain;
  98. /// Returns `true` is this operation is empty.
  99. ///
  100. /// An operation is considered empty if its [length] is equal to `0`.
  101. bool get isEmpty => length == 0;
  102. /// Returns `true` is this operation is not empty.
  103. bool get isNotEmpty => length! > 0;
  104. @override
  105. bool operator ==(other) {
  106. if (identical(this, other)) return true;
  107. if (other is! Operation) return false;
  108. final typedOther = other;
  109. return key == typedOther.key &&
  110. length == typedOther.length &&
  111. _valueEquality.equals(data, typedOther.data) &&
  112. hasSameAttributes(typedOther);
  113. }
  114. /// Returns `true` if this operation has attribute specified by [name].
  115. bool hasAttribute(String name) =>
  116. isNotPlain && _attributes!.containsKey(name);
  117. /// Returns `true` if [other] operation has the same attributes as this one.
  118. bool hasSameAttributes(Operation other) {
  119. return _attributeEquality.equals(_attributes, other._attributes);
  120. }
  121. @override
  122. int get hashCode {
  123. if (_attributes != null && _attributes!.isNotEmpty) {
  124. final attrsHash =
  125. hashObjects(_attributes!.entries.map((e) => hash2(e.key, e.value)));
  126. return hash3(key, value, attrsHash);
  127. }
  128. return hash2(key, value);
  129. }
  130. @override
  131. String toString() {
  132. final attr = attributes == null ? '' : ' + $attributes';
  133. final text = isInsert
  134. ? (data is String
  135. ? (data as String).replaceAll('\n', '⏎')
  136. : data.toString())
  137. : '$length';
  138. return '$key⟨ $text ⟩$attr';
  139. }
  140. }
  141. /// Delta represents a document or a modification of a document as a sequence of
  142. /// insert, delete and retain operations.
  143. ///
  144. /// Delta consisting of only "insert" operations is usually referred to as
  145. /// "document delta". When delta includes also "retain" or "delete" operations
  146. /// it is a "change delta".
  147. class Delta {
  148. /// Creates new empty [Delta].
  149. factory Delta() => Delta._(<Operation>[]);
  150. Delta._(List<Operation> operations) : _operations = operations;
  151. /// Creates new [Delta] from [other].
  152. factory Delta.from(Delta other) =>
  153. Delta._(List<Operation>.from(other._operations));
  154. /// Transforms two attribute sets.
  155. static Map<String, dynamic>? transformAttributes(
  156. Map<String, dynamic>? a, Map<String, dynamic>? b, bool priority) {
  157. if (a == null) return b;
  158. if (b == null) return null;
  159. if (!priority) return b;
  160. final result = b.keys.fold<Map<String, dynamic>>({}, (attributes, key) {
  161. if (!a.containsKey(key)) attributes[key] = b[key];
  162. return attributes;
  163. });
  164. return result.isEmpty ? null : result;
  165. }
  166. /// Composes two attribute sets.
  167. static Map<String, dynamic>? composeAttributes(
  168. Map<String, dynamic>? a, Map<String, dynamic>? b,
  169. {bool keepNull = false}) {
  170. a ??= const {};
  171. b ??= const {};
  172. final result = Map<String, dynamic>.from(a)..addAll(b);
  173. final keys = result.keys.toList(growable: false);
  174. if (!keepNull) {
  175. for (final key in keys) {
  176. if (result[key] == null) result.remove(key);
  177. }
  178. }
  179. return result.isEmpty ? null : result;
  180. }
  181. ///get anti-attr result base on base
  182. static Map<String, dynamic> invertAttributes(
  183. Map<String, dynamic>? attr, Map<String, dynamic>? base) {
  184. attr ??= const {};
  185. base ??= const {};
  186. final baseInverted = base.keys.fold({}, (dynamic memo, key) {
  187. if (base![key] != attr![key] && attr.containsKey(key)) {
  188. memo[key] = base[key];
  189. }
  190. return memo;
  191. });
  192. final inverted =
  193. Map<String, dynamic>.from(attr.keys.fold(baseInverted, (memo, key) {
  194. if (base![key] != attr![key] && !base.containsKey(key)) {
  195. memo[key] = null;
  196. }
  197. return memo;
  198. }));
  199. return inverted;
  200. }
  201. final List<Operation> _operations;
  202. int _modificationCount = 0;
  203. /// Creates [Delta] from de-serialized JSON representation.
  204. ///
  205. /// If `dataDecoder` parameter is not null then it is used to additionally
  206. /// decode the operation's data object. Only applied to insert operations.
  207. static Delta fromJson(List data, {DataDecoder? dataDecoder}) {
  208. return Delta._(data
  209. .map((op) => Operation.fromJson(op, dataDecoder: dataDecoder))
  210. .toList());
  211. }
  212. /// Returns list of operations in this delta.
  213. List<Operation> toList() => List.from(_operations);
  214. /// Returns JSON-serializable version of this delta.
  215. List toJson() => toList();
  216. /// Returns `true` if this delta is empty.
  217. bool get isEmpty => _operations.isEmpty;
  218. /// Returns `true` if this delta is not empty.
  219. bool get isNotEmpty => _operations.isNotEmpty;
  220. /// Returns number of operations in this delta.
  221. int get length => _operations.length;
  222. /// Returns [Operation] at specified [index] in this delta.
  223. Operation operator [](int index) => _operations[index];
  224. /// Returns [Operation] at specified [index] in this delta.
  225. Operation elementAt(int index) => _operations.elementAt(index);
  226. /// Returns the first [Operation] in this delta.
  227. Operation get first => _operations.first;
  228. /// Returns the last [Operation] in this delta.
  229. Operation get last => _operations.last;
  230. @override
  231. bool operator ==(dynamic other) {
  232. if (identical(this, other)) return true;
  233. if (other is! Delta) return false;
  234. final typedOther = other;
  235. const comparator = ListEquality<Operation>(DefaultEquality<Operation>());
  236. return comparator.equals(_operations, typedOther._operations);
  237. }
  238. @override
  239. int get hashCode => hashObjects(_operations);
  240. /// Retain [count] of characters from current position.
  241. void retain(int count, [Map<String, dynamic>? attributes]) {
  242. assert(count >= 0);
  243. if (count == 0) return; // no-op
  244. push(Operation.retain(count, attributes));
  245. }
  246. /// Insert [data] at current position.
  247. void insert(dynamic data, [Map<String, dynamic>? attributes]) {
  248. if (data is String && data.isEmpty) return; // no-op
  249. push(Operation.insert(data, attributes));
  250. }
  251. /// Delete [count] characters from current position.
  252. void delete(int count) {
  253. assert(count >= 0);
  254. if (count == 0) return;
  255. push(Operation.delete(count));
  256. }
  257. void _mergeWithTail(Operation operation) {
  258. assert(isNotEmpty);
  259. assert(last.key == operation.key);
  260. assert(operation.data is String && last.data is String);
  261. final length = operation.length! + last.length!;
  262. final lastText = last.data as String;
  263. final opText = operation.data as String;
  264. final resultText = lastText + opText;
  265. final index = _operations.length;
  266. _operations.replaceRange(index - 1, index, [
  267. Operation._(operation.key, length, resultText, operation.attributes),
  268. ]);
  269. }
  270. /// Pushes new operation into this delta.
  271. ///
  272. /// Performs compaction by composing [operation] with current tail operation
  273. /// of this delta, when possible. For instance, if current tail is
  274. /// `insert('abc')` and pushed operation is `insert('123')` then existing
  275. /// tail is replaced with `insert('abc123')` - a compound result of the two
  276. /// operations.
  277. void push(Operation operation) {
  278. if (operation.isEmpty) return;
  279. var index = _operations.length;
  280. final lastOp = _operations.isNotEmpty ? _operations.last : null;
  281. if (lastOp != null) {
  282. if (lastOp.isDelete && operation.isDelete) {
  283. _mergeWithTail(operation);
  284. return;
  285. }
  286. if (lastOp.isDelete && operation.isInsert) {
  287. index -= 1; // Always insert before deleting
  288. final nLastOp = (index > 0) ? _operations.elementAt(index - 1) : null;
  289. if (nLastOp == null) {
  290. _operations.insert(0, operation);
  291. return;
  292. }
  293. }
  294. if (lastOp.isInsert && operation.isInsert) {
  295. if (lastOp.hasSameAttributes(operation) &&
  296. operation.data is String &&
  297. lastOp.data is String) {
  298. _mergeWithTail(operation);
  299. return;
  300. }
  301. }
  302. if (lastOp.isRetain && operation.isRetain) {
  303. if (lastOp.hasSameAttributes(operation)) {
  304. _mergeWithTail(operation);
  305. return;
  306. }
  307. }
  308. }
  309. if (index == _operations.length) {
  310. _operations.add(operation);
  311. } else {
  312. final opAtIndex = _operations.elementAt(index);
  313. _operations.replaceRange(index, index + 1, [operation, opAtIndex]);
  314. }
  315. _modificationCount++;
  316. }
  317. /// Composes next operation from [thisIter] and [otherIter].
  318. ///
  319. /// Returns new operation or `null` if operations from [thisIter] and
  320. /// [otherIter] nullify each other. For instance, for the pair `insert('abc')`
  321. /// and `delete(3)` composition result would be empty string.
  322. Operation? _composeOperation(
  323. DeltaIterator thisIter, DeltaIterator otherIter) {
  324. if (otherIter.isNextInsert) return otherIter.next();
  325. if (thisIter.isNextDelete) return thisIter.next();
  326. final length = math.min(thisIter.peekLength(), otherIter.peekLength());
  327. final thisOp = thisIter.next(length as int);
  328. final otherOp = otherIter.next(length);
  329. assert(thisOp.length == otherOp.length);
  330. if (otherOp.isRetain) {
  331. final attributes = composeAttributes(
  332. thisOp.attributes,
  333. otherOp.attributes,
  334. keepNull: thisOp.isRetain,
  335. );
  336. if (thisOp.isRetain) {
  337. return Operation.retain(thisOp.length, attributes);
  338. } else if (thisOp.isInsert) {
  339. return Operation.insert(thisOp.data, attributes);
  340. } else {
  341. throw StateError('Unreachable');
  342. }
  343. } else {
  344. // otherOp == delete && thisOp in [retain, insert]
  345. assert(otherOp.isDelete);
  346. if (thisOp.isRetain) return otherOp;
  347. assert(thisOp.isInsert);
  348. // otherOp(delete) + thisOp(insert) => null
  349. }
  350. return null;
  351. }
  352. /// Composes this delta with [other] and returns new [Delta].
  353. ///
  354. /// It is not required for this and [other] delta to represent a document
  355. /// delta (consisting only of insert operations).
  356. Delta compose(Delta other) {
  357. final result = Delta();
  358. final thisIter = DeltaIterator(this);
  359. final otherIter = DeltaIterator(other);
  360. while (thisIter.hasNext || otherIter.hasNext) {
  361. final newOp = _composeOperation(thisIter, otherIter);
  362. if (newOp != null) result.push(newOp);
  363. }
  364. return result..trim();
  365. }
  366. /// Transforms next operation from [otherIter] against next operation in
  367. /// [thisIter].
  368. ///
  369. /// Returns `null` if both operations nullify each other.
  370. Operation? _transformOperation(
  371. DeltaIterator thisIter, DeltaIterator otherIter, bool priority) {
  372. if (thisIter.isNextInsert && (priority || !otherIter.isNextInsert)) {
  373. return Operation.retain(thisIter.next().length);
  374. } else if (otherIter.isNextInsert) {
  375. return otherIter.next();
  376. }
  377. final length = math.min(thisIter.peekLength(), otherIter.peekLength());
  378. final thisOp = thisIter.next(length as int);
  379. final otherOp = otherIter.next(length);
  380. assert(thisOp.length == otherOp.length);
  381. // At this point only delete and retain operations are possible.
  382. if (thisOp.isDelete) {
  383. // otherOp is either delete or retain, so they nullify each other.
  384. return null;
  385. } else if (otherOp.isDelete) {
  386. return otherOp;
  387. } else {
  388. // Retain otherOp which is either retain or insert.
  389. return Operation.retain(
  390. length,
  391. transformAttributes(thisOp.attributes, otherOp.attributes, priority),
  392. );
  393. }
  394. }
  395. /// Transforms [other] delta against operations in this delta.
  396. Delta transform(Delta other, bool priority) {
  397. final result = Delta();
  398. final thisIter = DeltaIterator(this);
  399. final otherIter = DeltaIterator(other);
  400. while (thisIter.hasNext || otherIter.hasNext) {
  401. final newOp = _transformOperation(thisIter, otherIter, priority);
  402. if (newOp != null) result.push(newOp);
  403. }
  404. return result..trim();
  405. }
  406. /// Removes trailing retain operation with empty attributes, if present.
  407. void trim() {
  408. if (isNotEmpty) {
  409. final last = _operations.last;
  410. if (last.isRetain && last.isPlain) _operations.removeLast();
  411. }
  412. }
  413. /// Concatenates [other] with this delta and returns the result.
  414. Delta concat(Delta other) {
  415. final result = Delta.from(this);
  416. if (other.isNotEmpty) {
  417. // In case first operation of other can be merged with last operation in
  418. // our list.
  419. result.push(other._operations.first);
  420. result._operations.addAll(other._operations.sublist(1));
  421. }
  422. return result;
  423. }
  424. /// Inverts this delta against [base].
  425. ///
  426. /// Returns new delta which negates effect of this delta when applied to
  427. /// [base]. This is an equivalent of "undo" operation on deltas.
  428. Delta invert(Delta base) {
  429. final inverted = Delta();
  430. if (base.isEmpty) return inverted;
  431. var baseIndex = 0;
  432. for (final op in _operations) {
  433. if (op.isInsert) {
  434. inverted.delete(op.length!);
  435. } else if (op.isRetain && op.isPlain) {
  436. inverted.retain(op.length!);
  437. baseIndex += op.length!;
  438. } else if (op.isDelete || (op.isRetain && op.isNotPlain)) {
  439. final length = op.length!;
  440. final sliceDelta = base.slice(baseIndex, baseIndex + length);
  441. sliceDelta.toList().forEach((baseOp) {
  442. if (op.isDelete) {
  443. inverted.push(baseOp);
  444. } else if (op.isRetain && op.isNotPlain) {
  445. final invertAttr =
  446. invertAttributes(op.attributes, baseOp.attributes);
  447. inverted.retain(
  448. baseOp.length!, invertAttr.isEmpty ? null : invertAttr);
  449. }
  450. });
  451. baseIndex += length;
  452. } else {
  453. throw StateError('Unreachable');
  454. }
  455. }
  456. inverted.trim();
  457. return inverted;
  458. }
  459. /// Returns slice of this delta from [start] index (inclusive) to [end]
  460. /// (exclusive).
  461. Delta slice(int start, [int? end]) {
  462. final delta = Delta();
  463. var index = 0;
  464. final opIterator = DeltaIterator(this);
  465. final actualEnd = end ?? double.infinity;
  466. while (index < actualEnd && opIterator.hasNext) {
  467. Operation op;
  468. if (index < start) {
  469. op = opIterator.next(start - index);
  470. } else {
  471. op = opIterator.next(actualEnd - index as int);
  472. delta.push(op);
  473. }
  474. index += op.length!;
  475. }
  476. return delta;
  477. }
  478. /// Transforms [index] against this delta.
  479. ///
  480. /// Any "delete" operation before specified [index] shifts it backward, as
  481. /// well as any "insert" operation shifts it forward.
  482. ///
  483. /// The [force] argument is used to resolve scenarios when there is an
  484. /// insert operation at the same position as [index]. If [force] is set to
  485. /// `true` (default) then position is forced to shift forward, otherwise
  486. /// position stays at the same index. In other words setting [force] to
  487. /// `false` gives higher priority to the transformed position.
  488. ///
  489. /// Useful to adjust caret or selection positions.
  490. int transformPosition(int index, {bool force = true}) {
  491. final iter = DeltaIterator(this);
  492. var offset = 0;
  493. while (iter.hasNext && offset <= index) {
  494. final op = iter.next();
  495. if (op.isDelete) {
  496. index -= math.min(op.length!, index - offset);
  497. continue;
  498. } else if (op.isInsert && (offset < index || force)) {
  499. index += op.length!;
  500. }
  501. offset += op.length!;
  502. }
  503. return index;
  504. }
  505. @override
  506. String toString() => _operations.join('\n');
  507. }
  508. /// Specialized iterator for [Delta]s.
  509. class DeltaIterator {
  510. DeltaIterator(this.delta) : _modificationCount = delta._modificationCount;
  511. final Delta delta;
  512. final int _modificationCount;
  513. int _index = 0;
  514. num _offset = 0;
  515. bool get isNextInsert => nextOperationKey == Operation.insertKey;
  516. bool get isNextDelete => nextOperationKey == Operation.deleteKey;
  517. bool get isNextRetain => nextOperationKey == Operation.retainKey;
  518. String? get nextOperationKey {
  519. if (_index < delta.length) {
  520. return delta.elementAt(_index).key;
  521. } else {
  522. return null;
  523. }
  524. }
  525. bool get hasNext => peekLength() < double.infinity;
  526. /// Returns length of next operation without consuming it.
  527. ///
  528. /// Returns [double.infinity] if there is no more operations left to iterate.
  529. num peekLength() {
  530. if (_index < delta.length) {
  531. final operation = delta._operations[_index];
  532. return operation.length! - _offset;
  533. }
  534. return double.infinity;
  535. }
  536. /// Consumes and returns next operation.
  537. ///
  538. /// Optional [length] specifies maximum length of operation to return. Note
  539. /// that actual length of returned operation may be less than specified value.
  540. Operation next([int length = 4294967296]) {
  541. if (_modificationCount != delta._modificationCount) {
  542. throw ConcurrentModificationError(delta);
  543. }
  544. if (_index < delta.length) {
  545. final op = delta.elementAt(_index);
  546. final opKey = op.key;
  547. final opAttributes = op.attributes;
  548. final _currentOffset = _offset;
  549. final actualLength = math.min(op.length! - _currentOffset, length);
  550. if (actualLength == op.length! - _currentOffset) {
  551. _index++;
  552. _offset = 0;
  553. } else {
  554. _offset += actualLength;
  555. }
  556. final opData = op.isInsert && op.data is String
  557. ? (op.data as String).substring(
  558. _currentOffset as int, _currentOffset + (actualLength as int))
  559. : op.data;
  560. final opIsNotEmpty =
  561. opData is String ? opData.isNotEmpty : true; // embeds are never empty
  562. final opLength = opData is String ? opData.length : 1;
  563. final opActualLength = opIsNotEmpty ? opLength : actualLength as int;
  564. return Operation._(opKey, opActualLength, opData, opAttributes);
  565. }
  566. return Operation.retain(length);
  567. }
  568. /// Skips [length] characters in source delta.
  569. ///
  570. /// Returns last skipped operation, or `null` if there was nothing to skip.
  571. Operation? skip(int length) {
  572. var skipped = 0;
  573. Operation? op;
  574. while (skipped < length && hasNext) {
  575. final opLength = peekLength();
  576. final skip = math.min(length - skipped, opLength);
  577. op = next(skip as int);
  578. skipped += op.length!;
  579. }
  580. return op;
  581. }
  582. }