From fa061ea832b7c220236253b699f8cad71b80c103 Mon Sep 17 00:00:00 2001 From: appflowy Date: Thu, 22 Jul 2021 21:43:01 +0800 Subject: [PATCH] add flowy-editor crate --- rust-lib/Cargo.toml | 1 + rust-lib/flowy-editor/Cargo.toml | 15 +++ rust-lib/flowy-editor/Flowy.toml | 3 + .../src/entities/doc/doc_create.rs | 44 +++++++ .../src/entities/doc/doc_modify.rs | 0 rust-lib/flowy-editor/src/entities/doc/mod.rs | 5 + .../src/entities/doc/parser/doc_name.rs | 12 ++ .../src/entities/doc/parser/doc_view_id.rs | 12 ++ .../src/entities/doc/parser/mod.rs | 5 + rust-lib/flowy-editor/src/entities/mod.rs | 1 + rust-lib/flowy-editor/src/errors.rs | 85 +++++++++++++ rust-lib/flowy-editor/src/event.rs | 0 .../flowy-editor/src/file_manager/file.rs | 117 ++++++++++++++++++ .../flowy-editor/src/file_manager/manager.rs | 39 ++++++ rust-lib/flowy-editor/src/file_manager/mod.rs | 2 + rust-lib/flowy-editor/src/handlers/mod.rs | 0 rust-lib/flowy-editor/src/lib.rs | 6 + rust-lib/flowy-editor/src/module.rs | 0 18 files changed, 347 insertions(+) create mode 100644 rust-lib/flowy-editor/Cargo.toml create mode 100644 rust-lib/flowy-editor/Flowy.toml create mode 100644 rust-lib/flowy-editor/src/entities/doc/doc_create.rs create mode 100644 rust-lib/flowy-editor/src/entities/doc/doc_modify.rs create mode 100644 rust-lib/flowy-editor/src/entities/doc/mod.rs create mode 100644 rust-lib/flowy-editor/src/entities/doc/parser/doc_name.rs create mode 100644 rust-lib/flowy-editor/src/entities/doc/parser/doc_view_id.rs create mode 100644 rust-lib/flowy-editor/src/entities/doc/parser/mod.rs create mode 100644 rust-lib/flowy-editor/src/entities/mod.rs create mode 100644 rust-lib/flowy-editor/src/errors.rs create mode 100644 rust-lib/flowy-editor/src/event.rs create mode 100644 rust-lib/flowy-editor/src/file_manager/file.rs create mode 100644 rust-lib/flowy-editor/src/file_manager/manager.rs create mode 100644 rust-lib/flowy-editor/src/file_manager/mod.rs create mode 100644 rust-lib/flowy-editor/src/handlers/mod.rs create mode 100644 rust-lib/flowy-editor/src/lib.rs create mode 100644 rust-lib/flowy-editor/src/module.rs diff --git a/rust-lib/Cargo.toml b/rust-lib/Cargo.toml index deef32c996..87a8758ff7 100644 --- a/rust-lib/Cargo.toml +++ b/rust-lib/Cargo.toml @@ -13,6 +13,7 @@ members = [ "flowy-infra", "flowy-workspace", "flowy-observable", + "flowy-editor", ] [profile.dev] diff --git a/rust-lib/flowy-editor/Cargo.toml b/rust-lib/flowy-editor/Cargo.toml new file mode 100644 index 0000000000..3f1a2ecaf4 --- /dev/null +++ b/rust-lib/flowy-editor/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "flowy-editor" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +derive_more = {version = "0.99", features = ["display"]} +flowy-dispatch = { path = "../flowy-dispatch" } +flowy-log = { path = "../flowy-log" } +flowy-derive = { path = "../flowy-derive" } +flowy-database = { path = "../flowy-database" } +diesel = {version = "1.4.7", features = ["sqlite"]} +diesel_derives = {version = "1.4.1", features = ["sqlite"]} \ No newline at end of file diff --git a/rust-lib/flowy-editor/Flowy.toml b/rust-lib/flowy-editor/Flowy.toml new file mode 100644 index 0000000000..728e5eb776 --- /dev/null +++ b/rust-lib/flowy-editor/Flowy.toml @@ -0,0 +1,3 @@ + +proto_crates = ["src/entities", "src/event.rs", "src/errors.rs"] +event_files = ["src/event.rs"] \ No newline at end of file diff --git a/rust-lib/flowy-editor/src/entities/doc/doc_create.rs b/rust-lib/flowy-editor/src/entities/doc/doc_create.rs new file mode 100644 index 0000000000..7cfe00c634 --- /dev/null +++ b/rust-lib/flowy-editor/src/entities/doc/doc_create.rs @@ -0,0 +1,44 @@ +use crate::{ + entities::doc::parser::*, + errors::{ErrorBuilder, *}, +}; +use flowy_derive::ProtoBuf; +use std::convert::TryInto; + +#[derive(ProtoBuf, Default)] +pub struct CreateDocRequest { + #[pb(index = 1)] + view_id: String, + + #[pb(index = 2)] + pub name: String, +} + +pub struct CreateDocParams { + pub view_id: String, + pub name: String, +} + +impl TryInto for CreateDocRequest { + type Error = WorkspaceError; + + fn try_into(self) -> Result { + let name = DocName::parse(self.name) + .map_err(|e| { + ErrorBuilder::new(EditorErrorCode::DocNameInvalid) + .msg(e) + .build() + })? + .0; + + let view_id = DocViewId::parse(self.view_id) + .map_err(|e| { + ErrorBuilder::new(EditorErrorCode::DocViewIdInvalid) + .msg(e) + .build() + })? + .0; + + Ok(CreateDocParams { view_id, name }) + } +} diff --git a/rust-lib/flowy-editor/src/entities/doc/doc_modify.rs b/rust-lib/flowy-editor/src/entities/doc/doc_modify.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/rust-lib/flowy-editor/src/entities/doc/mod.rs b/rust-lib/flowy-editor/src/entities/doc/mod.rs new file mode 100644 index 0000000000..c79e9aac02 --- /dev/null +++ b/rust-lib/flowy-editor/src/entities/doc/mod.rs @@ -0,0 +1,5 @@ +mod doc_create; +mod doc_modify; +mod parser; + +pub use doc_create::*; diff --git a/rust-lib/flowy-editor/src/entities/doc/parser/doc_name.rs b/rust-lib/flowy-editor/src/entities/doc/parser/doc_name.rs new file mode 100644 index 0000000000..3138acaaf6 --- /dev/null +++ b/rust-lib/flowy-editor/src/entities/doc/parser/doc_name.rs @@ -0,0 +1,12 @@ +#[derive(Debug)] +pub struct DocName(pub String); + +impl DocName { + pub fn parse(s: String) -> Result { + if s.trim().is_empty() { + return Err(format!("Doc name can not be empty or whitespace")); + } + + Ok(Self(s)) + } +} diff --git a/rust-lib/flowy-editor/src/entities/doc/parser/doc_view_id.rs b/rust-lib/flowy-editor/src/entities/doc/parser/doc_view_id.rs new file mode 100644 index 0000000000..6dea4f0fb0 --- /dev/null +++ b/rust-lib/flowy-editor/src/entities/doc/parser/doc_view_id.rs @@ -0,0 +1,12 @@ +#[derive(Debug)] +pub struct DocViewId(pub String); + +impl DocViewId { + pub fn parse(s: String) -> Result { + if s.trim().is_empty() { + return Err(format!("Doc view id can not be empty or whitespace")); + } + + Ok(Self(s)) + } +} diff --git a/rust-lib/flowy-editor/src/entities/doc/parser/mod.rs b/rust-lib/flowy-editor/src/entities/doc/parser/mod.rs new file mode 100644 index 0000000000..dd28c2920f --- /dev/null +++ b/rust-lib/flowy-editor/src/entities/doc/parser/mod.rs @@ -0,0 +1,5 @@ +mod doc_name; +mod doc_view_id; + +pub use doc_name::*; +pub use doc_view_id::*; diff --git a/rust-lib/flowy-editor/src/entities/mod.rs b/rust-lib/flowy-editor/src/entities/mod.rs new file mode 100644 index 0000000000..dc16bd593b --- /dev/null +++ b/rust-lib/flowy-editor/src/entities/mod.rs @@ -0,0 +1 @@ +pub mod doc; diff --git a/rust-lib/flowy-editor/src/errors.rs b/rust-lib/flowy-editor/src/errors.rs new file mode 100644 index 0000000000..f6fb12e2fd --- /dev/null +++ b/rust-lib/flowy-editor/src/errors.rs @@ -0,0 +1,85 @@ +use derive_more::Display; +use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; +use flowy_dispatch::prelude::{EventResponse, ResponseBuilder}; +use std::convert::TryInto; + +#[derive(Debug, Default, Clone, ProtoBuf)] +pub struct EditorError { + #[pb(index = 1)] + pub code: UserErrorCode, + + #[pb(index = 2)] + pub msg: String, +} + +impl EditorError { + fn new(code: EditorErrorCode, msg: &str) -> Self { + Self { + code, + msg: msg.to_owned(), + } + } +} + +#[derive(Debug, Clone, ProtoBuf_Enum, Display, PartialEq, Eq)] +pub enum EditorErrorCode { + #[display(fmt = "Unknown")] + Unknown = 0, + + #[display(fmt = "EditorDBInternalError")] + EditorDBInternalError = 1, + + #[display(fmt = "DocNameInvalid")] + DocNameInvalid = 10, + + #[display(fmt = "DocViewIdInvalid")] + DocViewIdInvalid = 11, +} + +impl std::default::Default for UserErrorCode { + fn default() -> Self { UserErrorCode::Unknown } +} + +impl std::convert::From for EditorError { + fn from(error: flowy_database::result::Error) -> Self { + ErrorBuilder::new(EditorErrorCode::EditorDBInternalError) + .error(error) + .build() + } +} + +impl flowy_dispatch::Error for EditorError { + fn as_response(&self) -> EventResponse { + let bytes: Vec = self.clone().try_into().unwrap(); + ResponseBuilder::Err().data(bytes).build() + } +} + +pub struct ErrorBuilder { + pub code: UserErrorCode, + pub msg: Option, +} + +impl ErrorBuilder { + pub fn new(code: EditorErrorCode) -> Self { ErrorBuilder { code, msg: None } } + + pub fn msg(mut self, msg: T) -> Self + where + T: Into, + { + self.msg = Some(msg.into()); + self + } + + pub fn error(mut self, msg: T) -> Self + where + T: std::fmt::Debug, + { + self.msg = Some(format!("{:?}", msg)); + self + } + + pub fn build(mut self) -> EditorError { + EditorError::new(self.code, &self.msg.take().unwrap_or("".to_owned())) + } +} diff --git a/rust-lib/flowy-editor/src/event.rs b/rust-lib/flowy-editor/src/event.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/rust-lib/flowy-editor/src/file_manager/file.rs b/rust-lib/flowy-editor/src/file_manager/file.rs new file mode 100644 index 0000000000..d0910d2ed9 --- /dev/null +++ b/rust-lib/flowy-editor/src/file_manager/file.rs @@ -0,0 +1,117 @@ +use std::{ + ffi::OsString, + fs, + fs::File, + io, + io::{Read, Write}, + path::{Path, PathBuf}, + str, + time::SystemTime, +}; +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct FileId(pub(crate) usize); + +#[derive(Debug, Clone, Copy)] +pub enum CharacterEncoding { + Utf8, + Utf8WithBom, +} + +const UTF8_BOM: &str = "\u{feff}"; +impl CharacterEncoding { + pub(crate) fn guess(s: &[u8]) -> Self { + if s.starts_with(UTF8_BOM.as_bytes()) { + CharacterEncoding::Utf8WithBom + } else { + CharacterEncoding::Utf8 + } + } +} + +pub enum FileError { + Io(io::Error, PathBuf), + UnknownEncoding(PathBuf), + HasChanged(PathBuf), +} + +pub struct FileInfo { + pub path: PathBuf, + pub modify_time: Option, + pub has_changed: bool, + pub encoding: CharacterEncoding, +} + +pub(crate) fn try_load_file

