diff --git a/packages/fleather/lib/src/widgets/controller.dart b/packages/fleather/lib/src/widgets/controller.dart index 65a7bc01..6ecb410a 100644 --- a/packages/fleather/lib/src/widgets/controller.dart +++ b/packages/fleather/lib/src/widgets/controller.dart @@ -23,7 +23,7 @@ List _toggleableStyleKeys = [ class FleatherController extends ChangeNotifier { FleatherController({ParchmentDocument? document, AutoFormats? autoFormats}) - : document = document ?? ParchmentDocument(), + : _document = document ?? ParchmentDocument(), _history = HistoryStack.doc(document), _autoFormats = autoFormats ?? AutoFormats.fallback(), _selection = const TextSelection.collapsed(offset: 0) { @@ -33,13 +33,15 @@ class FleatherController extends ChangeNotifier { ); } - /// Document managed by this controller. - final ParchmentDocument document; + ParchmentDocument _document; + + /// Doument managed by this controller. + ParchmentDocument get document => _document; // A list of changes applied to this doc. The changes could be undone or redone. - final HistoryStack _history; + HistoryStack _history; - late final _Throttled _throttledPush; + late _Throttled _throttledPush; Timer? _throttleTimer; // The auto format handler @@ -311,6 +313,32 @@ class FleatherController extends ChangeNotifier { composing: TextRange.empty, ); } + + /// Clear the controller state. + /// + /// It creates a new empty [ParchmentDocument] and a clean edit history. + /// The old [document] will be closed if [closeDocument] is true. + /// + /// Calling this will notify all the listeners of this [FleatherController] + /// that they need to update (it calls [notifyListeners]). For this reason, + /// this method should only be called between frames, e.g. in response to user + /// actions, not during the build, layout, or paint phases. + void clear({bool closeDocument = true}) { + _throttleTimer?.cancel(); + _toggledStyles = ParchmentStyle(); + _selection = const TextSelection.collapsed(offset: 0); + _autoFormats.cancelActive(); + if (closeDocument) { + document.close(); + } + _document = ParchmentDocument(); + _history = HistoryStack.doc(document); + _throttledPush = _throttle( + duration: throttleDuration, + function: _history.push, + ); + notifyListeners(); + } } // This duration was chosen as a best fit for the behavior of Mac, Linux, diff --git a/packages/fleather/test/widgets/controller_test.dart b/packages/fleather/test/widgets/controller_test.dart index 8f8c5bc3..b62c9691 100644 --- a/packages/fleather/test/widgets/controller_test.dart +++ b/packages/fleather/test/widgets/controller_test.dart @@ -274,6 +274,50 @@ void main() { // expect(controller.lastChangeSource, ChangeSource.local); }); + group('clear', () { + test('closes the document by default', () { + fakeAsync((async) { + final doc = controller.document; + controller.compose(Delta()..insert('word'), + selection: const TextSelection.collapsed(offset: 4)); + async.flushTimers(); + var notified = false; + controller.addListener(() => notified = true); + controller.clear(); + expect(identical(controller.document, doc), isFalse); + expect(controller.document.toDelta(), Delta()..insert('\n')); + expect(doc.isClosed, isTrue); + expect( + controller.selection, const TextSelection.collapsed(offset: 0)); + expect(controller.canUndo, isFalse); + expect(controller.canRedo, isFalse); + expect(controller.toggledStyles, ParchmentStyle()); + expect(notified, isTrue); + }); + }); + + test('closeDocument is false', () { + fakeAsync((async) { + final doc = controller.document; + controller.compose(Delta()..insert('word'), + selection: const TextSelection.collapsed(offset: 4)); + async.flushTimers(); + var notified = false; + controller.addListener(() => notified = true); + controller.clear(closeDocument: false); + expect(identical(controller.document, doc), isFalse); + expect(controller.document.toDelta(), Delta()..insert('\n')); + expect(doc.isClosed, isFalse); + expect( + controller.selection, const TextSelection.collapsed(offset: 0)); + expect(controller.canUndo, isFalse); + expect(controller.canRedo, isFalse); + expect(controller.toggledStyles, ParchmentStyle()); + expect(notified, isTrue); + }); + }); + }); + group('history', () { group('empty stack', () { test('undo returns null', () {