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