Browse Source

config editor for doc

appflowy 3 years ago
parent
commit
c05b772fe6
42 changed files with 1749 additions and 340 deletions
  1. 42 0
      app_flowy/lib/workspace/application/view/doc_watch_bloc.dart
  2. 542 0
      app_flowy/lib/workspace/application/view/doc_watch_bloc.freezed.dart
  3. 327 0
      app_flowy/lib/workspace/application/view/view_bloc.freezed.dart
  4. 8 0
      app_flowy/lib/workspace/domain/i_doc.dart
  5. 1 1
      app_flowy/lib/workspace/domain/page_stack/page_stack.dart
  6. 4 0
      app_flowy/lib/workspace/infrastructure/deps_resolver.dart
  7. 50 0
      app_flowy/lib/workspace/infrastructure/i_doc_impl.dart
  8. 33 0
      app_flowy/lib/workspace/infrastructure/i_view_impl.dart
  9. 10 3
      app_flowy/lib/workspace/infrastructure/repos/doc_repo.dart
  10. 51 0
      app_flowy/lib/workspace/presentation/doc/doc_page.dart
  11. 0 38
      app_flowy/lib/workspace/presentation/doc/doc_widget.dart
  12. 54 0
      app_flowy/lib/workspace/presentation/doc/editor_widget.dart
  13. 24 7
      app_flowy/packages/flowy_sdk/lib/dispatch/code_gen.dart
  14. 31 47
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-editor/doc_create.pb.dart
  15. 11 12
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-editor/doc_create.pbjson.dart
  16. 61 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-editor/doc_query.pb.dart
  17. 11 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-editor/doc_query.pbjson.dart
  18. 4 2
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-editor/errors.pbenum.dart
  19. 3 2
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-editor/errors.pbjson.dart
  20. 4 2
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-editor/event.pbenum.dart
  21. 3 2
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-editor/event.pbjson.dart
  22. 3 2
      rust-lib/flowy-derive/src/derive_cache/derive_cache.rs
  23. 2 5
      rust-lib/flowy-editor/src/entities/doc/doc_create.rs
  24. 42 1
      rust-lib/flowy-editor/src/entities/doc/doc_query.rs
  25. 12 0
      rust-lib/flowy-editor/src/entities/doc/parser/doc_path.rs
  26. 2 0
      rust-lib/flowy-editor/src/entities/doc/parser/mod.rs
  27. 6 3
      rust-lib/flowy-editor/src/errors.rs
  28. 10 6
      rust-lib/flowy-editor/src/event.rs
  29. 20 9
      rust-lib/flowy-editor/src/handlers/doc_handler.rs
  30. 2 1
      rust-lib/flowy-editor/src/module.rs
  31. 63 124
      rust-lib/flowy-editor/src/protobuf/model/doc_create.rs
  32. 215 6
      rust-lib/flowy-editor/src/protobuf/model/doc_query.rs
  33. 36 31
      rust-lib/flowy-editor/src/protobuf/model/errors.rs
  34. 19 13
      rust-lib/flowy-editor/src/protobuf/model/event.rs
  35. 3 4
      rust-lib/flowy-editor/src/protobuf/proto/doc_create.proto
  36. 4 0
      rust-lib/flowy-editor/src/protobuf/proto/doc_query.proto
  37. 2 1
      rust-lib/flowy-editor/src/protobuf/proto/errors.proto
  38. 2 1
      rust-lib/flowy-editor/src/protobuf/proto/event.proto
  39. 5 5
      rust-lib/flowy-editor/src/services/doc_controller.rs
  40. 4 4
      rust-lib/flowy-editor/src/sql_tables/doc/doc_table.rs
  41. 2 2
      rust-lib/flowy-editor/tests/editor/doc_test.rs
  42. 21 6
      rust-lib/flowy-editor/tests/editor/helper.rs

+ 42 - 0
app_flowy/lib/workspace/application/view/doc_watch_bloc.dart

@@ -0,0 +1,42 @@
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:app_flowy/workspace/domain/i_doc.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:flowy_sdk/protobuf/flowy-editor/errors.pb.dart';
+part 'doc_watch_bloc.freezed.dart';
+
+class DocWatchBloc extends Bloc<DocWatchEvent, DocWatchState> {
+  final IDoc iDocImpl;
+
+  DocWatchBloc({
+    required this.iDocImpl,
+  }) : super(const DocWatchState.loading());
+
+  @override
+  Stream<DocWatchState> mapEventToState(DocWatchEvent event) async* {
+    yield* event.map(
+      started: (_) async* {
+        yield* _readDoc();
+      },
+    );
+  }
+
+  Stream<DocWatchState> _readDoc() async* {
+    final docOrFail = await iDocImpl.readDoc();
+    yield docOrFail.fold(
+      (doc) => DocWatchState.loadDoc(doc),
+      (error) => DocWatchState.loadFail(error),
+    );
+  }
+}
+
+@freezed
+class DocWatchEvent with _$DocWatchEvent {
+  const factory DocWatchEvent.started() = Started;
+}
+
+@freezed
+class DocWatchState with _$DocWatchState {
+  const factory DocWatchState.loading() = Loading;
+  const factory DocWatchState.loadDoc(Doc doc) = LoadDoc;
+  const factory DocWatchState.loadFail(EditorError error) = LoadFail;
+}

+ 542 - 0
app_flowy/lib/workspace/application/view/doc_watch_bloc.freezed.dart

