diff --git a/app_flowy/packages/flowy_editor/lib/src/model/document/document.dart b/app_flowy/packages/flowy_editor/lib/src/model/document/document.dart index 8eb5f7a964..2619cd5885 100644 --- a/app_flowy/packages/flowy_editor/lib/src/model/document/document.dart +++ b/app_flowy/packages/flowy_editor/lib/src/model/document/document.dart @@ -69,6 +69,7 @@ class Document { length: replaceLength, ); + print('current document delta: $_delta'); print('insert delta: $delta'); compose(delta, ChangeSource.LOCAL); print('compose insert, current document $_delta'); @@ -79,6 +80,7 @@ class Document { assert(index >= 0 && length > 0); final delta = _rules.apply(RuleType.DELETE, this, index, length: length); if (delta.isNotEmpty) { + print('current document delta: $_delta'); compose(delta, ChangeSource.LOCAL); print('compose delete, current document $_delta'); } @@ -124,6 +126,7 @@ class Document { attribute: attribute, ); if (formatDelta.isNotEmpty) { + print('current document delta: $_delta'); compose(formatDelta, ChangeSource.LOCAL); print('compose format, current document $_delta'); delta = delta.compose(formatDelta); diff --git a/rust-lib/flowy-ot/Cargo.toml b/rust-lib/flowy-ot/Cargo.toml index 87b18ecc9e..c3bdaad774 100644 --- a/rust-lib/flowy-ot/Cargo.toml +++ b/rust-lib/flowy-ot/Cargo.toml @@ -14,6 +14,7 @@ log = "0.4" color-eyre = { version = "0.5", default-features = false } chrono = "0.4.19" lazy_static = "1.4.0" +url = "2.2" [dev-dependencies] criterion = "0.3" diff --git a/rust-lib/flowy-ot/src/client/extensions/insert/auto_format.rs b/rust-lib/flowy-ot/src/client/extensions/insert/auto_format.rs index 2ed1eea6e6..7c827f00d3 100644 --- a/rust-lib/flowy-ot/src/client/extensions/insert/auto_format.rs +++ b/rust-lib/flowy-ot/src/client/extensions/insert/auto_format.rs @@ -7,19 +7,83 @@ pub struct AutoFormatExt {} impl InsertExt for AutoFormatExt { fn ext_name(&self) -> &str { "AutoFormatExt" } - fn apply(&self, delta: &Delta, _replace_len: usize, text: &str, index: usize) -> Option { + fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option { // enter whitespace to trigger auto format if !is_whitespace(text) { return None; } let mut iter = DeltaIter::new(delta); - iter.seek::(index); - let prev = iter.next_op(); - if prev.is_none() { - return None; - } + if let Some(prev) = iter.next_op_before(index) { + match AutoFormat::parse(prev.get_data()) { + None => {}, + Some(formatter) => { + let mut new_attributes = prev.get_attributes(); - let _prev = prev.unwrap(); + // format_len should not greater than index. The url crate will add "/" to the + // end of input string that causes the format_len greater than the input string + let format_len = min(index, formatter.format_len()); + + let format_attributes = formatter.to_attributes(); + format_attributes.iter().for_each(|(k, v)| { + if !new_attributes.contains_key(k) { + new_attributes.insert(k.clone(), v.clone()); + } + }); + + let next_attributes = match iter.next_op() { + None => Attributes::empty(), + Some(op) => op.get_attributes(), + }; + + return Some( + DeltaBuilder::new() + .retain(index + replace_len - min(index, format_len)) + .retain_with_attributes(format_len, format_attributes) + .insert_with_attributes(text, next_attributes) + .build(), + ); + }, + } + } + + None + } +} + +use crate::{ + client::extensions::NEW_LINE, + core::{Attribute, AttributeBuilder, Attributes, DeltaBuilder, Operation}, +}; +use bytecount::num_chars; +use std::cmp::min; +use url::{ParseError, Url}; + +pub enum AutoFormatter { + Url(Url), +} + +impl AutoFormatter { + pub fn to_attributes(&self) -> Attributes { + match self { + AutoFormatter::Url(url) => AttributeBuilder::new().link(url.as_str(), true).build(), + } + } + + pub fn format_len(&self) -> usize { + let s = match self { + AutoFormatter::Url(url) => url.to_string(), + }; + + num_chars(s.as_bytes()) + } +} + +pub struct AutoFormat {} +impl AutoFormat { + fn parse(s: &str) -> Option { + if let Ok(url) = Url::parse(s) { + return Some(AutoFormatter::Url(url)); + } None } diff --git a/rust-lib/flowy-ot/src/client/extensions/mod.rs b/rust-lib/flowy-ot/src/client/extensions/mod.rs index 605c65dd30..ea61a89e7d 100644 --- a/rust-lib/flowy-ot/src/client/extensions/mod.rs +++ b/rust-lib/flowy-ot/src/client/extensions/mod.rs @@ -9,6 +9,7 @@ mod format; mod insert; pub const NEW_LINE: &'static str = "\n"; +pub const WHITESPACE: &'static str = " "; pub type InsertExtension = Box; pub type FormatExtension = Box; diff --git a/rust-lib/flowy-ot/src/client/util.rs b/rust-lib/flowy-ot/src/client/util.rs index 468e1d747a..6e9566c54e 100644 --- a/rust-lib/flowy-ot/src/client/util.rs +++ b/rust-lib/flowy-ot/src/client/util.rs @@ -1,4 +1,7 @@ -use crate::{client::extensions::NEW_LINE, core::Operation}; +use crate::{ + client::extensions::{NEW_LINE, WHITESPACE}, + core::Operation, +}; #[inline] pub fn find_newline(s: &str) -> Option { @@ -60,7 +63,7 @@ pub fn is_op_contains_newline(op: &Operation) -> bool { contain_newline(op.get_d pub fn is_newline(s: &str) -> bool { s == NEW_LINE } #[inline] -pub fn is_whitespace(s: &str) -> bool { s == " " } +pub fn is_whitespace(s: &str) -> bool { s == WHITESPACE } #[inline] pub fn contain_newline(s: &str) -> bool { s.contains(NEW_LINE) } diff --git a/rust-lib/flowy-ot/src/core/attributes/builder.rs b/rust-lib/flowy-ot/src/core/attributes/builder.rs index 275d4c9d40..66ca5f2511 100644 --- a/rust-lib/flowy-ot/src/core/attributes/builder.rs +++ b/rust-lib/flowy-ot/src/core/attributes/builder.rs @@ -96,7 +96,7 @@ impl std::convert::Into for Attribute { } } -pub struct AttrsBuilder { +pub struct AttributeBuilder { inner: Attributes, } @@ -112,7 +112,19 @@ macro_rules! impl_bool_attribute { }; } -impl AttrsBuilder { +macro_rules! impl_str_attribute { + ($name: ident,$key: expr) => { + pub fn $name(self, s: &str, value: bool) -> Self { + let value = match value { + true => s, + false => REMOVE_FLAG, + }; + self.insert($key, value) + } + }; +} + +impl AttributeBuilder { pub fn new() -> Self { Self { inner: Attributes::default(), @@ -134,10 +146,12 @@ impl AttrsBuilder { self } + // AttributeBuilder::new().bold(true).build() impl_bool_attribute!(bold, AttributeKey::Bold); impl_bool_attribute!(italic, AttributeKey::Italic); impl_bool_attribute!(underline, AttributeKey::Underline); impl_bool_attribute!(strike_through, AttributeKey::StrikeThrough); + impl_str_attribute!(link, AttributeKey::Link); pub fn build(self) -> Attributes { self.inner } } diff --git a/rust-lib/flowy-ot/tests/attribute_test.rs b/rust-lib/flowy-ot/tests/attribute_test.rs index 3aa7ab7f49..9cbfc1e278 100644 --- a/rust-lib/flowy-ot/tests/attribute_test.rs +++ b/rust-lib/flowy-ot/tests/attribute_test.rs @@ -3,7 +3,7 @@ pub mod helper; use crate::helper::{TestOp::*, *}; use flowy_ot::core::Interval; -use flowy_ot::client::extensions::NEW_LINE; +use flowy_ot::client::extensions::{NEW_LINE, WHITESPACE}; #[test] fn attributes_insert_text() { @@ -587,3 +587,51 @@ fn attributes_link_insert_newline_at_middle() { OpTester::new().run_script_with_newline(ops); } + +#[test] +fn attributes_auto_format_link() { + let site = "https://appflowy.io"; + let ops = vec![ + Insert(0, site, 0), + AssertOpsJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#), + Insert(0, WHITESPACE, site.len()), + AssertOpsJson( + 0, + r#"[{"insert":"https://appflowy.io","attributes":{"link":"https://appflowy.io/"}},{"insert":" \n"}]"#, + ), + ]; + + OpTester::new().run_script_with_newline(ops); +} + +#[test] +fn attributes_auto_format_exist_link() { + let site = "https://appflowy.io"; + let ops = vec![ + Insert(0, site, 0), + Link(0, Interval::new(0, site.len()), site, true), + Insert(0, WHITESPACE, site.len()), + AssertOpsJson( + 0, + r#"[{"insert":"https://appflowy.io","attributes":{"link":"https://appflowy.io/"}},{"insert":" \n"}]"#, + ), + ]; + + OpTester::new().run_script_with_newline(ops); +} + +#[test] +fn attributes_auto_format_exist_link2() { + let site = "https://appflowy.io"; + let ops = vec![ + Insert(0, site, 0), + Link(0, Interval::new(0, site.len() / 2), site, true), + Insert(0, WHITESPACE, site.len()), + AssertOpsJson( + 0, + r#"[{"insert":"https://a","attributes":{"link":"https://appflowy.io"}},{"insert":"ppflowy.io \n"}]"#, + ), + ]; + + OpTester::new().run_script_with_newline(ops); +} diff --git a/rust-lib/flowy-ot/tests/helper/mod.rs b/rust-lib/flowy-ot/tests/helper/mod.rs index 03567795ae..933a3f8dff 100644 --- a/rust-lib/flowy-ot/tests/helper/mod.rs +++ b/rust-lib/flowy-ot/tests/helper/mod.rs @@ -3,6 +3,8 @@ use flowy_ot::{client::Document, core::*}; use rand::{prelude::*, Rng as WrappedRng}; use std::{sync::Once, time::Duration}; +const LEVEL: &'static str = "info"; + #[derive(Clone, Debug, Display)] pub enum TestOp { #[display(fmt = "Insert")] @@ -63,7 +65,7 @@ impl OpTester { static INIT: Once = Once::new(); INIT.call_once(|| { color_eyre::install().unwrap(); - std::env::set_var("RUST_LOG", "info"); + std::env::set_var("RUST_LOG", LEVEL); env_logger::init(); }); diff --git a/rust-lib/flowy-ot/tests/op_test.rs b/rust-lib/flowy-ot/tests/op_test.rs index af3173d150..01fae207e0 100644 --- a/rust-lib/flowy-ot/tests/op_test.rs +++ b/rust-lib/flowy-ot/tests/op_test.rs @@ -226,7 +226,7 @@ fn delta_seek_4() { #[test] fn delta_seek_5() { let mut delta = Delta::default(); - let attributes = AttrsBuilder::new().bold(true).italic(true).build(); + let attributes = AttributeBuilder::new().bold(true).italic(true).build(); delta.add( OpBuilder::insert("1234") .attributes(attributes.clone()) @@ -469,7 +469,7 @@ fn transform2() { fn delta_transform_test() { let mut a = Delta::default(); let mut a_s = String::new(); - a.insert("123", AttrsBuilder::new().bold(true).build()); + a.insert("123", AttributeBuilder::new().bold(true).build()); a_s = a.apply(&a_s).unwrap(); assert_eq!(&a_s, "123"); diff --git a/rust-lib/flowy-ot/tests/serde_test.rs b/rust-lib/flowy-ot/tests/serde_test.rs index 2d821f4ead..e81059acfa 100644 --- a/rust-lib/flowy-ot/tests/serde_test.rs +++ b/rust-lib/flowy-ot/tests/serde_test.rs @@ -2,7 +2,7 @@ use flowy_ot::core::*; #[test] fn operation_insert_serialize_test() { - let attributes = AttrsBuilder::new().bold(true).italic(true).build(); + let attributes = AttributeBuilder::new().bold(true).italic(true).build(); let operation = OpBuilder::insert("123").attributes(attributes).build(); let json = serde_json::to_string(&operation).unwrap(); eprintln!("{}", json); @@ -32,7 +32,7 @@ fn operation_delete_serialize_test() { fn delta_serialize_test() { let mut delta = Delta::default(); - let attributes = AttrsBuilder::new().bold(true).italic(true).build(); + let attributes = AttributeBuilder::new().bold(true).italic(true).build(); let retain = OpBuilder::insert("123").attributes(attributes).build(); delta.add(retain);