[rust]: fix code point issue

This commit is contained in:
appflowy 2021-11-12 21:44:26 +08:00
parent be2224307f
commit 5cf47e9f55
21 changed files with 387 additions and 84 deletions

View File

@ -123,7 +123,7 @@ class DocBloc extends Bloc<DocEvent, DocState> {
Document _decodeJsonToDocument(String data) { Document _decodeJsonToDocument(String data) {
// String d = r''' // String d = r'''
// [{"insert":"\n👋 Welcome to AppFlowy!\n"},{"insert":"\n","attributes":{"header":1}},{"insert":"Here are the basics\n"},{"insert":"lick anywhere and just start typing\n"},{"insert":"H","attributes":{"list":"unchecked"}},{"insert":"ighlight any text, and use the menu at the bottom to style your writing however you like\n"},{"insert":"C","attributes":{"list":"unchecked"}},{"insert":"lick + New Page button at the bottom of your sidebar to add a new page\n"},{"insert":"C","attributes":{"list":"unchecked"}},{"insert":"lick the + next to any page title in the sidebar to quickly add a new subpage\n"},{"insert":"\n","attributes":{"list":"unchecked"}},{"insert":"Have a question? \n"},{"insert":"lick the '?' at the bottom right for help and support.\n\nLike AppFlowy? Follow us:\n"},{"insert":"G","attributes":{"header":2}},{"insert":"ithub: https://github.com/AppFlowy-IO/appflowy\n"},{"insert":"T","attributes":{"blockquote":true}},{"insert":"witter: https://twitter.com/appflowy\n"},{"insert":"N","attributes":{"blockquote":true}},{"insert":"ewsletter: https://www.appflowy.io/blog\n"},{"retain":1,"attributes":{"blockquote":true}},{"insert":"\n"}] // "[{"insert":"\n👋 Welcome to AppFlowy!\n"},{"insert":"\n","attributes":{"header":1}},{"insert":"Here are the basics\n"},{"insert":"C","attributes":{"header":2}},{"insert":"lick anywhere and just start typing\n"},{"insert":"H","attributes":{"list":"unchecked"}},{"insert":"ighlight any text, and use the menu at the bottom to style your writing however you like\n"},{"insert":"C","attributes":{"list":"unchecked"}},{"insert":"lick + New Page button at the bottom of your sidebar to add a new page\n"},{"insert":"C","attributes":{"list":"unchecked"}},{"insert":"lick the + next to any page title in the sidebar to quickly add a new subpage\n"},{"insert":"\n","attributes":{"list":"unchecked"}},{"insert":"Have a question? \n"},{"insert":"C","attributes":{"header":2}},{"insert":"lick the '?' at the bottom right for help and support.\n\nLike AppFlowy? Follow us:\n"},{"insert":"G","attributes":{"header":2}},{"insert":"ithub: https://github.com/AppFlowy-IO/appflowy\n"},{"insert":"T","attributes":{"blockquote":true}},{"insert":"witter: https://twitter.com/appflowy\n"},{"insert":"N","attributes":{"blockquote":true}},{"insert":"ewsletter: https://www.appflowy.io/blog\n"},{"retain":1,"attributes":{"blockquote":true}},{"insert":"\n"}]"
// '''; // ''';
final json = jsonDecode(data); final json = jsonDecode(data);

View File

@ -181,7 +181,7 @@ impl ServerEditDoc {
log::error!("Failed to acquire write lock of document"); log::error!("Failed to acquire write lock of document");
}, },
Some(mut write_guard) => { Some(mut write_guard) => {
let _ = write_guard.compose_delta(&delta).map_err(internal_error)?; let _ = write_guard.compose_delta(delta).map_err(internal_error)?;
tracing::Span::current().record("result", &write_guard.to_json().as_str()); tracing::Span::current().record("result", &write_guard.to_json().as_str());
}, },
} }

View File

