Skip to content

Commit

Permalink
fix: detached loro text issues (#665)
Browse files Browse the repository at this point in the history
* fix: detached loro text issues

resolve lorotext.toDelta must be attached? #661

* chore: changeset and update test

* chore: fix warnings
  • Loading branch information
zxch3n authored Feb 26, 2025
1 parent 28f0aae commit 6cf06e5
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 32 deletions.
5 changes: 5 additions & 0 deletions .changeset/twenty-rockets-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"loro-crdt": patch
---

fix: detached loro text issues #665
19 changes: 0 additions & 19 deletions crates/loro-internal/src/container/richtext/richtext_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ impl Display for RichtextState {
use cache::CachedCursor;

mod cache {
use tracing::trace;

use super::*;

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -143,7 +141,6 @@ mod cache {

let cached_index = match c.index.entry(pos_type) {
std::collections::hash_map::Entry::Vacant(vacant_entry) => {
trace!("new cache");
let index = self.get_index_from_cursor(
Cursor {
leaf: c.leaf,
Expand All @@ -164,7 +161,6 @@ mod cache {
}

if cached_index == index {
trace!("eq");
break 'block Some(Cursor {
leaf: c.leaf,
offset: 0,
Expand All @@ -175,14 +171,6 @@ mod cache {
let elem_len = elem.len_with(pos_type);
if cached_index + elem_len == index {
let offset = elem.len_with(PosType::Entity);
trace!(
"end cached_index={} index={} elem.len_with(pos_type) = {} offset={}",
cached_index,
index,
elem_len,
offset
);

break 'block Some(Cursor {
leaf: c.leaf,
offset,
Expand All @@ -195,11 +183,6 @@ mod cache {

let offset =
pos_type_offset_to_entity_offset(pos_type, elem, index - cached_index)?;
trace!(
"offset convert from {} to {:?}",
index - cached_index,
offset
);
Some(Cursor {
leaf: c.leaf,
offset,
Expand All @@ -214,7 +197,6 @@ mod cache {
{
if let Some(c) = ans.as_ref() {
let actual = self.get_index_from_cursor(*c, pos_type);
trace!("actual={} index={}", actual.unwrap(), index);
assert_eq!(actual.unwrap(), index);
}
}
Expand Down Expand Up @@ -2570,7 +2552,6 @@ fn entity_offset_to_pos_type_offset(
}
}

#[tracing::instrument(level = "trace")]
fn pos_type_offset_to_entity_offset(
pos_type: PosType,
elem: &RichtextStateChunk,
Expand Down
14 changes: 6 additions & 8 deletions crates/loro-internal/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1340,13 +1340,11 @@ impl TextHandler {
/// Get the version id of the richtext
///
/// This can be used to detect whether the richtext is changed
pub fn version_id(&self) -> usize {
pub fn version_id(&self) -> Option<usize> {
match &self.inner {
MaybeDetached::Detached(_) => {
unimplemented!("Detached text container does not have version id")
}
MaybeDetached::Detached(_) => None,
MaybeDetached::Attached(a) => {
a.with_state(|state| state.as_richtext_state_mut().unwrap().get_version_id())
Some(a.with_state(|state| state.as_richtext_state_mut().unwrap().get_version_id()))
}
}
}
Expand Down Expand Up @@ -1928,8 +1926,8 @@ impl TextHandler {
) -> LoroResult<()> {
match &self.inner {
MaybeDetached::Detached(t) => {
let mut v = t.try_lock().unwrap().value.clone();
self.mark_for_detached(&mut v, key, &value, start, end, false)
let mut g = t.try_lock().unwrap();
self.mark_for_detached(&mut g.value, key, &value, start, end, false)
}
MaybeDetached::Attached(a) => {
a.with_txn(|txn| self.mark_with_txn(txn, start, end, key, value, false))
Expand All @@ -1947,7 +1945,7 @@ impl TextHandler {
is_delete: bool,
) -> Result<(), LoroError> {
let key: InternalString = key.into();
let len = self.len_event();
let len = state.len_event();
if start >= end {
return Err(loro_common::LoroError::ArgErr(
"Start must be less than end".to_string().into_boxed_str(),
Expand Down
12 changes: 8 additions & 4 deletions crates/loro-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2507,16 +2507,20 @@ impl LoroText {
#[wasm_bindgen(js_name = "toDelta")]
pub fn to_delta(&mut self) -> JsStringDelta {
let version = self.handler.version_id();
if let Some((v, delta)) = self.delta_cache.as_ref() {
if *v == version {
return delta.clone().into();
if let Some(v) = version {
if let Some((vv, delta)) = self.delta_cache.as_ref() {
if v == *vv {
return delta.clone().into();
}
}
}

let delta = self.handler.get_richtext_value();
let value: JsValue = delta.into();
let ans: JsStringDelta = value.clone().into();
self.delta_cache = Some((version, value));
if let Some(v) = version {
self.delta_cache = Some((v, value));
}
ans
}

Expand Down
11 changes: 11 additions & 0 deletions crates/loro-wasm/tests/__snapshots__/basic.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`call toDelta on detached text 1`] = `
[
{
"attributes": {
"bold": true,
},
"insert": "Hello",
},
]
`;

exports[`can diff two versions 1`] = `
[
[
Expand Down
8 changes: 8 additions & 0 deletions crates/loro-wasm/tests/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1532,3 +1532,11 @@ it("text mark on LoroText", () => {
text.insert(0, "Hello");
text.mark({ start: 0, end: 5 }, "bold", true);
})

it("call toDelta on detached text", () => {
const text = new LoroText();
text.insert(0, "Hello");
text.mark({ start: 0, end: 5 }, "bold", true);
const d = text.toDelta();
expect(d).toMatchSnapshot();
})
17 changes: 16 additions & 1 deletion crates/loro/tests/loro_rust_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use loro::{
LoroText, LoroValue, ToJson, TreeParentId,
};
use loro_internal::{
encoding::EncodedBlobMode, handler::TextDelta, id::ID, version_range, vv, LoroResult,
encoding::EncodedBlobMode, fx_map, handler::TextDelta, id::ID, version_range, vv, LoroResult,
};
use rand::{Rng, SeedableRng};
use serde_json::json;
Expand Down Expand Up @@ -3260,3 +3260,18 @@ fn test_iter_change_on_edge() {
doc.fork_at(&Frontiers::from_id(ID::new(1, 10)));
doc.fork_at(&Frontiers::from_id(ID::new(1, 11)));
}

#[test]
fn test_to_delta_on_detached_text() {
let text = LoroText::new();
text.insert(0, "Hello").unwrap();
text.mark(0..5, "bold", true).unwrap();
let delta = text.to_delta();
assert_eq!(
delta,
vec![TextDelta::Insert {
insert: "Hello".to_string(),
attributes: Some(fx_map! { "bold".into() => LoroValue::Bool(true) }),
}]
);
}

0 comments on commit 6cf06e5

Please sign in to comment.