(path: P) -> Result<(String, FileInfo), FileError> +where + P: AsRef, +{ + let mut f = + File::open(path.as_ref()).map_err(|e| FileError::Io(e, path.as_ref().to_owned()))?; + let mut bytes = Vec::new(); + f.read_to_end(&mut bytes) + .map_err(|e| FileError::Io(e, path.as_ref().to_owned()))?; + + let encoding = CharacterEncoding::guess(&bytes); + let s = try_decode(bytes, encoding, path.as_ref())?; + let info = FileInfo { + encoding, + path: path.as_ref().to_owned(), + modify_time: get_mod_time(&path), + has_changed: false, + }; + Ok((s, info)) +} + +fn get_mod_time>(path: P) -> Option { + File::open(path) + .and_then(|f| f.metadata()) + .and_then(|meta| meta.modified()) + .ok() +} + +fn try_save( + path: &Path, + text: &str, + encoding: CharacterEncoding, + _file_info: Option<&FileInfo>, +) -> io::Result<()> { + let tmp_extension = path.extension().map_or_else( + || OsString::from("swp"), + |ext| { + let mut ext = ext.to_os_string(); + ext.push(".swp"); + ext + }, + ); + let tmp_path = &path.with_extension(tmp_extension); + + let mut f = File::create(tmp_path)?; + match encoding { + CharacterEncoding::Utf8WithBom => f.write_all(UTF8_BOM.as_bytes())?, + CharacterEncoding::Utf8 => (), + } + + f.write_all(text.as_bytes())?; + fs::rename(tmp_path, path)?; + + Ok(()) +} + +fn try_decode( + bytes: Vec, + encoding: CharacterEncoding, + path: &Path, +) -> Result { + match encoding { + CharacterEncoding::Utf8 => { + Ok(String::from(str::from_utf8(&bytes).map_err(|_e| { + FileError::UnknownEncoding(path.to_owned()) + })?)) + }, + CharacterEncoding::Utf8WithBom => { + let s = String::from_utf8(bytes) + .map_err(|_e| FileError::UnknownEncoding(path.to_owned()))?; + Ok(String::from(&s[UTF8_BOM.len()..])) + }, + } +} diff --git a/rust-lib/flowy-editor/src/file_manager/manager.rs b/rust-lib/flowy-editor/src/file_manager/manager.rs new file mode 100644 index 0000000000..0db0809916 --- /dev/null +++ b/rust-lib/flowy-editor/src/file_manager/manager.rs @@ -0,0 +1,39 @@ +use crate::file_manager::file::*; +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; + +pub struct FileManager { + open_files: HashMap, + file_info: HashMap, +} + +impl FileManager { + pub fn new() -> Self { + Self { + open_files: HashMap::new(), + file_info: HashMap::new(), + } + } + + pub fn get_info(&self, id: FileId) -> Option<&FileInfo> { self.file_info.get(&id) } + + pub fn get_editor(&self, path: &Path) -> Option { self.open_files.get(path).cloned() } + + pub fn open(&mut self, path: &Path, id: FileId) -> Result { + if !path.exists() { + return Ok("".to_string()); + } + + let (s, info) = try_load_file(path)?; + self.open_files.insert(path.to_owned(), id); + Ok(s) + } + + pub fn close(&mut self, id: FileId) { + if let Some(info) = self.file_info.remove(&id) { + self.open_files.remove(&info.path); + } + } +} diff --git a/rust-lib/flowy-editor/src/file_manager/mod.rs b/rust-lib/flowy-editor/src/file_manager/mod.rs new file mode 100644 index 0000000000..4fc7f96ac2 --- /dev/null +++ b/rust-lib/flowy-editor/src/file_manager/mod.rs @@ -0,0 +1,2 @@ +mod file; +mod manager; diff --git a/rust-lib/flowy-editor/src/handlers/mod.rs b/rust-lib/flowy-editor/src/handlers/mod.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/rust-lib/flowy-editor/src/lib.rs b/rust-lib/flowy-editor/src/lib.rs new file mode 100644 index 0000000000..66fe4a1173 --- /dev/null +++ b/rust-lib/flowy-editor/src/lib.rs @@ -0,0 +1,6 @@ +mod entities; +mod errors; +mod event; +mod file_manager; +mod handlers; +mod module; diff --git a/rust-lib/flowy-editor/src/module.rs b/rust-lib/flowy-editor/src/module.rs new file mode 100644 index 0000000000..e69de29bb2