@ -58,7 +58,7 @@ async fn workspace_update() {
async fn workspace_delete() { async fn workspace_delete() {
let test = WorkspaceTest::new().await; let test = WorkspaceTest::new().await;
let delete_params = WorkspaceIdentifier { let delete_params = WorkspaceIdentifier {
workspace_id: test.workspace.id.clone(), workspace_id: Some(test.workspace.id.clone()),
}; };
let _ = test.server.delete_workspace(delete_params).await; let _ = test.server.delete_workspace(delete_params).await;

View File

@ -21,7 +21,7 @@ flowy-net = { path = "../flowy-net", features = ["flowy_request"] }
diesel = {version = "1.4.7", features = ["sqlite"]} diesel = {version = "1.4.7", features = ["sqlite"]}
diesel_derives = {version = "1.4.1", features = ["sqlite"]} diesel_derives = {version = "1.4.1", features = ["sqlite"]}
protobuf = {version = "2.18.0"} protobuf = {version = "2.18.0"}
unicode-segmentation = "1.7.1" unicode-segmentation = "1.8"
lazy_static = "1.4.0" lazy_static = "1.4.0"
log = "0.4.14" log = "0.4.14"
tokio = {version = "1", features = ["sync"]} tokio = {version = "1", features = ["sync"]}

View File

@ -77,9 +77,10 @@ impl Document {
} }
} }
pub fn compose_delta(&mut self, delta: &Delta) -> Result<(), DocError> { pub fn compose_delta(&mut self, mut delta: Delta) -> Result<(), DocError> {
trim(&mut delta);
tracing::trace!("{} compose {}", &self.delta.to_json(), delta.to_json()); tracing::trace!("{} compose {}", &self.delta.to_json(), delta.to_json());
let composed_delta = self.delta.compose(delta)?; let mut composed_delta = self.delta.compose(&delta)?;
let mut undo_delta = delta.invert(&self.delta); let mut undo_delta = delta.invert(&self.delta);
let now = chrono::Utc::now().timestamp_millis() as usize; let now = chrono::Utc::now().timestamp_millis() as usize;
@ -100,6 +101,8 @@ impl Document {
} }
tracing::trace!("compose result: {}", composed_delta.to_json()); tracing::trace!("compose result: {}", composed_delta.to_json());
trim(&mut composed_delta);
self.set_delta(composed_delta); self.set_delta(composed_delta);
Ok(()) Ok(())
} }
@ -111,7 +114,7 @@ impl Document {
let text = data.to_string(); let text = data.to_string();
let delta = self.view.insert(&self.delta, &text, interval)?; let delta = self.view.insert(&self.delta, &text, interval)?;
tracing::trace!("👉 receive change: {}", delta); tracing::trace!("👉 receive change: {}", delta);
self.compose_delta(&delta)?; self.compose_delta(delta.clone())?;
Ok(delta) Ok(delta)
} }
@ -121,7 +124,7 @@ impl Document {
let delete = self.view.delete(&self.delta, interval)?; let delete = self.view.delete(&self.delta, interval)?;
if !delete.is_empty() { if !delete.is_empty() {
tracing::trace!("👉 receive change: {}", delete); tracing::trace!("👉 receive change: {}", delete);
let _ = self.compose_delta(&delete)?; let _ = self.compose_delta(delete.clone())?;
} }
Ok(delete) Ok(delete)
} }
@ -132,7 +135,7 @@ impl Document {
let format_delta = self.view.format(&self.delta, attribute.clone(), interval).unwrap(); let format_delta = self.view.format(&self.delta, attribute.clone(), interval).unwrap();
tracing::trace!("👉 receive change: {}", format_delta); tracing::trace!("👉 receive change: {}", format_delta);
self.compose_delta(&format_delta)?; self.compose_delta(format_delta.clone())?;
Ok(format_delta) Ok(format_delta)
} }
@ -143,7 +146,7 @@ impl Document {
if !text.is_empty() { if !text.is_empty() {
delta = self.view.insert(&self.delta, &text, interval)?; delta = self.view.insert(&self.delta, &text, interval)?;
tracing::trace!("👉 receive change: {}", delta); tracing::trace!("👉 receive change: {}", delta);
self.compose_delta(&delta)?; self.compose_delta(delta.clone())?;
} }
if !interval.is_empty() { if !interval.is_empty() {
@ -206,3 +209,12 @@ fn validate_interval(delta: &Delta, interval: &Interval) -> Result<(), DocError>
} }
Ok(()) Ok(())
} }
/// Removes trailing retain operation with empty attributes, if present.
pub fn trim(delta: &mut Delta) {
if let Some(last) = delta.ops.last() {
if last.is_retain() && last.is_plain() {
delta.ops.pop();
}
}
}

View File

