add rich_text crate

This commit is contained in:
appflowy 2021-12-07 19:59:08 +08:00
parent 2f413a8e67
commit 8a9a23ddbe
44 changed files with 296 additions and 280 deletions

View File

@ -13,7 +13,7 @@ use flowy_document_infra::{
entities::ws::{WsDataType, WsDocumentData}, entities::ws::{WsDataType, WsDocumentData},
protobuf::{Doc, RevId, RevType, Revision, RevisionRange, UpdateDocParams}, protobuf::{Doc, RevId, RevType, Revision, RevisionRange, UpdateDocParams},
}; };
use lib_ot::core::{OperationTransformable, RichTextDelta}; use lib_ot::{core::OperationTransformable, rich_text::RichTextDelta};
use parking_lot::RwLock; use parking_lot::RwLock;
use protobuf::Message; use protobuf::Message;
use sqlx::PgPool; use sqlx::PgPool;

View File

@ -1,6 +1,6 @@
use crate::document::helper::{DocScript, DocumentTest}; use crate::document::helper::{DocScript, DocumentTest};
use flowy_document_infra::core::{Document, FlowyDoc}; use flowy_document_infra::core::{Document, FlowyDoc};
use lib_ot::core::{Interval, RichTextAttribute}; use lib_ot::{core::Interval, rich_text::RichTextAttribute};
#[rustfmt::skip] #[rustfmt::skip]
// ┌─────────┐ ┌─────────┐ // ┌─────────┐ ┌─────────┐

View File

@ -12,8 +12,9 @@ use tokio::time::{sleep, Duration};
// use crate::helper::*; // use crate::helper::*;
use crate::util::helper::{spawn_server, TestServer}; use crate::util::helper::{spawn_server, TestServer};
use flowy_document_infra::{entities::doc::DocIdentifier, protobuf::UpdateDocParams}; use flowy_document_infra::{entities::doc::DocIdentifier, protobuf::UpdateDocParams};
use lib_ot::core::{RichTextAttribute, RichTextDelta, Interval}; use lib_ot::rich_text::{RichTextAttribute, RichTextDelta};
use parking_lot::RwLock; use parking_lot::RwLock;
use lib_ot::core::Interval;
pub struct DocumentTest { pub struct DocumentTest {
server: TestServer, server: TestServer,

View File

@ -17,7 +17,10 @@ use flowy_document_infra::{
errors::DocumentResult, errors::DocumentResult,
}; };
use lib_infra::retry::{ExponentialBackoff, Retry}; use lib_infra::retry::{ExponentialBackoff, Retry};
use lib_ot::core::{Interval, RichTextAttribute, RichTextDelta}; use lib_ot::{
core::Interval,
rich_text::{RichTextAttribute, RichTextDelta},
};
use lib_ws::WsConnectState; use lib_ws::WsConnectState;
use std::{convert::TryFrom, sync::Arc}; use std::{convert::TryFrom, sync::Arc};
use tokio::sync::{mpsc, mpsc::UnboundedSender, oneshot}; use tokio::sync::{mpsc, mpsc::UnboundedSender, oneshot};

View File

@ -6,7 +6,10 @@ use flowy_document_infra::{
errors::DocumentError, errors::DocumentError,
}; };
use futures::stream::StreamExt; use futures::stream::StreamExt;
use lib_ot::core::{Interval, OperationTransformable, RichTextAttribute, RichTextDelta}; use lib_ot::{
core::{Interval, OperationTransformable},
rich_text::{RichTextAttribute, RichTextDelta},
};
use std::{convert::TryFrom, sync::Arc}; use std::{convert::TryFrom, sync::Arc};
use tokio::sync::{mpsc, oneshot, RwLock}; use tokio::sync::{mpsc, oneshot, RwLock};

View File

@ -8,7 +8,7 @@ use flowy_document_infra::{
util::RevIdCounter, util::RevIdCounter,
}; };
use lib_infra::future::ResultFuture; use lib_infra::future::ResultFuture;
use lib_ot::core::{OperationTransformable, RichTextDelta}; use lib_ot::{core::OperationTransformable, rich_text::RichTextDelta};
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::mpsc; use tokio::sync::mpsc;