@@ -0,0 +1,542 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides
+
+part of 'doc_watch_bloc.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity<T>(T value) => value;
+
+final _privateConstructorUsedError = UnsupportedError(
+    'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
+
+/// @nodoc
+class _$DocWatchEventTearOff {
+  const _$DocWatchEventTearOff();
+
+  Started started() {
+    return const Started();
+  }
+}
+
+/// @nodoc
+const $DocWatchEvent = _$DocWatchEventTearOff();
+
+/// @nodoc
+mixin _$DocWatchEvent {
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() started,
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? started,
+    required TResult orElse(),
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(Started value) started,
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(Started value)? started,
+    required TResult orElse(),
+  }) =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $DocWatchEventCopyWith<$Res> {
+  factory $DocWatchEventCopyWith(
+          DocWatchEvent value, $Res Function(DocWatchEvent) then) =
+      _$DocWatchEventCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class _$DocWatchEventCopyWithImpl<$Res>
+    implements $DocWatchEventCopyWith<$Res> {
+  _$DocWatchEventCopyWithImpl(this._value, this._then);
+
+  final DocWatchEvent _value;
+  // ignore: unused_field
+  final $Res Function(DocWatchEvent) _then;
+}
+
+/// @nodoc
+abstract class $StartedCopyWith<$Res> {
+  factory $StartedCopyWith(Started value, $Res Function(Started) then) =
+      _$StartedCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class _$StartedCopyWithImpl<$Res> extends _$DocWatchEventCopyWithImpl<$Res>
+    implements $StartedCopyWith<$Res> {
+  _$StartedCopyWithImpl(Started _value, $Res Function(Started) _then)
+      : super(_value, (v) => _then(v as Started));
+
+  @override
+  Started get _value => super._value as Started;
+}
+
+/// @nodoc
+
+class _$Started implements Started {
+  const _$Started();
+
+  @override
+  String toString() {
+    return 'DocWatchEvent.started()';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) || (other is Started);
+  }
+
+  @override
+  int get hashCode => runtimeType.hashCode;
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() started,
+  }) {
+    return started();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? started,
+    required TResult orElse(),
+  }) {
+    if (started != null) {
+      return started();
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(Started value) started,
+  }) {
+    return started(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(Started value)? started,
+    required TResult orElse(),
+  }) {
+    if (started != null) {
+      return started(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class Started implements DocWatchEvent {
+  const factory Started() = _$Started;
+}
+
+/// @nodoc
+class _$DocWatchStateTearOff {
+  const _$DocWatchStateTearOff();
+
+  Loading loading() {
+    return const Loading();
+  }
+
+  LoadDoc loadDoc(Doc doc) {
+    return LoadDoc(
+      doc,
+    );
+  }
+
+  LoadFail loadFail(EditorError error) {
+    return LoadFail(
+      error,
+    );
+  }
+}
+
+/// @nodoc
+const $DocWatchState = _$DocWatchStateTearOff();
+
+/// @nodoc
+mixin _$DocWatchState {
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() loading,
+    required TResult Function(Doc doc) loadDoc,
+    required TResult Function(EditorError error) loadFail,
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? loading,
+    TResult Function(Doc doc)? loadDoc,
+    TResult Function(EditorError error)? loadFail,
+    required TResult orElse(),
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(Loading value) loading,
+    required TResult Function(LoadDoc value) loadDoc,
+    required TResult Function(LoadFail value) loadFail,
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(Loading value)? loading,
+    TResult Function(LoadDoc value)? loadDoc,
+    TResult Function(LoadFail value)? loadFail,
+    required TResult orElse(),
+  }) =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $DocWatchStateCopyWith<$Res> {
+  factory $DocWatchStateCopyWith(
+          DocWatchState value, $Res Function(DocWatchState) then) =
+      _$DocWatchStateCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class _$DocWatchStateCopyWithImpl<$Res>
+    implements $DocWatchStateCopyWith<$Res> {
+  _$DocWatchStateCopyWithImpl(this._value, this._then);
+
+  final DocWatchState _value;
+  // ignore: unused_field
+  final $Res Function(DocWatchState) _then;
+}
+
+/// @nodoc
+abstract class $LoadingCopyWith<$Res> {
+  factory $LoadingCopyWith(Loading value, $Res Function(Loading) then) =
+      _$LoadingCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class _$LoadingCopyWithImpl<$Res> extends _$DocWatchStateCopyWithImpl<$Res>
+    implements $LoadingCopyWith<$Res> {
+  _$LoadingCopyWithImpl(Loading _value, $Res Function(Loading) _then)
+      : super(_value, (v) => _then(v as Loading));
+
+  @override
+  Loading get _value => super._value as Loading;
+}
+
+/// @nodoc
+
+class _$Loading implements Loading {
+  const _$Loading();
+
+  @override
+  String toString() {
+    return 'DocWatchState.loading()';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) || (other is Loading);
+  }
+
+  @override
+  int get hashCode => runtimeType.hashCode;
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() loading,
+    required TResult Function(Doc doc) loadDoc,
+    required TResult Function(EditorError error) loadFail,
+  }) {
+    return loading();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? loading,
+    TResult Function(Doc doc)? loadDoc,
+    TResult Function(EditorError error)? loadFail,
+    required TResult orElse(),
+  }) {
+    if (loading != null) {
+      return loading();
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(Loading value) loading,
+    required TResult Function(LoadDoc value) loadDoc,
+    required TResult Function(LoadFail value) loadFail,
+  }) {
+    return loading(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(Loading value)? loading,
+    TResult Function(LoadDoc value)? loadDoc,
+    TResult Function(LoadFail value)? loadFail,
+    required TResult orElse(),
+  }) {
+    if (loading != null) {
+      return loading(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class Loading implements DocWatchState {
+  const factory Loading() = _$Loading;
+}
+
+/// @nodoc
+abstract class $LoadDocCopyWith<$Res> {
+  factory $LoadDocCopyWith(LoadDoc value, $Res Function(LoadDoc) then) =
+      _$LoadDocCopyWithImpl<$Res>;
+  $Res call({Doc doc});
+}
+
+/// @nodoc
+class _$LoadDocCopyWithImpl<$Res> extends _$DocWatchStateCopyWithImpl<$Res>
+    implements $LoadDocCopyWith<$Res> {
+  _$LoadDocCopyWithImpl(LoadDoc _value, $Res Function(LoadDoc) _then)
+      : super(_value, (v) => _then(v as LoadDoc));
+
+  @override
+  LoadDoc get _value => super._value as LoadDoc;
+
+  @override
+  $Res call({
+    Object? doc = freezed,
+  }) {
+    return _then(LoadDoc(
+      doc == freezed
+          ? _value.doc
+          : doc // ignore: cast_nullable_to_non_nullable
+              as Doc,
+    ));
+  }
+}
+
+/// @nodoc
+
+class _$LoadDoc implements LoadDoc {
+  const _$LoadDoc(this.doc);
+
+  @override
+  final Doc doc;
+
+  @override
+  String toString() {
+    return 'DocWatchState.loadDoc(doc: $doc)';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) ||
+        (other is LoadDoc &&
+            (identical(other.doc, doc) ||
+                const DeepCollectionEquality().equals(other.doc, doc)));
+  }
+
+  @override
+  int get hashCode =>
+      runtimeType.hashCode ^ const DeepCollectionEquality().hash(doc);
+
+  @JsonKey(ignore: true)
+  @override
+  $LoadDocCopyWith<LoadDoc> get copyWith =>
+      _$LoadDocCopyWithImpl<LoadDoc>(this, _$identity);
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() loading,
+    required TResult Function(Doc doc) loadDoc,
+    required TResult Function(EditorError error) loadFail,
+  }) {
+    return loadDoc(doc);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? loading,
+    TResult Function(Doc doc)? loadDoc,
+    TResult Function(EditorError error)? loadFail,
+    required TResult orElse(),
+  }) {
+    if (loadDoc != null) {
+      return loadDoc(doc);
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(Loading value) loading,
+    required TResult Function(LoadDoc value) loadDoc,
+    required TResult Function(LoadFail value) loadFail,
+  }) {
+    return loadDoc(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(Loading value)? loading,
+    TResult Function(LoadDoc value)? loadDoc,
+    TResult Function(LoadFail value)? loadFail,
+    required TResult orElse(),
+  }) {
+    if (loadDoc != null) {
+      return loadDoc(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class LoadDoc implements DocWatchState {
+  const factory LoadDoc(Doc doc) = _$LoadDoc;
+
+  Doc get doc => throw _privateConstructorUsedError;
+  @JsonKey(ignore: true)
+  $LoadDocCopyWith<LoadDoc> get copyWith => throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $LoadFailCopyWith<$Res> {
+  factory $LoadFailCopyWith(LoadFail value, $Res Function(LoadFail) then) =
+      _$LoadFailCopyWithImpl<$Res>;
+  $Res call({EditorError error});
+}
+
+/// @nodoc
+class _$LoadFailCopyWithImpl<$Res> extends _$DocWatchStateCopyWithImpl<$Res>
+    implements $LoadFailCopyWith<$Res> {
+  _$LoadFailCopyWithImpl(LoadFail _value, $Res Function(LoadFail) _then)
+      : super(_value, (v) => _then(v as LoadFail));
+
+  @override
+  LoadFail get _value => super._value as LoadFail;
+
+  @override
+  $Res call({
+    Object? error = freezed,
+  }) {
+    return _then(LoadFail(
+      error == freezed
+          ? _value.error
+          : error // ignore: cast_nullable_to_non_nullable
+              as EditorError,
+    ));
+  }
+}
+
+/// @nodoc
+
+class _$LoadFail implements LoadFail {
+  const _$LoadFail(this.error);
+
+  @override
+  final EditorError error;
+
+  @override
+  String toString() {
+    return 'DocWatchState.loadFail(error: $error)';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) ||
+        (other is LoadFail &&
+            (identical(other.error, error) ||
+                const DeepCollectionEquality().equals(other.error, error)));
+  }
+
+  @override
+  int get hashCode =>
+      runtimeType.hashCode ^ const DeepCollectionEquality().hash(error);
+
+  @JsonKey(ignore: true)
+  @override
+  $LoadFailCopyWith<LoadFail> get copyWith =>
+      _$LoadFailCopyWithImpl<LoadFail>(this, _$identity);
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() loading,
+    required TResult Function(Doc doc) loadDoc,
+    required TResult Function(EditorError error) loadFail,
+  }) {
+    return loadFail(error);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? loading,
+    TResult Function(Doc doc)? loadDoc,
+    TResult Function(EditorError error)? loadFail,
+    required TResult orElse(),
+  }) {
+    if (loadFail != null) {
+      return loadFail(error);
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(Loading value) loading,
+    required TResult Function(LoadDoc value) loadDoc,
+    required TResult Function(LoadFail value) loadFail,
+  }) {
+    return loadFail(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(Loading value)? loading,
+    TResult Function(LoadDoc value)? loadDoc,
+    TResult Function(LoadFail value)? loadFail,
+    required TResult orElse(),
+  }) {
+    if (loadFail != null) {
+      return loadFail(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class LoadFail implements DocWatchState {
+  const factory LoadFail(EditorError error) = _$LoadFail;
+
+  EditorError get error => throw _privateConstructorUsedError;
+  @JsonKey(ignore: true)
+  $LoadFailCopyWith<LoadFail> get copyWith =>
+      throw _privateConstructorUsedError;
+}

+ 327 - 0
app_flowy/lib/workspace/application/view/view_bloc.freezed.dart

@@ -0,0 +1,327 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides
+
+part of 'view_bloc.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity<T>(T value) => value;
+
+final _privateConstructorUsedError = UnsupportedError(
+    'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
+
+/// @nodoc
+class _$ViewEventTearOff {
+  const _$ViewEventTearOff();
+
+  Initial initial() {
+    return const Initial();
+  }
+}
+
+/// @nodoc
+const $ViewEvent = _$ViewEventTearOff();
+
+/// @nodoc
+mixin _$ViewEvent {
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() initial,
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? initial,
+    required TResult orElse(),
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(Initial value) initial,
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(Initial value)? initial,
+    required TResult orElse(),
+  }) =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $ViewEventCopyWith<$Res> {
+  factory $ViewEventCopyWith(ViewEvent value, $Res Function(ViewEvent) then) =
+      _$ViewEventCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class _$ViewEventCopyWithImpl<$Res> implements $ViewEventCopyWith<$Res> {
+  _$ViewEventCopyWithImpl(this._value, this._then);
+
+  final ViewEvent _value;
+  // ignore: unused_field
+  final $Res Function(ViewEvent) _then;
+}
+
+/// @nodoc
+abstract class $InitialCopyWith<$Res> {
+  factory $InitialCopyWith(Initial value, $Res Function(Initial) then) =
+      _$InitialCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class _$InitialCopyWithImpl<$Res> extends _$ViewEventCopyWithImpl<$Res>
+    implements $InitialCopyWith<$Res> {
+  _$InitialCopyWithImpl(Initial _value, $Res Function(Initial) _then)
+      : super(_value, (v) => _then(v as Initial));
+
+  @override
+  Initial get _value => super._value as Initial;
+}
+
+/// @nodoc
+
+class _$Initial implements Initial {
+  const _$Initial();
+
+  @override
+  String toString() {
+    return 'ViewEvent.initial()';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) || (other is Initial);
+  }
+
+  @override
+  int get hashCode => runtimeType.hashCode;
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() initial,
+  }) {
+    return initial();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? initial,
+    required TResult orElse(),
+  }) {
+    if (initial != null) {
+      return initial();
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(Initial value) initial,
+  }) {
+    return initial(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(Initial value)? initial,
+    required TResult orElse(),
+  }) {
+    if (initial != null) {
+      return initial(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class Initial implements ViewEvent {
+  const factory Initial() = _$Initial;
+}
+
+/// @nodoc
+class _$ViewStateTearOff {
+  const _$ViewStateTearOff();
+
+  _ViewState call(
+      {required bool isLoading,
+      required Option<View> view,
+      required Either<Unit, WorkspaceError> successOrFailure}) {
+    return _ViewState(
+      isLoading: isLoading,
+      view: view,
+      successOrFailure: successOrFailure,
+    );
+  }
+}
+
+/// @nodoc
+const $ViewState = _$ViewStateTearOff();
+
+/// @nodoc
+mixin _$ViewState {
+  bool get isLoading => throw _privateConstructorUsedError;
+  Option<View> get view => throw _privateConstructorUsedError;
+  Either<Unit, WorkspaceError> get successOrFailure =>
+      throw _privateConstructorUsedError;
+
+  @JsonKey(ignore: true)
+  $ViewStateCopyWith<ViewState> get copyWith =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $ViewStateCopyWith<$Res> {
+  factory $ViewStateCopyWith(ViewState value, $Res Function(ViewState) then) =
+      _$ViewStateCopyWithImpl<$Res>;
+  $Res call(
+      {bool isLoading,
+      Option<View> view,
+      Either<Unit, WorkspaceError> successOrFailure});
+}
+
+/// @nodoc
+class _$ViewStateCopyWithImpl<$Res> implements $ViewStateCopyWith<$Res> {
+  _$ViewStateCopyWithImpl(this._value, this._then);
+
+  final ViewState _value;
+  // ignore: unused_field
+  final $Res Function(ViewState) _then;
+
+  @override
+  $Res call({
+    Object? isLoading = freezed,
+    Object? view = freezed,
+    Object? successOrFailure = freezed,
+  }) {
+    return _then(_value.copyWith(
+      isLoading: isLoading == freezed
+          ? _value.isLoading
+          : isLoading // ignore: cast_nullable_to_non_nullable
+              as bool,
+      view: view == freezed
+          ? _value.view
+          : view // ignore: cast_nullable_to_non_nullable
+              as Option<View>,
+      successOrFailure: successOrFailure == freezed
+          ? _value.successOrFailure
+          : successOrFailure // ignore: cast_nullable_to_non_nullable
+              as Either<Unit, WorkspaceError>,
+    ));
+  }
+}
+
+/// @nodoc
+abstract class _$ViewStateCopyWith<$Res> implements $ViewStateCopyWith<$Res> {
+  factory _$ViewStateCopyWith(
+          _ViewState value, $Res Function(_ViewState) then) =
+      __$ViewStateCopyWithImpl<$Res>;
+  @override
+  $Res call(
+      {bool isLoading,
+      Option<View> view,
+      Either<Unit, WorkspaceError> successOrFailure});
+}
+
+/// @nodoc
+class __$ViewStateCopyWithImpl<$Res> extends _$ViewStateCopyWithImpl<$Res>
+    implements _$ViewStateCopyWith<$Res> {
+  __$ViewStateCopyWithImpl(_ViewState _value, $Res Function(_ViewState) _then)
+      : super(_value, (v) => _then(v as _ViewState));
+
+  @override
+  _ViewState get _value => super._value as _ViewState;
+
+  @override
+  $Res call({
+    Object? isLoading = freezed,
+    Object? view = freezed,
+    Object? successOrFailure = freezed,
+  }) {
+    return _then(_ViewState(
+      isLoading: isLoading == freezed
+          ? _value.isLoading
+          : isLoading // ignore: cast_nullable_to_non_nullable
+              as bool,
+      view: view == freezed
+          ? _value.view
+          : view // ignore: cast_nullable_to_non_nullable
+              as Option<View>,
+      successOrFailure: successOrFailure == freezed
+          ? _value.successOrFailure
+          : successOrFailure // ignore: cast_nullable_to_non_nullable
+              as Either<Unit, WorkspaceError>,
+    ));
+  }
+}
+
+/// @nodoc
+
+class _$_ViewState implements _ViewState {
+  const _$_ViewState(
+      {required this.isLoading,
+      required this.view,
+      required this.successOrFailure});
+
+  @override
+  final bool isLoading;
+  @override
+  final Option<View> view;
+  @override
+  final Either<Unit, WorkspaceError> successOrFailure;
+
+  @override
+  String toString() {
+    return 'ViewState(isLoading: $isLoading, view: $view, successOrFailure: $successOrFailure)';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) ||
+        (other is _ViewState &&
+            (identical(other.isLoading, isLoading) ||
+                const DeepCollectionEquality()
+                    .equals(other.isLoading, isLoading)) &&
+            (identical(other.view, view) ||
+                const DeepCollectionEquality().equals(other.view, view)) &&
+            (identical(other.successOrFailure, successOrFailure) ||
+                const DeepCollectionEquality()
+                    .equals(other.successOrFailure, successOrFailure)));
+  }
+
+  @override
+  int get hashCode =>
+      runtimeType.hashCode ^
+      const DeepCollectionEquality().hash(isLoading) ^
+      const DeepCollectionEquality().hash(view) ^
+      const DeepCollectionEquality().hash(successOrFailure);
+
+  @JsonKey(ignore: true)
+  @override
+  _$ViewStateCopyWith<_ViewState> get copyWith =>
+      __$ViewStateCopyWithImpl<_ViewState>(this, _$identity);
+}
+
+abstract class _ViewState implements ViewState {
+  const factory _ViewState(
+      {required bool isLoading,
+      required Option<View> view,
+      required Either<Unit, WorkspaceError> successOrFailure}) = _$_ViewState;
+
+  @override
+  bool get isLoading => throw _privateConstructorUsedError;
+  @override
+  Option<View> get view => throw _privateConstructorUsedError;
+  @override
+  Either<Unit, WorkspaceError> get successOrFailure =>
+      throw _privateConstructorUsedError;
+  @override
+  @JsonKey(ignore: true)
+  _$ViewStateCopyWith<_ViewState> get copyWith =>
+      throw _privateConstructorUsedError;
+}

+ 8 - 0
app_flowy/lib/workspace/domain/i_doc.dart

@@ -1,7 +1,15 @@
+import 'package:flowy_editor/flowy_editor.dart';
 import 'package:flowy_sdk/protobuf/flowy-editor/doc_create.pb.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/protobuf/flowy-editor/errors.pb.dart';
 
+class Doc {
+  final DocInfo info;
+  final Document data;
+
+  Doc({required this.info, required this.data});
+}
+
 abstract class IDoc {
   Future<Either<Doc, EditorError>> readDoc();
   Future<Either<Unit, EditorError>> updateDoc(

+ 1 - 1
app_flowy/lib/workspace/domain/page_stack/page_stack.dart

@@ -1,5 +1,5 @@
 import 'package:app_flowy/workspace/domain/page_stack/page_stack_bloc.dart';
-import 'package:app_flowy/workspace/presentation/doc/doc_widget.dart';
+import 'package:app_flowy/workspace/presentation/doc/doc_page.dart';
 import 'package:equatable/equatable.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
 import 'package:flutter/material.dart';

+ 4 - 0
app_flowy/lib/workspace/infrastructure/deps_resolver.dart

@@ -2,6 +2,7 @@ import 'package:app_flowy/workspace/application/app/app_bloc.dart';
 import 'package:app_flowy/workspace/application/app/app_watch_bloc.dart';
 import 'package:app_flowy/workspace/application/menu/menu_bloc.dart';
 import 'package:app_flowy/workspace/application/menu/menu_watch.dart';
+import 'package:app_flowy/workspace/application/view/doc_watch_bloc.dart';
 import 'package:app_flowy/workspace/application/view/view_bloc.dart';
 import 'package:app_flowy/workspace/domain/i_doc.dart';
 import 'package:app_flowy/workspace/domain/i_view.dart';
@@ -58,6 +59,9 @@ class HomeDepsResolver {
     getIt.registerFactoryParam<ViewBloc, String, void>(
         (viewId, _) => ViewBloc(iViewImpl: getIt<IView>(param1: viewId)));
 
+    getIt.registerFactoryParam<DocWatchBloc, String, void>(
+        (docId, _) => DocWatchBloc(iDocImpl: getIt<IDoc>(param1: docId)));
+
     // getIt.registerFactoryParam<ViewBloc, String, void>(
     //     (viewId, _) => ViewBloc(iViewImpl: getIt<IView>(param1: viewId)));
   }

+ 50 - 0
app_flowy/lib/workspace/infrastructure/i_doc_impl.dart

@@ -0,0 +1,50 @@
+import 'dart:convert';
+
+import 'package:app_flowy/workspace/domain/i_doc.dart';
+import 'package:app_flowy/workspace/infrastructure/repos/doc_repo.dart';
+import 'package:flowy_editor/flowy_editor.dart';
+import 'package:flowy_sdk/protobuf/flowy-editor/errors.pb.dart';
+import 'package:dartz/dartz.dart';
+
+class IDocImpl extends IDoc {
+  DocRepository repo;
+
+  IDocImpl({required this.repo});
+
+  @override
+  Future<Either<Unit, EditorError>> closeDoc() {
+    return repo.closeDoc();
+  }
+
+  @override
+  Future<Either<Doc, EditorError>> readDoc() async {
+    final docInfoOrFail = await repo.readDoc();
+    return docInfoOrFail.fold(
+      (info) => _loadDocument(info.path).then((result) => result.fold(
+          (document) => left(Doc(info: info, data: document)),
+          (error) => right(error))),
+      (error) => right(error),
+    );
+  }
+
+  @override
+  Future<Either<Unit, EditorError>> updateDoc(
+      {String? name, String? desc, String? text}) {
+    return repo.updateDoc(name: name, desc: desc, text: text);
+  }
+
+  Future<Either<Document, EditorError>> _loadDocument(String path) {
+    return repo.readDocData(path).then((docDataOrFail) {
+      return docDataOrFail.fold(
+        (docData) => left(_decodeToDocument(docData.text)),
+        (error) => right(error),
+      );
+    });
+  }
+
+  Document _decodeToDocument(String text) {
+    final json = jsonDecode(text);
+    final document = Document.fromJson(json);
+    return document;
+  }
+}

+ 33 - 0
app_flowy/lib/workspace/infrastructure/i_view_impl.dart

@@ -0,0 +1,33 @@
+import 'package:app_flowy/workspace/domain/i_view.dart';
+import 'package:app_flowy/workspace/infrastructure/repos/view_repo.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
+import 'package:dartz/dartz.dart';
+
+class IViewImpl extends IView {
+  ViewRepository repo;
+
+  IViewImpl({required this.repo});
+
+  @override
+  Future<Either<View, WorkspaceError>> readView() {
+    return repo.readView();
+  }
+}
+
+class IViewWatchImpl extends IViewWatch {
+  final ViewWatchRepository repo;
+  IViewWatchImpl({
+    required this.repo,
+  });
+
+  @override
+  void startWatching({ViewUpdatedCallback? updatedCallback}) {
+    repo.startWatching(updatedCallback: updatedCallback);
+  }
+
+  @override
+  Future<void> stopWatching() async {
+    await repo.close();
+  }
+}

+ 10 - 3
app_flowy/lib/workspace/infrastructure/repos/doc_repo.dart

@@ -11,16 +11,23 @@ class DocRepository {
     required this.docId,
   });
 
-  Future<Either<DocDescription, EditorError>> createDoc(
+  Future<Either<DocInfo, EditorError>> createDoc(
       {required String name, String? desc}) {
     final request = CreateDocRequest(id: docId, name: name, desc: desc);
 
     return EditorEventCreateDoc(request).send();
   }
 
-  Future<Either<Doc, EditorError>> readDoc() {
+  Future<Either<DocInfo, EditorError>> readDoc() {
     final request = QueryDocRequest.create()..docId = docId;
-    return EditorEventReadDoc(request).send();
+    return EditorEventReadDocInfo(request).send();
+  }
+
+  Future<Either<DocData, EditorError>> readDocData(String path) {
+    final request = QueryDocDataRequest.create()
+      ..docId = docId
+      ..path = path;
+    return EditorEventReadDocData(request).send();
   }
 
   Future<Either<Unit, EditorError>> updateDoc(

+ 51 - 0
app_flowy/lib/workspace/presentation/doc/doc_page.dart

@@ -0,0 +1,51 @@
+import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/workspace/application/view/doc_watch_bloc.dart';
+import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
+import 'package:app_flowy/workspace/presentation/doc/editor_widget.dart';
+import 'package:flowy_infra_ui/widget/error_page.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class DocPage extends HomeStackWidget {
+  const DocPage({Key? key, required DocPageStackView stackView})
+      : super(key: key, stackView: stackView);
+
+  @override
+  _DocPageState createState() => _DocPageState();
+}
+
+class _DocPageState extends State<DocPage> {
+  @override
+  Widget build(BuildContext context) {
+    final stackView = widget.stackView as DocPageStackView;
+    return MultiBlocProvider(
+      providers: [
+        BlocProvider<DocWatchBloc>(
+            create: (context) => getIt<DocWatchBloc>(param1: stackView.view.id)
+              ..add(const DocWatchEvent.started())),
+      ],
+      child:
+          BlocBuilder<DocWatchBloc, DocWatchState>(builder: (context, state) {
+        assert(widget.stackView is DocPageStackView);
+        return state.map(
+          loading: (_) => const CircularProgressIndicator.adaptive(),
+          loadDoc: (s) => EditorWdiget(doc: s.doc),
+          loadFail: (s) => FlowyErrorPage(s.error.toString()),
+        );
+      }),
+    );
+  }
+}
+
+class DocPageStackView extends HomeStackView {
+  final View view;
+  DocPageStackView(this.view)
+      : super(
+          type: view.viewType,
+          title: view.name,
+        );
+
+  @override
+  List<Object> get props => [view.id, type];
+}

+ 0 - 38
app_flowy/lib/workspace/presentation/doc/doc_widget.dart

@@ -1,38 +0,0 @@
-import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
-import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
-import 'package:flutter/material.dart';
-
-class DocPageStackView extends HomeStackView {
-  final View view;
-  DocPageStackView(this.view)
-      : super(
-          type: view.viewType,
-          title: view.name,
-        );
-
-  @override
-  List<Object> get props => [view.id, type];
-}
-
-class DocPage extends HomeStackWidget {
-  const DocPage({Key? key, required DocPageStackView stackView})
-      : super(key: key, stackView: stackView);
-
-  @override
-  _DocPageState createState() => _DocPageState();
-}
-
-class _DocPageState extends State<DocPage> {
-  @override
-  Widget build(BuildContext context) {
-    assert(widget.stackView is DocPageStackView);
-
-    final context = widget.stackView as DocPageStackView;
-    final filename = _extractFilename(context.view.id);
-    return Container();
-  }
-
-  String _extractFilename(String viewId) {
-    return viewId.replaceFirst('doc_', '');
-  }
-}

+ 54 - 0
app_flowy/lib/workspace/presentation/doc/editor_widget.dart

@@ -0,0 +1,54 @@
+import 'dart:io';
+
+import 'package:app_flowy/workspace/domain/i_doc.dart';
+import 'package:flowy_editor/flowy_editor.dart';
+import 'package:flutter/material.dart';
+
+class EditorWdiget extends StatelessWidget {
+  final FocusNode _focusNode = FocusNode();
+  final Doc doc;
+
+  EditorWdiget({Key? key, required this.doc}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final controller = EditorController(
+      document: doc.data,
+      selection: const TextSelection.collapsed(offset: 0),
+    );
+
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.spaceBetween,
+      children: [
+        _renderEditor(controller),
+        _renderToolbar(controller),
+      ],
+    );
+  }
+
+  Widget _renderEditor(EditorController controller) {
+    final editor = FlowyEditor(
+      controller: controller,
+      focusNode: _focusNode,
+      scrollable: true,
+      autoFocus: false,
+      expands: false,
+      padding: const EdgeInsets.symmetric(horizontal: 8.0),
+      readOnly: false,
+      scrollBottomInset: 0,
+      scrollController: ScrollController(),
+    );
+    return Expanded(child: editor);
+  }
+
+  Widget _renderToolbar(EditorController controller) {
+    return FlowyToolbar.basic(
+      controller: controller,
+      onImageSelectCallback: _onImageSelection,
+    );
+  }
+
+  Future<String> _onImageSelection(File file) {
+    throw UnimplementedError();
+  }
+}

+ 24 - 7
app_flowy/packages/flowy_sdk/lib/dispatch/code_gen.dart

@@ -6,14 +6,14 @@ class EditorEventCreateDoc {
      CreateDocRequest request;
      EditorEventCreateDoc(this.request);
 
-    Future<Either<DocDescription, EditorError>> send() {
+    Future<Either<DocInfo, EditorError>> send() {
     final request = FFIRequest.create()
           ..event = EditorEvent.CreateDoc.toString()
           ..payload = requestToBytes(this.request);
 
     return Dispatch.asyncRequest(request)
         .then((bytesResult) => bytesResult.fold(
-           (okBytes) => left(DocDescription.fromBuffer(okBytes)),
+           (okBytes) => left(DocInfo.fromBuffer(okBytes)),
            (errBytes) => right(EditorError.fromBuffer(errBytes)),
         ));
     }
@@ -36,18 +36,35 @@ class EditorEventUpdateDoc {
     }
 }
 
-class EditorEventReadDoc {
+class EditorEventReadDocInfo {
      QueryDocRequest request;
-     EditorEventReadDoc(this.request);
+     EditorEventReadDocInfo(this.request);
 
-    Future<Either<Doc, EditorError>> send() {
+    Future<Either<DocInfo, EditorError>> send() {
     final request = FFIRequest.create()
-          ..event = EditorEvent.ReadDoc.toString()
+          ..event = EditorEvent.ReadDocInfo.toString()
           ..payload = requestToBytes(this.request);
 
     return Dispatch.asyncRequest(request)
         .then((bytesResult) => bytesResult.fold(
-           (okBytes) => left(Doc.fromBuffer(okBytes)),
+           (okBytes) => left(DocInfo.fromBuffer(okBytes)),
+           (errBytes) => right(EditorError.fromBuffer(errBytes)),
+        ));
+    }
+}
+
+class EditorEventReadDocData {
+     QueryDocDataRequest request;
+     EditorEventReadDocData(this.request);
+
+    Future<Either<DocData, EditorError>> send() {
+    final request = FFIRequest.create()
+          ..event = EditorEvent.ReadDocData.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(DocData.fromBuffer(okBytes)),
            (errBytes) => right(EditorError.fromBuffer(errBytes)),
         ));
     }

+ 31 - 47
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-editor/doc_create.pb.dart

@@ -98,8 +98,8 @@ class CreateDocRequest extends $pb.GeneratedMessage {
   void clearText() => clearField(4);
 }
 
-class DocDescription extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'DocDescription', createEmptyInstance: create)
+class DocInfo extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'DocInfo', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
     ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
     ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
@@ -107,8 +107,8 @@ class DocDescription extends $pb.GeneratedMessage {
     ..hasRequiredFields = false
   ;
 
-  DocDescription._() : super();
-  factory DocDescription({
+  DocInfo._() : super();
+  factory DocInfo({
     $core.String? id,
     $core.String? name,
     $core.String? desc,
@@ -129,26 +129,26 @@ class DocDescription extends $pb.GeneratedMessage {
     }
     return _result;
   }
-  factory DocDescription.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory DocDescription.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  factory DocInfo.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory DocInfo.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
   'Will be removed in next major version')
-  DocDescription clone() => DocDescription()..mergeFromMessage(this);
+  DocInfo clone() => DocInfo()..mergeFromMessage(this);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
   'Will be removed in next major version')
-  DocDescription copyWith(void Function(DocDescription) updates) => super.copyWith((message) => updates(message as DocDescription)) as DocDescription; // ignore: deprecated_member_use
+  DocInfo copyWith(void Function(DocInfo) updates) => super.copyWith((message) => updates(message as DocInfo)) as DocInfo; // ignore: deprecated_member_use
   $pb.BuilderInfo get info_ => _i;
   @$core.pragma('dart2js:noInline')
-  static DocDescription create() => DocDescription._();
-  DocDescription createEmptyInstance() => create();
-  static $pb.PbList<DocDescription> createRepeated() => $pb.PbList<DocDescription>();
+  static DocInfo create() => DocInfo._();
+  DocInfo createEmptyInstance() => create();
+  static $pb.PbList<DocInfo> createRepeated() => $pb.PbList<DocInfo>();
   @$core.pragma('dart2js:noInline')
-  static DocDescription getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DocDescription>(create);
-  static DocDescription? _defaultInstance;
+  static DocInfo getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DocInfo>(create);
+  static DocInfo? _defaultInstance;
 
   @$pb.TagNumber(1)
   $core.String get id => $_getSZ(0);
@@ -187,66 +187,50 @@ class DocDescription extends $pb.GeneratedMessage {
   void clearPath() => clearField(4);
 }
 
-class Doc extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Doc', createEmptyInstance: create)
-    ..aOM<DocDescription>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc', subBuilder: DocDescription.create)
-    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'text')
+class DocData extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'DocData', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'text')
     ..hasRequiredFields = false
   ;
 
-  Doc._() : super();
-  factory Doc({
-    DocDescription? desc,
+  DocData._() : super();
+  factory DocData({
     $core.String? text,
   }) {
     final _result = create();
-    if (desc != null) {
-      _result.desc = desc;
-    }
     if (text != null) {
       _result.text = text;
     }
     return _result;
   }
-  factory Doc.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory Doc.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  factory DocData.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory DocData.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
   'Will be removed in next major version')
-  Doc clone() => Doc()..mergeFromMessage(this);
+  DocData clone() => DocData()..mergeFromMessage(this);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
   'Will be removed in next major version')
-  Doc copyWith(void Function(Doc) updates) => super.copyWith((message) => updates(message as Doc)) as Doc; // ignore: deprecated_member_use
+  DocData copyWith(void Function(DocData) updates) => super.copyWith((message) => updates(message as DocData)) as DocData; // ignore: deprecated_member_use
   $pb.BuilderInfo get info_ => _i;
   @$core.pragma('dart2js:noInline')
-  static Doc create() => Doc._();
-  Doc createEmptyInstance() => create();
-  static $pb.PbList<Doc> createRepeated() => $pb.PbList<Doc>();
+  static DocData create() => DocData._();
+  DocData createEmptyInstance() => create();
+  static $pb.PbList<DocData> createRepeated() => $pb.PbList<DocData>();
   @$core.pragma('dart2js:noInline')
-  static Doc getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Doc>(create);
-  static Doc? _defaultInstance;
+  static DocData getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DocData>(create);
+  static DocData? _defaultInstance;
 
   @$pb.TagNumber(1)
-  DocDescription get desc => $_getN(0);
+  $core.String get text => $_getSZ(0);
   @$pb.TagNumber(1)
-  set desc(DocDescription v) { setField(1, v); }
+  set text($core.String v) { $_setString(0, v); }
   @$pb.TagNumber(1)
-  $core.bool hasDesc() => $_has(0);
+  $core.bool hasText() => $_has(0);
   @$pb.TagNumber(1)
-  void clearDesc() => clearField(1);
-  @$pb.TagNumber(1)
-  DocDescription ensureDesc() => $_ensure(0);
-
-  @$pb.TagNumber(2)
-  $core.String get text => $_getSZ(1);
-  @$pb.TagNumber(2)
-  set text($core.String v) { $_setString(1, v); }
-  @$pb.TagNumber(2)
-  $core.bool hasText() => $_has(1);
-  @$pb.TagNumber(2)
-  void clearText() => clearField(2);
+  void clearText() => clearField(1);
 }
 

+ 11 - 12
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-editor/doc_create.pbjson.dart

@@ -21,9 +21,9 @@ const CreateDocRequest$json = const {
 
 /// Descriptor for `CreateDocRequest`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List createDocRequestDescriptor = $convert.base64Decode('ChBDcmVhdGVEb2NSZXF1ZXN0Eg4KAmlkGAEgASgJUgJpZBISCgRuYW1lGAIgASgJUgRuYW1lEhIKBGRlc2MYAyABKAlSBGRlc2MSEgoEdGV4dBgEIAEoCVIEdGV4dA==');
-@$core.Deprecated('Use docDescriptionDescriptor instead')
-const DocDescription$json = const {
-  '1': 'DocDescription',
+@$core.Deprecated('Use docInfoDescriptor instead')
+const DocInfo$json = const {
+  '1': 'DocInfo',
   '2': const [
     const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
     const {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'},
@@ -32,16 +32,15 @@ const DocDescription$json = const {
   ],
 };
 
-/// Descriptor for `DocDescription`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List docDescriptionDescriptor = $convert.base64Decode('Cg5Eb2NEZXNjcmlwdGlvbhIOCgJpZBgBIAEoCVICaWQSEgoEbmFtZRgCIAEoCVIEbmFtZRISCgRkZXNjGAMgASgJUgRkZXNjEhIKBHBhdGgYBCABKAlSBHBhdGg=');
-@$core.Deprecated('Use docDescriptor instead')
-const Doc$json = const {
-  '1': 'Doc',
+/// Descriptor for `DocInfo`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List docInfoDescriptor = $convert.base64Decode('CgdEb2NJbmZvEg4KAmlkGAEgASgJUgJpZBISCgRuYW1lGAIgASgJUgRuYW1lEhIKBGRlc2MYAyABKAlSBGRlc2MSEgoEcGF0aBgEIAEoCVIEcGF0aA==');
+@$core.Deprecated('Use docDataDescriptor instead')
+const DocData$json = const {
+  '1': 'DocData',
   '2': const [
-    const {'1': 'desc', '3': 1, '4': 1, '5': 11, '6': '.DocDescription', '10': 'desc'},
-    const {'1': 'text', '3': 2, '4': 1, '5': 9, '10': 'text'},
+    const {'1': 'text', '3': 1, '4': 1, '5': 9, '10': 'text'},
   ],
 };
 
-/// Descriptor for `Doc`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List docDescriptor = $convert.base64Decode('CgNEb2MSIwoEZGVzYxgBIAEoCzIPLkRvY0Rlc2NyaXB0aW9uUgRkZXNjEhIKBHRleHQYAiABKAlSBHRleHQ=');
+/// Descriptor for `DocData`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List docDataDescriptor = $convert.base64Decode('CgdEb2NEYXRhEhIKBHRleHQYASABKAlSBHRleHQ=');

+ 61 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-editor/doc_query.pb.dart

@@ -56,3 +56,64 @@ class QueryDocRequest extends $pb.GeneratedMessage {
   void clearDocId() => clearField(1);
 }
 
+class QueryDocDataRequest extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'QueryDocDataRequest', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'docId')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'path')
+    ..hasRequiredFields = false
+  ;
+
+  QueryDocDataRequest._() : super();
+  factory QueryDocDataRequest({
+    $core.String? docId,
+    $core.String? path,
+  }) {
+    final _result = create();
+    if (docId != null) {
+      _result.docId = docId;
+    }
+    if (path != null) {
+      _result.path = path;
+    }
+    return _result;
+  }
+  factory QueryDocDataRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory QueryDocDataRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  QueryDocDataRequest clone() => QueryDocDataRequest()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  QueryDocDataRequest copyWith(void Function(QueryDocDataRequest) updates) => super.copyWith((message) => updates(message as QueryDocDataRequest)) as QueryDocDataRequest; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static QueryDocDataRequest create() => QueryDocDataRequest._();
+  QueryDocDataRequest createEmptyInstance() => create();
+  static $pb.PbList<QueryDocDataRequest> createRepeated() => $pb.PbList<QueryDocDataRequest>();
+  @$core.pragma('dart2js:noInline')
+  static QueryDocDataRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<QueryDocDataRequest>(create);
+  static QueryDocDataRequest? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get docId => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set docId($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasDocId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearDocId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.String get path => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set path($core.String v) { $_setString(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasPath() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearPath() => clearField(2);
+}
+

+ 11 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-editor/doc_query.pbjson.dart

@@ -18,3 +18,14 @@ const QueryDocRequest$json = const {
 
 /// Descriptor for `QueryDocRequest`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List queryDocRequestDescriptor = $convert.base64Decode('Cg9RdWVyeURvY1JlcXVlc3QSFQoGZG9jX2lkGAEgASgJUgVkb2NJZA==');
+@$core.Deprecated('Use queryDocDataRequestDescriptor instead')
+const QueryDocDataRequest$json = const {
+  '1': 'QueryDocDataRequest',
+  '2': const [
+    const {'1': 'doc_id', '3': 1, '4': 1, '5': 9, '10': 'docId'},
+    const {'1': 'path', '3': 2, '4': 1, '5': 9, '10': 'path'},
+  ],
+};
+
+/// Descriptor for `QueryDocDataRequest`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List queryDocDataRequestDescriptor = $convert.base64Decode('ChNRdWVyeURvY0RhdGFSZXF1ZXN0EhUKBmRvY19pZBgBIAEoCVIFZG9jSWQSEgoEcGF0aBgCIAEoCVIEcGF0aA==');

+ 4 - 2
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-editor/errors.pbenum.dart

@@ -16,7 +16,8 @@ class EditorErrorCode extends $pb.ProtobufEnum {
   static const EditorErrorCode DocNameInvalid = EditorErrorCode._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DocNameInvalid');
   static const EditorErrorCode DocViewIdInvalid = EditorErrorCode._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DocViewIdInvalid');
   static const EditorErrorCode DocDescTooLong = EditorErrorCode._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DocDescTooLong');
-  static const EditorErrorCode DocFileError = EditorErrorCode._(13, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DocFileError');
+  static const EditorErrorCode DocOpenFileError = EditorErrorCode._(13, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DocOpenFileError');
+  static const EditorErrorCode DocFilePathInvalid = EditorErrorCode._(14, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DocFilePathInvalid');
   static const EditorErrorCode EditorUserNotLoginYet = EditorErrorCode._(100, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'EditorUserNotLoginYet');
 
   static const $core.List<EditorErrorCode> values = <EditorErrorCode> [
@@ -26,7 +27,8 @@ class EditorErrorCode extends $pb.ProtobufEnum {
     DocNameInvalid,
     DocViewIdInvalid,
     DocDescTooLong,
-    DocFileError,
+    DocOpenFileError,
+    DocFilePathInvalid,
     EditorUserNotLoginYet,
   ];
 

+ 3 - 2
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-editor/errors.pbjson.dart

@@ -18,13 +18,14 @@ const EditorErrorCode$json = const {
     const {'1': 'DocNameInvalid', '2': 10},
     const {'1': 'DocViewIdInvalid', '2': 11},
     const {'1': 'DocDescTooLong', '2': 12},
-    const {'1': 'DocFileError', '2': 13},
+    const {'1': 'DocOpenFileError', '2': 13},
+    const {'1': 'DocFilePathInvalid', '2': 14},
     const {'1': 'EditorUserNotLoginYet', '2': 100},
   ],
 };
 
 /// Descriptor for `EditorErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List editorErrorCodeDescriptor = $convert.base64Decode('Cg9FZGl0b3JFcnJvckNvZGUSCwoHVW5rbm93bhAAEhkKFUVkaXRvckRCSW50ZXJuYWxFcnJvchABEhYKEkVkaXRvckRCQ29ubkZhaWxlZBACEhIKDkRvY05hbWVJbnZhbGlkEAoSFAoQRG9jVmlld0lkSW52YWxpZBALEhIKDkRvY0Rlc2NUb29Mb25nEAwSEAoMRG9jRmlsZUVycm9yEA0SGQoVRWRpdG9yVXNlck5vdExvZ2luWWV0EGQ=');
+final $typed_data.Uint8List editorErrorCodeDescriptor = $convert.base64Decode('Cg9FZGl0b3JFcnJvckNvZGUSCwoHVW5rbm93bhAAEhkKFUVkaXRvckRCSW50ZXJuYWxFcnJvchABEhYKEkVkaXRvckRCQ29ubkZhaWxlZBACEhIKDkRvY05hbWVJbnZhbGlkEAoSFAoQRG9jVmlld0lkSW52YWxpZBALEhIKDkRvY0Rlc2NUb29Mb25nEAwSFAoQRG9jT3BlbkZpbGVFcnJvchANEhYKEkRvY0ZpbGVQYXRoSW52YWxpZBAOEhkKFUVkaXRvclVzZXJOb3RMb2dpbllldBBk');
 @$core.Deprecated('Use editorErrorDescriptor instead')
 const EditorError$json = const {
   '1': 'EditorError',

+ 4 - 2
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-editor/event.pbenum.dart

@@ -12,12 +12,14 @@ import 'package:protobuf/protobuf.dart' as $pb;
 class EditorEvent extends $pb.ProtobufEnum {
   static const EditorEvent CreateDoc = EditorEvent._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateDoc');
   static const EditorEvent UpdateDoc = EditorEvent._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateDoc');
-  static const EditorEvent ReadDoc = EditorEvent._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ReadDoc');
+  static const EditorEvent ReadDocInfo = EditorEvent._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ReadDocInfo');
+  static const EditorEvent ReadDocData = EditorEvent._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ReadDocData');
 
   static const $core.List<EditorEvent> values = <EditorEvent> [
     CreateDoc,
     UpdateDoc,
-    ReadDoc,
+    ReadDocInfo,
+    ReadDocData,
   ];
 
   static final $core.Map<$core.int, EditorEvent> _byValue = $pb.ProtobufEnum.initByValue(values);

+ 3 - 2
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-editor/event.pbjson.dart

@@ -14,9 +14,10 @@ const EditorEvent$json = const {
   '2': const [
     const {'1': 'CreateDoc', '2': 0},
     const {'1': 'UpdateDoc', '2': 1},
-    const {'1': 'ReadDoc', '2': 2},
+    const {'1': 'ReadDocInfo', '2': 2},
+    const {'1': 'ReadDocData', '2': 3},
   ],
 };
 
 /// Descriptor for `EditorEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List editorEventDescriptor = $convert.base64Decode('CgtFZGl0b3JFdmVudBINCglDcmVhdGVEb2MQABINCglVcGRhdGVEb2MQARILCgdSZWFkRG9jEAI=');
+final $typed_data.Uint8List editorEventDescriptor = $convert.base64Decode('CgtFZGl0b3JFdmVudBINCglDcmVhdGVEb2MQABINCglVcGRhdGVEb2MQARIPCgtSZWFkRG9jSW5mbxACEg8KC1JlYWREb2NEYXRhEAM=');

+ 3 - 2
rust-lib/flowy-derive/src/derive_cache/derive_cache.rs

@@ -18,9 +18,10 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
         "ObservableSubject"
         | "KeyValue"
         | "CreateDocRequest"
-        | "DocDescription"
-        | "Doc"
+        | "DocInfo"
+        | "DocData"
         | "QueryDocRequest"
+        | "QueryDocDataRequest"
         | "UpdateDocRequest"
         | "EditorError"
         | "QueryAppRequest"

+ 2 - 5
rust-lib/flowy-editor/src/entities/doc/doc_create.rs

@@ -57,7 +57,7 @@ impl TryInto<CreateDocParams> for CreateDocRequest {
 }
 
 #[derive(ProtoBuf, Default, Debug)]
-pub struct DocDescription {
+pub struct DocInfo {
     #[pb(index = 1)]
     pub id: String,
 
@@ -72,10 +72,7 @@ pub struct DocDescription {
 }
 
 #[derive(ProtoBuf, Default, Debug)]
-pub struct Doc {
+pub struct DocData {
     #[pb(index = 1)]
-    pub desc: DocDescription,
-
-    #[pb(index = 2)]
     pub text: String,
 }

+ 42 - 1
rust-lib/flowy-editor/src/entities/doc/doc_query.rs

@@ -1,4 +1,7 @@
-use crate::{entities::doc::parser::DocId, errors::*};
+use crate::{
+    entities::doc::parser::{DocId, DocPath},
+    errors::*,
+};
 use flowy_derive::ProtoBuf;
 use std::convert::TryInto;
 
@@ -27,3 +30,41 @@ impl TryInto<QueryDocParams> for QueryDocRequest {
         Ok(QueryDocParams { doc_id })
     }
 }
+
+#[derive(Default, ProtoBuf)]
+pub struct QueryDocDataRequest {
+    #[pb(index = 1)]
+    pub doc_id: String,
+
+    #[pb(index = 2)]
+    pub path: String,
+}
+
+pub(crate) struct QueryDocDataParams {
+    pub doc_id: String,
+    pub path: String,
+}
+
+impl TryInto<QueryDocDataParams> for QueryDocDataRequest {
+    type Error = EditorError;
+
+    fn try_into(self) -> Result<QueryDocDataParams, Self::Error> {
+        let doc_id = DocId::parse(self.doc_id)
+            .map_err(|e| {
+                ErrorBuilder::new(EditorErrorCode::DocViewIdInvalid)
+                    .msg(e)
+                    .build()
+            })?
+            .0;
+
+        let path = DocPath::parse(self.path)
+            .map_err(|e| {
+                ErrorBuilder::new(EditorErrorCode::DocFilePathInvalid)
+                    .msg(e)
+                    .build()
+            })?
+            .0;
+
+        Ok(QueryDocDataParams { doc_id, path })
+    }
+}

+ 12 - 0
rust-lib/flowy-editor/src/entities/doc/parser/doc_path.rs

@@ -0,0 +1,12 @@
+#[derive(Debug)]
+pub struct DocPath(pub String);
+
+impl DocPath {
+    pub fn parse(s: String) -> Result<DocPath, String> {
+        if s.trim().is_empty() {
+            return Err(format!("Doc path can not be empty or whitespace"));
+        }
+
+        Ok(Self(s))
+    }
+}

+ 2 - 0
rust-lib/flowy-editor/src/entities/doc/parser/mod.rs

@@ -1,9 +1,11 @@
 mod doc_desc;
 mod doc_id;
 mod doc_name;
+mod doc_path;
 mod doc_view_id;
 
 pub use doc_desc::*;
 pub use doc_id::*;
 pub use doc_name::*;
+pub use doc_path::*;
 pub use doc_view_id::*;

+ 6 - 3
rust-lib/flowy-editor/src/errors.rs

@@ -42,8 +42,11 @@ pub enum EditorErrorCode {
     #[display(fmt = "DocDescTooLong")]
     DocDescTooLong     = 12,
 
-    #[display(fmt = "DocDescTooLong")]
-    DocFileError       = 13,
+    #[display(fmt = "DocOpenFileError")]
+    DocOpenFileError   = 13,
+
+    #[display(fmt = "DocFilePathInvalid")]
+    DocFilePathInvalid = 14,
 
     #[display(fmt = "EditorUserNotLoginYet")]
     EditorUserNotLoginYet = 100,
@@ -63,7 +66,7 @@ impl std::convert::From<flowy_database::result::Error> for EditorError {
 
 impl std::convert::From<FileError> for EditorError {
     fn from(error: FileError) -> Self {
-        ErrorBuilder::new(EditorErrorCode::DocFileError)
+        ErrorBuilder::new(EditorErrorCode::DocOpenFileError)
             .error(error)
             .build()
     }

+ 10 - 6
rust-lib/flowy-editor/src/event.rs

@@ -5,14 +5,18 @@ use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
 #[event_err = "EditorError"]
 pub enum EditorEvent {
     #[display(fmt = "CreateDoc")]
-    #[event(input = "CreateDocRequest", output = "DocDescription")]
-    CreateDoc = 0,
+    #[event(input = "CreateDocRequest", output = "DocInfo")]
+    CreateDoc   = 0,
 
     #[display(fmt = "UpdateDoc")]
     #[event(input = "UpdateDocRequest")]
-    UpdateDoc = 1,
+    UpdateDoc   = 1,
 
-    #[display(fmt = "ReadDoc")]
-    #[event(input = "QueryDocRequest", output = "Doc")]
-    ReadDoc   = 2,
+    #[display(fmt = "ReadDocInfo")]
+    #[event(input = "QueryDocRequest", output = "DocInfo")]
+    ReadDocInfo = 2,
+
+    #[display(fmt = "ReadDocData")]
+    #[event(input = "QueryDocDataRequest", output = "DocData")]
+    ReadDocData = 3,
 }

+ 20 - 9
rust-lib/flowy-editor/src/handlers/doc_handler.rs

@@ -12,7 +12,7 @@ pub async fn create_doc(
     data: Data<CreateDocRequest>,
     controller: Unit<DocController>,
     manager: Unit<RwLock<FileManager>>,
-) -> ResponseResult<DocDescription, EditorError> {
+) -> ResponseResult<DocInfo, EditorError> {
     let params: CreateDocParams = data.into_inner().try_into()?;
     let dir = manager.read().await.user.user_doc_dir()?;
     let path = manager
@@ -30,18 +30,29 @@ pub async fn read_doc(
     data: Data<QueryDocRequest>,
     controller: Unit<DocController>,
     manager: Unit<RwLock<FileManager>>,
-) -> ResponseResult<Doc, EditorError> {
+) -> ResponseResult<DocInfo, EditorError> {
     let params: QueryDocParams = data.into_inner().try_into()?;
-    let desc = controller.read_doc(&params.doc_id).await?;
-    let content = manager
+    let doc_info = controller.read_doc(&params.doc_id).await?;
+    let _ = manager
         .write()
         .await
-        .open(Path::new(&desc.path), desc.id.clone())?;
+        .open(Path::new(&doc_info.path), doc_info.id.clone())?;
 
-    response_ok(Doc {
-        desc,
-        text: content,
-    })
+    response_ok(doc_info)
+}
+
+#[tracing::instrument(name = "read_doc_data", skip(data, manager))]
+pub async fn read_doc_data(
+    data: Data<QueryDocDataRequest>,
+    manager: Unit<RwLock<FileManager>>,
+) -> ResponseResult<DocData, EditorError> {
+    let params: QueryDocDataParams = data.into_inner().try_into()?;
+    let text = manager
+        .write()
+        .await
+        .open(Path::new(&params.path), params.doc_id)?;
+
+    response_ok(DocData { text })
 }
 
 pub async fn update_doc(

+ 2 - 1
rust-lib/flowy-editor/src/module.rs

@@ -30,5 +30,6 @@ pub fn create(database: Arc<dyn EditorDatabase>, user: Arc<dyn EditorUser>) -> M
         .data(doc_controller)
         .event(EditorEvent::CreateDoc, create_doc)
         .event(EditorEvent::UpdateDoc, update_doc)
-        .event(EditorEvent::ReadDoc, read_doc)
+        .event(EditorEvent::ReadDocInfo, read_doc)
+        .event(EditorEvent::ReadDocData, read_doc_data)
 }

+ 63 - 124
rust-lib/flowy-editor/src/protobuf/model/doc_create.rs

@@ -309,7 +309,7 @@ impl ::protobuf::reflect::ProtobufValue for CreateDocRequest {
 }
 
 #[derive(PartialEq,Clone,Default)]
-pub struct DocDescription {
+pub struct DocInfo {
     // message fields
     pub id: ::std::string::String,
     pub name: ::std::string::String,
@@ -320,14 +320,14 @@ pub struct DocDescription {
     pub cached_size: ::protobuf::CachedSize,
 }
 
-impl<'a> ::std::default::Default for &'a DocDescription {
-    fn default() -> &'a DocDescription {
-        <DocDescription as ::protobuf::Message>::default_instance()
+impl<'a> ::std::default::Default for &'a DocInfo {
+    fn default() -> &'a DocInfo {
+        <DocInfo as ::protobuf::Message>::default_instance()
     }
 }
 
-impl DocDescription {
-    pub fn new() -> DocDescription {
+impl DocInfo {
+    pub fn new() -> DocInfo {
         ::std::default::Default::default()
     }
 
@@ -436,7 +436,7 @@ impl DocDescription {
     }
 }
 
-impl ::protobuf::Message for DocDescription {
+impl ::protobuf::Message for DocInfo {
     fn is_initialized(&self) -> bool {
         true
     }
@@ -529,8 +529,8 @@ impl ::protobuf::Message for DocDescription {
         Self::descriptor_static()
     }
 
-    fn new() -> DocDescription {
-        DocDescription::new()
+    fn new() -> DocInfo {
+        DocInfo::new()
     }
 
     fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
@@ -539,39 +539,39 @@ impl ::protobuf::Message for DocDescription {
             let mut fields = ::std::vec::Vec::new();
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "id",
-                |m: &DocDescription| { &m.id },
-                |m: &mut DocDescription| { &mut m.id },
+                |m: &DocInfo| { &m.id },
+                |m: &mut DocInfo| { &mut m.id },
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "name",
-                |m: &DocDescription| { &m.name },
-                |m: &mut DocDescription| { &mut m.name },
+                |m: &DocInfo| { &m.name },
+                |m: &mut DocInfo| { &mut m.name },
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "desc",
-                |m: &DocDescription| { &m.desc },
-                |m: &mut DocDescription| { &mut m.desc },
+                |m: &DocInfo| { &m.desc },
+                |m: &mut DocInfo| { &mut m.desc },
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "path",
-                |m: &DocDescription| { &m.path },
-                |m: &mut DocDescription| { &mut m.path },
+                |m: &DocInfo| { &m.path },
+                |m: &mut DocInfo| { &mut m.path },
             ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<DocDescription>(
-                "DocDescription",
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<DocInfo>(
+                "DocInfo",
                 fields,
                 file_descriptor_proto()
             )
         })
     }
 
-    fn default_instance() -> &'static DocDescription {
-        static instance: ::protobuf::rt::LazyV2<DocDescription> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(DocDescription::new)
+    fn default_instance() -> &'static DocInfo {
+        static instance: ::protobuf::rt::LazyV2<DocInfo> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(DocInfo::new)
     }
 }
 
-impl ::protobuf::Clear for DocDescription {
+impl ::protobuf::Clear for DocInfo {
     fn clear(&mut self) {
         self.id.clear();
         self.name.clear();
@@ -581,73 +581,39 @@ impl ::protobuf::Clear for DocDescription {
     }
 }
 
-impl ::std::fmt::Debug for DocDescription {
+impl ::std::fmt::Debug for DocInfo {
     fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
         ::protobuf::text_format::fmt(self, f)
     }
 }
 
-impl ::protobuf::reflect::ProtobufValue for DocDescription {
+impl ::protobuf::reflect::ProtobufValue for DocInfo {
     fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
         ::protobuf::reflect::ReflectValueRef::Message(self)
     }
 }
 
 #[derive(PartialEq,Clone,Default)]
-pub struct Doc {
+pub struct DocData {
     // message fields
-    pub desc: ::protobuf::SingularPtrField<DocDescription>,
     pub text: ::std::string::String,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
 }
 
-impl<'a> ::std::default::Default for &'a Doc {
-    fn default() -> &'a Doc {
-        <Doc as ::protobuf::Message>::default_instance()
+impl<'a> ::std::default::Default for &'a DocData {
+    fn default() -> &'a DocData {
+        <DocData as ::protobuf::Message>::default_instance()
     }
 }
 
-impl Doc {
-    pub fn new() -> Doc {
+impl DocData {
+    pub fn new() -> DocData {
         ::std::default::Default::default()
     }
 
-    // .DocDescription desc = 1;
-
-
-    pub fn get_desc(&self) -> &DocDescription {
-        self.desc.as_ref().unwrap_or_else(|| <DocDescription as ::protobuf::Message>::default_instance())
-    }
-    pub fn clear_desc(&mut self) {
-        self.desc.clear();
-    }
-
-    pub fn has_desc(&self) -> bool {
-        self.desc.is_some()
-    }
-
-    // Param is passed by value, moved
-    pub fn set_desc(&mut self, v: DocDescription) {
-        self.desc = ::protobuf::SingularPtrField::some(v);
-    }
-
-    // Mutable pointer to the field.
-    // If field is not initialized, it is initialized with default value first.
-    pub fn mut_desc(&mut self) -> &mut DocDescription {
-        if self.desc.is_none() {
-            self.desc.set_default();
-        }
-        self.desc.as_mut().unwrap()
-    }
-
-    // Take field
-    pub fn take_desc(&mut self) -> DocDescription {
-        self.desc.take().unwrap_or_else(|| DocDescription::new())
-    }
-
-    // string text = 2;
+    // string text = 1;
 
 
     pub fn get_text(&self) -> &str {
@@ -674,13 +640,8 @@ impl Doc {
     }
 }
 
-impl ::protobuf::Message for Doc {
+impl ::protobuf::Message for DocData {
     fn is_initialized(&self) -> bool {
-        for v in &self.desc {
-            if !v.is_initialized() {
-                return false;
-            }
-        };
         true
     }
 
@@ -689,9 +650,6 @@ impl ::protobuf::Message for Doc {
             let (field_number, wire_type) = is.read_tag_unpack()?;
             match field_number {
                 1 => {
-                    ::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.desc)?;
-                },
-                2 => {
                     ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.text)?;
                 },
                 _ => {
@@ -706,12 +664,8 @@ impl ::protobuf::Message for Doc {
     #[allow(unused_variables)]
     fn compute_size(&self) -> u32 {
         let mut my_size = 0;
-        if let Some(ref v) = self.desc.as_ref() {
-            let len = v.compute_size();
-            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
-        }
         if !self.text.is_empty() {
-            my_size += ::protobuf::rt::string_size(2, &self.text);
+            my_size += ::protobuf::rt::string_size(1, &self.text);
         }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
@@ -719,13 +673,8 @@ impl ::protobuf::Message for Doc {
     }
 
     fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        if let Some(ref v) = self.desc.as_ref() {
-            os.write_tag(1, ::protobuf::wire_format::WireTypeLengthDelimited)?;
-            os.write_raw_varint32(v.get_cached_size())?;
-            v.write_to_with_cached_sizes(os)?;
-        }
         if !self.text.is_empty() {
-            os.write_string(2, &self.text)?;
+            os.write_string(1, &self.text)?;
         }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
@@ -757,53 +706,47 @@ impl ::protobuf::Message for Doc {
         Self::descriptor_static()
     }
 
-    fn new() -> Doc {
-        Doc::new()
+    fn new() -> DocData {
+        DocData::new()
     }
 
     fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
         static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
         descriptor.get(|| {
             let mut fields = ::std::vec::Vec::new();
-            fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<DocDescription>>(
-                "desc",
-                |m: &Doc| { &m.desc },
-                |m: &mut Doc| { &mut m.desc },
-            ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "text",
-                |m: &Doc| { &m.text },
-                |m: &mut Doc| { &mut m.text },
+                |m: &DocData| { &m.text },
+                |m: &mut DocData| { &mut m.text },
             ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<Doc>(
-                "Doc",
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<DocData>(
+                "DocData",
                 fields,
                 file_descriptor_proto()
             )
         })
     }
 
-    fn default_instance() -> &'static Doc {
-        static instance: ::protobuf::rt::LazyV2<Doc> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(Doc::new)
+    fn default_instance() -> &'static DocData {
+        static instance: ::protobuf::rt::LazyV2<DocData> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(DocData::new)
     }
 }
 
-impl ::protobuf::Clear for Doc {
+impl ::protobuf::Clear for DocData {
     fn clear(&mut self) {
-        self.desc.clear();
         self.text.clear();
         self.unknown_fields.clear();
     }
 }
 
-impl ::std::fmt::Debug for Doc {
+impl ::std::fmt::Debug for DocData {
     fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
         ::protobuf::text_format::fmt(self, f)
     }
 }
 
-impl ::protobuf::reflect::ProtobufValue for Doc {
+impl ::protobuf::reflect::ProtobufValue for DocData {
     fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
         ::protobuf::reflect::ReflectValueRef::Message(self)
     }
@@ -813,25 +756,24 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \n\x10doc_create.proto\"^\n\x10CreateDocRequest\x12\x0e\n\x02id\x18\x01\
     \x20\x01(\tR\x02id\x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12\x12\
     \n\x04desc\x18\x03\x20\x01(\tR\x04desc\x12\x12\n\x04text\x18\x04\x20\x01\
-    (\tR\x04text\"\\\n\x0eDocDescription\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\
-    \x02id\x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12\x12\n\x04desc\
-    \x18\x03\x20\x01(\tR\x04desc\x12\x12\n\x04path\x18\x04\x20\x01(\tR\x04pa\
-    th\">\n\x03Doc\x12#\n\x04desc\x18\x01\x20\x01(\x0b2\x0f.DocDescriptionR\
-    \x04desc\x12\x12\n\x04text\x18\x02\x20\x01(\tR\x04textJ\x80\x05\n\x06\
-    \x12\x04\0\0\x11\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\
-    \x04\x02\0\x07\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x18\n\x0b\n\x04\
-    \x04\0\x02\0\x12\x03\x03\x04\x12\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\
-    \x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x0b\r\n\x0c\n\x05\x04\0\
-    \x02\0\x03\x12\x03\x03\x10\x11\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\
-    \x14\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\
-    \x02\x01\x01\x12\x03\x04\x0b\x0f\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\
+    (\tR\x04text\"U\n\x07DocInfo\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\
+    \x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12\x12\n\x04desc\x18\x03\
+    \x20\x01(\tR\x04desc\x12\x12\n\x04path\x18\x04\x20\x01(\tR\x04path\"\x1d\
+    \n\x07DocData\x12\x12\n\x04text\x18\x01\x20\x01(\tR\x04textJ\xc9\x04\n\
+    \x06\x12\x04\0\0\x10\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\
+    \x12\x04\x02\0\x07\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x18\n\x0b\n\
+    \x04\x04\0\x02\0\x12\x03\x03\x04\x12\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\
+    \x03\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x0b\r\n\x0c\n\x05\x04\
+    \0\x02\0\x03\x12\x03\x03\x10\x11\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\
+    \x04\x14\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\n\n\x0c\n\x05\x04\
+    \0\x02\x01\x01\x12\x03\x04\x0b\x0f\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\
     \x04\x12\x13\n\x0b\n\x04\x04\0\x02\x02\x12\x03\x05\x04\x14\n\x0c\n\x05\
     \x04\0\x02\x02\x05\x12\x03\x05\x04\n\n\x0c\n\x05\x04\0\x02\x02\x01\x12\
     \x03\x05\x0b\x0f\n\x0c\n\x05\x04\0\x02\x02\x03\x12\x03\x05\x12\x13\n\x0b\
     \n\x04\x04\0\x02\x03\x12\x03\x06\x04\x14\n\x0c\n\x05\x04\0\x02\x03\x05\
     \x12\x03\x06\x04\n\n\x0c\n\x05\x04\0\x02\x03\x01\x12\x03\x06\x0b\x0f\n\
     \x0c\n\x05\x04\0\x02\x03\x03\x12\x03\x06\x12\x13\n\n\n\x02\x04\x01\x12\
-    \x04\x08\0\r\x01\n\n\n\x03\x04\x01\x01\x12\x03\x08\x08\x16\n\x0b\n\x04\
+    \x04\x08\0\r\x01\n\n\n\x03\x04\x01\x01\x12\x03\x08\x08\x0f\n\x0b\n\x04\
     \x04\x01\x02\0\x12\x03\t\x04\x12\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\t\
     \x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\t\x0b\r\n\x0c\n\x05\x04\x01\
     \x02\0\x03\x12\x03\t\x10\x11\n\x0b\n\x04\x04\x01\x02\x01\x12\x03\n\x04\
@@ -843,13 +785,10 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x0b\n\x04\x04\x01\x02\x03\x12\x03\x0c\x04\x14\n\x0c\n\x05\x04\x01\x02\
     \x03\x05\x12\x03\x0c\x04\n\n\x0c\n\x05\x04\x01\x02\x03\x01\x12\x03\x0c\
     \x0b\x0f\n\x0c\n\x05\x04\x01\x02\x03\x03\x12\x03\x0c\x12\x13\n\n\n\x02\
-    \x04\x02\x12\x04\x0e\0\x11\x01\n\n\n\x03\x04\x02\x01\x12\x03\x0e\x08\x0b\
-    \n\x0b\n\x04\x04\x02\x02\0\x12\x03\x0f\x04\x1c\n\x0c\n\x05\x04\x02\x02\0\
-    \x06\x12\x03\x0f\x04\x12\n\x0c\n\x05\x04\x02\x02\0\x01\x12\x03\x0f\x13\
-    \x17\n\x0c\n\x05\x04\x02\x02\0\x03\x12\x03\x0f\x1a\x1b\n\x0b\n\x04\x04\
-    \x02\x02\x01\x12\x03\x10\x04\x14\n\x0c\n\x05\x04\x02\x02\x01\x05\x12\x03\
-    \x10\x04\n\n\x0c\n\x05\x04\x02\x02\x01\x01\x12\x03\x10\x0b\x0f\n\x0c\n\
-    \x05\x04\x02\x02\x01\x03\x12\x03\x10\x12\x13b\x06proto3\
+    \x04\x02\x12\x04\x0e\0\x10\x01\n\n\n\x03\x04\x02\x01\x12\x03\x0e\x08\x0f\
+    \n\x0b\n\x04\x04\x02\x02\0\x12\x03\x0f\x04\x14\n\x0c\n\x05\x04\x02\x02\0\
+    \x05\x12\x03\x0f\x04\n\n\x0c\n\x05\x04\x02\x02\0\x01\x12\x03\x0f\x0b\x0f\
+    \n\x0c\n\x05\x04\x02\x02\0\x03\x12\x03\x0f\x12\x13b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 215 - 6
rust-lib/flowy-editor/src/protobuf/model/doc_query.rs

@@ -182,14 +182,223 @@ impl ::protobuf::reflect::ProtobufValue for QueryDocRequest {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct QueryDocDataRequest {
+    // message fields
+    pub doc_id: ::std::string::String,
+    pub path: ::std::string::String,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a QueryDocDataRequest {
+    fn default() -> &'a QueryDocDataRequest {
+        <QueryDocDataRequest as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl QueryDocDataRequest {
+    pub fn new() -> QueryDocDataRequest {
+        ::std::default::Default::default()
+    }
+
+    // string doc_id = 1;
+
+
+    pub fn get_doc_id(&self) -> &str {
+        &self.doc_id
+    }
+    pub fn clear_doc_id(&mut self) {
+        self.doc_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_doc_id(&mut self, v: ::std::string::String) {
+        self.doc_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_doc_id(&mut self) -> &mut ::std::string::String {
+        &mut self.doc_id
+    }
+
+    // Take field
+    pub fn take_doc_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.doc_id, ::std::string::String::new())
+    }
+
+    // string path = 2;
+
+
+    pub fn get_path(&self) -> &str {
+        &self.path
+    }
+    pub fn clear_path(&mut self) {
+        self.path.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_path(&mut self, v: ::std::string::String) {
+        self.path = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_path(&mut self) -> &mut ::std::string::String {
+        &mut self.path
+    }
+
+    // Take field
+    pub fn take_path(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.path, ::std::string::String::new())
+    }
+}
+
+impl ::protobuf::Message for QueryDocDataRequest {
+    fn is_initialized(&self) -> bool {
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.doc_id)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.path)?;
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        if !self.doc_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.doc_id);
+        }
+        if !self.path.is_empty() {
+            my_size += ::protobuf::rt::string_size(2, &self.path);
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        if !self.doc_id.is_empty() {
+            os.write_string(1, &self.doc_id)?;
+        }
+        if !self.path.is_empty() {
+            os.write_string(2, &self.path)?;
+        }
+        os.write_unknown_fields(self.get_unknown_fields())?;
+        ::std::result::Result::Ok(())
+    }
+
+    fn get_cached_size(&self) -> u32 {
+        self.cached_size.get()
+    }
+
+    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
+        &self.unknown_fields
+    }
+
+    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
+        &mut self.unknown_fields
+    }
+
+    fn as_any(&self) -> &dyn (::std::any::Any) {
+        self as &dyn (::std::any::Any)
+    }
+    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
+        self as &mut dyn (::std::any::Any)
+    }
+    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
+        self
+    }
+
+    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
+        Self::descriptor_static()
+    }
+
+    fn new() -> QueryDocDataRequest {
+        QueryDocDataRequest::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "doc_id",
+                |m: &QueryDocDataRequest| { &m.doc_id },
+                |m: &mut QueryDocDataRequest| { &mut m.doc_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "path",
+                |m: &QueryDocDataRequest| { &m.path },
+                |m: &mut QueryDocDataRequest| { &mut m.path },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<QueryDocDataRequest>(
+                "QueryDocDataRequest",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static QueryDocDataRequest {
+        static instance: ::protobuf::rt::LazyV2<QueryDocDataRequest> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(QueryDocDataRequest::new)
+    }
+}
+
+impl ::protobuf::Clear for QueryDocDataRequest {
+    fn clear(&mut self) {
+        self.doc_id.clear();
+        self.path.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for QueryDocDataRequest {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for QueryDocDataRequest {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 static file_descriptor_proto_data: &'static [u8] = b"\
     \n\x0fdoc_query.proto\"(\n\x0fQueryDocRequest\x12\x15\n\x06doc_id\x18\
-    \x01\x20\x01(\tR\x05docIdJa\n\x06\x12\x04\0\0\x04\x01\n\x08\n\x01\x0c\
-    \x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x04\x01\n\n\n\x03\x04\0\
-    \x01\x12\x03\x02\x08\x17\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x16\n\
-    \x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\
-    \x12\x03\x03\x0b\x11\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x14\x15b\
-    \x06proto3\
+    \x01\x20\x01(\tR\x05docId\"@\n\x13QueryDocDataRequest\x12\x15\n\x06doc_i\
+    d\x18\x01\x20\x01(\tR\x05docId\x12\x12\n\x04path\x18\x02\x20\x01(\tR\x04\
+    pathJ\xe7\x01\n\x06\x12\x04\0\0\x08\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\
+    \n\n\n\x02\x04\0\x12\x04\x02\0\x04\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\
+    \x08\x17\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x16\n\x0c\n\x05\x04\0\
+    \x02\0\x05\x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x0b\
+    \x11\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x14\x15\n\n\n\x02\x04\x01\
+    \x12\x04\x05\0\x08\x01\n\n\n\x03\x04\x01\x01\x12\x03\x05\x08\x1b\n\x0b\n\
+    \x04\x04\x01\x02\0\x12\x03\x06\x04\x16\n\x0c\n\x05\x04\x01\x02\0\x05\x12\
+    \x03\x06\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\x06\x0b\x11\n\x0c\n\
+    \x05\x04\x01\x02\0\x03\x12\x03\x06\x14\x15\n\x0b\n\x04\x04\x01\x02\x01\
+    \x12\x03\x07\x04\x14\n\x0c\n\x05\x04\x01\x02\x01\x05\x12\x03\x07\x04\n\n\
+    \x0c\n\x05\x04\x01\x02\x01\x01\x12\x03\x07\x0b\x0f\n\x0c\n\x05\x04\x01\
+    \x02\x01\x03\x12\x03\x07\x12\x13b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 36 - 31
rust-lib/flowy-editor/src/protobuf/model/errors.rs

@@ -221,7 +221,8 @@ pub enum EditorErrorCode {
     DocNameInvalid = 10,
     DocViewIdInvalid = 11,
     DocDescTooLong = 12,
-    DocFileError = 13,
+    DocOpenFileError = 13,
+    DocFilePathInvalid = 14,
     EditorUserNotLoginYet = 100,
 }
 
@@ -238,7 +239,8 @@ impl ::protobuf::ProtobufEnum for EditorErrorCode {
             10 => ::std::option::Option::Some(EditorErrorCode::DocNameInvalid),
             11 => ::std::option::Option::Some(EditorErrorCode::DocViewIdInvalid),
             12 => ::std::option::Option::Some(EditorErrorCode::DocDescTooLong),
-            13 => ::std::option::Option::Some(EditorErrorCode::DocFileError),
+            13 => ::std::option::Option::Some(EditorErrorCode::DocOpenFileError),
+            14 => ::std::option::Option::Some(EditorErrorCode::DocFilePathInvalid),
             100 => ::std::option::Option::Some(EditorErrorCode::EditorUserNotLoginYet),
             _ => ::std::option::Option::None
         }
@@ -252,7 +254,8 @@ impl ::protobuf::ProtobufEnum for EditorErrorCode {
             EditorErrorCode::DocNameInvalid,
             EditorErrorCode::DocViewIdInvalid,
             EditorErrorCode::DocDescTooLong,
-            EditorErrorCode::DocFileError,
+            EditorErrorCode::DocOpenFileError,
+            EditorErrorCode::DocFilePathInvalid,
             EditorErrorCode::EditorUserNotLoginYet,
         ];
         values
@@ -284,36 +287,38 @@ impl ::protobuf::reflect::ProtobufValue for EditorErrorCode {
 static file_descriptor_proto_data: &'static [u8] = b"\
     \n\x0cerrors.proto\"E\n\x0bEditorError\x12$\n\x04code\x18\x01\x20\x01(\
     \x0e2\x10.EditorErrorCodeR\x04code\x12\x10\n\x03msg\x18\x02\x20\x01(\tR\
-    \x03msg*\xbc\x01\n\x0fEditorErrorCode\x12\x0b\n\x07Unknown\x10\0\x12\x19\
+    \x03msg*\xd8\x01\n\x0fEditorErrorCode\x12\x0b\n\x07Unknown\x10\0\x12\x19\
     \n\x15EditorDBInternalError\x10\x01\x12\x16\n\x12EditorDBConnFailed\x10\
     \x02\x12\x12\n\x0eDocNameInvalid\x10\n\x12\x14\n\x10DocViewIdInvalid\x10\
-    \x0b\x12\x12\n\x0eDocDescTooLong\x10\x0c\x12\x10\n\x0cDocFileError\x10\r\
-    \x12\x19\n\x15EditorUserNotLoginYet\x10dJ\xf8\x03\n\x06\x12\x04\0\0\x0f\
-    \x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\
-    \x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x13\n\x0b\n\x04\x04\0\x02\0\x12\
-    \x03\x03\x04\x1d\n\x0c\n\x05\x04\0\x02\0\x06\x12\x03\x03\x04\x13\n\x0c\n\
-    \x05\x04\0\x02\0\x01\x12\x03\x03\x14\x18\n\x0c\n\x05\x04\0\x02\0\x03\x12\
-    \x03\x03\x1b\x1c\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\x13\n\x0c\n\
-    \x05\x04\0\x02\x01\x05\x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\x02\x01\x01\
-    \x12\x03\x04\x0b\x0e\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04\x11\x12\n\
-    \n\n\x02\x05\0\x12\x04\x06\0\x0f\x01\n\n\n\x03\x05\0\x01\x12\x03\x06\x05\
-    \x14\n\x0b\n\x04\x05\0\x02\0\x12\x03\x07\x04\x10\n\x0c\n\x05\x05\0\x02\0\
-    \x01\x12\x03\x07\x04\x0b\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x07\x0e\x0f\
-    \n\x0b\n\x04\x05\0\x02\x01\x12\x03\x08\x04\x1e\n\x0c\n\x05\x05\0\x02\x01\
-    \x01\x12\x03\x08\x04\x19\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x08\x1c\
-    \x1d\n\x0b\n\x04\x05\0\x02\x02\x12\x03\t\x04\x1b\n\x0c\n\x05\x05\0\x02\
-    \x02\x01\x12\x03\t\x04\x16\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\t\x19\
-    \x1a\n\x0b\n\x04\x05\0\x02\x03\x12\x03\n\x04\x18\n\x0c\n\x05\x05\0\x02\
-    \x03\x01\x12\x03\n\x04\x12\n\x0c\n\x05\x05\0\x02\x03\x02\x12\x03\n\x15\
-    \x17\n\x0b\n\x04\x05\0\x02\x04\x12\x03\x0b\x04\x1a\n\x0c\n\x05\x05\0\x02\
-    \x04\x01\x12\x03\x0b\x04\x14\n\x0c\n\x05\x05\0\x02\x04\x02\x12\x03\x0b\
-    \x17\x19\n\x0b\n\x04\x05\0\x02\x05\x12\x03\x0c\x04\x18\n\x0c\n\x05\x05\0\
-    \x02\x05\x01\x12\x03\x0c\x04\x12\n\x0c\n\x05\x05\0\x02\x05\x02\x12\x03\
-    \x0c\x15\x17\n\x0b\n\x04\x05\0\x02\x06\x12\x03\r\x04\x16\n\x0c\n\x05\x05\
-    \0\x02\x06\x01\x12\x03\r\x04\x10\n\x0c\n\x05\x05\0\x02\x06\x02\x12\x03\r\
-    \x13\x15\n\x0b\n\x04\x05\0\x02\x07\x12\x03\x0e\x04\x20\n\x0c\n\x05\x05\0\
-    \x02\x07\x01\x12\x03\x0e\x04\x19\n\x0c\n\x05\x05\0\x02\x07\x02\x12\x03\
-    \x0e\x1c\x1fb\x06proto3\
+    \x0b\x12\x12\n\x0eDocDescTooLong\x10\x0c\x12\x14\n\x10DocOpenFileError\
+    \x10\r\x12\x16\n\x12DocFilePathInvalid\x10\x0e\x12\x19\n\x15EditorUserNo\
+    tLoginYet\x10dJ\xa1\x04\n\x06\x12\x04\0\0\x10\x01\n\x08\n\x01\x0c\x12\
+    \x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\n\x03\x04\0\x01\
+    \x12\x03\x02\x08\x13\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x1d\n\x0c\n\
+    \x05\x04\0\x02\0\x06\x12\x03\x03\x04\x13\n\x0c\n\x05\x04\0\x02\0\x01\x12\
+    \x03\x03\x14\x18\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x1b\x1c\n\x0b\n\
+    \x04\x04\0\x02\x01\x12\x03\x04\x04\x13\n\x0c\n\x05\x04\0\x02\x01\x05\x12\
+    \x03\x04\x04\n\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\x0b\x0e\n\x0c\n\
+    \x05\x04\0\x02\x01\x03\x12\x03\x04\x11\x12\n\n\n\x02\x05\0\x12\x04\x06\0\
+    \x10\x01\n\n\n\x03\x05\0\x01\x12\x03\x06\x05\x14\n\x0b\n\x04\x05\0\x02\0\
+    \x12\x03\x07\x04\x10\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x07\x04\x0b\n\
+    \x0c\n\x05\x05\0\x02\0\x02\x12\x03\x07\x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\
+    \x12\x03\x08\x04\x1e\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x08\x04\x19\n\
+    \x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x08\x1c\x1d\n\x0b\n\x04\x05\0\x02\
+    \x02\x12\x03\t\x04\x1b\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\t\x04\x16\n\
+    \x0c\n\x05\x05\0\x02\x02\x02\x12\x03\t\x19\x1a\n\x0b\n\x04\x05\0\x02\x03\
+    \x12\x03\n\x04\x18\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\n\x04\x12\n\x0c\
+    \n\x05\x05\0\x02\x03\x02\x12\x03\n\x15\x17\n\x0b\n\x04\x05\0\x02\x04\x12\
+    \x03\x0b\x04\x1a\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x0b\x04\x14\n\x0c\
+    \n\x05\x05\0\x02\x04\x02\x12\x03\x0b\x17\x19\n\x0b\n\x04\x05\0\x02\x05\
+    \x12\x03\x0c\x04\x18\n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\x0c\x04\x12\n\
+    \x0c\n\x05\x05\0\x02\x05\x02\x12\x03\x0c\x15\x17\n\x0b\n\x04\x05\0\x02\
+    \x06\x12\x03\r\x04\x1a\n\x0c\n\x05\x05\0\x02\x06\x01\x12\x03\r\x04\x14\n\
+    \x0c\n\x05\x05\0\x02\x06\x02\x12\x03\r\x17\x19\n\x0b\n\x04\x05\0\x02\x07\
+    \x12\x03\x0e\x04\x1c\n\x0c\n\x05\x05\0\x02\x07\x01\x12\x03\x0e\x04\x16\n\
+    \x0c\n\x05\x05\0\x02\x07\x02\x12\x03\x0e\x19\x1b\n\x0b\n\x04\x05\0\x02\
+    \x08\x12\x03\x0f\x04\x20\n\x0c\n\x05\x05\0\x02\x08\x01\x12\x03\x0f\x04\
+    \x19\n\x0c\n\x05\x05\0\x02\x08\x02\x12\x03\x0f\x1c\x1fb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 19 - 13
rust-lib/flowy-editor/src/protobuf/model/event.rs

@@ -27,7 +27,8 @@
 pub enum EditorEvent {
     CreateDoc = 0,
     UpdateDoc = 1,
-    ReadDoc = 2,
+    ReadDocInfo = 2,
+    ReadDocData = 3,
 }
 
 impl ::protobuf::ProtobufEnum for EditorEvent {
@@ -39,7 +40,8 @@ impl ::protobuf::ProtobufEnum for EditorEvent {
         match value {
             0 => ::std::option::Option::Some(EditorEvent::CreateDoc),
             1 => ::std::option::Option::Some(EditorEvent::UpdateDoc),
-            2 => ::std::option::Option::Some(EditorEvent::ReadDoc),
+            2 => ::std::option::Option::Some(EditorEvent::ReadDocInfo),
+            3 => ::std::option::Option::Some(EditorEvent::ReadDocData),
             _ => ::std::option::Option::None
         }
     }
@@ -48,7 +50,8 @@ impl ::protobuf::ProtobufEnum for EditorEvent {
         static values: &'static [EditorEvent] = &[
             EditorEvent::CreateDoc,
             EditorEvent::UpdateDoc,
-            EditorEvent::ReadDoc,
+            EditorEvent::ReadDocInfo,
+            EditorEvent::ReadDocData,
         ];
         values
     }
@@ -77,16 +80,19 @@ impl ::protobuf::reflect::ProtobufValue for EditorEvent {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x0bevent.proto*8\n\x0bEditorEvent\x12\r\n\tCreateDoc\x10\0\x12\r\n\tU\
-    pdateDoc\x10\x01\x12\x0b\n\x07ReadDoc\x10\x02J\xa5\x01\n\x06\x12\x04\0\0\
-    \x06\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\x04\x02\0\
-    \x06\x01\n\n\n\x03\x05\0\x01\x12\x03\x02\x05\x10\n\x0b\n\x04\x05\0\x02\0\
-    \x12\x03\x03\x04\x12\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x03\x04\r\n\x0c\
-    \n\x05\x05\0\x02\0\x02\x12\x03\x03\x10\x11\n\x0b\n\x04\x05\0\x02\x01\x12\
-    \x03\x04\x04\x12\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x04\x04\r\n\x0c\n\
-    \x05\x05\0\x02\x01\x02\x12\x03\x04\x10\x11\n\x0b\n\x04\x05\0\x02\x02\x12\
-    \x03\x05\x04\x10\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\x05\x04\x0b\n\x0c\
-    \n\x05\x05\0\x02\x02\x02\x12\x03\x05\x0e\x0fb\x06proto3\
+    \n\x0bevent.proto*M\n\x0bEditorEvent\x12\r\n\tCreateDoc\x10\0\x12\r\n\tU\
+    pdateDoc\x10\x01\x12\x0f\n\x0bReadDocInfo\x10\x02\x12\x0f\n\x0bReadDocDa\
+    ta\x10\x03J\xce\x01\n\x06\x12\x04\0\0\x07\x01\n\x08\n\x01\x0c\x12\x03\0\
+    \0\x12\n\n\n\x02\x05\0\x12\x04\x02\0\x07\x01\n\n\n\x03\x05\0\x01\x12\x03\
+    \x02\x05\x10\n\x0b\n\x04\x05\0\x02\0\x12\x03\x03\x04\x12\n\x0c\n\x05\x05\
+    \0\x02\0\x01\x12\x03\x03\x04\r\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x03\
+    \x10\x11\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x04\x04\x12\n\x0c\n\x05\x05\0\
+    \x02\x01\x01\x12\x03\x04\x04\r\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x04\
+    \x10\x11\n\x0b\n\x04\x05\0\x02\x02\x12\x03\x05\x04\x14\n\x0c\n\x05\x05\0\
+    \x02\x02\x01\x12\x03\x05\x04\x0f\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\
+    \x05\x12\x13\n\x0b\n\x04\x05\0\x02\x03\x12\x03\x06\x04\x14\n\x0c\n\x05\
+    \x05\0\x02\x03\x01\x12\x03\x06\x04\x0f\n\x0c\n\x05\x05\0\x02\x03\x02\x12\
+    \x03\x06\x12\x13b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 3 - 4
rust-lib/flowy-editor/src/protobuf/proto/doc_create.proto

@@ -6,13 +6,12 @@ message CreateDocRequest {
     string desc = 3;
     string text = 4;
 }
-message DocDescription {
+message DocInfo {
     string id = 1;
     string name = 2;
     string desc = 3;
     string path = 4;
 }
-message Doc {
-    DocDescription desc = 1;
-    string text = 2;
+message DocData {
+    string text = 1;
 }

+ 4 - 0
rust-lib/flowy-editor/src/protobuf/proto/doc_query.proto

@@ -3,3 +3,7 @@ syntax = "proto3";
 message QueryDocRequest {
     string doc_id = 1;
 }
+message QueryDocDataRequest {
+    string doc_id = 1;
+    string path = 2;
+}

+ 2 - 1
rust-lib/flowy-editor/src/protobuf/proto/errors.proto

@@ -11,6 +11,7 @@ enum EditorErrorCode {
     DocNameInvalid = 10;
     DocViewIdInvalid = 11;
     DocDescTooLong = 12;
-    DocFileError = 13;
+    DocOpenFileError = 13;
+    DocFilePathInvalid = 14;
     EditorUserNotLoginYet = 100;
 }

+ 2 - 1
rust-lib/flowy-editor/src/protobuf/proto/event.proto

@@ -3,5 +3,6 @@ syntax = "proto3";
 enum EditorEvent {
     CreateDoc = 0;
     UpdateDoc = 1;
-    ReadDoc = 2;
+    ReadDocInfo = 2;
+    ReadDocData = 3;
 }

+ 5 - 5
rust-lib/flowy-editor/src/services/doc_controller.rs

@@ -1,5 +1,5 @@
 use crate::{
-    entities::doc::{CreateDocParams, Doc, DocDescription, QueryDocParams, UpdateDocParams},
+    entities::doc::{CreateDocParams, DocData, DocInfo, QueryDocParams, UpdateDocParams},
     errors::EditorError,
     module::EditorDatabase,
     sql_tables::doc::{DocTable, DocTableChangeset, DocTableSql},
@@ -20,9 +20,9 @@ impl DocController {
         &self,
         params: CreateDocParams,
         path: &str,
-    ) -> Result<DocDescription, EditorError> {
+    ) -> Result<DocInfo, EditorError> {
         let doc_table = DocTable::new(params, path);
-        let doc: DocDescription = doc_table.clone().into();
+        let doc: DocInfo = doc_table.clone().into();
         let _ = self.sql.create_doc_table(doc_table)?;
 
         Ok(doc)
@@ -34,9 +34,9 @@ impl DocController {
         Ok(())
     }
 
-    pub(crate) async fn read_doc(&self, doc_id: &str) -> Result<DocDescription, EditorError> {
+    pub(crate) async fn read_doc(&self, doc_id: &str) -> Result<DocInfo, EditorError> {
         let doc_table = self.sql.read_doc_table(doc_id)?;
-        let doc_desc: DocDescription = doc_table.into();
+        let doc_desc: DocInfo = doc_table.into();
         Ok(doc_desc)
     }
 }

+ 4 - 4
rust-lib/flowy-editor/src/sql_tables/doc/doc_table.rs

@@ -1,4 +1,4 @@
-use crate::entities::doc::{CreateDocParams, DocDescription, UpdateDocParams};
+use crate::entities::doc::{CreateDocParams, DocInfo, UpdateDocParams};
 use flowy_database::schema::doc_table;
 use flowy_infra::{timestamp, uuid};
 use std::convert::TryInto;
@@ -48,9 +48,9 @@ impl DocTableChangeset {
     }
 }
 
-impl std::convert::Into<DocDescription> for DocTable {
-    fn into(self) -> DocDescription {
-        DocDescription {
+impl std::convert::Into<DocInfo> for DocTable {
+    fn into(self) -> DocInfo {
+        DocInfo {
             id: self.id,
             name: self.name,
             desc: self.desc,

+ 2 - 2
rust-lib/flowy-editor/tests/editor/doc_test.rs

@@ -5,7 +5,7 @@ fn file_create_test() {
     let doc_desc = create_doc("hello world", "flutter ❤️ rust", "123");
     dbg!(&doc_desc);
 
-    let doc = read_doc(&doc_desc.id);
+    let doc = read_doc_data(&doc_desc.id, &doc_desc.path);
     assert_eq!(doc.text, "123".to_owned());
 }
 
@@ -17,6 +17,6 @@ fn file_update_text_test() {
     let content = "😁😁😁😁😁😁😁😁😁😁".to_owned();
     save_doc(&doc_desc, &content);
 
-    let doc = read_doc(&doc_desc.id);
+    let doc = read_doc_data(&doc_desc.id, &doc_desc.path);
     assert_eq!(doc.text, content);
 }

+ 21 - 6
rust-lib/flowy-editor/tests/editor/helper.rs

@@ -3,7 +3,7 @@ use flowy_test::builder::SingleUserTestBuilder;
 use flowy_editor::{entities::doc::*, event::EditorEvent::*};
 use flowy_infra::uuid;
 
-pub fn create_doc(name: &str, desc: &str, text: &str) -> DocDescription {
+pub fn create_doc(name: &str, desc: &str, text: &str) -> DocInfo {
     let request = CreateDocRequest {
         id: uuid(),
         name: name.to_owned(),
@@ -15,12 +15,12 @@ pub fn create_doc(name: &str, desc: &str, text: &str) -> DocDescription {
         .event(CreateDoc)
         .request(request)
         .sync_send()
-        .parse::<DocDescription>();
+        .parse::<DocInfo>();
 
     doc_desc
 }
 
-pub fn save_doc(desc: &DocDescription, content: &str) {
+pub fn save_doc(desc: &DocInfo, content: &str) {
     let request = UpdateDocRequest {
         id: desc.id.clone(),
         name: Some(desc.name.clone()),
@@ -34,16 +34,31 @@ pub fn save_doc(desc: &DocDescription, content: &str) {
         .sync_send();
 }
 
-pub fn read_doc(doc_id: &str) -> Doc {
+pub fn read_doc(doc_id: &str) -> DocInfo {
     let request = QueryDocRequest {
         doc_id: doc_id.to_string(),
     };
 
     let doc = SingleUserTestBuilder::new()
-        .event(ReadDoc)
+        .event(ReadDocInfo)
         .request(request)
         .sync_send()
-        .parse::<Doc>();
+        .parse::<DocInfo>();
+
+    doc
+}
+
+pub fn read_doc_data(doc_id: &str, path: &str) -> DocData {
+    let request = QueryDocDataRequest {
+        doc_id: doc_id.to_string(),
+        path: path.to_string(),
+    };
+
+    let doc = SingleUserTestBuilder::new()
+        .event(ReadDocData)
+        .request(request)
+        .sync_send()
+        .parse::<DocData>();
 
     doc
 }