@ -104,10 +104,6 @@ impl DocumentActor {
let data = self.document.read().await.to_json(); let data = self.document.read().await.to_json();
let _ = ret.send(Ok(data)); let _ = ret.send(Ok(data));
}, },
DocumentMsg::SaveDocument { rev_id: _, ret } => {
// let result = self.save_to_disk(rev_id).await;
let _ = ret.send(Ok(()));
},
} }
Ok(()) Ok(())
} }
@ -116,11 +112,12 @@ impl DocumentActor {
async fn composed_delta(&self, delta: Delta) -> DocResult<()> { async fn composed_delta(&self, delta: Delta) -> DocResult<()> {
// tracing::debug!("{:?} thread handle_message", thread::current(),); // tracing::debug!("{:?} thread handle_message", thread::current(),);
let mut document = self.document.write().await; let mut document = self.document.write().await;
let result = document.compose_delta(&delta);
tracing::Span::current().record( tracing::Span::current().record(
"composed_delta", "composed_delta",
&format!("doc_id:{} - {}", &self.doc_id, delta.to_json()).as_str(), &format!("doc_id:{} - {}", &self.doc_id, delta.to_json()).as_str(),
); );
let result = document.compose_delta(delta);
drop(document); drop(document);
result result

View File

@ -72,8 +72,8 @@ impl ClientEditDoc {
}; };
let _ = self.document.send(msg); let _ = self.document.send(msg);
let delta = rx.await.map_err(internal_error)??; let delta = rx.await.map_err(internal_error)??;
let rev_id = self.save_local_delta(delta).await?; let _ = self.save_local_delta(delta).await?;
save_document(self.document.clone(), rev_id.into()).await Ok(())
} }
pub async fn delete(&self, interval: Interval) -> Result<(), DocError> { pub async fn delete(&self, interval: Interval) -> Result<(), DocError> {
@ -171,8 +171,8 @@ impl ClientEditDoc {
let _ = self.document.send(msg); let _ = self.document.send(msg);
let _ = rx.await.map_err(internal_error)??; let _ = rx.await.map_err(internal_error)??;
let rev_id = self.save_local_delta(delta).await?; let _ = self.save_local_delta(delta).await?;
save_document(self.document.clone(), rev_id).await Ok(())
} }
#[cfg(feature = "flowy_test")] #[cfg(feature = "flowy_test")]
@ -249,8 +249,6 @@ impl ClientEditDoc {
RevType::Remote, RevType::Remote,
); );
let _ = self.ws.send(revision.into()); let _ = self.ws.send(revision.into());
let _ = save_document(self.document.clone(), local_rev_id.into()).await?;
Ok(()) Ok(())
} }
@ -311,13 +309,6 @@ fn spawn_rev_receiver(mut receiver: mpsc::UnboundedReceiver<Revision>, ws: Arc<d
}); });
} }
async fn save_document(document: UnboundedSender<DocumentMsg>, rev_id: RevId) -> DocResult<()> {
let (ret, rx) = oneshot::channel::<DocResult<()>>();
let _ = document.send(DocumentMsg::SaveDocument { rev_id, ret });
let result = rx.await.map_err(internal_error)?;
result
}
fn spawn_doc_edit_actor(doc_id: &str, delta: Delta, _pool: Arc<ConnectionPool>) -> UnboundedSender<DocumentMsg> { fn spawn_doc_edit_actor(doc_id: &str, delta: Delta, _pool: Arc<ConnectionPool>) -> UnboundedSender<DocumentMsg> {
let (sender, receiver) = mpsc::unbounded_channel::<DocumentMsg>(); let (sender, receiver) = mpsc::unbounded_channel::<DocumentMsg>();
let actor = DocumentActor::new(doc_id, delta, receiver); let actor = DocumentActor::new(doc_id, delta, receiver);

View File

@ -50,10 +50,6 @@ pub enum DocumentMsg {
Doc { Doc {
ret: Ret<String>, ret: Ret<String>,
}, },
SaveDocument {
rev_id: RevId,
ret: Ret<()>,
},
} }
pub struct TransformDeltas { pub struct TransformDeltas {

View File

@ -1,9 +1,7 @@
use std::cmp::min; use std::cmp::min;
use bytecount::num_chars;
use url::Url; use url::Url;
use flowy_ot::core::{plain_attributes, Attribute, Attributes, Delta, DeltaBuilder, DeltaIter}; use flowy_ot::core::{count_utf16_code_units, plain_attributes, Attribute, Attributes, Delta, DeltaBuilder, DeltaIter};
use crate::services::{doc::extensions::InsertExt, util::is_whitespace}; use crate::services::{doc::extensions::InsertExt, util::is_whitespace};
@ -70,7 +68,7 @@ impl AutoFormatter {
AutoFormatter::Url(url) => url.to_string(), AutoFormatter::Url(url) => url.to_string(),
}; };
num_chars(s.as_bytes()) count_utf16_code_units(&s)
} }
} }

View File

