-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support incremental to update textblock's delta
- Loading branch information
Showing
18 changed files
with
820 additions
and
99 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
use crate::blocks::text_entities::TextDelta; | ||
use collab::preclude::*; | ||
use std::collections::HashMap; | ||
|
||
pub struct TextOperation { | ||
root: MapRefWrapper, | ||
} | ||
|
||
impl TextOperation { | ||
pub fn new(root: MapRefWrapper) -> Self { | ||
Self { root } | ||
} | ||
|
||
pub fn get_text_with_txn(&self, txn: &mut TransactionMut, text_id: &str) -> TextRefWrapper { | ||
self | ||
.root | ||
.get_text_ref_with_txn(txn, text_id) | ||
.unwrap_or_else(|| self.create_text_with_txn(txn, text_id)) | ||
} | ||
|
||
pub fn create_text_with_txn(&self, txn: &mut TransactionMut, text_id: &str) -> TextRefWrapper { | ||
self.root.insert_text_with_txn(txn, text_id) | ||
} | ||
|
||
pub fn delete_text_with_txn(&self, txn: &mut TransactionMut, text_id: &str) { | ||
self.root.delete_with_txn(txn, text_id); | ||
} | ||
|
||
pub fn get_delta_with_txn<T: ReadTxn>(&self, txn: &T, text_id: &str) -> Option<Vec<TextDelta>> { | ||
let text_ref = self.root.get_text_ref_with_txn(txn, text_id)?; | ||
Some( | ||
text_ref | ||
.get_delta_with_txn(txn) | ||
.iter() | ||
.map(|d| TextDelta::from(txn, d.to_owned())) | ||
.collect(), | ||
) | ||
} | ||
|
||
pub fn apply_delta_with_txn( | ||
&self, | ||
txn: &mut TransactionMut, | ||
text_id: &str, | ||
delta: Vec<TextDelta>, | ||
) { | ||
let text_ref = self.get_text_with_txn(txn, text_id); | ||
let delta = delta.iter().map(|d| d.to_owned().to_delta()).collect(); | ||
text_ref.apply_delta_with_txn(txn, delta); | ||
} | ||
|
||
pub fn serialize_all_text_delta(&self) -> HashMap<String, String> { | ||
let txn = self.root.transact(); | ||
self | ||
.root | ||
.iter(&txn) | ||
.filter_map(|(k, _)| { | ||
self.get_delta_with_txn(&txn, k).map(|delta| { | ||
( | ||
k.to_string(), | ||
serde_json::to_string(&delta).unwrap_or_default(), | ||
) | ||
}) | ||
}) | ||
.collect() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
use collab::preclude::{Attrs, Delta, ReadTxn, Value as YrsValue}; | ||
use lib0::any::Any; | ||
use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor}; | ||
use serde::ser::SerializeMap; | ||
use serde::{Serialize, Serializer}; | ||
use std::collections::HashMap; | ||
use std::fmt; | ||
use std::rc::Rc; | ||
|
||
const FIELD_INSERT: &str = "insert"; | ||
const FIELD_DELETE: &str = "delete"; | ||
const FIELD_RETAIN: &str = "retain"; | ||
const FIELD_ATTRIBUTES: &str = "attributes"; | ||
const FIELDS: &[&str] = &[FIELD_INSERT, FIELD_DELETE, FIELD_RETAIN, FIELD_ATTRIBUTES]; | ||
|
||
#[derive(Debug, Clone)] | ||
pub enum TextDelta { | ||
/// Determines a change that resulted in insertion of a piece of text, which optionally could have been | ||
/// formatted with provided set of attributes. | ||
Inserted(String, Option<Attrs>), | ||
|
||
/// Determines a change that resulted in removing a consecutive range of characters. | ||
Deleted(u32), | ||
|
||
/// Determines a number of consecutive unchanged characters. Used to recognize non-edited spaces | ||
/// between [Delta::Inserted] and/or [Delta::Deleted] chunks. Can contain an optional set of | ||
/// attributes, which have been used to format an existing piece of text. | ||
Retain(u32, Option<Attrs>), | ||
} | ||
|
||
impl PartialEq for TextDelta { | ||
fn eq(&self, other: &Self) -> bool { | ||
match (self, other) { | ||
(Self::Inserted(content1, attrs1), Self::Inserted(content2, attrs2)) => { | ||
content1 == content2 && attrs1 == attrs2 | ||
}, | ||
(Self::Deleted(len1), Self::Deleted(len2)) => len1 == len2, | ||
(Self::Retain(len1, attrs1), Self::Retain(len2, attrs2)) => len1 == len2 && attrs1 == attrs2, | ||
_ => false, | ||
} | ||
} | ||
} | ||
|
||
impl Eq for TextDelta {} | ||
|
||
impl TextDelta { | ||
pub fn from<T: ReadTxn>(txn: &T, value: Delta) -> Self { | ||
match value { | ||
Delta::Inserted(content, attrs) => { | ||
let content = content.to_string(txn); | ||
Self::Inserted(content, attrs.map(|attrs| *attrs)) | ||
}, | ||
Delta::Deleted(len) => Self::Deleted(len), | ||
Delta::Retain(len, attrs) => Self::Retain(len, attrs.map(|attrs| *attrs)), | ||
} | ||
} | ||
|
||
pub fn to_delta(self) -> Delta { | ||
match self { | ||
Self::Inserted(content, attrs) => { | ||
let content = YrsValue::from(content); | ||
Delta::Inserted(content, attrs.map(Box::new)) | ||
}, | ||
Self::Deleted(len) => Delta::Deleted(len), | ||
Self::Retain(len, attrs) => Delta::Retain(len, attrs.map(Box::new)), | ||
} | ||
} | ||
} | ||
|
||
impl Serialize for TextDelta { | ||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||
where | ||
S: Serializer, | ||
{ | ||
match self { | ||
Self::Inserted(content, attrs) => { | ||
let mut map = serializer.serialize_map(Some(2))?; | ||
map.serialize_entry(FIELD_INSERT, content)?; | ||
if let Some(attrs) = attrs { | ||
let attrs_hash = attrs | ||
.iter() | ||
.map(|(k, v)| (k.to_string(), v.to_owned())) | ||
.collect::<HashMap<String, Any>>(); | ||
map.serialize_entry(FIELD_ATTRIBUTES, &attrs_hash)?; | ||
} | ||
map.end() | ||
}, | ||
Self::Deleted(len) => { | ||
let mut map = serializer.serialize_map(Some(1))?; | ||
map.serialize_entry(FIELD_DELETE, len)?; | ||
map.end() | ||
}, | ||
Self::Retain(len, attrs) => { | ||
let mut map = serializer.serialize_map(Some(1))?; | ||
map.serialize_entry(FIELD_RETAIN, len)?; | ||
if let Some(attrs) = attrs { | ||
let attrs_hash = attrs | ||
.iter() | ||
.map(|(k, v)| (k.to_string(), v.to_owned())) | ||
.collect::<HashMap<String, Any>>(); | ||
map.serialize_entry(FIELD_ATTRIBUTES, &attrs_hash)?; | ||
} | ||
map.end() | ||
}, | ||
} | ||
} | ||
} | ||
|
||
impl<'de> Deserialize<'de> for TextDelta { | ||
fn deserialize<D>(deserializer: D) -> Result<TextDelta, D::Error> | ||
where | ||
D: Deserializer<'de>, | ||
{ | ||
struct TextDeltaVisitor; | ||
|
||
impl<'de> Visitor<'de> for TextDeltaVisitor { | ||
type Value = TextDelta; | ||
|
||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { | ||
formatter.write_str("a valid TextDelta") | ||
} | ||
|
||
fn visit_map<A>(self, mut map: A) -> Result<TextDelta, A::Error> | ||
where | ||
A: MapAccess<'de>, | ||
{ | ||
let mut delta_type: Option<String> = None; | ||
let mut content: Option<String> = None; | ||
let mut len: Option<usize> = None; | ||
let mut attrs: Option<HashMap<Rc<str>, Any>> = None; | ||
|
||
while let Some(key) = map.next_key::<String>()? { | ||
match key.as_str() { | ||
FIELD_INSERT => { | ||
if delta_type.is_some() { | ||
return Err(de::Error::duplicate_field(FIELD_INSERT)); | ||
} | ||
content = Some(map.next_value()?); | ||
delta_type = Some(key); | ||
}, | ||
FIELD_DELETE => { | ||
if delta_type.is_some() { | ||
return Err(de::Error::duplicate_field(FIELD_DELETE)); | ||
} | ||
len = Some(map.next_value()?); | ||
delta_type = Some(key); | ||
}, | ||
FIELD_RETAIN => { | ||
if delta_type.is_some() { | ||
return Err(de::Error::duplicate_field(FIELD_RETAIN)); | ||
} | ||
len = Some(map.next_value()?); | ||
delta_type = Some(key); | ||
}, | ||
FIELD_ATTRIBUTES => { | ||
if attrs.is_none() { | ||
attrs = Some(HashMap::new()); | ||
} | ||
let attrs_val = map.next_value::<HashMap<String, Any>>()?; | ||
attrs_val.iter().for_each(|(key, val)| { | ||
attrs | ||
.as_mut() | ||
.unwrap() | ||
.insert(Rc::from(key.to_string()), val.clone()); | ||
}); | ||
}, | ||
_ => { | ||
return Err(de::Error::unknown_field(key.as_str(), FIELDS)); | ||
}, | ||
} | ||
} | ||
|
||
match delta_type { | ||
Some(delta_type) => match delta_type.as_str() { | ||
FIELD_INSERT => { | ||
if let Some(attrs) = attrs { | ||
Ok(TextDelta::Inserted( | ||
content.ok_or_else(|| de::Error::missing_field(FIELD_INSERT))?, | ||
Some(attrs), | ||
)) | ||
} else { | ||
Ok(TextDelta::Inserted( | ||
content.ok_or_else(|| de::Error::missing_field(FIELD_INSERT))?, | ||
None, | ||
)) | ||
} | ||
}, | ||
FIELD_DELETE => Ok(TextDelta::Deleted( | ||
len.ok_or_else(|| de::Error::missing_field(FIELD_DELETE))? as u32, | ||
)), | ||
FIELD_RETAIN => { | ||
if let Some(attrs) = attrs { | ||
Ok(TextDelta::Retain( | ||
len.ok_or_else(|| de::Error::missing_field(FIELD_RETAIN))? as u32, | ||
Some(attrs), | ||
)) | ||
} else { | ||
Ok(TextDelta::Retain( | ||
len.ok_or_else(|| de::Error::missing_field(FIELD_RETAIN))? as u32, | ||
None, | ||
)) | ||
} | ||
}, | ||
_ => Err(de::Error::unknown_variant( | ||
&delta_type, | ||
&[FIELD_INSERT, FIELD_DELETE, FIELD_RETAIN], | ||
)), | ||
}, | ||
None => Err(de::Error::missing_field("delta type")), | ||
} | ||
} | ||
} | ||
|
||
deserializer.deserialize_map(TextDeltaVisitor) | ||
} | ||
} |
Oops, something went wrong.