mirror of
https://github.com/loro-dev/loro.git
synced 2025-01-22 04:45:46 +00:00
refactor: hold doc reference in handler (#624)
* refactor: doc arc in handler * chore: cargo fix * fix: clean txn arena * chore: cargo fix * fix: clean global txn * fix: weak doc * refactor: loro wasm remove arc * chore: cargo fix
This commit is contained in:
parent
e26ee357b8
commit
245838f64c
26 changed files with 729 additions and 921 deletions
|
@ -140,14 +140,14 @@ impl Actor {
|
|||
// trace!("BeforeUndo {:#?}", self.loro.get_deep_value_with_id());
|
||||
// println!("\n\nstart undo\n");
|
||||
for _ in 0..undo_length {
|
||||
self.undo_manager.undo.undo(&self.loro).unwrap();
|
||||
self.undo_manager.undo.undo().unwrap();
|
||||
self.loro.commit();
|
||||
}
|
||||
// trace!("AfterUndo {:#?}", self.loro.get_deep_value_with_id());
|
||||
|
||||
// println!("\n\nstart redo\n");
|
||||
for _ in 0..undo_length {
|
||||
self.undo_manager.undo.redo(&self.loro).unwrap();
|
||||
self.undo_manager.undo.redo().unwrap();
|
||||
self.loro.commit();
|
||||
}
|
||||
// trace!("AfterRedo {:#?}", self.loro.get_deep_value_with_id());
|
||||
|
|
|
@ -13,18 +13,18 @@ impl UndoManager {
|
|||
}
|
||||
|
||||
/// Undo the last change made by the peer.
|
||||
pub fn undo(&self, doc: &LoroDoc) -> LoroResult<bool> {
|
||||
self.0.write().unwrap().undo(doc)
|
||||
pub fn undo(&self) -> LoroResult<bool> {
|
||||
self.0.write().unwrap().undo()
|
||||
}
|
||||
|
||||
/// Redo the last change made by the peer.
|
||||
pub fn redo(&self, doc: &LoroDoc) -> LoroResult<bool> {
|
||||
self.0.write().unwrap().redo(doc)
|
||||
pub fn redo(&self) -> LoroResult<bool> {
|
||||
self.0.write().unwrap().redo()
|
||||
}
|
||||
|
||||
/// Record a new checkpoint.
|
||||
pub fn record_new_checkpoint(&self, doc: &LoroDoc) -> LoroResult<()> {
|
||||
self.0.write().unwrap().record_new_checkpoint(doc)
|
||||
pub fn record_new_checkpoint(&self) -> LoroResult<()> {
|
||||
self.0.write().unwrap().record_new_checkpoint()
|
||||
}
|
||||
|
||||
/// Whether the undo manager can undo.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::{
|
||||
hash::Hash,
|
||||
sync::{Mutex, Weak},
|
||||
sync::Weak,
|
||||
};
|
||||
|
||||
use fxhash::FxHashMap;
|
||||
|
@ -8,8 +8,7 @@ use loro_common::IdLp;
|
|||
use serde::{ser::SerializeStruct, Serialize};
|
||||
|
||||
use crate::{
|
||||
arena::SharedArena, change::Lamport, handler::ValueOrHandler, id::PeerID, span::HasLamport,
|
||||
txn::Transaction, DocState, InternalString, LoroValue,
|
||||
change::Lamport, handler::ValueOrHandler, id::PeerID, span::HasLamport, InternalString, LoroDocInner, LoroValue,
|
||||
};
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize)]
|
||||
|
@ -66,17 +65,11 @@ pub struct ResolvedMapValue {
|
|||
}
|
||||
|
||||
impl ResolvedMapValue {
|
||||
pub(crate) fn from_map_value(
|
||||
v: MapValue,
|
||||
arena: &SharedArena,
|
||||
txn: &Weak<Mutex<Option<Transaction>>>,
|
||||
state: &Weak<Mutex<DocState>>,
|
||||
) -> Self {
|
||||
pub(crate) fn from_map_value(v: MapValue, doc: &Weak<LoroDocInner>) -> Self {
|
||||
let doc = &doc.upgrade().unwrap();
|
||||
ResolvedMapValue {
|
||||
idlp: IdLp::new(v.peer, v.lamp),
|
||||
value: v
|
||||
.value
|
||||
.map(|v| ValueOrHandler::from_value(v, arena, txn, state)),
|
||||
value: v.value.map(|v| ValueOrHandler::from_value(v, doc)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use super::{state::DocState, txn::Transaction};
|
||||
use crate::{
|
||||
arena::SharedArena,
|
||||
container::{
|
||||
idx::ContainerIdx,
|
||||
list::list_op::{DeleteSpan, DeleteSpanWithId, ListOp},
|
||||
|
@ -14,6 +13,7 @@ use crate::{
|
|||
state::{IndexType, State, TreeParentId},
|
||||
txn::EventHint,
|
||||
utils::{string_slice::StringSlice, utf16::count_utf16_len},
|
||||
LoroDoc, LoroDocInner,
|
||||
};
|
||||
use append_only_bytes::BytesSlice;
|
||||
use enum_as_inner::EnumAsInner;
|
||||
|
@ -30,7 +30,7 @@ use std::{
|
|||
collections::BinaryHeap,
|
||||
fmt::Debug,
|
||||
ops::Deref,
|
||||
sync::{Arc, Mutex, Weak},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use tracing::{error, info, instrument};
|
||||
|
||||
|
@ -52,6 +52,7 @@ pub trait HandlerTrait: Clone + Sized {
|
|||
fn kind(&self) -> ContainerType;
|
||||
fn to_handler(&self) -> Handler;
|
||||
fn from_handler(h: Handler) -> Option<Self>;
|
||||
fn doc(&self) -> Option<LoroDoc>;
|
||||
/// This method returns an attached handler.
|
||||
fn attach(
|
||||
&self,
|
||||
|
@ -84,29 +85,22 @@ pub trait HandlerTrait: Clone + Sized {
|
|||
.ok_or(LoroError::MisuseDetachedContainer {
|
||||
method: "with_state",
|
||||
})?;
|
||||
let state = inner.state.upgrade().unwrap();
|
||||
let state = inner.doc.state.clone();
|
||||
let mut guard = state.try_lock().unwrap();
|
||||
guard.with_state_mut(inner.container_idx, f)
|
||||
}
|
||||
}
|
||||
|
||||
fn create_handler(inner: &BasicHandler, id: ContainerID) -> Handler {
|
||||
Handler::new_attached(
|
||||
id,
|
||||
inner.arena.clone(),
|
||||
inner.txn.clone(),
|
||||
inner.state.clone(),
|
||||
)
|
||||
Handler::new_attached(id, inner.doc.clone())
|
||||
}
|
||||
|
||||
/// Flatten attributes that allow overlap
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BasicHandler {
|
||||
id: ContainerID,
|
||||
arena: SharedArena,
|
||||
container_idx: ContainerIdx,
|
||||
txn: Weak<Mutex<Option<Transaction>>>,
|
||||
state: Weak<Mutex<DocState>>,
|
||||
doc: Arc<LoroDocInner>,
|
||||
}
|
||||
|
||||
struct DetachedInner<T> {
|
||||
|
@ -174,9 +168,13 @@ impl<T> From<BasicHandler> for MaybeDetached<T> {
|
|||
}
|
||||
|
||||
impl BasicHandler {
|
||||
pub(crate) fn doc(&self) -> LoroDoc {
|
||||
LoroDoc::from_inner(self.doc.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_doc_state<R>(&self, f: impl FnOnce(&mut DocState) -> R) -> R {
|
||||
let state = self.state.upgrade().unwrap();
|
||||
let state = self.doc.state.clone();
|
||||
let mut guard = state.try_lock().unwrap();
|
||||
f(&mut guard)
|
||||
}
|
||||
|
@ -185,23 +183,18 @@ impl BasicHandler {
|
|||
&self,
|
||||
f: impl FnOnce(&mut Transaction) -> Result<R, LoroError>,
|
||||
) -> Result<R, LoroError> {
|
||||
with_txn(&self.txn, f)
|
||||
with_txn(&self.doc.txn, f)
|
||||
}
|
||||
|
||||
fn get_parent(&self) -> Option<Handler> {
|
||||
let parent_idx = self.arena.get_parent(self.container_idx)?;
|
||||
let parent_id = self.arena.get_container_id(parent_idx).unwrap();
|
||||
let parent_idx = self.doc.arena.get_parent(self.container_idx)?;
|
||||
let parent_id = self.doc.arena.get_container_id(parent_idx).unwrap();
|
||||
{
|
||||
let arena = self.arena.clone();
|
||||
let txn = self.txn.clone();
|
||||
let state = self.state.clone();
|
||||
let kind = parent_id.container_type();
|
||||
let handler = BasicHandler {
|
||||
container_idx: parent_idx,
|
||||
id: parent_id,
|
||||
txn,
|
||||
arena,
|
||||
state,
|
||||
doc: self.doc.clone(),
|
||||
};
|
||||
|
||||
Some(match kind {
|
||||
|
@ -230,26 +223,23 @@ impl BasicHandler {
|
|||
}
|
||||
|
||||
pub fn get_value(&self) -> LoroValue {
|
||||
self.state
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
self.doc
|
||||
.state
|
||||
.try_lock()
|
||||
.unwrap()
|
||||
.get_value_by_idx(self.container_idx)
|
||||
}
|
||||
|
||||
pub fn get_deep_value(&self) -> LoroValue {
|
||||
self.state
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
self.doc
|
||||
.state
|
||||
.try_lock()
|
||||
.unwrap()
|
||||
.get_container_deep_value(self.container_idx)
|
||||
}
|
||||
|
||||
fn with_state<R>(&self, f: impl FnOnce(&mut State) -> R) -> R {
|
||||
let state = self.state.upgrade().unwrap();
|
||||
let mut guard = state.try_lock().unwrap();
|
||||
let mut guard = self.doc.state.try_lock().unwrap();
|
||||
guard.with_state_mut(self.container_idx, f)
|
||||
}
|
||||
|
||||
|
@ -258,10 +248,11 @@ impl BasicHandler {
|
|||
}
|
||||
|
||||
fn is_deleted(&self) -> bool {
|
||||
match self.state.upgrade() {
|
||||
None => false,
|
||||
Some(state) => state.try_lock().unwrap().is_deleted(self.container_idx),
|
||||
}
|
||||
self.doc
|
||||
.state
|
||||
.try_lock()
|
||||
.unwrap()
|
||||
.is_deleted(self.container_idx)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -351,6 +342,13 @@ impl HandlerTrait for TextHandler {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn doc(&self) -> Option<LoroDoc> {
|
||||
match &self.inner {
|
||||
MaybeDetached::Detached(_) => None,
|
||||
MaybeDetached::Attached(a) => Some(a.doc()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for TextHandler {
|
||||
|
@ -574,6 +572,13 @@ impl HandlerTrait for MapHandler {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn doc(&self) -> Option<LoroDoc> {
|
||||
match &self.inner {
|
||||
MaybeDetached::Detached(_) => None,
|
||||
MaybeDetached::Attached(a) => Some(a.doc()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for MapHandler {
|
||||
|
@ -692,6 +697,13 @@ impl HandlerTrait for MovableListHandler {
|
|||
MaybeDetached::Attached(_a) => Some(self.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn doc(&self) -> Option<LoroDoc> {
|
||||
match &self.inner {
|
||||
MaybeDetached::Detached(_) => None,
|
||||
MaybeDetached::Attached(a) => Some(a.doc()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for MovableListHandler {
|
||||
|
@ -803,6 +815,13 @@ impl HandlerTrait for ListHandler {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn doc(&self) -> Option<LoroDoc> {
|
||||
match &self.inner {
|
||||
MaybeDetached::Detached(_) => None,
|
||||
MaybeDetached::Attached(a) => Some(a.doc()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -868,6 +887,10 @@ impl HandlerTrait for UnknownHandler {
|
|||
fn get_attached(&self) -> Option<Self> {
|
||||
Some(self.clone())
|
||||
}
|
||||
|
||||
fn doc(&self) -> Option<LoroDoc> {
|
||||
Some(self.inner.doc())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, EnumAsInner, Debug)]
|
||||
|
@ -995,22 +1018,28 @@ impl HandlerTrait for Handler {
|
|||
fn from_handler(h: Handler) -> Option<Self> {
|
||||
Some(h)
|
||||
}
|
||||
|
||||
fn doc(&self) -> Option<LoroDoc> {
|
||||
match self {
|
||||
Self::Text(x) => x.doc(),
|
||||
Self::Map(x) => x.doc(),
|
||||
Self::List(x) => x.doc(),
|
||||
Self::MovableList(x) => x.doc(),
|
||||
Self::Tree(x) => x.doc(),
|
||||
#[cfg(feature = "counter")]
|
||||
Self::Counter(x) => x.doc(),
|
||||
Self::Unknown(x) => x.doc(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
pub(crate) fn new_attached(
|
||||
id: ContainerID,
|
||||
arena: SharedArena,
|
||||
txn: Weak<Mutex<Option<Transaction>>>,
|
||||
state: Weak<Mutex<DocState>>,
|
||||
) -> Self {
|
||||
pub(crate) fn new_attached(id: ContainerID, doc: Arc<LoroDocInner>) -> Self {
|
||||
let kind = id.container_type();
|
||||
let handler = BasicHandler {
|
||||
container_idx: arena.register_container(&id),
|
||||
container_idx: doc.arena.register_container(&id),
|
||||
id,
|
||||
txn,
|
||||
arena,
|
||||
state,
|
||||
doc,
|
||||
};
|
||||
|
||||
match kind {
|
||||
|
@ -1260,19 +1289,9 @@ pub enum ValueOrHandler {
|
|||
}
|
||||
|
||||
impl ValueOrHandler {
|
||||
pub(crate) fn from_value(
|
||||
value: LoroValue,
|
||||
arena: &SharedArena,
|
||||
txn: &Weak<Mutex<Option<Transaction>>>,
|
||||
state: &Weak<Mutex<DocState>>,
|
||||
) -> Self {
|
||||
pub(crate) fn from_value(value: LoroValue, doc: &Arc<LoroDocInner>) -> Self {
|
||||
if let LoroValue::Container(c) = value {
|
||||
ValueOrHandler::Handler(Handler::new_attached(
|
||||
c,
|
||||
arena.clone(),
|
||||
txn.clone(),
|
||||
state.clone(),
|
||||
))
|
||||
ValueOrHandler::Handler(Handler::new_attached(c, doc.clone()))
|
||||
} else {
|
||||
ValueOrHandler::Value(value)
|
||||
}
|
||||
|
@ -1795,7 +1814,7 @@ impl TextHandler {
|
|||
unicode_len: unicode_len as u32,
|
||||
event_len: event_len as u32,
|
||||
},
|
||||
&inner.state,
|
||||
&inner.doc,
|
||||
)?;
|
||||
|
||||
Ok(override_styles)
|
||||
|
@ -1878,7 +1897,7 @@ impl TextHandler {
|
|||
},
|
||||
unicode_len: range.entity_len(),
|
||||
},
|
||||
&inner.state,
|
||||
&inner.doc,
|
||||
)?;
|
||||
event_end = event_start;
|
||||
}
|
||||
|
@ -2021,8 +2040,7 @@ impl TextHandler {
|
|||
let inner = self.inner.try_attached_state()?;
|
||||
let key: InternalString = key.into();
|
||||
|
||||
let mutex = &inner.state.upgrade().unwrap();
|
||||
let mut doc_state = mutex.try_lock().unwrap();
|
||||
let mut doc_state = inner.doc.state.try_lock().unwrap();
|
||||
let (entity_range, skip) = doc_state.with_state_mut(inner.container_idx, |state| {
|
||||
let (entity_range, styles) = state
|
||||
.as_richtext_state_mut()
|
||||
|
@ -2072,14 +2090,14 @@ impl TextHandler {
|
|||
end: end as u32,
|
||||
style: crate::container::richtext::Style { key, data: value },
|
||||
},
|
||||
&inner.state,
|
||||
&inner.doc,
|
||||
)?;
|
||||
|
||||
txn.apply_local_op(
|
||||
inner.container_idx,
|
||||
crate::op::RawOpContent::List(ListOp::StyleEnd),
|
||||
EventHint::MarkEnd,
|
||||
&inner.state,
|
||||
&inner.doc,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
|
@ -2369,7 +2387,7 @@ impl ListHandler {
|
|||
pos,
|
||||
}),
|
||||
EventHint::InsertList { len: 1, pos },
|
||||
&inner.state,
|
||||
&inner.doc,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -2453,7 +2471,7 @@ impl ListHandler {
|
|||
pos,
|
||||
}),
|
||||
EventHint::InsertList { len: 1, pos },
|
||||
&inner.state,
|
||||
&inner.doc,
|
||||
)?;
|
||||
let ans = child.attach(txn, inner, container_id)?;
|
||||
Ok(ans)
|
||||
|
@ -2500,7 +2518,7 @@ impl ListHandler {
|
|||
1,
|
||||
))),
|
||||
EventHint::DeleteList(DeleteSpan::new(pos as isize, 1)),
|
||||
&inner.state,
|
||||
&inner.doc,
|
||||
)?;
|
||||
}
|
||||
|
||||
|
@ -2826,7 +2844,7 @@ impl MovableListHandler {
|
|||
pos: op_index,
|
||||
}),
|
||||
EventHint::InsertList { len: 1, pos },
|
||||
&inner.state,
|
||||
&inner.doc,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -2908,7 +2926,7 @@ impl MovableListHandler {
|
|||
from: from as u32,
|
||||
to: to as u32,
|
||||
},
|
||||
&inner.state,
|
||||
&inner.doc,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -3021,7 +3039,7 @@ impl MovableListHandler {
|
|||
pos: op_index,
|
||||
}),
|
||||
EventHint::InsertList { len: 1, pos },
|
||||
&inner.state,
|
||||
&inner.doc,
|
||||
)?;
|
||||
child.attach(txn, inner, container_id)
|
||||
}
|
||||
|
@ -3075,7 +3093,7 @@ impl MovableListHandler {
|
|||
});
|
||||
|
||||
let hint = EventHint::SetList { index, value };
|
||||
txn.apply_local_op(inner.container_idx, op, hint, &inner.state)
|
||||
txn.apply_local_op(inner.container_idx, op, hint, &inner.doc)
|
||||
}
|
||||
|
||||
pub fn set_container<H: HandlerTrait>(&self, pos: usize, child: H) -> LoroResult<H> {
|
||||
|
@ -3118,7 +3136,7 @@ impl MovableListHandler {
|
|||
index: pos,
|
||||
value: v,
|
||||
},
|
||||
&inner.state,
|
||||
&inner.doc,
|
||||
)?;
|
||||
|
||||
child.attach(txn, inner, container_id)
|
||||
|
@ -3179,7 +3197,7 @@ impl MovableListHandler {
|
|||
1,
|
||||
))),
|
||||
EventHint::DeleteList(DeleteSpan::new(user_pos as isize, 1)),
|
||||
&inner.state,
|
||||
&inner.doc,
|
||||
)?;
|
||||
}
|
||||
|
||||
|
@ -3254,9 +3272,8 @@ impl MovableListHandler {
|
|||
pub fn get_deep_value_with_id(&self) -> LoroValue {
|
||||
let inner = self.inner.try_attached_state().unwrap();
|
||||
inner
|
||||
.doc
|
||||
.state
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.try_lock()
|
||||
.unwrap()
|
||||
.get_container_deep_value_with_id(inner.container_idx, None)
|
||||
|
@ -3520,7 +3537,7 @@ impl MapHandler {
|
|||
key: key.into(),
|
||||
value: Some(value.clone()),
|
||||
},
|
||||
&inner.state,
|
||||
&inner.doc,
|
||||
)
|
||||
}),
|
||||
}
|
||||
|
@ -3556,7 +3573,7 @@ impl MapHandler {
|
|||
key: key.into(),
|
||||
value: Some(value.clone()),
|
||||
},
|
||||
&inner.state,
|
||||
&inner.doc,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -3594,7 +3611,7 @@ impl MapHandler {
|
|||
key: key.into(),
|
||||
value: Some(LoroValue::Container(container_id.clone())),
|
||||
},
|
||||
&inner.state,
|
||||
&inner.doc,
|
||||
)?;
|
||||
|
||||
child.attach(txn, inner, container_id)
|
||||
|
@ -3623,7 +3640,7 @@ impl MapHandler {
|
|||
key: key.into(),
|
||||
value: None,
|
||||
},
|
||||
&inner.state,
|
||||
&inner.doc,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -3872,11 +3889,10 @@ impl MapHandler {
|
|||
|
||||
#[inline(always)]
|
||||
fn with_txn<R>(
|
||||
txn: &Weak<Mutex<Option<Transaction>>>,
|
||||
txn: &Arc<Mutex<Option<Transaction>>>,
|
||||
f: impl FnOnce(&mut Transaction) -> LoroResult<R>,
|
||||
) -> LoroResult<R> {
|
||||
let mutex = &txn.upgrade().unwrap();
|
||||
let mut txn = mutex.try_lock().unwrap();
|
||||
let mut txn = txn.try_lock().unwrap();
|
||||
match &mut *txn {
|
||||
Some(t) => f(t),
|
||||
None => Err(LoroError::AutoCommitNotStarted),
|
||||
|
@ -3935,7 +3951,7 @@ pub mod counter {
|
|||
inner.container_idx,
|
||||
crate::op::RawOpContent::Counter(n),
|
||||
EventHint::Counter(n),
|
||||
&inner.state,
|
||||
&inner.doc,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -4029,6 +4045,13 @@ pub mod counter {
|
|||
MaybeDetached::Detached(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn doc(&self) -> Option<crate::LoroDoc> {
|
||||
match &self.inner {
|
||||
MaybeDetached::Detached(_) => None,
|
||||
MaybeDetached::Attached(a) => Some(a.doc()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4036,9 +4059,9 @@ pub mod counter {
|
|||
mod test {
|
||||
|
||||
use super::{HandlerTrait, TextDelta};
|
||||
use crate::loro::LoroDoc;
|
||||
use crate::state::TreeParentId;
|
||||
use crate::version::Frontiers;
|
||||
use crate::LoroDoc;
|
||||
use crate::{fx_map, ToJson};
|
||||
use loro_common::ID;
|
||||
use serde_json::json;
|
||||
|
|
|
@ -257,6 +257,13 @@ impl HandlerTrait for TreeHandler {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn doc(&self) -> Option<crate::LoroDoc> {
|
||||
match &self.inner {
|
||||
MaybeDetached::Detached(_) => None,
|
||||
MaybeDetached::Attached(a) => Some(a.doc()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for TreeHandler {
|
||||
|
@ -309,7 +316,7 @@ impl TreeHandler {
|
|||
old_index: index
|
||||
},
|
||||
}]),
|
||||
&inner.state,
|
||||
&inner.doc,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -414,7 +421,7 @@ impl TreeHandler {
|
|||
position: position.clone(),
|
||||
},
|
||||
}]),
|
||||
&inner.state,
|
||||
&inner.doc,
|
||||
)?;
|
||||
|
||||
Ok(self
|
||||
|
@ -492,7 +499,7 @@ impl TreeHandler {
|
|||
old_index: self.get_index_by_tree_id(&target).unwrap(),
|
||||
},
|
||||
}]),
|
||||
&inner.state,
|
||||
&inner.doc,
|
||||
)
|
||||
})?;
|
||||
Ok(true)
|
||||
|
@ -667,7 +674,7 @@ impl TreeHandler {
|
|||
position,
|
||||
},
|
||||
}]),
|
||||
&inner.state,
|
||||
&inner.doc,
|
||||
)?;
|
||||
Ok(tree_id)
|
||||
}
|
||||
|
@ -700,7 +707,7 @@ impl TreeHandler {
|
|||
old_index,
|
||||
},
|
||||
}]),
|
||||
&inner.state,
|
||||
&inner.doc,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,8 @@ use tracing::trace;
|
|||
use crate::handler::{
|
||||
Handler, ListHandler, MapHandler, MovableListHandler, TextHandler, TreeHandler, ValueOrHandler,
|
||||
};
|
||||
use crate::loro::LoroDoc;
|
||||
|
||||
use crate::LoroDoc;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -708,7 +709,6 @@ impl PathValue for LoroValue {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::loro::LoroDoc;
|
||||
|
||||
#[test]
|
||||
fn test_parse_jsonpath() -> Result<(), JsonPathError> {
|
||||
|
|
|
@ -110,7 +110,26 @@ pub use version::VersionVector;
|
|||
/// `LoroApp::detach()` separates [AppState] from [OpLog]. In this mode,
|
||||
/// updates to [OpLog] won't affect [AppState], while updates to [AppState]
|
||||
/// will continue to affect [OpLog].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LoroDoc {
|
||||
inner: Arc<LoroDocInner>,
|
||||
}
|
||||
|
||||
impl LoroDoc {
|
||||
pub(crate) fn from_inner(inner: Arc<LoroDocInner>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for LoroDoc {
|
||||
type Target = LoroDocInner;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LoroDocInner {
|
||||
oplog: Arc<Mutex<OpLog>>,
|
||||
state: Arc<Mutex<DocState>>,
|
||||
arena: SharedArena,
|
||||
|
|
|
@ -15,7 +15,7 @@ use std::{
|
|||
AtomicBool,
|
||||
Ordering::{Acquire, Release},
|
||||
},
|
||||
Arc, Mutex, Weak,
|
||||
Arc, Mutex,
|
||||
},
|
||||
};
|
||||
use tracing::{debug_span, info, info_span, instrument, warn};
|
||||
|
@ -45,17 +45,16 @@ use crate::{
|
|||
oplog::{loro_dag::FrontiersNotIncluded, OpLog},
|
||||
state::DocState,
|
||||
subscription::{LocalUpdateCallback, Observer, Subscriber},
|
||||
txn::Transaction,
|
||||
undo::DiffBatch,
|
||||
utils::subscription::{SubscriberSetWithQueue, Subscription},
|
||||
version::{shrink_frontiers, Frontiers, ImVersionVector, VersionRange, VersionVectorDiff},
|
||||
ChangeMeta, DocDiff, HandlerTrait, InternalString, ListHandler, LoroError, MapHandler,
|
||||
ChangeMeta, DocDiff, HandlerTrait, InternalString, ListHandler, LoroDoc, LoroError, MapHandler,
|
||||
VersionVector,
|
||||
};
|
||||
|
||||
pub use crate::encoding::ExportMode;
|
||||
pub use crate::state::analyzer::{ContainerAnalysisInfo, DocAnalysis};
|
||||
pub(crate) use crate::LoroDoc;
|
||||
pub(crate) use crate::LoroDocInner;
|
||||
|
||||
impl Default for LoroDoc {
|
||||
fn default() -> Self {
|
||||
|
@ -63,7 +62,7 @@ impl Default for LoroDoc {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for LoroDoc {
|
||||
impl std::fmt::Debug for LoroDocInner {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("LoroDoc")
|
||||
.field("config", &self.config)
|
||||
|
@ -77,23 +76,25 @@ impl LoroDoc {
|
|||
pub fn new() -> Self {
|
||||
let oplog = OpLog::new();
|
||||
let arena = oplog.arena.clone();
|
||||
let global_txn = Arc::new(Mutex::new(None));
|
||||
let config: Configure = oplog.configure.clone();
|
||||
// share arena
|
||||
let state = DocState::new_arc(arena.clone(), Arc::downgrade(&global_txn), config.clone());
|
||||
Self {
|
||||
oplog: Arc::new(Mutex::new(oplog)),
|
||||
state,
|
||||
config,
|
||||
detached: AtomicBool::new(false),
|
||||
auto_commit: AtomicBool::new(false),
|
||||
observer: Arc::new(Observer::new(arena.clone())),
|
||||
diff_calculator: Arc::new(Mutex::new(DiffCalculator::new(true))),
|
||||
txn: global_txn,
|
||||
arena,
|
||||
local_update_subs: SubscriberSetWithQueue::new(),
|
||||
peer_id_change_subs: SubscriberSetWithQueue::new(),
|
||||
}
|
||||
let global_txn = Arc::new(Mutex::new(None));
|
||||
let inner = Arc::new_cyclic(|w| {
|
||||
let state = DocState::new_arc(w.clone(), arena.clone(), config.clone());
|
||||
LoroDocInner {
|
||||
oplog: Arc::new(Mutex::new(oplog)),
|
||||
state,
|
||||
config,
|
||||
detached: AtomicBool::new(false),
|
||||
auto_commit: AtomicBool::new(false),
|
||||
observer: Arc::new(Observer::new(arena.clone())),
|
||||
diff_calculator: Arc::new(Mutex::new(DiffCalculator::new(true))),
|
||||
txn: global_txn,
|
||||
arena,
|
||||
local_update_subs: SubscriberSetWithQueue::new(),
|
||||
peer_id_change_subs: SubscriberSetWithQueue::new(),
|
||||
}
|
||||
});
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
pub fn fork(&self) -> Self {
|
||||
|
@ -113,30 +114,6 @@ impl LoroDoc {
|
|||
doc
|
||||
}
|
||||
|
||||
/// Set whether to record the timestamp of each change. Default is `false`.
|
||||
///
|
||||
/// If enabled, the Unix timestamp will be recorded for each change automatically.
|
||||
///
|
||||
/// You can also set each timestamp manually when you commit a change.
|
||||
/// The timestamp manually set will override the automatic one.
|
||||
///
|
||||
/// NOTE: Timestamps are forced to be in ascending order.
|
||||
/// If you commit a new change with a timestamp that is less than the existing one,
|
||||
/// the largest existing timestamp will be used instead.
|
||||
#[inline]
|
||||
pub fn set_record_timestamp(&self, record: bool) {
|
||||
self.config.set_record_timestamp(record);
|
||||
}
|
||||
|
||||
/// Set the interval of mergeable changes, in seconds.
|
||||
///
|
||||
/// If two continuous local changes are within the interval, they will be merged into one change.
|
||||
/// The default value is 1000 seconds.
|
||||
#[inline]
|
||||
pub fn set_change_merge_interval(&self, interval: i64) {
|
||||
self.config.set_merge_interval(interval);
|
||||
}
|
||||
|
||||
/// Enables editing of the document in detached mode.
|
||||
///
|
||||
/// By default, the document cannot be edited in detached mode (after calling
|
||||
|
@ -161,25 +138,6 @@ impl LoroDoc {
|
|||
}
|
||||
}
|
||||
|
||||
/// Renews the PeerID for the document.
|
||||
pub(crate) fn renew_peer_id(&self) {
|
||||
let peer_id = DefaultRandom.next_u64();
|
||||
self.set_peer_id(peer_id).unwrap();
|
||||
}
|
||||
|
||||
pub fn can_edit(&self) -> bool {
|
||||
!self.is_detached() || self.config.detached_editing()
|
||||
}
|
||||
|
||||
pub fn is_detached_editing_enabled(&self) -> bool {
|
||||
self.config.detached_editing()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn config_text_style(&self, text_style: StyleConfigMap) {
|
||||
*self.config.text_style_config.try_write().unwrap() = text_style;
|
||||
}
|
||||
|
||||
/// Create a doc with auto commit enabled.
|
||||
#[inline]
|
||||
pub fn new_auto_commit() -> Self {
|
||||
|
@ -188,57 +146,6 @@ impl LoroDoc {
|
|||
doc
|
||||
}
|
||||
|
||||
pub fn from_snapshot(bytes: &[u8]) -> LoroResult<Self> {
|
||||
let doc = Self::new();
|
||||
let ParsedHeaderAndBody { mode, body, .. } = parse_header_and_body(bytes, true)?;
|
||||
if mode.is_snapshot() {
|
||||
decode_snapshot(&doc, mode, body)?;
|
||||
Ok(doc)
|
||||
} else {
|
||||
Err(LoroError::DecodeError(
|
||||
"Invalid encode mode".to_string().into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Is the document empty? (no ops)
|
||||
#[inline(always)]
|
||||
pub fn can_reset_with_snapshot(&self) -> bool {
|
||||
let oplog = self.oplog.try_lock().unwrap();
|
||||
if oplog.batch_importing {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.is_detached() {
|
||||
return false;
|
||||
}
|
||||
|
||||
oplog.is_empty() && self.state.try_lock().unwrap().can_import_snapshot()
|
||||
}
|
||||
|
||||
/// Whether [OpLog] and [DocState] are detached.
|
||||
///
|
||||
/// If so, the document is in readonly mode by default and importing will not change the state of the document.
|
||||
/// It also doesn't change the version of the [DocState]. The changes will be recorded into [OpLog] only.
|
||||
/// You need to call `checkout` to make it take effect.
|
||||
#[inline(always)]
|
||||
pub fn is_detached(&self) -> bool {
|
||||
self.detached.load(Acquire)
|
||||
}
|
||||
|
||||
pub(crate) fn set_detached(&self, detached: bool) {
|
||||
self.detached.store(detached, Release);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn peer_id(&self) -> PeerID {
|
||||
self.state
|
||||
.try_lock()
|
||||
.unwrap()
|
||||
.peer
|
||||
.load(std::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn set_peer_id(&self, peer: PeerID) -> LoroResult<()> {
|
||||
if peer == PeerID::MAX {
|
||||
|
@ -280,22 +187,10 @@ impl LoroDoc {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn detach(&self) {
|
||||
self.commit_then_stop();
|
||||
self.set_detached(true);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn attach(&self) {
|
||||
self.checkout_to_latest()
|
||||
}
|
||||
|
||||
/// Get the timestamp of the current state.
|
||||
/// It's the last edit time of the [DocState].
|
||||
pub fn state_timestamp(&self) -> Timestamp {
|
||||
let f = &self.state.try_lock().unwrap().frontiers;
|
||||
self.oplog.try_lock().unwrap().get_timestamp_of_version(f)
|
||||
/// Renews the PeerID for the document.
|
||||
pub(crate) fn renew_peer_id(&self) {
|
||||
let peer_id = DefaultRandom.next_u64();
|
||||
self.set_peer_id(peer_id).unwrap();
|
||||
}
|
||||
|
||||
/// Commit the cumulative auto commit transaction.
|
||||
|
@ -372,9 +267,110 @@ impl LoroDoc {
|
|||
}
|
||||
}
|
||||
|
||||
/// Set whether to record the timestamp of each change. Default is `false`.
|
||||
///
|
||||
/// If enabled, the Unix timestamp will be recorded for each change automatically.
|
||||
///
|
||||
/// You can also set each timestamp manually when you commit a change.
|
||||
/// The timestamp manually set will override the automatic one.
|
||||
///
|
||||
/// NOTE: Timestamps are forced to be in ascending order.
|
||||
/// If you commit a new change with a timestamp that is less than the existing one,
|
||||
/// the largest existing timestamp will be used instead.
|
||||
#[inline]
|
||||
pub(crate) fn get_global_txn(&self) -> Weak<Mutex<Option<Transaction>>> {
|
||||
Arc::downgrade(&self.txn)
|
||||
pub fn set_record_timestamp(&self, record: bool) {
|
||||
self.config.set_record_timestamp(record);
|
||||
}
|
||||
|
||||
/// Set the interval of mergeable changes, in seconds.
|
||||
///
|
||||
/// If two continuous local changes are within the interval, they will be merged into one change.
|
||||
/// The default value is 1000 seconds.
|
||||
#[inline]
|
||||
pub fn set_change_merge_interval(&self, interval: i64) {
|
||||
self.config.set_merge_interval(interval);
|
||||
}
|
||||
|
||||
pub fn can_edit(&self) -> bool {
|
||||
!self.is_detached() || self.config.detached_editing()
|
||||
}
|
||||
|
||||
pub fn is_detached_editing_enabled(&self) -> bool {
|
||||
self.config.detached_editing()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn config_text_style(&self, text_style: StyleConfigMap) {
|
||||
*self.config.text_style_config.try_write().unwrap() = text_style;
|
||||
}
|
||||
|
||||
pub fn from_snapshot(bytes: &[u8]) -> LoroResult<Self> {
|
||||
let doc = Self::new();
|
||||
let ParsedHeaderAndBody { mode, body, .. } = parse_header_and_body(bytes, true)?;
|
||||
if mode.is_snapshot() {
|
||||
decode_snapshot(&doc, mode, body)?;
|
||||
Ok(doc)
|
||||
} else {
|
||||
Err(LoroError::DecodeError(
|
||||
"Invalid encode mode".to_string().into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Is the document empty? (no ops)
|
||||
#[inline(always)]
|
||||
pub fn can_reset_with_snapshot(&self) -> bool {
|
||||
let oplog = self.oplog.try_lock().unwrap();
|
||||
if oplog.batch_importing {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.is_detached() {
|
||||
return false;
|
||||
}
|
||||
|
||||
oplog.is_empty() && self.state.try_lock().unwrap().can_import_snapshot()
|
||||
}
|
||||
|
||||
/// Whether [OpLog] and [DocState] are detached.
|
||||
///
|
||||
/// If so, the document is in readonly mode by default and importing will not change the state of the document.
|
||||
/// It also doesn't change the version of the [DocState]. The changes will be recorded into [OpLog] only.
|
||||
/// You need to call `checkout` to make it take effect.
|
||||
#[inline(always)]
|
||||
pub fn is_detached(&self) -> bool {
|
||||
self.detached.load(Acquire)
|
||||
}
|
||||
|
||||
pub(crate) fn set_detached(&self, detached: bool) {
|
||||
self.detached.store(detached, Release);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn peer_id(&self) -> PeerID {
|
||||
self.state
|
||||
.try_lock()
|
||||
.unwrap()
|
||||
.peer
|
||||
.load(std::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn detach(&self) {
|
||||
self.commit_then_stop();
|
||||
self.set_detached(true);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn attach(&self) {
|
||||
self.checkout_to_latest()
|
||||
}
|
||||
|
||||
/// Get the timestamp of the current state.
|
||||
/// It's the last edit time of the [DocState].
|
||||
pub fn state_timestamp(&self) -> Timestamp {
|
||||
let f = &self.state.try_lock().unwrap().frontiers;
|
||||
self.oplog.try_lock().unwrap().get_timestamp_of_version(f)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -617,9 +613,7 @@ impl LoroDoc {
|
|||
if let LoroValue::Container(c) = value {
|
||||
Some(ValueOrHandler::Handler(Handler::new_attached(
|
||||
c.clone(),
|
||||
self.arena.clone(),
|
||||
self.get_global_txn(),
|
||||
Arc::downgrade(&self.state),
|
||||
self.inner.clone(),
|
||||
)))
|
||||
} else {
|
||||
Some(ValueOrHandler::Value(value))
|
||||
|
@ -635,12 +629,7 @@ impl LoroDoc {
|
|||
#[inline]
|
||||
pub fn get_handler(&self, id: ContainerID) -> Handler {
|
||||
self.assert_container_exists(&id);
|
||||
Handler::new_attached(
|
||||
id,
|
||||
self.arena.clone(),
|
||||
self.get_global_txn(),
|
||||
Arc::downgrade(&self.state),
|
||||
)
|
||||
Handler::new_attached(id, self.inner.clone())
|
||||
}
|
||||
|
||||
/// id can be a str, ContainerID, or ContainerIdRaw.
|
||||
|
@ -649,14 +638,9 @@ impl LoroDoc {
|
|||
pub fn get_text<I: IntoContainerId>(&self, id: I) -> TextHandler {
|
||||
let id = id.into_container_id(&self.arena, ContainerType::Text);
|
||||
self.assert_container_exists(&id);
|
||||
Handler::new_attached(
|
||||
id,
|
||||
self.arena.clone(),
|
||||
self.get_global_txn(),
|
||||
Arc::downgrade(&self.state),
|
||||
)
|
||||
.into_text()
|
||||
.unwrap()
|
||||
Handler::new_attached(id, self.inner.clone())
|
||||
.into_text()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// id can be a str, ContainerID, or ContainerIdRaw.
|
||||
|
@ -665,14 +649,9 @@ impl LoroDoc {
|
|||
pub fn get_list<I: IntoContainerId>(&self, id: I) -> ListHandler {
|
||||
let id = id.into_container_id(&self.arena, ContainerType::List);
|
||||
self.assert_container_exists(&id);
|
||||
Handler::new_attached(
|
||||
id,
|
||||
self.arena.clone(),
|
||||
self.get_global_txn(),
|
||||
Arc::downgrade(&self.state),
|
||||
)
|
||||
.into_list()
|
||||
.unwrap()
|
||||
Handler::new_attached(id, self.inner.clone())
|
||||
.into_list()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// id can be a str, ContainerID, or ContainerIdRaw.
|
||||
|
@ -681,14 +660,9 @@ impl LoroDoc {
|
|||
pub fn get_movable_list<I: IntoContainerId>(&self, id: I) -> MovableListHandler {
|
||||
let id = id.into_container_id(&self.arena, ContainerType::MovableList);
|
||||
self.assert_container_exists(&id);
|
||||
Handler::new_attached(
|
||||
id,
|
||||
self.arena.clone(),
|
||||
self.get_global_txn(),
|
||||
Arc::downgrade(&self.state),
|
||||
)
|
||||
.into_movable_list()
|
||||
.unwrap()
|
||||
Handler::new_attached(id, self.inner.clone())
|
||||
.into_movable_list()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// id can be a str, ContainerID, or ContainerIdRaw.
|
||||
|
@ -697,14 +671,9 @@ impl LoroDoc {
|
|||
pub fn get_map<I: IntoContainerId>(&self, id: I) -> MapHandler {
|
||||
let id = id.into_container_id(&self.arena, ContainerType::Map);
|
||||
self.assert_container_exists(&id);
|
||||
Handler::new_attached(
|
||||
id,
|
||||
self.arena.clone(),
|
||||
self.get_global_txn(),
|
||||
Arc::downgrade(&self.state),
|
||||
)
|
||||
.into_map()
|
||||
.unwrap()
|
||||
Handler::new_attached(id, self.inner.clone())
|
||||
.into_map()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// id can be a str, ContainerID, or ContainerIdRaw.
|
||||
|
@ -713,14 +682,9 @@ impl LoroDoc {
|
|||
pub fn get_tree<I: IntoContainerId>(&self, id: I) -> TreeHandler {
|
||||
let id = id.into_container_id(&self.arena, ContainerType::Tree);
|
||||
self.assert_container_exists(&id);
|
||||
Handler::new_attached(
|
||||
id,
|
||||
self.arena.clone(),
|
||||
self.get_global_txn(),
|
||||
Arc::downgrade(&self.state),
|
||||
)
|
||||
.into_tree()
|
||||
.unwrap()
|
||||
Handler::new_attached(id, self.inner.clone())
|
||||
.into_tree()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[cfg(feature = "counter")]
|
||||
|
@ -730,14 +694,9 @@ impl LoroDoc {
|
|||
) -> crate::handler::counter::CounterHandler {
|
||||
let id = id.into_container_id(&self.arena, ContainerType::Counter);
|
||||
self.assert_container_exists(&id);
|
||||
Handler::new_attached(
|
||||
id,
|
||||
self.arena.clone(),
|
||||
self.get_global_txn(),
|
||||
Arc::downgrade(&self.state),
|
||||
)
|
||||
.into_counter()
|
||||
.unwrap()
|
||||
Handler::new_attached(id, self.inner.clone())
|
||||
.into_counter()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn assert_container_exists(&self, id: &ContainerID) {
|
||||
|
|
|
@ -29,9 +29,8 @@ use crate::{
|
|||
handler::ValueOrHandler,
|
||||
id::PeerID,
|
||||
op::{Op, RawOp},
|
||||
txn::Transaction,
|
||||
version::Frontiers,
|
||||
ContainerDiff, ContainerType, DocDiff, InternalString, LoroValue, OpLog,
|
||||
ContainerDiff, ContainerType, DocDiff, InternalString, LoroDocInner, LoroValue, OpLog,
|
||||
};
|
||||
|
||||
pub(crate) mod analyzer;
|
||||
|
@ -71,8 +70,7 @@ pub struct DocState {
|
|||
pub(super) arena: SharedArena,
|
||||
pub(crate) config: Configure,
|
||||
// resolve event stuff
|
||||
weak_state: Weak<Mutex<DocState>>,
|
||||
global_txn: Weak<Mutex<Option<Transaction>>>,
|
||||
doc: Weak<LoroDocInner>,
|
||||
// txn related stuff
|
||||
in_txn: bool,
|
||||
changed_idx_in_txn: FxHashSet<ContainerIdx>,
|
||||
|
@ -99,9 +97,7 @@ pub(crate) struct ContainerCreationContext<'a> {
|
|||
|
||||
pub(crate) struct DiffApplyContext<'a> {
|
||||
pub mode: DiffMode,
|
||||
pub arena: &'a SharedArena,
|
||||
pub txn: &'a Weak<Mutex<Option<Transaction>>>,
|
||||
pub state: &'a Weak<Mutex<DocState>>,
|
||||
pub doc: &'a Weak<LoroDocInner>,
|
||||
}
|
||||
|
||||
pub(crate) trait FastStateSnapshot {
|
||||
|
@ -135,12 +131,7 @@ pub(crate) trait ContainerState {
|
|||
|
||||
fn apply_local_op(&mut self, raw_op: &RawOp, op: &Op) -> LoroResult<ApplyLocalOpReturn>;
|
||||
/// Convert a state to a diff, such that an empty state will be transformed into the same as this state when it's applied.
|
||||
fn to_diff(
|
||||
&mut self,
|
||||
arena: &SharedArena,
|
||||
txn: &Weak<Mutex<Option<Transaction>>>,
|
||||
state: &Weak<Mutex<DocState>>,
|
||||
) -> Diff;
|
||||
fn to_diff(&mut self, doc: &Weak<LoroDocInner>) -> Diff;
|
||||
|
||||
fn get_value(&mut self) -> LoroValue;
|
||||
|
||||
|
@ -213,13 +204,8 @@ impl<T: ContainerState> ContainerState for Box<T> {
|
|||
}
|
||||
|
||||
#[doc = r" Convert a state to a diff, such that an empty state will be transformed into the same as this state when it's applied."]
|
||||
fn to_diff(
|
||||
&mut self,
|
||||
arena: &SharedArena,
|
||||
txn: &Weak<Mutex<Option<Transaction>>>,
|
||||
state: &Weak<Mutex<DocState>>,
|
||||
) -> Diff {
|
||||
self.as_mut().to_diff(arena, txn, state)
|
||||
fn to_diff(&mut self, doc: &Weak<LoroDocInner>) -> Diff {
|
||||
self.as_mut().to_diff(doc)
|
||||
}
|
||||
|
||||
fn get_value(&mut self) -> LoroValue {
|
||||
|
@ -366,53 +352,48 @@ impl State {
|
|||
impl DocState {
|
||||
#[inline]
|
||||
pub fn new_arc(
|
||||
doc: Weak<LoroDocInner>,
|
||||
arena: SharedArena,
|
||||
global_txn: Weak<Mutex<Option<Transaction>>>,
|
||||
config: Configure,
|
||||
) -> Arc<Mutex<Self>> {
|
||||
let peer = DefaultRandom.next_u64();
|
||||
// TODO: maybe we should switch to certain version in oplog?
|
||||
Arc::new_cyclic(|weak| {
|
||||
let peer = Arc::new(AtomicU64::new(peer));
|
||||
Mutex::new(Self {
|
||||
store: ContainerStore::new(arena.clone(), config.clone(), peer.clone()),
|
||||
peer,
|
||||
arena,
|
||||
frontiers: Frontiers::default(),
|
||||
weak_state: weak.clone(),
|
||||
config,
|
||||
global_txn,
|
||||
in_txn: false,
|
||||
changed_idx_in_txn: FxHashSet::default(),
|
||||
event_recorder: Default::default(),
|
||||
dead_containers_cache: Default::default(),
|
||||
})
|
||||
})
|
||||
|
||||
let peer = Arc::new(AtomicU64::new(peer));
|
||||
Arc::new(Mutex::new(Self {
|
||||
store: ContainerStore::new(arena.clone(), config.clone(), peer.clone()),
|
||||
peer,
|
||||
arena,
|
||||
frontiers: Frontiers::default(),
|
||||
doc,
|
||||
config,
|
||||
in_txn: false,
|
||||
changed_idx_in_txn: FxHashSet::default(),
|
||||
event_recorder: Default::default(),
|
||||
dead_containers_cache: Default::default(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn fork_with_new_peer_id(
|
||||
&mut self,
|
||||
doc: Weak<LoroDocInner>,
|
||||
arena: SharedArena,
|
||||
global_txn: Weak<Mutex<Option<Transaction>>>,
|
||||
config: Configure,
|
||||
) -> Arc<Mutex<Self>> {
|
||||
let peer = Arc::new(AtomicU64::new(DefaultRandom.next_u64()));
|
||||
let store = self.store.fork(arena.clone(), peer.clone(), config.clone());
|
||||
Arc::new_cyclic(move |weak| {
|
||||
Mutex::new(Self {
|
||||
peer,
|
||||
frontiers: self.frontiers.clone(),
|
||||
store,
|
||||
arena,
|
||||
config,
|
||||
weak_state: weak.clone(),
|
||||
global_txn,
|
||||
in_txn: false,
|
||||
changed_idx_in_txn: FxHashSet::default(),
|
||||
event_recorder: Default::default(),
|
||||
dead_containers_cache: Default::default(),
|
||||
})
|
||||
})
|
||||
Arc::new(Mutex::new(Self {
|
||||
peer,
|
||||
frontiers: self.frontiers.clone(),
|
||||
store,
|
||||
arena,
|
||||
config,
|
||||
doc,
|
||||
in_txn: false,
|
||||
changed_idx_in_txn: FxHashSet::default(),
|
||||
event_recorder: Default::default(),
|
||||
dead_containers_cache: Default::default(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn start_recording(&mut self) {
|
||||
|
@ -590,8 +571,7 @@ impl DocState {
|
|||
continue;
|
||||
}
|
||||
|
||||
let external_diff =
|
||||
state.to_diff(&self.arena, &self.global_txn, &self.weak_state);
|
||||
let external_diff = state.to_diff(&self.doc);
|
||||
trigger_on_new_container(
|
||||
&external_diff,
|
||||
|cid| {
|
||||
|
@ -618,8 +598,7 @@ impl DocState {
|
|||
crate::event::DiffVariant::None => {
|
||||
if is_recording {
|
||||
let state = self.store.get_or_create_mut(diff.idx);
|
||||
let extern_diff =
|
||||
state.to_diff(&self.arena, &self.global_txn, &self.weak_state);
|
||||
let extern_diff = state.to_diff(&self.doc);
|
||||
trigger_on_new_container(
|
||||
&extern_diff,
|
||||
|cid| {
|
||||
|
@ -645,20 +624,16 @@ impl DocState {
|
|||
internal_diff.into_internal().unwrap(),
|
||||
DiffApplyContext {
|
||||
mode: diff.diff_mode,
|
||||
arena: &self.arena,
|
||||
txn: &self.global_txn,
|
||||
state: &self.weak_state,
|
||||
doc: &self.doc,
|
||||
},
|
||||
);
|
||||
state.to_diff(&self.arena, &self.global_txn, &self.weak_state)
|
||||
state.to_diff(&self.doc)
|
||||
} else {
|
||||
state.apply_diff_and_convert(
|
||||
internal_diff.into_internal().unwrap(),
|
||||
DiffApplyContext {
|
||||
mode: diff.diff_mode,
|
||||
arena: &self.arena,
|
||||
txn: &self.global_txn,
|
||||
state: &self.weak_state,
|
||||
doc: &self.doc,
|
||||
},
|
||||
)
|
||||
};
|
||||
|
@ -675,9 +650,7 @@ impl DocState {
|
|||
internal_diff.into_internal().unwrap(),
|
||||
DiffApplyContext {
|
||||
mode: diff.diff_mode,
|
||||
arena: &self.arena,
|
||||
txn: &self.global_txn,
|
||||
state: &self.weak_state,
|
||||
doc: &self.doc,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -701,7 +674,7 @@ impl DocState {
|
|||
continue;
|
||||
}
|
||||
|
||||
let external_diff = state.to_diff(&self.arena, &self.global_txn, &self.weak_state);
|
||||
let external_diff = state.to_diff(&self.doc);
|
||||
trigger_on_new_container(
|
||||
&external_diff,
|
||||
|cid| {
|
||||
|
@ -892,7 +865,7 @@ impl DocState {
|
|||
peer: self.peer.load(Ordering::Relaxed),
|
||||
},
|
||||
)
|
||||
.to_diff(&self.arena, &self.global_txn, &self.weak_state)
|
||||
.to_diff(&self.doc)
|
||||
.into(),
|
||||
diff_mode: DiffMode::Checkout,
|
||||
})
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
use std::sync::{Mutex, Weak};
|
||||
use std::sync::Weak;
|
||||
|
||||
use loro_common::{ContainerID, LoroError, LoroResult, LoroValue};
|
||||
|
||||
use crate::{
|
||||
arena::SharedArena,
|
||||
configure::Configure,
|
||||
container::idx::ContainerIdx,
|
||||
encoding::{StateSnapshotDecodeContext, StateSnapshotEncoder},
|
||||
event::{Diff, Index, InternalDiff},
|
||||
op::{Op, RawOp, RawOpContent},
|
||||
txn::Transaction,
|
||||
DocState,
|
||||
LoroDocInner,
|
||||
};
|
||||
|
||||
use super::{ApplyLocalOpReturn, ContainerState, DiffApplyContext};
|
||||
|
@ -64,12 +62,7 @@ impl ContainerState for CounterState {
|
|||
}
|
||||
|
||||
#[doc = " Convert a state to a diff, such that an empty state will be transformed into the same as this state when it\'s applied."]
|
||||
fn to_diff(
|
||||
&mut self,
|
||||
_arena: &SharedArena,
|
||||
_txn: &Weak<Mutex<Option<Transaction>>>,
|
||||
_state: &Weak<Mutex<DocState>>,
|
||||
) -> Diff {
|
||||
fn to_diff(&mut self, _doc: &Weak<LoroDocInner>) -> Diff {
|
||||
Diff::Counter(self.value)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,20 +1,14 @@
|
|||
use std::{
|
||||
io::Write,
|
||||
ops::RangeBounds,
|
||||
sync::{Mutex, Weak},
|
||||
};
|
||||
use std::{io::Write, ops::RangeBounds, sync::Weak};
|
||||
|
||||
use super::{ApplyLocalOpReturn, ContainerState, DiffApplyContext, FastStateSnapshot};
|
||||
use crate::{
|
||||
arena::SharedArena,
|
||||
configure::Configure,
|
||||
container::{idx::ContainerIdx, list::list_op::ListOp, ContainerID},
|
||||
encoding::{EncodeMode, StateSnapshotDecodeContext, StateSnapshotEncoder},
|
||||
event::{Diff, Index, InternalDiff, ListDiff},
|
||||
handler::ValueOrHandler,
|
||||
op::{ListSlice, Op, RawOp, RawOpContent},
|
||||
txn::Transaction,
|
||||
DocState, LoroValue,
|
||||
LoroDocInner, LoroValue,
|
||||
};
|
||||
|
||||
use fxhash::FxHashMap;
|
||||
|
@ -377,15 +371,14 @@ impl ContainerState for ListState {
|
|||
fn apply_diff_and_convert(
|
||||
&mut self,
|
||||
diff: InternalDiff,
|
||||
DiffApplyContext {
|
||||
arena, txn, state, ..
|
||||
}: DiffApplyContext,
|
||||
DiffApplyContext { doc, .. }: DiffApplyContext,
|
||||
) -> Diff {
|
||||
let InternalDiff::ListRaw(delta) = diff else {
|
||||
unreachable!()
|
||||
};
|
||||
let mut ans: ListDiff = ListDiff::default();
|
||||
let mut index = 0;
|
||||
let doc = &doc.upgrade().unwrap();
|
||||
for span in delta.iter() {
|
||||
match span {
|
||||
crate::delta::DeltaItem::Retain { retain: len, .. } => {
|
||||
|
@ -397,7 +390,7 @@ impl ContainerState for ListState {
|
|||
match &value.values {
|
||||
either::Either::Left(range) => {
|
||||
for i in range.to_range() {
|
||||
let value = arena.get_value(i).unwrap();
|
||||
let value = doc.arena.get_value(i).unwrap();
|
||||
arr.push(value);
|
||||
}
|
||||
}
|
||||
|
@ -405,7 +398,7 @@ impl ContainerState for ListState {
|
|||
}
|
||||
for arr in ArrayVec::from_many(
|
||||
arr.iter()
|
||||
.map(|v| ValueOrHandler::from_value(v.clone(), arena, txn, state)),
|
||||
.map(|v| ValueOrHandler::from_value(v.clone(), doc)),
|
||||
) {
|
||||
ans.push_insert(arr, Default::default());
|
||||
}
|
||||
|
@ -423,7 +416,8 @@ impl ContainerState for ListState {
|
|||
Diff::List(ans)
|
||||
}
|
||||
|
||||
fn apply_diff(&mut self, diff: InternalDiff, DiffApplyContext { arena, .. }: DiffApplyContext) {
|
||||
fn apply_diff(&mut self, diff: InternalDiff, DiffApplyContext { doc, .. }: DiffApplyContext) {
|
||||
let doc = &doc.upgrade().unwrap();
|
||||
match diff {
|
||||
InternalDiff::ListRaw(delta) => {
|
||||
let mut index = 0;
|
||||
|
@ -437,7 +431,7 @@ impl ContainerState for ListState {
|
|||
match &value.values {
|
||||
either::Either::Left(range) => {
|
||||
for i in range.to_range() {
|
||||
let value = arena.get_value(i).unwrap();
|
||||
let value = doc.arena.get_value(i).unwrap();
|
||||
arr.push(value);
|
||||
}
|
||||
}
|
||||
|
@ -492,16 +486,12 @@ impl ContainerState for ListState {
|
|||
|
||||
#[doc = " Convert a state to a diff that when apply this diff on a empty state,"]
|
||||
#[doc = " the state will be the same as this state."]
|
||||
fn to_diff(
|
||||
&mut self,
|
||||
arena: &SharedArena,
|
||||
txn: &Weak<Mutex<Option<Transaction>>>,
|
||||
state: &Weak<Mutex<DocState>>,
|
||||
) -> Diff {
|
||||
fn to_diff(&mut self, doc: &Weak<LoroDocInner>) -> Diff {
|
||||
let doc = &doc.upgrade().unwrap();
|
||||
Diff::List(ListDiff::from_many(
|
||||
self.to_vec()
|
||||
.into_iter()
|
||||
.map(|v| ValueOrHandler::from_value(v, arena, txn, state)),
|
||||
.map(|v| ValueOrHandler::from_value(v, doc)),
|
||||
))
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
use std::{
|
||||
collections::BTreeMap,
|
||||
mem,
|
||||
sync::{Mutex, Weak},
|
||||
};
|
||||
use std::{collections::BTreeMap, mem, sync::Weak};
|
||||
|
||||
use fxhash::FxHashMap;
|
||||
use loro_common::{ContainerID, IdLp, LoroResult, PeerID};
|
||||
use rle::HasLength;
|
||||
|
||||
use crate::{
|
||||
arena::SharedArena,
|
||||
configure::Configure,
|
||||
container::{idx::ContainerIdx, map::MapSet},
|
||||
delta::{MapValue, ResolvedMapDelta, ResolvedMapValue},
|
||||
|
@ -18,8 +13,7 @@ use crate::{
|
|||
event::{Diff, Index, InternalDiff},
|
||||
handler::ValueOrHandler,
|
||||
op::{Op, RawOp, RawOpContent},
|
||||
txn::Transaction,
|
||||
DocState, InternalString, LoroValue,
|
||||
InternalString, LoroDocInner, LoroValue,
|
||||
};
|
||||
|
||||
use super::{ApplyLocalOpReturn, ContainerState, DiffApplyContext};
|
||||
|
@ -48,16 +42,12 @@ impl ContainerState for MapState {
|
|||
fn apply_diff_and_convert(
|
||||
&mut self,
|
||||
diff: InternalDiff,
|
||||
DiffApplyContext {
|
||||
arena,
|
||||
txn,
|
||||
state,
|
||||
mode,
|
||||
}: DiffApplyContext,
|
||||
DiffApplyContext { doc, mode }: DiffApplyContext,
|
||||
) -> Diff {
|
||||
let InternalDiff::Map(delta) = diff else {
|
||||
unreachable!()
|
||||
};
|
||||
let doc = &doc.upgrade().unwrap();
|
||||
let force = matches!(mode, DiffMode::Checkout | DiffMode::Linear);
|
||||
let mut resolved_delta = ResolvedMapDelta::new();
|
||||
for (key, value) in delta.updated.into_iter() {
|
||||
|
@ -88,9 +78,7 @@ impl ContainerState for MapState {
|
|||
key,
|
||||
ResolvedMapValue {
|
||||
idlp: IdLp::new(value.peer, value.lamp),
|
||||
value: value
|
||||
.value
|
||||
.map(|v| ValueOrHandler::from_value(v, arena, txn, state)),
|
||||
value: value.value.map(|v| ValueOrHandler::from_value(v, doc)),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -132,18 +120,13 @@ impl ContainerState for MapState {
|
|||
|
||||
#[doc = " Convert a state to a diff that when apply this diff on a empty state,"]
|
||||
#[doc = " the state will be the same as this state."]
|
||||
fn to_diff(
|
||||
&mut self,
|
||||
arena: &SharedArena,
|
||||
txn: &Weak<Mutex<Option<Transaction>>>,
|
||||
state: &Weak<Mutex<DocState>>,
|
||||
) -> Diff {
|
||||
fn to_diff(&mut self, doc: &Weak<LoroDocInner>) -> Diff {
|
||||
Diff::Map(ResolvedMapDelta {
|
||||
updated: self
|
||||
.map
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, ResolvedMapValue::from_map_value(v, arena, txn, state)))
|
||||
.map(|(k, v)| (k, ResolvedMapValue::from_map_value(v, doc)))
|
||||
.collect::<FxHashMap<_, _>>(),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use itertools::Itertools;
|
||||
use loro_delta::{array_vec::ArrayVec, DeltaRope, DeltaRopeBuilder};
|
||||
use serde_columnar::columnar;
|
||||
use std::sync::{Mutex, Weak};
|
||||
use std::sync::Weak;
|
||||
use tracing::{instrument, warn};
|
||||
|
||||
use fxhash::FxHashMap;
|
||||
|
@ -9,7 +9,6 @@ use generic_btree::BTree;
|
|||
use loro_common::{CompactIdLp, ContainerID, IdFull, IdLp, LoroResult, LoroValue, PeerID, ID};
|
||||
|
||||
use crate::{
|
||||
arena::SharedArena,
|
||||
configure::Configure,
|
||||
container::{idx::ContainerIdx, list::list_op::ListOp},
|
||||
delta::DeltaItem,
|
||||
|
@ -19,8 +18,7 @@ use crate::{
|
|||
handler::ValueOrHandler,
|
||||
op::{ListSlice, Op, RawOp},
|
||||
state::movable_list_state::inner::PushElemInfo,
|
||||
txn::Transaction,
|
||||
DocState, ListDiff,
|
||||
ListDiff, LoroDocInner,
|
||||
};
|
||||
|
||||
use self::{
|
||||
|
@ -1025,12 +1023,7 @@ impl ContainerState for MovableListState {
|
|||
fn apply_diff_and_convert(
|
||||
&mut self,
|
||||
diff: InternalDiff,
|
||||
DiffApplyContext {
|
||||
arena,
|
||||
txn,
|
||||
state,
|
||||
mode,
|
||||
}: DiffApplyContext,
|
||||
DiffApplyContext { doc, mode }: DiffApplyContext,
|
||||
) -> Diff {
|
||||
let InternalDiff::MovableList(mut diff) = diff else {
|
||||
unreachable!()
|
||||
|
@ -1123,6 +1116,7 @@ impl ContainerState for MovableListState {
|
|||
}
|
||||
|
||||
{
|
||||
let doc = &doc.upgrade().unwrap();
|
||||
// Apply element changes
|
||||
//
|
||||
// In this block, we need to handle the events generated from the following sources:
|
||||
|
@ -1160,7 +1154,7 @@ impl ContainerState for MovableListState {
|
|||
.delete(1)
|
||||
.insert(
|
||||
ArrayVec::from([ValueOrHandler::from_value(
|
||||
value, arena, txn, state,
|
||||
value, doc,
|
||||
)]),
|
||||
ListDeltaMeta { from_move: false },
|
||||
)
|
||||
|
@ -1191,7 +1185,7 @@ impl ContainerState for MovableListState {
|
|||
.retain(new_index, Default::default())
|
||||
.insert(
|
||||
ArrayVec::from([ValueOrHandler::from_value(
|
||||
new_value, arena, txn, state,
|
||||
new_value, doc,
|
||||
)]),
|
||||
ListDeltaMeta {
|
||||
from_move: (result.delete.is_some() && !value_updated)
|
||||
|
@ -1238,9 +1232,7 @@ impl ContainerState for MovableListState {
|
|||
&DeltaRopeBuilder::new()
|
||||
.retain(index, Default::default())
|
||||
.insert(
|
||||
ArrayVec::from([ValueOrHandler::from_value(
|
||||
value, arena, txn, state,
|
||||
)]),
|
||||
ArrayVec::from([ValueOrHandler::from_value(value, doc)]),
|
||||
ListDeltaMeta {
|
||||
from_move: (result.delete.is_some() && !value_updated)
|
||||
|| from_delete,
|
||||
|
@ -1350,18 +1342,14 @@ impl ContainerState for MovableListState {
|
|||
Ok(ans)
|
||||
}
|
||||
|
||||
fn to_diff(
|
||||
&mut self,
|
||||
arena: &SharedArena,
|
||||
txn: &Weak<Mutex<Option<Transaction>>>,
|
||||
state: &Weak<Mutex<DocState>>,
|
||||
) -> Diff {
|
||||
fn to_diff(&mut self, doc: &Weak<LoroDocInner>) -> Diff {
|
||||
let doc = &doc.upgrade().unwrap();
|
||||
Diff::List(
|
||||
DeltaRopeBuilder::new()
|
||||
.insert_many(
|
||||
self.to_vec()
|
||||
.into_iter()
|
||||
.map(|v| ValueOrHandler::from_value(v, arena, txn, state)),
|
||||
.map(|v| ValueOrHandler::from_value(v, doc)),
|
||||
Default::default(),
|
||||
)
|
||||
.build(),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::{
|
||||
ops::Range,
|
||||
sync::{Arc, Mutex, RwLock, Weak},
|
||||
sync::{Arc, RwLock, Weak},
|
||||
};
|
||||
|
||||
use fxhash::{FxHashMap, FxHashSet};
|
||||
|
@ -9,7 +9,6 @@ use loro_common::{ContainerID, InternalString, LoroError, LoroResult, LoroValue,
|
|||
use loro_delta::DeltaRopeBuilder;
|
||||
|
||||
use crate::{
|
||||
arena::SharedArena,
|
||||
container::{
|
||||
idx::ContainerIdx,
|
||||
list::list_op,
|
||||
|
@ -26,9 +25,8 @@ use crate::{
|
|||
event::{Diff, Index, InternalDiff, TextDiff},
|
||||
handler::TextDelta,
|
||||
op::{Op, RawOp},
|
||||
txn::Transaction,
|
||||
utils::{lazy::LazyLoad, string_slice::StringSlice},
|
||||
DocState,
|
||||
LoroDocInner,
|
||||
};
|
||||
|
||||
use super::{ApplyLocalOpReturn, ContainerState, DiffApplyContext};
|
||||
|
@ -645,12 +643,7 @@ impl ContainerState for RichtextState {
|
|||
Ok(Default::default())
|
||||
}
|
||||
|
||||
fn to_diff(
|
||||
&mut self,
|
||||
_arena: &SharedArena,
|
||||
_txn: &Weak<Mutex<Option<Transaction>>>,
|
||||
_state: &Weak<Mutex<DocState>>,
|
||||
) -> Diff {
|
||||
fn to_diff(&mut self, _doc: &Weak<LoroDocInner>) -> Diff {
|
||||
let mut delta = TextDiff::new();
|
||||
for span in self.state.get_mut().iter() {
|
||||
delta.push_insert(
|
||||
|
|
|
@ -13,7 +13,7 @@ use serde::Serialize;
|
|||
use std::collections::VecDeque;
|
||||
use std::fmt::Debug;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::{Mutex, Weak};
|
||||
use std::sync::Weak;
|
||||
|
||||
use super::{ApplyLocalOpReturn, ContainerState, DiffApplyContext};
|
||||
use crate::configure::Configure;
|
||||
|
@ -23,15 +23,13 @@ use crate::diff_calc::DiffMode;
|
|||
use crate::encoding::{EncodeMode, StateSnapshotDecodeContext, StateSnapshotEncoder};
|
||||
use crate::event::InternalDiff;
|
||||
use crate::op::Op;
|
||||
use crate::txn::Transaction;
|
||||
use crate::DocState;
|
||||
use crate::{
|
||||
arena::SharedArena,
|
||||
container::tree::tree_op::TreeOp,
|
||||
delta::TreeInternalDiff,
|
||||
event::{Diff, Index},
|
||||
op::RawOp,
|
||||
};
|
||||
use crate::{DocState, LoroDocInner};
|
||||
|
||||
#[derive(Clone, Debug, EnumAsInner)]
|
||||
pub enum TreeFractionalIndexConfigInner {
|
||||
|
@ -1321,12 +1319,7 @@ impl ContainerState for TreeState {
|
|||
Ok(ApplyLocalOpReturn { deleted_containers })
|
||||
}
|
||||
|
||||
fn to_diff(
|
||||
&mut self,
|
||||
_arena: &SharedArena,
|
||||
_txn: &Weak<Mutex<Option<Transaction>>>,
|
||||
_state: &Weak<Mutex<DocState>>,
|
||||
) -> Diff {
|
||||
fn to_diff(&mut self, _doc: &Weak<LoroDocInner>) -> Diff {
|
||||
let mut diffs = vec![];
|
||||
let Some(roots) = self.children.get(&TreeParentId::Root) else {
|
||||
return Diff::Tree(TreeDiff { diff: vec![] });
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
use std::sync::{Mutex, Weak};
|
||||
use std::sync::Weak;
|
||||
|
||||
use loro_common::{ContainerID, LoroResult, LoroValue};
|
||||
|
||||
use crate::{
|
||||
arena::SharedArena,
|
||||
configure::Configure,
|
||||
container::idx::ContainerIdx,
|
||||
encoding::{StateSnapshotDecodeContext, StateSnapshotEncoder},
|
||||
event::{Diff, Index, InternalDiff},
|
||||
op::{Op, RawOp},
|
||||
txn::Transaction,
|
||||
DocState,
|
||||
LoroDocInner,
|
||||
};
|
||||
|
||||
use super::{ApplyLocalOpReturn, ContainerState, DiffApplyContext};
|
||||
|
@ -52,12 +50,7 @@ impl ContainerState for UnknownState {
|
|||
}
|
||||
|
||||
#[doc = r" Convert a state to a diff, such that an empty state will be transformed into the same as this state when it's applied."]
|
||||
fn to_diff(
|
||||
&mut self,
|
||||
_arena: &SharedArena,
|
||||
_txn: &Weak<Mutex<Option<Transaction>>>,
|
||||
_state: &Weak<Mutex<DocState>>,
|
||||
) -> Diff {
|
||||
fn to_diff(&mut self, _doc: &Weak<LoroDocInner>) -> Diff {
|
||||
Diff::Unknown
|
||||
}
|
||||
|
||||
|
|
|
@ -157,7 +157,7 @@ mod test {
|
|||
use tracing::trace;
|
||||
|
||||
use super::*;
|
||||
use crate::{handler::HandlerTrait, loro::LoroDoc};
|
||||
use crate::{handler::HandlerTrait, LoroDoc};
|
||||
|
||||
#[test]
|
||||
fn test_recursive_events() {
|
||||
|
|
|
@ -2,7 +2,7 @@ use core::panic;
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
mem::take,
|
||||
sync::{Arc, Mutex, Weak},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use enum_as_inner::EnumAsInner;
|
||||
|
@ -28,7 +28,7 @@ use crate::{
|
|||
op::{Op, RawOp, RawOpContent},
|
||||
span::HasIdSpan,
|
||||
version::Frontiers,
|
||||
InternalString, LoroError, LoroValue,
|
||||
InternalString, LoroDocInner, LoroError, LoroValue,
|
||||
};
|
||||
|
||||
use super::{
|
||||
|
@ -61,12 +61,7 @@ impl crate::LoroDoc {
|
|||
));
|
||||
}
|
||||
|
||||
let mut txn = Transaction::new_with_origin(
|
||||
self.state.clone(),
|
||||
self.oplog.clone(),
|
||||
origin.into(),
|
||||
self.get_global_txn(),
|
||||
);
|
||||
let mut txn = Transaction::new_with_origin(self.inner.clone(), origin.into());
|
||||
|
||||
let obs = self.observer.clone();
|
||||
let local_update_subs_weak = self.local_update_subs.downgrade();
|
||||
|
@ -135,15 +130,13 @@ pub(crate) type OnCommitFn =
|
|||
Box<dyn FnOnce(&Arc<Mutex<DocState>>, &Arc<Mutex<OpLog>>, IdSpan) + Sync + Send>;
|
||||
|
||||
pub struct Transaction {
|
||||
global_txn: Weak<Mutex<Option<Transaction>>>,
|
||||
peer: PeerID,
|
||||
origin: InternalString,
|
||||
start_counter: Counter,
|
||||
next_counter: Counter,
|
||||
start_lamport: Lamport,
|
||||
next_lamport: Lamport,
|
||||
state: Arc<Mutex<DocState>>,
|
||||
oplog: Arc<Mutex<OpLog>>,
|
||||
doc: Arc<LoroDocInner>,
|
||||
frontiers: Frontiers,
|
||||
local_ops: RleVec<[Op; 1]>, // TODO: use a more efficient data structure
|
||||
event_hints: Vec<EventHint>,
|
||||
|
@ -320,26 +313,17 @@ impl generic_btree::rle::Mergeable for EventHint {
|
|||
|
||||
impl Transaction {
|
||||
#[inline]
|
||||
pub fn new(
|
||||
state: Arc<Mutex<DocState>>,
|
||||
oplog: Arc<Mutex<OpLog>>,
|
||||
global_txn: Weak<Mutex<Option<Transaction>>>,
|
||||
) -> Self {
|
||||
Self::new_with_origin(state, oplog, "".into(), global_txn)
|
||||
pub fn new(doc: Arc<LoroDocInner>) -> Self {
|
||||
Self::new_with_origin(doc.clone(), "".into())
|
||||
}
|
||||
|
||||
pub fn new_with_origin(
|
||||
state: Arc<Mutex<DocState>>,
|
||||
oplog: Arc<Mutex<OpLog>>,
|
||||
origin: InternalString,
|
||||
global_txn: Weak<Mutex<Option<Transaction>>>,
|
||||
) -> Self {
|
||||
let mut state_lock = state.try_lock().unwrap();
|
||||
pub fn new_with_origin(doc: Arc<LoroDocInner>, origin: InternalString) -> Self {
|
||||
let mut state_lock = doc.state.try_lock().unwrap();
|
||||
if state_lock.is_in_txn() {
|
||||
panic!("Cannot start a transaction while another one is in progress");
|
||||
}
|
||||
|
||||
let oplog_lock = oplog.try_lock().unwrap();
|
||||
let oplog_lock = doc.oplog.try_lock().unwrap();
|
||||
state_lock.start_txn(origin, crate::event::EventTriggerKind::Local);
|
||||
let arena = state_lock.arena.clone();
|
||||
let frontiers = state_lock.frontiers.clone();
|
||||
|
@ -354,12 +338,10 @@ impl Transaction {
|
|||
drop(oplog_lock);
|
||||
Self {
|
||||
peer,
|
||||
state,
|
||||
doc,
|
||||
arena,
|
||||
oplog,
|
||||
frontiers,
|
||||
timestamp: None,
|
||||
global_txn,
|
||||
next_counter,
|
||||
next_lamport,
|
||||
origin: Default::default(),
|
||||
|
@ -405,14 +387,14 @@ impl Transaction {
|
|||
}
|
||||
|
||||
self.finished = true;
|
||||
let mut state = self.state.try_lock().unwrap();
|
||||
let mut state = self.doc.state.try_lock().unwrap();
|
||||
if self.local_ops.is_empty() {
|
||||
state.abort_txn();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let ops = std::mem::take(&mut self.local_ops);
|
||||
let mut oplog = self.oplog.try_lock().unwrap();
|
||||
let mut oplog = self.doc.oplog.try_lock().unwrap();
|
||||
let deps = take(&mut self.frontiers);
|
||||
let change = Change {
|
||||
lamport: self.start_lamport,
|
||||
|
@ -429,9 +411,7 @@ impl Transaction {
|
|||
let diff = if state.is_recording() {
|
||||
Some(change_to_diff(
|
||||
&change,
|
||||
&oplog.arena,
|
||||
&self.global_txn,
|
||||
&Arc::downgrade(&self.state),
|
||||
self.doc.clone(),
|
||||
std::mem::take(&mut self.event_hints),
|
||||
))
|
||||
} else {
|
||||
|
@ -468,7 +448,11 @@ impl Transaction {
|
|||
drop(state);
|
||||
drop(oplog);
|
||||
if let Some(on_commit) = self.on_commit.take() {
|
||||
on_commit(&self.state, &self.oplog, self.id_span());
|
||||
on_commit(
|
||||
&self.doc.state.clone(),
|
||||
&self.doc.oplog.clone(),
|
||||
self.id_span(),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -479,19 +463,20 @@ impl Transaction {
|
|||
content: RawOpContent,
|
||||
event: EventHint,
|
||||
// check whether context and txn are referring to the same state context
|
||||
state_ref: &Weak<Mutex<DocState>>,
|
||||
doc: &Arc<LoroDocInner>,
|
||||
) -> LoroResult<()> {
|
||||
if Arc::as_ptr(&self.state) != Weak::as_ptr(state_ref) {
|
||||
// TODO: need to check if the doc is the same
|
||||
if Arc::as_ptr(&self.doc.state) != Arc::as_ptr(&doc.state) {
|
||||
return Err(LoroError::UnmatchedContext {
|
||||
expected: self
|
||||
.doc
|
||||
.state
|
||||
.try_lock()
|
||||
.unwrap()
|
||||
.peer
|
||||
.load(std::sync::atomic::Ordering::Relaxed),
|
||||
found: state_ref
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
found: doc
|
||||
.state
|
||||
.try_lock()
|
||||
.unwrap()
|
||||
.peer
|
||||
|
@ -511,7 +496,7 @@ impl Transaction {
|
|||
content,
|
||||
};
|
||||
|
||||
let mut state = self.state.try_lock().unwrap();
|
||||
let mut state = self.doc.state.try_lock().unwrap();
|
||||
if state.is_deleted(container) {
|
||||
return Err(LoroError::ContainerDeleted {
|
||||
container: Box::new(state.arena.idx_to_id(container).unwrap()),
|
||||
|
@ -522,7 +507,7 @@ impl Transaction {
|
|||
state.apply_local_op(&raw_op, &op)?;
|
||||
{
|
||||
// update version info
|
||||
let mut oplog = self.oplog.try_lock().unwrap();
|
||||
let mut oplog = self.doc.oplog.try_lock().unwrap();
|
||||
let dep_id = Frontiers::from_id(ID::new(self.peer, self.next_counter - 1));
|
||||
let start_id = ID::new(self.peer, self.next_counter);
|
||||
self.next_counter += len as Counter;
|
||||
|
@ -566,56 +551,36 @@ impl Transaction {
|
|||
/// if it's str it will use Root container, which will not be None
|
||||
pub fn get_text<I: IntoContainerId>(&self, id: I) -> TextHandler {
|
||||
let id = id.into_container_id(&self.arena, ContainerType::Text);
|
||||
Handler::new_attached(
|
||||
id,
|
||||
self.arena.clone(),
|
||||
self.global_txn.clone(),
|
||||
Arc::downgrade(&self.state),
|
||||
)
|
||||
.into_text()
|
||||
.unwrap()
|
||||
Handler::new_attached(id, self.doc.clone())
|
||||
.into_text()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// id can be a str, ContainerID, or ContainerIdRaw.
|
||||
/// if it's str it will use Root container, which will not be None
|
||||
pub fn get_list<I: IntoContainerId>(&self, id: I) -> ListHandler {
|
||||
let id = id.into_container_id(&self.arena, ContainerType::List);
|
||||
Handler::new_attached(
|
||||
id,
|
||||
self.arena.clone(),
|
||||
self.global_txn.clone(),
|
||||
Arc::downgrade(&self.state),
|
||||
)
|
||||
.into_list()
|
||||
.unwrap()
|
||||
Handler::new_attached(id, self.doc.clone())
|
||||
.into_list()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// id can be a str, ContainerID, or ContainerIdRaw.
|
||||
/// if it's str it will use Root container, which will not be None
|
||||
pub fn get_map<I: IntoContainerId>(&self, id: I) -> MapHandler {
|
||||
let id = id.into_container_id(&self.arena, ContainerType::Map);
|
||||
Handler::new_attached(
|
||||
id,
|
||||
self.arena.clone(),
|
||||
self.global_txn.clone(),
|
||||
Arc::downgrade(&self.state),
|
||||
)
|
||||
.into_map()
|
||||
.unwrap()
|
||||
Handler::new_attached(id, self.doc.clone())
|
||||
.into_map()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// id can be a str, ContainerID, or ContainerIdRaw.
|
||||
/// if it's str it will use Root container, which will not be None
|
||||
pub fn get_tree<I: IntoContainerId>(&self, id: I) -> TreeHandler {
|
||||
let id = id.into_container_id(&self.arena, ContainerType::Tree);
|
||||
Handler::new_attached(
|
||||
id,
|
||||
self.arena.clone(),
|
||||
self.global_txn.clone(),
|
||||
Arc::downgrade(&self.state),
|
||||
)
|
||||
.into_tree()
|
||||
.unwrap()
|
||||
Handler::new_attached(id, self.doc.clone())
|
||||
.into_tree()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn next_id(&self) -> ID {
|
||||
|
@ -666,9 +631,7 @@ pub(crate) struct TxnContainerDiff {
|
|||
// PERF: could be compacter
|
||||
fn change_to_diff(
|
||||
change: &Change,
|
||||
arena: &SharedArena,
|
||||
txn: &Weak<Mutex<Option<Transaction>>>,
|
||||
state: &Weak<Mutex<DocState>>,
|
||||
doc: Arc<LoroDocInner>,
|
||||
event_hints: Vec<EventHint>,
|
||||
) -> Vec<TxnContainerDiff> {
|
||||
let mut ans: Vec<TxnContainerDiff> = Vec::with_capacity(change.ops.len());
|
||||
|
@ -777,10 +740,11 @@ fn change_to_diff(
|
|||
// be using op index for the MovableList
|
||||
for op in ops.iter() {
|
||||
let (range, _) = op.content.as_list().unwrap().as_insert().unwrap();
|
||||
let values = arena
|
||||
let values = doc
|
||||
.arena
|
||||
.get_values(range.to_range())
|
||||
.into_iter()
|
||||
.map(|v| ValueOrHandler::from_value(v, arena, txn, state));
|
||||
.map(|v| ValueOrHandler::from_value(v, &doc));
|
||||
ans.push(TxnContainerDiff {
|
||||
idx: op.container,
|
||||
diff: Diff::List(
|
||||
|
@ -808,7 +772,7 @@ fn change_to_diff(
|
|||
diff: Diff::Map(ResolvedMapDelta::new().with_entry(
|
||||
key,
|
||||
ResolvedMapValue {
|
||||
value: value.map(|v| ValueOrHandler::from_value(v, arena, txn, state)),
|
||||
value: value.map(|v| ValueOrHandler::from_value(v, &doc)),
|
||||
idlp: IdLp::new(peer, lamport),
|
||||
},
|
||||
)),
|
||||
|
@ -830,7 +794,7 @@ fn change_to_diff(
|
|||
&DeltaRopeBuilder::new()
|
||||
.retain(to as usize, Default::default())
|
||||
.insert(
|
||||
ArrayVec::from([ValueOrHandler::from_value(value, arena, txn, state)]),
|
||||
ArrayVec::from([ValueOrHandler::from_value(value, &doc)]),
|
||||
ListDeltaMeta { from_move: true },
|
||||
)
|
||||
.build(),
|
||||
|
@ -848,9 +812,7 @@ fn change_to_diff(
|
|||
.retain(index, Default::default())
|
||||
.delete(1)
|
||||
.insert(
|
||||
ArrayVec::from([ValueOrHandler::from_value(
|
||||
value, arena, txn, state,
|
||||
)]),
|
||||
ArrayVec::from([ValueOrHandler::from_value(value, &doc)]),
|
||||
Default::default(),
|
||||
)
|
||||
.build(),
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::{
|
|||
use either::Either;
|
||||
use fxhash::FxHashMap;
|
||||
use loro_common::{
|
||||
ContainerID, Counter, CounterSpan, HasIdSpan, IdSpan, LoroError, LoroResult, LoroValue, PeerID,
|
||||
ContainerID, Counter, CounterSpan, HasIdSpan, IdSpan, LoroResult, LoroValue, PeerID,
|
||||
};
|
||||
use tracing::{debug_span, info_span, instrument};
|
||||
|
||||
|
@ -154,6 +154,7 @@ pub struct UndoManager {
|
|||
inner: Arc<Mutex<UndoManagerInner>>,
|
||||
_peer_id_change_sub: Subscription,
|
||||
_undo_sub: Subscription,
|
||||
doc: LoroDoc,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for UndoManager {
|
||||
|
@ -553,6 +554,7 @@ impl UndoManager {
|
|||
inner,
|
||||
_peer_id_change_sub: sub,
|
||||
_undo_sub: undo_sub,
|
||||
doc: doc.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -576,16 +578,9 @@ impl UndoManager {
|
|||
.push(prefix.into());
|
||||
}
|
||||
|
||||
pub fn record_new_checkpoint(&mut self, doc: &LoroDoc) -> LoroResult<()> {
|
||||
if doc.peer_id() != self.peer() {
|
||||
return Err(LoroError::UndoWithDifferentPeerId {
|
||||
expected: self.peer(),
|
||||
actual: doc.peer_id(),
|
||||
});
|
||||
}
|
||||
|
||||
doc.commit_then_renew();
|
||||
let counter = get_counter_end(doc, self.peer());
|
||||
pub fn record_new_checkpoint(&mut self) -> LoroResult<()> {
|
||||
self.doc.commit_then_renew();
|
||||
let counter = get_counter_end(&self.doc, self.peer());
|
||||
self.inner
|
||||
.try_lock()
|
||||
.unwrap()
|
||||
|
@ -594,9 +589,8 @@ impl UndoManager {
|
|||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub fn undo(&mut self, doc: &LoroDoc) -> LoroResult<bool> {
|
||||
pub fn undo(&mut self) -> LoroResult<bool> {
|
||||
self.perform(
|
||||
doc,
|
||||
|x| &mut x.undo_stack,
|
||||
|x| &mut x.redo_stack,
|
||||
UndoOrRedo::Undo,
|
||||
|
@ -604,9 +598,8 @@ impl UndoManager {
|
|||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub fn redo(&mut self, doc: &LoroDoc) -> LoroResult<bool> {
|
||||
pub fn redo(&mut self) -> LoroResult<bool> {
|
||||
self.perform(
|
||||
doc,
|
||||
|x| &mut x.redo_stack,
|
||||
|x| &mut x.undo_stack,
|
||||
UndoOrRedo::Redo,
|
||||
|
@ -615,11 +608,11 @@ impl UndoManager {
|
|||
|
||||
fn perform(
|
||||
&mut self,
|
||||
doc: &LoroDoc,
|
||||
get_stack: impl Fn(&mut UndoManagerInner) -> &mut Stack,
|
||||
get_opposite: impl Fn(&mut UndoManagerInner) -> &mut Stack,
|
||||
kind: UndoOrRedo,
|
||||
) -> LoroResult<bool> {
|
||||
let doc = &self.doc.clone();
|
||||
// When in the undo/redo loop, the new undo/redo stack item should restore the selection
|
||||
// to the state it was in before the item that was popped two steps ago from the stack.
|
||||
//
|
||||
|
@ -673,8 +666,7 @@ impl UndoManager {
|
|||
// Because users may change the selections during the undo/redo loop, it's
|
||||
// more stable to keep the selection stored in the last stack item
|
||||
// rather than using the current selection directly.
|
||||
|
||||
self.record_new_checkpoint(doc)?;
|
||||
self.record_new_checkpoint()?;
|
||||
let end_counter = get_counter_end(doc, self.peer());
|
||||
let mut top = {
|
||||
let mut inner = self.inner.try_lock().unwrap();
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use js_sys::{Array, Map, Object, Reflect, Uint8Array};
|
||||
use loro_common::{IdLp, LoroListValue, LoroMapValue, LoroValue};
|
||||
|
@ -9,7 +8,7 @@ use loro_internal::event::{Diff, ListDeltaMeta, ListDiff, TextDiff, TextMeta};
|
|||
use loro_internal::handler::{Handler, ValueOrHandler};
|
||||
use loro_internal::version::VersionRange;
|
||||
use loro_internal::StringSlice;
|
||||
use loro_internal::{Counter, CounterSpan, FxHashMap, IdSpan, ListDiffItem, LoroDoc};
|
||||
use loro_internal::{Counter, CounterSpan, FxHashMap, IdSpan, ListDiffItem};
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
|
||||
use crate::{
|
||||
|
@ -132,7 +131,7 @@ pub(crate) fn js_to_version_vector(
|
|||
Ok(vv)
|
||||
}
|
||||
|
||||
pub(crate) fn resolved_diff_to_js(value: &Diff, doc: &Arc<LoroDoc>) -> JsValue {
|
||||
pub(crate) fn resolved_diff_to_js(value: &Diff) -> JsValue {
|
||||
// create a obj
|
||||
let obj = Object::new();
|
||||
match value {
|
||||
|
@ -149,7 +148,7 @@ pub(crate) fn resolved_diff_to_js(value: &Diff, doc: &Arc<LoroDoc>) -> JsValue {
|
|||
let arr = Array::new();
|
||||
let mut i = 0;
|
||||
for v in list.iter() {
|
||||
let (a, b) = delta_item_to_js(v.clone(), doc);
|
||||
let (a, b) = delta_item_to_js(v.clone());
|
||||
arr.set(i as u32, a);
|
||||
i += 1;
|
||||
if let Some(b) = b {
|
||||
|
@ -180,12 +179,8 @@ pub(crate) fn resolved_diff_to_js(value: &Diff, doc: &Arc<LoroDoc>) -> JsValue {
|
|||
js_sys::Reflect::set(&obj, &JsValue::from_str("type"), &JsValue::from_str("map"))
|
||||
.unwrap();
|
||||
|
||||
js_sys::Reflect::set(
|
||||
&obj,
|
||||
&JsValue::from_str("updated"),
|
||||
&map_delta_to_js(map, doc),
|
||||
)
|
||||
.unwrap();
|
||||
js_sys::Reflect::set(&obj, &JsValue::from_str("updated"), &map_delta_to_js(map))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Diff::Counter(v) => {
|
||||
|
@ -244,7 +239,7 @@ pub(crate) fn js_diff_to_inner_diff(js: JsValue) -> JsResult<Diff> {
|
|||
}
|
||||
}
|
||||
|
||||
fn delta_item_to_js(item: ListDiffItem, doc: &Arc<LoroDoc>) -> (JsValue, Option<JsValue>) {
|
||||
fn delta_item_to_js(item: ListDiffItem) -> (JsValue, Option<JsValue>) {
|
||||
match item {
|
||||
loro_internal::loro_delta::DeltaItem::Retain { len, attr: _ } => {
|
||||
let obj = Object::new();
|
||||
|
@ -269,7 +264,7 @@ fn delta_item_to_js(item: ListDiffItem, doc: &Arc<LoroDoc>) -> (JsValue, Option<
|
|||
for (i, v) in value.into_iter().enumerate() {
|
||||
let value = match v {
|
||||
ValueOrHandler::Value(v) => convert(v),
|
||||
ValueOrHandler::Handler(h) => handler_to_js_value(h, Some(doc.clone())),
|
||||
ValueOrHandler::Handler(h) => handler_to_js_value(h),
|
||||
};
|
||||
arr.set(i as u32, value);
|
||||
}
|
||||
|
@ -400,13 +395,13 @@ impl From<ImportBlobMetadata> for JsImportBlobMetadata {
|
|||
}
|
||||
}
|
||||
|
||||
fn map_delta_to_js(value: &ResolvedMapDelta, doc: &Arc<LoroDoc>) -> JsValue {
|
||||
fn map_delta_to_js(value: &ResolvedMapDelta) -> JsValue {
|
||||
let obj = Object::new();
|
||||
for (key, value) in value.updated.iter() {
|
||||
let value = if let Some(value) = value.value.clone() {
|
||||
match value {
|
||||
ValueOrHandler::Value(v) => convert(v),
|
||||
ValueOrHandler::Handler(h) => handler_to_js_value(h, Some(doc.clone())),
|
||||
ValueOrHandler::Handler(h) => handler_to_js_value(h),
|
||||
}
|
||||
} else {
|
||||
JsValue::null()
|
||||
|
@ -418,19 +413,18 @@ fn map_delta_to_js(value: &ResolvedMapDelta, doc: &Arc<LoroDoc>) -> JsValue {
|
|||
obj.into_js_result().unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn handler_to_js_value(handler: Handler, doc: Option<Arc<LoroDoc>>) -> JsValue {
|
||||
pub(crate) fn handler_to_js_value(handler: Handler) -> JsValue {
|
||||
match handler {
|
||||
Handler::Text(t) => LoroText {
|
||||
handler: t,
|
||||
doc,
|
||||
delta_cache: None,
|
||||
}
|
||||
.into(),
|
||||
Handler::Map(m) => LoroMap { handler: m, doc }.into(),
|
||||
Handler::List(l) => LoroList { handler: l, doc }.into(),
|
||||
Handler::Tree(t) => LoroTree { handler: t, doc }.into(),
|
||||
Handler::MovableList(m) => LoroMovableList { handler: m, doc }.into(),
|
||||
Handler::Counter(c) => LoroCounter { handler: c, doc }.into(),
|
||||
Handler::Map(m) => LoroMap { handler: m }.into(),
|
||||
Handler::List(l) => LoroList { handler: l }.into(),
|
||||
Handler::Tree(t) => LoroTree { handler: t }.into(),
|
||||
Handler::MovableList(m) => LoroMovableList { handler: m }.into(),
|
||||
Handler::Counter(c) => LoroCounter { handler: c }.into(),
|
||||
Handler::Unknown(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use super::subscription_to_js_function_callback;
|
||||
use loro_internal::{
|
||||
handler::{counter::CounterHandler, Handler},
|
||||
HandlerTrait, LoroDoc,
|
||||
HandlerTrait,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
@ -16,7 +16,6 @@ use crate::{
|
|||
#[wasm_bindgen]
|
||||
pub struct LoroCounter {
|
||||
pub(crate) handler: CounterHandler,
|
||||
pub(crate) doc: Option<Arc<LoroDoc>>,
|
||||
}
|
||||
|
||||
impl Default for LoroCounter {
|
||||
|
@ -32,7 +31,6 @@ impl LoroCounter {
|
|||
pub fn new() -> Self {
|
||||
Self {
|
||||
handler: CounterHandler::new_detached(),
|
||||
doc: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,14 +68,13 @@ impl LoroCounter {
|
|||
pub fn subscribe(&self, f: js_sys::Function) -> JsResult<JsValue> {
|
||||
let observer = observer::Observer::new(f);
|
||||
let doc = self
|
||||
.doc
|
||||
.clone()
|
||||
.handler
|
||||
.doc()
|
||||
.ok_or_else(|| JsError::new("Document is not attached"))?;
|
||||
let doc_clone = doc.clone();
|
||||
let sub = doc.subscribe(
|
||||
&self.handler.id(),
|
||||
Arc::new(move |e| {
|
||||
call_after_micro_task(observer.clone(), e, &doc_clone);
|
||||
call_after_micro_task(observer.clone(), e);
|
||||
}),
|
||||
);
|
||||
Ok(subscription_to_js_function_callback(sub))
|
||||
|
@ -90,7 +87,7 @@ impl LoroCounter {
|
|||
/// the WASM boundary.
|
||||
pub fn parent(&self) -> JsContainerOrUndefined {
|
||||
if let Some(p) = HandlerTrait::parent(&self.handler) {
|
||||
handler_to_js_value(p, self.doc.clone()).into()
|
||||
handler_to_js_value(p).into()
|
||||
} else {
|
||||
JsContainerOrUndefined::from(JsValue::UNDEFINED)
|
||||
}
|
||||
|
@ -115,7 +112,7 @@ impl LoroCounter {
|
|||
}
|
||||
|
||||
if let Some(h) = self.handler.get_attached() {
|
||||
handler_to_js_value(Handler::Counter(h), self.doc.clone()).into()
|
||||
handler_to_js_value(Handler::Counter(h)).into()
|
||||
} else {
|
||||
JsValue::UNDEFINED.into()
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ type JsResult<T> = Result<T, JsValue>;
|
|||
/// const tree = loro.getTree("tree");
|
||||
/// ```
|
||||
#[wasm_bindgen]
|
||||
pub struct LoroDoc(Arc<LoroDocInner>);
|
||||
pub struct LoroDoc(LoroDocInner);
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
|
@ -368,7 +368,7 @@ impl LoroDoc {
|
|||
pub fn new() -> Self {
|
||||
let doc = LoroDocInner::new();
|
||||
doc.start_auto_commit();
|
||||
Self(Arc::new(doc))
|
||||
Self(doc)
|
||||
}
|
||||
|
||||
/// Enables editing in detached mode, which is disabled by default.
|
||||
|
@ -510,7 +510,7 @@ impl LoroDoc {
|
|||
pub fn from_snapshot(snapshot: &[u8]) -> JsResult<LoroDoc> {
|
||||
let doc = LoroDocInner::from_snapshot(snapshot)?;
|
||||
doc.start_auto_commit();
|
||||
Ok(Self(Arc::new(doc)))
|
||||
Ok(Self(doc))
|
||||
}
|
||||
|
||||
/// Attach the document state to the latest known version.
|
||||
|
@ -590,7 +590,7 @@ impl LoroDoc {
|
|||
/// When called in detached mode, it will fork at the current state frontiers.
|
||||
/// It will have the same effect as `forkAt(&self.frontiers())`.
|
||||
pub fn fork(&self) -> Self {
|
||||
Self(Arc::new(self.0.fork()))
|
||||
Self(self.0.fork())
|
||||
}
|
||||
|
||||
/// Creates a new LoroDoc at a specified version (Frontiers)
|
||||
|
@ -598,9 +598,7 @@ impl LoroDoc {
|
|||
/// The created doc will only contain the history before the specified frontiers.
|
||||
#[wasm_bindgen(js_name = "forkAt")]
|
||||
pub fn fork_at(&self, frontiers: Vec<JsID>) -> JsResult<LoroDoc> {
|
||||
Ok(Self(Arc::new(
|
||||
self.0.fork_at(&ids_to_frontiers(frontiers)?),
|
||||
)))
|
||||
Ok(Self(self.0.fork_at(&ids_to_frontiers(frontiers)?)))
|
||||
}
|
||||
|
||||
/// Checkout the `DocState` to the latest version of `OpLog`.
|
||||
|
@ -897,7 +895,6 @@ impl LoroDoc {
|
|||
.get_text(js_value_to_container_id(cid, ContainerType::Text)?);
|
||||
Ok(LoroText {
|
||||
handler: text,
|
||||
doc: Some(self.0.clone()),
|
||||
delta_cache: None,
|
||||
})
|
||||
}
|
||||
|
@ -919,10 +916,7 @@ impl LoroDoc {
|
|||
let map = self
|
||||
.0
|
||||
.get_map(js_value_to_container_id(cid, ContainerType::Map)?);
|
||||
Ok(LoroMap {
|
||||
handler: map,
|
||||
doc: Some(self.0.clone()),
|
||||
})
|
||||
Ok(LoroMap { handler: map })
|
||||
}
|
||||
|
||||
/// Get a LoroList by container id
|
||||
|
@ -942,10 +936,7 @@ impl LoroDoc {
|
|||
let list = self
|
||||
.0
|
||||
.get_list(js_value_to_container_id(cid, ContainerType::List)?);
|
||||
Ok(LoroList {
|
||||
handler: list,
|
||||
doc: Some(self.0.clone()),
|
||||
})
|
||||
Ok(LoroList { handler: list })
|
||||
}
|
||||
|
||||
/// Get a LoroMovableList by container id
|
||||
|
@ -965,10 +956,7 @@ impl LoroDoc {
|
|||
let list = self
|
||||
.0
|
||||
.get_movable_list(js_value_to_container_id(cid, ContainerType::MovableList)?);
|
||||
Ok(LoroMovableList {
|
||||
handler: list,
|
||||
doc: Some(self.0.clone()),
|
||||
})
|
||||
Ok(LoroMovableList { handler: list })
|
||||
}
|
||||
|
||||
/// Get a LoroCounter by container id
|
||||
|
@ -977,10 +965,7 @@ impl LoroDoc {
|
|||
let counter = self
|
||||
.0
|
||||
.get_counter(js_value_to_container_id(cid, ContainerType::Counter)?);
|
||||
Ok(LoroCounter {
|
||||
handler: counter,
|
||||
doc: Some(self.0.clone()),
|
||||
})
|
||||
Ok(LoroCounter { handler: counter })
|
||||
}
|
||||
|
||||
/// Get a LoroTree by container id
|
||||
|
@ -1000,10 +985,7 @@ impl LoroDoc {
|
|||
let tree = self
|
||||
.0
|
||||
.get_tree(js_value_to_container_id(cid, ContainerType::Tree)?);
|
||||
Ok(LoroTree {
|
||||
handler: tree,
|
||||
doc: Some(self.0.clone()),
|
||||
})
|
||||
Ok(LoroTree { handler: tree })
|
||||
}
|
||||
|
||||
/// Get the container corresponding to the container id
|
||||
|
@ -1025,52 +1007,32 @@ impl LoroDoc {
|
|||
Ok(match ty {
|
||||
ContainerType::Map => {
|
||||
let map = self.0.get_map(container_id);
|
||||
LoroMap {
|
||||
handler: map,
|
||||
doc: Some(self.0.clone()),
|
||||
}
|
||||
.into()
|
||||
LoroMap { handler: map }.into()
|
||||
}
|
||||
ContainerType::List => {
|
||||
let list = self.0.get_list(container_id);
|
||||
LoroList {
|
||||
handler: list,
|
||||
doc: Some(self.0.clone()),
|
||||
}
|
||||
.into()
|
||||
LoroList { handler: list }.into()
|
||||
}
|
||||
ContainerType::Text => {
|
||||
let richtext = self.0.get_text(container_id);
|
||||
LoroText {
|
||||
handler: richtext,
|
||||
doc: Some(self.0.clone()),
|
||||
|
||||
delta_cache: None,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
ContainerType::Tree => {
|
||||
let tree = self.0.get_tree(container_id);
|
||||
LoroTree {
|
||||
handler: tree,
|
||||
doc: Some(self.0.clone()),
|
||||
}
|
||||
.into()
|
||||
LoroTree { handler: tree }.into()
|
||||
}
|
||||
ContainerType::MovableList => {
|
||||
let movelist = self.0.get_movable_list(container_id);
|
||||
LoroMovableList {
|
||||
handler: movelist,
|
||||
doc: Some(self.0.clone()),
|
||||
}
|
||||
.into()
|
||||
LoroMovableList { handler: movelist }.into()
|
||||
}
|
||||
ContainerType::Counter => {
|
||||
let counter = self.0.get_counter(container_id);
|
||||
LoroCounter {
|
||||
handler: counter,
|
||||
doc: Some(self.0.clone()),
|
||||
}
|
||||
.into()
|
||||
LoroCounter { handler: counter }.into()
|
||||
}
|
||||
ContainerType::Unknown(_) => {
|
||||
return Err(JsValue::from_str(
|
||||
|
@ -1114,7 +1076,7 @@ impl LoroDoc {
|
|||
.into_iter()
|
||||
{
|
||||
ans.push(&match v {
|
||||
ValueOrHandler::Handler(h) => handler_to_js_value(h, Some(self.0.clone())),
|
||||
ValueOrHandler::Handler(h) => handler_to_js_value(h),
|
||||
ValueOrHandler::Value(v) => v.into(),
|
||||
});
|
||||
}
|
||||
|
@ -1546,9 +1508,8 @@ impl LoroDoc {
|
|||
#[wasm_bindgen(skip_typescript)]
|
||||
pub fn subscribe(&self, f: js_sys::Function) -> JsValue {
|
||||
let observer = observer::Observer::new(f);
|
||||
let doc = self.0.clone();
|
||||
let sub = self.0.subscribe_root(Arc::new(move |e| {
|
||||
call_after_micro_task(observer.clone(), e, &doc)
|
||||
call_after_micro_task(observer.clone(), e)
|
||||
// call_subscriber(observer.clone(), e);
|
||||
}));
|
||||
subscription_to_js_function_callback(sub)
|
||||
|
@ -1808,7 +1769,7 @@ impl LoroDoc {
|
|||
pub fn get_by_path(&self, path: &str) -> JsValueOrContainerOrUndefined {
|
||||
let ans = self.0.get_by_str_path(path);
|
||||
let v: JsValue = match ans {
|
||||
Some(ValueOrHandler::Handler(h)) => handler_to_js_value(h, Some(self.0.clone())),
|
||||
Some(ValueOrHandler::Handler(h)) => handler_to_js_value(h),
|
||||
Some(ValueOrHandler::Value(v)) => v.into(),
|
||||
None => JsValue::UNDEFINED,
|
||||
};
|
||||
|
@ -1943,7 +1904,7 @@ impl LoroDoc {
|
|||
let obj = js_sys::Object::new();
|
||||
for (id, d) in diff.iter() {
|
||||
let id_str = id.to_string();
|
||||
let v = resolved_diff_to_js(d, &self.0);
|
||||
let v = resolved_diff_to_js(d);
|
||||
Reflect::set(&obj, &JsValue::from_str(&id_str), &v)?;
|
||||
}
|
||||
let v: JsValue = obj.into();
|
||||
|
@ -1952,26 +1913,26 @@ impl LoroDoc {
|
|||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn call_subscriber(ob: observer::Observer, e: DiffEvent, doc: &Arc<LoroDocInner>) {
|
||||
fn call_subscriber(ob: observer::Observer, e: DiffEvent) {
|
||||
// We convert the event to js object here, so that we don't need to worry about GC.
|
||||
// In the future, when FinalizationRegistry[1] is stable, we can use `--weak-ref`[2] feature
|
||||
// in wasm-bindgen to avoid this.
|
||||
//
|
||||
// [1]: https://caniuse.com/?search=FinalizationRegistry
|
||||
// [2]: https://rustwasm.github.io/wasm-bindgen/reference/weak-references.html
|
||||
let event = diff_event_to_js_value(e, doc);
|
||||
let event = diff_event_to_js_value(e);
|
||||
if let Err(e) = ob.call1(&event) {
|
||||
throw_error_after_micro_task(e);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn call_after_micro_task(ob: observer::Observer, event: DiffEvent, doc: &Arc<LoroDocInner>) {
|
||||
fn call_after_micro_task(ob: observer::Observer, event: DiffEvent) {
|
||||
let promise = Promise::resolve(&JsValue::NULL);
|
||||
type C = Closure<dyn FnMut(JsValue)>;
|
||||
let drop_handler: Rc<RefCell<Option<C>>> = Rc::new(RefCell::new(None));
|
||||
let copy = drop_handler.clone();
|
||||
let event = diff_event_to_js_value(event, doc);
|
||||
let event = diff_event_to_js_value(event);
|
||||
let closure = Closure::once(move |_: JsValue| {
|
||||
let ans = ob.call1(&event);
|
||||
drop(copy);
|
||||
|
@ -1990,7 +1951,7 @@ impl Default for LoroDoc {
|
|||
}
|
||||
}
|
||||
|
||||
fn diff_event_to_js_value(event: DiffEvent, doc: &Arc<LoroDocInner>) -> JsValue {
|
||||
fn diff_event_to_js_value(event: DiffEvent) -> JsValue {
|
||||
let obj = js_sys::Object::new();
|
||||
Reflect::set(&obj, &"by".into(), &event.event_meta.by.to_string().into()).unwrap();
|
||||
let origin: &str = &event.event_meta.origin;
|
||||
|
@ -2001,7 +1962,7 @@ fn diff_event_to_js_value(event: DiffEvent, doc: &Arc<LoroDocInner>) -> JsValue
|
|||
|
||||
let events = js_sys::Array::new_with_length(event.events.len() as u32);
|
||||
for (i, &event) in event.events.iter().enumerate() {
|
||||
events.set(i as u32, container_diff_to_js_value(event, doc));
|
||||
events.set(i as u32, container_diff_to_js_value(event));
|
||||
}
|
||||
|
||||
Reflect::set(&obj, &"events".into(), &events.into()).unwrap();
|
||||
|
@ -2035,13 +1996,10 @@ fn diff_event_to_js_value(event: DiffEvent, doc: &Arc<LoroDocInner>) -> JsValue
|
|||
/// path: Path;
|
||||
/// }
|
||||
///
|
||||
fn container_diff_to_js_value(
|
||||
event: &loro_internal::ContainerDiff,
|
||||
doc: &Arc<LoroDocInner>,
|
||||
) -> JsValue {
|
||||
fn container_diff_to_js_value(event: &loro_internal::ContainerDiff) -> JsValue {
|
||||
let obj = js_sys::Object::new();
|
||||
Reflect::set(&obj, &"target".into(), &event.id.to_string().into()).unwrap();
|
||||
Reflect::set(&obj, &"diff".into(), &resolved_diff_to_js(&event.diff, doc)).unwrap();
|
||||
Reflect::set(&obj, &"diff".into(), &resolved_diff_to_js(&event.diff)).unwrap();
|
||||
Reflect::set(
|
||||
&obj,
|
||||
&"path".into(),
|
||||
|
@ -2067,7 +2025,6 @@ fn convert_container_path_to_js_value(path: &[(ContainerID, Index)]) -> JsContai
|
|||
#[wasm_bindgen]
|
||||
pub struct LoroText {
|
||||
handler: TextHandler,
|
||||
doc: Option<Arc<LoroDocInner>>,
|
||||
delta_cache: Option<(usize, JsValue)>,
|
||||
}
|
||||
|
||||
|
@ -2087,7 +2044,6 @@ impl LoroText {
|
|||
pub fn new() -> Self {
|
||||
Self {
|
||||
handler: TextHandler::new_detached(),
|
||||
doc: None,
|
||||
delta_cache: None,
|
||||
}
|
||||
}
|
||||
|
@ -2445,14 +2401,13 @@ impl LoroText {
|
|||
pub fn subscribe(&self, f: js_sys::Function) -> JsResult<JsValue> {
|
||||
let observer = observer::Observer::new(f);
|
||||
let doc = self
|
||||
.doc
|
||||
.clone()
|
||||
.handler
|
||||
.doc()
|
||||
.ok_or_else(|| JsError::new("Document is not attached"))?;
|
||||
let doc_clone = doc.clone();
|
||||
let ans = doc.subscribe(
|
||||
&self.handler.id(),
|
||||
Arc::new(move |e| {
|
||||
call_after_micro_task(observer.clone(), e, &doc_clone);
|
||||
call_after_micro_task(observer.clone(), e);
|
||||
}),
|
||||
);
|
||||
|
||||
|
@ -2497,7 +2452,7 @@ impl LoroText {
|
|||
/// the WASM boundary.
|
||||
pub fn parent(&self) -> JsContainerOrUndefined {
|
||||
if let Some(p) = self.handler.parent() {
|
||||
handler_to_js_value(p, self.doc.clone()).into()
|
||||
handler_to_js_value(p).into()
|
||||
} else {
|
||||
JsContainerOrUndefined::from(JsValue::UNDEFINED)
|
||||
}
|
||||
|
@ -2522,7 +2477,7 @@ impl LoroText {
|
|||
}
|
||||
|
||||
if let Some(h) = self.handler.get_attached() {
|
||||
handler_to_js_value(Handler::Text(h), self.doc.clone()).into()
|
||||
handler_to_js_value(Handler::Text(h)).into()
|
||||
} else {
|
||||
JsValue::UNDEFINED.into()
|
||||
}
|
||||
|
@ -2582,7 +2537,6 @@ impl Default for LoroText {
|
|||
#[wasm_bindgen]
|
||||
pub struct LoroMap {
|
||||
handler: MapHandler,
|
||||
doc: Option<Arc<LoroDocInner>>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
|
@ -2595,7 +2549,6 @@ impl LoroMap {
|
|||
pub fn new() -> Self {
|
||||
Self {
|
||||
handler: MapHandler::new_detached(),
|
||||
doc: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2659,7 +2612,7 @@ impl LoroMap {
|
|||
pub fn get(&self, key: &str) -> JsValueOrContainerOrUndefined {
|
||||
let v = self.handler.get_(key);
|
||||
(match v {
|
||||
Some(ValueOrHandler::Handler(c)) => handler_to_js_value(c, self.doc.clone()),
|
||||
Some(ValueOrHandler::Handler(c)) => handler_to_js_value(c),
|
||||
Some(ValueOrHandler::Value(v)) => v.into(),
|
||||
None => JsValue::UNDEFINED,
|
||||
})
|
||||
|
@ -2686,7 +2639,7 @@ impl LoroMap {
|
|||
let handler = self
|
||||
.handler
|
||||
.get_or_create_container(key, child.to_handler())?;
|
||||
Ok(handler_to_js_value(handler, self.doc.clone()).into())
|
||||
Ok(handler_to_js_value(handler).into())
|
||||
}
|
||||
|
||||
/// Get the keys of the map.
|
||||
|
@ -2725,7 +2678,7 @@ impl LoroMap {
|
|||
pub fn values(&self) -> Vec<JsValue> {
|
||||
let mut ans: Vec<JsValue> = Vec::with_capacity(self.handler.len());
|
||||
self.handler.for_each(|_, v| {
|
||||
ans.push(loro_value_to_js_value_or_container(v, self.doc.clone()));
|
||||
ans.push(loro_value_to_js_value_or_container(v));
|
||||
});
|
||||
ans
|
||||
}
|
||||
|
@ -2748,7 +2701,7 @@ impl LoroMap {
|
|||
self.handler.for_each(|k, v| {
|
||||
let array = Array::new();
|
||||
array.push(&k.to_string().into());
|
||||
array.push(&loro_value_to_js_value_or_container(v, self.doc.clone()));
|
||||
array.push(&loro_value_to_js_value_or_container(v));
|
||||
let v: JsValue = array.into();
|
||||
ans.push(v.into());
|
||||
});
|
||||
|
@ -2797,7 +2750,7 @@ impl LoroMap {
|
|||
pub fn insert_container(&mut self, key: &str, child: JsContainer) -> JsResult<JsContainer> {
|
||||
let child = convert::js_to_container(child)?;
|
||||
let c = self.handler.insert_container(key, child.to_handler())?;
|
||||
Ok(handler_to_js_value(c, self.doc.clone()).into())
|
||||
Ok(handler_to_js_value(c).into())
|
||||
}
|
||||
|
||||
/// Subscribe to the changes of the map.
|
||||
|
@ -2829,14 +2782,13 @@ impl LoroMap {
|
|||
pub fn subscribe(&self, f: js_sys::Function) -> JsResult<JsValue> {
|
||||
let observer = observer::Observer::new(f);
|
||||
let doc = self
|
||||
.doc
|
||||
.clone()
|
||||
.handler
|
||||
.doc()
|
||||
.ok_or_else(|| JsError::new("Document is not attached"))?;
|
||||
let doc_clone = doc.clone();
|
||||
let sub = doc.subscribe(
|
||||
&self.handler.id(),
|
||||
Arc::new(move |e| {
|
||||
call_after_micro_task(observer.clone(), e, &doc_clone);
|
||||
call_after_micro_task(observer.clone(), e);
|
||||
}),
|
||||
);
|
||||
|
||||
|
@ -2866,7 +2818,7 @@ impl LoroMap {
|
|||
/// the WASM boundary.
|
||||
pub fn parent(&self) -> JsContainerOrUndefined {
|
||||
if let Some(p) = self.handler.parent() {
|
||||
handler_to_js_value(p, self.doc.clone()).into()
|
||||
handler_to_js_value(p).into()
|
||||
} else {
|
||||
JsContainerOrUndefined::from(JsValue::UNDEFINED)
|
||||
}
|
||||
|
@ -2893,7 +2845,7 @@ impl LoroMap {
|
|||
let Some(h) = self.handler.get_attached() else {
|
||||
return JsValue::UNDEFINED.into();
|
||||
};
|
||||
handler_to_js_value(Handler::Map(h), self.doc.clone()).into()
|
||||
handler_to_js_value(Handler::Map(h)).into()
|
||||
}
|
||||
|
||||
/// Delete all key-value pairs in the map.
|
||||
|
@ -2959,7 +2911,6 @@ impl Default for LoroMap {
|
|||
#[wasm_bindgen]
|
||||
pub struct LoroList {
|
||||
handler: ListHandler,
|
||||
doc: Option<Arc<LoroDocInner>>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
|
@ -2972,7 +2923,6 @@ impl LoroList {
|
|||
pub fn new() -> Self {
|
||||
Self {
|
||||
handler: ListHandler::new_detached(),
|
||||
doc: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3038,7 +2988,7 @@ impl LoroList {
|
|||
|
||||
(match v {
|
||||
ValueOrHandler::Value(v) => v.into(),
|
||||
ValueOrHandler::Handler(h) => handler_to_js_value(h, self.doc.clone()),
|
||||
ValueOrHandler::Handler(h) => handler_to_js_value(h),
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
@ -3075,7 +3025,7 @@ impl LoroList {
|
|||
v.into()
|
||||
}
|
||||
ValueOrHandler::Handler(h) => {
|
||||
let v: JsValue = handler_to_js_value(h, self.doc.clone());
|
||||
let v: JsValue = handler_to_js_value(h);
|
||||
v.into()
|
||||
}
|
||||
});
|
||||
|
@ -3120,7 +3070,7 @@ impl LoroList {
|
|||
pub fn insert_container(&mut self, index: usize, child: JsContainer) -> JsResult<JsContainer> {
|
||||
let child = js_to_container(child)?;
|
||||
let c = self.handler.insert_container(index, child.to_handler())?;
|
||||
Ok(handler_to_js_value(c, self.doc.clone()).into())
|
||||
Ok(handler_to_js_value(c).into())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "pushContainer", skip_typescript)]
|
||||
|
@ -3155,14 +3105,13 @@ impl LoroList {
|
|||
pub fn subscribe(&self, f: js_sys::Function) -> JsResult<JsValue> {
|
||||
let observer = observer::Observer::new(f);
|
||||
let doc = self
|
||||
.doc
|
||||
.clone()
|
||||
.handler
|
||||
.doc()
|
||||
.ok_or_else(|| JsError::new("Document is not attached"))?;
|
||||
let doc_clone = doc.clone();
|
||||
let sub = doc.subscribe(
|
||||
&self.handler.id(),
|
||||
Arc::new(move |e| {
|
||||
call_after_micro_task(observer.clone(), e, &doc_clone);
|
||||
call_after_micro_task(observer.clone(), e);
|
||||
}),
|
||||
);
|
||||
Ok(subscription_to_js_function_callback(sub))
|
||||
|
@ -3193,7 +3142,7 @@ impl LoroList {
|
|||
/// the WASM boundary.
|
||||
pub fn parent(&self) -> JsContainerOrUndefined {
|
||||
if let Some(p) = self.handler.parent() {
|
||||
handler_to_js_value(p, self.doc.clone()).into()
|
||||
handler_to_js_value(p).into()
|
||||
} else {
|
||||
JsContainerOrUndefined::from(JsValue::UNDEFINED)
|
||||
}
|
||||
|
@ -3218,7 +3167,7 @@ impl LoroList {
|
|||
}
|
||||
|
||||
if let Some(h) = self.handler.get_attached() {
|
||||
handler_to_js_value(Handler::List(h), self.doc.clone()).into()
|
||||
handler_to_js_value(Handler::List(h)).into()
|
||||
} else {
|
||||
JsValue::UNDEFINED.into()
|
||||
}
|
||||
|
@ -3311,7 +3260,6 @@ impl Default for LoroList {
|
|||
#[wasm_bindgen]
|
||||
pub struct LoroMovableList {
|
||||
handler: MovableListHandler,
|
||||
doc: Option<Arc<LoroDocInner>>,
|
||||
}
|
||||
|
||||
impl Default for LoroMovableList {
|
||||
|
@ -3330,7 +3278,6 @@ impl LoroMovableList {
|
|||
pub fn new() -> Self {
|
||||
Self {
|
||||
handler: MovableListHandler::new_detached(),
|
||||
doc: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3396,7 +3343,7 @@ impl LoroMovableList {
|
|||
|
||||
(match v {
|
||||
ValueOrHandler::Value(v) => v.into(),
|
||||
ValueOrHandler::Handler(h) => handler_to_js_value(h, self.doc.clone()),
|
||||
ValueOrHandler::Handler(h) => handler_to_js_value(h),
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
@ -3433,7 +3380,7 @@ impl LoroMovableList {
|
|||
v.into()
|
||||
}
|
||||
ValueOrHandler::Handler(h) => {
|
||||
let v: JsValue = handler_to_js_value(h, self.doc.clone());
|
||||
let v: JsValue = handler_to_js_value(h);
|
||||
v.into()
|
||||
}
|
||||
});
|
||||
|
@ -3478,7 +3425,7 @@ impl LoroMovableList {
|
|||
pub fn insert_container(&mut self, index: usize, child: JsContainer) -> JsResult<JsContainer> {
|
||||
let child = js_to_container(child)?;
|
||||
let c = self.handler.insert_container(index, child.to_handler())?;
|
||||
Ok(handler_to_js_value(c, self.doc.clone()).into())
|
||||
Ok(handler_to_js_value(c).into())
|
||||
}
|
||||
|
||||
/// Push a container to the end of the list.
|
||||
|
@ -3514,14 +3461,13 @@ impl LoroMovableList {
|
|||
pub fn subscribe(&self, f: js_sys::Function) -> JsResult<JsValue> {
|
||||
let observer = observer::Observer::new(f);
|
||||
let loro = self
|
||||
.doc
|
||||
.as_ref()
|
||||
.handler
|
||||
.doc()
|
||||
.ok_or_else(|| JsError::new("Document is not attached"))?;
|
||||
let doc_clone = loro.clone();
|
||||
let sub = loro.subscribe(
|
||||
&self.handler.id(),
|
||||
Arc::new(move |e| {
|
||||
call_after_micro_task(observer.clone(), e, &doc_clone);
|
||||
call_after_micro_task(observer.clone(), e);
|
||||
}),
|
||||
);
|
||||
Ok(subscription_to_js_function_callback(sub))
|
||||
|
@ -3552,7 +3498,7 @@ impl LoroMovableList {
|
|||
/// the WASM boundary.
|
||||
pub fn parent(&self) -> JsContainerOrUndefined {
|
||||
if let Some(p) = self.handler.parent() {
|
||||
handler_to_js_value(p, self.doc.clone()).into()
|
||||
handler_to_js_value(p).into()
|
||||
} else {
|
||||
JsContainerOrUndefined::from(JsValue::UNDEFINED)
|
||||
}
|
||||
|
@ -3577,7 +3523,7 @@ impl LoroMovableList {
|
|||
}
|
||||
|
||||
if let Some(h) = self.handler.get_attached() {
|
||||
handler_to_js_value(Handler::MovableList(h), self.doc.clone()).into()
|
||||
handler_to_js_value(Handler::MovableList(h)).into()
|
||||
} else {
|
||||
JsValue::UNDEFINED.into()
|
||||
}
|
||||
|
@ -3633,7 +3579,7 @@ impl LoroMovableList {
|
|||
pub fn setContainer(&self, pos: usize, child: JsContainer) -> JsResult<JsContainer> {
|
||||
let child = js_to_container(child)?;
|
||||
let c = self.handler.set_container(pos, child.to_handler())?;
|
||||
Ok(handler_to_js_value(c, self.doc.clone()).into())
|
||||
Ok(handler_to_js_value(c).into())
|
||||
}
|
||||
|
||||
/// Push a value to the end of the list.
|
||||
|
@ -3709,7 +3655,6 @@ impl LoroMovableList {
|
|||
#[wasm_bindgen]
|
||||
pub struct LoroTree {
|
||||
handler: TreeHandler,
|
||||
doc: Option<Arc<LoroDocInner>>,
|
||||
}
|
||||
|
||||
extern crate alloc;
|
||||
|
@ -3720,7 +3665,6 @@ extern crate alloc;
|
|||
pub struct LoroTreeNode {
|
||||
id: TreeID,
|
||||
tree: TreeHandler,
|
||||
doc: Option<Arc<LoroDocInner>>,
|
||||
}
|
||||
|
||||
fn parse_js_parent(parent: &JsParentTreeID) -> JsResult<Option<TreeID>> {
|
||||
|
@ -3752,8 +3696,8 @@ fn parse_js_tree_id(target: &JsTreeID) -> JsResult<TreeID> {
|
|||
|
||||
#[wasm_bindgen]
|
||||
impl LoroTreeNode {
|
||||
fn from_tree(id: TreeID, tree: TreeHandler, doc: Option<Arc<LoroDocInner>>) -> Self {
|
||||
Self { id, tree, doc }
|
||||
fn from_tree(id: TreeID, tree: TreeHandler) -> Self {
|
||||
Self { id, tree }
|
||||
}
|
||||
|
||||
/// The TreeID of the node.
|
||||
|
@ -3789,7 +3733,7 @@ impl LoroTreeNode {
|
|||
} else {
|
||||
self.tree.create(TreeParentId::Node(self.id))?
|
||||
};
|
||||
let node = LoroTreeNode::from_tree(id, self.tree.clone(), self.doc.clone());
|
||||
let node = LoroTreeNode::from_tree(id, self.tree.clone());
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
|
@ -3880,10 +3824,7 @@ impl LoroTreeNode {
|
|||
#[wasm_bindgen(getter, skip_typescript)]
|
||||
pub fn data(&self) -> JsResult<LoroMap> {
|
||||
let data = self.tree.get_meta(self.id)?;
|
||||
let map = LoroMap {
|
||||
handler: data,
|
||||
doc: self.doc.clone(),
|
||||
};
|
||||
let map = LoroMap { handler: data };
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
|
@ -3901,7 +3842,6 @@ impl LoroTreeNode {
|
|||
};
|
||||
LoroTree {
|
||||
handler: self.tree.clone(),
|
||||
doc: self.doc.clone(),
|
||||
}
|
||||
.tree_node_to_js_obj(node, true)
|
||||
}
|
||||
|
@ -3915,7 +3855,7 @@ impl LoroTreeNode {
|
|||
.ok_or(JsValue::from_str(&format!("TreeID({}) not found", self.id)))?;
|
||||
let ans = parent
|
||||
.tree_id()
|
||||
.map(|p| LoroTreeNode::from_tree(p, self.tree.clone(), self.doc.clone()));
|
||||
.map(|p| LoroTreeNode::from_tree(p, self.tree.clone()));
|
||||
Ok(ans)
|
||||
}
|
||||
|
||||
|
@ -3929,7 +3869,7 @@ impl LoroTreeNode {
|
|||
return JsValue::undefined();
|
||||
};
|
||||
let children = children.into_iter().map(|c| {
|
||||
let node = LoroTreeNode::from_tree(c, self.tree.clone(), self.doc.clone());
|
||||
let node = LoroTreeNode::from_tree(c, self.tree.clone());
|
||||
JsValue::from(node)
|
||||
});
|
||||
Array::from_iter(children).into()
|
||||
|
@ -3970,7 +3910,6 @@ impl LoroTree {
|
|||
pub fn new() -> Self {
|
||||
Self {
|
||||
handler: TreeHandler::new_detached(),
|
||||
doc: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4009,7 +3948,7 @@ impl LoroTree {
|
|||
} else {
|
||||
self.handler.create(parent.into())?
|
||||
};
|
||||
let node = LoroTreeNode::from_tree(id, self.handler.clone(), self.doc.clone());
|
||||
let node = LoroTreeNode::from_tree(id, self.handler.clone());
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
|
@ -4076,11 +4015,7 @@ impl LoroTree {
|
|||
if self.handler.is_node_unexist(&target) {
|
||||
return None;
|
||||
}
|
||||
Some(LoroTreeNode::from_tree(
|
||||
target,
|
||||
self.handler.clone(),
|
||||
self.doc.clone(),
|
||||
))
|
||||
Some(LoroTreeNode::from_tree(target, self.handler.clone()))
|
||||
}
|
||||
|
||||
/// Get the id of the container.
|
||||
|
@ -4132,12 +4067,12 @@ impl LoroTree {
|
|||
};
|
||||
let nodes = Array::new();
|
||||
for v in self.handler.get_nodes_under(TreeParentId::Root) {
|
||||
let node = LoroTreeNode::from_tree(v.id, self.handler.clone(), self.doc.clone());
|
||||
let node = LoroTreeNode::from_tree(v.id, self.handler.clone());
|
||||
nodes.push(&node.into());
|
||||
}
|
||||
if with_deleted {
|
||||
for v in self.handler.get_nodes_under(TreeParentId::Deleted) {
|
||||
let node = LoroTreeNode::from_tree(v.id, self.handler.clone(), self.doc.clone());
|
||||
let node = LoroTreeNode::from_tree(v.id, self.handler.clone());
|
||||
nodes.push(&node.into());
|
||||
}
|
||||
}
|
||||
|
@ -4224,7 +4159,7 @@ impl LoroTree {
|
|||
self.handler
|
||||
.nodes()
|
||||
.into_iter()
|
||||
.map(|n| LoroTreeNode::from_tree(n, self.handler.clone(), self.doc.clone()))
|
||||
.map(|n| LoroTreeNode::from_tree(n, self.handler.clone()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
@ -4233,7 +4168,7 @@ impl LoroTree {
|
|||
self.handler
|
||||
.roots()
|
||||
.into_iter()
|
||||
.map(|n| LoroTreeNode::from_tree(n, self.handler.clone(), self.doc.clone()))
|
||||
.map(|n| LoroTreeNode::from_tree(n, self.handler.clone()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
@ -4278,14 +4213,13 @@ impl LoroTree {
|
|||
pub fn subscribe(&self, f: js_sys::Function) -> JsResult<JsValue> {
|
||||
let observer = observer::Observer::new(f);
|
||||
let doc = self
|
||||
.doc
|
||||
.clone()
|
||||
.handler
|
||||
.doc()
|
||||
.ok_or_else(|| JsError::new("Document is not attached"))?;
|
||||
let doc_clone = doc.clone();
|
||||
let ans = doc.subscribe(
|
||||
&self.handler.id(),
|
||||
Arc::new(move |e| {
|
||||
call_after_micro_task(observer.clone(), e, &doc_clone);
|
||||
call_after_micro_task(observer.clone(), e);
|
||||
}),
|
||||
);
|
||||
Ok(subscription_to_js_function_callback(ans))
|
||||
|
@ -4298,7 +4232,7 @@ impl LoroTree {
|
|||
/// the WASM boundary.
|
||||
pub fn parent(&self) -> JsContainerOrUndefined {
|
||||
if let Some(p) = HandlerTrait::parent(&self.handler) {
|
||||
handler_to_js_value(p, self.doc.clone()).into()
|
||||
handler_to_js_value(p).into()
|
||||
} else {
|
||||
JsContainerOrUndefined::from(JsValue::UNDEFINED)
|
||||
}
|
||||
|
@ -4323,7 +4257,7 @@ impl LoroTree {
|
|||
}
|
||||
|
||||
if let Some(h) = self.handler.get_attached() {
|
||||
handler_to_js_value(Handler::Tree(h), self.doc.clone()).into()
|
||||
handler_to_js_value(Handler::Tree(h)).into()
|
||||
} else {
|
||||
JsValue::UNDEFINED.into()
|
||||
}
|
||||
|
@ -4500,17 +4434,14 @@ impl Cursor {
|
|||
}
|
||||
}
|
||||
|
||||
fn loro_value_to_js_value_or_container(
|
||||
value: ValueOrHandler,
|
||||
doc: Option<Arc<LoroDocInner>>,
|
||||
) -> JsValue {
|
||||
fn loro_value_to_js_value_or_container(value: ValueOrHandler) -> JsValue {
|
||||
match value {
|
||||
ValueOrHandler::Value(v) => {
|
||||
let value: JsValue = v.into();
|
||||
value
|
||||
}
|
||||
ValueOrHandler::Handler(c) => {
|
||||
let handler: JsValue = handler_to_js_value(c, doc.clone());
|
||||
let handler: JsValue = handler_to_js_value(c);
|
||||
handler
|
||||
}
|
||||
}
|
||||
|
@ -4532,7 +4463,6 @@ fn loro_value_to_js_value_or_container(
|
|||
#[derive(Debug)]
|
||||
pub struct UndoManager {
|
||||
undo: InnerUndoManager,
|
||||
doc: Arc<LoroDocInner>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
|
@ -4587,10 +4517,7 @@ impl UndoManager {
|
|||
undo.add_exclude_origin_prefix(&prefix);
|
||||
}
|
||||
|
||||
let mut ans = UndoManager {
|
||||
undo,
|
||||
doc: doc.0.clone(),
|
||||
};
|
||||
let mut ans = UndoManager { undo };
|
||||
|
||||
if let Some(on_push) = on_push {
|
||||
ans.setOnPush(on_push);
|
||||
|
@ -4603,13 +4530,13 @@ impl UndoManager {
|
|||
|
||||
/// Undo the last operation.
|
||||
pub fn undo(&mut self) -> JsResult<bool> {
|
||||
let executed = self.undo.undo(&self.doc)?;
|
||||
let executed = self.undo.undo()?;
|
||||
Ok(executed)
|
||||
}
|
||||
|
||||
/// Redo the last undone operation.
|
||||
pub fn redo(&mut self) -> JsResult<bool> {
|
||||
let executed = self.undo.redo(&self.doc)?;
|
||||
let executed = self.undo.redo()?;
|
||||
Ok(executed)
|
||||
}
|
||||
|
||||
|
@ -4643,17 +4570,11 @@ impl UndoManager {
|
|||
self.undo.add_exclude_origin_prefix(&prefix)
|
||||
}
|
||||
|
||||
/// Check if the undo manager is bound to the given document.
|
||||
pub fn checkBinding(&self, doc: &LoroDoc) -> bool {
|
||||
Arc::ptr_eq(&self.doc, &doc.0)
|
||||
}
|
||||
|
||||
/// Set the on push event listener.
|
||||
///
|
||||
/// Every time an undo step or redo step is pushed, the on push event listener will be called.
|
||||
#[wasm_bindgen(skip_typescript)]
|
||||
pub fn setOnPush(&mut self, on_push: JsValue) {
|
||||
let doc = Arc::downgrade(&self.doc);
|
||||
let on_push = on_push.dyn_into::<js_sys::Function>().ok();
|
||||
if let Some(on_push) = on_push {
|
||||
let on_push = observer::Observer::new(on_push);
|
||||
|
@ -4676,12 +4597,8 @@ impl UndoManager {
|
|||
|
||||
let mut undo_item_meta = UndoItemMeta::new();
|
||||
let r = if let Some(e) = event {
|
||||
if let Some(doc_ref) = doc.upgrade() {
|
||||
let diff = diff_event_to_js_value(e, &doc_ref);
|
||||
on_push.call3(&is_undo, &counter_range, &diff)
|
||||
} else {
|
||||
on_push.call2(&is_undo, &counter_range)
|
||||
}
|
||||
let diff = diff_event_to_js_value(e);
|
||||
on_push.call3(&is_undo, &counter_range, &diff)
|
||||
} else {
|
||||
on_push.call2(&is_undo, &counter_range)
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@ use loro_internal::{
|
|||
container::ContainerID, handler::counter::CounterHandler, HandlerTrait, LoroResult,
|
||||
};
|
||||
|
||||
use crate::{Container, ContainerTrait, SealedTrait};
|
||||
use crate::{Container, ContainerTrait, LoroDoc, SealedTrait};
|
||||
|
||||
/// A counter that can be incremented or decremented.
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -81,4 +81,8 @@ impl ContainerTrait for LoroCounter {
|
|||
fn is_deleted(&self) -> bool {
|
||||
self.handler.is_deleted()
|
||||
}
|
||||
|
||||
fn doc(&self) -> Option<LoroDoc> {
|
||||
self.handler.doc().map(LoroDoc::_new)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -998,6 +998,8 @@ pub trait ContainerTrait: SealedTrait {
|
|||
Self: Sized;
|
||||
/// Whether the container is deleted.
|
||||
fn is_deleted(&self) -> bool;
|
||||
/// Get the doc of the container.
|
||||
fn doc(&self) -> Option<LoroDoc>;
|
||||
}
|
||||
|
||||
/// LoroList container. It's used to model array.
|
||||
|
@ -1053,6 +1055,10 @@ impl ContainerTrait for LoroList {
|
|||
fn is_deleted(&self) -> bool {
|
||||
self.handler.is_deleted()
|
||||
}
|
||||
|
||||
fn doc(&self) -> Option<LoroDoc> {
|
||||
self.handler.doc().map(LoroDoc::_new)
|
||||
}
|
||||
}
|
||||
|
||||
impl LoroList {
|
||||
|
@ -1321,6 +1327,10 @@ impl ContainerTrait for LoroMap {
|
|||
fn is_deleted(&self) -> bool {
|
||||
self.handler.is_deleted()
|
||||
}
|
||||
|
||||
fn doc(&self) -> Option<LoroDoc> {
|
||||
self.handler.doc().map(LoroDoc::_new)
|
||||
}
|
||||
}
|
||||
|
||||
impl LoroMap {
|
||||
|
@ -1485,6 +1495,10 @@ impl ContainerTrait for LoroText {
|
|||
fn is_deleted(&self) -> bool {
|
||||
self.handler.is_deleted()
|
||||
}
|
||||
|
||||
fn doc(&self) -> Option<LoroDoc> {
|
||||
self.handler.doc().map(LoroDoc::_new)
|
||||
}
|
||||
}
|
||||
|
||||
impl LoroText {
|
||||
|
@ -1850,6 +1864,9 @@ impl ContainerTrait for LoroTree {
|
|||
fn is_deleted(&self) -> bool {
|
||||
self.handler.is_deleted()
|
||||
}
|
||||
fn doc(&self) -> Option<LoroDoc> {
|
||||
self.handler.doc().map(LoroDoc::_new)
|
||||
}
|
||||
}
|
||||
|
||||
/// A tree node in the [LoroTree].
|
||||
|
@ -2258,6 +2275,9 @@ impl ContainerTrait for LoroMovableList {
|
|||
fn is_deleted(&self) -> bool {
|
||||
self.handler.is_deleted()
|
||||
}
|
||||
fn doc(&self) -> Option<LoroDoc> {
|
||||
self.handler.doc().map(LoroDoc::_new)
|
||||
}
|
||||
}
|
||||
|
||||
impl LoroMovableList {
|
||||
|
@ -2533,6 +2553,9 @@ impl ContainerTrait for LoroUnknown {
|
|||
fn is_deleted(&self) -> bool {
|
||||
self.handler.is_deleted()
|
||||
}
|
||||
fn doc(&self) -> Option<LoroDoc> {
|
||||
self.handler.doc().map(LoroDoc::_new)
|
||||
}
|
||||
}
|
||||
|
||||
use enum_as_inner::EnumAsInner;
|
||||
|
@ -2636,6 +2659,18 @@ impl ContainerTrait for Container {
|
|||
Container::Unknown(x) => x.is_deleted(),
|
||||
}
|
||||
}
|
||||
fn doc(&self) -> Option<LoroDoc> {
|
||||
match self {
|
||||
Container::List(x) => x.doc(),
|
||||
Container::Map(x) => x.doc(),
|
||||
Container::Text(x) => x.doc(),
|
||||
Container::Tree(x) => x.doc(),
|
||||
Container::MovableList(x) => x.doc(),
|
||||
#[cfg(feature = "counter")]
|
||||
Container::Counter(x) => x.doc(),
|
||||
Container::Unknown(x) => x.doc(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Container {
|
||||
|
@ -2750,18 +2785,18 @@ impl UndoManager {
|
|||
}
|
||||
|
||||
/// Undo the last change made by the peer.
|
||||
pub fn undo(&mut self, doc: &LoroDoc) -> LoroResult<bool> {
|
||||
self.0.undo(&doc.doc)
|
||||
pub fn undo(&mut self) -> LoroResult<bool> {
|
||||
self.0.undo()
|
||||
}
|
||||
|
||||
/// Redo the last change made by the peer.
|
||||
pub fn redo(&mut self, doc: &LoroDoc) -> LoroResult<bool> {
|
||||
self.0.redo(&doc.doc)
|
||||
pub fn redo(&mut self) -> LoroResult<bool> {
|
||||
self.0.redo()
|
||||
}
|
||||
|
||||
/// Record a new checkpoint.
|
||||
pub fn record_new_checkpoint(&mut self, doc: &LoroDoc) -> LoroResult<()> {
|
||||
self.0.record_new_checkpoint(&doc.doc)
|
||||
pub fn record_new_checkpoint(&mut self) -> LoroResult<()> {
|
||||
self.0.record_new_checkpoint()
|
||||
}
|
||||
|
||||
/// Whether the undo manager can undo.
|
||||
|
|
|
@ -250,9 +250,9 @@ fn undo_still_works_after_detached_editing() {
|
|||
doc.commit();
|
||||
doc.get_text("text").insert(5, " world!").unwrap();
|
||||
doc.commit();
|
||||
undo.undo(&doc).unwrap();
|
||||
undo.undo().unwrap();
|
||||
assert_eq!(doc.get_text("text").to_string(), "Hello");
|
||||
undo.redo(&doc).unwrap();
|
||||
undo.redo().unwrap();
|
||||
assert_eq!(doc.get_text("text").to_string(), "Hello world!");
|
||||
|
||||
doc.set_detached_editing(true);
|
||||
|
@ -263,10 +263,10 @@ fn undo_still_works_after_detached_editing() {
|
|||
doc.commit();
|
||||
assert!(undo.can_undo());
|
||||
assert!(!undo.can_redo());
|
||||
undo.undo(&doc).unwrap();
|
||||
undo.undo().unwrap();
|
||||
assert_eq!(doc.get_text("text").to_string(), "Hello");
|
||||
assert!(!undo.can_undo());
|
||||
assert!(undo.can_redo());
|
||||
undo.redo(&doc).unwrap();
|
||||
undo.redo().unwrap();
|
||||
assert_eq!(doc.get_text("text").to_string(), "Hello alice!");
|
||||
}
|
||||
|
|
|
@ -27,14 +27,14 @@ fn basic_list_undo_insertion() -> Result<(), LoroError> {
|
|||
"list": ["12", "34"]
|
||||
})
|
||||
);
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
"list": ["12"]
|
||||
})
|
||||
);
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
|
@ -63,7 +63,7 @@ fn basic_list_undo_deletion() -> Result<(), LoroError> {
|
|||
"list": ["12"]
|
||||
})
|
||||
);
|
||||
undo.undo(&doc)?; // op 3
|
||||
undo.undo()?; // op 3
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
|
@ -73,7 +73,7 @@ fn basic_list_undo_deletion() -> Result<(), LoroError> {
|
|||
|
||||
// Now, to undo "34" correctly we need to include the latest change
|
||||
// If we only undo op 1, op 3 will create "34" again.
|
||||
undo.undo(&doc)?; // op 4
|
||||
undo.undo()?; // op 4
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
|
@ -97,23 +97,23 @@ fn basic_map_undo() -> Result<(), LoroError> {
|
|||
doc_a.commit();
|
||||
doc_a.get_map("map").delete("a")?;
|
||||
doc_a.commit();
|
||||
undo.undo(&doc_a)?; // op 3
|
||||
undo.undo()?; // op 3
|
||||
assert_eq!(
|
||||
doc_a.get_deep_value().to_json_value(),
|
||||
json!({"map": {"a": "a", "b": "b"}})
|
||||
);
|
||||
|
||||
undo.undo(&doc_a)?; // op 4
|
||||
undo.undo()?; // op 4
|
||||
assert_eq!(
|
||||
doc_a.get_deep_value().to_json_value(),
|
||||
json!({"map": {"a": "a"}})
|
||||
);
|
||||
|
||||
undo.undo(&doc_a)?; // op 5
|
||||
undo.undo()?; // op 5
|
||||
assert_eq!(doc_a.get_deep_value().to_json_value(), json!({"map": {}}));
|
||||
|
||||
// Redo
|
||||
undo.redo(&doc_a)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(
|
||||
doc_a.get_deep_value().to_json_value(),
|
||||
json!({"map": {
|
||||
|
@ -122,7 +122,7 @@ fn basic_map_undo() -> Result<(), LoroError> {
|
|||
);
|
||||
|
||||
// Redo
|
||||
undo.redo(&doc_a)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(
|
||||
doc_a.get_deep_value().to_json_value(),
|
||||
json!({"map": {
|
||||
|
@ -132,7 +132,7 @@ fn basic_map_undo() -> Result<(), LoroError> {
|
|||
);
|
||||
|
||||
// Redo
|
||||
undo.redo(&doc_a)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(
|
||||
doc_a.get_deep_value().to_json_value(),
|
||||
json!({"map": {
|
||||
|
@ -157,7 +157,7 @@ fn map_collaborative_undo() -> Result<(), LoroError> {
|
|||
doc_b.commit();
|
||||
|
||||
doc_a.import(&doc_b.export_from(&Default::default()))?;
|
||||
undo.undo(&doc_a)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(
|
||||
doc_a.get_deep_value().to_json_value(),
|
||||
json!({"map": {"b": "b"}})
|
||||
|
@ -177,17 +177,17 @@ fn map_container_undo() -> Result<(), LoroError> {
|
|||
doc.commit();
|
||||
map.insert("number", 0)?; // op 2
|
||||
doc.commit();
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({"map": {"text": "T"}})
|
||||
);
|
||||
undo.undo(&doc)?;
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
undo.undo()?;
|
||||
assert_eq!(doc.get_deep_value().to_json_value(), json!({"map": {}}));
|
||||
undo.redo(&doc)?;
|
||||
undo.redo(&doc)?;
|
||||
undo.redo(&doc)?;
|
||||
undo.redo()?;
|
||||
undo.redo()?;
|
||||
undo.redo()?;
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({"map": {"text": "T", "number": 0}})
|
||||
|
@ -228,17 +228,17 @@ fn one_register_collaborative_undo() -> Result<(), LoroError> {
|
|||
sync(&doc_a, &doc_b);
|
||||
let mut undo = UndoManager::new(&doc_a);
|
||||
doc_a.get_map("map").insert("color", "red")?;
|
||||
undo.record_new_checkpoint(&doc_a)?;
|
||||
undo.record_new_checkpoint()?;
|
||||
sync(&doc_a, &doc_b);
|
||||
doc_b.get_map("map").insert("color", "green")?;
|
||||
sync(&doc_a, &doc_b);
|
||||
undo.record_new_checkpoint(&doc_a)?;
|
||||
undo.undo(&doc_a)?;
|
||||
undo.record_new_checkpoint()?;
|
||||
undo.undo()?;
|
||||
assert_eq!(
|
||||
doc_a.get_deep_value().to_json_value(),
|
||||
json!({"map": {"color": "black"}})
|
||||
);
|
||||
undo.redo(&doc_a)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(
|
||||
doc_a.get_deep_value().to_json_value(),
|
||||
json!({"map": {"color": "green"}})
|
||||
|
@ -302,7 +302,7 @@ fn undo_id_span_that_contains_remote_deps_inside() -> Result<(), LoroError> {
|
|||
})
|
||||
);
|
||||
while undo_a.can_undo() {
|
||||
undo_a.undo(&doc_a)?;
|
||||
undo_a.undo()?;
|
||||
}
|
||||
assert_eq!(
|
||||
doc_a.get_deep_value().to_json_value(),
|
||||
|
@ -336,7 +336,7 @@ fn undo_id_span_that_contains_remote_deps_inside_many_times() -> Result<(), Loro
|
|||
|
||||
// Undo all ops from A
|
||||
while undo.can_undo() {
|
||||
undo.undo(&doc_a)?;
|
||||
undo.undo()?;
|
||||
}
|
||||
assert_eq!(
|
||||
doc_a.get_deep_value().to_json_value(),
|
||||
|
@ -353,25 +353,25 @@ fn undo_manager() -> Result<(), LoroError> {
|
|||
doc.set_peer_id(1)?;
|
||||
let mut undo = UndoManager::new(&doc);
|
||||
doc.get_text("text").insert(0, "123")?;
|
||||
undo.record_new_checkpoint(&doc)?;
|
||||
undo.record_new_checkpoint()?;
|
||||
doc.get_text("text").insert(3, "456")?;
|
||||
undo.record_new_checkpoint(&doc)?;
|
||||
undo.record_new_checkpoint()?;
|
||||
doc.get_text("text").insert(6, "789")?;
|
||||
undo.record_new_checkpoint(&doc)?;
|
||||
undo.record_new_checkpoint()?;
|
||||
for i in 0..10 {
|
||||
info_span!("round", i).in_scope(|| {
|
||||
assert_eq!(doc.get_text("text").to_string(), "123456789");
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(doc.get_text("text").to_string(), "123456");
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(doc.get_text("text").to_string(), "123");
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(doc.get_text("text").to_string(), "");
|
||||
undo.redo(&doc)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(doc.get_text("text").to_string(), "123");
|
||||
undo.redo(&doc)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(doc.get_text("text").to_string(), "123456");
|
||||
undo.redo(&doc)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(doc.get_text("text").to_string(), "123456789");
|
||||
Ok::<(), loro::LoroError>(())
|
||||
})?;
|
||||
|
@ -386,11 +386,11 @@ fn undo_manager_with_sub_container() -> Result<(), LoroError> {
|
|||
doc.set_peer_id(1)?;
|
||||
let mut undo = UndoManager::new(&doc);
|
||||
let map = doc.get_list("list").insert_container(0, LoroMap::new())?;
|
||||
undo.record_new_checkpoint(&doc)?;
|
||||
undo.record_new_checkpoint()?;
|
||||
let text = map.insert_container("text", LoroText::new())?;
|
||||
undo.record_new_checkpoint(&doc)?;
|
||||
undo.record_new_checkpoint()?;
|
||||
text.insert(0, "123")?;
|
||||
undo.record_new_checkpoint(&doc)?;
|
||||
undo.record_new_checkpoint()?;
|
||||
for i in 0..10 {
|
||||
info_span!("round", ?i).in_scope(|| {
|
||||
assert_eq!(
|
||||
|
@ -401,7 +401,7 @@ fn undo_manager_with_sub_container() -> Result<(), LoroError> {
|
|||
}]
|
||||
})
|
||||
);
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
|
@ -410,28 +410,28 @@ fn undo_manager_with_sub_container() -> Result<(), LoroError> {
|
|||
}]
|
||||
})
|
||||
);
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
"list": [{}]
|
||||
})
|
||||
);
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
"list": []
|
||||
})
|
||||
);
|
||||
undo.redo(&doc)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
"list": [{}]
|
||||
})
|
||||
);
|
||||
undo.redo(&doc)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
|
@ -440,7 +440,7 @@ fn undo_manager_with_sub_container() -> Result<(), LoroError> {
|
|||
}]
|
||||
})
|
||||
);
|
||||
undo.redo(&doc)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
|
@ -465,29 +465,29 @@ fn test_undo_container_deletion() -> LoroResult<()> {
|
|||
|
||||
let map = doc.get_map("map");
|
||||
let text = map.insert_container("text", LoroText::new())?;
|
||||
undo.record_new_checkpoint(&doc)?;
|
||||
undo.record_new_checkpoint()?;
|
||||
text.insert(0, "T")?;
|
||||
undo.record_new_checkpoint(&doc)?;
|
||||
undo.record_new_checkpoint()?;
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({"map": {"text": "T"}})
|
||||
);
|
||||
map.delete("text")?;
|
||||
assert_eq!(doc.get_deep_value().to_json_value(), json!({"map": {}}));
|
||||
undo.record_new_checkpoint(&doc)?;
|
||||
undo.undo(&doc)?;
|
||||
undo.record_new_checkpoint()?;
|
||||
undo.undo()?;
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({"map": {"text": "T"}})
|
||||
);
|
||||
undo.redo(&doc)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(doc.get_deep_value().to_json_value(), json!({"map": {}}));
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({"map": {"text": "T"}})
|
||||
);
|
||||
undo.redo(&doc)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(doc.get_deep_value().to_json_value(), json!({"map": {}}));
|
||||
doc.commit();
|
||||
Ok(())
|
||||
|
@ -526,9 +526,9 @@ fn undo_richtext_editing() -> LoroResult<()> {
|
|||
let mut undo = UndoManager::new(&doc);
|
||||
let text = doc.get_text("text");
|
||||
text.insert(0, "Hello")?;
|
||||
undo.record_new_checkpoint(&doc)?;
|
||||
undo.record_new_checkpoint()?;
|
||||
text.mark(0..5, "bold", true)?;
|
||||
undo.record_new_checkpoint(&doc)?;
|
||||
undo.record_new_checkpoint()?;
|
||||
assert_eq!(
|
||||
text.get_richtext_value().to_json_value(),
|
||||
json!([
|
||||
|
@ -537,17 +537,17 @@ fn undo_richtext_editing() -> LoroResult<()> {
|
|||
);
|
||||
for i in 0..10 {
|
||||
debug_span!("round", i).in_scope(|| {
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(
|
||||
text.get_richtext_value().to_json_value(),
|
||||
json!([
|
||||
{"insert": "Hello", }
|
||||
])
|
||||
);
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(text.get_richtext_value().to_json_value(), json!([]));
|
||||
debug_span!("redo 1").in_scope(|| {
|
||||
undo.redo(&doc).unwrap();
|
||||
undo.redo().unwrap();
|
||||
});
|
||||
assert_eq!(
|
||||
text.get_richtext_value().to_json_value(),
|
||||
|
@ -556,7 +556,7 @@ fn undo_richtext_editing() -> LoroResult<()> {
|
|||
])
|
||||
);
|
||||
debug_span!("redo 2").in_scope(|| {
|
||||
undo.redo(&doc).unwrap();
|
||||
undo.redo().unwrap();
|
||||
});
|
||||
assert_eq!(
|
||||
text.get_richtext_value().to_json_value(),
|
||||
|
@ -579,12 +579,12 @@ fn undo_richtext_editing_collab() -> LoroResult<()> {
|
|||
let doc_b = LoroDoc::new();
|
||||
doc_b.set_peer_id(2)?;
|
||||
doc_a.get_text("text").insert(0, "A fox jumped")?;
|
||||
undo.record_new_checkpoint(&doc_a)?;
|
||||
undo.record_new_checkpoint()?;
|
||||
sync(&doc_a, &doc_b);
|
||||
doc_b.get_text("text").mark(2..12, "italic", true)?;
|
||||
sync(&doc_a, &doc_b);
|
||||
doc_a.get_text("text").mark(0..5, "bold", true)?;
|
||||
undo.record_new_checkpoint(&doc_a)?;
|
||||
undo.record_new_checkpoint()?;
|
||||
sync(&doc_a, &doc_b);
|
||||
assert_eq!(
|
||||
doc_a.get_text("text").get_richtext_value().to_json_value(),
|
||||
|
@ -595,7 +595,7 @@ fn undo_richtext_editing_collab() -> LoroResult<()> {
|
|||
])
|
||||
);
|
||||
for _ in 0..10 {
|
||||
undo.undo(&doc_a)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(
|
||||
doc_a.get_text("text").get_richtext_value().to_json_value(),
|
||||
json!([
|
||||
|
@ -604,7 +604,7 @@ fn undo_richtext_editing_collab() -> LoroResult<()> {
|
|||
])
|
||||
);
|
||||
// FIXME: right now redo/undo like this is wasteful
|
||||
undo.redo(&doc_a)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(
|
||||
doc_a.get_text("text").get_richtext_value().to_json_value(),
|
||||
json!([
|
||||
|
@ -636,12 +636,12 @@ fn undo_richtext_conflict_set_style() -> LoroResult<()> {
|
|||
doc_b.set_peer_id(2)?;
|
||||
|
||||
doc_a.get_text("text").insert(0, "A fox jumped")?;
|
||||
undo.record_new_checkpoint(&doc_a)?;
|
||||
undo.record_new_checkpoint()?;
|
||||
sync(&doc_a, &doc_b);
|
||||
doc_b.get_text("text").mark(2..12, "color", "red")?;
|
||||
sync(&doc_a, &doc_b);
|
||||
doc_a.get_text("text").mark(0..5, "color", "green")?;
|
||||
undo.record_new_checkpoint(&doc_a)?;
|
||||
undo.record_new_checkpoint()?;
|
||||
sync(&doc_a, &doc_b);
|
||||
assert_eq!(
|
||||
doc_a.get_text("text").get_richtext_value().to_json_value(),
|
||||
|
@ -651,7 +651,7 @@ fn undo_richtext_conflict_set_style() -> LoroResult<()> {
|
|||
])
|
||||
);
|
||||
for _ in 0..10 {
|
||||
undo.undo(&doc_a)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(
|
||||
doc_a.get_text("text").get_richtext_value().to_json_value(),
|
||||
json!([
|
||||
|
@ -659,12 +659,12 @@ fn undo_richtext_conflict_set_style() -> LoroResult<()> {
|
|||
{"insert": "fox jumped", "attributes": {"color": "red"}}
|
||||
])
|
||||
);
|
||||
undo.undo(&doc_a)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(
|
||||
doc_a.get_text("text").get_richtext_value().to_json_value(),
|
||||
json!([])
|
||||
);
|
||||
undo.redo(&doc_a)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(
|
||||
doc_a.get_text("text").get_richtext_value().to_json_value(),
|
||||
json!([
|
||||
|
@ -672,7 +672,7 @@ fn undo_richtext_conflict_set_style() -> LoroResult<()> {
|
|||
{"insert": "fox jumped", "attributes": {"color": "red"}}
|
||||
])
|
||||
);
|
||||
undo.redo(&doc_a)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(
|
||||
doc_a.get_text("text").get_richtext_value().to_json_value(),
|
||||
json!([
|
||||
|
@ -693,30 +693,30 @@ fn undo_text_collab_delete() -> LoroResult<()> {
|
|||
let doc_b = LoroDoc::new();
|
||||
doc_b.set_peer_id(2)?;
|
||||
doc_a.get_text("text").insert(0, "A ")?;
|
||||
undo.record_new_checkpoint(&doc_a)?;
|
||||
undo.record_new_checkpoint()?;
|
||||
doc_a.get_text("text").insert(2, "fox ")?;
|
||||
undo.record_new_checkpoint(&doc_a)?;
|
||||
undo.record_new_checkpoint()?;
|
||||
doc_a.get_text("text").insert(6, "jumped")?;
|
||||
undo.record_new_checkpoint(&doc_a)?;
|
||||
undo.record_new_checkpoint()?;
|
||||
sync(&doc_a, &doc_b);
|
||||
|
||||
doc_b.get_text("text").delete(2, 4)?;
|
||||
sync(&doc_a, &doc_b);
|
||||
doc_a.get_text("text").insert(0, "123!")?;
|
||||
undo.record_new_checkpoint(&doc_a)?;
|
||||
undo.record_new_checkpoint()?;
|
||||
for _ in 0..3 {
|
||||
assert_eq!(doc_a.get_text("text").to_string(), "123!A jumped");
|
||||
undo.undo(&doc_a)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(doc_a.get_text("text").to_string(), "A jumped");
|
||||
undo.undo(&doc_a)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(doc_a.get_text("text").to_string(), "A ");
|
||||
undo.undo(&doc_a)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(doc_a.get_text("text").to_string(), "");
|
||||
undo.redo(&doc_a)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(doc_a.get_text("text").to_string(), "A ");
|
||||
undo.redo(&doc_a)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(doc_a.get_text("text").to_string(), "A jumped");
|
||||
undo.redo(&doc_a)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(doc_a.get_text("text").to_string(), "123!A jumped");
|
||||
}
|
||||
Ok(())
|
||||
|
@ -772,7 +772,7 @@ fn collab_undo() -> anyhow::Result<()> {
|
|||
{"insert": " fox jumped."}
|
||||
])
|
||||
);
|
||||
undo_a.undo(&doc_a)?;
|
||||
undo_a.undo()?;
|
||||
assert!(undo_a.can_redo());
|
||||
assert_eq!(
|
||||
doc_a.get_text("text").get_richtext_value().to_json_value(),
|
||||
|
@ -782,14 +782,14 @@ fn collab_undo() -> anyhow::Result<()> {
|
|||
{"insert": " fox jumped."}
|
||||
])
|
||||
);
|
||||
undo_a.undo(&doc_a)?;
|
||||
undo_a.undo()?;
|
||||
assert_eq!(
|
||||
doc_a.get_text("text").get_richtext_value().to_json_value(),
|
||||
json!([
|
||||
{"insert": "Hello A fox jumped."},
|
||||
])
|
||||
);
|
||||
undo_a.undo(&doc_a)?;
|
||||
undo_a.undo()?;
|
||||
assert_eq!(
|
||||
doc_a.get_text("text").get_richtext_value().to_json_value(),
|
||||
json!([
|
||||
|
@ -798,7 +798,7 @@ fn collab_undo() -> anyhow::Result<()> {
|
|||
);
|
||||
|
||||
assert!(!undo_a.can_undo());
|
||||
undo_a.redo(&doc_a)?;
|
||||
undo_a.redo()?;
|
||||
assert_eq!(
|
||||
doc_a.get_text("text").get_richtext_value().to_json_value(),
|
||||
json!([
|
||||
|
@ -806,7 +806,7 @@ fn collab_undo() -> anyhow::Result<()> {
|
|||
])
|
||||
);
|
||||
|
||||
undo_a.redo(&doc_a)?;
|
||||
undo_a.redo()?;
|
||||
assert_eq!(
|
||||
doc_a.get_text("text").get_richtext_value().to_json_value(),
|
||||
json!([
|
||||
|
@ -815,7 +815,7 @@ fn collab_undo() -> anyhow::Result<()> {
|
|||
{"insert": " fox jumped."}
|
||||
])
|
||||
);
|
||||
undo_a.redo(&doc_a)?;
|
||||
undo_a.redo()?;
|
||||
Ok::<(), LoroError>(())
|
||||
})?;
|
||||
}
|
||||
|
@ -831,7 +831,7 @@ fn collab_undo() -> anyhow::Result<()> {
|
|||
{"insert": " fox jumped."}
|
||||
])
|
||||
);
|
||||
undo_b.undo(&doc_b)?;
|
||||
undo_b.undo()?;
|
||||
assert!(undo_b.can_redo());
|
||||
assert_eq!(
|
||||
doc_b.get_text("text").get_richtext_value().to_json_value(),
|
||||
|
@ -842,7 +842,7 @@ fn collab_undo() -> anyhow::Result<()> {
|
|||
])
|
||||
);
|
||||
|
||||
undo_b.undo(&doc_b)?;
|
||||
undo_b.undo()?;
|
||||
assert_eq!(
|
||||
doc_b.get_text("text").get_richtext_value().to_json_value(),
|
||||
json!([
|
||||
|
@ -851,7 +851,7 @@ fn collab_undo() -> anyhow::Result<()> {
|
|||
{"insert": " "},
|
||||
])
|
||||
);
|
||||
undo_b.undo(&doc_b)?;
|
||||
undo_b.undo()?;
|
||||
assert_eq!(
|
||||
doc_b.get_text("text").get_richtext_value().to_json_value(),
|
||||
json!([
|
||||
|
@ -860,7 +860,7 @@ fn collab_undo() -> anyhow::Result<()> {
|
|||
);
|
||||
assert!(!undo_b.can_undo());
|
||||
assert!(undo_b.can_redo());
|
||||
undo_b.redo(&doc_b)?;
|
||||
undo_b.redo()?;
|
||||
assert_eq!(
|
||||
doc_b.get_text("text").get_richtext_value().to_json_value(),
|
||||
json!([
|
||||
|
@ -869,7 +869,7 @@ fn collab_undo() -> anyhow::Result<()> {
|
|||
{"insert": " "},
|
||||
])
|
||||
);
|
||||
undo_b.redo(&doc_b)?;
|
||||
undo_b.redo()?;
|
||||
assert_eq!(
|
||||
doc_b.get_text("text").get_richtext_value().to_json_value(),
|
||||
json!([
|
||||
|
@ -878,7 +878,7 @@ fn collab_undo() -> anyhow::Result<()> {
|
|||
{"insert": " fox"}
|
||||
])
|
||||
);
|
||||
undo_b.redo(&doc_b)?;
|
||||
undo_b.redo()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -949,25 +949,25 @@ fn undo_sub_sub_container() -> anyhow::Result<()> {
|
|||
])
|
||||
);
|
||||
|
||||
undo_a.undo(&doc_a)?; // 4 -> 3
|
||||
undo_a.undo()?; // 4 -> 3
|
||||
assert_eq!(
|
||||
text_a.get_richtext_value().to_json_value(),
|
||||
json!([
|
||||
{"insert": "Fox World!"},
|
||||
])
|
||||
);
|
||||
undo_a.undo(&doc_a)?; // 3 -> 2
|
||||
// It should be "FHoexllo World!" here ideally
|
||||
// But it's too expensive to calculate and make the code too complicated
|
||||
// So we skip the test
|
||||
undo_a.undo(&doc_a)?; // 2 -> 1.5
|
||||
undo_a.undo()?; // 3 -> 2
|
||||
// It should be "FHoexllo World!" here ideally
|
||||
// But it's too expensive to calculate and make the code too complicated
|
||||
// So we skip the test
|
||||
undo_a.undo()?; // 2 -> 1.5
|
||||
assert_eq!(
|
||||
text_a.get_richtext_value().to_json_value(),
|
||||
json!([
|
||||
{"insert": "Fox"},
|
||||
])
|
||||
);
|
||||
undo_a.undo(&doc_a)?; // 1.5 -> 1
|
||||
undo_a.undo()?; // 1.5 -> 1
|
||||
assert_eq!(
|
||||
doc_a.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
|
@ -975,7 +975,7 @@ fn undo_sub_sub_container() -> anyhow::Result<()> {
|
|||
})
|
||||
);
|
||||
|
||||
undo_a.undo(&doc_a)?; // 1 -> 0
|
||||
undo_a.undo()?; // 1 -> 0
|
||||
assert_eq!(
|
||||
doc_a.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
|
@ -983,23 +983,23 @@ fn undo_sub_sub_container() -> anyhow::Result<()> {
|
|||
})
|
||||
);
|
||||
|
||||
undo_a.redo(&doc_a)?; // 0 -> 1
|
||||
undo_a.redo()?; // 0 -> 1
|
||||
assert_eq!(
|
||||
doc_a.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
"map": {"list": []}
|
||||
})
|
||||
);
|
||||
undo_a.redo(&doc_a)?; // 1 -> 1.5
|
||||
undo_a.redo()?; // 1 -> 1.5
|
||||
assert_eq!(
|
||||
text_a.get_richtext_value().to_json_value(),
|
||||
json!([
|
||||
{"insert": "Fox"},
|
||||
])
|
||||
);
|
||||
undo_a.redo(&doc_a)?; // 1.5 -> 2
|
||||
undo_a.redo()?; // 1.5 -> 2
|
||||
|
||||
undo_a.redo(&doc_a)?; // 2 -> 3
|
||||
undo_a.redo()?; // 2 -> 3
|
||||
|
||||
assert_eq!(
|
||||
doc_a.get_deep_value().to_json_value(),
|
||||
|
@ -1019,7 +1019,7 @@ fn undo_sub_sub_container() -> anyhow::Result<()> {
|
|||
.unwrap()
|
||||
.into_text()
|
||||
.unwrap();
|
||||
undo_a.redo(&doc_a)?; // 3 -> 4
|
||||
undo_a.redo()?; // 3 -> 4
|
||||
assert_eq!(
|
||||
text_a.get_richtext_value().to_json_value(),
|
||||
json!([
|
||||
|
@ -1089,7 +1089,7 @@ fn test_remote_merge_transform() -> LoroResult<()> {
|
|||
])
|
||||
);
|
||||
|
||||
undo_a.undo(&doc_a)?;
|
||||
undo_a.undo()?;
|
||||
assert_eq!(
|
||||
text_a.get_richtext_value().to_json_value(),
|
||||
json!([
|
||||
|
@ -1097,7 +1097,7 @@ fn test_remote_merge_transform() -> LoroResult<()> {
|
|||
])
|
||||
);
|
||||
|
||||
undo_a.undo(&doc_a)?;
|
||||
undo_a.undo()?;
|
||||
assert_eq!(text_a.get_richtext_value().to_json_value(), json!([]));
|
||||
|
||||
Ok(())
|
||||
|
@ -1123,7 +1123,7 @@ fn undo_tree_move() -> LoroResult<()> {
|
|||
doc_b.import(&doc_a.export_from(&Default::default()))?;
|
||||
let latest_value = tree_a.get_value();
|
||||
// a
|
||||
undo.undo(&doc_a)?;
|
||||
undo.undo()?;
|
||||
let a_value = tree_a.get_value().as_list().unwrap().clone();
|
||||
assert_eq!(a_value.len(), 2);
|
||||
assert!(a_value[0]
|
||||
|
@ -1139,14 +1139,14 @@ fn undo_tree_move() -> LoroResult<()> {
|
|||
.unwrap()
|
||||
.is_null());
|
||||
|
||||
undo.redo(&doc_a)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(tree_a.get_value(), latest_value);
|
||||
// b
|
||||
undo2.undo(&doc_b)?;
|
||||
undo2.undo()?;
|
||||
let b_value = tree_b.get_value().as_list().unwrap().clone();
|
||||
assert_eq!(b_value.len(), 0);
|
||||
undo.undo(&doc_a)?;
|
||||
undo.undo(&doc_a)?;
|
||||
undo.undo()?;
|
||||
undo.undo()?;
|
||||
let a_value = tree_a.get_value().as_list().unwrap().clone();
|
||||
assert_eq!(a_value.len(), 1);
|
||||
assert_eq!(
|
||||
|
@ -1181,7 +1181,7 @@ fn undo_tree_concurrent_delete() -> LoroResult<()> {
|
|||
tree_b.delete(child)?;
|
||||
doc_a.import(&doc_b.export_from(&Default::default()))?;
|
||||
doc_b.import(&doc_a.export_from(&Default::default()))?;
|
||||
undo_b.undo(&doc_b)?;
|
||||
undo_b.undo()?;
|
||||
assert!(tree_b.get_value().as_list().unwrap().is_empty());
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1203,7 +1203,7 @@ fn undo_tree_concurrent_delete2() -> LoroResult<()> {
|
|||
tree_b.delete(child)?;
|
||||
doc_a.import(&doc_b.export_from(&Default::default()))?;
|
||||
doc_b.import(&doc_a.export_from(&Default::default()))?;
|
||||
undo_b.undo(&doc_b)?;
|
||||
undo_b.undo()?;
|
||||
assert_eq!(tree_b.get_value().as_list().unwrap().len(), 1);
|
||||
assert_eq!(
|
||||
tree_b.get_value().as_list().unwrap()[0]
|
||||
|
@ -1289,7 +1289,7 @@ fn undo_redo_when_collab() -> anyhow::Result<()> {
|
|||
text_a.insert(0, "Alice")?;
|
||||
sync(&doc_a, &doc_b);
|
||||
text_b.delete(0, 5)?;
|
||||
undo_a.undo(&doc_a)?;
|
||||
undo_a.undo()?;
|
||||
assert_eq!(
|
||||
doc_a.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
|
@ -1297,7 +1297,7 @@ fn undo_redo_when_collab() -> anyhow::Result<()> {
|
|||
})
|
||||
);
|
||||
doc_a.import(&doc_b.export_from(&Default::default()))?;
|
||||
undo_a.undo(&doc_a)?;
|
||||
undo_a.undo()?;
|
||||
assert_eq!(
|
||||
doc_a.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
|
@ -1306,7 +1306,7 @@ fn undo_redo_when_collab() -> anyhow::Result<()> {
|
|||
);
|
||||
text_b.insert(0, "Bob ")?;
|
||||
doc_a.import(&doc_b.export_from(&Default::default()))?;
|
||||
undo_a.undo(&doc_a)?;
|
||||
undo_a.undo()?;
|
||||
assert_eq!(
|
||||
doc_a.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
|
@ -1315,21 +1315,21 @@ fn undo_redo_when_collab() -> anyhow::Result<()> {
|
|||
);
|
||||
|
||||
assert!(undo_a.can_redo());
|
||||
undo_a.redo(&doc_a)?;
|
||||
undo_a.redo()?;
|
||||
assert_eq!(
|
||||
doc_a.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
"text": "Bob Hi "
|
||||
})
|
||||
);
|
||||
undo_a.redo(&doc_a)?;
|
||||
undo_a.redo()?;
|
||||
assert_eq!(
|
||||
doc_a.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
"text": "Bob Hi World"
|
||||
})
|
||||
);
|
||||
undo_a.redo(&doc_a)?;
|
||||
undo_a.redo()?;
|
||||
assert_eq!(
|
||||
doc_a.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
|
@ -1366,7 +1366,7 @@ fn undo_list_move() -> anyhow::Result<()> {
|
|||
"list": ["2", "1", "0"]
|
||||
})
|
||||
);
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
assert!(undo.can_redo());
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
|
@ -1374,14 +1374,14 @@ fn undo_list_move() -> anyhow::Result<()> {
|
|||
"list": ["1", "2", "0"]
|
||||
})
|
||||
);
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
"list": ["0", "1", "2"]
|
||||
})
|
||||
);
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
|
@ -1389,28 +1389,28 @@ fn undo_list_move() -> anyhow::Result<()> {
|
|||
})
|
||||
);
|
||||
|
||||
undo.undo(&doc)?;
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
undo.undo()?;
|
||||
assert!(!undo.can_undo());
|
||||
undo.redo(&doc)?;
|
||||
undo.redo()?;
|
||||
assert!(undo.can_undo());
|
||||
undo.redo(&doc)?;
|
||||
undo.redo()?;
|
||||
|
||||
undo.redo(&doc)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
"list": ["0", "1", "2"]
|
||||
})
|
||||
);
|
||||
undo.redo(&doc)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
"list": ["1", "2", "0"]
|
||||
})
|
||||
);
|
||||
undo.redo(&doc)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
|
@ -1442,7 +1442,7 @@ fn undo_collab_list_move() -> LoroResult<()> {
|
|||
doc_b.get_movable_list("list").mov(0, 1)?;
|
||||
sync(&doc, &doc_b);
|
||||
assert_eq!(list.get_value().to_json_value(), json!(["1", "0", "2"]));
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
// FIXME: cannot infer move correctly for now
|
||||
assert_eq!(list.get_value().to_json_value(), json!(["0", "1", "2"]));
|
||||
Ok(())
|
||||
|
@ -1469,14 +1469,14 @@ fn exclude_certain_local_ops_from_undo() -> anyhow::Result<()> {
|
|||
"text": "x1y2z3abc"
|
||||
})
|
||||
);
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
"text": "x1y2z3"
|
||||
})
|
||||
);
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
|
@ -1484,14 +1484,14 @@ fn exclude_certain_local_ops_from_undo() -> anyhow::Result<()> {
|
|||
})
|
||||
);
|
||||
assert!(!undo.can_undo());
|
||||
undo.redo(&doc)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
"text": "x1y2z3"
|
||||
})
|
||||
);
|
||||
undo.redo(&doc)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(
|
||||
doc.get_deep_value().to_json_value(),
|
||||
json!({
|
||||
|
@ -1522,7 +1522,7 @@ fn should_not_trigger_update_when_undo_ops_depend_on_deleted_container() -> anyh
|
|||
doc_a.get_map("map").delete("text")?;
|
||||
sync(&doc_a, &doc_b);
|
||||
let f = doc_b.oplog_frontiers();
|
||||
undo.undo(&doc_b)?;
|
||||
undo.undo()?;
|
||||
// should not update doc_b, because the undo operation depends on a deleted container
|
||||
assert_eq!(f, doc_b.oplog_frontiers());
|
||||
Ok(())
|
||||
|
@ -1561,18 +1561,18 @@ fn undo_manager_events() -> anyhow::Result<()> {
|
|||
doc.commit();
|
||||
assert_eq!(push_count.load(atomic::Ordering::SeqCst), 2);
|
||||
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(&*popped_value.try_lock().unwrap(), &LoroValue::I64(5));
|
||||
assert_eq!(pop_count.load(atomic::Ordering::SeqCst), 1);
|
||||
assert_eq!(push_count.load(atomic::Ordering::SeqCst), 3);
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
assert_eq!(&*popped_value.try_lock().unwrap(), &LoroValue::I64(0));
|
||||
assert_eq!(pop_count.load(atomic::Ordering::SeqCst), 2);
|
||||
assert_eq!(push_count.load(atomic::Ordering::SeqCst), 4);
|
||||
undo.redo(&doc)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(pop_count.load(atomic::Ordering::SeqCst), 3);
|
||||
assert_eq!(push_count.load(atomic::Ordering::SeqCst), 5);
|
||||
undo.redo(&doc)?;
|
||||
undo.redo()?;
|
||||
assert_eq!(pop_count.load(atomic::Ordering::SeqCst), 4);
|
||||
assert_eq!(push_count.load(atomic::Ordering::SeqCst), 6);
|
||||
Ok(())
|
||||
|
@ -1620,7 +1620,7 @@ fn undo_transform_cursor_position() -> anyhow::Result<()> {
|
|||
assert_eq!(text.to_string(), "Hi Hii world!");
|
||||
}
|
||||
assert_eq!(popped_cursors.try_lock().unwrap().len(), 0);
|
||||
undo.undo(&doc)?;
|
||||
undo.undo()?;
|
||||
|
||||
// Undo will create new "Hello". They have different IDs than the original ones.
|
||||
// But the original cursors are bound on the original deleted text.
|
||||
|
|
Loading…
Reference in a new issue