undo_manager.dart 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  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. import 'package:flutter/foundation.dart';
  8. /// This class contains operations committed by users.
  9. /// If a [HistoryItem] is not sealed, operations can be added sequentially.
  10. /// Otherwise, the operations should be added to a new [HistoryItem].
  11. class HistoryItem extends LinkedListEntry<HistoryItem> {
  12. final List<Operation> operations = [];
  13. Selection? beforeSelection;
  14. Selection? afterSelection;
  15. bool _sealed = false;
  16. HistoryItem();
  17. seal() {
  18. _sealed = true;
  19. }
  20. bool get sealed => _sealed;
  21. add(Operation op) {
  22. operations.add(op);
  23. }
  24. addAll(Iterable<Operation> iterable) {
  25. operations.addAll(iterable);
  26. }
  27. Transaction toTransaction(EditorState state) {
  28. final builder = TransactionBuilder(state);
  29. for (var i = operations.length - 1; i >= 0; i--) {
  30. final operation = operations[i];
  31. final inverted = operation.invert();
  32. builder.add(inverted);
  33. }
  34. builder.afterSelection = beforeSelection;
  35. builder.beforeSelection = afterSelection;
  36. return builder.finish();
  37. }
  38. }
  39. class FixedSizeStack {
  40. final _list = LinkedList<HistoryItem>();
  41. final int maxSize;
  42. FixedSizeStack(this.maxSize);
  43. push(HistoryItem stackItem) {
  44. if (_list.length >= maxSize) {
  45. _list.remove(_list.first);
  46. }
  47. _list.add(stackItem);
  48. }
  49. HistoryItem? pop() {
  50. if (_list.isEmpty) {
  51. return null;
  52. }
  53. final last = _list.last;
  54. _list.remove(last);
  55. return last;
  56. }
  57. clear() {
  58. _list.clear();
  59. }
  60. HistoryItem get last => _list.last;
  61. bool get isEmpty => _list.isEmpty;
  62. bool get isNonEmpty => _list.isNotEmpty;
  63. }
  64. class UndoManager {
  65. final FixedSizeStack undoStack;
  66. final FixedSizeStack redoStack;
  67. EditorState? state;
  68. UndoManager([int stackSize = 20])
  69. : undoStack = FixedSizeStack(stackSize),
  70. redoStack = FixedSizeStack(stackSize);
  71. HistoryItem getUndoHistoryItem() {
  72. if (undoStack.isEmpty) {
  73. final item = HistoryItem();
  74. undoStack.push(item);
  75. return item;
  76. }
  77. final last = undoStack.last;
  78. if (last.sealed) {
  79. redoStack.clear();
  80. final item = HistoryItem();
  81. undoStack.push(item);
  82. return item;
  83. }
  84. return last;
  85. }
  86. undo() {
  87. debugPrint('undo');
  88. final s = state;
  89. if (s == null) {
  90. return;
  91. }
  92. final historyItem = undoStack.pop();
  93. if (historyItem == null) {
  94. return;
  95. }
  96. final transaction = historyItem.toTransaction(s);
  97. s.apply(
  98. transaction,
  99. const ApplyOptions(
  100. recordUndo: false,
  101. recordRedo: true,
  102. ));
  103. }
  104. redo() {
  105. debugPrint('redo');
  106. final s = state;
  107. if (s == null) {
  108. return;
  109. }
  110. final historyItem = redoStack.pop();
  111. if (historyItem == null) {
  112. return;
  113. }
  114. final transaction = historyItem.toTransaction(s);
  115. s.apply(
  116. transaction,
  117. const ApplyOptions(
  118. recordUndo: true,
  119. recordRedo: false,
  120. ));
  121. }
  122. }