Skip to main content

loro_internal/
handler.rs

1use super::{state::DocState, txn::Transaction};
2use crate::sync::Mutex;
3use crate::{
4    container::{
5        idx::ContainerIdx,
6        list::list_op::{DeleteSpan, DeleteSpanWithId, ListOp},
7        richtext::{richtext_state::PosType, RichtextState, StyleKey, StyleOp, TextStyleInfoFlag},
8    },
9    cursor::{Cursor, Side},
10    delta::{DeltaItem, Meta, StyleMeta, TreeExternalDiff},
11    diff::{diff, diff_impl::UpdateTimeoutError, OperateProxy},
12    event::{Diff, TextDiff, TextDiffItem, TextMeta},
13    op::ListSlice,
14    state::{IndexType, State, TreeParentId},
15    txn::EventHint,
16    utils::{string_slice::StringSlice, utf16::count_utf16_len},
17    LoroDoc, LoroDocInner,
18};
19use append_only_bytes::BytesSlice;
20use enum_as_inner::EnumAsInner;
21use generic_btree::rle::HasLength;
22use loro_common::{
23    ContainerID, ContainerType, IdFull, InternalString, LoroError, LoroResult, LoroValue, PeerID,
24    TreeID, ID,
25};
26use rustc_hash::FxHashMap;
27use serde::{Deserialize, Serialize};
28use std::{borrow::Cow, cmp::Reverse, collections::BinaryHeap, fmt::Debug, ops::Deref, sync::Arc};
29use tracing::{error, instrument};
30
31pub use crate::diff::diff_impl::UpdateOptions;
32pub use tree::TreeHandler;
33mod movable_list_apply_delta;
34mod tree;
35
36const REGULAR_CONTAINER_VALUE_ARG_ERROR: &str =
37    "Cannot use a LoroValue::Container as a regular value. To create a child container, use insert_container/set_container, or ensure_mergeable_* on maps for mergeable children";
38
39mod text_update;
40
41fn ensure_no_regular_container_value(value: &LoroValue) -> LoroResult<()> {
42    // Fast path: scalar values can never transitively hold a container, so we
43    // skip the heap allocation + traversal below. This is the common case on
44    // the per-op insert hot path (inserting numbers/strings/bools), where the
45    // previous unconditional `vec![value]` allocation showed up as a measurable
46    // regression.
47    if !matches!(
48        value,
49        LoroValue::Container(_) | LoroValue::List(_) | LoroValue::Map(_)
50    ) {
51        return Ok(());
52    }
53
54    let mut stack = vec![value];
55    while let Some(value) = stack.pop() {
56        match value {
57            LoroValue::Container(_) => {
58                return Err(LoroError::ArgErr(
59                    REGULAR_CONTAINER_VALUE_ARG_ERROR
60                        .to_string()
61                        .into_boxed_str(),
62                ));
63            }
64            LoroValue::List(list) => {
65                stack.extend(list.iter());
66            }
67            LoroValue::Map(map) => {
68                stack.extend(map.values());
69            }
70            LoroValue::Null
71            | LoroValue::Bool(_)
72            | LoroValue::Double(_)
73            | LoroValue::I64(_)
74            | LoroValue::Binary(_)
75            | LoroValue::String(_) => {}
76        }
77    }
78
79    Ok(())
80}
81
82fn checked_range_end(
83    pos: usize,
84    len: usize,
85    container_len: usize,
86    // Lazily built: this is on the per-op edit hot path, so the position-context
87    // string must only be allocated when a bound check actually fails.
88    info: impl Fn() -> Box<str>,
89) -> LoroResult<usize> {
90    let end = pos.checked_add(len).ok_or_else(|| LoroError::OutOfBound {
91        pos: usize::MAX,
92        len: container_len,
93        info: info(),
94    })?;
95    if end > container_len {
96        return Err(LoroError::OutOfBound {
97            pos: end,
98            len: container_len,
99            info: info(),
100        });
101    }
102
103    Ok(end)
104}
105
106fn checked_delta_index_end(pos: usize, len: usize, container_len: usize) -> LoroResult<usize> {
107    pos.checked_add(len).ok_or_else(|| LoroError::OutOfBound {
108        pos: usize::MAX,
109        len: container_len,
110        info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
111    })
112}
113
114pub trait HandlerTrait: Clone + Sized {
115    fn is_attached(&self) -> bool;
116    fn attached_handler(&self) -> Option<&BasicHandler>;
117    fn get_value(&self) -> LoroValue;
118    fn get_deep_value(&self) -> LoroValue;
119    fn kind(&self) -> ContainerType;
120    fn to_handler(&self) -> Handler;
121    fn from_handler(h: Handler) -> Option<Self>;
122    fn doc(&self) -> Option<LoroDoc>;
123    /// This method returns an attached handler.
124    fn attach(
125        &self,
126        txn: &mut Transaction,
127        parent: &BasicHandler,
128        self_id: ContainerID,
129    ) -> LoroResult<Self>;
130    /// If a detached container is attached, this method will return its corresponding attached handler.
131    fn get_attached(&self) -> Option<Self>;
132
133    fn parent(&self) -> Option<Handler> {
134        self.attached_handler().and_then(|x| x.parent())
135    }
136
137    fn idx(&self) -> ContainerIdx {
138        self.attached_handler()
139            .map(|x| x.container_idx)
140            .unwrap_or_else(|| {
141                ContainerIdx::from_index_and_type(ContainerIdx::INDEX_MASK, self.kind())
142            })
143    }
144
145    fn id(&self) -> ContainerID {
146        self.attached_handler()
147            .map(|x| x.id.clone())
148            .unwrap_or_else(|| ContainerID::new_normal(ID::NONE_ID, self.kind()))
149    }
150
151    fn with_state<R>(&self, f: impl FnOnce(&mut State) -> LoroResult<R>) -> LoroResult<R> {
152        let inner = self
153            .attached_handler()
154            .ok_or(LoroError::MisuseDetachedContainer {
155                method: "with_state",
156            })?;
157        let state = inner.doc.state.clone();
158        let mut guard = state.lock();
159        guard.with_state_mut(inner.container_idx, f)
160    }
161}
162
163fn create_handler(inner: &BasicHandler, id: ContainerID) -> Handler {
164    Handler::new_attached(id, inner.doc.clone())
165}
166
167fn value_to_value_or_handler(inner: &BasicHandler, value: LoroValue) -> ValueOrHandler {
168    match value {
169        LoroValue::Container(container_id) => {
170            ValueOrHandler::Handler(create_handler(inner, container_id))
171        }
172        value => ValueOrHandler::Value(value),
173    }
174}
175
176/// Flatten attributes that allow overlap
177#[derive(Clone, Debug)]
178pub struct BasicHandler {
179    id: ContainerID,
180    container_idx: ContainerIdx,
181    doc: LoroDoc,
182}
183
184struct DetachedInner<T> {
185    value: T,
186    /// If the handler attached later, this field will be filled.
187    attached: Option<BasicHandler>,
188}
189
190impl<T> DetachedInner<T> {
191    fn new(v: T) -> Self {
192        Self {
193            value: v,
194            attached: None,
195        }
196    }
197}
198
199enum MaybeDetached<T> {
200    Detached(Arc<Mutex<DetachedInner<T>>>),
201    Attached(BasicHandler),
202}
203
204impl<T> Clone for MaybeDetached<T> {
205    fn clone(&self) -> Self {
206        match self {
207            MaybeDetached::Detached(a) => MaybeDetached::Detached(Arc::clone(a)),
208            MaybeDetached::Attached(a) => MaybeDetached::Attached(a.clone()),
209        }
210    }
211}
212
213impl<T> MaybeDetached<T> {
214    fn new_detached(v: T) -> Self {
215        MaybeDetached::Detached(Arc::new(Mutex::new(DetachedInner::new(v))))
216    }
217
218    fn is_attached(&self) -> bool {
219        match self {
220            MaybeDetached::Detached(_) => false,
221            MaybeDetached::Attached(_) => true,
222        }
223    }
224
225    fn attached_handler(&self) -> Option<&BasicHandler> {
226        match self {
227            MaybeDetached::Detached(_) => None,
228            MaybeDetached::Attached(a) => Some(a),
229        }
230    }
231
232    fn try_attached_state(&self) -> LoroResult<&BasicHandler> {
233        match self {
234            MaybeDetached::Detached(_) => Err(LoroError::MisuseDetachedContainer {
235                method: "inner_state",
236            }),
237            MaybeDetached::Attached(a) => Ok(a),
238        }
239    }
240}
241
242impl<T> From<BasicHandler> for MaybeDetached<T> {
243    fn from(a: BasicHandler) -> Self {
244        MaybeDetached::Attached(a)
245    }
246}
247
248impl BasicHandler {
249    pub(crate) fn doc(&self) -> LoroDoc {
250        self.doc.clone()
251    }
252
253    #[inline]
254    fn with_doc_state<R>(&self, f: impl FnOnce(&mut DocState) -> R) -> R {
255        let state = self.doc.state.clone();
256        let mut guard = state.lock();
257        f(&mut guard)
258    }
259
260    fn with_txn<R>(
261        &self,
262        f: impl FnOnce(&mut Transaction) -> Result<R, LoroError>,
263    ) -> Result<R, LoroError> {
264        with_txn(&self.doc, f)
265    }
266
267    fn get_parent(&self) -> Option<Handler> {
268        let parent_idx = self.doc.arena.get_parent(self.container_idx)?;
269        let parent_id = self.doc.arena.get_container_id(parent_idx).unwrap();
270        {
271            let kind = parent_id.container_type();
272            let handler = BasicHandler {
273                container_idx: parent_idx,
274                id: parent_id,
275                doc: self.doc.clone(),
276            };
277
278            Some(match kind {
279                ContainerType::Map => Handler::Map(MapHandler {
280                    inner: handler.into(),
281                }),
282                ContainerType::List => Handler::List(ListHandler {
283                    inner: handler.into(),
284                }),
285                ContainerType::Tree => Handler::Tree(TreeHandler {
286                    inner: handler.into(),
287                }),
288                ContainerType::Text => Handler::Text(TextHandler {
289                    inner: handler.into(),
290                }),
291                ContainerType::MovableList => Handler::MovableList(MovableListHandler {
292                    inner: handler.into(),
293                }),
294                #[cfg(feature = "counter")]
295                ContainerType::Counter => Handler::Counter(counter::CounterHandler {
296                    inner: handler.into(),
297                }),
298                ContainerType::Unknown(_) => unreachable!(),
299            })
300        }
301    }
302
303    pub fn get_value(&self) -> LoroValue {
304        self.doc.state.lock().get_value_by_idx(self.container_idx)
305    }
306
307    pub fn get_deep_value(&self) -> LoroValue {
308        self.doc
309            .state
310            .lock()
311            .get_container_deep_value(self.container_idx)
312    }
313
314    fn with_state<R>(&self, f: impl FnOnce(&mut State) -> R) -> R {
315        let mut guard = self.doc.state.lock();
316        guard.with_state_mut(self.container_idx, f)
317    }
318
319    pub fn parent(&self) -> Option<Handler> {
320        self.get_parent()
321    }
322
323    fn is_deleted(&self) -> bool {
324        self.doc.state.lock().is_deleted(self.container_idx)
325    }
326
327    fn has_decoded_state(&self) -> bool {
328        self.with_doc_state(|state| state.has_decoded_container_state(self.container_idx))
329    }
330}
331
332/// Flatten attributes that allow overlap
333#[derive(Clone)]
334pub struct TextHandler {
335    inner: MaybeDetached<RichtextState>,
336}
337
338impl HandlerTrait for TextHandler {
339    fn to_handler(&self) -> Handler {
340        Handler::Text(self.clone())
341    }
342
343    fn attach(
344        &self,
345        txn: &mut Transaction,
346        parent: &BasicHandler,
347        self_id: ContainerID,
348    ) -> LoroResult<Self> {
349        match &self.inner {
350            MaybeDetached::Detached(t) => {
351                let mut t = t.lock();
352                let inner = create_handler(parent, self_id);
353                let text = inner.into_text().unwrap();
354                let mut delta: Vec<TextDelta> = Vec::new();
355                for span in t.value.iter() {
356                    delta.push(TextDelta::Insert {
357                        insert: span.text.to_string(),
358                        attributes: span.attributes.to_option_map(),
359                    });
360                }
361
362                text.apply_delta_with_txn(txn, &delta)?;
363                t.attached = text.attached_handler().cloned();
364                Ok(text)
365            }
366            MaybeDetached::Attached(a) => {
367                let new_inner = create_handler(a, self_id);
368                let ans = new_inner.into_text().unwrap();
369
370                let delta = self.get_delta();
371                ans.apply_delta_with_txn(txn, &delta)?;
372                Ok(ans)
373            }
374        }
375    }
376
377    fn attached_handler(&self) -> Option<&BasicHandler> {
378        self.inner.attached_handler()
379    }
380
381    fn get_value(&self) -> LoroValue {
382        match &self.inner {
383            MaybeDetached::Detached(t) => {
384                let t = t.lock();
385                LoroValue::String((t.value.to_string()).into())
386            }
387            MaybeDetached::Attached(a) => a.get_value(),
388        }
389    }
390
391    fn get_deep_value(&self) -> LoroValue {
392        self.get_value()
393    }
394
395    fn is_attached(&self) -> bool {
396        matches!(&self.inner, MaybeDetached::Attached(..))
397    }
398
399    fn kind(&self) -> ContainerType {
400        ContainerType::Text
401    }
402
403    fn get_attached(&self) -> Option<Self> {
404        match &self.inner {
405            MaybeDetached::Detached(d) => d.lock().attached.clone().map(|x| Self {
406                inner: MaybeDetached::Attached(x),
407            }),
408            MaybeDetached::Attached(_a) => Some(self.clone()),
409        }
410    }
411
412    fn from_handler(h: Handler) -> Option<Self> {
413        match h {
414            Handler::Text(x) => Some(x),
415            _ => None,
416        }
417    }
418
419    fn doc(&self) -> Option<LoroDoc> {
420        match &self.inner {
421            MaybeDetached::Detached(_) => None,
422            MaybeDetached::Attached(a) => Some(a.doc()),
423        }
424    }
425}
426
427impl std::fmt::Debug for TextHandler {
428    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
429        match &self.inner {
430            MaybeDetached::Detached(_) => {
431                write!(f, "TextHandler(Unattached)")
432            }
433            MaybeDetached::Attached(a) => {
434                write!(f, "TextHandler({:?})", &a.id)
435            }
436        }
437    }
438}
439
440#[derive(Debug, Clone, EnumAsInner, Deserialize, Serialize, PartialEq)]
441#[serde(untagged)]
442pub enum TextDelta {
443    Retain {
444        retain: usize,
445        attributes: Option<FxHashMap<String, LoroValue>>,
446    },
447    Insert {
448        insert: String,
449        attributes: Option<FxHashMap<String, LoroValue>>,
450    },
451    Delete {
452        delete: usize,
453    },
454}
455
456impl TextDelta {
457    pub fn from_text_diff<'a>(diff: impl Iterator<Item = &'a TextDiffItem>) -> Vec<TextDelta> {
458        let mut ans = Vec::with_capacity(diff.size_hint().0);
459        for iter in diff {
460            match iter {
461                loro_delta::DeltaItem::Retain { len, attr } => {
462                    ans.push(TextDelta::Retain {
463                        retain: *len,
464                        attributes: if attr.0.is_empty() {
465                            None
466                        } else {
467                            Some(attr.0.clone())
468                        },
469                    });
470                }
471                loro_delta::DeltaItem::Replace {
472                    value,
473                    attr,
474                    delete,
475                } => {
476                    if value.rle_len() > 0 {
477                        ans.push(TextDelta::Insert {
478                            insert: value.to_string(),
479                            attributes: if attr.0.is_empty() {
480                                None
481                            } else {
482                                Some(attr.0.clone())
483                            },
484                        });
485                    }
486                    if *delete > 0 {
487                        ans.push(TextDelta::Delete { delete: *delete });
488                    }
489                }
490            }
491        }
492
493        ans
494    }
495
496    pub fn into_text_diff(vec: impl Iterator<Item = Self>) -> TextDiff {
497        let mut delta = TextDiff::new();
498        for item in vec {
499            match item {
500                TextDelta::Retain { retain, attributes } => {
501                    delta.push_retain(retain, TextMeta(attributes.unwrap_or_default().clone()));
502                }
503                TextDelta::Insert { insert, attributes } => {
504                    delta.push_insert(
505                        StringSlice::from(insert.as_str()),
506                        TextMeta(attributes.unwrap_or_default()),
507                    );
508                }
509                TextDelta::Delete { delete } => {
510                    delta.push_delete(delete);
511                }
512            }
513        }
514
515        delta
516    }
517}
518
519impl From<&DeltaItem<StringSlice, StyleMeta>> for TextDelta {
520    fn from(value: &DeltaItem<StringSlice, StyleMeta>) -> Self {
521        match value {
522            crate::delta::DeltaItem::Retain { retain, attributes } => TextDelta::Retain {
523                retain: *retain,
524                attributes: attributes.to_option_map(),
525            },
526            crate::delta::DeltaItem::Insert { insert, attributes } => TextDelta::Insert {
527                insert: insert.to_string(),
528                attributes: attributes.to_option_map(),
529            },
530            crate::delta::DeltaItem::Delete {
531                delete,
532                attributes: _,
533            } => TextDelta::Delete { delete: *delete },
534        }
535    }
536}
537
538#[derive(Clone)]
539pub struct MapHandler {
540    inner: MaybeDetached<FxHashMap<String, ValueOrHandler>>,
541}
542
543impl HandlerTrait for MapHandler {
544    fn is_attached(&self) -> bool {
545        matches!(&self.inner, MaybeDetached::Attached(..))
546    }
547
548    fn attached_handler(&self) -> Option<&BasicHandler> {
549        match &self.inner {
550            MaybeDetached::Detached(_) => None,
551            MaybeDetached::Attached(a) => Some(a),
552        }
553    }
554
555    fn get_value(&self) -> LoroValue {
556        match &self.inner {
557            MaybeDetached::Detached(m) => {
558                let m = m.lock();
559                let mut map = FxHashMap::default();
560                for (k, v) in m.value.iter() {
561                    map.insert(k.to_string(), v.to_value());
562                }
563                LoroValue::Map(map.into())
564            }
565            MaybeDetached::Attached(a) => a.get_value(),
566        }
567    }
568
569    fn get_deep_value(&self) -> LoroValue {
570        match &self.inner {
571            MaybeDetached::Detached(m) => {
572                let m = m.lock();
573                let mut map = FxHashMap::default();
574                for (k, v) in m.value.iter() {
575                    map.insert(k.to_string(), v.to_deep_value());
576                }
577                LoroValue::Map(map.into())
578            }
579            MaybeDetached::Attached(a) => a.get_deep_value(),
580        }
581    }
582
583    fn kind(&self) -> ContainerType {
584        ContainerType::Map
585    }
586
587    fn to_handler(&self) -> Handler {
588        Handler::Map(self.clone())
589    }
590
591    fn attach(
592        &self,
593        txn: &mut Transaction,
594        parent: &BasicHandler,
595        self_id: ContainerID,
596    ) -> LoroResult<Self> {
597        match &self.inner {
598            MaybeDetached::Detached(m) => {
599                let mut m = m.lock();
600                let inner = create_handler(parent, self_id);
601                let map = inner.into_map().unwrap();
602                for (k, v) in m.value.iter() {
603                    match v {
604                        ValueOrHandler::Value(v) => {
605                            map.insert_with_txn(txn, k, v.clone())?;
606                        }
607                        ValueOrHandler::Handler(h) => {
608                            map.insert_container_with_txn(txn, k, h.clone())?;
609                        }
610                    }
611                }
612                m.attached = map.attached_handler().cloned();
613                Ok(map)
614            }
615            MaybeDetached::Attached(a) => {
616                let new_inner = create_handler(a, self_id);
617                let ans = new_inner.into_map().unwrap();
618
619                for (k, v) in self.get_value().into_map().unwrap().iter() {
620                    if let LoroValue::Container(id) = v {
621                        ans.insert_container_with_txn(txn, k, create_handler(a, id.clone()))?;
622                    } else {
623                        ans.insert_with_txn(txn, k, v.clone())?;
624                    }
625                }
626
627                Ok(ans)
628            }
629        }
630    }
631
632    fn get_attached(&self) -> Option<Self> {
633        match &self.inner {
634            MaybeDetached::Detached(d) => d.lock().attached.clone().map(|x| Self {
635                inner: MaybeDetached::Attached(x),
636            }),
637            MaybeDetached::Attached(_a) => Some(self.clone()),
638        }
639    }
640
641    fn from_handler(h: Handler) -> Option<Self> {
642        match h {
643            Handler::Map(x) => Some(x),
644            _ => None,
645        }
646    }
647
648    fn doc(&self) -> Option<LoroDoc> {
649        match &self.inner {
650            MaybeDetached::Detached(_) => None,
651            MaybeDetached::Attached(a) => Some(a.doc()),
652        }
653    }
654}
655
656impl std::fmt::Debug for MapHandler {
657    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
658        match &self.inner {
659            MaybeDetached::Detached(_) => write!(f, "MapHandler Detached"),
660            MaybeDetached::Attached(a) => write!(f, "MapHandler {}", a.id),
661        }
662    }
663}
664
665#[derive(Clone)]
666pub struct ListHandler {
667    inner: MaybeDetached<Vec<ValueOrHandler>>,
668}
669
670#[derive(Clone)]
671pub struct MovableListHandler {
672    inner: MaybeDetached<Vec<ValueOrHandler>>,
673}
674
675impl HandlerTrait for MovableListHandler {
676    fn is_attached(&self) -> bool {
677        matches!(&self.inner, MaybeDetached::Attached(..))
678    }
679
680    fn attached_handler(&self) -> Option<&BasicHandler> {
681        match &self.inner {
682            MaybeDetached::Detached(_) => None,
683            MaybeDetached::Attached(a) => Some(a),
684        }
685    }
686
687    fn get_value(&self) -> LoroValue {
688        match &self.inner {
689            MaybeDetached::Detached(a) => {
690                let a = a.lock();
691                LoroValue::List(a.value.iter().map(|v| v.to_value()).collect())
692            }
693            MaybeDetached::Attached(a) => a.get_value(),
694        }
695    }
696
697    fn get_deep_value(&self) -> LoroValue {
698        match &self.inner {
699            MaybeDetached::Detached(a) => {
700                let a = a.lock();
701                LoroValue::List(a.value.iter().map(|v| v.to_deep_value()).collect())
702            }
703            MaybeDetached::Attached(a) => a.get_deep_value(),
704        }
705    }
706
707    fn kind(&self) -> ContainerType {
708        ContainerType::MovableList
709    }
710
711    fn to_handler(&self) -> Handler {
712        Handler::MovableList(self.clone())
713    }
714
715    fn from_handler(h: Handler) -> Option<Self> {
716        match h {
717            Handler::MovableList(x) => Some(x),
718            _ => None,
719        }
720    }
721
722    fn attach(
723        &self,
724        txn: &mut Transaction,
725        parent: &BasicHandler,
726        self_id: ContainerID,
727    ) -> LoroResult<Self> {
728        match &self.inner {
729            MaybeDetached::Detached(l) => {
730                let mut l = l.lock();
731                let inner = create_handler(parent, self_id);
732                let list = inner.into_movable_list().unwrap();
733                for (index, v) in l.value.iter().enumerate() {
734                    match v {
735                        ValueOrHandler::Value(v) => {
736                            list.insert_with_txn(txn, index, v.clone())?;
737                        }
738                        ValueOrHandler::Handler(h) => {
739                            list.insert_container_with_txn(txn, index, h.clone())?;
740                        }
741                    }
742                }
743                l.attached = list.attached_handler().cloned();
744                Ok(list)
745            }
746            MaybeDetached::Attached(a) => {
747                let new_inner = create_handler(a, self_id);
748                let ans = new_inner.into_movable_list().unwrap();
749
750                for (i, v) in self.get_value().into_list().unwrap().iter().enumerate() {
751                    if let LoroValue::Container(id) = v {
752                        ans.insert_container_with_txn(txn, i, create_handler(a, id.clone()))?;
753                    } else {
754                        ans.insert_with_txn(txn, i, v.clone())?;
755                    }
756                }
757
758                Ok(ans)
759            }
760        }
761    }
762
763    fn get_attached(&self) -> Option<Self> {
764        match &self.inner {
765            MaybeDetached::Detached(d) => d.lock().attached.clone().map(|x| Self {
766                inner: MaybeDetached::Attached(x),
767            }),
768            MaybeDetached::Attached(_a) => Some(self.clone()),
769        }
770    }
771
772    fn doc(&self) -> Option<LoroDoc> {
773        match &self.inner {
774            MaybeDetached::Detached(_) => None,
775            MaybeDetached::Attached(a) => Some(a.doc()),
776        }
777    }
778}
779
780impl std::fmt::Debug for MovableListHandler {
781    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
782        write!(f, "MovableListHandler {}", self.id())
783    }
784}
785
786impl std::fmt::Debug for ListHandler {
787    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
788        match &self.inner {
789            MaybeDetached::Detached(_) => write!(f, "ListHandler Detached"),
790            MaybeDetached::Attached(a) => write!(f, "ListHandler {}", a.id),
791        }
792    }
793}
794
795impl HandlerTrait for ListHandler {
796    fn is_attached(&self) -> bool {
797        self.inner.is_attached()
798    }
799
800    fn attached_handler(&self) -> Option<&BasicHandler> {
801        self.inner.attached_handler()
802    }
803
804    fn get_value(&self) -> LoroValue {
805        match &self.inner {
806            MaybeDetached::Detached(a) => {
807                let a = a.lock();
808                LoroValue::List(a.value.iter().map(|v| v.to_value()).collect())
809            }
810            MaybeDetached::Attached(a) => a.get_value(),
811        }
812    }
813
814    fn get_deep_value(&self) -> LoroValue {
815        match &self.inner {
816            MaybeDetached::Detached(a) => {
817                let a = a.lock();
818                LoroValue::List(a.value.iter().map(|v| v.to_deep_value()).collect())
819            }
820            MaybeDetached::Attached(a) => a.get_deep_value(),
821        }
822    }
823
824    fn kind(&self) -> ContainerType {
825        ContainerType::List
826    }
827
828    fn to_handler(&self) -> Handler {
829        Handler::List(self.clone())
830    }
831
832    fn attach(
833        &self,
834        txn: &mut Transaction,
835        parent: &BasicHandler,
836        self_id: ContainerID,
837    ) -> LoroResult<Self> {
838        match &self.inner {
839            MaybeDetached::Detached(l) => {
840                let mut l = l.lock();
841                let inner = create_handler(parent, self_id);
842                let list = inner.into_list().unwrap();
843                for (index, v) in l.value.iter().enumerate() {
844                    match v {
845                        ValueOrHandler::Value(v) => {
846                            list.insert_with_txn(txn, index, v.clone())?;
847                        }
848                        ValueOrHandler::Handler(h) => {
849                            list.insert_container_with_txn(txn, index, h.clone())?;
850                        }
851                    }
852                }
853                l.attached = list.attached_handler().cloned();
854                Ok(list)
855            }
856            MaybeDetached::Attached(a) => {
857                let new_inner = create_handler(a, self_id);
858                let ans = new_inner.into_list().unwrap();
859
860                for (i, v) in self.get_value().into_list().unwrap().iter().enumerate() {
861                    if let LoroValue::Container(id) = v {
862                        ans.insert_container_with_txn(txn, i, create_handler(a, id.clone()))?;
863                    } else {
864                        ans.insert_with_txn(txn, i, v.clone())?;
865                    }
866                }
867
868                Ok(ans)
869            }
870        }
871    }
872
873    fn get_attached(&self) -> Option<Self> {
874        match &self.inner {
875            MaybeDetached::Detached(d) => d.lock().attached.clone().map(|x| Self {
876                inner: MaybeDetached::Attached(x),
877            }),
878            MaybeDetached::Attached(_a) => Some(self.clone()),
879        }
880    }
881
882    fn from_handler(h: Handler) -> Option<Self> {
883        match h {
884            Handler::List(x) => Some(x),
885            _ => None,
886        }
887    }
888
889    fn doc(&self) -> Option<LoroDoc> {
890        match &self.inner {
891            MaybeDetached::Detached(_) => None,
892            MaybeDetached::Attached(a) => Some(a.doc()),
893        }
894    }
895}
896
897#[derive(Clone)]
898pub struct UnknownHandler {
899    inner: BasicHandler,
900}
901
902impl Debug for UnknownHandler {
903    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
904        write!(f, "UnknownHandler")
905    }
906}
907
908impl UnknownHandler {
909    pub fn is_deleted(&self) -> bool {
910        self.inner.is_deleted()
911    }
912}
913
914impl HandlerTrait for UnknownHandler {
915    fn is_attached(&self) -> bool {
916        true
917    }
918
919    fn attached_handler(&self) -> Option<&BasicHandler> {
920        Some(&self.inner)
921    }
922
923    fn get_value(&self) -> LoroValue {
924        todo!()
925    }
926
927    fn get_deep_value(&self) -> LoroValue {
928        todo!()
929    }
930
931    fn kind(&self) -> ContainerType {
932        self.inner.id.container_type()
933    }
934
935    fn to_handler(&self) -> Handler {
936        Handler::Unknown(self.clone())
937    }
938
939    fn from_handler(h: Handler) -> Option<Self> {
940        match h {
941            Handler::Unknown(x) => Some(x),
942            _ => None,
943        }
944    }
945
946    fn attach(
947        &self,
948        _txn: &mut Transaction,
949        _parent: &BasicHandler,
950        self_id: ContainerID,
951    ) -> LoroResult<Self> {
952        let new_inner = create_handler(&self.inner, self_id);
953        let ans = new_inner.into_unknown().unwrap();
954        Ok(ans)
955    }
956
957    fn get_attached(&self) -> Option<Self> {
958        Some(self.clone())
959    }
960
961    fn doc(&self) -> Option<LoroDoc> {
962        Some(self.inner.doc())
963    }
964}
965
966#[derive(Clone, EnumAsInner, Debug)]
967pub enum Handler {
968    Text(TextHandler),
969    Map(MapHandler),
970    List(ListHandler),
971    MovableList(MovableListHandler),
972    Tree(TreeHandler),
973    #[cfg(feature = "counter")]
974    Counter(counter::CounterHandler),
975    Unknown(UnknownHandler),
976}
977
978impl HandlerTrait for Handler {
979    fn is_attached(&self) -> bool {
980        match self {
981            Self::Text(x) => x.is_attached(),
982            Self::Map(x) => x.is_attached(),
983            Self::List(x) => x.is_attached(),
984            Self::Tree(x) => x.is_attached(),
985            Self::MovableList(x) => x.is_attached(),
986            #[cfg(feature = "counter")]
987            Self::Counter(x) => x.is_attached(),
988            Self::Unknown(x) => x.is_attached(),
989        }
990    }
991
992    fn attached_handler(&self) -> Option<&BasicHandler> {
993        match self {
994            Self::Text(x) => x.attached_handler(),
995            Self::Map(x) => x.attached_handler(),
996            Self::List(x) => x.attached_handler(),
997            Self::MovableList(x) => x.attached_handler(),
998            Self::Tree(x) => x.attached_handler(),
999            #[cfg(feature = "counter")]
1000            Self::Counter(x) => x.attached_handler(),
1001            Self::Unknown(x) => x.attached_handler(),
1002        }
1003    }
1004
1005    fn get_value(&self) -> LoroValue {
1006        match self {
1007            Self::Text(x) => x.get_value(),
1008            Self::Map(x) => x.get_value(),
1009            Self::List(x) => x.get_value(),
1010            Self::MovableList(x) => x.get_value(),
1011            Self::Tree(x) => x.get_value(),
1012            #[cfg(feature = "counter")]
1013            Self::Counter(x) => x.get_value(),
1014            Self::Unknown(x) => x.get_value(),
1015        }
1016    }
1017
1018    fn get_deep_value(&self) -> LoroValue {
1019        match self {
1020            Self::Text(x) => x.get_deep_value(),
1021            Self::Map(x) => x.get_deep_value(),
1022            Self::List(x) => x.get_deep_value(),
1023            Self::MovableList(x) => x.get_deep_value(),
1024            Self::Tree(x) => x.get_deep_value(),
1025            #[cfg(feature = "counter")]
1026            Self::Counter(x) => x.get_deep_value(),
1027            Self::Unknown(x) => x.get_deep_value(),
1028        }
1029    }
1030
1031    fn kind(&self) -> ContainerType {
1032        match self {
1033            Self::Text(x) => x.kind(),
1034            Self::Map(x) => x.kind(),
1035            Self::List(x) => x.kind(),
1036            Self::MovableList(x) => x.kind(),
1037            Self::Tree(x) => x.kind(),
1038            #[cfg(feature = "counter")]
1039            Self::Counter(x) => x.kind(),
1040            Self::Unknown(x) => x.kind(),
1041        }
1042    }
1043
1044    fn to_handler(&self) -> Handler {
1045        match self {
1046            Self::Text(x) => x.to_handler(),
1047            Self::Map(x) => x.to_handler(),
1048            Self::List(x) => x.to_handler(),
1049            Self::MovableList(x) => x.to_handler(),
1050            Self::Tree(x) => x.to_handler(),
1051            #[cfg(feature = "counter")]
1052            Self::Counter(x) => x.to_handler(),
1053            Self::Unknown(x) => x.to_handler(),
1054        }
1055    }
1056
1057    fn attach(
1058        &self,
1059        txn: &mut Transaction,
1060        parent: &BasicHandler,
1061        self_id: ContainerID,
1062    ) -> LoroResult<Self> {
1063        match self {
1064            Self::Text(x) => Ok(Handler::Text(x.attach(txn, parent, self_id)?)),
1065            Self::Map(x) => Ok(Handler::Map(x.attach(txn, parent, self_id)?)),
1066            Self::List(x) => Ok(Handler::List(x.attach(txn, parent, self_id)?)),
1067            Self::MovableList(x) => Ok(Handler::MovableList(x.attach(txn, parent, self_id)?)),
1068            Self::Tree(x) => Ok(Handler::Tree(x.attach(txn, parent, self_id)?)),
1069            #[cfg(feature = "counter")]
1070            Self::Counter(x) => Ok(Handler::Counter(x.attach(txn, parent, self_id)?)),
1071            Self::Unknown(x) => Ok(Handler::Unknown(x.attach(txn, parent, self_id)?)),
1072        }
1073    }
1074
1075    fn get_attached(&self) -> Option<Self> {
1076        match self {
1077            Self::Text(x) => x.get_attached().map(Handler::Text),
1078            Self::Map(x) => x.get_attached().map(Handler::Map),
1079            Self::List(x) => x.get_attached().map(Handler::List),
1080            Self::MovableList(x) => x.get_attached().map(Handler::MovableList),
1081            Self::Tree(x) => x.get_attached().map(Handler::Tree),
1082            #[cfg(feature = "counter")]
1083            Self::Counter(x) => x.get_attached().map(Handler::Counter),
1084            Self::Unknown(x) => x.get_attached().map(Handler::Unknown),
1085        }
1086    }
1087
1088    fn from_handler(h: Handler) -> Option<Self> {
1089        Some(h)
1090    }
1091
1092    fn doc(&self) -> Option<LoroDoc> {
1093        match self {
1094            Self::Text(x) => x.doc(),
1095            Self::Map(x) => x.doc(),
1096            Self::List(x) => x.doc(),
1097            Self::MovableList(x) => x.doc(),
1098            Self::Tree(x) => x.doc(),
1099            #[cfg(feature = "counter")]
1100            Self::Counter(x) => x.doc(),
1101            Self::Unknown(x) => x.doc(),
1102        }
1103    }
1104}
1105
1106impl Handler {
1107    fn apply_map_container_diff_value(
1108        map: &MapHandler,
1109        key: &str,
1110        old_id: ContainerID,
1111        on_container_remap: &mut dyn FnMut(ContainerID, ContainerID),
1112    ) -> LoroResult<()> {
1113        if old_id.is_mergeable() {
1114            let parent_id = map.id();
1115            let kind = old_id.container_type();
1116            let new_id = ContainerID::new_mergeable(&parent_id, key, kind);
1117            let marker = loro_common::mergeable_marker(&parent_id, key, kind);
1118            map.insert_without_skipping(key, marker)?;
1119            on_container_remap(old_id, new_id);
1120            return Ok(());
1121        }
1122
1123        let new_h = map.insert_container(key, Handler::new_unattached(old_id.container_type()))?;
1124        let new_id = new_h.id();
1125        on_container_remap(old_id, new_id);
1126        Ok(())
1127    }
1128
1129    pub(crate) fn new_attached(id: ContainerID, doc: LoroDoc) -> Self {
1130        let kind = id.container_type();
1131        let handler = BasicHandler {
1132            container_idx: doc.arena.register_container(&id),
1133            id,
1134            doc,
1135        };
1136
1137        match kind {
1138            ContainerType::Map => Self::Map(MapHandler {
1139                inner: handler.into(),
1140            }),
1141            ContainerType::List => Self::List(ListHandler {
1142                inner: handler.into(),
1143            }),
1144            ContainerType::Tree => Self::Tree(TreeHandler {
1145                inner: handler.into(),
1146            }),
1147            ContainerType::Text => Self::Text(TextHandler {
1148                inner: handler.into(),
1149            }),
1150            ContainerType::MovableList => Self::MovableList(MovableListHandler {
1151                inner: handler.into(),
1152            }),
1153            #[cfg(feature = "counter")]
1154            ContainerType::Counter => Self::Counter(counter::CounterHandler {
1155                inner: handler.into(),
1156            }),
1157            ContainerType::Unknown(_) => Self::Unknown(UnknownHandler { inner: handler }),
1158        }
1159    }
1160
1161    #[allow(unused)]
1162    pub(crate) fn new_unattached(kind: ContainerType) -> Self {
1163        match kind {
1164            ContainerType::Text => Self::Text(TextHandler::new_detached()),
1165            ContainerType::Map => Self::Map(MapHandler::new_detached()),
1166            ContainerType::List => Self::List(ListHandler::new_detached()),
1167            ContainerType::Tree => Self::Tree(TreeHandler::new_detached()),
1168            ContainerType::MovableList => Self::MovableList(MovableListHandler::new_detached()),
1169            #[cfg(feature = "counter")]
1170            ContainerType::Counter => Self::Counter(counter::CounterHandler::new_detached()),
1171            ContainerType::Unknown(_) => unreachable!(),
1172        }
1173    }
1174
1175    pub fn id(&self) -> ContainerID {
1176        match self {
1177            Self::Map(x) => x.id(),
1178            Self::List(x) => x.id(),
1179            Self::Text(x) => x.id(),
1180            Self::Tree(x) => x.id(),
1181            Self::MovableList(x) => x.id(),
1182            #[cfg(feature = "counter")]
1183            Self::Counter(x) => x.id(),
1184            Self::Unknown(x) => x.id(),
1185        }
1186    }
1187
1188    pub(crate) fn container_idx(&self) -> ContainerIdx {
1189        match self {
1190            Self::Map(x) => x.idx(),
1191            Self::List(x) => x.idx(),
1192            Self::Text(x) => x.idx(),
1193            Self::Tree(x) => x.idx(),
1194            Self::MovableList(x) => x.idx(),
1195            #[cfg(feature = "counter")]
1196            Self::Counter(x) => x.idx(),
1197            Self::Unknown(x) => x.idx(),
1198        }
1199    }
1200
1201    pub fn c_type(&self) -> ContainerType {
1202        match self {
1203            Self::Map(_) => ContainerType::Map,
1204            Self::List(_) => ContainerType::List,
1205            Self::Text(_) => ContainerType::Text,
1206            Self::Tree(_) => ContainerType::Tree,
1207            Self::MovableList(_) => ContainerType::MovableList,
1208            #[cfg(feature = "counter")]
1209            Self::Counter(_) => ContainerType::Counter,
1210            Self::Unknown(x) => x.id().container_type(),
1211        }
1212    }
1213
1214    fn get_deep_value(&self) -> LoroValue {
1215        match self {
1216            Self::Map(x) => x.get_deep_value(),
1217            Self::List(x) => x.get_deep_value(),
1218            Self::MovableList(x) => x.get_deep_value(),
1219            Self::Text(x) => x.get_deep_value(),
1220            Self::Tree(x) => x.get_deep_value(),
1221            #[cfg(feature = "counter")]
1222            Self::Counter(x) => x.get_deep_value(),
1223            Self::Unknown(x) => x.get_deep_value(),
1224        }
1225    }
1226
1227    pub(crate) fn apply_diff(
1228        &self,
1229        diff: Diff,
1230        container_remap: &mut FxHashMap<ContainerID, ContainerID>,
1231    ) -> LoroResult<()> {
1232        // In this method we will not clone the values of the containers if
1233        // they are remapped. It's the caller's duty to do so
1234        let on_container_remap = &mut |old_id, new_id| {
1235            if old_id != new_id {
1236                container_remap.insert(old_id, new_id);
1237            }
1238        };
1239        match self {
1240            Self::Map(x) => {
1241                let diff = match diff {
1242                    crate::event::Diff::Map(d) => d,
1243                    _ => {
1244                        return Err(LoroError::DecodeError(
1245                            "Invalid diff type for map container".into(),
1246                        ));
1247                    }
1248                };
1249                for (key, value) in diff.updated.into_iter() {
1250                    match value.value {
1251                        Some(ValueOrHandler::Handler(h)) => {
1252                            Self::apply_map_container_diff_value(
1253                                x,
1254                                &key,
1255                                h.id(),
1256                                on_container_remap,
1257                            )?;
1258                        }
1259                        Some(ValueOrHandler::Value(LoroValue::Container(old_id))) => {
1260                            Self::apply_map_container_diff_value(
1261                                x,
1262                                &key,
1263                                old_id,
1264                                on_container_remap,
1265                            )?;
1266                        }
1267                        Some(ValueOrHandler::Value(v)) => {
1268                            x.insert_without_skipping(&key, v)?;
1269                        }
1270                        None => {
1271                            x.delete(&key)?;
1272                        }
1273                    }
1274                }
1275            }
1276            Self::Text(x) => {
1277                let delta = match diff {
1278                    crate::event::Diff::Text(d) => d,
1279                    _ => {
1280                        return Err(LoroError::DecodeError(
1281                            "Invalid diff type for text container".into(),
1282                        ));
1283                    }
1284                };
1285                x.apply_delta(&TextDelta::from_text_diff(delta.iter()))?;
1286            }
1287            Self::List(x) => {
1288                let delta = match diff {
1289                    crate::event::Diff::List(d) => d,
1290                    _ => {
1291                        return Err(LoroError::DecodeError(
1292                            "Invalid diff type for list container".into(),
1293                        ));
1294                    }
1295                };
1296                x.apply_delta(delta, on_container_remap)?;
1297            }
1298            Self::MovableList(x) => {
1299                let delta = match diff {
1300                    crate::event::Diff::List(d) => d,
1301                    _ => {
1302                        return Err(LoroError::DecodeError(
1303                            "Invalid diff type for movable list container".into(),
1304                        ));
1305                    }
1306                };
1307                x.apply_delta(delta, container_remap)?;
1308            }
1309            Self::Tree(x) => {
1310                fn remap_tree_id(
1311                    id: &mut TreeID,
1312                    container_remap: &FxHashMap<ContainerID, ContainerID>,
1313                ) {
1314                    let mut remapped = false;
1315                    let mut map_id = id.associated_meta_container();
1316                    while let Some(rid) = container_remap.get(&map_id) {
1317                        remapped = true;
1318                        map_id = rid.clone();
1319                    }
1320                    if remapped {
1321                        *id = TreeID::new(
1322                            *map_id.as_normal().unwrap().0,
1323                            *map_id.as_normal().unwrap().1,
1324                        )
1325                    }
1326                }
1327                let tree_diff = match diff {
1328                    crate::event::Diff::Tree(d) => d,
1329                    _ => {
1330                        return Err(LoroError::DecodeError(
1331                            "Invalid diff type for tree container".into(),
1332                        ));
1333                    }
1334                };
1335                for diff in tree_diff.diff {
1336                    let mut target = diff.target;
1337                    match diff.action {
1338                        TreeExternalDiff::Create {
1339                            mut parent,
1340                            index: _,
1341                            position,
1342                        } => {
1343                            if let TreeParentId::Node(p) = &mut parent {
1344                                remap_tree_id(p, container_remap)
1345                            }
1346                            remap_tree_id(&mut target, container_remap);
1347                            if !x.is_node_unexist(&target) && !x.is_node_deleted(&target)? {
1348                                // 1@0 is the parent of 2@1
1349                                // ┌────┐    ┌───────────────┐
1350                                // │xxxx│◀───│Move 2@1 to 0@0◀┐
1351                                // └────┘    └───────────────┘│
1352                                // ┌───────┐                  │ ┌────────┐
1353                                // │Del 1@0│◀─────────────────┴─│Meta 2@1│ ◀───  undo 2 ops redo 2 ops
1354                                // └───────┘                    └────────┘
1355                                //
1356                                // When we undo the delete operation, we should not create a new tree node and its child.
1357                                // However, the concurrent operation has moved the child to another parent. It's still alive.
1358                                // So when we redo the delete operation, we should check if the target is still alive.
1359                                // If it's alive, we should move it back instead of creating new one.
1360                                x.move_at_with_target_for_apply_diff(parent, position, target)?;
1361                            } else {
1362                                let new_target = x.__internal__next_tree_id();
1363                                if x.create_at_with_target_for_apply_diff(
1364                                    parent, position, new_target,
1365                                )? {
1366                                    container_remap.insert(
1367                                        target.associated_meta_container(),
1368                                        new_target.associated_meta_container(),
1369                                    );
1370                                }
1371                            }
1372                        }
1373                        TreeExternalDiff::Move {
1374                            mut parent,
1375                            index: _,
1376                            position,
1377                            old_parent: _,
1378                            old_index: _,
1379                        } => {
1380                            if let TreeParentId::Node(p) = &mut parent {
1381                                remap_tree_id(p, container_remap)
1382                            }
1383                            remap_tree_id(&mut target, container_remap);
1384                            // determine if the target is deleted
1385                            if x.is_node_unexist(&target) || x.is_node_deleted(&target)? {
1386                                // create the target node, we should use the new target id
1387                                let new_target = x.__internal__next_tree_id();
1388                                if x.create_at_with_target_for_apply_diff(
1389                                    parent, position, new_target,
1390                                )? {
1391                                    container_remap.insert(
1392                                        target.associated_meta_container(),
1393                                        new_target.associated_meta_container(),
1394                                    );
1395                                }
1396                            } else {
1397                                x.move_at_with_target_for_apply_diff(parent, position, target)?;
1398                            }
1399                        }
1400                        TreeExternalDiff::Delete { .. } => {
1401                            remap_tree_id(&mut target, container_remap);
1402                            if !x.is_node_deleted(&target)? {
1403                                x.delete(target)?;
1404                            }
1405                        }
1406                    }
1407                }
1408            }
1409            #[cfg(feature = "counter")]
1410            Self::Counter(x) => {
1411                let delta = match diff {
1412                    crate::event::Diff::Counter(d) => d,
1413                    _ => {
1414                        return Err(LoroError::DecodeError(
1415                            "Invalid diff type for counter container".into(),
1416                        ));
1417                    }
1418                };
1419                x.increment(delta)?;
1420            }
1421            Self::Unknown(_) => {
1422                // do nothing
1423            }
1424        }
1425
1426        Ok(())
1427    }
1428
1429    pub fn clear(&self) -> LoroResult<()> {
1430        match self {
1431            Handler::Text(text_handler) => text_handler.clear(),
1432            Handler::Map(map_handler) => map_handler.clear(),
1433            Handler::List(list_handler) => list_handler.clear(),
1434            Handler::MovableList(movable_list_handler) => movable_list_handler.clear(),
1435            Handler::Tree(tree_handler) => tree_handler.clear(),
1436            #[cfg(feature = "counter")]
1437            Handler::Counter(counter_handler) => counter_handler.clear(),
1438            Handler::Unknown(_unknown_handler) => Ok(()),
1439        }
1440    }
1441}
1442
1443#[derive(Clone, EnumAsInner, Debug)]
1444pub enum ValueOrHandler {
1445    Value(LoroValue),
1446    Handler(Handler),
1447}
1448
1449impl ValueOrHandler {
1450    pub(crate) fn from_value(value: LoroValue, doc: &Arc<LoroDocInner>) -> Self {
1451        if let LoroValue::Container(c) = value {
1452            ValueOrHandler::Handler(Handler::new_attached(c, LoroDoc::from_inner(doc.clone())))
1453        } else {
1454            ValueOrHandler::Value(value)
1455        }
1456    }
1457
1458    pub(crate) fn to_value(&self) -> LoroValue {
1459        match self {
1460            Self::Value(v) => v.clone(),
1461            Self::Handler(h) => LoroValue::Container(h.id().clone()),
1462        }
1463    }
1464
1465    pub(crate) fn to_deep_value(&self) -> LoroValue {
1466        match self {
1467            Self::Value(v) => v.clone(),
1468            Self::Handler(h) => h.get_deep_value(),
1469        }
1470    }
1471}
1472
1473impl From<LoroValue> for ValueOrHandler {
1474    fn from(value: LoroValue) -> Self {
1475        ValueOrHandler::Value(value)
1476    }
1477}
1478
1479impl TextHandler {
1480    /// Create a new container that is detached from the document.
1481    ///
1482    /// The edits on a detached container will not be persisted.
1483    /// To attach the container to the document, please insert it into an attached container.
1484    pub fn new_detached() -> Self {
1485        Self {
1486            inner: MaybeDetached::new_detached(RichtextState::default()),
1487        }
1488    }
1489
1490    /// Get the version id of the richtext
1491    ///
1492    /// This can be used to detect whether the richtext is changed
1493    pub fn version_id(&self) -> Option<usize> {
1494        match &self.inner {
1495            MaybeDetached::Detached(_) => None,
1496            MaybeDetached::Attached(a) => {
1497                Some(a.with_state(|state| state.as_richtext_state_mut().unwrap().get_version_id()))
1498            }
1499        }
1500    }
1501
1502    pub fn get_richtext_value(&self) -> LoroValue {
1503        match &self.inner {
1504            MaybeDetached::Detached(t) => {
1505                let t = t.lock();
1506                t.value.get_richtext_value()
1507            }
1508            MaybeDetached::Attached(a) => {
1509                a.with_state(|state| state.as_richtext_state_mut().unwrap().get_richtext_value())
1510            }
1511        }
1512    }
1513
1514    pub fn is_empty(&self) -> bool {
1515        match &self.inner {
1516            MaybeDetached::Detached(t) => t.lock().value.is_empty(),
1517            MaybeDetached::Attached(a) if a.has_decoded_state() => {
1518                a.with_state(|state| state.as_richtext_state_mut().unwrap().is_empty())
1519            }
1520            MaybeDetached::Attached(a) => a.get_value().as_string().unwrap().is_empty(),
1521        }
1522    }
1523
1524    pub fn len_utf8(&self) -> usize {
1525        match &self.inner {
1526            MaybeDetached::Detached(t) => {
1527                let t = t.lock();
1528                t.value.len_utf8()
1529            }
1530            MaybeDetached::Attached(a) => {
1531                a.with_doc_state(|state| state.get_text_len(a.container_idx, PosType::Bytes))
1532            }
1533        }
1534    }
1535
1536    pub fn len_utf16(&self) -> usize {
1537        match &self.inner {
1538            MaybeDetached::Detached(t) => {
1539                let t = t.lock();
1540                t.value.len_utf16()
1541            }
1542            MaybeDetached::Attached(a) => {
1543                a.with_doc_state(|state| state.get_text_len(a.container_idx, PosType::Utf16))
1544            }
1545        }
1546    }
1547
1548    pub fn len_unicode(&self) -> usize {
1549        match &self.inner {
1550            MaybeDetached::Detached(t) => {
1551                let t = t.lock();
1552                t.value.len_unicode()
1553            }
1554            MaybeDetached::Attached(a) => {
1555                a.with_doc_state(|state| state.get_text_len(a.container_idx, PosType::Unicode))
1556            }
1557        }
1558    }
1559
1560    /// if `wasm` feature is enabled, it is a UTF-16 length
1561    /// otherwise, it is a Unicode length
1562    pub fn len_event(&self) -> usize {
1563        if cfg!(feature = "wasm") {
1564            self.len_utf16()
1565        } else {
1566            self.len_unicode()
1567        }
1568    }
1569
1570    fn len(&self, pos_type: PosType) -> usize {
1571        match &self.inner {
1572            MaybeDetached::Detached(t) => t.lock().value.len(pos_type),
1573            MaybeDetached::Attached(a) => {
1574                a.with_doc_state(|state| state.get_text_len(a.container_idx, pos_type))
1575            }
1576        }
1577    }
1578
1579    fn validate_text_boundary(&self, pos: usize, pos_type: PosType) -> LoroResult<()> {
1580        let err = match pos_type {
1581            PosType::Bytes => Some(LoroError::UTF8InUnicodeCodePoint { pos }),
1582            PosType::Utf16 => Some(LoroError::UTF16InUnicodeCodePoint { pos }),
1583            PosType::Event if cfg!(feature = "wasm") => {
1584                Some(LoroError::UTF16InUnicodeCodePoint { pos })
1585            }
1586            _ => None,
1587        };
1588
1589        let Some(err) = err else {
1590            return Ok(());
1591        };
1592
1593        let len = self.len(pos_type);
1594        if pos > len {
1595            return Ok(());
1596        }
1597
1598        if self.all_text_positions_are_boundaries(pos_type, len) {
1599            return Ok(());
1600        }
1601
1602        let Some(unicode_pos) = self.convert_pos(pos, pos_type, PosType::Unicode) else {
1603            return Err(err);
1604        };
1605        if self.convert_pos(unicode_pos, PosType::Unicode, pos_type) != Some(pos) {
1606            return Err(err);
1607        }
1608
1609        Ok(())
1610    }
1611
1612    fn all_text_positions_are_boundaries(&self, pos_type: PosType, len: usize) -> bool {
1613        match pos_type {
1614            PosType::Bytes => len == self.len_unicode(),
1615            PosType::Utf16 => len == self.len_unicode(),
1616            PosType::Event if cfg!(feature = "wasm") => len == self.len_unicode(),
1617            _ => false,
1618        }
1619    }
1620
1621    pub fn diagnose(&self) {
1622        match &self.inner {
1623            MaybeDetached::Detached(t) => {
1624                let t = t.lock();
1625                t.value.diagnose();
1626            }
1627            MaybeDetached::Attached(a) => {
1628                a.with_state(|state| state.as_richtext_state_mut().unwrap().diagnose());
1629            }
1630        }
1631    }
1632
1633    pub fn iter(&self, mut callback: impl FnMut(&str) -> bool) {
1634        // Do not call user callbacks while holding the state lock; callbacks may re-enter Loro.
1635        let spans: Vec<String> = match &self.inner {
1636            MaybeDetached::Detached(t) => {
1637                let t = t.lock();
1638                t.value
1639                    .iter()
1640                    .map(|span| span.text.as_str().to_owned())
1641                    .collect()
1642            }
1643            MaybeDetached::Attached(a) => a.with_state(|state| {
1644                let mut spans = Vec::new();
1645                state.as_richtext_state_mut().unwrap().iter(|span| {
1646                    spans.push(span.to_owned());
1647                    true
1648                });
1649                spans
1650            }),
1651        };
1652
1653        for span in spans {
1654            if !callback(span.as_str()) {
1655                return;
1656            }
1657        }
1658    }
1659
1660    /// Get a character at `pos` in the coordinate system specified by `pos_type`.
1661    pub fn char_at(&self, pos: usize, pos_type: PosType) -> LoroResult<char> {
1662        let len = self.len(pos_type);
1663        if pos >= len {
1664            return Err(LoroError::OutOfBound {
1665                pos,
1666                len,
1667                info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
1668            });
1669        }
1670        if let Ok(c) = match &self.inner {
1671            MaybeDetached::Detached(t) => {
1672                let t = t.lock();
1673                let event_pos = match pos_type {
1674                    PosType::Event => pos,
1675                    _ => t.value.index_to_event_index(pos, pos_type),
1676                };
1677                t.value.get_char_by_event_index(event_pos)
1678            }
1679            MaybeDetached::Attached(a) if a.has_decoded_state() || pos_type == PosType::Entity => a
1680                .with_state(|state| {
1681                    let state = state.as_richtext_state_mut().unwrap();
1682                    let event_pos = match pos_type {
1683                        PosType::Event => pos,
1684                        _ => state.index_to_event_index(pos, pos_type),
1685                    };
1686                    state.get_char_by_event_index(event_pos)
1687                }),
1688            MaybeDetached::Attached(a) => {
1689                return text_char_at(a.get_value().as_string().unwrap(), pos, pos_type);
1690            }
1691        } {
1692            Ok(c)
1693        } else {
1694            Err(LoroError::OutOfBound {
1695                pos,
1696                len,
1697                info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
1698            })
1699        }
1700    }
1701
1702    /// `start_index` and `end_index` are Event Index:
1703    ///
1704    /// - if feature="wasm", pos is a UTF-16 index
1705    /// - if feature!="wasm", pos is a Unicode index
1706    ///
1707    pub fn slice(
1708        &self,
1709        start_index: usize,
1710        end_index: usize,
1711        pos_type: PosType,
1712    ) -> LoroResult<String> {
1713        self.slice_with_pos_type(start_index, end_index, pos_type)
1714    }
1715
1716    pub fn slice_utf16(&self, start_index: usize, end_index: usize) -> LoroResult<String> {
1717        self.slice(start_index, end_index, PosType::Utf16)
1718    }
1719
1720    fn slice_with_pos_type(
1721        &self,
1722        start_index: usize,
1723        end_index: usize,
1724        pos_type: PosType,
1725    ) -> LoroResult<String> {
1726        if end_index < start_index {
1727            return Err(LoroError::EndIndexLessThanStartIndex {
1728                start: start_index,
1729                end: end_index,
1730            });
1731        }
1732        if start_index == end_index {
1733            return Ok(String::new());
1734        }
1735
1736        let info = || format!("Position: {}:{}", file!(), line!()).into_boxed_str();
1737        match &self.inner {
1738            MaybeDetached::Detached(t) => {
1739                let t = t.lock();
1740                let len = t.value.len(pos_type);
1741                if end_index > len {
1742                    return Err(LoroError::OutOfBound {
1743                        pos: end_index,
1744                        len,
1745                        info: info(),
1746                    });
1747                }
1748                let (start, end) = match pos_type {
1749                    PosType::Event => (start_index, end_index),
1750                    _ => (
1751                        t.value.index_to_event_index(start_index, pos_type),
1752                        t.value.index_to_event_index(end_index, pos_type),
1753                    ),
1754                };
1755                t.value.get_text_slice_by_event_index(start, end - start)
1756            }
1757            MaybeDetached::Attached(a) if a.has_decoded_state() || pos_type == PosType::Entity => a
1758                .with_state(|state| {
1759                    let state = state.as_richtext_state_mut().unwrap();
1760                    let len = state.len(pos_type);
1761                    if end_index > len {
1762                        return Err(LoroError::OutOfBound {
1763                            pos: end_index,
1764                            len,
1765                            info: info(),
1766                        });
1767                    }
1768                    let (start, end) = match pos_type {
1769                        PosType::Event => (start_index, end_index),
1770                        _ => (
1771                            state.index_to_event_index(start_index, pos_type),
1772                            state.index_to_event_index(end_index, pos_type),
1773                        ),
1774                    };
1775                    state.get_text_slice_by_event_index(start, end - start)
1776                }),
1777            MaybeDetached::Attached(a) => text_slice(
1778                a.get_value().as_string().unwrap(),
1779                start_index,
1780                end_index,
1781                pos_type,
1782            )
1783            .map_err(|err| match err {
1784                LoroError::OutOfBound { pos, len, .. } => LoroError::OutOfBound {
1785                    pos,
1786                    len,
1787                    info: info(),
1788                },
1789                err => err,
1790            }),
1791        }
1792    }
1793
1794    pub fn slice_delta(
1795        &self,
1796        start_index: usize,
1797        end_index: usize,
1798        pos_type: PosType,
1799    ) -> LoroResult<Vec<TextDelta>> {
1800        if end_index < start_index {
1801            return Err(LoroError::EndIndexLessThanStartIndex {
1802                start: start_index,
1803                end: end_index,
1804            });
1805        }
1806        if start_index == end_index {
1807            return Ok(Vec::new());
1808        }
1809
1810        let len = self.len(pos_type);
1811        if end_index > len {
1812            return Err(LoroError::OutOfBound {
1813                pos: end_index,
1814                len,
1815                info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
1816            });
1817        }
1818        self.validate_text_boundary(start_index, pos_type)?;
1819        self.validate_text_boundary(end_index, pos_type)?;
1820
1821        match &self.inner {
1822            MaybeDetached::Detached(t) => {
1823                let t = t.lock();
1824                let ans = t.value.slice_delta(start_index, end_index, pos_type)?;
1825                Ok(ans
1826                    .into_iter()
1827                    .map(|(s, a)| TextDelta::Insert {
1828                        insert: s,
1829                        attributes: a.to_option_map_without_null_value(),
1830                    })
1831                    .collect())
1832            }
1833            MaybeDetached::Attached(a) => a.with_state(|state| {
1834                let ans = state.as_richtext_state_mut().unwrap().slice_delta(
1835                    start_index,
1836                    end_index,
1837                    pos_type,
1838                )?;
1839                Ok(ans
1840                    .into_iter()
1841                    .map(|(s, a)| TextDelta::Insert {
1842                        insert: s,
1843                        attributes: a.to_option_map_without_null_value(),
1844                    })
1845                    .collect())
1846            }),
1847        }
1848    }
1849
1850    /// `pos` is a Event Index:
1851    ///
1852    /// - if feature="wasm", pos is a UTF-16 index
1853    /// - if feature!="wasm", pos is a Unicode index
1854    ///
1855    /// This method requires auto_commit to be enabled.
1856    pub fn splice(&self, pos: usize, len: usize, s: &str, pos_type: PosType) -> LoroResult<String> {
1857        let end = checked_range_end(pos, len, self.len(pos_type), || {
1858            format!("Position: {}:{}", file!(), line!()).into_boxed_str()
1859        })?;
1860        let x = self.slice(pos, end, pos_type)?;
1861        self.delete(pos, len, pos_type)?;
1862        self.insert(pos, s, pos_type)?;
1863        Ok(x)
1864    }
1865
1866    pub fn splice_utf8(&self, pos: usize, len: usize, s: &str) -> LoroResult<()> {
1867        // let x = self.slice(pos, pos + len)?;
1868        self.delete_utf8(pos, len)?;
1869        self.insert_utf8(pos, s)?;
1870        Ok(())
1871    }
1872
1873    pub fn splice_utf16(&self, pos: usize, len: usize, s: &str) -> LoroResult<()> {
1874        self.delete(pos, len, PosType::Utf16)?;
1875        self.insert(pos, s, PosType::Utf16)?;
1876        Ok(())
1877    }
1878
1879    /// Insert text at `pos` using the given `pos_type` coordinate system.
1880    ///
1881    /// This method requires auto_commit to be enabled.
1882    pub fn insert(&self, pos: usize, s: &str, pos_type: PosType) -> LoroResult<()> {
1883        match &self.inner {
1884            MaybeDetached::Detached(t) => {
1885                let len = self.len(pos_type);
1886                if pos > len {
1887                    return Err(LoroError::OutOfBound {
1888                        pos,
1889                        len,
1890                        info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
1891                    });
1892                }
1893                self.validate_text_boundary(pos, pos_type)?;
1894
1895                let mut t = t.lock();
1896                let (index, _) = t
1897                    .value
1898                    .get_entity_index_for_text_insert(pos, pos_type)
1899                    .unwrap();
1900                t.value.insert_at_entity_index(
1901                    index,
1902                    BytesSlice::from_bytes(s.as_bytes()),
1903                    IdFull::NONE_ID,
1904                );
1905                Ok(())
1906            }
1907            MaybeDetached::Attached(a) => {
1908                if s.is_empty() {
1909                    let len = self.len(pos_type);
1910                    if pos > len {
1911                        return Err(LoroError::OutOfBound {
1912                            pos,
1913                            len,
1914                            info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
1915                        });
1916                    }
1917                    self.validate_text_boundary(pos, pos_type)?;
1918                    return Ok(());
1919                }
1920
1921                a.with_txn(|txn| self.insert_with_txn(txn, pos, s, pos_type))
1922            }
1923        }
1924    }
1925
1926    pub fn insert_utf8(&self, pos: usize, s: &str) -> LoroResult<()> {
1927        self.insert(pos, s, PosType::Bytes)
1928    }
1929
1930    pub fn insert_utf16(&self, pos: usize, s: &str) -> LoroResult<()> {
1931        self.insert(pos, s, PosType::Utf16)
1932    }
1933
1934    pub fn insert_unicode(&self, pos: usize, s: &str) -> LoroResult<()> {
1935        self.insert(pos, s, PosType::Unicode)
1936    }
1937
1938    /// Insert text within an existing transaction using the provided `pos_type`.
1939    pub fn insert_with_txn(
1940        &self,
1941        txn: &mut Transaction,
1942        pos: usize,
1943        s: &str,
1944        pos_type: PosType,
1945    ) -> LoroResult<()> {
1946        self.insert_with_txn_and_attr(txn, pos, s, None, pos_type)?;
1947        Ok(())
1948    }
1949
1950    pub fn insert_with_txn_utf8(
1951        &self,
1952        txn: &mut Transaction,
1953        pos: usize,
1954        s: &str,
1955    ) -> LoroResult<()> {
1956        self.insert_with_txn(txn, pos, s, PosType::Bytes)
1957    }
1958
1959    /// Delete a span using the coordinate system described by `pos_type`.
1960    ///
1961    /// This method requires auto_commit to be enabled.
1962    pub fn delete(&self, pos: usize, len: usize, pos_type: PosType) -> LoroResult<()> {
1963        if len == 0 {
1964            return Ok(());
1965        }
1966
1967        let text_len = self.len(pos_type);
1968        let end = checked_range_end(pos, len, text_len, || {
1969            format!("Position: {}:{}", file!(), line!()).into_boxed_str()
1970        })?;
1971        self.validate_text_boundary(pos, pos_type)?;
1972        self.validate_text_boundary(end, pos_type)?;
1973
1974        match &self.inner {
1975            MaybeDetached::Detached(t) => {
1976                let mut t = t.lock();
1977                let ranges = t.value.get_text_entity_ranges(pos, len, pos_type)?;
1978                for range in ranges.iter().rev() {
1979                    t.value
1980                        .drain_by_entity_index(range.entity_start, range.entity_len(), None);
1981                }
1982                Ok(())
1983            }
1984            MaybeDetached::Attached(a) => {
1985                a.with_txn(|txn| self.delete_with_txn(txn, pos, len, pos_type))
1986            }
1987        }
1988    }
1989
1990    pub fn delete_utf8(&self, pos: usize, len: usize) -> LoroResult<()> {
1991        self.delete(pos, len, PosType::Bytes)
1992    }
1993
1994    pub fn delete_utf16(&self, pos: usize, len: usize) -> LoroResult<()> {
1995        self.delete(pos, len, PosType::Utf16)
1996    }
1997
1998    pub fn delete_unicode(&self, pos: usize, len: usize) -> LoroResult<()> {
1999        self.delete(pos, len, PosType::Unicode)
2000    }
2001
2002    /// If attr is specified, it will be used as the attribute of the inserted text.
2003    /// It will override the existing attribute of the text.
2004    fn insert_with_txn_and_attr(
2005        &self,
2006        txn: &mut Transaction,
2007        pos: usize,
2008        s: &str,
2009        attr: Option<&FxHashMap<String, LoroValue>>,
2010        pos_type: PosType,
2011    ) -> Result<Vec<(InternalString, LoroValue)>, LoroError> {
2012        if s.is_empty() {
2013            return Ok(Vec::new());
2014        }
2015
2016        // Fast path: plain-text insert into a style-free document (non-wasm).
2017        // With no style anchors, entity_index == unicode pos and the event index
2018        // equals the unicode index, so bounds + no-styles are checked in a single
2019        // state access and the entire read phase (cursor location + two
2020        // visit_previous_caches walks + styles lookup) is skipped; apply_local_op
2021        // then locates the cursor exactly once.
2022        #[cfg(not(feature = "wasm"))]
2023        if attr.is_none() && pos_type == PosType::Unicode {
2024            let inner = self.inner.try_attached_state()?;
2025            let fast = inner.with_state(|state| {
2026                let rt = state.as_richtext_state_mut().unwrap();
2027                if rt.has_styles() {
2028                    return Ok(false);
2029                }
2030                let len = rt.len_unicode();
2031                if pos > len {
2032                    return Err(LoroError::OutOfBound {
2033                        pos,
2034                        len,
2035                        info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
2036                    });
2037                }
2038                Ok(true)
2039            })?;
2040            if fast {
2041                let unicode_len = s.chars().count();
2042                txn.apply_local_op(
2043                    inner.container_idx,
2044                    crate::op::RawOpContent::List(
2045                        crate::container::list::list_op::ListOp::Insert {
2046                            slice: ListSlice::RawStr {
2047                                str: Cow::Borrowed(s),
2048                                unicode_len,
2049                            },
2050                            // entity_index == unicode pos (no style anchors)
2051                            pos,
2052                        },
2053                    ),
2054                    EventHint::InsertText {
2055                        // event index == unicode index (non-wasm)
2056                        pos: pos as u32,
2057                        styles: StyleMeta::empty(),
2058                        unicode_len: unicode_len as u32,
2059                        event_len: unicode_len as u32,
2060                    },
2061                    &inner.doc,
2062                )?;
2063                return Ok(Vec::new());
2064            }
2065        }
2066
2067        match pos_type {
2068            PosType::Event => {
2069                if pos > self.len_event() {
2070                    return Err(LoroError::OutOfBound {
2071                        pos,
2072                        len: self.len_event(),
2073                        info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
2074                    });
2075                }
2076            }
2077            PosType::Bytes => {
2078                if pos > self.len_utf8() {
2079                    return Err(LoroError::OutOfBound {
2080                        pos,
2081                        len: self.len_utf8(),
2082                        info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
2083                    });
2084                }
2085            }
2086            PosType::Unicode => {
2087                if pos > self.len_unicode() {
2088                    return Err(LoroError::OutOfBound {
2089                        pos,
2090                        len: self.len_unicode(),
2091                        info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
2092                    });
2093                }
2094            }
2095            PosType::Entity => {}
2096            PosType::Utf16 => {
2097                if pos > self.len_utf16() {
2098                    return Err(LoroError::OutOfBound {
2099                        pos,
2100                        len: self.len_utf16(),
2101                        info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
2102                    });
2103                }
2104            }
2105        }
2106        self.validate_text_boundary(pos, pos_type)?;
2107
2108        let inner = self.inner.try_attached_state()?;
2109        let (entity_index, event_index, styles) = inner.with_state(|state| {
2110            let richtext_state = state.as_richtext_state_mut().unwrap();
2111            let ret = richtext_state.get_entity_index_for_text_insert(pos, pos_type);
2112            let (entity_index, cursor) = match ret {
2113                Err(_) => match pos_type {
2114                    PosType::Bytes => {
2115                        return (
2116                            Err(LoroError::UTF8InUnicodeCodePoint { pos }),
2117                            0,
2118                            StyleMeta::empty(),
2119                        );
2120                    }
2121                    PosType::Utf16 | PosType::Event => {
2122                        return (
2123                            Err(LoroError::UTF16InUnicodeCodePoint { pos }),
2124                            0,
2125                            StyleMeta::empty(),
2126                        );
2127                    }
2128                    _ => unreachable!(),
2129                },
2130                Ok(x) => x,
2131            };
2132            let event_index = if let Some(cursor) = cursor {
2133                if pos_type == PosType::Event {
2134                    debug_assert_eq!(
2135                        richtext_state.get_event_index_by_cursor(cursor),
2136                        pos,
2137                        "pos={} cursor={:?} state={:#?}",
2138                        pos,
2139                        cursor,
2140                        &richtext_state
2141                    );
2142                    pos
2143                } else {
2144                    richtext_state.get_event_index_by_cursor(cursor)
2145                }
2146            } else {
2147                assert_eq!(entity_index, 0);
2148                0
2149            };
2150            let styles = richtext_state.get_styles_at_entity_index(entity_index);
2151            (Ok(entity_index), event_index, styles)
2152        });
2153
2154        let entity_index = match entity_index {
2155            Err(x) => return Err(x),
2156            _ => entity_index.unwrap(),
2157        };
2158
2159        let mut override_styles = Vec::new();
2160        if let Some(attr) = attr {
2161            // current styles
2162            let map: FxHashMap<_, _> = styles.iter().map(|x| (x.0.clone(), x.1.data)).collect();
2163            for (key, style) in map.iter() {
2164                match attr.get(key.deref()) {
2165                    Some(v) if v == style => {}
2166                    new_style_value => {
2167                        // need to override
2168                        let new_style_value = new_style_value.cloned().unwrap_or(LoroValue::Null);
2169                        override_styles.push((key.clone(), new_style_value));
2170                    }
2171                }
2172            }
2173
2174            for (key, style) in attr.iter() {
2175                let key = key.as_str().into();
2176                if !map.contains_key(&key) {
2177                    override_styles.push((key, style.clone()));
2178                }
2179            }
2180        }
2181
2182        let unicode_len = s.chars().count();
2183        let event_len = if cfg!(feature = "wasm") {
2184            count_utf16_len(s.as_bytes())
2185        } else {
2186            unicode_len
2187        };
2188
2189        txn.apply_local_op(
2190            inner.container_idx,
2191            crate::op::RawOpContent::List(crate::container::list::list_op::ListOp::Insert {
2192                slice: ListSlice::RawStr {
2193                    str: Cow::Borrowed(s),
2194                    unicode_len,
2195                },
2196                pos: entity_index,
2197            }),
2198            EventHint::InsertText {
2199                pos: event_index as u32,
2200                styles,
2201                unicode_len: unicode_len as u32,
2202                event_len: event_len as u32,
2203            },
2204            &inner.doc,
2205        )?;
2206
2207        Ok(override_styles)
2208    }
2209
2210    /// Delete text within a transaction using the specified `pos_type`.
2211    pub fn delete_with_txn(
2212        &self,
2213        txn: &mut Transaction,
2214        pos: usize,
2215        len: usize,
2216        pos_type: PosType,
2217    ) -> LoroResult<()> {
2218        self.delete_with_txn_inline(txn, pos, len, pos_type)
2219    }
2220
2221    fn delete_with_txn_inline(
2222        &self,
2223        txn: &mut Transaction,
2224        pos: usize,
2225        len: usize,
2226        pos_type: PosType,
2227    ) -> LoroResult<()> {
2228        if len == 0 {
2229            return Ok(());
2230        }
2231
2232        let text_len = self.len(pos_type);
2233        let end = checked_range_end(pos, len, text_len, || {
2234            format!("Position: {}:{}", file!(), line!()).into_boxed_str()
2235        })
2236        .inspect_err(|_| error!("pos={} len={} len={}", pos, len, text_len))?;
2237        self.validate_text_boundary(pos, pos_type)?;
2238        self.validate_text_boundary(end, pos_type)?;
2239
2240        let inner = self.inner.try_attached_state()?;
2241        let s = tracing::span!(tracing::Level::INFO, "delete", "pos={} len={}", pos, len);
2242        let _e = s.enter();
2243        let mut event_pos = 0;
2244        let mut event_len = 0;
2245        let ranges = inner.with_state(|state| {
2246            let richtext_state = state.as_richtext_state_mut().unwrap();
2247            // Fast path: with no style anchors (non-wasm), the event index equals
2248            // the unicode index, so the two index_to_event_index walks collapse to
2249            // identity.
2250            let fast = cfg!(not(feature = "wasm"))
2251                && pos_type == PosType::Unicode
2252                && !richtext_state.has_styles();
2253            if fast {
2254                event_pos = pos;
2255                event_len = len;
2256            } else {
2257                event_pos = richtext_state.index_to_event_index(pos, pos_type);
2258                let event_end = richtext_state.index_to_event_index(end, pos_type);
2259                event_len = event_end - event_pos;
2260            }
2261
2262            richtext_state.get_text_entity_ranges_in_event_index_range(event_pos, event_len)
2263        })?;
2264
2265        //debug_assert_eq!(ranges.iter().map(|x| x.event_len).sum::<usize>(), len);
2266        let pos = event_pos as isize;
2267        let len = event_len as isize;
2268        let mut event_end = pos + len;
2269        for range in ranges.iter().rev() {
2270            let event_start = event_end - range.event_len as isize;
2271            txn.apply_local_op(
2272                inner.container_idx,
2273                crate::op::RawOpContent::List(ListOp::Delete(DeleteSpanWithId::new(
2274                    range.id_start,
2275                    range.entity_start as isize,
2276                    range.entity_len() as isize,
2277                ))),
2278                EventHint::DeleteText {
2279                    span: DeleteSpan {
2280                        pos: event_start,
2281                        signed_len: range.event_len as isize,
2282                    },
2283                    unicode_len: range.entity_len(),
2284                },
2285                &inner.doc,
2286            )?;
2287            event_end = event_start;
2288        }
2289
2290        Ok(())
2291    }
2292
2293    /// `start` and `end` are interpreted using `pos_type`.
2294    ///
2295    /// This method requires auto_commit to be enabled.
2296    pub fn mark(
2297        &self,
2298        start: usize,
2299        end: usize,
2300        key: impl Into<InternalString>,
2301        value: LoroValue,
2302        pos_type: PosType,
2303    ) -> LoroResult<()> {
2304        match &self.inner {
2305            MaybeDetached::Detached(t) => {
2306                let mut g = t.lock();
2307                self.mark_for_detached(&mut g.value, key, &value, start, end, pos_type)
2308            }
2309            MaybeDetached::Attached(a) => {
2310                a.with_txn(|txn| self.mark_with_txn(txn, start, end, key, value, pos_type))
2311            }
2312        }
2313    }
2314
2315    fn mark_for_detached(
2316        &self,
2317        state: &mut RichtextState,
2318        key: impl Into<InternalString>,
2319        value: &LoroValue,
2320        start: usize,
2321        end: usize,
2322        pos_type: PosType,
2323    ) -> Result<(), LoroError> {
2324        let key: InternalString = key.into();
2325        let is_delete = matches!(value, &LoroValue::Null);
2326        if start >= end {
2327            return Err(loro_common::LoroError::ArgErr(
2328                "Start must be less than end".to_string().into_boxed_str(),
2329            ));
2330        }
2331        ensure_no_regular_container_value(value)?;
2332
2333        let len = state.len(pos_type);
2334        if end > len {
2335            return Err(LoroError::OutOfBound {
2336                pos: end,
2337                len,
2338                info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
2339            });
2340        }
2341        let (entity_range, styles) =
2342            state.get_entity_range_and_text_styles_at_range(start..end, pos_type);
2343        if let Some(styles) = styles {
2344            if styles.has_key_value(&key, value) {
2345                return Ok(());
2346            }
2347        }
2348
2349        let has_target_style =
2350            state.range_has_style_key(entity_range.clone(), &StyleKey::Key(key.clone()));
2351        let missing_style_key = is_delete && !has_target_style;
2352
2353        if missing_style_key {
2354            return Ok(());
2355        }
2356
2357        let style_op = Arc::new(StyleOp {
2358            lamport: 0,
2359            peer: 0,
2360            cnt: 0,
2361            key,
2362            value: value.clone(),
2363            // TODO: describe this behavior in the document
2364            info: if is_delete {
2365                TextStyleInfoFlag::BOLD.to_delete()
2366            } else {
2367                TextStyleInfoFlag::BOLD
2368            },
2369        });
2370        state.mark_with_entity_index(entity_range, style_op);
2371        Ok(())
2372    }
2373
2374    /// `start` and `end` are interpreted using `pos_type`.
2375    pub fn unmark(
2376        &self,
2377        start: usize,
2378        end: usize,
2379        key: impl Into<InternalString>,
2380        pos_type: PosType,
2381    ) -> LoroResult<()> {
2382        match &self.inner {
2383            MaybeDetached::Detached(t) => self.mark_for_detached(
2384                &mut t.lock().value,
2385                key,
2386                &LoroValue::Null,
2387                start,
2388                end,
2389                pos_type,
2390            ),
2391            MaybeDetached::Attached(a) => a.with_txn(|txn| {
2392                self.mark_with_txn(txn, start, end, key, LoroValue::Null, pos_type)
2393            }),
2394        }
2395    }
2396
2397    /// `start` and `end` are interpreted using `pos_type`.
2398    pub fn mark_with_txn(
2399        &self,
2400        txn: &mut Transaction,
2401        start: usize,
2402        end: usize,
2403        key: impl Into<InternalString>,
2404        value: LoroValue,
2405        pos_type: PosType,
2406    ) -> LoroResult<()> {
2407        if start >= end {
2408            return Err(loro_common::LoroError::ArgErr(
2409                "Start must be less than end".to_string().into_boxed_str(),
2410            ));
2411        }
2412        ensure_no_regular_container_value(&value)?;
2413
2414        let inner = self.inner.try_attached_state()?;
2415        let key: InternalString = key.into();
2416        let is_delete = matches!(&value, &LoroValue::Null);
2417
2418        let mut doc_state = inner.doc.state.lock();
2419        let len = doc_state.with_state_mut(inner.container_idx, |state| {
2420            state.as_richtext_state_mut().unwrap().len(pos_type)
2421        });
2422
2423        if end > len {
2424            return Err(LoroError::OutOfBound {
2425                pos: end,
2426                len,
2427                info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
2428            });
2429        }
2430
2431        let (entity_range, skip, missing_style_key, event_start, event_end) = doc_state
2432            .with_state_mut(inner.container_idx, |state| {
2433                let state = state.as_richtext_state_mut().unwrap();
2434                let event_start = state.index_to_event_index(start, pos_type);
2435                let event_end = state.index_to_event_index(end, pos_type);
2436                let (entity_range, styles) =
2437                    state.get_entity_range_and_styles_at_range(start..end, pos_type);
2438
2439                let skip = styles
2440                    .as_ref()
2441                    .map(|styles| styles.has_key_value(&key, &value))
2442                    .unwrap_or(false);
2443                let has_target_style = state.has_style_key_in_entity_range(
2444                    entity_range.clone(),
2445                    &StyleKey::Key(key.clone()),
2446                );
2447                let missing_style_key = is_delete && !has_target_style;
2448
2449                (
2450                    entity_range,
2451                    skip,
2452                    missing_style_key,
2453                    event_start,
2454                    event_end,
2455                )
2456            });
2457
2458        if skip || missing_style_key {
2459            return Ok(());
2460        }
2461
2462        let entity_start = entity_range.start;
2463        let entity_end = entity_range.end;
2464        let style_config = doc_state.config.text_style_config.read();
2465        let flag = if is_delete {
2466            style_config
2467                .get_style_flag_for_unmark(&key)
2468                .ok_or_else(|| LoroError::StyleConfigMissing(key.clone()))?
2469        } else {
2470            style_config
2471                .get_style_flag(&key)
2472                .ok_or_else(|| LoroError::StyleConfigMissing(key.clone()))?
2473        };
2474
2475        drop(style_config);
2476        drop(doc_state);
2477        txn.apply_local_op(
2478            inner.container_idx,
2479            crate::op::RawOpContent::List(ListOp::StyleStart {
2480                start: entity_start as u32,
2481                end: entity_end as u32,
2482                key: key.clone(),
2483                value: value.clone(),
2484                info: flag,
2485            }),
2486            EventHint::Mark {
2487                start: event_start as u32,
2488                end: event_end as u32,
2489                style: crate::container::richtext::Style { key, data: value },
2490            },
2491            &inner.doc,
2492        )?;
2493
2494        txn.apply_local_op(
2495            inner.container_idx,
2496            crate::op::RawOpContent::List(ListOp::StyleEnd),
2497            EventHint::MarkEnd,
2498            &inner.doc,
2499        )?;
2500
2501        Ok(())
2502    }
2503
2504    pub fn check(&self) {
2505        match &self.inner {
2506            MaybeDetached::Detached(t) => {
2507                let t = t.lock();
2508                t.value.check_consistency_between_content_and_style_ranges();
2509            }
2510            MaybeDetached::Attached(a) => a.with_state(|state| {
2511                state
2512                    .as_richtext_state_mut()
2513                    .unwrap()
2514                    .check_consistency_between_content_and_style_ranges();
2515            }),
2516        }
2517    }
2518
2519    pub fn apply_delta(&self, delta: &[TextDelta]) -> LoroResult<()> {
2520        match &self.inner {
2521            MaybeDetached::Detached(t) => {
2522                let _t = t.lock();
2523                // TODO: implement
2524                Err(LoroError::NotImplemented(
2525                    "`apply_delta` on a detached text container",
2526                ))
2527            }
2528            MaybeDetached::Attached(a) => a.with_txn(|txn| self.apply_delta_with_txn(txn, delta)),
2529        }
2530    }
2531
2532    pub fn apply_delta_with_txn(
2533        &self,
2534        txn: &mut Transaction,
2535        delta: &[TextDelta],
2536    ) -> LoroResult<()> {
2537        let mut index = 0;
2538        struct PendingMark {
2539            start: usize,
2540            end: usize,
2541            attributes: FxHashMap<InternalString, LoroValue>,
2542        }
2543        let mut marks: Vec<PendingMark> = Vec::new();
2544        for d in delta {
2545            match d {
2546                TextDelta::Insert { insert, attributes } => {
2547                    let insert_len = event_len(insert.as_str());
2548                    if insert_len == 0 {
2549                        continue;
2550                    }
2551
2552                    let mut empty_attr = None;
2553                    let attr_ref = attributes.as_ref().unwrap_or_else(|| {
2554                        empty_attr = Some(FxHashMap::default());
2555                        empty_attr.as_ref().unwrap()
2556                    });
2557
2558                    let end = checked_delta_index_end(index, insert_len, self.len_event())?;
2559                    let override_styles = self.insert_with_txn_and_attr(
2560                        txn,
2561                        index,
2562                        insert.as_str(),
2563                        Some(attr_ref),
2564                        PosType::Event,
2565                    )?;
2566
2567                    let mut pending_mark = PendingMark {
2568                        start: index,
2569                        end,
2570                        attributes: FxHashMap::default(),
2571                    };
2572                    for (key, value) in override_styles {
2573                        pending_mark.attributes.insert(key, value);
2574                    }
2575                    marks.push(pending_mark);
2576                    index = end;
2577                }
2578                TextDelta::Delete { delete } => {
2579                    self.delete_with_txn(txn, index, *delete, PosType::Event)?;
2580                }
2581                TextDelta::Retain { attributes, retain } => {
2582                    let end = checked_delta_index_end(index, *retain, self.len_event())?;
2583                    match attributes {
2584                        Some(attr) if !attr.is_empty() => {
2585                            let mut pending_mark = PendingMark {
2586                                start: index,
2587                                end,
2588                                attributes: FxHashMap::default(),
2589                            };
2590                            for (key, value) in attr {
2591                                pending_mark
2592                                    .attributes
2593                                    .insert(key.deref().into(), value.clone());
2594                            }
2595                            marks.push(pending_mark);
2596                        }
2597                        _ => {}
2598                    }
2599                    index = end;
2600                }
2601            }
2602        }
2603
2604        let mut len = match &self.inner {
2605            MaybeDetached::Detached(_) => self.len_event(),
2606            MaybeDetached::Attached(a) => {
2607                a.with_state(|state| state.as_richtext_state_mut().unwrap().len(PosType::Event))
2608            }
2609        };
2610        for pending_mark in marks {
2611            if pending_mark.start >= len {
2612                self.insert_with_txn(
2613                    txn,
2614                    len,
2615                    &"\n".repeat(pending_mark.start - len + 1),
2616                    PosType::Event,
2617                )?;
2618                len = pending_mark.start;
2619            }
2620
2621            for (key, value) in pending_mark.attributes {
2622                self.mark_with_txn(
2623                    txn,
2624                    pending_mark.start,
2625                    pending_mark.end,
2626                    key.deref(),
2627                    value,
2628                    PosType::Event,
2629                )?;
2630            }
2631        }
2632
2633        Ok(())
2634    }
2635
2636    pub fn update(&self, text: &str, options: UpdateOptions) -> Result<(), UpdateTimeoutError> {
2637        let old_str = self.to_string();
2638        let new = text.chars().map(|x| x as u32).collect::<Vec<u32>>();
2639        let old = old_str.chars().map(|x| x as u32).collect::<Vec<u32>>();
2640        diff(
2641            &mut OperateProxy::new(text_update::DiffHook::new(self, &new)),
2642            options,
2643            &old,
2644            &new,
2645        )?;
2646        Ok(())
2647    }
2648
2649    pub fn update_by_line(
2650        &self,
2651        text: &str,
2652        options: UpdateOptions,
2653    ) -> Result<(), UpdateTimeoutError> {
2654        let hook = text_update::DiffHookForLine::new(self, text);
2655        let old_lines = hook.get_old_arr().to_vec();
2656        let new_lines = hook.get_new_arr().to_vec();
2657        diff(
2658            &mut OperateProxy::new(hook),
2659            options,
2660            &old_lines,
2661            &new_lines,
2662        )
2663    }
2664
2665    #[allow(clippy::inherent_to_string)]
2666    pub fn to_string(&self) -> String {
2667        match &self.inner {
2668            MaybeDetached::Detached(t) => t.lock().value.to_string(),
2669            MaybeDetached::Attached(a) => a.get_value().into_string().unwrap().unwrap(),
2670        }
2671    }
2672
2673    pub fn get_cursor(&self, event_index: usize, side: Side) -> Option<Cursor> {
2674        self.get_cursor_internal(event_index, side, true)
2675    }
2676
2677    /// Get the stable position representation for the target pos
2678    pub(crate) fn get_cursor_internal(
2679        &self,
2680        index: usize,
2681        side: Side,
2682        get_by_event_index: bool,
2683    ) -> Option<Cursor> {
2684        match &self.inner {
2685            MaybeDetached::Detached(_) => None,
2686            MaybeDetached::Attached(a) => {
2687                let (id, len, origin_pos) = a.with_state(|s| {
2688                    let s = s.as_richtext_state_mut().unwrap();
2689                    (
2690                        s.get_stable_position(index, get_by_event_index),
2691                        if get_by_event_index {
2692                            s.len_event()
2693                        } else {
2694                            s.len_unicode()
2695                        },
2696                        if get_by_event_index {
2697                            s.event_index_to_unicode_index(index)
2698                        } else {
2699                            index
2700                        },
2701                    )
2702                });
2703
2704                if len == 0 {
2705                    return Some(Cursor {
2706                        id: None,
2707                        container: self.id(),
2708                        side: if side == Side::Middle {
2709                            Side::Left
2710                        } else {
2711                            side
2712                        },
2713                        origin_pos: 0,
2714                    });
2715                }
2716
2717                if len <= index {
2718                    return Some(Cursor {
2719                        id: None,
2720                        container: self.id(),
2721                        side: Side::Right,
2722                        origin_pos: len,
2723                    });
2724                }
2725
2726                let id = id?;
2727                Some(Cursor {
2728                    id: Some(id),
2729                    container: self.id(),
2730                    side,
2731                    origin_pos,
2732                })
2733            }
2734        }
2735    }
2736
2737    pub(crate) fn convert_entity_index_to_event_index(&self, entity_index: usize) -> usize {
2738        match &self.inner {
2739            MaybeDetached::Detached(s) => s.lock().value.entity_index_to_event_index(entity_index),
2740            MaybeDetached::Attached(a) => {
2741                let mut pos = 0;
2742                a.with_state(|s| {
2743                    let s = s.as_richtext_state_mut().unwrap();
2744                    pos = s.entity_index_to_event_index(entity_index);
2745                });
2746                pos
2747            }
2748        }
2749    }
2750
2751    pub fn get_delta(&self) -> Vec<TextDelta> {
2752        match &self.inner {
2753            MaybeDetached::Detached(s) => {
2754                let mut delta = Vec::new();
2755                for span in s.lock().value.iter() {
2756                    if span.text.as_str().is_empty() {
2757                        continue;
2758                    }
2759
2760                    let next_attr = span.attributes.to_option_map();
2761                    match delta.last_mut() {
2762                        Some(TextDelta::Insert { insert, attributes })
2763                            if &next_attr == attributes =>
2764                        {
2765                            insert.push_str(span.text.as_str());
2766                            continue;
2767                        }
2768                        _ => {}
2769                    }
2770
2771                    delta.push(TextDelta::Insert {
2772                        insert: span.text.as_str().to_string(),
2773                        attributes: next_attr,
2774                    })
2775                }
2776                delta
2777            }
2778            MaybeDetached::Attached(_a) => self
2779                .with_state(|state| {
2780                    let state = state.as_richtext_state_mut().unwrap();
2781                    Ok(state.get_delta())
2782                })
2783                .unwrap(),
2784        }
2785    }
2786
2787    pub fn is_deleted(&self) -> bool {
2788        match &self.inner {
2789            MaybeDetached::Detached(_) => false,
2790            MaybeDetached::Attached(a) => a.is_deleted(),
2791        }
2792    }
2793
2794    pub fn push_str(&self, s: &str) -> LoroResult<()> {
2795        self.insert_utf8(self.len_utf8(), s)
2796    }
2797
2798    pub fn clear(&self) -> LoroResult<()> {
2799        match &self.inner {
2800            MaybeDetached::Detached(mutex) => {
2801                let mut t = mutex.lock();
2802                let len = t.value.len_unicode();
2803                let ranges = t.value.get_text_entity_ranges(0, len, PosType::Unicode)?;
2804                for range in ranges.iter().rev() {
2805                    t.value
2806                        .drain_by_entity_index(range.entity_start, range.entity_len(), None);
2807                }
2808                Ok(())
2809            }
2810            MaybeDetached::Attached(a) => a.with_txn(|txn| {
2811                let len = a.with_state(|s| s.as_richtext_state_mut().unwrap().len_unicode());
2812                self.delete_with_txn_inline(txn, 0, len, PosType::Unicode)
2813            }),
2814        }
2815    }
2816
2817    /// Convert a position `index` from one coordinate system to another.
2818    ///
2819    /// Supported `PosType` conversions: `Event`, `Unicode`, `Utf16`, and `Bytes`.
2820    /// Returns `None` if the index is out of bounds or the conversion is unsupported.
2821    pub fn convert_pos(&self, index: usize, from: PosType, to: PosType) -> Option<usize> {
2822        if from == to {
2823            return Some(index);
2824        }
2825
2826        if matches!(from, PosType::Entity) || matches!(to, PosType::Entity) {
2827            return None;
2828        }
2829
2830        // Normalize to event + unicode indices for the given position.
2831        let (event_index, unicode_index) = match &self.inner {
2832            MaybeDetached::Detached(t) => {
2833                let t = t.lock();
2834                if index > t.value.len(from) {
2835                    return None;
2836                }
2837                let event_index = if from == PosType::Event {
2838                    index
2839                } else {
2840                    t.value.index_to_event_index(index, from)
2841                };
2842                let unicode_index = if from == PosType::Unicode {
2843                    index
2844                } else {
2845                    t.value.event_index_to_unicode_index(event_index)
2846                };
2847                (event_index, unicode_index)
2848            }
2849            MaybeDetached::Attached(a) if a.has_decoded_state() => {
2850                let res: Option<(usize, usize)> = a.with_state(|state| {
2851                    let state = state.as_richtext_state_mut().unwrap();
2852                    if index > state.len(from) {
2853                        return None;
2854                    }
2855
2856                    let event_index = if from == PosType::Event {
2857                        index
2858                    } else {
2859                        state.index_to_event_index(index, from)
2860                    };
2861                    let unicode_index = if from == PosType::Unicode {
2862                        index
2863                    } else {
2864                        state.event_index_to_unicode_index(event_index)
2865                    };
2866                    Some((event_index, unicode_index))
2867                });
2868
2869                res?
2870            }
2871            MaybeDetached::Attached(a) => {
2872                let value = a.get_value();
2873                let s = value.as_string().unwrap();
2874                let unicode_index = text_pos_to_unicode(s, index, from)?;
2875                let event_index = unicode_to_text_pos(s, unicode_index, PosType::Event)?;
2876                (event_index, unicode_index)
2877            }
2878        };
2879
2880        let result = match to {
2881            PosType::Unicode => Some(unicode_index),
2882            PosType::Event => Some(event_index),
2883            PosType::Bytes | PosType::Utf16 => {
2884                // Map the event-index position onto the target coordinate via the
2885                // rope's prefix caches. This is O(log n); materializing the prefix
2886                // string would be O(n) and makes repeated edits O(n^2).
2887                match &self.inner {
2888                    MaybeDetached::Detached(t) => {
2889                        let t = t.lock();
2890                        if event_index > t.value.len_event() {
2891                            return None;
2892                        }
2893                        Some(t.value.event_index_to_index(event_index, to))
2894                    }
2895                    MaybeDetached::Attached(a) if a.has_decoded_state() => a.with_state(|state| {
2896                        let state = state.as_richtext_state_mut().unwrap();
2897                        if event_index > state.len_event() {
2898                            return None;
2899                        }
2900                        Some(state.event_index_to_index(event_index, to))
2901                    }),
2902                    MaybeDetached::Attached(a) => {
2903                        let value = a.get_value();
2904                        let s = value.as_string().unwrap();
2905                        unicode_to_text_pos(s, unicode_index, to)
2906                    }
2907                }
2908            }
2909            PosType::Entity => None,
2910        };
2911        result
2912    }
2913}
2914
2915fn event_len(s: &str) -> usize {
2916    if cfg!(feature = "wasm") {
2917        count_utf16_len(s.as_bytes())
2918    } else {
2919        s.chars().count()
2920    }
2921}
2922
2923fn text_len(s: &str, pos_type: PosType) -> Option<usize> {
2924    Some(match pos_type {
2925        PosType::Bytes => s.len(),
2926        PosType::Unicode => s.chars().count(),
2927        PosType::Utf16 => count_utf16_len(s.as_bytes()),
2928        PosType::Event => event_len(s),
2929        PosType::Entity => return None,
2930    })
2931}
2932
2933fn text_pos_to_unicode(s: &str, index: usize, pos_type: PosType) -> Option<usize> {
2934    match pos_type {
2935        PosType::Unicode => (index <= s.chars().count()).then_some(index),
2936        PosType::Bytes => {
2937            if index > s.len() {
2938                None
2939            } else {
2940                Some(
2941                    s.char_indices()
2942                        .take_while(|(pos, c)| *pos + c.len_utf8() <= index)
2943                        .count(),
2944                )
2945            }
2946        }
2947        PosType::Utf16 => utf16_to_unicode_pos(s, index),
2948        PosType::Event if cfg!(feature = "wasm") => utf16_to_unicode_pos(s, index),
2949        PosType::Event => (index <= s.chars().count()).then_some(index),
2950        PosType::Entity => None,
2951    }
2952}
2953
2954fn unicode_to_text_pos(s: &str, index: usize, pos_type: PosType) -> Option<usize> {
2955    match pos_type {
2956        PosType::Unicode => (index <= s.chars().count()).then_some(index),
2957        PosType::Bytes => unicode_to_byte_pos(s, index),
2958        PosType::Utf16 => unicode_to_utf16_pos(s, index),
2959        PosType::Event if cfg!(feature = "wasm") => unicode_to_utf16_pos(s, index),
2960        PosType::Event => (index <= s.chars().count()).then_some(index),
2961        PosType::Entity => None,
2962    }
2963}
2964
2965fn unicode_to_byte_pos(s: &str, index: usize) -> Option<usize> {
2966    if index == 0 {
2967        return Some(0);
2968    }
2969
2970    let mut unicode_pos = 0;
2971    for (byte_pos, _) in s.char_indices() {
2972        if unicode_pos == index {
2973            return Some(byte_pos);
2974        }
2975        unicode_pos += 1;
2976    }
2977
2978    (unicode_pos == index).then_some(s.len())
2979}
2980
2981fn unicode_to_utf16_pos(s: &str, index: usize) -> Option<usize> {
2982    let mut unicode_pos = 0;
2983    let mut utf16_pos = 0;
2984    if index == 0 {
2985        return Some(0);
2986    }
2987
2988    for c in s.chars() {
2989        unicode_pos += 1;
2990        utf16_pos += c.len_utf16();
2991        if unicode_pos == index {
2992            return Some(utf16_pos);
2993        }
2994    }
2995
2996    (unicode_pos == index).then_some(utf16_pos)
2997}
2998
2999fn utf16_to_unicode_pos(s: &str, index: usize) -> Option<usize> {
3000    let mut unicode_pos = 0;
3001    let mut utf16_pos = 0;
3002    if index == 0 {
3003        return Some(0);
3004    }
3005
3006    for c in s.chars() {
3007        let next_utf16_pos = utf16_pos + c.len_utf16();
3008        if index < next_utf16_pos {
3009            return Some(unicode_pos);
3010        }
3011        if index == next_utf16_pos {
3012            return Some(unicode_pos + 1);
3013        }
3014        utf16_pos = next_utf16_pos;
3015        unicode_pos += 1;
3016    }
3017
3018    (index == utf16_pos).then_some(unicode_pos)
3019}
3020
3021fn text_boundary_error(pos: usize, pos_type: PosType) -> LoroError {
3022    match pos_type {
3023        PosType::Bytes => LoroError::UTF8InUnicodeCodePoint { pos },
3024        PosType::Utf16 => LoroError::UTF16InUnicodeCodePoint { pos },
3025        PosType::Event if cfg!(feature = "wasm") => LoroError::UTF16InUnicodeCodePoint { pos },
3026        _ => LoroError::OutOfBound {
3027            pos,
3028            len: 0,
3029            info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3030        },
3031    }
3032}
3033
3034fn text_char_at(s: &str, pos: usize, pos_type: PosType) -> LoroResult<char> {
3035    let len = text_len(s, pos_type).unwrap_or(0);
3036    if pos >= len {
3037        return Err(LoroError::OutOfBound {
3038            pos,
3039            len,
3040            info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3041        });
3042    }
3043
3044    let unicode_pos =
3045        text_pos_to_unicode(s, pos, pos_type).ok_or_else(|| text_boundary_error(pos, pos_type))?;
3046    s.chars().nth(unicode_pos).ok_or(LoroError::OutOfBound {
3047        pos,
3048        len,
3049        info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3050    })
3051}
3052
3053fn text_slice(s: &str, start: usize, end: usize, pos_type: PosType) -> LoroResult<String> {
3054    if end < start {
3055        return Err(LoroError::EndIndexLessThanStartIndex { start, end });
3056    }
3057    if start == end {
3058        return Ok(String::new());
3059    }
3060
3061    let len = text_len(s, pos_type).unwrap_or(0);
3062    if end > len {
3063        return Err(LoroError::OutOfBound {
3064            pos: end,
3065            len,
3066            info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3067        });
3068    }
3069
3070    let start = text_pos_to_unicode(s, start, pos_type)
3071        .ok_or_else(|| text_boundary_error(start, pos_type))?;
3072    let end =
3073        text_pos_to_unicode(s, end, pos_type).ok_or_else(|| text_boundary_error(end, pos_type))?;
3074    let start = unicode_to_byte_pos(s, start).expect("unicode index must map to a byte boundary");
3075    let end = unicode_to_byte_pos(s, end).expect("unicode index must map to a byte boundary");
3076    Ok(s[start..end].to_string())
3077}
3078
3079impl ListHandler {
3080    /// Create a new container that is detached from the document.
3081    /// The edits on a detached container will not be persisted.
3082    /// To attach the container to the document, please insert it into an attached container.
3083    pub fn new_detached() -> Self {
3084        Self {
3085            inner: MaybeDetached::new_detached(Vec::new()),
3086        }
3087    }
3088
3089    pub fn insert(&self, pos: usize, v: impl Into<LoroValue>) -> LoroResult<()> {
3090        match &self.inner {
3091            MaybeDetached::Detached(l) => {
3092                let mut list = l.lock();
3093                let len = list.value.len();
3094                if pos > len {
3095                    return Err(LoroError::OutOfBound {
3096                        pos,
3097                        info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3098                        len,
3099                    });
3100                }
3101                let value = v.into();
3102                ensure_no_regular_container_value(&value)?;
3103                list.value.insert(pos, ValueOrHandler::Value(value));
3104                Ok(())
3105            }
3106            MaybeDetached::Attached(a) => {
3107                a.with_txn(|txn| self.insert_with_txn(txn, pos, v.into()))
3108            }
3109        }
3110    }
3111
3112    pub fn insert_with_txn(
3113        &self,
3114        txn: &mut Transaction,
3115        pos: usize,
3116        v: LoroValue,
3117    ) -> LoroResult<()> {
3118        if pos > self.len() {
3119            return Err(LoroError::OutOfBound {
3120                pos,
3121                info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3122                len: self.len(),
3123            });
3124        }
3125
3126        let inner = self.inner.try_attached_state()?;
3127        ensure_no_regular_container_value(&v)?;
3128
3129        txn.apply_local_op(
3130            inner.container_idx,
3131            crate::op::RawOpContent::List(crate::container::list::list_op::ListOp::Insert {
3132                slice: ListSlice::RawData(Cow::Owned(vec![v.clone()])),
3133                pos,
3134            }),
3135            EventHint::InsertList { len: 1, pos },
3136            &inner.doc,
3137        )
3138    }
3139
3140    pub fn push(&self, v: impl Into<LoroValue>) -> LoroResult<()> {
3141        match &self.inner {
3142            MaybeDetached::Detached(l) => {
3143                let mut list = l.lock();
3144                let value = v.into();
3145                ensure_no_regular_container_value(&value)?;
3146                list.value.push(ValueOrHandler::Value(value));
3147                Ok(())
3148            }
3149            MaybeDetached::Attached(a) => a.with_txn(|txn| self.push_with_txn(txn, v.into())),
3150        }
3151    }
3152
3153    pub fn push_with_txn(&self, txn: &mut Transaction, v: LoroValue) -> LoroResult<()> {
3154        let pos = self.len();
3155        self.insert_with_txn(txn, pos, v)
3156    }
3157
3158    pub fn pop(&self) -> LoroResult<Option<LoroValue>> {
3159        match &self.inner {
3160            MaybeDetached::Detached(l) => {
3161                let mut list = l.lock();
3162                Ok(list.value.pop().map(|v| v.to_value()))
3163            }
3164            MaybeDetached::Attached(a) => a.with_txn(|txn| self.pop_with_txn(txn)),
3165        }
3166    }
3167
3168    pub fn pop_with_txn(&self, txn: &mut Transaction) -> LoroResult<Option<LoroValue>> {
3169        let len = self.len();
3170        if len == 0 {
3171            return Ok(None);
3172        }
3173
3174        let v = self.get(len - 1);
3175        self.delete_with_txn(txn, len - 1, 1)?;
3176        Ok(v)
3177    }
3178
3179    pub fn insert_container<H: HandlerTrait>(&self, pos: usize, child: H) -> LoroResult<H> {
3180        match &self.inner {
3181            MaybeDetached::Detached(l) => {
3182                let mut list = l.lock();
3183                if pos > list.value.len() {
3184                    return Err(LoroError::OutOfBound {
3185                        pos,
3186                        info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3187                        len: list.value.len(),
3188                    });
3189                }
3190                list.value
3191                    .insert(pos, ValueOrHandler::Handler(child.to_handler()));
3192                Ok(child)
3193            }
3194            MaybeDetached::Attached(a) => {
3195                a.with_txn(|txn| self.insert_container_with_txn(txn, pos, child))
3196            }
3197        }
3198    }
3199
3200    pub fn push_container<H: HandlerTrait>(&self, child: H) -> LoroResult<H> {
3201        self.insert_container(self.len(), child)
3202    }
3203
3204    pub fn insert_container_with_txn<H: HandlerTrait>(
3205        &self,
3206        txn: &mut Transaction,
3207        pos: usize,
3208        child: H,
3209    ) -> LoroResult<H> {
3210        if pos > self.len() {
3211            return Err(LoroError::OutOfBound {
3212                pos,
3213                info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3214                len: self.len(),
3215            });
3216        }
3217
3218        let inner = self.inner.try_attached_state()?;
3219        let id = txn.next_id();
3220        let container_id = ContainerID::new_normal(id, child.kind());
3221        let v = LoroValue::Container(container_id.clone());
3222        txn.apply_local_op(
3223            inner.container_idx,
3224            crate::op::RawOpContent::List(crate::container::list::list_op::ListOp::Insert {
3225                slice: ListSlice::RawData(Cow::Owned(vec![v.clone()])),
3226                pos,
3227            }),
3228            EventHint::InsertList { len: 1, pos },
3229            &inner.doc,
3230        )?;
3231        let ans = child.attach(txn, inner, container_id)?;
3232        Ok(ans)
3233    }
3234
3235    pub fn delete(&self, pos: usize, len: usize) -> LoroResult<()> {
3236        match &self.inner {
3237            MaybeDetached::Detached(l) => {
3238                let mut list = l.lock();
3239                let end = checked_range_end(pos, len, list.value.len(), || {
3240                    format!("Position: {}:{}", file!(), line!()).into_boxed_str()
3241                })?;
3242                list.value.drain(pos..end);
3243                Ok(())
3244            }
3245            MaybeDetached::Attached(a) => a.with_txn(|txn| self.delete_with_txn(txn, pos, len)),
3246        }
3247    }
3248
3249    pub fn delete_with_txn(&self, txn: &mut Transaction, pos: usize, len: usize) -> LoroResult<()> {
3250        if len == 0 {
3251            return Ok(());
3252        }
3253
3254        let list_len = self.len();
3255        let end = checked_range_end(pos, len, list_len, || {
3256            format!("Position: {}:{}", file!(), line!()).into_boxed_str()
3257        })?;
3258
3259        let inner = self.inner.try_attached_state()?;
3260        let ids: Vec<_> = inner.with_state(|state| {
3261            let list = state.as_list_state().unwrap();
3262            (pos..end).map(|i| list.get_id_at(i).unwrap()).collect()
3263        });
3264
3265        for id in ids.into_iter() {
3266            txn.apply_local_op(
3267                inner.container_idx,
3268                crate::op::RawOpContent::List(ListOp::Delete(DeleteSpanWithId::new(
3269                    id.id(),
3270                    pos as isize,
3271                    1,
3272                ))),
3273                EventHint::DeleteList(DeleteSpan::new(pos as isize, 1)),
3274                &inner.doc,
3275            )?;
3276        }
3277
3278        Ok(())
3279    }
3280
3281    pub fn get_child_handler(&self, index: usize) -> LoroResult<Handler> {
3282        match &self.inner {
3283            MaybeDetached::Detached(l) => {
3284                let list = l.lock();
3285                let value = list.value.get(index).ok_or(LoroError::OutOfBound {
3286                    pos: index,
3287                    info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3288                    len: list.value.len(),
3289                })?;
3290                match value {
3291                    ValueOrHandler::Handler(h) => Ok(h.clone()),
3292                    _ => Err(LoroError::ArgErr(
3293                        format!(
3294                            "Expected container at index {}, but found {:?}",
3295                            index, value
3296                        )
3297                        .into_boxed_str(),
3298                    )),
3299                }
3300            }
3301            MaybeDetached::Attached(_) => {
3302                let Some(value) = self.get_(index) else {
3303                    return Err(LoroError::OutOfBound {
3304                        pos: index,
3305                        info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3306                        len: self.len(),
3307                    });
3308                };
3309                match value {
3310                    ValueOrHandler::Handler(handler) => Ok(handler),
3311                    ValueOrHandler::Value(value) => Err(LoroError::ArgErr(
3312                        format!(
3313                            "Expected container at index {}, but found {:?}",
3314                            index, value
3315                        )
3316                        .into_boxed_str(),
3317                    )),
3318                }
3319            }
3320        }
3321    }
3322
3323    pub fn len(&self) -> usize {
3324        match &self.inner {
3325            MaybeDetached::Detached(l) => l.lock().value.len(),
3326            MaybeDetached::Attached(a) => {
3327                a.with_doc_state(|state| state.get_list_len(a.container_idx))
3328            }
3329        }
3330    }
3331
3332    pub fn is_empty(&self) -> bool {
3333        self.len() == 0
3334    }
3335
3336    pub fn get_deep_value_with_id(&self) -> LoroResult<LoroValue> {
3337        let inner = self.inner.try_attached_state()?;
3338        Ok(inner.with_doc_state(|state| {
3339            state.get_container_deep_value_with_id(inner.container_idx, None)
3340        }))
3341    }
3342
3343    pub fn get(&self, index: usize) -> Option<LoroValue> {
3344        match &self.inner {
3345            MaybeDetached::Detached(l) => l.lock().value.get(index).map(|x| x.to_value()),
3346            MaybeDetached::Attached(a) => {
3347                a.with_doc_state(|state| state.get_list_value_at(a.container_idx, index))
3348            }
3349        }
3350    }
3351
3352    /// Get value at given index, if it's a container, return a handler to the container
3353    pub fn get_(&self, index: usize) -> Option<ValueOrHandler> {
3354        match &self.inner {
3355            MaybeDetached::Detached(l) => {
3356                let l = l.lock();
3357                l.value.get(index).cloned()
3358            }
3359            MaybeDetached::Attached(inner) => {
3360                let value = inner
3361                    .with_doc_state(|state| state.get_list_value_at(inner.container_idx, index));
3362                value.map(|value| value_to_value_or_handler(inner, value))
3363            }
3364        }
3365    }
3366
3367    pub fn for_each<I>(&self, mut f: I)
3368    where
3369        I: FnMut(ValueOrHandler),
3370    {
3371        match &self.inner {
3372            MaybeDetached::Detached(l) => {
3373                let l = l.lock();
3374                for v in l.value.iter() {
3375                    f(v.clone())
3376                }
3377            }
3378            MaybeDetached::Attached(inner) => {
3379                let temp = inner.with_doc_state(|state| {
3380                    state
3381                        .get_list_values(inner.container_idx)
3382                        .into_iter()
3383                        .map(|value| value_to_value_or_handler(inner, value))
3384                        .collect::<Vec<_>>()
3385                });
3386                for v in temp.into_iter() {
3387                    f(v);
3388                }
3389            }
3390        }
3391    }
3392
3393    pub fn get_cursor(&self, pos: usize, side: Side) -> Option<Cursor> {
3394        match &self.inner {
3395            MaybeDetached::Detached(_) => None,
3396            MaybeDetached::Attached(a) => {
3397                let (id, len) = a.with_state(|s| {
3398                    let l = s.as_list_state().unwrap();
3399                    (l.get_id_at(pos), l.len())
3400                });
3401
3402                if len == 0 {
3403                    return Some(Cursor {
3404                        id: None,
3405                        container: self.id(),
3406                        side: if side == Side::Middle {
3407                            Side::Left
3408                        } else {
3409                            side
3410                        },
3411                        origin_pos: 0,
3412                    });
3413                }
3414
3415                if len <= pos {
3416                    return Some(Cursor {
3417                        id: None,
3418                        container: self.id(),
3419                        side: Side::Right,
3420                        origin_pos: len,
3421                    });
3422                }
3423
3424                let id = id?;
3425                Some(Cursor {
3426                    id: Some(id.id()),
3427                    container: self.id(),
3428                    side,
3429                    origin_pos: pos,
3430                })
3431            }
3432        }
3433    }
3434
3435    fn apply_delta(
3436        &self,
3437        delta: loro_delta::DeltaRope<
3438            loro_delta::array_vec::ArrayVec<ValueOrHandler, 8>,
3439            crate::event::ListDeltaMeta,
3440        >,
3441        on_container_remap: &mut dyn FnMut(ContainerID, ContainerID),
3442    ) -> LoroResult<()> {
3443        match &self.inner {
3444            MaybeDetached::Detached(_) => unimplemented!(),
3445            MaybeDetached::Attached(_) => {
3446                let mut index = 0;
3447                for item in delta.iter() {
3448                    match item {
3449                        loro_delta::DeltaItem::Retain { len, .. } => {
3450                            index += len;
3451                        }
3452                        loro_delta::DeltaItem::Replace { value, delete, .. } => {
3453                            if *delete > 0 {
3454                                self.delete(index, *delete)?;
3455                            }
3456
3457                            for v in value.iter() {
3458                                match v {
3459                                    ValueOrHandler::Value(LoroValue::Container(old_id)) => {
3460                                        let new_h = self.insert_container(
3461                                            index,
3462                                            Handler::new_unattached(old_id.container_type()),
3463                                        )?;
3464                                        let new_id = new_h.id();
3465                                        on_container_remap(old_id.clone(), new_id);
3466                                    }
3467                                    ValueOrHandler::Handler(h) => {
3468                                        let old_id = h.id();
3469                                        let new_h = self.insert_container(
3470                                            index,
3471                                            Handler::new_unattached(old_id.container_type()),
3472                                        )?;
3473                                        let new_id = new_h.id();
3474                                        on_container_remap(old_id, new_id);
3475                                    }
3476                                    ValueOrHandler::Value(v) => {
3477                                        self.insert(index, v.clone())?;
3478                                    }
3479                                }
3480
3481                                index += 1;
3482                            }
3483                        }
3484                    }
3485                }
3486            }
3487        }
3488
3489        Ok(())
3490    }
3491
3492    pub fn is_deleted(&self) -> bool {
3493        match &self.inner {
3494            MaybeDetached::Detached(_) => false,
3495            MaybeDetached::Attached(a) => a.is_deleted(),
3496        }
3497    }
3498
3499    pub fn clear(&self) -> LoroResult<()> {
3500        match &self.inner {
3501            MaybeDetached::Detached(l) => {
3502                let mut l = l.lock();
3503                l.value.clear();
3504                Ok(())
3505            }
3506            MaybeDetached::Attached(a) => a.with_txn(|txn| self.clear_with_txn(txn)),
3507        }
3508    }
3509
3510    pub fn clear_with_txn(&self, txn: &mut Transaction) -> LoroResult<()> {
3511        self.delete_with_txn(txn, 0, self.len())
3512    }
3513
3514    pub fn get_id_at(&self, pos: usize) -> Option<ID> {
3515        match &self.inner {
3516            MaybeDetached::Detached(_) => None,
3517            MaybeDetached::Attached(a) => a.with_state(|state| {
3518                state
3519                    .as_list_state()
3520                    .unwrap()
3521                    .get_id_at(pos)
3522                    .map(|x| x.id())
3523            }),
3524        }
3525    }
3526}
3527
3528impl MovableListHandler {
3529    pub fn insert(&self, pos: usize, v: impl Into<LoroValue>) -> LoroResult<()> {
3530        match &self.inner {
3531            MaybeDetached::Detached(d) => {
3532                let mut d = d.lock();
3533                if pos > d.value.len() {
3534                    return Err(LoroError::OutOfBound {
3535                        pos,
3536                        info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3537                        len: d.value.len(),
3538                    });
3539                }
3540                let value = v.into();
3541                ensure_no_regular_container_value(&value)?;
3542                d.value.insert(pos, ValueOrHandler::Value(value));
3543                Ok(())
3544            }
3545            MaybeDetached::Attached(a) => {
3546                a.with_txn(|txn| self.insert_with_txn(txn, pos, v.into()))
3547            }
3548        }
3549    }
3550
3551    #[instrument(skip_all)]
3552    pub fn insert_with_txn(
3553        &self,
3554        txn: &mut Transaction,
3555        pos: usize,
3556        v: LoroValue,
3557    ) -> LoroResult<()> {
3558        if pos > self.len() {
3559            return Err(LoroError::OutOfBound {
3560                pos,
3561                info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3562                len: self.len(),
3563            });
3564        }
3565
3566        ensure_no_regular_container_value(&v)?;
3567
3568        let op_index = self.with_state(|state| {
3569            let list = state.as_movable_list_state().unwrap();
3570            Ok(list
3571                .convert_index(pos, IndexType::ForUser, IndexType::ForOp)
3572                .unwrap())
3573        })?;
3574
3575        let inner = self.inner.try_attached_state()?;
3576        txn.apply_local_op(
3577            inner.container_idx,
3578            crate::op::RawOpContent::List(crate::container::list::list_op::ListOp::Insert {
3579                slice: ListSlice::RawData(Cow::Owned(vec![v.clone()])),
3580                pos: op_index,
3581            }),
3582            EventHint::InsertList { len: 1, pos },
3583            &inner.doc,
3584        )
3585    }
3586
3587    #[inline]
3588    pub fn mov(&self, from: usize, to: usize) -> LoroResult<()> {
3589        match &self.inner {
3590            MaybeDetached::Detached(d) => {
3591                let mut d = d.lock();
3592                if from >= d.value.len() {
3593                    return Err(LoroError::OutOfBound {
3594                        pos: from,
3595                        info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3596                        len: d.value.len(),
3597                    });
3598                }
3599                if to >= d.value.len() {
3600                    return Err(LoroError::OutOfBound {
3601                        pos: to,
3602                        info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3603                        len: d.value.len(),
3604                    });
3605                }
3606                let v = d.value.remove(from);
3607                d.value.insert(to, v);
3608                Ok(())
3609            }
3610            MaybeDetached::Attached(a) => a.with_txn(|txn| self.move_with_txn(txn, from, to)),
3611        }
3612    }
3613
3614    /// Move element from `from` to `to`. After this op, elem will be at pos `to`.
3615    #[instrument(skip_all)]
3616    pub fn move_with_txn(&self, txn: &mut Transaction, from: usize, to: usize) -> LoroResult<()> {
3617        if from == to {
3618            return Ok(());
3619        }
3620
3621        if from >= self.len() {
3622            return Err(LoroError::OutOfBound {
3623                pos: from,
3624                info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3625                len: self.len(),
3626            });
3627        }
3628
3629        if to >= self.len() {
3630            return Err(LoroError::OutOfBound {
3631                pos: to,
3632                info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3633                len: self.len(),
3634            });
3635        }
3636
3637        let (op_from, op_to, elem_id, value) = self.with_state(|state| {
3638            let list = state.as_movable_list_state().unwrap();
3639            let (elem_id, elem) = list
3640                .get_elem_at_given_pos(from, IndexType::ForUser)
3641                .unwrap();
3642            Ok((
3643                list.convert_index(from, IndexType::ForUser, IndexType::ForOp)
3644                    .unwrap(),
3645                list.convert_index(to, IndexType::ForUser, IndexType::ForOp)
3646                    .unwrap(),
3647                elem_id,
3648                elem.value().clone(),
3649            ))
3650        })?;
3651
3652        let inner = self.inner.try_attached_state()?;
3653        txn.apply_local_op(
3654            inner.container_idx,
3655            crate::op::RawOpContent::List(crate::container::list::list_op::ListOp::Move {
3656                from: op_from as u32,
3657                to: op_to as u32,
3658                elem_id: elem_id.to_id(),
3659            }),
3660            EventHint::Move {
3661                value,
3662                from: from as u32,
3663                to: to as u32,
3664            },
3665            &inner.doc,
3666        )
3667    }
3668
3669    pub fn push(&self, v: LoroValue) -> LoroResult<()> {
3670        match &self.inner {
3671            MaybeDetached::Detached(d) => {
3672                let mut d = d.lock();
3673                d.value.push(v.into());
3674                Ok(())
3675            }
3676            MaybeDetached::Attached(a) => a.with_txn(|txn| self.push_with_txn(txn, v)),
3677        }
3678    }
3679
3680    pub fn push_with_txn(&self, txn: &mut Transaction, v: LoroValue) -> LoroResult<()> {
3681        let pos = self.len();
3682        self.insert_with_txn(txn, pos, v)
3683    }
3684
3685    pub fn pop_(&self) -> LoroResult<Option<ValueOrHandler>> {
3686        match &self.inner {
3687            MaybeDetached::Detached(d) => {
3688                let mut d = d.lock();
3689                Ok(d.value.pop())
3690            }
3691            MaybeDetached::Attached(a) => {
3692                if self.is_empty() {
3693                    return Ok(None);
3694                }
3695                let last = self.len() - 1;
3696                let ans = self.get_(last);
3697                a.with_txn(|txn| self.pop_with_txn(txn))?;
3698                Ok(ans)
3699            }
3700        }
3701    }
3702
3703    pub fn pop(&self) -> LoroResult<Option<LoroValue>> {
3704        match &self.inner {
3705            MaybeDetached::Detached(a) => {
3706                let mut a = a.lock();
3707                Ok(a.value.pop().map(|x| x.to_value()))
3708            }
3709            MaybeDetached::Attached(a) => a.with_txn(|txn| self.pop_with_txn(txn)),
3710        }
3711    }
3712
3713    pub fn pop_with_txn(&self, txn: &mut Transaction) -> LoroResult<Option<LoroValue>> {
3714        let len = self.len();
3715        if len == 0 {
3716            return Ok(None);
3717        }
3718
3719        let v = self.get(len - 1);
3720        self.delete_with_txn(txn, len - 1, 1)?;
3721        Ok(v)
3722    }
3723
3724    pub fn insert_container<H: HandlerTrait>(&self, pos: usize, child: H) -> LoroResult<H> {
3725        match &self.inner {
3726            MaybeDetached::Detached(d) => {
3727                let mut d = d.lock();
3728                if pos > d.value.len() {
3729                    return Err(LoroError::OutOfBound {
3730                        pos,
3731                        info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3732                        len: d.value.len(),
3733                    });
3734                }
3735                d.value
3736                    .insert(pos, ValueOrHandler::Handler(child.to_handler()));
3737                Ok(child)
3738            }
3739            MaybeDetached::Attached(a) => {
3740                a.with_txn(|txn| self.insert_container_with_txn(txn, pos, child))
3741            }
3742        }
3743    }
3744
3745    pub fn push_container<H: HandlerTrait>(&self, child: H) -> LoroResult<H> {
3746        self.insert_container(self.len(), child)
3747    }
3748
3749    pub fn insert_container_with_txn<H: HandlerTrait>(
3750        &self,
3751        txn: &mut Transaction,
3752        pos: usize,
3753        child: H,
3754    ) -> LoroResult<H> {
3755        if pos > self.len() {
3756            return Err(LoroError::OutOfBound {
3757                pos,
3758                info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3759                len: self.len(),
3760            });
3761        }
3762
3763        let op_index = self.with_state(|state| {
3764            let list = state.as_movable_list_state().unwrap();
3765            Ok(list
3766                .convert_index(pos, IndexType::ForUser, IndexType::ForOp)
3767                .unwrap())
3768        })?;
3769
3770        let id = txn.next_id();
3771        let container_id = ContainerID::new_normal(id, child.kind());
3772        let v = LoroValue::Container(container_id.clone());
3773        let inner = self.inner.try_attached_state()?;
3774        txn.apply_local_op(
3775            inner.container_idx,
3776            crate::op::RawOpContent::List(crate::container::list::list_op::ListOp::Insert {
3777                slice: ListSlice::RawData(Cow::Owned(vec![v.clone()])),
3778                pos: op_index,
3779            }),
3780            EventHint::InsertList { len: 1, pos },
3781            &inner.doc,
3782        )?;
3783        child.attach(txn, inner, container_id)
3784    }
3785
3786    pub fn set(&self, index: usize, value: impl Into<LoroValue>) -> LoroResult<()> {
3787        match &self.inner {
3788            MaybeDetached::Detached(d) => {
3789                let mut d = d.lock();
3790                if index >= d.value.len() {
3791                    return Err(LoroError::OutOfBound {
3792                        pos: index,
3793                        info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3794                        len: d.value.len(),
3795                    });
3796                }
3797                let value = value.into();
3798                ensure_no_regular_container_value(&value)?;
3799                d.value[index] = ValueOrHandler::Value(value);
3800                Ok(())
3801            }
3802            MaybeDetached::Attached(a) => {
3803                a.with_txn(|txn| self.set_with_txn(txn, index, value.into()))
3804            }
3805        }
3806    }
3807
3808    pub fn set_with_txn(
3809        &self,
3810        txn: &mut Transaction,
3811        index: usize,
3812        value: LoroValue,
3813    ) -> LoroResult<()> {
3814        if index >= self.len() {
3815            return Err(LoroError::OutOfBound {
3816                pos: index,
3817                info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3818                len: self.len(),
3819            });
3820        }
3821
3822        let inner = self.inner.try_attached_state()?;
3823        let Some(elem_id) = self.with_state(|state| {
3824            let list = state.as_movable_list_state().unwrap();
3825            Ok(list.get_elem_id_at(index, IndexType::ForUser))
3826        })?
3827        else {
3828            unreachable!()
3829        };
3830        ensure_no_regular_container_value(&value)?;
3831
3832        let op = crate::op::RawOpContent::List(crate::container::list::list_op::ListOp::Set {
3833            elem_id: elem_id.to_id(),
3834            value: value.clone(),
3835        });
3836
3837        let hint = EventHint::SetList { index, value };
3838        txn.apply_local_op(inner.container_idx, op, hint, &inner.doc)
3839    }
3840
3841    pub fn set_container<H: HandlerTrait>(&self, pos: usize, child: H) -> LoroResult<H> {
3842        match &self.inner {
3843            MaybeDetached::Detached(d) => {
3844                let mut d = d.lock();
3845                if pos >= d.value.len() {
3846                    return Err(LoroError::OutOfBound {
3847                        pos,
3848                        info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3849                        len: d.value.len(),
3850                    });
3851                }
3852                d.value[pos] = ValueOrHandler::Handler(child.to_handler());
3853                Ok(child)
3854            }
3855            MaybeDetached::Attached(a) => {
3856                a.with_txn(|txn| self.set_container_with_txn(txn, pos, child))
3857            }
3858        }
3859    }
3860
3861    pub fn set_container_with_txn<H: HandlerTrait>(
3862        &self,
3863        txn: &mut Transaction,
3864        pos: usize,
3865        child: H,
3866    ) -> LoroResult<H> {
3867        let id = txn.next_id();
3868        let container_id = ContainerID::new_normal(id, child.kind());
3869        let v = LoroValue::Container(container_id.clone());
3870        let Some(elem_id) = self.with_state(|state| {
3871            let list = state.as_movable_list_state().unwrap();
3872            Ok(list.get_elem_id_at(pos, IndexType::ForUser))
3873        })?
3874        else {
3875            let len = self.len();
3876            if pos >= len {
3877                return Err(LoroError::OutOfBound {
3878                    pos,
3879                    len,
3880                    info: "".into(),
3881                });
3882            } else {
3883                unreachable!()
3884            }
3885        };
3886        let inner = self.inner.try_attached_state()?;
3887        txn.apply_local_op(
3888            inner.container_idx,
3889            crate::op::RawOpContent::List(crate::container::list::list_op::ListOp::Set {
3890                elem_id: elem_id.to_id(),
3891                value: v.clone(),
3892            }),
3893            EventHint::SetList {
3894                index: pos,
3895                value: v,
3896            },
3897            &inner.doc,
3898        )?;
3899
3900        child.attach(txn, inner, container_id)
3901    }
3902
3903    pub fn delete(&self, pos: usize, len: usize) -> LoroResult<()> {
3904        match &self.inner {
3905            MaybeDetached::Detached(d) => {
3906                let mut d = d.lock();
3907                let end = checked_range_end(pos, len, d.value.len(), || {
3908                    format!("Position: {}:{}", file!(), line!()).into_boxed_str()
3909                })?;
3910                d.value.drain(pos..end);
3911                Ok(())
3912            }
3913            MaybeDetached::Attached(a) => a.with_txn(|txn| self.delete_with_txn(txn, pos, len)),
3914        }
3915    }
3916
3917    #[instrument(skip_all)]
3918    pub fn delete_with_txn(&self, txn: &mut Transaction, pos: usize, len: usize) -> LoroResult<()> {
3919        if len == 0 {
3920            return Ok(());
3921        }
3922
3923        let list_len = self.len();
3924        let end = checked_range_end(pos, len, list_len, || {
3925            format!("Position: {}:{}", file!(), line!()).into_boxed_str()
3926        })?;
3927
3928        let (ids, new_poses) = self.with_state(|state| {
3929            let list = state.as_movable_list_state().unwrap();
3930            let ids: Vec<_> = (pos..end)
3931                .map(|i| list.get_list_id_at(i, IndexType::ForUser).unwrap())
3932                .collect();
3933            let poses: Vec<_> = (pos..end)
3934                // need to -i because we delete the previous ones
3935                .map(|user_index| {
3936                    let op_index = list
3937                        .convert_index(user_index, IndexType::ForUser, IndexType::ForOp)
3938                        .unwrap();
3939                    assert!(op_index >= user_index);
3940                    op_index - (user_index - pos)
3941                })
3942                .collect();
3943            Ok((ids, poses))
3944        })?;
3945
3946        loro_common::info!(?pos, ?len, ?ids, ?new_poses, "delete_with_txn");
3947        let user_pos = pos;
3948        let inner = self.inner.try_attached_state()?;
3949        for (id, op_pos) in ids.into_iter().zip(new_poses.into_iter()) {
3950            txn.apply_local_op(
3951                inner.container_idx,
3952                crate::op::RawOpContent::List(ListOp::Delete(DeleteSpanWithId::new(
3953                    id,
3954                    op_pos as isize,
3955                    1,
3956                ))),
3957                EventHint::DeleteList(DeleteSpan::new(user_pos as isize, 1)),
3958                &inner.doc,
3959            )?;
3960        }
3961
3962        Ok(())
3963    }
3964
3965    pub fn get_child_handler(&self, index: usize) -> LoroResult<Handler> {
3966        match &self.inner {
3967            MaybeDetached::Detached(l) => {
3968                let list = l.lock();
3969                let value = list.value.get(index).ok_or(LoroError::OutOfBound {
3970                    pos: index,
3971                    info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3972                    len: list.value.len(),
3973                })?;
3974                match value {
3975                    ValueOrHandler::Handler(h) => Ok(h.clone()),
3976                    _ => Err(LoroError::ArgErr(
3977                        format!(
3978                            "Expected container at index {}, but found {:?}",
3979                            index, value
3980                        )
3981                        .into_boxed_str(),
3982                    )),
3983                }
3984            }
3985            MaybeDetached::Attached(_) => {
3986                let Some(value) = self.get_(index) else {
3987                    return Err(LoroError::OutOfBound {
3988                        pos: index,
3989                        info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(),
3990                        len: self.len(),
3991                    });
3992                };
3993                match value {
3994                    ValueOrHandler::Handler(handler) => Ok(handler),
3995                    ValueOrHandler::Value(value) => Err(LoroError::ArgErr(
3996                        format!(
3997                            "Expected container at index {}, but found {:?}",
3998                            index, value
3999                        )
4000                        .into_boxed_str(),
4001                    )),
4002                }
4003            }
4004        }
4005    }
4006
4007    pub fn len(&self) -> usize {
4008        match &self.inner {
4009            MaybeDetached::Detached(d) => {
4010                let d = d.lock();
4011                d.value.len()
4012            }
4013            MaybeDetached::Attached(a) => {
4014                a.with_doc_state(|state| state.get_list_len(a.container_idx))
4015            }
4016        }
4017    }
4018
4019    pub fn is_empty(&self) -> bool {
4020        self.len() == 0
4021    }
4022
4023    pub fn get_deep_value_with_id(&self) -> LoroValue {
4024        let inner = self.inner.try_attached_state().unwrap();
4025        inner
4026            .doc
4027            .state
4028            .lock()
4029            .get_container_deep_value_with_id(inner.container_idx, None)
4030    }
4031
4032    pub fn get(&self, index: usize) -> Option<LoroValue> {
4033        match &self.inner {
4034            MaybeDetached::Detached(d) => {
4035                let d = d.lock();
4036                d.value.get(index).map(|v| v.to_value())
4037            }
4038            MaybeDetached::Attached(a) => {
4039                a.with_doc_state(|state| state.get_list_value_at(a.container_idx, index))
4040            }
4041        }
4042    }
4043
4044    /// Get value at given index, if it's a container, return a handler to the container
4045    pub fn get_(&self, index: usize) -> Option<ValueOrHandler> {
4046        match &self.inner {
4047            MaybeDetached::Detached(d) => {
4048                let d = d.lock();
4049                d.value.get(index).cloned()
4050            }
4051            MaybeDetached::Attached(m) => {
4052                let value =
4053                    m.with_doc_state(|state| state.get_list_value_at(m.container_idx, index));
4054                value.map(|value| value_to_value_or_handler(m, value))
4055            }
4056        }
4057    }
4058
4059    pub fn for_each<I>(&self, mut f: I)
4060    where
4061        I: FnMut(ValueOrHandler),
4062    {
4063        match &self.inner {
4064            MaybeDetached::Detached(d) => {
4065                let d = d.lock();
4066                for v in d.value.iter() {
4067                    f(v.clone());
4068                }
4069            }
4070            MaybeDetached::Attached(m) => {
4071                let temp = m.with_doc_state(|state| {
4072                    state
4073                        .get_list_values(m.container_idx)
4074                        .into_iter()
4075                        .map(|value| value_to_value_or_handler(m, value))
4076                        .collect::<Vec<_>>()
4077                });
4078
4079                for v in temp.into_iter() {
4080                    f(v);
4081                }
4082            }
4083        }
4084    }
4085
4086    pub fn log_internal_state(&self) -> String {
4087        match &self.inner {
4088            MaybeDetached::Detached(d) => {
4089                let d = d.lock();
4090                format!("{:#?}", &d.value)
4091            }
4092            MaybeDetached::Attached(a) => a.with_state(|state| {
4093                let a = state.as_movable_list_state().unwrap();
4094                format!("{a:#?}")
4095            }),
4096        }
4097    }
4098
4099    pub fn new_detached() -> MovableListHandler {
4100        MovableListHandler {
4101            inner: MaybeDetached::new_detached(Default::default()),
4102        }
4103    }
4104
4105    pub fn get_cursor(&self, pos: usize, side: Side) -> Option<Cursor> {
4106        match &self.inner {
4107            MaybeDetached::Detached(_) => None,
4108            MaybeDetached::Attached(inner) => {
4109                let (id, len) = inner.with_state(|s| {
4110                    let l = s.as_movable_list_state().unwrap();
4111                    (l.get_list_item_id_at(pos), l.len())
4112                });
4113
4114                if len == 0 {
4115                    return Some(Cursor {
4116                        id: None,
4117                        container: self.id(),
4118                        side: if side == Side::Middle {
4119                            Side::Left
4120                        } else {
4121                            side
4122                        },
4123                        origin_pos: 0,
4124                    });
4125                }
4126
4127                if len <= pos {
4128                    return Some(Cursor {
4129                        id: None,
4130                        container: self.id(),
4131                        side: Side::Right,
4132                        origin_pos: len,
4133                    });
4134                }
4135
4136                let id = id?;
4137                Some(Cursor {
4138                    id: Some(id.id()),
4139                    container: self.id(),
4140                    side,
4141                    origin_pos: pos,
4142                })
4143            }
4144        }
4145    }
4146
4147    pub(crate) fn op_pos_to_user_pos(&self, new_pos: usize) -> usize {
4148        match &self.inner {
4149            MaybeDetached::Detached(_) => new_pos,
4150            MaybeDetached::Attached(inner) => {
4151                let mut pos = new_pos;
4152                inner.with_state(|s| {
4153                    let l = s.as_movable_list_state().unwrap();
4154                    pos = l
4155                        .convert_index(new_pos, IndexType::ForOp, IndexType::ForUser)
4156                        .unwrap_or(l.len());
4157                });
4158                pos
4159            }
4160        }
4161    }
4162
4163    pub fn is_deleted(&self) -> bool {
4164        match &self.inner {
4165            MaybeDetached::Detached(_) => false,
4166            MaybeDetached::Attached(a) => a.is_deleted(),
4167        }
4168    }
4169
4170    pub fn clear(&self) -> LoroResult<()> {
4171        match &self.inner {
4172            MaybeDetached::Detached(d) => {
4173                let mut d = d.lock();
4174                d.value.clear();
4175                Ok(())
4176            }
4177            MaybeDetached::Attached(a) => a.with_txn(|txn| self.clear_with_txn(txn)),
4178        }
4179    }
4180
4181    pub fn clear_with_txn(&self, txn: &mut Transaction) -> LoroResult<()> {
4182        self.delete_with_txn(txn, 0, self.len())
4183    }
4184
4185    pub fn get_creator_at(&self, pos: usize) -> Option<PeerID> {
4186        match &self.inner {
4187            MaybeDetached::Detached(_) => None,
4188            MaybeDetached::Attached(a) => {
4189                a.with_state(|state| state.as_movable_list_state().unwrap().get_creator_at(pos))
4190            }
4191        }
4192    }
4193
4194    pub fn get_last_mover_at(&self, pos: usize) -> Option<PeerID> {
4195        match &self.inner {
4196            MaybeDetached::Detached(_) => None,
4197            MaybeDetached::Attached(a) => a.with_state(|state| {
4198                state
4199                    .as_movable_list_state()
4200                    .unwrap()
4201                    .get_last_mover_at(pos)
4202            }),
4203        }
4204    }
4205
4206    pub fn get_last_editor_at(&self, pos: usize) -> Option<PeerID> {
4207        match &self.inner {
4208            MaybeDetached::Detached(_) => None,
4209            MaybeDetached::Attached(a) => a.with_state(|state| {
4210                state
4211                    .as_movable_list_state()
4212                    .unwrap()
4213                    .get_last_editor_at(pos)
4214            }),
4215        }
4216    }
4217}
4218
4219impl MapHandler {
4220    /// Create a new container that is detached from the document.
4221    /// The edits on a detached container will not be persisted.
4222    /// To attach the container to the document, please insert it into an attached container.
4223    pub fn new_detached() -> Self {
4224        Self {
4225            inner: MaybeDetached::new_detached(Default::default()),
4226        }
4227    }
4228
4229    pub fn insert(&self, key: &str, value: impl Into<LoroValue>) -> LoroResult<()> {
4230        match &self.inner {
4231            MaybeDetached::Detached(m) => {
4232                let mut m = m.lock();
4233                let value = value.into();
4234                ensure_no_regular_container_value(&value)?;
4235                m.value.insert(key.into(), ValueOrHandler::Value(value));
4236                Ok(())
4237            }
4238            MaybeDetached::Attached(a) => {
4239                a.with_txn(|txn| self.insert_with_txn(txn, key, value.into()))
4240            }
4241        }
4242    }
4243
4244    /// This method will insert the value even if the same value is already in the given entry.
4245    fn insert_without_skipping(&self, key: &str, value: impl Into<LoroValue>) -> LoroResult<()> {
4246        match &self.inner {
4247            MaybeDetached::Detached(m) => {
4248                let mut m = m.lock();
4249                let value = value.into();
4250                ensure_no_regular_container_value(&value)?;
4251                m.value.insert(key.into(), ValueOrHandler::Value(value));
4252                Ok(())
4253            }
4254            MaybeDetached::Attached(a) => a.with_txn(|txn| {
4255                let this = &self;
4256                let value = value.into();
4257                ensure_no_regular_container_value(&value)?;
4258
4259                let inner = this.inner.try_attached_state()?;
4260                txn.apply_local_op(
4261                    inner.container_idx,
4262                    crate::op::RawOpContent::Map(crate::container::map::MapSet {
4263                        key: key.into(),
4264                        value: Some(value.clone()),
4265                    }),
4266                    EventHint::Map {
4267                        key: key.into(),
4268                        value: Some(value.clone()),
4269                    },
4270                    &inner.doc,
4271                )
4272            }),
4273        }
4274    }
4275
4276    pub fn insert_with_txn(
4277        &self,
4278        txn: &mut Transaction,
4279        key: &str,
4280        value: LoroValue,
4281    ) -> LoroResult<()> {
4282        ensure_no_regular_container_value(&value)?;
4283
4284        if self.get(key).map(|x| x == value).unwrap_or(false) {
4285            // skip if the value is already set
4286            return Ok(());
4287        }
4288
4289        let inner = self.inner.try_attached_state()?;
4290        txn.apply_local_op(
4291            inner.container_idx,
4292            crate::op::RawOpContent::Map(crate::container::map::MapSet {
4293                key: key.into(),
4294                value: Some(value.clone()),
4295            }),
4296            EventHint::Map {
4297                key: key.into(),
4298                value: Some(value.clone()),
4299            },
4300            &inner.doc,
4301        )
4302    }
4303
4304    pub fn insert_container<T: HandlerTrait>(&self, key: &str, handler: T) -> LoroResult<T> {
4305        match &self.inner {
4306            MaybeDetached::Detached(m) => {
4307                let mut m = m.lock();
4308                let to_insert = handler.to_handler();
4309                m.value
4310                    .insert(key.into(), ValueOrHandler::Handler(to_insert.clone()));
4311                Ok(handler)
4312            }
4313            MaybeDetached::Attached(a) => {
4314                a.with_txn(|txn| self.insert_container_with_txn(txn, key, handler))
4315            }
4316        }
4317    }
4318
4319    pub fn insert_container_with_txn<H: HandlerTrait>(
4320        &self,
4321        txn: &mut Transaction,
4322        key: &str,
4323        child: H,
4324    ) -> LoroResult<H> {
4325        let inner = self.inner.try_attached_state()?;
4326        let id = txn.next_id();
4327        let container_id = ContainerID::new_normal(id, child.kind());
4328        txn.apply_local_op(
4329            inner.container_idx,
4330            crate::op::RawOpContent::Map(crate::container::map::MapSet {
4331                key: key.into(),
4332                value: Some(LoroValue::Container(container_id.clone())),
4333            }),
4334            EventHint::Map {
4335                key: key.into(),
4336                value: Some(LoroValue::Container(container_id.clone())),
4337            },
4338            &inner.doc,
4339        )?;
4340
4341        child.attach(txn, inner, container_id)
4342    }
4343
4344    pub fn delete(&self, key: &str) -> LoroResult<()> {
4345        match &self.inner {
4346            MaybeDetached::Detached(m) => {
4347                let mut m = m.lock();
4348                m.value.remove(key);
4349                Ok(())
4350            }
4351            MaybeDetached::Attached(a) => a.with_txn(|txn| self.delete_with_txn(txn, key)),
4352        }
4353    }
4354
4355    pub fn delete_with_txn(&self, txn: &mut Transaction, key: &str) -> LoroResult<()> {
4356        let inner = self.inner.try_attached_state()?;
4357        txn.apply_local_op(
4358            inner.container_idx,
4359            crate::op::RawOpContent::Map(crate::container::map::MapSet {
4360                key: key.into(),
4361                value: None,
4362            }),
4363            EventHint::Map {
4364                key: key.into(),
4365                value: None,
4366            },
4367            &inner.doc,
4368        )
4369    }
4370
4371    pub fn for_each<I>(&self, mut f: I)
4372    where
4373        I: FnMut(&str, ValueOrHandler),
4374    {
4375        match &self.inner {
4376            MaybeDetached::Detached(m) => {
4377                let m = m.lock();
4378                for (k, v) in m.value.iter() {
4379                    f(k, v.clone());
4380                }
4381            }
4382            MaybeDetached::Attached(inner) => {
4383                let temp = inner.with_doc_state(|state| {
4384                    state
4385                        .get_map_entries(inner.container_idx)
4386                        .into_iter()
4387                        .map(|(key, value)| {
4388                            let translated = loro_common::translate_mergeable_marker_value(
4389                                &inner.id,
4390                                key.as_ref(),
4391                                value,
4392                            );
4393                            (
4394                                key.to_string(),
4395                                value_to_value_or_handler(inner, translated),
4396                            )
4397                        })
4398                        .collect::<Vec<_>>()
4399                });
4400
4401                for (k, v) in temp.into_iter() {
4402                    f(&k, v.clone());
4403                }
4404            }
4405        }
4406    }
4407
4408    pub fn get_child_handler(&self, key: &str) -> LoroResult<Handler> {
4409        match &self.inner {
4410            MaybeDetached::Detached(m) => {
4411                let m = m.lock();
4412                let value = m.value.get(key).unwrap();
4413                match value {
4414                    ValueOrHandler::Value(v) => Err(LoroError::ArgErr(
4415                        format!("Expected Handler but found {:?}", v).into_boxed_str(),
4416                    )),
4417                    ValueOrHandler::Handler(h) => Ok(h.clone()),
4418                }
4419            }
4420            MaybeDetached::Attached(_) => {
4421                let Some(value) = self.get_(key) else {
4422                    return Err(LoroError::ArgErr(
4423                        format!("Key {key} does not exist").into_boxed_str(),
4424                    ));
4425                };
4426                match value {
4427                    ValueOrHandler::Handler(handler) => Ok(handler),
4428                    ValueOrHandler::Value(value) => Err(LoroError::ArgErr(
4429                        format!("Expected Handler but found {:?}", value).into_boxed_str(),
4430                    )),
4431                }
4432            }
4433        }
4434    }
4435
4436    pub fn get_deep_value_with_id(&self) -> LoroResult<LoroValue> {
4437        match &self.inner {
4438            MaybeDetached::Detached(_) => Err(LoroError::MisuseDetachedContainer {
4439                method: "get_deep_value_with_id",
4440            }),
4441            MaybeDetached::Attached(inner) => Ok(inner.with_doc_state(|state| {
4442                state.get_container_deep_value_with_id(inner.container_idx, None)
4443            })),
4444        }
4445    }
4446
4447    pub fn get(&self, key: &str) -> Option<LoroValue> {
4448        match &self.inner {
4449            MaybeDetached::Detached(m) => {
4450                let m = m.lock();
4451                m.value.get(key).map(|v| v.to_value())
4452            }
4453            MaybeDetached::Attached(inner) => {
4454                let value = inner
4455                    .with_doc_state(|state| state.get_map_value_by_key(inner.container_idx, key))?;
4456                Some(loro_common::translate_mergeable_marker_value(
4457                    &inner.id, key, value,
4458                ))
4459            }
4460        }
4461    }
4462
4463    /// Get the value at given key, if value is a container, return a handler to the container
4464    pub fn get_(&self, key: &str) -> Option<ValueOrHandler> {
4465        match &self.inner {
4466            MaybeDetached::Detached(m) => {
4467                let m = m.lock();
4468                m.value.get(key).cloned()
4469            }
4470            MaybeDetached::Attached(inner) => {
4471                let value = inner
4472                    .with_doc_state(|state| state.get_map_value_by_key(inner.container_idx, key))?;
4473                let value = loro_common::translate_mergeable_marker_value(&inner.id, key, value);
4474                Some(value_to_value_or_handler(inner, value))
4475            }
4476        }
4477    }
4478
4479    /// Get or create a regular child container at `key`.
4480    ///
4481    /// This legacy method creates regular op-id child containers when the key is empty or `null`.
4482    /// It is not mergeable: concurrent first creation at the same map key can fork child state and
4483    /// leave one branch hidden by map conflict resolution. Prefer `ensure_mergeable_*` for lazy
4484    /// map-key child creation.
4485    #[deprecated(
4486        note = "use ensure_mergeable_map/list/movable_list/text/tree/counter for lazy map-key child creation; this method creates regular op-id children"
4487    )]
4488    pub fn get_or_create_container<C: HandlerTrait>(&self, key: &str, child: C) -> LoroResult<C> {
4489        if let Some(ans) = self.get_(key) {
4490            if let ValueOrHandler::Handler(h) = ans {
4491                let kind = h.kind();
4492                return C::from_handler(h).ok_or_else(move || {
4493                    LoroError::ArgErr(
4494                        format!("Expected value type {} but found {:?}", child.kind(), kind)
4495                            .into_boxed_str(),
4496                    )
4497                });
4498            } else if let ValueOrHandler::Value(LoroValue::Null) = ans {
4499                // do nothing
4500            } else {
4501                return Err(LoroError::ArgErr(
4502                    format!("Expected value type {} but found {:?}", child.kind(), ans)
4503                        .into_boxed_str(),
4504                ));
4505            }
4506        }
4507
4508        self.insert_container(key, child)
4509    }
4510
4511    /// Shared implementation for all `ensure_mergeable_*` methods.
4512    ///
4513    /// Computes a deterministic [`ContainerID::Root`] in the mergeable namespace from
4514    /// `(parent.id, key, child.kind())` and constructs the handler from it. Two peers calling this
4515    /// with the same `(parent, key, kind)` receive handlers with identical container ids, which is
4516    /// what makes the child container mergeable on concurrent first-write.
4517    ///
4518    /// # Errors
4519    ///
4520    /// Returns [`LoroError::MisuseDetachedContainer`] when called on a detached handler. The
4521    /// deterministic cid is computed from the parent's cid, which a detached parent does not have
4522    /// yet; falling back to a non-deterministic regular child would silently drop the mergeable
4523    /// guarantee at attach time. Detached callers must attach the parent first.
4524    ///
4525    /// Returns [`LoroError::ArgErr`] if the parent slot already holds a non-mergeable value, or if
4526    /// `C::from_handler` rejects the handler built from the deterministic cid (unreachable by
4527    /// construction; guards against future drift between `from_handler` and `kind`).
4528    fn ensure_mergeable_container<C: HandlerTrait>(&self, key: &str, child: C) -> LoroResult<C> {
4529        let MaybeDetached::Attached(parent) = &self.inner else {
4530            return Err(LoroError::MisuseDetachedContainer {
4531                method: "ensure_mergeable_container",
4532            });
4533        };
4534
4535        // Compare against the raw marker bytes (skipping `MapHandler::get`'s user-facing
4536        // marker → Container translation) so the non-mergeable-occupant guard sees the real
4537        // slot value and the same-kind idempotent-skip can match.
4538        let existing_raw =
4539            parent.with_doc_state(|state| state.get_map_value_by_key(parent.container_idx, key));
4540
4541        // A non-mergeable occupant (scalar, arbitrary binary, regular child container) would be
4542        // silently clobbered by the marker write, so reject rather than overwrite under a
4543        // `get_`-named API. Only the exact binary marker for this `(parent, key, kind)` is
4544        // accepted as an existing mergeable occupant.
4545        if let Some(existing) = &existing_raw {
4546            if !matches!(existing, LoroValue::Null)
4547                && loro_common::parse_mergeable_marker(&parent.id, key, existing).is_none()
4548            {
4549                return Err(LoroError::ArgErr(
4550                    format!(
4551                        "Cannot create a mergeable {} at key {key:?}: the key already holds a non-mergeable value",
4552                        child.kind()
4553                    )
4554                    .into_boxed_str(),
4555                ));
4556            }
4557        }
4558
4559        let cid = ContainerID::new_mergeable(&parent.id, key, child.kind());
4560        let marker = loro_common::mergeable_marker(&parent.id, key, child.kind());
4561
4562        // Idempotent-skip on same marker: `MapHandler::get` translates markers to Container, so
4563        // `insert_with_txn`'s equality check can't see this collision — do it directly.
4564        // A different-kind marker is a deliberate kind change; let the insert through.
4565        if existing_raw.as_ref() != Some(&marker) {
4566            self.insert(key, marker)?;
4567        }
4568
4569        C::from_handler(create_handler(parent, cid.clone())).ok_or_else(|| {
4570            LoroError::ArgErr(
4571                format!(
4572                    "Expected value type {} but found {}",
4573                    child.kind(),
4574                    cid.container_type()
4575                )
4576                .into_boxed_str(),
4577            )
4578        })
4579    }
4580
4581    #[cfg(feature = "counter")]
4582    /// Ensure a mergeable Counter child exists under `key` and return its handler.
4583    ///
4584    /// Returns [`LoroError::MisuseDetachedContainer`] when called on a detached map.
4585    /// Returns [`LoroError::ArgErr`] if the parent slot already holds a non-mergeable value.
4586    /// Repeated same-kind calls are idempotent; different mergeable kinds deliberately rewrite
4587    /// the active marker while preserving each mergeable child's deterministic state.
4588    pub fn ensure_mergeable_counter(&self, key: &str) -> LoroResult<counter::CounterHandler> {
4589        self.ensure_mergeable_container(key, counter::CounterHandler::new_detached())
4590    }
4591
4592    /// Ensure a mergeable Map child exists under `key` and return its handler.
4593    ///
4594    /// Returns [`LoroError::MisuseDetachedContainer`] when called on a detached map.
4595    /// Returns [`LoroError::ArgErr`] if the parent slot already holds a non-mergeable value.
4596    /// Repeated same-kind calls are idempotent; different mergeable kinds deliberately rewrite
4597    /// the active marker while preserving each mergeable child's deterministic state.
4598    ///
4599    /// Prefer to avoid very deep mergeable-map chains: mergeable cids encode their flattened
4600    /// logical path, so cid size still grows with depth and rides through every op/snapshot
4601    /// reference to it. See [`MERGEABLE_NAMESPACE_PREFIX`](loro_common::MERGEABLE_NAMESPACE_PREFIX).
4602    pub fn ensure_mergeable_map(&self, key: &str) -> LoroResult<MapHandler> {
4603        self.ensure_mergeable_container(key, MapHandler::new_detached())
4604    }
4605
4606    /// Ensure a mergeable List child exists under `key` and return its handler.
4607    ///
4608    /// Returns [`LoroError::MisuseDetachedContainer`] when called on a detached map.
4609    /// Returns [`LoroError::ArgErr`] if the parent slot already holds a non-mergeable value.
4610    /// Repeated same-kind calls are idempotent; different mergeable kinds deliberately rewrite
4611    /// the active marker while preserving each mergeable child's deterministic state.
4612    pub fn ensure_mergeable_list(&self, key: &str) -> LoroResult<ListHandler> {
4613        self.ensure_mergeable_container(key, ListHandler::new_detached())
4614    }
4615
4616    /// Ensure a mergeable MovableList child exists under `key` and return its handler.
4617    ///
4618    /// Returns [`LoroError::MisuseDetachedContainer`] when called on a detached map.
4619    /// Returns [`LoroError::ArgErr`] if the parent slot already holds a non-mergeable value.
4620    /// Repeated same-kind calls are idempotent; different mergeable kinds deliberately rewrite
4621    /// the active marker while preserving each mergeable child's deterministic state.
4622    pub fn ensure_mergeable_movable_list(&self, key: &str) -> LoroResult<MovableListHandler> {
4623        self.ensure_mergeable_container(key, MovableListHandler::new_detached())
4624    }
4625
4626    /// Ensure a mergeable Text child exists under `key` and return its handler.
4627    ///
4628    /// Returns [`LoroError::MisuseDetachedContainer`] when called on a detached map.
4629    /// Returns [`LoroError::ArgErr`] if the parent slot already holds a non-mergeable value.
4630    /// Repeated same-kind calls are idempotent; different mergeable kinds deliberately rewrite
4631    /// the active marker while preserving each mergeable child's deterministic state.
4632    pub fn ensure_mergeable_text(&self, key: &str) -> LoroResult<TextHandler> {
4633        self.ensure_mergeable_container(key, TextHandler::new_detached())
4634    }
4635
4636    /// Ensure a mergeable Tree child exists under `key` and return its handler.
4637    ///
4638    /// Returns [`LoroError::MisuseDetachedContainer`] when called on a detached map.
4639    /// Returns [`LoroError::ArgErr`] if the parent slot already holds a non-mergeable value.
4640    /// Repeated same-kind calls are idempotent; different mergeable kinds deliberately rewrite
4641    /// the active marker while preserving each mergeable child's deterministic state.
4642    pub fn ensure_mergeable_tree(&self, key: &str) -> LoroResult<TreeHandler> {
4643        self.ensure_mergeable_container(key, TreeHandler::new_detached())
4644    }
4645
4646    pub fn contains_key(&self, key: &str) -> bool {
4647        self.get(key).is_some()
4648    }
4649
4650    pub fn len(&self) -> usize {
4651        match &self.inner {
4652            MaybeDetached::Detached(m) => m.lock().value.len(),
4653            MaybeDetached::Attached(a) => {
4654                a.with_doc_state(|state| state.get_map_len(a.container_idx))
4655            }
4656        }
4657    }
4658
4659    pub fn is_empty(&self) -> bool {
4660        self.len() == 0
4661    }
4662
4663    pub fn is_deleted(&self) -> bool {
4664        match &self.inner {
4665            MaybeDetached::Detached(_) => false,
4666            MaybeDetached::Attached(a) => a.is_deleted(),
4667        }
4668    }
4669
4670    pub fn clear(&self) -> LoroResult<()> {
4671        match &self.inner {
4672            MaybeDetached::Detached(m) => {
4673                let mut m = m.lock();
4674                m.value.clear();
4675                Ok(())
4676            }
4677            MaybeDetached::Attached(a) => a.with_txn(|txn| self.clear_with_txn(txn)),
4678        }
4679    }
4680
4681    pub fn clear_with_txn(&self, txn: &mut Transaction) -> LoroResult<()> {
4682        let keys: Vec<InternalString> = self.inner.try_attached_state()?.with_state(|state| {
4683            state
4684                .as_map_state()
4685                .unwrap()
4686                .iter()
4687                .map(|(k, _)| k.clone())
4688                .collect()
4689        });
4690
4691        for key in keys {
4692            self.delete_with_txn(txn, &key)?;
4693        }
4694
4695        Ok(())
4696    }
4697
4698    pub fn keys(&self) -> impl Iterator<Item = InternalString> + '_ {
4699        let keys: Vec<InternalString> = match &self.inner {
4700            MaybeDetached::Detached(m) => {
4701                let m = m.lock();
4702                m.value.keys().map(|x| x.as_str().into()).collect()
4703            }
4704            MaybeDetached::Attached(a) => {
4705                a.with_doc_state(|state| state.get_map_keys(a.container_idx))
4706            }
4707        };
4708
4709        keys.into_iter()
4710    }
4711
4712    pub fn values(&self) -> impl Iterator<Item = ValueOrHandler> + '_ {
4713        let values: Vec<ValueOrHandler> = match &self.inner {
4714            MaybeDetached::Detached(m) => {
4715                let m = m.lock();
4716                m.value.values().cloned().collect()
4717            }
4718            MaybeDetached::Attached(a) => a.with_doc_state(|state| {
4719                // A mergeable child's marker lives in the parent map's value table; iterate
4720                // entries (key + value) so the user-boundary translation can resolve each marker
4721                // to the deterministic child cid before wrapping into a handler.
4722                state
4723                    .get_map_entries(a.container_idx)
4724                    .into_iter()
4725                    .map(|(key, value)| {
4726                        let translated = loro_common::translate_mergeable_marker_value(
4727                            &a.id,
4728                            key.as_ref(),
4729                            value,
4730                        );
4731                        value_to_value_or_handler(a, translated)
4732                    })
4733                    .collect()
4734            }),
4735        };
4736
4737        values.into_iter()
4738    }
4739
4740    pub fn get_last_editor(&self, key: &str) -> Option<PeerID> {
4741        match &self.inner {
4742            MaybeDetached::Detached(_) => None,
4743            MaybeDetached::Attached(a) => a.with_state(|state| {
4744                let m = state.as_map_state().unwrap();
4745                m.get_last_edit_peer(key)
4746            }),
4747        }
4748    }
4749}
4750
4751fn with_txn<R>(doc: &LoroDoc, f: impl FnOnce(&mut Transaction) -> LoroResult<R>) -> LoroResult<R> {
4752    let txn = &doc.txn;
4753    let mut txn = txn.lock();
4754    loop {
4755        if let Some(txn) = &mut *txn {
4756            return f(txn);
4757        } else if cfg!(target_arch = "wasm32") || !doc.can_edit() {
4758            return Err(LoroError::AutoCommitNotStarted);
4759        } else {
4760            drop(txn);
4761            #[cfg(loom)]
4762            loom::thread::yield_now();
4763            doc.start_auto_commit();
4764            txn = doc.txn.lock();
4765        }
4766    }
4767}
4768
4769#[cfg(feature = "counter")]
4770pub mod counter {
4771
4772    use loro_common::LoroResult;
4773
4774    use crate::{
4775        txn::{EventHint, Transaction},
4776        HandlerTrait,
4777    };
4778
4779    use super::{create_handler, Handler, MaybeDetached};
4780
4781    #[derive(Clone)]
4782    pub struct CounterHandler {
4783        pub(super) inner: MaybeDetached<f64>,
4784    }
4785
4786    impl CounterHandler {
4787        pub fn new_detached() -> Self {
4788            Self {
4789                inner: MaybeDetached::new_detached(0.),
4790            }
4791        }
4792
4793        pub fn increment(&self, n: f64) -> LoroResult<()> {
4794            match &self.inner {
4795                MaybeDetached::Detached(d) => {
4796                    let d = &mut d.lock().value;
4797                    *d += n;
4798                    Ok(())
4799                }
4800                MaybeDetached::Attached(a) => a.with_txn(|txn| self.increment_with_txn(txn, n)),
4801            }
4802        }
4803
4804        pub fn decrement(&self, n: f64) -> LoroResult<()> {
4805            match &self.inner {
4806                MaybeDetached::Detached(d) => {
4807                    let d = &mut d.lock().value;
4808                    *d -= n;
4809                    Ok(())
4810                }
4811                MaybeDetached::Attached(a) => a.with_txn(|txn| self.increment_with_txn(txn, -n)),
4812            }
4813        }
4814
4815        fn increment_with_txn(&self, txn: &mut Transaction, n: f64) -> LoroResult<()> {
4816            let inner = self.inner.try_attached_state()?;
4817            txn.apply_local_op(
4818                inner.container_idx,
4819                crate::op::RawOpContent::Counter(n),
4820                EventHint::Counter(n),
4821                &inner.doc,
4822            )
4823        }
4824
4825        pub fn is_deleted(&self) -> bool {
4826            match &self.inner {
4827                MaybeDetached::Detached(_) => false,
4828                MaybeDetached::Attached(a) => a.is_deleted(),
4829            }
4830        }
4831
4832        pub fn clear(&self) -> LoroResult<()> {
4833            self.decrement(self.get_value().into_double().unwrap())
4834        }
4835    }
4836
4837    impl std::fmt::Debug for CounterHandler {
4838        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4839            match &self.inner {
4840                MaybeDetached::Detached(_) => write!(f, "CounterHandler Detached"),
4841                MaybeDetached::Attached(a) => write!(f, "CounterHandler {}", a.id),
4842            }
4843        }
4844    }
4845
4846    impl HandlerTrait for CounterHandler {
4847        fn is_attached(&self) -> bool {
4848            matches!(&self.inner, MaybeDetached::Attached(..))
4849        }
4850
4851        fn attached_handler(&self) -> Option<&crate::BasicHandler> {
4852            self.inner.attached_handler()
4853        }
4854
4855        fn get_value(&self) -> loro_common::LoroValue {
4856            match &self.inner {
4857                MaybeDetached::Detached(t) => {
4858                    let t = t.lock();
4859                    t.value.into()
4860                }
4861                MaybeDetached::Attached(a) => a.get_value(),
4862            }
4863        }
4864
4865        fn get_deep_value(&self) -> loro_common::LoroValue {
4866            self.get_value()
4867        }
4868
4869        fn kind(&self) -> loro_common::ContainerType {
4870            loro_common::ContainerType::Counter
4871        }
4872
4873        fn to_handler(&self) -> super::Handler {
4874            Handler::Counter(self.clone())
4875        }
4876
4877        fn from_handler(h: super::Handler) -> Option<Self> {
4878            match h {
4879                Handler::Counter(x) => Some(x),
4880                _ => None,
4881            }
4882        }
4883
4884        fn attach(
4885            &self,
4886            txn: &mut crate::txn::Transaction,
4887            parent: &crate::BasicHandler,
4888            self_id: loro_common::ContainerID,
4889        ) -> loro_common::LoroResult<Self> {
4890            match &self.inner {
4891                MaybeDetached::Detached(v) => {
4892                    let mut v = v.lock();
4893                    let inner = create_handler(parent, self_id);
4894                    let c = inner.into_counter().unwrap();
4895
4896                    c.increment_with_txn(txn, v.value)?;
4897
4898                    v.attached = c.attached_handler().cloned();
4899                    Ok(c)
4900                }
4901                MaybeDetached::Attached(a) => {
4902                    let new_inner = create_handler(a, self_id);
4903                    let ans = new_inner.into_counter().unwrap();
4904                    let delta = *self.get_value().as_double().unwrap();
4905                    ans.increment_with_txn(txn, delta)?;
4906                    Ok(ans)
4907                }
4908            }
4909        }
4910
4911        fn get_attached(&self) -> Option<Self> {
4912            match &self.inner {
4913                MaybeDetached::Attached(a) => Some(Self {
4914                    inner: MaybeDetached::Attached(a.clone()),
4915                }),
4916                MaybeDetached::Detached(v) => v.lock().attached.clone().map(|x| Self {
4917                    inner: MaybeDetached::Attached(x),
4918                }),
4919            }
4920        }
4921
4922        fn doc(&self) -> Option<crate::LoroDoc> {
4923            match &self.inner {
4924                MaybeDetached::Detached(_) => None,
4925                MaybeDetached::Attached(a) => Some(a.doc()),
4926            }
4927        }
4928    }
4929}
4930
4931#[cfg(test)]
4932mod test {
4933    use std::borrow::Cow;
4934
4935    use super::{
4936        Handler, HandlerTrait, ListHandler, MapHandler, MovableListHandler, TextDelta, TextHandler,
4937        ValueOrHandler,
4938    };
4939    use crate::container::list::list_op::ListOp;
4940    use crate::cursor::PosType;
4941    use crate::loro::ExportMode;
4942    use crate::op::ListSlice;
4943    use crate::state::TreeParentId;
4944    use crate::txn::EventHint;
4945    use crate::version::Frontiers;
4946    use crate::LoroDoc;
4947    use crate::{fx_map, ToJson};
4948    use loro_common::{ContainerID, ContainerType, LoroError, LoroValue, ID};
4949    use serde_json::json;
4950
4951    fn recheck_fast_blob(mut bytes: Vec<u8>) -> Vec<u8> {
4952        let checksum = xxhash_rust::xxh32::xxh32(&bytes[20..], u32::from_le_bytes(*b"LORO"));
4953        bytes[16..20].copy_from_slice(&checksum.to_le_bytes());
4954        bytes
4955    }
4956
4957    fn replace_fast_snapshot_state_bytes(mut snapshot: Vec<u8>, state_bytes: &[u8]) -> Vec<u8> {
4958        let mut body = &snapshot[22..];
4959        let oplog_len = u32::from_le_bytes(body[..4].try_into().unwrap()) as usize;
4960        body = &body[4 + oplog_len..];
4961        let old_state_len = u32::from_le_bytes(body[..4].try_into().unwrap()) as usize;
4962        let state_len_pos = 22 + 4 + oplog_len;
4963        let state_start = state_len_pos + 4;
4964        let state_end = state_start + old_state_len;
4965        snapshot[state_len_pos..state_start]
4966            .copy_from_slice(&(state_bytes.len() as u32).to_le_bytes());
4967        snapshot.splice(state_start..state_end, state_bytes.iter().copied());
4968        recheck_fast_blob(snapshot)
4969    }
4970
4971    fn insert_many_with_single_list_op(
4972        txn: &mut crate::txn::Transaction,
4973        list: &crate::handler::ListHandler,
4974        pos: usize,
4975        values: Vec<LoroValue>,
4976    ) {
4977        let len = values.len();
4978        let inner = list.inner.try_attached_state().unwrap();
4979        txn.apply_local_op(
4980            inner.container_idx,
4981            crate::op::RawOpContent::List(ListOp::Insert {
4982                slice: ListSlice::RawData(Cow::Owned(values)),
4983                pos,
4984            }),
4985            EventHint::InsertList {
4986                len: len as u32,
4987                pos,
4988            },
4989            &inner.doc,
4990        )
4991        .unwrap();
4992    }
4993
4994    #[test]
4995    fn richtext_handler() {
4996        let loro = LoroDoc::new();
4997        loro.set_peer_id(1).unwrap();
4998        let loro2 = LoroDoc::new();
4999        loro2.set_peer_id(2).unwrap();
5000
5001        let mut txn = loro.txn().unwrap();
5002        let text = txn.get_text("hello");
5003        text.insert_with_txn(&mut txn, 0, "hello", PosType::Unicode)
5004            .unwrap();
5005        txn.commit().unwrap();
5006        let exported = loro.export(ExportMode::all_updates()).unwrap();
5007
5008        loro2.import(&exported).unwrap();
5009        let mut txn = loro2.txn().unwrap();
5010        let text = txn.get_text("hello");
5011        assert_eq!(&**text.get_value().as_string().unwrap(), "hello");
5012        text.insert_with_txn(&mut txn, 5, " world", PosType::Unicode)
5013            .unwrap();
5014        assert_eq!(&**text.get_value().as_string().unwrap(), "hello world");
5015        txn.commit().unwrap();
5016
5017        loro.import(&loro2.export(ExportMode::all_updates()).unwrap())
5018            .unwrap();
5019        let txn = loro.txn().unwrap();
5020        let text = txn.get_text("hello");
5021        assert_eq!(&**text.get_value().as_string().unwrap(), "hello world");
5022        txn.commit().unwrap();
5023
5024        // test checkout
5025        loro.checkout(&Frontiers::from_id(ID::new(2, 1))).unwrap();
5026        assert_eq!(&**text.get_value().as_string().unwrap(), "hello w");
5027    }
5028
5029    #[test]
5030    fn richtext_handler_concurrent() {
5031        let loro = LoroDoc::new();
5032        let mut txn = loro.txn().unwrap();
5033        let handler = loro.get_text("richtext");
5034        handler
5035            .insert_with_txn(&mut txn, 0, "hello", PosType::Unicode)
5036            .unwrap();
5037        txn.commit().unwrap();
5038        for i in 0..100 {
5039            let new_loro = LoroDoc::new();
5040            new_loro
5041                .import(&loro.export(ExportMode::all_updates()).unwrap())
5042                .unwrap();
5043            let mut txn = new_loro.txn().unwrap();
5044            let handler = new_loro.get_text("richtext");
5045            handler
5046                .insert_with_txn(&mut txn, i % 5, &i.to_string(), PosType::Unicode)
5047                .unwrap();
5048            txn.commit().unwrap();
5049            loro.import(
5050                &new_loro
5051                    .export(ExportMode::updates(&loro.oplog_vv()))
5052                    .unwrap(),
5053            )
5054            .unwrap();
5055        }
5056    }
5057
5058    #[test]
5059    fn cross_doc_txn_is_rejected() {
5060        // `insert_with_txn`/`delete_with_txn` are public API, so a transaction
5061        // from one document can be fed to another document's handler. That must
5062        // be rejected with `UnmatchedContext` rather than silently stamping the
5063        // target doc's state/oplog with the wrong peer+counter. Regression test
5064        // for the always-on (release included) context check in
5065        // `Transaction::apply_local_op`.
5066        let doc_a = LoroDoc::new();
5067        doc_a.set_peer_id(1).unwrap();
5068        let doc_b = LoroDoc::new();
5069        doc_b.set_peer_id(2).unwrap();
5070
5071        // Seed doc_b so it has real state we can prove stays untouched.
5072        {
5073            let mut txn_b = doc_b.txn().unwrap();
5074            doc_b
5075                .get_text("text")
5076                .insert_with_txn(&mut txn_b, 0, "ok", PosType::Unicode)
5077                .unwrap();
5078            txn_b.commit().unwrap();
5079        }
5080        let vv_before = doc_b.oplog_vv();
5081
5082        // Feed doc_a's transaction to doc_b's handler.
5083        let mut txn_a = doc_a.txn().unwrap();
5084        let text_b = doc_b.get_text("text");
5085        let insert_err = text_b
5086            .insert_with_txn(&mut txn_a, 0, "x", PosType::Unicode)
5087            .unwrap_err();
5088        assert!(matches!(insert_err, LoroError::UnmatchedContext { .. }));
5089        let delete_err = text_b
5090            .delete_with_txn(&mut txn_a, 0, 1, PosType::Unicode)
5091            .unwrap_err();
5092        assert!(matches!(delete_err, LoroError::UnmatchedContext { .. }));
5093        txn_a.commit().unwrap();
5094
5095        // doc_b is unchanged: content and version vector identical.
5096        assert_eq!(&**text_b.get_value().as_string().unwrap(), "ok");
5097        assert_eq!(doc_b.oplog_vv(), vv_before);
5098    }
5099
5100    #[test]
5101    fn list_import_batch_stays_consistent_after_repeated_tail_splits() {
5102        let doc_a = LoroDoc::new();
5103        doc_a.set_peer_id(1).unwrap();
5104        let mut txn = doc_a.txn().unwrap();
5105        let list_a = txn.get_list("list");
5106        insert_many_with_single_list_op(
5107            &mut txn,
5108            &list_a,
5109            0,
5110            (0..300).map(|i| LoroValue::I64(i)).collect(),
5111        );
5112        txn.commit().unwrap();
5113
5114        let doc_b = LoroDoc::new();
5115        doc_b.set_peer_id(2).unwrap();
5116        doc_b
5117            .import(&doc_a.export(ExportMode::all_updates()).unwrap())
5118            .unwrap();
5119
5120        let list_b = doc_b.get_list("list");
5121        let mut vv = doc_a.oplog_vv();
5122        let mut updates = Vec::new();
5123        for (i, pos) in [100, 201, 252, 278].into_iter().enumerate() {
5124            list_b.insert(pos, 1000 + i as i64).unwrap();
5125            updates.push(doc_b.export(ExportMode::updates(&vv)).unwrap());
5126            vv = doc_b.oplog_vv();
5127        }
5128
5129        doc_a.import_batch(&updates).unwrap();
5130        doc_a.check_state_diff_calc_consistency_slow();
5131        doc_b.check_state_diff_calc_consistency_slow();
5132        assert_eq!(doc_a.get_deep_value(), doc_b.get_deep_value());
5133    }
5134
5135    #[test]
5136    fn richtext_handler_mark() {
5137        let loro = LoroDoc::new_auto_commit();
5138        let handler = loro.get_text("richtext");
5139        handler.insert(0, "hello world", PosType::Unicode).unwrap();
5140        handler
5141            .mark(0, 5, "bold", true.into(), PosType::Event)
5142            .unwrap();
5143        loro.commit_then_renew();
5144
5145        // assert has bold
5146        let value = handler.get_richtext_value();
5147        assert_eq!(value[0]["insert"], "hello".into());
5148        let meta = value[0]["attributes"].as_map().unwrap();
5149        assert_eq!(meta.len(), 1);
5150        meta.get("bold").unwrap();
5151
5152        let loro2 = LoroDoc::new_auto_commit();
5153        loro2
5154            .import(&loro.export(ExportMode::all_updates()).unwrap())
5155            .unwrap();
5156        let handler2 = loro2.get_text("richtext");
5157        assert_eq!(&**handler2.get_value().as_string().unwrap(), "hello world");
5158
5159        // assert has bold
5160        let value = handler2.get_richtext_value();
5161        assert_eq!(value[0]["insert"], "hello".into());
5162        let meta = value[0]["attributes"].as_map().unwrap();
5163        assert_eq!(meta.len(), 1);
5164        meta.get("bold").unwrap();
5165
5166        // insert after bold should be bold
5167        {
5168            handler2.insert(5, " new", PosType::Unicode).unwrap();
5169            let value = handler2.get_richtext_value();
5170            assert_eq!(
5171                value.to_json_value(),
5172                serde_json::json!([
5173                    {"insert": "hello new", "attributes": {"bold": true}},
5174                    {"insert": " world"}
5175                ])
5176            );
5177        }
5178    }
5179
5180    #[test]
5181    fn richtext_snapshot() {
5182        let loro = LoroDoc::new();
5183        let mut txn = loro.txn().unwrap();
5184        let handler = loro.get_text("richtext");
5185        handler
5186            .insert_with_txn(&mut txn, 0, "hello world", PosType::Unicode)
5187            .unwrap();
5188        handler
5189            .mark_with_txn(&mut txn, 0, 5, "bold", true.into(), PosType::Event)
5190            .unwrap();
5191        txn.commit().unwrap();
5192
5193        let loro2 = LoroDoc::new();
5194        loro2
5195            .import(&loro.export(ExportMode::snapshot()).unwrap())
5196            .unwrap();
5197        let handler2 = loro2.get_text("richtext");
5198        assert_eq!(
5199            handler2.get_richtext_value().to_json_value(),
5200            serde_json::json!([
5201                {"insert": "hello", "attributes": {"bold": true}},
5202                {"insert": " world"}
5203            ])
5204        );
5205    }
5206
5207    #[test]
5208    fn text_snapshot_string_queries_do_not_decode_state() {
5209        let loro = LoroDoc::new_auto_commit();
5210        let text = loro.get_text("text");
5211        text.insert(0, "a😀文", PosType::Unicode).unwrap();
5212        text.mark(1, 3, "bold", true.into(), PosType::Unicode)
5213            .unwrap();
5214
5215        let restored = LoroDoc::new();
5216        restored
5217            .import(&loro.export(ExportMode::snapshot()).unwrap())
5218            .unwrap();
5219        let text = restored.get_text("text");
5220        assert!(!text.attached_handler().unwrap().has_decoded_state());
5221
5222        assert_eq!(text.len_unicode(), 3);
5223        assert_eq!(text.len_utf16(), 4);
5224        assert_eq!(text.len_utf8(), "a😀文".len());
5225        assert_eq!(text.char_at(1, PosType::Unicode).unwrap(), '😀');
5226        assert_eq!(text.slice(1, 3, PosType::Unicode).unwrap(), "😀文");
5227        assert_eq!(
5228            text.convert_pos(2, PosType::Unicode, PosType::Utf16),
5229            Some(3)
5230        );
5231        assert!(matches!(
5232            text.delete_utf16(2, 1),
5233            Err(LoroError::UTF16InUnicodeCodePoint { pos: 2 })
5234        ));
5235        assert!(matches!(
5236            text.delete_utf8(2, 1),
5237            Err(LoroError::UTF8InUnicodeCodePoint { pos: 2 })
5238        ));
5239        assert!(matches!(
5240            text.slice_delta(2, 3, PosType::Utf16),
5241            Err(LoroError::UTF16InUnicodeCodePoint { pos: 2 })
5242        ));
5243        assert!(matches!(
5244            text.slice_delta(2, 3, PosType::Bytes),
5245            Err(LoroError::UTF8InUnicodeCodePoint { pos: 2 })
5246        ));
5247        assert!(!text.attached_handler().unwrap().has_decoded_state());
5248
5249        assert_eq!(text.get_delta().len(), 2);
5250        assert!(text.attached_handler().unwrap().has_decoded_state());
5251    }
5252
5253    #[test]
5254    fn text_lazy_event_queries_match_decoded_state() {
5255        let loro = LoroDoc::new_auto_commit();
5256        let text = loro.get_text("text");
5257        text.insert(0, "ab😀cd", PosType::Unicode).unwrap();
5258        text.mark(1, 4, "bold", true.into(), PosType::Unicode)
5259            .unwrap();
5260        text.mark(2, 3, "link", "x".into(), PosType::Unicode)
5261            .unwrap();
5262
5263        let lazy_doc = LoroDoc::new();
5264        lazy_doc
5265            .import(&loro.export(ExportMode::snapshot()).unwrap())
5266            .unwrap();
5267        let lazy_text = lazy_doc.get_text("text");
5268
5269        let decoded_doc = LoroDoc::new();
5270        decoded_doc
5271            .import(&loro.export(ExportMode::snapshot()).unwrap())
5272            .unwrap();
5273        let decoded_text = decoded_doc.get_text("text");
5274        decoded_text.get_delta();
5275
5276        assert!(!lazy_text.attached_handler().unwrap().has_decoded_state());
5277        assert!(decoded_text.attached_handler().unwrap().has_decoded_state());
5278
5279        for pos_type in [
5280            PosType::Event,
5281            PosType::Unicode,
5282            PosType::Utf16,
5283            PosType::Bytes,
5284        ] {
5285            assert_eq!(lazy_text.len(pos_type), decoded_text.len(pos_type));
5286            for pos in 0..=decoded_text.len(pos_type) {
5287                assert_eq!(
5288                    lazy_text.convert_pos(pos, pos_type, PosType::Unicode),
5289                    decoded_text.convert_pos(pos, pos_type, PosType::Unicode),
5290                    "convert {pos_type:?} pos {pos} to unicode"
5291                );
5292                assert_eq!(
5293                    lazy_text.convert_pos(pos, pos_type, PosType::Event),
5294                    decoded_text.convert_pos(pos, pos_type, PosType::Event),
5295                    "convert {pos_type:?} pos {pos} to event"
5296                );
5297                if pos < decoded_text.len(pos_type) {
5298                    assert_eq!(
5299                        lazy_text.char_at(pos, pos_type),
5300                        decoded_text.char_at(pos, pos_type),
5301                        "char_at {pos_type:?} pos {pos}"
5302                    );
5303                }
5304                for end in pos..=decoded_text.len(pos_type) {
5305                    assert_eq!(
5306                        lazy_text.slice(pos, end, pos_type),
5307                        decoded_text.slice(pos, end, pos_type),
5308                        "slice {pos_type:?} {pos}..{end}"
5309                    );
5310                }
5311            }
5312        }
5313    }
5314
5315    #[test]
5316    fn deep_value_with_id_uses_lazy_values_for_snapshot_roots() {
5317        let loro = LoroDoc::new_auto_commit();
5318        let text = loro.get_text("text");
5319        text.insert(0, "hello", PosType::Unicode).unwrap();
5320        let map = loro.get_map("map");
5321        map.insert("key", "value").unwrap();
5322        let list = loro.get_list("list");
5323        list.push("item").unwrap();
5324
5325        let restored = LoroDoc::new();
5326        restored
5327            .import(&loro.export(ExportMode::snapshot()).unwrap())
5328            .unwrap();
5329        let text = restored.get_text("text");
5330        let map = restored.get_map("map");
5331        let list = restored.get_list("list");
5332
5333        let value = restored.get_deep_value_with_id();
5334        assert_eq!(value["text"]["value"], "hello".into());
5335        assert_eq!(value["map"]["value"]["key"], "value".into());
5336        assert_eq!(value["list"]["value"][0], "item".into());
5337        assert!(!text.attached_handler().unwrap().has_decoded_state());
5338        assert!(!map.attached_handler().unwrap().has_decoded_state());
5339        assert!(!list.attached_handler().unwrap().has_decoded_state());
5340    }
5341
5342    #[test]
5343    fn lazy_value_reads_do_not_write_stale_snapshot_after_mutation() {
5344        let loro = LoroDoc::new_auto_commit();
5345        let map = loro.get_map("map");
5346        map.insert("key", "old").unwrap();
5347        let child = map
5348            .insert_container("child", MapHandler::new_detached())
5349            .unwrap();
5350        child.insert("nested", "old").unwrap();
5351        let list = loro.get_list("list");
5352        list.push("old").unwrap();
5353        let child_list = list.push_container(ListHandler::new_detached()).unwrap();
5354        child_list.push("nested-old").unwrap();
5355
5356        let restored = LoroDoc::new();
5357        restored
5358            .import(&loro.export(ExportMode::snapshot()).unwrap())
5359            .unwrap();
5360        let map = restored.get_map("map");
5361        let list = restored.get_list("list");
5362
5363        assert_eq!(map.get("key").unwrap(), "old".into());
5364        assert_eq!(list.get(0).unwrap(), "old".into());
5365        let child = match map.get_("child").unwrap() {
5366            ValueOrHandler::Handler(handler) => handler.into_map().unwrap(),
5367            ValueOrHandler::Value(value) => panic!("expected child map, got {value:?}"),
5368        };
5369        let child_list = match list.get_(1).unwrap() {
5370            ValueOrHandler::Handler(handler) => handler.into_list().unwrap(),
5371            ValueOrHandler::Value(value) => panic!("expected child list, got {value:?}"),
5372        };
5373
5374        map.insert("key", "new").unwrap();
5375        child.insert("nested", "new").unwrap();
5376        list.delete(0, 1).unwrap();
5377        list.insert(0, "new").unwrap();
5378        child_list.delete(0, 1).unwrap();
5379        child_list.insert(0, "nested-new").unwrap();
5380        restored.commit_then_renew();
5381
5382        let roundtrip = LoroDoc::new();
5383        roundtrip
5384            .import(&restored.export(ExportMode::snapshot()).unwrap())
5385            .unwrap();
5386        assert_eq!(
5387            roundtrip.get_deep_value().to_json_value(),
5388            serde_json::json!({
5389                "map": { "key": "new", "child": { "nested": "new" } },
5390                "list": ["new", ["nested-new"]]
5391            })
5392        );
5393    }
5394
5395    #[test]
5396    fn fast_snapshot_with_trailing_bytes_is_rejected_on_import() {
5397        let loro = LoroDoc::new_auto_commit();
5398        let map = loro.get_map("map");
5399        map.insert("key", "value").unwrap();
5400        let mut snapshot = loro.export(ExportMode::snapshot()).unwrap();
5401        snapshot.push(0xff);
5402        let corrupted = recheck_fast_blob(snapshot);
5403
5404        let doc = LoroDoc::new();
5405        assert!(doc.import(&corrupted).is_err());
5406    }
5407
5408    #[test]
5409    fn fast_snapshot_with_trailing_bytes_is_rejected_by_meta_decoder() {
5410        let loro = LoroDoc::new_auto_commit();
5411        let map = loro.get_map("map");
5412        map.insert("key", "value").unwrap();
5413        let mut snapshot = loro.export(ExportMode::snapshot()).unwrap();
5414        snapshot.push(0xff);
5415        let corrupted = recheck_fast_blob(snapshot);
5416
5417        assert!(LoroDoc::decode_import_blob_meta(&corrupted, true).is_err());
5418    }
5419
5420    #[test]
5421    fn fast_snapshot_empty_sstable_meta_is_rejected_on_import() {
5422        let loro = LoroDoc::new_auto_commit();
5423        let map = loro.get_map("map");
5424        map.insert("key", "value").unwrap();
5425        let snapshot = loro.export(ExportMode::snapshot()).unwrap();
5426
5427        let mut malformed_state = Vec::new();
5428        malformed_state.extend_from_slice(b"LORO");
5429        malformed_state.push(0);
5430        malformed_state.extend_from_slice(&0u32.to_le_bytes());
5431        let checksum = xxhash_rust::xxh32::xxh32(&[], u32::from_le_bytes(*b"LORO"));
5432        malformed_state.extend_from_slice(&checksum.to_le_bytes());
5433        malformed_state.extend_from_slice(&5u32.to_le_bytes());
5434        let corrupted = replace_fast_snapshot_state_bytes(snapshot, &malformed_state);
5435
5436        let doc = LoroDoc::new();
5437        assert!(doc.import(&corrupted).is_err());
5438    }
5439
5440    #[test]
5441    fn tree_meta() {
5442        let loro = LoroDoc::new_auto_commit();
5443        loro.set_peer_id(1).unwrap();
5444        let tree = loro.get_tree("root");
5445        let id = tree.create(TreeParentId::Root).unwrap();
5446        let meta = tree.get_meta(id).unwrap();
5447        meta.insert("a", 123).unwrap();
5448        loro.commit_then_renew();
5449        let meta = tree.get_meta(id).unwrap();
5450        assert_eq!(meta.get("a").unwrap(), 123.into());
5451        assert_eq!(
5452            json!([{"parent":null,"meta":{"a":123},"id":"0@1","index":0,"children":[],"fractional_index":"80"}]),
5453            tree.get_deep_value().to_json_value()
5454        );
5455        let bytes = loro.export(ExportMode::snapshot()).unwrap();
5456        let loro2 = LoroDoc::new();
5457        loro2.import(&bytes).unwrap();
5458    }
5459
5460    #[test]
5461    fn tree_meta_event() {
5462        use std::sync::Arc;
5463        let loro = LoroDoc::new_auto_commit();
5464        let tree = loro.get_tree("root");
5465        let text = loro.get_text("text");
5466
5467        let id = tree.create(TreeParentId::Root).unwrap();
5468        let meta = tree.get_meta(id).unwrap();
5469        meta.insert("a", 1).unwrap();
5470        text.insert(0, "abc", PosType::Unicode).unwrap();
5471        let _id2 = tree.create(TreeParentId::Root).unwrap();
5472        meta.insert("b", 2).unwrap();
5473
5474        let loro2 = LoroDoc::new_auto_commit();
5475        let _g = loro2.subscribe_root(Arc::new(|e| {
5476            println!("{} {:?} ", e.event_meta.by, e.event_meta.diff)
5477        }));
5478        loro2
5479            .import(&loro.export(ExportMode::all_updates()).unwrap())
5480            .unwrap();
5481        assert_eq!(loro.get_deep_value(), loro2.get_deep_value());
5482    }
5483
5484    #[test]
5485    fn richtext_apply_delta() {
5486        let loro = LoroDoc::new_auto_commit();
5487        let text = loro.get_text("text");
5488        text.apply_delta(&[TextDelta::Insert {
5489            insert: "Hello World!".into(),
5490            attributes: None,
5491        }])
5492        .unwrap();
5493        dbg!(text.get_richtext_value());
5494        text.apply_delta(&[
5495            TextDelta::Retain {
5496                retain: 6,
5497                attributes: Some(fx_map!("italic".into() => loro_common::LoroValue::Bool(true))),
5498            },
5499            TextDelta::Insert {
5500                insert: "New ".into(),
5501                attributes: Some(fx_map!("bold".into() => loro_common::LoroValue::Bool(true))),
5502            },
5503        ])
5504        .unwrap();
5505        dbg!(text.get_richtext_value());
5506        loro.commit_then_renew();
5507        assert_eq!(
5508            text.get_richtext_value().to_json_value(),
5509            json!([
5510                {"insert": "Hello ", "attributes": {"italic": true}},
5511                {"insert": "New ", "attributes": {"bold": true}},
5512                {"insert": "World!"}
5513
5514            ])
5515        )
5516    }
5517
5518    #[test]
5519    fn richtext_apply_delta_marks_without_growth() {
5520        let loro = LoroDoc::new_auto_commit();
5521        let text = loro.get_text("text");
5522        text.insert(0, "abc", PosType::Unicode).unwrap();
5523
5524        text.apply_delta(&[TextDelta::Retain {
5525            retain: 3,
5526            attributes: Some(fx_map!("bold".into() => LoroValue::Bool(true))),
5527        }])
5528        .unwrap();
5529        loro.commit_then_renew();
5530
5531        assert_eq!(text.to_string(), "abc");
5532        assert_eq!(
5533            text.get_richtext_value().to_json_value(),
5534            json!([{"insert": "abc", "attributes": {"bold": true}}])
5535        );
5536    }
5537
5538    #[test]
5539    fn richtext_apply_delta_grows_for_mark_gap() {
5540        let loro = LoroDoc::new_auto_commit();
5541        let text = loro.get_text("text");
5542
5543        text.apply_delta(&[TextDelta::Retain {
5544            retain: 1,
5545            attributes: Some(fx_map!("bold".into() => LoroValue::Bool(true))),
5546        }])
5547        .unwrap();
5548        loro.commit_then_renew();
5549
5550        assert_eq!(text.to_string(), "\n");
5551        assert_eq!(
5552            text.get_richtext_value().to_json_value(),
5553            json!([{"insert": "\n", "attributes": {"bold": true}}])
5554        );
5555    }
5556
5557    #[test]
5558    fn richtext_apply_delta_ignores_empty_inserts() {
5559        let loro = LoroDoc::new_auto_commit();
5560        let text = loro.get_text("text");
5561        text.insert(0, "seed", PosType::Unicode).unwrap();
5562
5563        text.apply_delta(&[TextDelta::Insert {
5564            insert: "".into(),
5565            attributes: Some(fx_map!("bold".into() => LoroValue::Bool(true))),
5566        }])
5567        .unwrap();
5568        loro.commit_then_renew();
5569
5570        assert_eq!(text.to_string(), "seed");
5571        assert_eq!(
5572            text.get_richtext_value().to_json_value(),
5573            json!([{"insert": "seed"}])
5574        );
5575    }
5576
5577    #[test]
5578    fn handler_trait_dispatch_reports_attached_container_identity() {
5579        let loro = LoroDoc::new_auto_commit();
5580        let handlers = [
5581            (loro.get_text("text").to_handler(), ContainerType::Text),
5582            (loro.get_map("map").to_handler(), ContainerType::Map),
5583            (loro.get_list("list").to_handler(), ContainerType::List),
5584            (
5585                loro.get_movable_list("movable").to_handler(),
5586                ContainerType::MovableList,
5587            ),
5588            (loro.get_tree("tree").to_handler(), ContainerType::Tree),
5589        ];
5590
5591        for (handler, expected_type) in handlers {
5592            assert!(handler.is_attached());
5593            assert!(handler.attached_handler().is_some());
5594            assert!(handler.doc().is_some());
5595            assert!(handler.get_attached().is_some());
5596            assert_eq!(handler.kind(), expected_type);
5597            assert_eq!(handler.c_type(), expected_type);
5598            assert_eq!(handler.id().container_type(), expected_type);
5599            assert_eq!(
5600                Handler::from_handler(handler.clone()).unwrap().c_type(),
5601                expected_type
5602            );
5603
5604            handler.get_value();
5605            handler.get_deep_value();
5606            handler.clear().unwrap();
5607        }
5608    }
5609
5610    #[test]
5611    fn handler_trait_dispatch_reports_detached_container_identity() {
5612        let handlers = [
5613            (
5614                Handler::new_unattached(ContainerType::Text),
5615                ContainerType::Text,
5616            ),
5617            (
5618                Handler::new_unattached(ContainerType::Map),
5619                ContainerType::Map,
5620            ),
5621            (
5622                Handler::new_unattached(ContainerType::List),
5623                ContainerType::List,
5624            ),
5625            (
5626                Handler::new_unattached(ContainerType::MovableList),
5627                ContainerType::MovableList,
5628            ),
5629            (
5630                Handler::new_unattached(ContainerType::Tree),
5631                ContainerType::Tree,
5632            ),
5633        ];
5634
5635        for (handler, expected_type) in handlers {
5636            assert!(!handler.is_attached());
5637            assert!(handler.attached_handler().is_none());
5638            assert!(handler.doc().is_none());
5639            assert!(handler.get_attached().is_none());
5640            assert_eq!(handler.kind(), expected_type);
5641            assert_eq!(handler.c_type(), expected_type);
5642            assert_eq!(handler.id().container_type(), expected_type);
5643            assert_eq!(handler.idx().get_type(), expected_type);
5644            assert_eq!(
5645                Handler::from_handler(handler.clone()).unwrap().c_type(),
5646                expected_type
5647            );
5648        }
5649    }
5650
5651    #[test]
5652    fn attaching_detached_handlers_sets_parent_and_attached_back_reference() {
5653        let loro = LoroDoc::new_auto_commit();
5654
5655        let map = loro.get_map("map");
5656        let detached_text = TextHandler::new_detached();
5657        detached_text
5658            .insert(0, "detached", PosType::Unicode)
5659            .unwrap();
5660        let attached_text = map.insert_container("text", detached_text.clone()).unwrap();
5661        assert!(attached_text.is_attached());
5662        assert_eq!(attached_text.to_string(), "detached");
5663        assert_eq!(attached_text.parent().unwrap().c_type(), ContainerType::Map);
5664        assert_eq!(
5665            detached_text.get_attached().unwrap().id(),
5666            attached_text.id()
5667        );
5668
5669        let list = loro.get_list("list");
5670        let detached_map = MapHandler::new_detached();
5671        detached_map.insert("k", 1_i64).unwrap();
5672        let attached_map = list.insert_container(0, detached_map.clone()).unwrap();
5673        assert!(attached_map.is_attached());
5674        assert_eq!(attached_map.parent().unwrap().c_type(), ContainerType::List);
5675        assert_eq!(detached_map.get_attached().unwrap().id(), attached_map.id());
5676
5677        let movable = loro.get_movable_list("movable");
5678        let detached_list = ListHandler::new_detached();
5679        detached_list.push("item").unwrap();
5680        let attached_list = movable.insert_container(0, detached_list.clone()).unwrap();
5681        assert!(attached_list.is_attached());
5682        assert_eq!(
5683            attached_list.parent().unwrap().c_type(),
5684            ContainerType::MovableList
5685        );
5686        assert_eq!(
5687            detached_list.get_attached().unwrap().id(),
5688            attached_list.id()
5689        );
5690
5691        let nested = attached_map
5692            .insert_container("movable", MovableListHandler::new_detached())
5693            .unwrap();
5694        assert_eq!(nested.parent().unwrap().id(), attached_map.id());
5695    }
5696
5697    #[test]
5698    fn unknown_handler_reports_identity_without_materializing_value() {
5699        let loro = LoroDoc::new_auto_commit();
5700        let id = ContainerID::Root {
5701            name: "unknown".into(),
5702            container_type: ContainerType::Unknown(7),
5703        };
5704        let handler = Handler::new_attached(id.clone(), loro.clone());
5705        let unknown = handler.as_unknown().unwrap();
5706
5707        assert!(unknown.is_attached());
5708        assert_eq!(unknown.kind(), ContainerType::Unknown(7));
5709        assert_eq!(unknown.id(), id);
5710        assert_eq!(unknown.to_handler().c_type(), ContainerType::Unknown(7));
5711        assert!(unknown.doc().is_some());
5712        assert!(!unknown.is_deleted());
5713        assert_eq!(format!("{unknown:?}"), "UnknownHandler");
5714        assert!(unknown.get_attached().is_some());
5715        assert!(super::UnknownHandler::from_handler(handler).is_some());
5716    }
5717}