فهرست منبع

add auto format link test

appflowy 3 سال پیش
والد
کامیت
0fb808ef4c

+ 3 - 0
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);

+ 1 - 0
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"

+ 70 - 6
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<Delta> {
+    fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
         // enter whitespace to trigger auto format
         if !is_whitespace(text) {
             return None;
         }
         let mut iter = DeltaIter::new(delta);
-        iter.seek::<CharMetric>(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();
+
+                    // 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(),
         }
+    }
 
-        let _prev = prev.unwrap();
+    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<AutoFormatter> {
+        if let Ok(url) = Url::parse(s) {
+            return Some(AutoFormatter::Url(url));
+        }
 
         None
     }

+ 1 - 0
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<dyn InsertExt>;
 pub type FormatExtension = Box<dyn FormatExt>;

+ 5 - 2
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<usize> {
@@ -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) }

+ 16 - 2
rust-lib/flowy-ot/src/core/attributes/builder.rs

@@ -96,7 +96,7 @@ impl std::convert::Into<Attributes> 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 }
 }

+ 49 - 1
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);
+}

+ 3 - 1
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();
         });
 

+ 2 - 2
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");
 

+ 2 - 2
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);