View File

@ -9,7 +9,10 @@ use flowy_database::{ConnectionPool, SqliteConnection};
use flowy_document_infra::entities::doc::{revision_from_doc, Doc, RevId, RevType, Revision, RevisionRange}; use flowy_document_infra::entities::doc::{revision_from_doc, Doc, RevId, RevType, Revision, RevisionRange};
use futures::stream::StreamExt; use futures::stream::StreamExt;
use lib_infra::future::ResultFuture; use lib_infra::future::ResultFuture;
use lib_ot::core::{Operation, OperationTransformable, RichTextDelta}; use lib_ot::{
core::{Operation, OperationTransformable},
rich_text::RichTextDelta,
};
use std::{collections::VecDeque, sync::Arc, time::Duration}; use std::{collections::VecDeque, sync::Arc, time::Duration};
use tokio::{ use tokio::{
sync::{broadcast, mpsc, RwLock}, sync::{broadcast, mpsc, RwLock},

View File

@ -1,8 +1,9 @@
#![cfg_attr(rustfmt, rustfmt::skip)] #![cfg_attr(rustfmt, rustfmt::skip)]
use crate::editor::{TestBuilder, TestOp::*}; use crate::editor::{TestBuilder, TestOp::*};
use flowy_document_infra::core::{FlowyDoc, PlainDoc}; use flowy_document_infra::core::{FlowyDoc, PlainDoc};
use lib_ot::core::{Interval, OperationTransformable, NEW_LINE, WHITESPACE, FlowyStr, RichTextDelta}; use lib_ot::core::{Interval, OperationTransformable, NEW_LINE, WHITESPACE, FlowyStr};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use lib_ot::rich_text::RichTextDelta;
#[test] #[test]
fn attributes_bold_added() { fn attributes_bold_added() {

View File

@ -6,7 +6,10 @@ mod undo_redo_test;
use derive_more::Display; use derive_more::Display;
use flowy_document_infra::core::{CustomDocument, Document}; use flowy_document_infra::core::{CustomDocument, Document};
use lib_ot::core::*; use lib_ot::{
core::*,
rich_text::{RichTextAttribute, RichTextAttributes, RichTextDelta},
};
use rand::{prelude::*, Rng as WrappedRng}; use rand::{prelude::*, Rng as WrappedRng};
use std::{sync::Once, time::Duration}; use std::{sync::Once, time::Duration};

View File

@ -1,7 +1,10 @@
#![allow(clippy::all)] #![allow(clippy::all)]
use crate::editor::{Rng, TestBuilder, TestOp::*}; use crate::editor::{Rng, TestBuilder, TestOp::*};
use flowy_document_infra::core::{FlowyDoc, PlainDoc}; use flowy_document_infra::core::{FlowyDoc, PlainDoc};
use lib_ot::core::*; use lib_ot::{
core::*,
rich_text::{AttributeBuilder, RichTextAttribute, RichTextAttributes, RichTextDelta},
};
#[test] #[test]
fn attributes_insert_text() { fn attributes_insert_text() {

View File

@ -1,5 +1,8 @@
use flowy_document_infra::core::{Document, PlainDoc}; use flowy_document_infra::core::{Document, PlainDoc};
use lib_ot::core::*; use lib_ot::{
core::*,
rich_text::{AttributeBuilder, RichTextAttribute, RichTextAttributeValue, RichTextDelta},
};
#[test] #[test]
fn operation_insert_serialize_test() { fn operation_insert_serialize_test() {

View File

@ -6,7 +6,10 @@ use crate::{
errors::DocumentError, errors::DocumentError,
user_default::doc_initial_delta, user_default::doc_initial_delta,
}; };
use lib_ot::core::*; use lib_ot::{
core::*,
rich_text::{RichTextAttribute, RichTextDelta},
};
use tokio::sync::mpsc; use tokio::sync::mpsc;
pub trait CustomDocument { pub trait CustomDocument {

View File

@ -1,5 +1,8 @@
use crate::core::extensions::DeleteExt; use crate::core::extensions::DeleteExt;
use lib_ot::core::{DeltaBuilder, Interval, RichTextDelta}; use lib_ot::{
core::{DeltaBuilder, Interval},
rich_text::RichTextDelta,
};
pub struct DefaultDelete {} pub struct DefaultDelete {}
impl DeleteExt for DefaultDelete { impl DeleteExt for DefaultDelete {

View File

@ -1,13 +1,7 @@
use crate::{core::extensions::DeleteExt, util::is_newline}; use crate::{core::extensions::DeleteExt, util::is_newline};
use lib_ot::core::{ use lib_ot::{
plain_attributes, core::{Attributes, CharMetric, DeltaBuilder, DeltaIter, Interval, NEW_LINE},
Attributes, rich_text::{plain_attributes, RichTextDelta},
CharMetric,
DeltaBuilder,
DeltaIter,
Interval,
RichTextDelta,
NEW_LINE,
}; };
pub struct PreserveLineFormatOnMerge {} pub struct PreserveLineFormatOnMerge {}

View File

@ -1,5 +1,8 @@
use crate::util::find_newline; use crate::util::find_newline;
use lib_ot::core::{plain_attributes, AttributeScope, RichTextAttribute, RichTextDelta, RichTextOperation}; use lib_ot::{
core::RichTextOperation,
rich_text::{plain_attributes, AttributeScope, RichTextAttribute, RichTextDelta},
};
pub(crate) fn line_break( pub(crate) fn line_break(
op: &RichTextOperation, op: &RichTextOperation,

View File

@ -2,14 +2,9 @@ use crate::{
core::extensions::{format::helper::line_break, FormatExt}, core::extensions::{format::helper::line_break, FormatExt},
util::find_newline, util::find_newline,
}; };
use lib_ot::core::{ use lib_ot::{
plain_attributes, core::{DeltaBuilder, DeltaIter, Interval},
AttributeScope, rich_text::{plain_attributes, AttributeScope, RichTextAttribute, RichTextDelta},
DeltaBuilder,
DeltaIter,
Interval,
RichTextAttribute,
RichTextDelta,
}; };
pub struct ResolveBlockFormat {} pub struct ResolveBlockFormat {}

View File

@ -2,7 +2,10 @@ use crate::{
core::extensions::{format::helper::line_break, FormatExt}, core::extensions::{format::helper::line_break, FormatExt},
util::find_newline, util::find_newline,
}; };
use lib_ot::core::{AttributeScope, DeltaBuilder, DeltaIter, Interval, RichTextAttribute, RichTextDelta}; use lib_ot::{
core::{DeltaBuilder, DeltaIter, Interval},
rich_text::{AttributeScope, RichTextAttribute, RichTextDelta},
};
pub struct ResolveInlineFormat {} pub struct ResolveInlineFormat {}
impl FormatExt for ResolveInlineFormat { impl FormatExt for ResolveInlineFormat {

View File

@ -1,11 +1,7 @@
use crate::{core::extensions::InsertExt, util::is_newline}; use crate::{core::extensions::InsertExt, util::is_newline};
use lib_ot::core::{ use lib_ot::{
attributes_except_header, core::{is_empty_line_at_index, DeltaBuilder, DeltaIter},
is_empty_line_at_index, rich_text::{attributes_except_header, RichTextAttributeKey, RichTextDelta},
DeltaBuilder,
DeltaIter,
RichTextAttributeKey,
RichTextDelta,
}; };
pub struct AutoExitBlock {} pub struct AutoExitBlock {}

View File

@ -1,12 +1,7 @@
use crate::{core::extensions::InsertExt, util::is_whitespace}; use crate::{core::extensions::InsertExt, util::is_whitespace};
use lib_ot::core::{ use lib_ot::{
count_utf16_code_units, core::{count_utf16_code_units, DeltaBuilder, DeltaIter},
plain_attributes, rich_text::{plain_attributes, RichTextAttribute, RichTextAttributes, RichTextDelta},
DeltaBuilder,
DeltaIter,
RichTextAttribute,
RichTextAttributes,
RichTextDelta,
}; };
use std::cmp::min; use std::cmp::min;
use url::Url; use url::Url;

View File

@ -1,12 +1,7 @@
use crate::core::extensions::InsertExt; use crate::core::extensions::InsertExt;
use lib_ot::core::{ use lib_ot::{
Attributes, core::{Attributes, DeltaBuilder, DeltaIter, NEW_LINE},
DeltaBuilder, rich_text::{RichTextAttributeKey, RichTextAttributes, RichTextDelta},
DeltaIter,
RichTextAttributeKey,
RichTextAttributes,
RichTextDelta,
NEW_LINE,
}; };
pub struct DefaultInsertAttribute {} pub struct DefaultInsertAttribute {}

View File

@ -2,7 +2,7 @@ use crate::core::extensions::InsertExt;
pub use auto_exit_block::*; pub use auto_exit_block::*;
pub use auto_format::*; pub use auto_format::*;
pub use default_insert::*; pub use default_insert::*;
use lib_ot::core::RichTextDelta; use lib_ot::rich_text::RichTextDelta;
pub use preserve_block_format::*; pub use preserve_block_format::*;
pub use preserve_inline_format::*; pub use preserve_inline_format::*;
pub use reset_format_on_new_line::*; pub use reset_format_on_new_line::*;

View File

@ -1,14 +1,14 @@
use crate::{core::extensions::InsertExt, util::is_newline}; use crate::{core::extensions::InsertExt, util::is_newline};
use lib_ot::core::{ use lib_ot::{
attributes_except_header, core::{DeltaBuilder, DeltaIter, NEW_LINE},
plain_attributes, rich_text::{
DeltaBuilder, attributes_except_header,
DeltaIter, plain_attributes,
RichTextAttribute, RichTextAttribute,
RichTextAttributeKey, RichTextAttributeKey,
RichTextAttributes, RichTextAttributes,
RichTextDelta, RichTextDelta,
NEW_LINE, },
}; };
pub struct PreserveBlockFormatOnInsert {} pub struct PreserveBlockFormatOnInsert {}

View File

@ -2,14 +2,9 @@ use crate::{
core::extensions::InsertExt, core::extensions::InsertExt,
util::{contain_newline, is_newline}, util::{contain_newline, is_newline},
}; };
use lib_ot::core::{ use lib_ot::{
plain_attributes, core::{DeltaBuilder, DeltaIter, OpNewline, NEW_LINE},
DeltaBuilder, rich_text::{plain_attributes, RichTextAttributeKey, RichTextDelta},
DeltaIter,
OpNewline,
RichTextAttributeKey,
RichTextDelta,
NEW_LINE,
}; };
pub struct PreserveInlineFormat {} pub struct PreserveInlineFormat {}

View File

@ -1,12 +1,7 @@
use crate::{core::extensions::InsertExt, util::is_newline}; use crate::{core::extensions::InsertExt, util::is_newline};
use lib_ot::core::{ use lib_ot::{
CharMetric, core::{CharMetric, DeltaBuilder, DeltaIter, NEW_LINE},
DeltaBuilder, rich_text::{RichTextAttributeKey, RichTextAttributes, RichTextDelta},
DeltaIter,
RichTextAttributeKey,
RichTextAttributes,
RichTextDelta,
NEW_LINE,
}; };
pub struct ResetLineFormatOnNewLine {} pub struct ResetLineFormatOnNewLine {}

View File

@ -2,7 +2,10 @@ pub use delete::*;
pub use format::*; pub use format::*;
pub use insert::*; pub use insert::*;
use lib_ot::core::{Interval, RichTextAttribute, RichTextDelta}; use lib_ot::{
core::Interval,
rich_text::{RichTextAttribute, RichTextDelta},
};
mod delete; mod delete;
mod format; mod format;

View File

@ -1,4 +1,4 @@
use lib_ot::core::RichTextDelta; use lib_ot::rich_text::RichTextDelta;
const MAX_UNDOS: usize = 20; const MAX_UNDOS: usize = 20;

View File

@ -1,7 +1,8 @@
use crate::core::extensions::*; use crate::core::extensions::*;
use lib_ot::{ use lib_ot::{
core::{trim, Interval, RichTextAttribute, RichTextDelta}, core::{trim, Interval},
errors::{ErrorBuilder, OTError, OTErrorCode}, errors::{ErrorBuilder, OTError, OTErrorCode},
rich_text::{RichTextAttribute, RichTextDelta},
}; };
pub const RECORD_THRESHOLD: usize = 400; // in milliseconds pub const RECORD_THRESHOLD: usize = 400; // in milliseconds

View File

@ -1,5 +1,5 @@
use flowy_derive::ProtoBuf; use flowy_derive::ProtoBuf;
use lib_ot::{core::RichTextDelta, errors::OTError}; use lib_ot::{errors::OTError, rich_text::RichTextDelta};
#[derive(ProtoBuf, Default, Debug, Clone)] #[derive(ProtoBuf, Default, Debug, Clone)]
pub struct CreateDocParams { pub struct CreateDocParams {

View File

@ -1,6 +1,6 @@
use crate::{entities::doc::Doc, util::md5}; use crate::{entities::doc::Doc, util::md5};
use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use lib_ot::core::RichTextDelta; use lib_ot::rich_text::RichTextDelta;
use std::{fmt::Formatter, ops::RangeInclusive}; use std::{fmt::Formatter, ops::RangeInclusive};
#[derive(Debug, ProtoBuf_Enum, Clone, Eq, PartialEq)] #[derive(Debug, ProtoBuf_Enum, Clone, Eq, PartialEq)]

View File

@ -1,4 +1,4 @@
use lib_ot::core::{DeltaBuilder, RichTextDelta}; use lib_ot::{core::DeltaBuilder, rich_text::RichTextDelta};
#[inline] #[inline]
pub fn doc_initial_delta() -> RichTextDelta { DeltaBuilder::new().insert("\n").build() } pub fn doc_initial_delta() -> RichTextDelta { DeltaBuilder::new().insert("\n").build() }

View File

@ -1,170 +0,0 @@
use crate::{
core::{
Attributes,
OperationTransformable,
RichTextAttribute,
RichTextAttributeKey,
RichTextAttributeValue,
RichTextOperation,
},
errors::OTError,
};
use std::{collections::HashMap, fmt};
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct RichTextAttributes {
pub(crate) inner: HashMap<RichTextAttributeKey, RichTextAttributeValue>,
}
impl std::default::Default for RichTextAttributes {
fn default() -> Self {
Self {
inner: HashMap::with_capacity(0),
}
}
}
impl fmt::Display for RichTextAttributes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_fmt(format_args!("{:?}", self.inner)) }
}
pub fn plain_attributes() -> RichTextAttributes { RichTextAttributes::default() }
impl RichTextAttributes {
pub fn new() -> Self { RichTextAttributes { inner: HashMap::new() } }
pub fn is_empty(&self) -> bool { self.inner.is_empty() }
pub fn add(&mut self, attribute: RichTextAttribute) {
let RichTextAttribute { key, value, scope: _ } = attribute;
self.inner.insert(key, value);
}
pub fn add_kv(&mut self, key: RichTextAttributeKey, value: RichTextAttributeValue) {
self.inner.insert(key, value);
}
pub fn delete(&mut self, key: &RichTextAttributeKey) {
self.inner.insert(key.clone(), RichTextAttributeValue(None));
}
pub fn mark_all_as_removed_except(&mut self, attribute: Option<RichTextAttributeKey>) {
match attribute {
None => {
self.inner.iter_mut().for_each(|(_k, v)| v.0 = None);
},
Some(attribute) => {
self.inner.iter_mut().for_each(|(k, v)| {
if k != &attribute {
v.0 = None;
}
});
},
}
}
pub fn remove(&mut self, key: RichTextAttributeKey) { self.inner.retain(|k, _| k != &key); }
// pub fn block_attributes_except_header(attributes: &Attributes) -> Attributes
// { let mut new_attributes = Attributes::new();
// attributes.iter().for_each(|(k, v)| {
// if k != &AttributeKey::Header {
// new_attributes.insert(k.clone(), v.clone());
// }
// });
//
// new_attributes
// }
// Update inner by constructing new attributes from the other if it's
// not None and replace the key/value with self key/value.
pub fn merge(&mut self, other: Option<RichTextAttributes>) {
if other.is_none() {
return;
}
let mut new_attributes = other.unwrap().inner;
self.inner.iter().for_each(|(k, v)| {
new_attributes.insert(k.clone(), v.clone());
});
self.inner = new_attributes;
}
}
impl Attributes for RichTextAttributes {
fn is_empty(&self) -> bool { self.inner.is_empty() }
fn remove_empty(&mut self) { self.inner.retain(|_, v| v.0.is_some()); }
fn extend_other(&mut self, other: Self) { self.inner.extend(other.inner); }
}
impl OperationTransformable for RichTextAttributes {
fn compose(&self, other: &Self) -> Result<Self, OTError>
where
Self: Sized,
{
let mut attributes = self.clone();
attributes.extend_other(other.clone());
Ok(attributes)
}
fn transform(&self, other: &Self) -> Result<(Self, Self), OTError>
where
Self: Sized,
{
let a = self
.iter()
.fold(RichTextAttributes::new(), |mut new_attributes, (k, v)| {
if !other.contains_key(k) {
new_attributes.insert(k.clone(), v.clone());
}
new_attributes
});
let b = other
.iter()
.fold(RichTextAttributes::new(), |mut new_attributes, (k, v)| {
if !self.contains_key(k) {
new_attributes.insert(k.clone(), v.clone());
}
new_attributes
});
Ok((a, b))
}
fn invert(&self, other: &Self) -> Self {
let base_inverted = other.iter().fold(RichTextAttributes::new(), |mut attributes, (k, v)| {
if other.get(k) != self.get(k) && self.contains_key(k) {
attributes.insert(k.clone(), v.clone());
}
attributes
});
let inverted = self.iter().fold(base_inverted, |mut attributes, (k, _)| {
if other.get(k) != self.get(k) && !other.contains_key(k) {
attributes.delete(k);
}
attributes
});
inverted
}
}
impl std::ops::Deref for RichTextAttributes {
type Target = HashMap<RichTextAttributeKey, RichTextAttributeValue>;
fn deref(&self) -> &Self::Target { &self.inner }
}
impl std::ops::DerefMut for RichTextAttributes {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
}
pub fn attributes_except_header(op: &RichTextOperation) -> RichTextAttributes {
let mut attributes = op.get_attributes();
attributes.remove(RichTextAttributeKey::Header);
attributes
}

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
core::{attributes::*, operation::*, DeltaIter, FlowyStr, Interval, OperationTransformable, MAX_IV_LEN}, core::{operation::*, DeltaIter, FlowyStr, Interval, OperationTransformable, MAX_IV_LEN},
errors::{ErrorBuilder, OTError, OTErrorCode}, errors::{ErrorBuilder, OTError, OTErrorCode},
}; };
use bytes::Bytes; use bytes::Bytes;
@ -469,8 +469,6 @@ fn transform_op_attribute<T: Attributes>(left: &Option<Operation<T>>, right: &Op
left.transform(&right).unwrap().0 left.transform(&right).unwrap().0
} }
pub type RichTextDelta = Delta<RichTextAttributes>;
impl<T> Delta<T> impl<T> Delta<T>
where where
T: Attributes + DeserializeOwned, T: Attributes + DeserializeOwned,
@ -503,22 +501,31 @@ where
} }
} }
impl FromStr for RichTextDelta { impl<T> FromStr for Delta<T>
where
T: Attributes,
{
type Err = (); type Err = ();
fn from_str(s: &str) -> Result<RichTextDelta, Self::Err> { fn from_str(s: &str) -> Result<Delta<T>, Self::Err> {
let mut delta = Delta::with_capacity(1); let mut delta = Delta::with_capacity(1);
delta.add(Operation::Insert(s.into())); delta.add(Operation::Insert(s.into()));
Ok(delta) Ok(delta)
} }
} }
impl std::convert::TryFrom<Vec<u8>> for RichTextDelta { impl<T> std::convert::TryFrom<Vec<u8>> for Delta<T>
where
T: Attributes + DeserializeOwned,
{
type Error = OTError; type Error = OTError;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> { Delta::from_bytes(bytes) } fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> { Delta::from_bytes(bytes) }
} }
impl std::convert::TryFrom<Bytes> for RichTextDelta { impl<T> std::convert::TryFrom<Bytes> for Delta<T>
where
T: Attributes + DeserializeOwned,
{
type Error = OTError; type Error = OTError;
fn try_from(bytes: Bytes) -> Result<Self, Self::Error> { Delta::from_bytes(&bytes) } fn try_from(bytes: Bytes) -> Result<Self, Self::Error> { Delta::from_bytes(&bytes) }

View File

@ -1,5 +1,8 @@
use super::cursor::*; use super::cursor::*;
use crate::core::{Attributes, Delta, Interval, Operation, RichTextAttributes, NEW_LINE}; use crate::{
core::{Attributes, Delta, Interval, Operation, NEW_LINE},
rich_text::RichTextAttributes,
};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
pub(crate) const MAX_IV_LEN: usize = i32::MAX as usize; pub(crate) const MAX_IV_LEN: usize = i32::MAX as usize;

View File

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

View File

@ -1,4 +1,7 @@
use crate::core::{Attributes, Operation, RichTextAttributes}; use crate::{
core::{Attributes, Operation},
rich_text::RichTextAttributes,
};
pub type RichTextOpBuilder = OpBuilder<RichTextAttributes>; pub type RichTextOpBuilder = OpBuilder<RichTextAttributes>;

View File

@ -1,4 +1,7 @@
use crate::core::{FlowyStr, Interval, OpBuilder, OperationTransformable, RichTextAttribute, RichTextAttributes}; use crate::{
core::{FlowyStr, Interval, OpBuilder, OperationTransformable},
rich_text::{RichTextAttribute, RichTextAttributes},
};
use serde::__private::Formatter; use serde::__private::Formatter;
use std::{ use std::{
cmp::min, cmp::min,

View File

@ -1,2 +1,3 @@
pub mod core; pub mod core;
pub mod errors; pub mod errors;
pub mod rich_text;

View File

@ -1,11 +1,179 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::{
use crate::{block_attribute, core::RichTextAttributes, ignore_attribute, inline_attribute, list_attribute}; block_attribute,
core::{Attributes, OperationTransformable, RichTextOperation},
errors::OTError,
ignore_attribute,
inline_attribute,
list_attribute,
};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use std::{
use std::{collections::HashSet, fmt, fmt::Formatter, iter::FromIterator}; collections::{HashMap, HashSet},
fmt,
fmt::Formatter,
iter::FromIterator,
};
use strum_macros::Display; use strum_macros::Display;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct RichTextAttributes {
pub(crate) inner: HashMap<RichTextAttributeKey, RichTextAttributeValue>,
}
impl std::default::Default for RichTextAttributes {
fn default() -> Self {
Self {
inner: HashMap::with_capacity(0),
}
}
}
impl fmt::Display for RichTextAttributes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_fmt(format_args!("{:?}", self.inner)) }
}
pub fn plain_attributes() -> RichTextAttributes { RichTextAttributes::default() }
impl RichTextAttributes {
pub fn new() -> Self { RichTextAttributes { inner: HashMap::new() } }
pub fn is_empty(&self) -> bool { self.inner.is_empty() }
pub fn add(&mut self, attribute: RichTextAttribute) {
let RichTextAttribute { key, value, scope: _ } = attribute;
self.inner.insert(key, value);
}
pub fn add_kv(&mut self, key: RichTextAttributeKey, value: RichTextAttributeValue) {
self.inner.insert(key, value);
}
pub fn delete(&mut self, key: &RichTextAttributeKey) {
self.inner.insert(key.clone(), RichTextAttributeValue(None));
}
pub fn mark_all_as_removed_except(&mut self, attribute: Option<RichTextAttributeKey>) {
match attribute {
None => {
self.inner.iter_mut().for_each(|(_k, v)| v.0 = None);
},
Some(attribute) => {
self.inner.iter_mut().for_each(|(k, v)| {
if k != &attribute {
v.0 = None;
}
});
},
}
}
pub fn remove(&mut self, key: RichTextAttributeKey) { self.inner.retain(|k, _| k != &key); }
// pub fn block_attributes_except_header(attributes: &Attributes) -> Attributes
// { let mut new_attributes = Attributes::new();
// attributes.iter().for_each(|(k, v)| {
// if k != &AttributeKey::Header {
// new_attributes.insert(k.clone(), v.clone());
// }
// });
//
// new_attributes
// }
// Update inner by constructing new attributes from the other if it's
// not None and replace the key/value with self key/value.
pub fn merge(&mut self, other: Option<RichTextAttributes>) {
if other.is_none() {
return;
}
let mut new_attributes = other.unwrap().inner;
self.inner.iter().for_each(|(k, v)| {
new_attributes.insert(k.clone(), v.clone());
});
self.inner = new_attributes;
}
}
impl Attributes for RichTextAttributes {
fn is_empty(&self) -> bool { self.inner.is_empty() }
fn remove_empty(&mut self) { self.inner.retain(|_, v| v.0.is_some()); }
fn extend_other(&mut self, other: Self) { self.inner.extend(other.inner); }
}
impl OperationTransformable for RichTextAttributes {
fn compose(&self, other: &Self) -> Result<Self, OTError>
where
Self: Sized,
{
let mut attributes = self.clone();
attributes.extend_other(other.clone());
Ok(attributes)
}
fn transform(&self, other: &Self) -> Result<(Self, Self), OTError>
where
Self: Sized,
{
let a = self
.iter()
.fold(RichTextAttributes::new(), |mut new_attributes, (k, v)| {
if !other.contains_key(k) {
new_attributes.insert(k.clone(), v.clone());
}
new_attributes
});
let b = other
.iter()
.fold(RichTextAttributes::new(), |mut new_attributes, (k, v)| {
if !self.contains_key(k) {
new_attributes.insert(k.clone(), v.clone());
}
new_attributes
});
Ok((a, b))
}
fn invert(&self, other: &Self) -> Self {
let base_inverted = other.iter().fold(RichTextAttributes::new(), |mut attributes, (k, v)| {
if other.get(k) != self.get(k) && self.contains_key(k) {
attributes.insert(k.clone(), v.clone());
}
attributes
});
let inverted = self.iter().fold(base_inverted, |mut attributes, (k, _)| {
if other.get(k) != self.get(k) && !other.contains_key(k) {
attributes.delete(k);
}
attributes
});
inverted
}
}
impl std::ops::Deref for RichTextAttributes {
type Target = HashMap<RichTextAttributeKey, RichTextAttributeValue>;
fn deref(&self) -> &Self::Target { &self.inner }
}
impl std::ops::DerefMut for RichTextAttributes {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
}
pub fn attributes_except_header(op: &RichTextOperation) -> RichTextAttributes {
let mut attributes = op.get_attributes();
attributes.remove(RichTextAttributeKey::Header);
attributes
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct RichTextAttribute { pub struct RichTextAttribute {
pub key: RichTextAttributeKey, pub key: RichTextAttributeKey,

View File

@ -1,6 +1,5 @@
#[rustfmt::skip] #[rustfmt::skip]
use crate::core::RichTextAttributeValue; use crate::rich_text::{RichTextAttribute, RichTextAttributeKey, RichTextAttributes, RichTextAttributeValue};
use crate::core::{RichTextAttribute, RichTextAttributeKey, RichTextAttributes};
use serde::{ use serde::{
de, de,
de::{MapAccess, Visitor}, de::{MapAccess, Visitor},

View File

@ -1,5 +1,6 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::core::{RichTextAttribute, RichTextAttributes}; use crate::rich_text::{RichTextAttribute, RichTextAttributes};
pub struct AttributeBuilder { pub struct AttributeBuilder {
inner: RichTextAttributes, inner: RichTextAttributes,
} }

View File

@ -0,0 +1,3 @@
use crate::{core::Delta, rich_text::RichTextAttributes};
pub type RichTextDelta = Delta<RichTextAttributes>;

View File

@ -1,12 +1,11 @@
#![allow(clippy::module_inception)]
mod attribute;
mod attributes; mod attributes;
mod attributes_serde; mod attributes_serde;
mod builder; mod builder;
#[macro_use] #[macro_use]
mod macros; mod macros;
mod delta;
pub use attribute::*;
pub use attributes::*; pub use attributes::*;
pub use builder::*; pub use builder::*;
pub use delta::*;

View File