@ -1,4 +1,5 @@
use super::extensions::*; use super::extensions::*;
use crate::services::doc::trim;
use flowy_ot::{ use flowy_ot::{
core::{Attribute, Delta, Interval}, core::{Attribute, Delta, Interval},
errors::{ErrorBuilder, OTError, OTErrorCode}, errors::{ErrorBuilder, OTError, OTErrorCode},
@ -24,7 +25,8 @@ impl View {
pub(crate) fn insert(&self, delta: &Delta, text: &str, interval: Interval) -> Result<Delta, OTError> { pub(crate) fn insert(&self, delta: &Delta, text: &str, interval: Interval) -> Result<Delta, OTError> {
let mut new_delta = None; let mut new_delta = None;
for ext in &self.insert_exts { for ext in &self.insert_exts {
if let Some(delta) = ext.apply(delta, interval.size(), text, interval.start) { if let Some(mut delta) = ext.apply(delta, interval.size(), text, interval.start) {
trim(&mut delta);
tracing::trace!("[{}]: applied, delta: {}", ext.ext_name(), delta); tracing::trace!("[{}]: applied, delta: {}", ext.ext_name(), delta);
new_delta = Some(delta); new_delta = Some(delta);
break; break;
@ -40,7 +42,8 @@ impl View {
pub(crate) fn delete(&self, delta: &Delta, interval: Interval) -> Result<Delta, OTError> { pub(crate) fn delete(&self, delta: &Delta, interval: Interval) -> Result<Delta, OTError> {
let mut new_delta = None; let mut new_delta = None;
for ext in &self.delete_exts { for ext in &self.delete_exts {
if let Some(delta) = ext.apply(delta, interval) { if let Some(mut delta) = ext.apply(delta, interval) {
trim(&mut delta);
tracing::trace!("[{}]: applied, delta: {}", ext.ext_name(), delta); tracing::trace!("[{}]: applied, delta: {}", ext.ext_name(), delta);
new_delta = Some(delta); new_delta = Some(delta);
break; break;
@ -56,7 +59,8 @@ impl View {
pub(crate) fn format(&self, delta: &Delta, attribute: Attribute, interval: Interval) -> Result<Delta, OTError> { pub(crate) fn format(&self, delta: &Delta, attribute: Attribute, interval: Interval) -> Result<Delta, OTError> {
let mut new_delta = None; let mut new_delta = None;
for ext in &self.format_exts { for ext in &self.format_exts {
if let Some(delta) = ext.apply(delta, interval, &attribute) { if let Some(mut delta) = ext.apply(delta, interval, &attribute) {
trim(&mut delta);
tracing::trace!("[{}]: applied, delta: {}", ext.ext_name(), delta); tracing::trace!("[{}]: applied, delta: {}", ext.ext_name(), delta);
new_delta = Some(delta); new_delta = Some(delta);
break; break;

View File

@ -1,8 +1,8 @@
#![cfg_attr(rustfmt, rustfmt::skip)] #![cfg_attr(rustfmt, rustfmt::skip)]
use crate::editor::{TestBuilder, TestOp::*}; use crate::editor::{TestBuilder, TestOp::*};
use flowy_document::services::doc::{FlowyDoc, PlainDoc}; use flowy_document::services::doc::{FlowyDoc, PlainDoc};
use flowy_ot::core::{Delta, Interval, OperationTransformable, NEW_LINE, WHITESPACE}; use flowy_ot::core::{Delta, Interval, OperationTransformable, NEW_LINE, WHITESPACE, FlowyStr};
use unicode_segmentation::UnicodeSegmentation;
#[test] #[test]
fn attributes_bold_added() { fn attributes_bold_added() {
@ -719,6 +719,25 @@ fn attributes_preserve_header_format_on_merge() {
TestBuilder::new().run_script::<FlowyDoc>(ops); TestBuilder::new().run_script::<FlowyDoc>(ops);
} }
#[test]
fn attributes_format_emoji() {
let emoji_s = "👋 ";
let s: FlowyStr = emoji_s.into();
let len = s.count_utf16_code_units();
assert_eq!(3, len);
assert_eq!(2, s.graphemes(true).count());
let ops = vec![
Insert(0, emoji_s, 0),
AssertDocJson(0, r#"[{"insert":"👋 \n"}]"#),
Header(0, Interval::new(0, len), 1),
AssertDocJson(
0,
r#"[{"insert":"👋 "},{"insert":"\n","attributes":{"header":1}}]"#,
),
];
TestBuilder::new().run_script::<FlowyDoc>(ops);
}
#[test] #[test]
fn attributes_preserve_list_format_on_merge() { fn attributes_preserve_list_format_on_merge() {
let ops = vec![ let ops = vec![

View File

@ -247,7 +247,7 @@ impl TestBuilder {
}, },
TestOp::DocComposeDelta(doc_index, delta_i) => { TestOp::DocComposeDelta(doc_index, delta_i) => {
let delta = self.deltas.get(*delta_i).unwrap().as_ref().unwrap(); let delta = self.deltas.get(*delta_i).unwrap().as_ref().unwrap();
self.documents[*doc_index].compose_delta(delta).unwrap(); self.documents[*doc_index].compose_delta(delta.clone()).unwrap();
}, },
TestOp::DocComposePrime(doc_index, prime_i) => { TestOp::DocComposePrime(doc_index, prime_i) => {
let delta = self let delta = self

View File

@ -1,5 +1,4 @@
use crate::editor::{Rng, TestBuilder, TestOp::*}; use crate::editor::{Rng, TestBuilder, TestOp::*};
use bytecount::num_chars;
use flowy_document::services::doc::{FlowyDoc, PlainDoc}; use flowy_document::services::doc::{FlowyDoc, PlainDoc};
use flowy_ot::core::*; use flowy_ot::core::*;
@ -326,9 +325,9 @@ fn sequence() {
fn apply_1000() { fn apply_1000() {
for _ in 0..1000 { for _ in 0..1000 {
let mut rng = Rng::default(); let mut rng = Rng::default();
let s = rng.gen_string(50); let s: FlowyStr = rng.gen_string(50).into();
let delta = rng.gen_delta(&s); let delta = rng.gen_delta(&s);
assert_eq!(num_chars(s.as_bytes()), delta.base_len); assert_eq!(s.count_utf16_code_units(), delta.base_len);
assert_eq!(delta.apply(&s).unwrap().chars().count(), delta.target_len); assert_eq!(delta.apply(&s).unwrap().chars().count(), delta.target_len);
} }
} }
@ -441,16 +440,16 @@ fn compose() {
let mut rng = Rng::default(); let mut rng = Rng::default();
let s = rng.gen_string(20); let s = rng.gen_string(20);
let a = rng.gen_delta(&s); let a = rng.gen_delta(&s);
let after_a = a.apply(&s).unwrap(); let after_a: FlowyStr = a.apply(&s).unwrap().into();
assert_eq!(a.target_len, num_chars(after_a.as_bytes())); assert_eq!(a.target_len, after_a.count_utf16_code_units());
let b = rng.gen_delta(&after_a); let b = rng.gen_delta(&after_a);
let after_b = b.apply(&after_a).unwrap(); let after_b: FlowyStr = b.apply(&after_a).unwrap().into();
assert_eq!(b.target_len, num_chars(after_b.as_bytes())); assert_eq!(b.target_len, after_b.count_utf16_code_units());
let ab = a.compose(&b).unwrap(); let ab = a.compose(&b).unwrap();
assert_eq!(ab.target_len, b.target_len); assert_eq!(ab.target_len, b.target_len);
let after_ab = ab.apply(&s).unwrap(); let after_ab: FlowyStr = ab.apply(&s).unwrap().into();
assert_eq!(after_b, after_ab); assert_eq!(after_b, after_ab);
} }
} }

View File

@ -19,3 +19,4 @@ bytes = "1.0"

View File

@ -1,8 +1,7 @@
use crate::{ use crate::{
core::{attributes::*, operation::*, DeltaIter, Interval, OperationTransformable, MAX_IV_LEN}, core::{attributes::*, operation::*, DeltaIter, FlowyStr, Interval, OperationTransformable, MAX_IV_LEN},
errors::{ErrorBuilder, OTError, OTErrorCode}, errors::{ErrorBuilder, OTError, OTErrorCode},
}; };
use bytecount::num_chars;
use bytes::Bytes; use bytes::Bytes;
use std::{ use std::{
cmp::{min, Ordering}, cmp::{min, Ordering},
@ -127,26 +126,27 @@ impl Delta {
} }
pub fn insert(&mut self, s: &str, attributes: Attributes) { pub fn insert(&mut self, s: &str, attributes: Attributes) {
let s: FlowyStr = s.into();
if s.is_empty() { if s.is_empty() {
return; return;
} }
self.target_len += num_chars(s.as_bytes()); self.target_len += s.count_utf16_code_units();
let new_last = match self.ops.as_mut_slice() { let new_last = match self.ops.as_mut_slice() {
[.., Operation::Insert(insert)] => { [.., Operation::Insert(insert)] => {
// //
insert.merge_or_new_op(s, attributes) insert.merge_or_new_op(&s, attributes)
}, },
[.., Operation::Insert(pre_insert), Operation::Delete(_)] => { [.., Operation::Insert(pre_insert), Operation::Delete(_)] => {
// //
pre_insert.merge_or_new_op(s, attributes) pre_insert.merge_or_new_op(&s, attributes)
}, },
[.., op_last @ Operation::Delete(_)] => { [.., op_last @ Operation::Delete(_)] => {
let new_last = op_last.clone(); let new_last = op_last.clone();
*op_last = OpBuilder::insert(s).attributes(attributes).build(); *op_last = OpBuilder::insert(&s).attributes(attributes).build();
Some(new_last) Some(new_last)
}, },
_ => Some(OpBuilder::insert(s).attributes(attributes).build()), _ => Some(OpBuilder::insert(&s).attributes(attributes).build()),
}; };
match new_last { match new_last {
@ -173,7 +173,8 @@ impl Delta {
/// Applies an operation to a string, returning a new string. /// Applies an operation to a string, returning a new string.
pub fn apply(&self, s: &str) -> Result<String, OTError> { pub fn apply(&self, s: &str) -> Result<String, OTError> {
if num_chars(s.as_bytes()) != self.base_len { let s: FlowyStr = s.into();
if s.count_utf16_code_units() != self.base_len {
return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength).build()); return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength).build());
} }
let mut new_s = String::new(); let mut new_s = String::new();
@ -214,7 +215,7 @@ impl Delta {
} }
}, },
Operation::Insert(insert) => { Operation::Insert(insert) => {
inverted.delete(insert.num_chars()); inverted.delete(insert.count_of_code_units());
}, },
Operation::Delete(delete) => { Operation::Delete(delete) => {
inverted.insert(&chars.take(*delete as usize).collect::<String>(), op.get_attributes()); inverted.insert(&chars.take(*delete as usize).collect::<String>(), op.get_attributes());
@ -325,12 +326,12 @@ impl OperationTransformable for Delta {
(Some(Operation::Insert(insert)), _) => { (Some(Operation::Insert(insert)), _) => {
// let composed_attrs = transform_attributes(&next_op1, &next_op2, true); // let composed_attrs = transform_attributes(&next_op1, &next_op2, true);
a_prime.insert(&insert.s, insert.attributes.clone()); a_prime.insert(&insert.s, insert.attributes.clone());
b_prime.retain(insert.num_chars(), insert.attributes.clone()); b_prime.retain(insert.count_of_code_units(), insert.attributes.clone());
next_op1 = ops1.next(); next_op1 = ops1.next();
}, },
(_, Some(Operation::Insert(o_insert))) => { (_, Some(Operation::Insert(o_insert))) => {
let composed_attrs = transform_op_attribute(&next_op1, &next_op2); let composed_attrs = transform_op_attribute(&next_op1, &next_op2);
a_prime.retain(o_insert.num_chars(), composed_attrs.clone()); a_prime.retain(o_insert.count_of_code_units(), composed_attrs.clone());
b_prime.insert(&o_insert.s, composed_attrs); b_prime.insert(&o_insert.s, composed_attrs);
next_op2 = ops2.next(); next_op2 = ops2.next();
}, },
@ -417,7 +418,6 @@ impl OperationTransformable for Delta {
}, },
} }
} }
Ok((a_prime, b_prime)) Ok((a_prime, b_prime))
} }

View File

@ -162,7 +162,7 @@ impl<'a> Iterator for AttributesIter<'a> {
Operation::Insert(insert) => { Operation::Insert(insert) => {
tracing::trace!("extend insert attributes with {} ", &insert.attributes); tracing::trace!("extend insert attributes with {} ", &insert.attributes);
attributes.extend(insert.attributes.clone()); attributes.extend(insert.attributes.clone());
length = insert.num_chars(); length = insert.count_of_code_units();
}, },
} }

View File

@ -0,0 +1,264 @@
use serde::{de, de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
use std::{fmt, fmt::Formatter, slice};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FlowyStr(pub String);
impl FlowyStr {
pub fn count_utf16_code_units(&self) -> usize { count_utf16_code_units(&self.0) }
pub fn iter(&self) -> FlowyUtf16Iterator { FlowyUtf16Iterator::new(self, 0) }
pub fn sub_str(&self, interval: Interval) -> String {
match self.with_interval(interval) {
None => "".to_owned(),
Some(s) => s.0,
}
}
pub fn with_interval(&self, interval: Interval) -> Option<FlowyStr> {
let mut iter = CodePointIterator::new(self);
let mut buf = vec![];
while let Some((byte, _len)) = iter.next() {
if interval.start < iter.code_point_offset && interval.end >= iter.code_point_offset {
buf.extend_from_slice(byte);
}
}
if buf.is_empty() {
return None;
}
match str::from_utf8(&buf) {
Ok(item) => Some(item.into()),
Err(_e) => None,
}
}
}
pub struct CodePointIterator<'a> {
s: &'a FlowyStr,
bytes_offset: usize,
code_point_offset: usize,
iter_index: usize,
iter: slice::Iter<'a, u8>,
}
impl<'a> CodePointIterator<'a> {
pub fn new(s: &'a FlowyStr) -> Self {
CodePointIterator {
s,
bytes_offset: 0,
code_point_offset: 0,
iter_index: 0,
iter: s.as_bytes().iter(),
}
}
}
impl<'a> Iterator for CodePointIterator<'a> {
type Item = (&'a [u8], usize);
fn next(&mut self) -> Option<Self::Item> {
let start = self.bytes_offset;
let _end = start;
while let Some(&b) = self.iter.next() {
self.iter_index += 1;
let mut code_point_count = 0;
if self.bytes_offset > self.iter_index {
continue;
}
if self.bytes_offset == self.iter_index {
break;
}
if (b as i8) >= -0x40 {
code_point_count += 1
}
if b >= 0xf0 {
code_point_count += 1
}
self.bytes_offset += len_utf8_from_first_byte(b);
self.code_point_offset += code_point_count;
if code_point_count == 1 {
break;
}
}
if start == self.bytes_offset {
return None;
}
let byte = &self.s.as_bytes()[start..self.bytes_offset];
Some((byte, self.bytes_offset - start))
}
}
impl std::ops::Deref for FlowyStr {
type Target = String;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl std::ops::DerefMut for FlowyStr {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
}
impl std::convert::From<String> for FlowyStr {
fn from(s: String) -> Self { FlowyStr(s) }
}
impl std::convert::From<&str> for FlowyStr {
fn from(s: &str) -> Self { s.to_owned().into() }
}
impl std::fmt::Display for FlowyStr {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str(&self.0) }
}
impl std::ops::Add<&str> for FlowyStr {
type Output = FlowyStr;
fn add(self, rhs: &str) -> FlowyStr {
let new_value = self.0 + rhs;
new_value.into()
}
}
impl std::ops::AddAssign<&str> for FlowyStr {
fn add_assign(&mut self, rhs: &str) { self.0 += rhs; }
}
impl Serialize for FlowyStr {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.0)
}
}
impl<'de> Deserialize<'de> for FlowyStr {
fn deserialize<D>(deserializer: D) -> Result<FlowyStr, D::Error>
where
D: Deserializer<'de>,
{
struct FlowyStrVisitor;
impl<'de> Visitor<'de> for FlowyStrVisitor {
type Value = FlowyStr;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a str") }
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(s.into())
}
}
deserializer.deserialize_str(FlowyStrVisitor)
}
}
pub struct FlowyUtf16Iterator<'a> {
s: &'a FlowyStr,
offset: usize,
}
impl<'a> FlowyUtf16Iterator<'a> {
pub fn new(s: &'a FlowyStr, offset: usize) -> Self { FlowyUtf16Iterator { s, offset } }
}
use crate::core::Interval;
use std::str;
impl<'a> Iterator for FlowyUtf16Iterator<'a> {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
if self.offset == self.s.len() {
None
} else {
let byte = self.s.as_bytes()[self.offset];
let end = len_utf8_from_first_byte(byte);
let buf = &self.s.as_bytes()[self.offset..self.offset + end];
self.offset += end;
match str::from_utf8(buf) {
Ok(item) => Some(item.to_string()),
Err(_e) => None,
}
}
}
}
pub fn count_utf16_code_units(s: &str) -> usize {
// bytecount::num_chars(s.as_bytes())
let mut utf16_count = 0;
for &b in s.as_bytes() {
if (b as i8) >= -0x40 {
utf16_count += 1;
}
if b >= 0xf0 {
utf16_count += 1;
}
}
utf16_count
}
/// Given the initial byte of a UTF-8 codepoint, returns the number of
/// bytes required to represent the codepoint.
/// RFC reference : https://tools.ietf.org/html/rfc3629#section-4
pub fn len_utf8_from_first_byte(b: u8) -> usize {
match b {
b if b < 0x80 => 1,
b if b < 0xe0 => 2,
b if b < 0xf0 => 3,
_ => 4,
}
}
#[cfg(test)]
mod tests {
use crate::core::{FlowyStr, Interval};
#[test]
fn flowy_str_utf16_test() {
let s: FlowyStr = "👋😁👋😁".into();
let mut iter = s.iter();
assert_eq!(iter.next().unwrap(), "👋".to_string());
assert_eq!(iter.next().unwrap(), "😁".to_string());
assert_eq!(iter.next().unwrap(), "👋".to_string());
assert_eq!(iter.next().unwrap(), "😁".to_string());
assert_eq!(iter.next(), None);
}
#[test]
fn flowy_str_utf16_iter_test() {
let s: FlowyStr = "👋👋😁😁👋👋".into();
let iter = s.iter();
let result = iter.skip(2).take(2).collect::<String>();
assert_eq!(result, "😁😁".to_string());
}
#[test]
fn flowy_str_code_point_test() {
let s: FlowyStr = "👋 \n👋".into();
let output = s.with_interval(Interval::new(0, 2)).unwrap().0;
assert_eq!(output, "👋");
let output = s.with_interval(Interval::new(2, 3)).unwrap().0;
assert_eq!(output, " ");
let output = s.with_interval(Interval::new(3, 4)).unwrap().0;
assert_eq!(output, "\n");
let output = s.with_interval(Interval::new(4, 6)).unwrap().0;
assert_eq!(output, "👋");
}
}

View File

@ -1,11 +1,13 @@
mod attributes; mod attributes;
mod delta; mod delta;
mod flowy_str;
mod interval; mod interval;
mod operation; mod operation;
use crate::errors::OTError; use crate::errors::OTError;
pub use attributes::*; pub use attributes::*;
pub use delta::*; pub use delta::*;
pub use flowy_str::*;
pub use interval::*; pub use interval::*;
pub use operation::*; pub use operation::*;

View File

@ -1,11 +1,9 @@
use crate::core::{Attribute, Attributes, Interval, OpBuilder}; use crate::core::{Attribute, Attributes, FlowyStr, Interval, OpBuilder};
use bytecount::num_chars;
use serde::__private::Formatter; use serde::__private::Formatter;
use std::{ use std::{
cmp::min, cmp::min,
fmt, fmt,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
str::Chars,
}; };
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
@ -47,11 +45,12 @@ impl Operation {
} }
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
match self { let len = match self {
Operation::Delete(n) => *n, Operation::Delete(n) => *n,
Operation::Retain(r) => r.n, Operation::Retain(r) => r.n,
Operation::Insert(i) => i.num_chars(), Operation::Insert(i) => i.count_of_code_units(),
} };
len
} }
pub fn is_empty(&self) -> bool { self.len() == 0 } pub fn is_empty(&self) -> bool { self.len() == 0 }
@ -78,7 +77,7 @@ impl Operation {
.build(), .build(),
); );
right = Some( right = Some(
OpBuilder::insert(&insert.s[index..insert.num_chars()]) OpBuilder::insert(&insert.s[index..insert.count_of_code_units()])
.attributes(attributes) .attributes(attributes)
.build(), .build(),
); );
@ -95,13 +94,18 @@ impl Operation {
.attributes(retain.attributes.clone()) .attributes(retain.attributes.clone())
.build(), .build(),
Operation::Insert(insert) => { Operation::Insert(insert) => {
if interval.start > insert.num_chars() { if interval.start > insert.count_of_code_units() {
OpBuilder::insert("").build() OpBuilder::insert("").build()
} else { } else {
let chars = insert.chars().skip(interval.start); // let s = &insert
let s = &chars.take(min(interval.size(), insert.num_chars())).collect::<String>(); // .s
// .chars()
// .skip(interval.start)
// .take(min(interval.size(), insert.count_of_code_units()))
// .collect::<String>();
OpBuilder::insert(s).attributes(insert.attributes.clone()).build() let s = insert.s.sub_str(interval);
OpBuilder::insert(&s).attributes(insert.attributes.clone()).build()
} }
}, },
}; };
@ -132,6 +136,14 @@ impl Operation {
} }
false false
} }
pub fn is_plain(&self) -> bool {
match self {
Operation::Delete(_) => true,
Operation::Retain(retain) => retain.is_plain(),
Operation::Insert(insert) => insert.is_plain(),
}
}
} }
impl fmt::Display for Operation { impl fmt::Display for Operation {
@ -212,7 +224,7 @@ impl DerefMut for Retain {
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Insert { pub struct Insert {
#[serde(rename(serialize = "insert", deserialize = "insert"))] #[serde(rename(serialize = "insert", deserialize = "insert"))]
pub s: String, pub s: FlowyStr,
#[serde(skip_serializing_if = "is_empty")] #[serde(skip_serializing_if = "is_empty")]
pub attributes: Attributes, pub attributes: Attributes,
@ -224,7 +236,7 @@ impl fmt::Display for Insert {
if s.ends_with("\n") { if s.ends_with("\n") {
s.pop(); s.pop();
if s.is_empty() { if s.is_empty() {
s = "new_line".to_owned(); s = "new_line".into();
} }
} }
@ -237,11 +249,7 @@ impl fmt::Display for Insert {
} }
impl Insert { impl Insert {
pub fn as_bytes(&self) -> &[u8] { self.s.as_bytes() } pub fn count_of_code_units(&self) -> usize { self.s.count_utf16_code_units() }
pub fn chars(&self) -> Chars<'_> { self.s.chars() }
pub fn num_chars(&self) -> usize { num_chars(self.s.as_bytes()) as _ }
pub fn merge_or_new_op(&mut self, s: &str, attributes: Attributes) -> Option<Operation> { pub fn merge_or_new_op(&mut self, s: &str, attributes: Attributes) -> Option<Operation> {
if self.attributes == attributes { if self.attributes == attributes {
@ -251,12 +259,14 @@ impl Insert {
Some(OpBuilder::insert(s).attributes(attributes).build()) Some(OpBuilder::insert(s).attributes(attributes).build())
} }
} }
pub fn is_plain(&self) -> bool { self.attributes.is_empty() }
} }
impl std::convert::From<String> for Insert { impl std::convert::From<String> for Insert {
fn from(s: String) -> Self { fn from(s: String) -> Self {
Insert { Insert {
s, s: s.into(),
attributes: Attributes::default(), attributes: Attributes::default(),
} }
} }
@ -265,4 +275,14 @@ impl std::convert::From<String> for Insert {
impl std::convert::From<&str> for Insert { impl std::convert::From<&str> for Insert {
fn from(s: &str) -> Self { Insert::from(s.to_owned()) } fn from(s: &str) -> Self { Insert::from(s.to_owned()) }
} }
impl std::convert::From<FlowyStr> for Insert {
fn from(s: FlowyStr) -> Self {
Insert {
s,
attributes: Attributes::default(),
}
}
}
fn is_empty(attributes: &Attributes) -> bool { attributes.is_empty() } fn is_empty(attributes: &Attributes) -> bool { attributes.is_empty() }

View File

@ -9,7 +9,7 @@ edition = "2018"
flowy-derive = { path = "../flowy-derive" } flowy-derive = { path = "../flowy-derive" }
protobuf = {version = "2.18.0"} protobuf = {version = "2.18.0"}
bytes = "1.0" bytes = "1.0"
unicode-segmentation = "1.7.1" unicode-segmentation = "1.8"
derive_more = {version = "0.99", features = ["display"]} derive_more = {version = "0.99", features = ["display"]}
validator = "0.12.0" validator = "0.12.0"
log = "0.4.14" log = "0.4.14"

View File

@ -9,7 +9,7 @@ edition = "2018"
flowy-derive = { path = "../flowy-derive" } flowy-derive = { path = "../flowy-derive" }
protobuf = {version = "2.18.0"} protobuf = {version = "2.18.0"}
bytes = "1.0" bytes = "1.0"
unicode-segmentation = "1.7.1" unicode-segmentation = "1.8"
strum = "0.21" strum = "0.21"
strum_macros = "0.21" strum_macros = "0.21"
derive_more = {version = "0.99", features = ["display"]} derive_more = {version = "0.99", features = ["display"]}