undo_manager.dart 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  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. class HistoryItem extends LinkedListEntry<HistoryItem> {
  8. final List<Operation> operations = [];
  9. Selection? beforeSelection;
  10. Selection? afterSelection;
  11. bool _sealed = false;
  12. HistoryItem();
  13. seal() {
  14. _sealed = true;
  15. }
  16. add(Operation op) {
  17. operations.add(op);
  18. }
  19. addAll(Iterable<Operation> iterable) {
  20. operations.addAll(iterable);
  21. }
  22. bool get sealed {
  23. return _sealed;
  24. }
  25. Transaction toTransaction(EditorState state) {
  26. final builder = TransactionBuilder(state);
  27. for (var i = operations.length - 1; i >= 0; i--) {
  28. final operation = operations[i];
  29. final inverted = operation.invert();
  30. builder.add(inverted);
  31. }
  32. builder.afterSelection = beforeSelection;
  33. builder.beforeSelection = afterSelection;
  34. return builder.finish();
  35. }
  36. }
  37. class FixedSizeStack {
  38. final _list = LinkedList<HistoryItem>();
  39. final int maxSize;
  40. FixedSizeStack(this.maxSize);
  41. push(HistoryItem stackItem) {
  42. if (_list.length >= maxSize) {
  43. _list.remove(_list.first);
  44. }
  45. _list.add(stackItem);
  46. }
  47. HistoryItem? pop() {
  48. if (_list.isEmpty) {
  49. return null;
  50. }
  51. final last = _list.last;
  52. _list.remove(last);
  53. return last;
  54. }
  55. HistoryItem get last {
  56. return _list.last;
  57. }
  58. bool get isEmpty {
  59. return _list.isEmpty;
  60. }
  61. bool get isNonEmpty {
  62. return _list.isNotEmpty;
  63. }
  64. }
  65. class UndoManager {
  66. final undoStack = FixedSizeStack(20);
  67. final redoStack = FixedSizeStack(20);
  68. EditorState? state;
  69. HistoryItem getUndoHistoryItem() {
  70. if (undoStack.isEmpty) {
  71. final item = HistoryItem();
  72. undoStack.push(item);
  73. return item;
  74. }
  75. final last = undoStack.last;
  76. if (last.sealed) {
  77. final item = HistoryItem();
  78. undoStack.push(item);
  79. return item;
  80. }
  81. return last;
  82. }
  83. undo() {
  84. final historyItem = undoStack.pop();
  85. if (historyItem == null) {
  86. return;
  87. }
  88. final transaction = historyItem.toTransaction(state!);
  89. state!.apply(transaction, const ApplyOptions(noLog: true));
  90. }
  91. }