Selaa lähdekoodia

Merge pull request #878 from LucasXu0/logger_refactor

feat: [improvements] integrate logging library in AppFlowyEditor
Nathan.fooo 2 vuotta sitten
vanhempi
commit
1d2dd15142
16 muutettua tiedostoa jossa 351 lisäystä ja 39 poistoa
  1. 7 3
      frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart
  2. 1 0
      frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart
  3. 8 2
      frontend/app_flowy/packages/appflowy_editor/lib/src/editor_state.dart
  4. 138 0
      frontend/app_flowy/packages/appflowy_editor/lib/src/infra/log.dart
  5. 0 1
      frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart
  6. 3 1
      frontend/app_flowy/packages/appflowy_editor/lib/src/service/input_service.dart
  7. 5 5
      frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart
  8. 0 1
      frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/delete_text_handler.dart
  9. 2 7
      frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/slash_handler.dart
  10. 3 4
      frontend/app_flowy/packages/appflowy_editor/lib/src/service/keyboard_service.dart
  11. 4 3
      frontend/app_flowy/packages/appflowy_editor/lib/src/service/render_plugin_service.dart
  12. 3 2
      frontend/app_flowy/packages/appflowy_editor/lib/src/service/scroll_service.dart
  13. 4 7
      frontend/app_flowy/packages/appflowy_editor/lib/src/service/selection_service.dart
  14. 3 3
      frontend/app_flowy/packages/appflowy_editor/lib/src/undo_manager.dart
  15. 1 0
      frontend/app_flowy/packages/appflowy_editor/pubspec.yaml
  16. 169 0
      frontend/app_flowy/packages/appflowy_editor/test/infra/log_test.dart

+ 7 - 3
frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart

@@ -97,9 +97,13 @@ class _MyHomePageState extends State<MyHomePage> {
       builder: (context, snapshot) {
       builder: (context, snapshot) {
         if (snapshot.hasData) {
         if (snapshot.hasData) {
           final data = Map<String, Object>.from(json.decode(snapshot.data!));
           final data = Map<String, Object>.from(json.decode(snapshot.data!));
-          return _buildAppFlowyEditor(EditorState(
-            document: StateTree.fromJson(data),
-          ));
+          final editorState = EditorState(document: StateTree.fromJson(data));
+          editorState.logConfiguration
+            ..level = LogLevel.all
+            ..handler = (message) {
+              debugPrint(message);
+            };
+          return _buildAppFlowyEditor(editorState);
         } else {
         } else {
           return const Center(
           return const Center(
             child: CircularProgressIndicator(),
             child: CircularProgressIndicator(),

+ 1 - 0
frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart

@@ -1,6 +1,7 @@
 /// AppFlowyEditor library
 /// AppFlowyEditor library
 library appflowy_editor;
 library appflowy_editor;
 
 
+export 'src/infra/log.dart';
 export 'src/document/node.dart';
 export 'src/document/node.dart';
 export 'src/document/path.dart';
 export 'src/document/path.dart';
 export 'src/document/position.dart';
 export 'src/document/position.dart';

+ 8 - 2
frontend/app_flowy/packages/appflowy_editor/lib/src/editor_state.dart

@@ -1,4 +1,5 @@
 import 'dart:async';
 import 'dart:async';
+import 'package:appflowy_editor/src/infra/log.dart';
 import 'package:appflowy_editor/src/service/service.dart';
 import 'package:appflowy_editor/src/service/service.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 
 
@@ -48,10 +49,15 @@ class EditorState {
   // Service reference.
   // Service reference.
   final service = FlowyService();
   final service = FlowyService();
 
 
+  /// Configures log output parameters,
+  /// such as log level and log output callbacks,
+  /// with this variable.
+  LogConfiguration get logConfiguration => LogConfiguration();
+
   final UndoManager undoManager = UndoManager();
   final UndoManager undoManager = UndoManager();
   Selection? _cursorSelection;
   Selection? _cursorSelection;
 
 
-  /// TODO: only for testing.
+  // TODO: only for testing.
   bool disableSealTimer = false;
   bool disableSealTimer = false;
 
 
   Selection? get cursorSelection {
   Selection? get cursorSelection {
@@ -120,7 +126,7 @@ class EditorState {
     _debouncedSealHistoryItemTimer =
     _debouncedSealHistoryItemTimer =
         Timer(const Duration(milliseconds: 1000), () {
         Timer(const Duration(milliseconds: 1000), () {
       if (undoManager.undoStack.isNonEmpty) {
       if (undoManager.undoStack.isNonEmpty) {
-        debugPrint('Seal history item');
+        Log.editor.debug('Seal history item');
         final last = undoManager.undoStack.last;
         final last = undoManager.undoStack.last;
         last.seal();
         last.seal();
       }
       }

+ 138 - 0
frontend/app_flowy/packages/appflowy_editor/lib/src/infra/log.dart

@@ -0,0 +1,138 @@
+import 'package:logging/logging.dart';
+
+enum LogLevel {
+  off,
+  error,
+  warn,
+  info,
+  debug,
+  all,
+}
+
+typedef LogHandler = void Function(String message);
+
+/// Manages log service for [AppFlowyEditor]
+///
+/// Set the log level and config the handler depending on your need.
+class LogConfiguration {
+  LogConfiguration._() {
+    Logger.root.onRecord.listen((record) {
+      if (handler != null) {
+        handler!(
+          '[${record.level.toLogLevel().name}][${record.loggerName}]: ${record.time}: ${record.message}',
+        );
+      }
+    });
+  }
+
+  factory LogConfiguration() => _logConfiguration;
+
+  static final LogConfiguration _logConfiguration = LogConfiguration._();
+
+  LogHandler? handler;
+
+  LogLevel _level = LogLevel.off;
+
+  LogLevel get level => _level;
+  set level(LogLevel level) {
+    _level = level;
+    Logger.root.level = level.toLevel();
+  }
+}
+
+/// For logging message in AppFlowyEditor
+class Log {
+  Log._({
+    required this.name,
+  }) : _logger = Logger(name);
+
+  final String name;
+  late final Logger _logger;
+
+  /// For logging message related to [AppFlowyEditor].
+  ///
+  /// For example, uses the logger when registering plugins
+  ///   or handling something related to [EditorState].
+  static Log editor = Log._(name: 'editor');
+
+  /// For logging message related to [AppFlowySelectionService].
+  ///
+  /// For example, uses the logger when updating or clearing selection.
+  static Log selection = Log._(name: 'selection');
+
+  /// For logging message related to [AppFlowyKeyboardService].
+  ///
+  /// For example, uses the logger when processing shortcut events.
+  static Log keyboard = Log._(name: 'keyboard');
+
+  /// For logging message related to [AppFlowyInputService].
+  ///
+  /// For example, uses the logger when processing text inputs.
+  static Log input = Log._(name: 'input');
+
+  /// For logging message related to [AppFlowyScrollService].
+  ///
+  /// For example, uses the logger when processing scroll events.
+  static Log scroll = Log._(name: 'scroll');
+
+  /// For logging message related to UI.
+  ///
+  /// For example, uses the logger when building the widget.
+  static Log ui = Log._(name: 'ui');
+
+  void error(String message) => _logger.severe(message);
+  void warn(String message) => _logger.warning(message);
+  void info(String message) => _logger.info(message);
+  void debug(String message) => _logger.fine(message);
+}
+
+extension on LogLevel {
+  Level toLevel() {
+    switch (this) {
+      case LogLevel.off:
+        return Level.OFF;
+      case LogLevel.error:
+        return Level.SEVERE;
+      case LogLevel.warn:
+        return Level.WARNING;
+      case LogLevel.info:
+        return Level.INFO;
+      case LogLevel.debug:
+        return Level.FINE;
+      case LogLevel.all:
+        return Level.ALL;
+    }
+  }
+
+  String get name {
+    switch (this) {
+      case LogLevel.off:
+        return 'OFF';
+      case LogLevel.error:
+        return 'ERROR';
+      case LogLevel.warn:
+        return 'WARN';
+      case LogLevel.info:
+        return 'INFO';
+      case LogLevel.debug:
+        return 'DEBUG';
+      case LogLevel.all:
+        return 'ALL';
+    }
+  }
+}
+
+extension on Level {
+  LogLevel toLogLevel() {
+    if (this == Level.SEVERE) {
+      return LogLevel.error;
+    } else if (this == Level.WARNING) {
+      return LogLevel.warn;
+    } else if (this == Level.INFO) {
+      return LogLevel.info;
+    } else if (this == Level.FINE) {
+      return LogLevel.debug;
+    }
+    return LogLevel.off;
+  }
+}

+ 0 - 1
frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart

@@ -83,7 +83,6 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
                 name: check ? 'check' : 'uncheck',
                 name: check ? 'check' : 'uncheck',
               ),
               ),
               onTap: () {
               onTap: () {
-                debugPrint('[Checkbox] onTap...');
                 TransactionBuilder(widget.editorState)
                 TransactionBuilder(widget.editorState)
                   ..updateNode(widget.textNode, {
                   ..updateNode(widget.textNode, {
                     StyleKey.checkbox: !check,
                     StyleKey.checkbox: !check,

+ 3 - 1
frontend/app_flowy/packages/appflowy_editor/lib/src/service/input_service.dart

@@ -1,3 +1,4 @@
+import 'package:appflowy_editor/src/infra/log.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter/services.dart';
 
 
@@ -243,7 +244,8 @@ class _AppFlowyInputState extends State<AppFlowyInput>
 
 
   @override
   @override
   void updateEditingValueWithDeltas(List<TextEditingDelta> textEditingDeltas) {
   void updateEditingValueWithDeltas(List<TextEditingDelta> textEditingDeltas) {
-    debugPrint(textEditingDeltas.map((delta) => delta.toString()).toString());
+    Log.input
+        .debug(textEditingDeltas.map((delta) => delta.toString()).toString());
 
 
     apply(textEditingDeltas);
     apply(textEditingDeltas);
   }
   }

+ 5 - 5
frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart

@@ -1,6 +1,7 @@
 import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:appflowy_editor/src/infra/html_converter.dart';
 import 'package:appflowy_editor/src/infra/html_converter.dart';
 import 'package:appflowy_editor/src/document/node_iterator.dart';
 import 'package:appflowy_editor/src/document/node_iterator.dart';
+import 'package:appflowy_editor/src/infra/log.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter/services.dart';
 import 'package:rich_clipboard/rich_clipboard.dart';
 import 'package:rich_clipboard/rich_clipboard.dart';
@@ -19,10 +20,10 @@ _handleCopy(EditorState editorState) async {
               startOffset: selection.start.offset,
               startOffset: selection.start.offset,
               endOffset: selection.end.offset)
               endOffset: selection.end.offset)
           .toHTMLString();
           .toHTMLString();
-      debugPrint('copy html: $htmlString');
+      Log.keyboard.debug('copy html: $htmlString');
       RichClipboard.setData(RichClipboardData(html: htmlString));
       RichClipboard.setData(RichClipboardData(html: htmlString));
     } else {
     } else {
-      debugPrint("unimplemented: copy non-text");
+      Log.keyboard.debug('unimplemented: copy non-text');
     }
     }
     return;
     return;
   }
   }
@@ -37,7 +38,7 @@ _handleCopy(EditorState editorState) async {
           startOffset: selection.start.offset,
           startOffset: selection.start.offset,
           endOffset: selection.end.offset)
           endOffset: selection.end.offset)
       .toHTMLString();
       .toHTMLString();
-  debugPrint('copy html: $copyString');
+  Log.keyboard.debug('copy html: $copyString');
   RichClipboard.setData(RichClipboardData(html: copyString));
   RichClipboard.setData(RichClipboardData(html: copyString));
 }
 }
 
 
@@ -54,7 +55,7 @@ _pasteHTML(EditorState editorState, String html) {
     return;
     return;
   }
   }
 
 
-  debugPrint('paste html: $html');
+  Log.keyboard.debug('paste html: $html');
   final nodes = HTMLToNodesConverter(html).toNodes();
   final nodes = HTMLToNodesConverter(html).toNodes();
 
 
   if (nodes.isEmpty) {
   if (nodes.isEmpty) {
@@ -250,7 +251,6 @@ _handlePastePlainText(EditorState editorState, String plainText) {
 /// 1. copy the selected content
 /// 1. copy the selected content
 /// 2. delete selected content
 /// 2. delete selected content
 _handleCut(EditorState editorState) {
 _handleCut(EditorState editorState) {
-  debugPrint('cut');
   _handleCopy(editorState);
   _handleCopy(editorState);
   _deleteSelectedContent(editorState);
   _deleteSelectedContent(editorState);
 }
 }

+ 0 - 1
frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/delete_text_handler.dart

@@ -97,7 +97,6 @@ KeyEventResult _handleDelete(EditorState editorState, RawKeyEvent event) {
   if (textNodes.length == 1) {
   if (textNodes.length == 1) {
     final textNode = textNodes.first;
     final textNode = textNodes.first;
     if (selection.start.offset >= textNode.delta.length) {
     if (selection.start.offset >= textNode.delta.length) {
-      debugPrint("merge next line");
       final nextNode = textNode.next;
       final nextNode = textNode.next;
       if (nextNode == null) {
       if (nextNode == null) {
         return KeyEventResult.ignored;
         return KeyEventResult.ignored;

+ 2 - 7
frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/slash_handler.dart

@@ -3,6 +3,7 @@ import 'dart:math';
 import 'package:appflowy_editor/src/document/node.dart';
 import 'package:appflowy_editor/src/document/node.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
 import 'package:appflowy_editor/src/infra/flowy_svg.dart';
 import 'package:appflowy_editor/src/infra/flowy_svg.dart';
+import 'package:appflowy_editor/src/infra/log.dart';
 import 'package:appflowy_editor/src/operation/transaction_builder.dart';
 import 'package:appflowy_editor/src/operation/transaction_builder.dart';
 import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
 import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
 import 'package:appflowy_editor/src/service/default_text_operations/format_rich_text_style.dart';
 import 'package:appflowy_editor/src/service/default_text_operations/format_rich_text_style.dart';
@@ -50,12 +51,6 @@ final List<PopupListItem> _popupListItems = [
     icon: _popupListIcon('bullets'),
     icon: _popupListIcon('bullets'),
     handler: (editorState) => insertBulletedListAfterSelection(editorState),
     handler: (editorState) => insertBulletedListAfterSelection(editorState),
   ),
   ),
-  // PopupListItem(
-  //   text: 'Numbered list',
-  //   keywords: ['numbered list'],
-  //   icon: _popupListIcon('number'),
-  //   handler: (editorState) => debugPrint('Not implement yet!'),
-  // ),
   PopupListItem(
   PopupListItem(
     text: 'To-do List',
     text: 'To-do List',
     keywords: ['checkbox', 'todo'],
     keywords: ['checkbox', 'todo'],
@@ -293,7 +288,7 @@ class _PopupListWidgetState extends State<PopupListWidget> {
   }
   }
 
 
   KeyEventResult _onKey(FocusNode node, RawKeyEvent event) {
   KeyEventResult _onKey(FocusNode node, RawKeyEvent event) {
-    debugPrint('slash on key $event');
+    Log.keyboard.debug('slash command, on key $event');
     if (event is! RawKeyDownEvent) {
     if (event is! RawKeyDownEvent) {
       return KeyEventResult.ignored;
       return KeyEventResult.ignored;
     }
     }

+ 3 - 4
frontend/app_flowy/packages/appflowy_editor/lib/src/service/keyboard_service.dart

@@ -1,4 +1,5 @@
 import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:appflowy_editor/src/infra/log.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter/services.dart';
 
 
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
@@ -94,15 +95,13 @@ class _AppFlowyKeyboardState extends State<AppFlowyKeyboard>
 
 
   @override
   @override
   KeyEventResult onKey(RawKeyEvent event) {
   KeyEventResult onKey(RawKeyEvent event) {
-    debugPrint('on keyboard event $event');
+    Log.keyboard.debug('on keyboard event $event');
 
 
     if (event is! RawKeyDownEvent) {
     if (event is! RawKeyDownEvent) {
       return KeyEventResult.ignored;
       return KeyEventResult.ignored;
     }
     }
 
 
     for (final handler in widget.handlers) {
     for (final handler in widget.handlers) {
-      // debugPrint('handle keyboard event $event by $handler');
-
       KeyEventResult result = handler(widget.editorState, event);
       KeyEventResult result = handler(widget.editorState, event);
 
 
       switch (result) {
       switch (result) {
@@ -119,7 +118,7 @@ class _AppFlowyKeyboardState extends State<AppFlowyKeyboard>
   }
   }
 
 
   void _onFocusChange(bool value) {
   void _onFocusChange(bool value) {
-    debugPrint('[KeyBoard Service] focus change $value');
+    Log.keyboard.debug('on keyboard event focus change $value');
   }
   }
 
 
   KeyEventResult _onKey(FocusNode node, RawKeyEvent event) {
   KeyEventResult _onKey(FocusNode node, RawKeyEvent event) {

+ 4 - 3
frontend/app_flowy/packages/appflowy_editor/lib/src/service/render_plugin_service.dart

@@ -1,5 +1,6 @@
 import 'package:appflowy_editor/src/document/node.dart';
 import 'package:appflowy_editor/src/document/node.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
+import 'package:appflowy_editor/src/infra/log.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
 import 'package:provider/provider.dart';
 
 
@@ -86,7 +87,7 @@ class AppFlowyRenderPlugin extends AppFlowyRenderPluginService {
 
 
   @override
   @override
   void register(String name, NodeWidgetBuilder builder) {
   void register(String name, NodeWidgetBuilder builder) {
-    debugPrint('[Plugins] registering $name...');
+    Log.editor.info('registers plugin($name)...');
     _validatePlugin(name);
     _validatePlugin(name);
     _builders[name] = builder;
     _builders[name] = builder;
   }
   }
@@ -111,7 +112,7 @@ class AppFlowyRenderPlugin extends AppFlowyRenderPluginService {
           builder: (_, child) {
           builder: (_, child) {
             return Consumer<TextNode>(
             return Consumer<TextNode>(
               builder: ((_, value, child) {
               builder: ((_, value, child) {
-                debugPrint('Text Node is rebuilding...');
+                Log.ui.debug('TextNode is rebuilding...');
                 return builder.build(context);
                 return builder.build(context);
               }),
               }),
             );
             );
@@ -122,7 +123,7 @@ class AppFlowyRenderPlugin extends AppFlowyRenderPluginService {
           builder: (_, child) {
           builder: (_, child) {
             return Consumer<Node>(
             return Consumer<Node>(
               builder: ((_, value, child) {
               builder: ((_, value, child) {
-                debugPrint('Node is rebuilding...');
+                Log.ui.debug('Node is rebuilding...');
                 return builder.build(context);
                 return builder.build(context);
               }),
               }),
             );
             );

+ 3 - 2
frontend/app_flowy/packages/appflowy_editor/lib/src/service/scroll_service.dart

@@ -1,3 +1,4 @@
+import 'package:appflowy_editor/src/infra/log.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:appflowy_editor/src/extensions/object_extensions.dart';
 import 'package:appflowy_editor/src/extensions/object_extensions.dart';
@@ -113,13 +114,13 @@ class _AppFlowyScrollState extends State<AppFlowyScroll>
   @override
   @override
   void disable() {
   void disable() {
     _scrollEnabled = false;
     _scrollEnabled = false;
-    debugPrint('[scroll] $_scrollEnabled');
+    Log.scroll.debug('disable scroll service');
   }
   }
 
 
   @override
   @override
   void enable() {
   void enable() {
     _scrollEnabled = true;
     _scrollEnabled = true;
-    debugPrint('[scroll] $_scrollEnabled');
+    Log.scroll.debug('enable scroll service');
   }
   }
 
 
   void _onPointerSignal(PointerSignalEvent event) {
   void _onPointerSignal(PointerSignalEvent event) {

+ 4 - 7
frontend/app_flowy/packages/appflowy_editor/lib/src/service/selection_service.dart

@@ -1,3 +1,4 @@
+import 'package:appflowy_editor/src/infra/log.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 
 
 import 'package:appflowy_editor/src/document/node.dart';
 import 'package:appflowy_editor/src/document/node.dart';
@@ -185,12 +186,12 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
 
 
     if (selection != null) {
     if (selection != null) {
       if (selection.isCollapsed) {
       if (selection.isCollapsed) {
-        /// updates cursor area.
-        debugPrint('updating cursor, $selection');
+        // updates cursor area.
+        Log.selection.debug('update cursor area, $selection');
         _updateCursorAreas(selection.start);
         _updateCursorAreas(selection.start);
       } else {
       } else {
         // updates selection area.
         // updates selection area.
-        debugPrint('updating selection, $selection');
+        Log.selection.debug('update cursor area, $selection');
         _updateSelectionAreas(selection);
         _updateSelectionAreas(selection);
       }
       }
     }
     }
@@ -312,14 +313,10 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
 
 
     // compute the selection in range.
     // compute the selection in range.
     if (first != null && last != null) {
     if (first != null && last != null) {
-      bool isDownward = (identical(first, last))
-          ? panStartOffset.dx < panEndOffset.dx
-          : panStartOffset.dy < panEndOffset.dy;
       final start =
       final start =
           first.getSelectionInRange(panStartOffset, panEndOffset).start;
           first.getSelectionInRange(panStartOffset, panEndOffset).start;
       final end = last.getSelectionInRange(panStartOffset, panEndOffset).end;
       final end = last.getSelectionInRange(panStartOffset, panEndOffset).end;
       final selection = Selection(start: start, end: end);
       final selection = Selection(start: start, end: end);
-      debugPrint('[_onPanUpdate] isDownward = $isDownward, $selection');
       updateSelection(selection);
       updateSelection(selection);
     }
     }
 
 

+ 3 - 3
frontend/app_flowy/packages/appflowy_editor/lib/src/undo_manager.dart

@@ -1,11 +1,11 @@
 import 'dart:collection';
 import 'dart:collection';
 
 
 import 'package:appflowy_editor/src/document/selection.dart';
 import 'package:appflowy_editor/src/document/selection.dart';
+import 'package:appflowy_editor/src/infra/log.dart';
 import 'package:appflowy_editor/src/operation/operation.dart';
 import 'package:appflowy_editor/src/operation/operation.dart';
 import 'package:appflowy_editor/src/operation/transaction_builder.dart';
 import 'package:appflowy_editor/src/operation/transaction_builder.dart';
 import 'package:appflowy_editor/src/operation/transaction.dart';
 import 'package:appflowy_editor/src/operation/transaction.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
-import 'package:flutter/foundation.dart';
 
 
 /// A [HistoryItem] contains list of operations committed by users.
 /// A [HistoryItem] contains list of operations committed by users.
 /// If a [HistoryItem] is not sealed, operations can be added sequentially.
 /// If a [HistoryItem] is not sealed, operations can be added sequentially.
@@ -112,7 +112,7 @@ class UndoManager {
   }
   }
 
 
   undo() {
   undo() {
-    debugPrint('undo');
+    Log.editor.debug('undo');
     final s = state;
     final s = state;
     if (s == null) {
     if (s == null) {
       return;
       return;
@@ -131,7 +131,7 @@ class UndoManager {
   }
   }
 
 
   redo() {
   redo() {
-    debugPrint('redo');
+    Log.editor.debug('redo');
     final s = state;
     final s = state;
     if (s == null) {
     if (s == null) {
       return;
       return;

+ 1 - 0
frontend/app_flowy/packages/appflowy_editor/pubspec.yaml

@@ -16,6 +16,7 @@ dependencies:
   flutter_svg: ^1.1.1+1
   flutter_svg: ^1.1.1+1
   provider: ^6.0.3
   provider: ^6.0.3
   url_launcher: ^6.1.5
   url_launcher: ^6.1.5
+  logging: ^1.0.2
 
 
 dev_dependencies:
 dev_dependencies:
   flutter_test:
   flutter_test:

+ 169 - 0
frontend/app_flowy/packages/appflowy_editor/test/infra/log_test.dart

@@ -0,0 +1,169 @@
+import 'package:appflowy_editor/src/infra/log.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'test_editor.dart';
+
+void main() async {
+  group('log.dart', () {
+    testWidgets('test LogConfiguration in EditorState', (tester) async {
+      TestWidgetsFlutterBinding.ensureInitialized();
+
+      const text = 'Welcome to Appflowy 😁';
+
+      final List<String> logs = [];
+
+      final editor = tester.editor;
+      editor.editorState.logConfiguration
+        ..level = LogLevel.all
+        ..handler = (message) {
+          logs.add(message);
+        };
+
+      Log.editor.debug(text);
+      expect(logs.last.contains('DEBUG'), true);
+      expect(logs.length, 1);
+    });
+
+    test('test LogLevel.all', () {
+      const text = 'Welcome to Appflowy 😁';
+
+      final List<String> logs = [];
+      LogConfiguration()
+        ..level = LogLevel.all
+        ..handler = (message) {
+          logs.add(message);
+        };
+
+      Log.editor.debug(text);
+      expect(logs.last.contains('DEBUG'), true);
+      Log.editor.info(text);
+      expect(logs.last.contains('INFO'), true);
+      Log.editor.warn(text);
+      expect(logs.last.contains('WARN'), true);
+      Log.editor.error(text);
+      expect(logs.last.contains('ERROR'), true);
+
+      expect(logs.length, 4);
+    });
+
+    test('test LogLevel.off', () {
+      const text = 'Welcome to Appflowy 😁';
+
+      final List<String> logs = [];
+      LogConfiguration()
+        ..level = LogLevel.off
+        ..handler = (message) {
+          logs.add(message);
+        };
+
+      Log.editor.debug(text);
+      Log.editor.info(text);
+      Log.editor.warn(text);
+      Log.editor.error(text);
+
+      expect(logs.length, 0);
+    });
+
+    test('test LogLevel.error', () {
+      const text = 'Welcome to Appflowy 😁';
+
+      final List<String> logs = [];
+      LogConfiguration()
+        ..level = LogLevel.error
+        ..handler = (message) {
+          logs.add(message);
+        };
+
+      Log.editor.debug(text);
+      Log.editor.info(text);
+      Log.editor.warn(text);
+      Log.editor.error(text);
+
+      expect(logs.length, 1);
+    });
+
+    test('test LogLevel.warn', () {
+      const text = 'Welcome to Appflowy 😁';
+
+      final List<String> logs = [];
+      LogConfiguration()
+        ..level = LogLevel.warn
+        ..handler = (message) {
+          logs.add(message);
+        };
+
+      Log.editor.debug(text);
+      Log.editor.info(text);
+      Log.editor.warn(text);
+      Log.editor.error(text);
+
+      expect(logs.length, 2);
+    });
+
+    test('test LogLevel.info', () {
+      const text = 'Welcome to Appflowy 😁';
+
+      final List<String> logs = [];
+      LogConfiguration()
+        ..level = LogLevel.info
+        ..handler = (message) {
+          logs.add(message);
+        };
+
+      Log.editor.debug(text);
+      Log.editor.info(text);
+      Log.editor.warn(text);
+      Log.editor.error(text);
+
+      expect(logs.length, 3);
+    });
+
+    test('test LogLevel.debug', () {
+      const text = 'Welcome to Appflowy 😁';
+
+      final List<String> logs = [];
+      LogConfiguration()
+        ..level = LogLevel.debug
+        ..handler = (message) {
+          logs.add(message);
+        };
+
+      Log.editor.debug(text);
+      Log.editor.info(text);
+      Log.editor.warn(text);
+      Log.editor.error(text);
+
+      expect(logs.length, 4);
+    });
+
+    test('test logger', () {
+      const text = 'Welcome to Appflowy 😁';
+
+      final List<String> logs = [];
+      LogConfiguration()
+        ..level = LogLevel.all
+        ..handler = (message) {
+          logs.add(message);
+        };
+
+      Log.editor.debug(text);
+      expect(logs.last.contains('editor'), true);
+
+      Log.selection.debug(text);
+      expect(logs.last.contains('selection'), true);
+
+      Log.keyboard.debug(text);
+      expect(logs.last.contains('keyboard'), true);
+
+      Log.input.debug(text);
+      expect(logs.last.contains('input'), true);
+
+      Log.scroll.debug(text);
+      expect(logs.last.contains('scroll'), true);
+
+      Log.ui.debug(text);
+      expect(logs.last.contains('ui'), true);
+
+      expect(logs.length, 6);
+    });
+  });
+}