undo_manager.dart 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import 'dart:collection';
  2. import 'package:flowy_editor/document/selection.dart';
  3. import 'package:flowy_editor/operation/operation.dart';
  4. import 'package:flowy_editor/operation/transaction_builder.dart';
  5. import 'package:flowy_editor/operation/transaction.dart';
  6. import 'package:flowy_editor/editor_state.dart';
  7. /// This class contains operations committed by users.
  8. /// If a [HistoryItem] is not sealed, operations can be added sequentially.
  9. /// Otherwise, the operations should be added to a new [HistoryItem].
  10. class HistoryItem extends LinkedListEntry<HistoryItem> {
  11. final List<Operation> operations = [];
  12. Selection? beforeSelection;
  13. Selection? afterSelection;
  14. bool _sealed = false;
  15. HistoryItem();
  16. seal() {
  17. _sealed = true;
  18. }
  19. bool get sealed => _sealed;
  20. add(Operation op) {
  21. operations.add(op);
  22. }
  23. addAll(Iterable<Operation> iterable) {
  24. operations.addAll(iterable);
  25. }
  26. Transaction toTransaction(EditorState state) {
  27. final builder = TransactionBuilder(state);
  28. for (var i = operations.length - 1; i >= 0; i--) {
  29. final operation = operations[i];
  30. final inverted = operation.invert();
  31. builder.add(inverted);
  32. }
  33. builder.afterSelection = beforeSelection;
  34. builder.beforeSelection = afterSelection;
  35. return builder.finish();
  36. }
  37. }
  38. class FixedSizeStack {
  39. final _list = LinkedList<HistoryItem>();
  40. final int maxSize;
  41. FixedSizeStack(this.maxSize);
  42. push(HistoryItem stackItem) {
  43. if (_list.length >= maxSize) {
  44. _list.remove(_list.first);
  45. }
  46. _list.add(stackItem);
  47. }
  48. HistoryItem? pop() {
  49. if (_list.isEmpty) {
  50. return null;
  51. }
  52. final last = _list.last;
  53. _list.remove(last);
  54. return last;
  55. }
  56. HistoryItem get last => _list.last;
  57. bool get isEmpty => _list.isEmpty;
  58. bool get isNonEmpty => _list.isNotEmpty;
  59. }
  60. class UndoManager {
  61. final FixedSizeStack undoStack;
  62. final FixedSizeStack redoStack;
  63. EditorState? state;
  64. UndoManager([int stackSize = 20])
  65. : undoStack = FixedSizeStack(stackSize),
  66. redoStack = FixedSizeStack(stackSize);
  67. HistoryItem getUndoHistoryItem() {
  68. if (undoStack.isEmpty) {
  69. final item = HistoryItem();
  70. undoStack.push(item);
  71. return item;
  72. }
  73. final last = undoStack.last;
  74. if (last.sealed) {
  75. final item = HistoryItem();
  76. undoStack.push(item);
  77. return item;
  78. }
  79. return last;
  80. }
  81. undo() {
  82. final s = state;
  83. if (s == null) {
  84. return;
  85. }
  86. final historyItem = undoStack.pop();
  87. if (historyItem == null) {
  88. return;
  89. }
  90. final transaction = historyItem.toTransaction(s);
  91. s.apply(transaction, const ApplyOptions(recordUndo: false));
  92. }
  93. }