loro/lib.rs
1#![doc = include_str!("../README.md")]
2#![allow(clippy::uninlined_format_args)]
3#![warn(missing_docs)]
4#![warn(missing_debug_implementations)]
5use event::DiffBatch;
6use event::{DiffEvent, Subscriber};
7pub use loro_common::InternalString;
8pub use loro_internal::cursor::CannotFindRelativePosition;
9use loro_internal::cursor::Cursor;
10use loro_internal::cursor::PosQueryResult;
11use loro_internal::cursor::Side;
12pub use loro_internal::encoding::ImportStatus;
13use loro_internal::handler::{HandlerTrait, ValueOrHandler};
14pub use loro_internal::loro::ChangeTravelError;
15pub use loro_internal::pre_commit::{
16 ChangeModifier, FirstCommitFromPeerCallback, FirstCommitFromPeerPayload, PreCommitCallback,
17 PreCommitCallbackPayload,
18};
19pub use loro_internal::sync;
20pub use loro_internal::undo::{OnPop, UndoItemMeta, UndoOrRedo};
21use loro_internal::version::shrink_frontiers;
22pub use loro_internal::version::ImVersionVector;
23use loro_internal::DocState;
24use loro_internal::LoroDoc as InnerLoroDoc;
25use loro_internal::OpLog;
26use loro_internal::{
27 handler::Handler as InnerHandler, ListHandler as InnerListHandler,
28 MapHandler as InnerMapHandler, MovableListHandler as InnerMovableListHandler,
29 TextHandler as InnerTextHandler, TreeHandler as InnerTreeHandler,
30 UnknownHandler as InnerUnknownHandler,
31};
32use rustc_hash::FxHashSet;
33use std::cmp::Ordering;
34use std::ops::ControlFlow;
35use std::ops::Deref;
36use std::ops::Range;
37use std::sync::Arc;
38use tracing::info;
39
40pub use loro_internal::diff::diff_impl::UpdateOptions;
41pub use loro_internal::diff::diff_impl::UpdateTimeoutError;
42pub use loro_internal::subscription::LocalUpdateCallback;
43pub use loro_internal::subscription::PeerIdUpdateCallback;
44pub use loro_internal::ChangeMeta;
45pub use loro_internal::LORO_VERSION;
46pub mod event;
47pub use loro_internal::awareness;
48pub use loro_internal::change::Timestamp;
49pub use loro_internal::configure::Configure;
50pub use loro_internal::configure::{StyleConfig, StyleConfigMap};
51pub use loro_internal::container::richtext::ExpandType;
52pub use loro_internal::container::{ContainerID, ContainerType, IntoContainerId};
53pub use loro_internal::cursor;
54pub use loro_internal::delta::{TreeDeltaItem, TreeDiff, TreeDiffItem, TreeExternalDiff};
55pub use loro_internal::encoding::ImportBlobMetadata;
56pub use loro_internal::encoding::{EncodedBlobMode, ExportMode};
57pub use loro_internal::event::{EventTriggerKind, Index};
58pub use loro_internal::handler::TextDelta;
59pub use loro_internal::json;
60pub use loro_internal::json::{
61 FutureOp as JsonFutureOp, FutureOpWrapper as JsonFutureOpWrapper, JsonChange, JsonOp,
62 JsonOpContent, JsonSchema, ListOp as JsonListOp, MapOp as JsonMapOp,
63 MovableListOp as JsonMovableListOp, TextOp as JsonTextOp, TreeOp as JsonTreeOp,
64};
65pub use loro_internal::kv_store::{KvStore, MemKvStore};
66pub use loro_internal::loro::CommitOptions;
67pub use loro_internal::loro::DocAnalysis;
68pub use loro_internal::oplog::FrontiersNotIncluded;
69pub use loro_internal::undo;
70pub use loro_internal::version::{Frontiers, VersionRange, VersionVector, VersionVectorDiff};
71pub use loro_internal::ApplyDiff;
72pub use loro_internal::Subscription;
73pub use loro_internal::UndoManager as InnerUndoManager;
74pub use loro_internal::{loro_value, to_value};
75pub use loro_internal::{
76 Counter, CounterSpan, FractionalIndex, IdLp, IdSpan, Lamport, PeerID, TreeID, TreeParentId, ID,
77};
78pub use loro_internal::{
79 LoroBinaryValue, LoroEncodeError, LoroError, LoroListValue, LoroMapValue, LoroResult,
80 LoroStringValue, LoroTreeError, LoroValue, ToJson,
81};
82pub use loro_kv_store as kv_store;
83
84#[cfg(feature = "jsonpath")]
85pub use loro_internal::jsonpath;
86#[cfg(feature = "jsonpath")]
87pub use loro_internal::jsonpath::SubscribeJsonPathCallback;
88
89#[cfg(feature = "counter")]
90mod counter;
91#[cfg(feature = "counter")]
92pub use counter::LoroCounter;
93
94/// `LoroDoc` is the entry for the whole document.
95/// When it's dropped, all the associated [`Container`]s will be invalidated.
96///
97/// **Important:** Loro is a pure library and does not handle network protocols.
98/// It is the responsibility of the user to manage the storage, loading, and synchronization
99/// of the bytes exported by Loro in a manner suitable for their specific environment.
100#[derive(Debug)]
101pub struct LoroDoc {
102 doc: InnerLoroDoc,
103 // This field is here to prevent some weird issues in debug mode
104 #[cfg(debug_assertions)]
105 _temp: u8,
106}
107
108impl Default for LoroDoc {
109 fn default() -> Self {
110 Self::new()
111 }
112}
113
114impl Clone for LoroDoc {
115 /// This creates a reference clone, not a deep clone. The cloned doc will share the same
116 /// underlying doc as the original one.
117 ///
118 /// For deep clone, please use the `.fork()` method.
119 fn clone(&self) -> Self {
120 let doc = self.doc.clone();
121 LoroDoc::_new(doc)
122 }
123}
124
125impl LoroDoc {
126 #[inline(always)]
127 fn _new(doc: InnerLoroDoc) -> Self {
128 Self {
129 doc,
130 #[cfg(debug_assertions)]
131 _temp: 0,
132 }
133 }
134
135 /// Create a new `LoroDoc` instance.
136 #[inline]
137 pub fn new() -> Self {
138 let doc = InnerLoroDoc::default();
139 doc.start_auto_commit();
140
141 LoroDoc::_new(doc)
142 }
143
144 /// Duplicate the document with a different PeerID
145 ///
146 /// The time complexity and space complexity of this operation are both O(n),
147 ///
148 /// When called in detached mode, it will fork at the current state frontiers.
149 /// It will have the same effect as `fork_at(&self.state_frontiers())`.
150 #[inline]
151 pub fn fork(&self) -> Self {
152 let doc = self.doc.fork();
153 LoroDoc::_new(doc)
154 }
155
156 /// Fork the document at the given frontiers.
157 ///
158 /// The created doc will only contain the history before the specified frontiers.
159 pub fn fork_at(&self, frontiers: &Frontiers) -> LoroDoc {
160 let new_doc = self.doc.fork_at(frontiers);
161 new_doc.start_auto_commit();
162 LoroDoc::_new(new_doc)
163 }
164
165 /// Get the configurations of the document.
166 #[inline]
167 pub fn config(&self) -> &Configure {
168 self.doc.config()
169 }
170
171 /// Get `Change` at the given id.
172 ///
173 /// `Change` is a grouped continuous operations that share the same id, timestamp, commit message.
174 ///
175 /// - The id of the `Change` is the id of its first op.
176 /// - The second op's id is `{ peer: change.id.peer, counter: change.id.counter + 1 }`
177 ///
178 /// The same applies on `Lamport`:
179 ///
180 /// - The lamport of the `Change` is the lamport of its first op.
181 /// - The second op's lamport is `change.lamport + 1`
182 ///
183 /// The length of the `Change` is how many operations it contains
184 pub fn get_change(&self, id: ID) -> Option<ChangeMeta> {
185 let change = self.doc.oplog().lock().unwrap().get_change_at(id)?;
186 Some(ChangeMeta::from_change(&change))
187 }
188
189 /// Decodes the metadata for an imported blob from the provided bytes.
190 ///
191 /// # Example
192 /// ```
193 /// use loro::{LoroDoc, ExportMode};
194 ///
195 /// let doc = LoroDoc::new();
196 /// doc.get_text("t").insert(0, "Hello").unwrap();
197 /// let updates = doc.export(ExportMode::all_updates()).unwrap();
198 /// let meta = LoroDoc::decode_import_blob_meta(&updates, true).unwrap();
199 /// assert!(meta.change_num >= 1);
200 /// ```
201 #[inline]
202 pub fn decode_import_blob_meta(
203 bytes: &[u8],
204 check_checksum: bool,
205 ) -> LoroResult<ImportBlobMetadata> {
206 InnerLoroDoc::decode_import_blob_meta(bytes, check_checksum)
207 }
208
209 /// Set whether to record the timestamp of each change. Default is `false`.
210 ///
211 /// If enabled, the Unix timestamp will be recorded for each change automatically.
212 /// You can also set a timestamp explicitly via [`set_next_commit_timestamp`].
213 ///
214 /// Important: this is a runtime configuration. It is not serialized into updates or
215 /// snapshots. You must reapply it for each new `LoroDoc` you create or load.
216 ///
217 /// NOTE: Timestamps are forced to be in ascending order. If you commit a new change with
218 /// a timestamp earlier than the latest, the largest existing timestamp will be used instead.
219 ///
220 /// # Example
221 /// ```
222 /// use loro::LoroDoc;
223 /// let doc = LoroDoc::new();
224 /// doc.set_record_timestamp(true);
225 /// doc.get_text("t").insert(0, "hi").unwrap();
226 /// doc.commit();
227 /// ```
228 #[inline]
229 pub fn set_record_timestamp(&self, record: bool) {
230 self.doc.set_record_timestamp(record);
231 }
232
233 /// Enables editing in detached mode, which is disabled by default.
234 ///
235 /// The doc enter detached mode after calling `detach` or checking out a non-latest version.
236 ///
237 /// # Important Notes:
238 ///
239 /// - This mode uses a different PeerID for each checkout.
240 /// - Ensure no concurrent operations share the same PeerID if set manually.
241 /// - Importing does not affect the document's state or version; changes are
242 /// recorded in the [OpLog] only. Call `checkout` to apply changes.
243 ///
244 /// # Example
245 /// ```
246 /// use loro::LoroDoc;
247 ///
248 /// let doc = LoroDoc::new();
249 /// let v0 = doc.state_frontiers();
250 /// // Make some edits…
251 /// doc.get_text("t").insert(0, "Hello").unwrap();
252 /// doc.commit();
253 ///
254 /// // Travel back and enable detached editing
255 /// doc.checkout(&v0).unwrap();
256 /// assert!(doc.is_detached());
257 /// doc.set_detached_editing(true);
258 /// doc.get_text("t").insert(0, "old").unwrap();
259 /// // Later, re-attach to see latest again
260 /// doc.attach();
261 /// ```
262 #[inline]
263 pub fn set_detached_editing(&self, enable: bool) {
264 self.doc.set_detached_editing(enable);
265 }
266
267 /// Whether editing the doc in detached mode is allowed, which is disabled by
268 /// default.
269 ///
270 /// The doc enter detached mode after calling `detach` or checking out a non-latest version.
271 ///
272 /// # Important Notes:
273 ///
274 /// - This mode uses a different PeerID for each checkout.
275 /// - Ensure no concurrent operations share the same PeerID if set manually.
276 /// - Importing does not affect the document's state or version; changes are
277 /// recorded in the [OpLog] only. Call `checkout` to apply changes.
278 #[inline]
279 pub fn is_detached_editing_enabled(&self) -> bool {
280 self.doc.is_detached_editing_enabled()
281 }
282
283 /// Set the interval of mergeable changes, **in seconds**.
284 ///
285 /// If two continuous local changes are within the interval, they will be merged into one change.
286 /// The default value is 1000 seconds.
287 ///
288 /// By default, we record timestamps in seconds for each change. So if the merge interval is 1, and changes A and B
289 /// have timestamps of 3 and 4 respectively, then they will be merged into one change.
290 #[inline]
291 pub fn set_change_merge_interval(&self, interval: i64) {
292 self.doc.set_change_merge_interval(interval);
293 }
294
295 /// Set the rich text format configuration of the document.
296 ///
297 /// Configure the `expand` behavior for marks used by [`LoroText::mark`]/[`LoroText::unmark`].
298 /// This controls how marks grow when text is inserted at their boundaries.
299 ///
300 /// - `after` (default): inserts just after the range expand the mark
301 /// - `before`: inserts just before the range expand the mark
302 /// - `both`: inserts on either side expand the mark
303 /// - `none`: do not expand at boundaries
304 ///
305 /// # Example
306 /// ```
307 /// use loro::{LoroDoc, StyleConfigMap, StyleConfig, ExpandType};
308 /// let doc = LoroDoc::new();
309 /// let mut styles = StyleConfigMap::new();
310 /// styles.insert("bold".into(), StyleConfig { expand: ExpandType::After });
311 /// doc.config_text_style(styles);
312 /// ```
313 #[inline]
314 pub fn config_text_style(&self, text_style: StyleConfigMap) {
315 self.doc.config_text_style(text_style)
316 }
317
318 /// Configures the default text style for the document.
319 ///
320 /// This method sets the default text style configuration for the document when using LoroText.
321 /// If `None` is provided, the default style is reset.
322 ///
323 /// # Parameters
324 ///
325 /// - `text_style`: The style configuration to set as the default. `None` to reset.
326 ///
327 /// # Example
328 /// ```
329 /// use loro::{LoroDoc, StyleConfig, ExpandType};
330 /// let doc = LoroDoc::new();
331 /// doc.config_default_text_style(Some(StyleConfig { expand: ExpandType::After }));
332 /// ```
333 pub fn config_default_text_style(&self, text_style: Option<StyleConfig>) {
334 self.doc.config_default_text_style(text_style);
335 }
336
337 /// Attach the document state to the latest known version.
338 ///
339 /// > The document becomes detached during a `checkout` operation.
340 /// > Being `detached` implies that the `DocState` is not synchronized with the latest version of the `OpLog`.
341 /// > In a detached state, the document is not editable, and any `import` operations will be
342 /// > recorded in the `OpLog` without being applied to the `DocState`.
343 #[inline]
344 pub fn attach(&self) {
345 self.doc.attach()
346 }
347
348 /// Checkout the `DocState` to a specific version.
349 ///
350 /// The document becomes detached during a `checkout` operation.
351 /// Being `detached` implies that the `DocState` is not synchronized with the latest version of the `OpLog`.
352 /// In a detached state, the document is not editable, and any `import` operations will be
353 /// recorded in the `OpLog` without being applied to the `DocState`.
354 ///
355 /// You should call `attach` (or `checkout_to_latest`) to reattach the `DocState` to the latest version of `OpLog`.
356 /// If you need to edit while detached, enable [`set_detached_editing(true)`], but note it uses a different
357 /// PeerID per checkout.
358 #[inline]
359 pub fn checkout(&self, frontiers: &Frontiers) -> LoroResult<()> {
360 self.doc.checkout(frontiers)
361 }
362
363 /// Checkout the `DocState` to the latest version.
364 ///
365 /// > The document becomes detached during a `checkout` operation.
366 /// > Being `detached` implies that the `DocState` is not synchronized with the latest version of the `OpLog`.
367 /// > In a detached state, the document is not editable, and any `import` operations will be
368 /// > recorded in the `OpLog` without being applied to the `DocState`.
369 ///
370 /// This has the same effect as `attach`.
371 #[inline]
372 pub fn checkout_to_latest(&self) {
373 self.doc.checkout_to_latest()
374 }
375
376 /// Compare the frontiers with the current OpLog's version.
377 ///
378 /// If `other` contains any version that's not contained in the current OpLog, return [Ordering::Less].
379 #[inline]
380 pub fn cmp_with_frontiers(&self, other: &Frontiers) -> Ordering {
381 self.doc.cmp_with_frontiers(other)
382 }
383
384 /// Compare two frontiers.
385 ///
386 /// If the frontiers are not included in the document, return [`FrontiersNotIncluded`].
387 #[inline]
388 pub fn cmp_frontiers(
389 &self,
390 a: &Frontiers,
391 b: &Frontiers,
392 ) -> Result<Option<Ordering>, FrontiersNotIncluded> {
393 self.doc.cmp_frontiers(a, b)
394 }
395
396 /// Force the document enter the detached mode.
397 ///
398 /// In this mode, importing new updates only records them in the OpLog; the [loro_internal::DocState] is not updated until you reattach.
399 ///
400 /// Learn more at https://loro.dev/docs/advanced/doc_state_and_oplog#attacheddetached-status
401 #[inline]
402 pub fn detach(&self) {
403 self.doc.detach()
404 }
405
406 /// Import a batch of updates/snapshot.
407 ///
408 /// The data can be in arbitrary order. The import result will be the same.
409 /// Auto-commit: same as [`import`], this finalizes the current transaction first.
410 ///
411 /// # Example
412 /// ```
413 /// use loro::{LoroDoc, ExportMode};
414 /// let a = LoroDoc::new();
415 /// a.get_text("t").insert(0, "A").unwrap();
416 /// let u1 = a.export(ExportMode::all_updates()).unwrap();
417 /// a.get_text("t").insert(1, "B").unwrap();
418 /// let u2 = a.export(ExportMode::all_updates()).unwrap();
419 ///
420 /// let b = LoroDoc::new();
421 /// let status = b.import_batch(&[u2, u1]).unwrap(); // arbitrary order
422 /// assert!(status.pending.is_none());
423 /// ```
424 #[inline]
425 pub fn import_batch(&self, bytes: &[Vec<u8>]) -> LoroResult<ImportStatus> {
426 self.doc.import_batch(bytes)
427 }
428
429 /// Get a [Container] by container id.
430 #[inline]
431 pub fn get_container(&self, id: ContainerID) -> Option<Container> {
432 self.doc.get_handler(id).map(Container::from_handler)
433 }
434
435 /// Get a [LoroMovableList] by container id.
436 ///
437 /// If the provided id is string, it will be converted into a root container id with the name of the string.
438 #[inline]
439 pub fn get_movable_list<I: IntoContainerId>(&self, id: I) -> LoroMovableList {
440 LoroMovableList {
441 handler: self.doc.get_movable_list(id),
442 }
443 }
444
445 /// Get a [LoroList] by container id.
446 ///
447 /// If the provided id is string, it will be converted into a root container id with the name of the string.
448 /// Note: creating/accessing a root container does not record history; creating nested
449 /// containers (e.g., `Map::insert_container`) does.
450 #[inline]
451 pub fn get_list<I: IntoContainerId>(&self, id: I) -> LoroList {
452 LoroList {
453 handler: self.doc.get_list(id),
454 }
455 }
456
457 /// Get a [LoroMap] by container id.
458 ///
459 /// If the provided id is string, it will be converted into a root container id with the name of the string.
460 /// Note: creating/accessing a root container does not record history; creating nested
461 /// containers (e.g., `Map::insert_container`) does.
462 #[inline]
463 pub fn get_map<I: IntoContainerId>(&self, id: I) -> LoroMap {
464 LoroMap {
465 handler: self.doc.get_map(id),
466 }
467 }
468
469 /// Get a [LoroText] by container id.
470 ///
471 /// If the provided id is string, it will be converted into a root container id with the name of the string.
472 /// Note: creating/accessing a root container does not record history; creating nested
473 /// containers (e.g., `Map::insert_container`) does.
474 #[inline]
475 pub fn get_text<I: IntoContainerId>(&self, id: I) -> LoroText {
476 LoroText {
477 handler: self.doc.get_text(id),
478 }
479 }
480
481 /// Get a [LoroTree] by container id.
482 ///
483 /// If the provided id is string, it will be converted into a root container id with the name of the string.
484 /// Note: creating/accessing a root container does not record history; creating nested
485 /// containers (e.g., `Map::insert_container`) does.
486 #[inline]
487 pub fn get_tree<I: IntoContainerId>(&self, id: I) -> LoroTree {
488 LoroTree {
489 handler: self.doc.get_tree(id),
490 }
491 }
492
493 #[cfg(feature = "counter")]
494 /// Get a [LoroCounter] by container id.
495 ///
496 /// If the provided id is string, it will be converted into a root container id with the name of the string.
497 #[inline]
498 pub fn get_counter<I: IntoContainerId>(&self, id: I) -> LoroCounter {
499 LoroCounter {
500 handler: self.doc.get_counter(id),
501 }
502 }
503
504 /// Commit the cumulative auto commit transaction.
505 ///
506 /// There is a transaction behind every operation.
507 /// The events will be emitted after a transaction is committed. A transaction is committed when:
508 ///
509 /// - `doc.commit()` is called.
510 /// - `doc.export(mode)` is called.
511 /// - `doc.import(data)` is called.
512 /// - `doc.checkout(version)` is called.
513 ///
514 /// Note: Loro transactions are not ACID database transactions. There is no rollback or
515 /// isolation; they are a grouping mechanism for events/history. For interactive undo/redo,
516 /// use [`UndoManager`].
517 ///
518 /// Empty-commit behavior: this method is an explicit commit. If the pending
519 /// transaction is empty, any previously set next-commit options (message/timestamp/origin)
520 /// are swallowed and will not carry over.
521 #[inline]
522 pub fn commit(&self) {
523 self.doc.commit_then_renew();
524 }
525
526 /// Commit the cumulative auto commit transaction with custom options.
527 ///
528 /// There is a transaction behind every operation.
529 /// It will automatically commit when users invoke export or import.
530 /// The event will be sent after a transaction is committed
531 ///
532 /// See also: [`set_next_commit_message`], [`set_next_commit_origin`],
533 /// [`set_next_commit_timestamp`]. Commit messages are persisted and replicate to peers;
534 /// origins are local-only metadata.
535 ///
536 /// Empty-commit behavior: this method is an explicit commit. If the pending
537 /// transaction is empty, the provided options are swallowed and will not carry over.
538 /// For implicit commits triggered by `export`/`checkout` (commit barriers),
539 /// message/timestamp/origin from an empty transaction are preserved for the next commit.
540 #[inline]
541 pub fn commit_with(&self, options: CommitOptions) {
542 self.doc.commit_with(options);
543 }
544
545 /// Set commit message for the current uncommitted changes
546 ///
547 /// It will be persisted.
548 pub fn set_next_commit_message(&self, msg: &str) {
549 self.doc.set_next_commit_message(msg)
550 }
551
552 /// Set `origin` for the current uncommitted changes, it can be used to track the source of changes in an event.
553 ///
554 /// It will NOT be persisted.
555 pub fn set_next_commit_origin(&self, origin: &str) {
556 self.doc.set_next_commit_origin(origin)
557 }
558
559 /// Set the timestamp of the next commit.
560 ///
561 /// It will be persisted and stored in the `OpLog`.
562 /// You can get the timestamp from the [`Change`] type.
563 pub fn set_next_commit_timestamp(&self, timestamp: Timestamp) {
564 self.doc.set_next_commit_timestamp(timestamp)
565 }
566
567 /// Set the options of the next commit.
568 ///
569 /// It will be used when the next commit is performed.
570 ///
571 /// # Example
572 /// ```
573 /// use loro::{LoroDoc, CommitOptions};
574 /// let doc = LoroDoc::new();
575 /// doc.set_next_commit_options(CommitOptions::new().origin("ui").commit_msg("tagged"));
576 /// doc.get_text("t").insert(0, "x").unwrap();
577 /// doc.commit();
578 /// ```
579 pub fn set_next_commit_options(&self, options: CommitOptions) {
580 self.doc.set_next_commit_options(options);
581 }
582
583 /// Clear the options of the next commit.
584 pub fn clear_next_commit_options(&self) {
585 self.doc.clear_next_commit_options();
586 }
587
588 /// Whether the document is in detached mode, where the [loro_internal::DocState] is not
589 /// synchronized with the latest version of the [loro_internal::OpLog].
590 #[inline]
591 pub fn is_detached(&self) -> bool {
592 self.doc.is_detached()
593 }
594
595 /// Create a new `LoroDoc` from a snapshot.
596 ///
597 /// The snapshot is created via [`LoroDoc::export`] with [`ExportMode::Snapshot`].
598 ///
599 /// # Example
600 /// ```
601 /// use loro::{LoroDoc, ExportMode};
602 ///
603 /// let doc = LoroDoc::new();
604 /// let text = doc.get_text("text");
605 /// text.insert(0, "Hello").unwrap();
606 /// let snapshot = doc.export(ExportMode::Snapshot).unwrap();
607 ///
608 /// let restored = LoroDoc::from_snapshot(&snapshot).unwrap();
609 /// assert_eq!(restored.get_deep_value(), doc.get_deep_value());
610 /// ```
611 pub fn from_snapshot(bytes: &[u8]) -> LoroResult<Self> {
612 let inner = InnerLoroDoc::from_snapshot(bytes)?;
613 inner.start_auto_commit();
614 Ok(Self::_new(inner))
615 }
616
617 /// Import data exported by [`LoroDoc::export`].
618 ///
619 /// Use [`ExportMode::Snapshot`] for full-state snapshots, or
620 /// [`ExportMode::all_updates`] / [`ExportMode::updates`] for updates.
621 ///
622 /// # Example
623 /// ```
624 /// use loro::{LoroDoc, ExportMode};
625 ///
626 /// let a = LoroDoc::new();
627 /// a.get_text("text").insert(0, "Hello").unwrap();
628 /// let updates = a.export(ExportMode::all_updates()).unwrap();
629 ///
630 /// let b = LoroDoc::new();
631 /// b.import(&updates).unwrap();
632 /// assert_eq!(a.get_deep_value(), b.get_deep_value());
633 /// ```
634 /// Pitfalls:
635 /// - Missing dependencies: check the returned [`ImportStatus`]. If `pending` is non-empty,
636 /// fetch those missing ranges (e.g., using `export(ExportMode::updates(&doc.oplog_vv()))`) and re-import.
637 /// - Auto-commit: `import` finalizes the current transaction before applying incoming data.
638 #[inline]
639 pub fn import(&self, bytes: &[u8]) -> Result<ImportStatus, LoroError> {
640 self.doc.import_with(bytes, "".into())
641 }
642
643 /// Import data exported by [`LoroDoc::export`] and mark it with a custom origin.
644 ///
645 /// The `origin` string will be attached to the ensuing change event, which is handy
646 /// for telemetry or filtering.
647 /// Pitfalls:
648 /// - Same as [`import`]: verify `ImportStatus.pending` and fetch dependencies if needed.
649 #[inline]
650 pub fn import_with(&self, bytes: &[u8], origin: &str) -> Result<ImportStatus, LoroError> {
651 self.doc.import_with(bytes, origin.into())
652 }
653
654 /// Import the json schema updates.
655 ///
656 /// # Example
657 /// ```
658 /// use loro::{LoroDoc, VersionVector};
659 /// let a = LoroDoc::new();
660 /// a.get_text("t").insert(0, "hi").unwrap();
661 /// a.commit();
662 /// let json = a.export_json_updates(&VersionVector::default(), &a.oplog_vv());
663 ///
664 /// let b = LoroDoc::new();
665 /// b.import_json_updates(json).unwrap();
666 /// assert_eq!(a.get_deep_value(), b.get_deep_value());
667 /// ```
668 #[inline]
669 pub fn import_json_updates<T: TryInto<JsonSchema>>(
670 &self,
671 json: T,
672 ) -> Result<ImportStatus, LoroError> {
673 self.doc.import_json_updates(json)
674 }
675
676 /// Export the current state with json-string format of the document.
677 ///
678 /// # Example
679 /// ```
680 /// use loro::{LoroDoc, VersionVector};
681 /// let doc = LoroDoc::new();
682 /// let start = VersionVector::default();
683 /// let end = doc.oplog_vv();
684 /// let json = doc.export_json_updates(&start, &end);
685 /// ```
686 #[inline]
687 pub fn export_json_updates(
688 &self,
689 start_vv: &VersionVector,
690 end_vv: &VersionVector,
691 ) -> JsonSchema {
692 self.doc.export_json_updates(start_vv, end_vv, true)
693 }
694
695 /// Export the current state with json-string format of the document, without peer compression.
696 ///
697 /// Compared to [`export_json_updates`], this method does not compress the peer IDs in the updates.
698 /// So the operations are easier to be processed by application code.
699 ///
700 /// # Example
701 /// ```
702 /// use loro::{LoroDoc, VersionVector};
703 /// let doc = LoroDoc::new();
704 /// let start = VersionVector::default();
705 /// let end = doc.oplog_vv();
706 /// let json = doc.export_json_updates_without_peer_compression(&start, &end);
707 /// ```
708 #[inline]
709 pub fn export_json_updates_without_peer_compression(
710 &self,
711 start_vv: &VersionVector,
712 end_vv: &VersionVector,
713 ) -> JsonSchema {
714 self.doc.export_json_updates(start_vv, end_vv, false)
715 }
716
717 /// Exports changes within the specified ID span to JSON schema format.
718 ///
719 /// The JSON schema format produced by this method is identical to the one generated by `export_json_updates`.
720 /// It ensures deterministic output, making it ideal for hash calculations and integrity checks.
721 ///
722 /// This method can also export pending changes from the uncommitted transaction that have not yet been applied to the OpLog.
723 ///
724 /// This method will NOT trigger a new commit implicitly.
725 ///
726 /// # Example
727 /// ```
728 /// use loro::{LoroDoc, IdSpan};
729 ///
730 /// let doc = LoroDoc::new();
731 /// doc.set_peer_id(0).unwrap();
732 /// doc.get_text("text").insert(0, "a").unwrap();
733 /// doc.commit();
734 /// let doc_clone = doc.clone();
735 /// let _sub = doc.subscribe_pre_commit(Box::new(move |e| {
736 /// let changes = doc_clone.export_json_in_id_span(IdSpan::new(
737 /// 0,
738 /// 0,
739 /// e.change_meta.id.counter + e.change_meta.len as i32,
740 /// ));
741 /// // 2 because commit one and the uncommit one
742 /// assert_eq!(changes.len(), 2);
743 /// true
744 /// }));
745 /// doc.get_text("text").insert(0, "b").unwrap();
746 /// let changes = doc.export_json_in_id_span(IdSpan::new(0, 0, 2));
747 /// assert_eq!(changes.len(), 1);
748 /// doc.commit();
749 /// // change merged
750 /// assert_eq!(changes.len(), 1);
751 /// ```
752 pub fn export_json_in_id_span(&self, id_span: IdSpan) -> Vec<JsonChange> {
753 self.doc.export_json_in_id_span(id_span)
754 }
755
756 /// Convert `Frontiers` into `VersionVector`
757 ///
758 /// Returns `None` if the frontiers are not included by this doc's OpLog.
759 ///
760 /// # Example
761 /// ```
762 /// use loro::LoroDoc;
763 /// let doc = LoroDoc::new();
764 /// let f = doc.state_frontiers();
765 /// let vv = doc.frontiers_to_vv(&f);
766 /// assert!(vv.is_some());
767 /// ```
768 #[inline]
769 pub fn frontiers_to_vv(&self, frontiers: &Frontiers) -> Option<VersionVector> {
770 self.doc.frontiers_to_vv(frontiers)
771 }
772
773 /// Minimize the frontiers by removing the unnecessary entries.
774 ///
775 /// Returns `Err(ID)` if any frontier is not included by this doc's history.
776 ///
777 /// # Example
778 /// ```
779 /// use loro::LoroDoc;
780 /// let doc = LoroDoc::new();
781 /// let f = doc.state_frontiers();
782 /// let _minimized = doc.minimize_frontiers(&f).unwrap();
783 /// ```
784 pub fn minimize_frontiers(&self, frontiers: &Frontiers) -> Result<Frontiers, ID> {
785 self.with_oplog(|oplog| shrink_frontiers(frontiers, oplog.dag()))
786 }
787
788 /// Convert `VersionVector` into `Frontiers`
789 #[inline]
790 pub fn vv_to_frontiers(&self, vv: &VersionVector) -> Frontiers {
791 self.doc.vv_to_frontiers(vv)
792 }
793
794 /// Access the `OpLog`.
795 ///
796 /// NOTE: The API in `OpLog` is unstable. Keep the closure short; avoid calling methods
797 /// that might re-enter the document while holding the lock.
798 #[inline]
799 pub fn with_oplog<R>(&self, f: impl FnOnce(&OpLog) -> R) -> R {
800 let oplog = self.doc.oplog().lock().unwrap();
801 f(&oplog)
802 }
803
804 /// Access the `DocState`.
805 ///
806 /// NOTE: The API in `DocState` is unstable. Keep the closure short; avoid calling methods
807 /// that might re-enter the document while holding the lock.
808 #[inline]
809 pub fn with_state<R>(&self, f: impl FnOnce(&mut DocState) -> R) -> R {
810 let mut state = self.doc.app_state().lock().unwrap();
811 f(&mut state)
812 }
813
814 /// Get the `VersionVector` version of `OpLog`
815 #[inline]
816 pub fn oplog_vv(&self) -> VersionVector {
817 self.doc.oplog_vv()
818 }
819
820 /// Get the `VersionVector` version of `DocState`
821 #[inline]
822 pub fn state_vv(&self) -> VersionVector {
823 self.doc.state_vv()
824 }
825
826 /// The doc only contains the history since this version
827 ///
828 /// This is empty if the doc is not shallow.
829 ///
830 /// The ops included by the shallow history start version vector are not in the doc.
831 #[inline]
832 pub fn shallow_since_vv(&self) -> ImVersionVector {
833 self.doc.shallow_since_vv()
834 }
835
836 /// The doc only contains the history since this version
837 ///
838 /// This is empty if the doc is not shallow.
839 ///
840 /// The ops included by the shallow history start frontiers are not in the doc.
841 #[inline]
842 pub fn shallow_since_frontiers(&self) -> Frontiers {
843 self.doc.shallow_since_frontiers()
844 }
845
846 /// Get the total number of operations in the `OpLog`
847 #[inline]
848 pub fn len_ops(&self) -> usize {
849 self.doc.len_ops()
850 }
851
852 /// Get the total number of changes in the `OpLog`
853 #[inline]
854 pub fn len_changes(&self) -> usize {
855 self.doc.len_changes()
856 }
857
858 /// Get the shallow value of the document.
859 #[inline]
860 pub fn get_value(&self) -> LoroValue {
861 self.doc.get_value()
862 }
863
864 /// Get the entire state of the current DocState
865 #[inline]
866 pub fn get_deep_value(&self) -> LoroValue {
867 self.doc.get_deep_value()
868 }
869
870 /// Get the entire state of the current DocState with container id
871 pub fn get_deep_value_with_id(&self) -> LoroValue {
872 self.doc
873 .app_state()
874 .lock()
875 .unwrap()
876 .get_deep_value_with_id()
877 }
878
879 /// Get the `Frontiers` version of `OpLog`.
880 #[inline]
881 pub fn oplog_frontiers(&self) -> Frontiers {
882 self.doc.oplog_frontiers()
883 }
884
885 /// Get the `Frontiers` version of `DocState`.
886 ///
887 /// When detached or during checkout, `state_frontiers()` may differ from `oplog_frontiers()`.
888 /// Learn more about [`Frontiers`](https://loro.dev/docs/advanced/version_deep_dive).
889 ///
890 /// # Example
891 /// ```
892 /// use loro::LoroDoc;
893 /// let doc = LoroDoc::new();
894 /// let before = doc.state_frontiers();
895 /// doc.get_text("t").insert(0, "x").unwrap();
896 /// let after = doc.state_frontiers();
897 /// assert_ne!(before, after);
898 /// ```
899 #[inline]
900 pub fn state_frontiers(&self) -> Frontiers {
901 self.doc.state_frontiers()
902 }
903
904 /// Get the PeerID
905 #[inline]
906 pub fn peer_id(&self) -> PeerID {
907 self.doc.peer_id()
908 }
909
910 /// Change the PeerID
911 ///
912 /// Pitfalls:
913 /// - Never reuse the same PeerID across concurrent writers (multiple tabs/devices). Duplicate
914 /// PeerIDs can produce conflicting OpIDs and corrupt the document.
915 /// - Do not assign a fixed PeerID to a user or device without strict single-ownership locking.
916 /// Prefer the default random PeerID per process/session.
917 #[inline]
918 pub fn set_peer_id(&self, peer: PeerID) -> LoroResult<()> {
919 self.doc.set_peer_id(peer)
920 }
921
922 /// Subscribe the events of a container.
923 ///
924 /// The callback will be invoked after a transaction that change the container.
925 /// Returns a subscription that can be used to unsubscribe.
926 ///
927 /// The events will be emitted after a transaction is committed. A transaction is committed when:
928 ///
929 /// - `doc.commit()` is called.
930 /// - `doc.export(mode)` is called.
931 /// - `doc.import(data)` is called.
932 /// - `doc.checkout(version)` is called.
933 ///
934 /// # Example
935 ///
936 /// ```
937 /// # use loro::LoroDoc;
938 /// # use loro::ContainerTrait;
939 /// # use std::sync::{atomic::AtomicBool, Arc};
940 /// # use loro::{event::DiffEvent, LoroResult, TextDelta};
941 /// #
942 /// let doc = LoroDoc::new();
943 /// let text = doc.get_text("text");
944 /// let ran = Arc::new(AtomicBool::new(false));
945 /// let ran2 = ran.clone();
946 /// let sub = doc.subscribe(
947 /// &text.id(),
948 /// Arc::new(move |event| {
949 /// assert!(event.triggered_by.is_local());
950 /// for event in event.events {
951 /// let delta = event.diff.as_text().unwrap();
952 /// let d = TextDelta::Insert {
953 /// insert: "123".into(),
954 /// attributes: Default::default(),
955 /// };
956 /// assert_eq!(delta, &vec![d]);
957 /// ran2.store(true, std::sync::atomic::Ordering::Relaxed);
958 /// }
959 /// }),
960 /// );
961 /// text.insert(0, "123").unwrap();
962 /// doc.commit();
963 /// assert!(ran.load(std::sync::atomic::Ordering::Relaxed));
964 /// // unsubscribe
965 /// sub.unsubscribe();
966 /// ```
967 #[inline]
968 pub fn subscribe(&self, container_id: &ContainerID, callback: Subscriber) -> Subscription {
969 self.doc.subscribe(
970 container_id,
971 Arc::new(move |e| {
972 callback(DiffEvent::from(e));
973 }),
974 )
975 }
976
977 /// Subscribe all the events.
978 ///
979 /// The callback will be invoked when any part of the [loro_internal::DocState] is changed.
980 /// Returns a subscription that can be used to unsubscribe.
981 ///
982 /// The events will be emitted after a transaction is committed. A transaction is committed when:
983 ///
984 /// - `doc.commit()` is called.
985 /// - `doc.export(mode)` is called.
986 /// - `doc.import(data)` is called.
987 /// - `doc.checkout(version)` is called.
988 #[inline]
989 pub fn subscribe_root(&self, callback: Subscriber) -> Subscription {
990 // self.doc.subscribe_root(callback)
991 self.doc.subscribe_root(Arc::new(move |e| {
992 callback(DiffEvent::from(e));
993 }))
994 }
995
996 /// Subscribe to local document updates.
997 ///
998 /// The callback receives encoded update bytes whenever local changes are committed.
999 /// This is useful for syncing changes to other document instances or persisting updates.
1000 ///
1001 /// **Auto-unsubscription**: If the callback returns `false`, the subscription will be
1002 /// automatically removed, providing a convenient way to implement one-time or conditional
1003 /// subscriptions in Rust.
1004 ///
1005 /// # Parameters
1006 /// - `callback`: Function that receives `&Vec<u8>` (encoded updates) and returns `bool`
1007 /// - Return `true` to keep the subscription active
1008 /// - Return `false` to automatically unsubscribe
1009 ///
1010 /// # Example
1011 /// ```rust
1012 /// use loro::LoroDoc;
1013 /// use std::sync::{Arc, Mutex};
1014 ///
1015 /// let doc = LoroDoc::new();
1016 /// let updates = Arc::new(Mutex::new(Vec::new()));
1017 /// let updates_clone = updates.clone();
1018 /// let count = Arc::new(Mutex::new(0));
1019 /// let count_clone = count.clone();
1020 ///
1021 /// // Subscribe and collect first 3 updates, then auto-unsubscribe
1022 /// let sub = doc.subscribe_local_update(Box::new(move |bytes| {
1023 /// updates_clone.lock().unwrap().push(bytes.clone());
1024 /// let mut c = count_clone.lock().unwrap();
1025 /// *c += 1;
1026 /// *c < 3 // Auto-unsubscribe after 3 updates
1027 /// }));
1028 ///
1029 /// doc.get_text("text").insert(0, "hello").unwrap();
1030 /// doc.commit();
1031 /// ```
1032 pub fn subscribe_local_update(&self, callback: LocalUpdateCallback) -> Subscription {
1033 self.doc.subscribe_local_update(callback)
1034 }
1035
1036 /// Subscribe to peer ID changes in the document.
1037 ///
1038 /// The callback is triggered whenever the document's peer ID is modified.
1039 /// This is useful for tracking identity changes and updating related state accordingly.
1040 ///
1041 /// **Auto-unsubscription**: If the callback returns `false`, the subscription will be
1042 /// automatically removed, providing a convenient way to implement one-time or conditional
1043 /// subscriptions in Rust.
1044 ///
1045 /// # Parameters
1046 /// - `callback`: Function that receives `&ID` (the new peer ID) and returns `bool`
1047 /// - Return `true` to keep the subscription active
1048 /// - Return `false` to automatically unsubscribe
1049 ///
1050 /// # Example
1051 /// ```rust
1052 /// use loro::LoroDoc;
1053 /// use std::sync::{Arc, Mutex};
1054 ///
1055 /// let doc = LoroDoc::new();
1056 /// let peer_changes = Arc::new(Mutex::new(Vec::new()));
1057 /// let changes_clone = peer_changes.clone();
1058 ///
1059 /// let sub = doc.subscribe_peer_id_change(Box::new(move |new_peer_id| {
1060 /// changes_clone.lock().unwrap().push(*new_peer_id);
1061 /// true // Keep subscription active
1062 /// }));
1063 ///
1064 /// doc.set_peer_id(42).unwrap();
1065 /// doc.set_peer_id(100).unwrap();
1066 /// ```
1067 pub fn subscribe_peer_id_change(&self, callback: PeerIdUpdateCallback) -> Subscription {
1068 self.doc.subscribe_peer_id_change(callback)
1069 }
1070
1071 /// Check the correctness of the document state by comparing it with the state
1072 /// calculated by applying all the history.
1073 #[inline]
1074 pub fn check_state_correctness_slow(&self) {
1075 self.doc.check_state_diff_calc_consistency_slow()
1076 }
1077
1078 /// Get the handler by the path.
1079 #[inline]
1080 pub fn get_by_path(&self, path: &[Index]) -> Option<ValueOrContainer> {
1081 self.doc.get_by_path(path).map(ValueOrContainer::from)
1082 }
1083
1084 /// Get the handler by the string path.
1085 ///
1086 /// The path can be specified in different ways depending on the container type:
1087 ///
1088 /// For Tree:
1089 /// 1. Using node IDs: `tree/{node_id}/property`
1090 /// 2. Using indices: `tree/0/1/property`
1091 ///
1092 /// For List and MovableList:
1093 /// - Using indices: `list/0` or `list/1/property`
1094 ///
1095 /// For Map:
1096 /// - Using keys: `map/key` or `map/nested/property`
1097 ///
1098 /// For tree structures, index-based paths follow depth-first traversal order.
1099 /// The indices start from 0 and represent the position of a node among its siblings.
1100 ///
1101 /// # Examples
1102 /// ```
1103 /// # use loro::{LoroDoc, LoroValue};
1104 /// let doc = LoroDoc::new();
1105 ///
1106 /// // Tree example
1107 /// let tree = doc.get_tree("tree");
1108 /// let root = tree.create(None).unwrap();
1109 /// tree.get_meta(root).unwrap().insert("name", "root").unwrap();
1110 /// // Access tree by ID or index
1111 /// let name1 = doc.get_by_str_path(&format!("tree/{}/name", root)).unwrap().into_value().unwrap();
1112 /// let name2 = doc.get_by_str_path("tree/0/name").unwrap().into_value().unwrap();
1113 /// assert_eq!(name1, name2);
1114 ///
1115 /// // List example
1116 /// let list = doc.get_list("list");
1117 /// list.insert(0, "first").unwrap();
1118 /// list.insert(1, "second").unwrap();
1119 /// // Access list by index
1120 /// let item = doc.get_by_str_path("list/0");
1121 /// assert_eq!(item.unwrap().into_value().unwrap().into_string().unwrap(), "first".into());
1122 ///
1123 /// // Map example
1124 /// let map = doc.get_map("map");
1125 /// map.insert("key", "value").unwrap();
1126 /// // Access map by key
1127 /// let value = doc.get_by_str_path("map/key");
1128 /// assert_eq!(value.unwrap().into_value().unwrap().into_string().unwrap(), "value".into());
1129 ///
1130 /// // MovableList example
1131 /// let mlist = doc.get_movable_list("mlist");
1132 /// mlist.insert(0, "item").unwrap();
1133 /// // Access movable list by index
1134 /// let item = doc.get_by_str_path("mlist/0");
1135 /// assert_eq!(item.unwrap().into_value().unwrap().into_string().unwrap(), "item".into());
1136 /// ```
1137 #[inline]
1138 pub fn get_by_str_path(&self, path: &str) -> Option<ValueOrContainer> {
1139 self.doc.get_by_str_path(path).map(ValueOrContainer::from)
1140 }
1141
1142 /// Get the absolute position of the given cursor.
1143 ///
1144 /// # Example
1145 ///
1146 /// ```
1147 /// # use loro::{LoroDoc, ToJson};
1148 /// let doc = LoroDoc::new();
1149 /// let text = &doc.get_text("text");
1150 /// text.insert(0, "01234").unwrap();
1151 /// let pos = text.get_cursor(5, Default::default()).unwrap();
1152 /// assert_eq!(doc.get_cursor_pos(&pos).unwrap().current.pos, 5);
1153 /// text.insert(0, "01234").unwrap();
1154 /// assert_eq!(doc.get_cursor_pos(&pos).unwrap().current.pos, 10);
1155 /// text.delete(0, 10).unwrap();
1156 /// assert_eq!(doc.get_cursor_pos(&pos).unwrap().current.pos, 0);
1157 /// text.insert(0, "01234").unwrap();
1158 /// assert_eq!(doc.get_cursor_pos(&pos).unwrap().current.pos, 5);
1159 /// ```
1160 #[inline]
1161 pub fn get_cursor_pos(
1162 &self,
1163 cursor: &Cursor,
1164 ) -> Result<PosQueryResult, CannotFindRelativePosition> {
1165 self.doc.query_pos(cursor)
1166 }
1167
1168 /// Get the inner LoroDoc ref.
1169 #[inline]
1170 pub fn inner(&self) -> &InnerLoroDoc {
1171 &self.doc
1172 }
1173
1174 /// Whether the history cache is built.
1175 #[inline]
1176 pub fn has_history_cache(&self) -> bool {
1177 self.doc.has_history_cache()
1178 }
1179
1180 /// Free the history cache that is used for making checkout faster.
1181 ///
1182 /// If you use checkout that switching to an old/concurrent version, the history cache will be built.
1183 /// You can free it by calling this method.
1184 #[inline]
1185 pub fn free_history_cache(&self) {
1186 self.doc.free_history_cache()
1187 }
1188
1189 /// Free the cached diff calculator that is used for checkout.
1190 #[inline]
1191 pub fn free_diff_calculator(&self) {
1192 self.doc.free_diff_calculator()
1193 }
1194
1195 /// Encoded all ops and history cache to bytes and store them in the kv store.
1196 ///
1197 /// This will free up the memory that used by parsed ops
1198 #[inline]
1199 pub fn compact_change_store(&self) {
1200 self.doc.compact_change_store()
1201 }
1202
1203 /// Export the document in the given mode.
1204 ///
1205 /// Common modes:
1206 /// - [`ExportMode::Snapshot`]: full state + history
1207 /// - [`ExportMode::all_updates()`]: all known ops
1208 /// - [`ExportMode::updates(&VersionVector)`]: ops since a specific version
1209 /// - [`ExportMode::shallow_snapshot(..)`]: GC’d snapshot starting at frontiers
1210 /// - [`ExportMode::updates_in_range(..)`]: ops in specific ID spans
1211 ///
1212 /// Important notes:
1213 /// - Auto-commit: `export` finalizes the current transaction before producing bytes.
1214 /// - Shallow snapshots: peers cannot import updates from before the shallow start.
1215 /// - Performance: exporting fresh snapshots periodically can reduce import time for new peers.
1216 ///
1217 /// # Examples
1218 /// ```
1219 /// use loro::{ExportMode, LoroDoc};
1220 ///
1221 /// let doc = LoroDoc::new();
1222 /// doc.get_text("text").insert(0, "Hello").unwrap();
1223 ///
1224 /// // 1) Full snapshot
1225 /// let snapshot = doc.export(ExportMode::Snapshot).unwrap();
1226 ///
1227 /// // 2) All updates
1228 /// let all = doc.export(ExportMode::all_updates()).unwrap();
1229 ///
1230 /// // 3) Updates from another peer’s version vector
1231 /// let vv = doc.oplog_vv();
1232 /// let delta = doc.export(ExportMode::updates(&vv)).unwrap();
1233 /// assert!(!delta.is_empty());
1234 /// ```
1235 pub fn export(&self, mode: ExportMode) -> Result<Vec<u8>, LoroEncodeError> {
1236 self.doc.export(mode)
1237 }
1238
1239 /// Analyze the container info of the doc
1240 ///
1241 /// This is used for development and debugging. It can be slow.
1242 pub fn analyze(&self) -> DocAnalysis {
1243 self.doc.analyze()
1244 }
1245
1246 /// Get the path from the root to the container
1247 pub fn get_path_to_container(&self, id: &ContainerID) -> Option<Vec<(ContainerID, Index)>> {
1248 self.doc.get_path_to_container(id)
1249 }
1250
1251 /// Evaluate a JSONPath expression on the document and return matching values or handlers.
1252 ///
1253 /// This method allows querying the document structure using JSONPath syntax.
1254 /// It returns a vector of `ValueOrHandler` which can represent either primitive values
1255 /// or container handlers, depending on what the JSONPath expression matches.
1256 ///
1257 /// # Arguments
1258 ///
1259 /// * `path` - A string slice containing the JSONPath expression to evaluate.
1260 ///
1261 /// # Returns
1262 ///
1263 /// A `Result` containing either:
1264 /// - `Ok(Vec<ValueOrHandler>)`: A vector of matching values or handlers.
1265 /// - `Err(String)`: An error message if the JSONPath expression is invalid or evaluation fails.
1266 ///
1267 /// # Example
1268 ///
1269 /// ```
1270 /// # use loro::{LoroDoc, ToJson};
1271 ///
1272 /// let doc = LoroDoc::new();
1273 /// let map = doc.get_map("users");
1274 /// map.insert("alice", 30).unwrap();
1275 /// map.insert("bob", 25).unwrap();
1276 ///
1277 /// let result = doc.jsonpath("$.users.alice").unwrap();
1278 /// assert_eq!(result.len(), 1);
1279 /// assert_eq!(result[0].as_value().unwrap().to_json_value(), serde_json::json!(30));
1280 /// ```
1281 #[inline]
1282 #[cfg(feature = "jsonpath")]
1283 pub fn jsonpath(&self, path: &str) -> Result<Vec<ValueOrContainer>, JsonPathError> {
1284 self.doc
1285 .jsonpath(path)
1286 .map(|vec| vec.into_iter().map(ValueOrContainer::from).collect())
1287 }
1288
1289 /// Subscribe to updates that might affect the given JSONPath query.
1290 ///
1291 /// The callback:
1292 /// - may fire **false positives** (never false negatives) to stay lightweight;
1293 /// - does **not** include the query result so the caller can debounce/throttle
1294 /// and run JSONPath themselves if desired;
1295 /// - can be debounced/throttled before executing an expensive JSONPath read.
1296 #[cfg(feature = "jsonpath")]
1297 pub fn subscribe_jsonpath(
1298 &self,
1299 jsonpath: &str,
1300 callback: SubscribeJsonPathCallback,
1301 ) -> LoroResult<Subscription> {
1302 self.doc.subscribe_jsonpath(jsonpath, callback)
1303 }
1304
1305 /// Get the number of operations in the pending transaction.
1306 ///
1307 /// The pending transaction is the one that is not committed yet. It will be committed
1308 /// after calling `doc.commit()`, `doc.export(mode)` or `doc.checkout(version)`.
1309 pub fn get_pending_txn_len(&self) -> usize {
1310 self.doc.get_pending_txn_len()
1311 }
1312
1313 /// Traverses the ancestors of the Change containing the given ID, including itself.
1314 ///
1315 /// This method visits all ancestors in causal order, from the latest to the oldest,
1316 /// based on their Lamport timestamps.
1317 ///
1318 /// # Arguments
1319 ///
1320 /// * `ids` - The IDs of the Change to start the traversal from.
1321 /// * `f` - A mutable function that is called for each ancestor. It can return `ControlFlow::Break(())` to stop the traversal.
1322 pub fn travel_change_ancestors(
1323 &self,
1324 ids: &[ID],
1325 f: &mut dyn FnMut(ChangeMeta) -> ControlFlow<()>,
1326 ) -> Result<(), ChangeTravelError> {
1327 self.doc.travel_change_ancestors(ids, f)
1328 }
1329
1330 /// Check if the doc contains the full history.
1331 pub fn is_shallow(&self) -> bool {
1332 self.doc.is_shallow()
1333 }
1334
1335 /// Gets container IDs modified in the given ID range.
1336 ///
1337 /// Pitfalls:
1338 /// - This method will implicitly commit the current transaction to ensure the change range is finalized.
1339 ///
1340 /// This method can be used in conjunction with `doc.travel_change_ancestors()` to traverse
1341 /// the history and identify all changes that affected specific containers.
1342 ///
1343 /// # Arguments
1344 ///
1345 /// * `id` - The starting ID of the change range
1346 /// * `len` - The length of the change range to check
1347 pub fn get_changed_containers_in(&self, id: ID, len: usize) -> FxHashSet<ContainerID> {
1348 self.doc.get_changed_containers_in(id, len)
1349 }
1350
1351 /// Find the operation id spans that between the `from` version and the `to` version.
1352 ///
1353 /// Useful for exporting just the changes in a range, e.g., in response to a subscription.
1354 ///
1355 /// # Example
1356 /// ```
1357 /// use loro::LoroDoc;
1358 /// let doc = LoroDoc::new();
1359 /// let a = doc.state_frontiers();
1360 /// doc.get_text("t").insert(0, "x").unwrap();
1361 /// doc.commit();
1362 /// let b = doc.state_frontiers();
1363 /// let spans = doc.find_id_spans_between(&a, &b);
1364 /// assert!(!spans.forward.is_empty());
1365 /// ```
1366 #[inline]
1367 pub fn find_id_spans_between(&self, from: &Frontiers, to: &Frontiers) -> VersionVectorDiff {
1368 self.doc.find_id_spans_between(from, to)
1369 }
1370
1371 /// Revert the current document state back to the target version
1372 ///
1373 /// Internally, it will generate a series of local operations that can revert the
1374 /// current doc to the target version. It will calculate the diff between the current
1375 /// state and the target state, and apply the diff to the current state.
1376 ///
1377 /// Pitfalls:
1378 /// - The target frontiers must be included by the document's history. If the document
1379 /// is shallow and the target is before the shallow start, revert will fail.
1380 ///
1381 /// # Example
1382 /// ```
1383 /// use loro::LoroDoc;
1384 /// let doc = LoroDoc::new();
1385 /// let t = doc.get_text("text");
1386 /// t.insert(0, "Hello").unwrap();
1387 /// let v0 = doc.state_frontiers();
1388 /// t.insert(5, ", world").unwrap();
1389 /// doc.commit();
1390 /// doc.revert_to(&v0).unwrap();
1391 /// assert_eq!(t.to_string(), "Hello");
1392 /// ```
1393 #[inline]
1394 pub fn revert_to(&self, version: &Frontiers) -> LoroResult<()> {
1395 self.doc.revert_to(version)
1396 }
1397
1398 /// Apply a diff to the current document state.
1399 ///
1400 /// Internally, it will apply the diff to the current state.
1401 #[inline]
1402 pub fn apply_diff(&self, diff: DiffBatch) -> LoroResult<()> {
1403 self.doc.apply_diff(diff.into())
1404 }
1405
1406 /// Calculate the diff between two versions.
1407 ///
1408 /// # Example
1409 /// ```
1410 /// use loro::{LoroDoc};
1411 /// let doc = LoroDoc::new();
1412 /// let t = doc.get_text("text");
1413 /// let a = doc.state_frontiers();
1414 /// t.insert(0, "a").unwrap();
1415 /// let b = doc.state_frontiers();
1416 /// let diff = doc.diff(&a, &b).unwrap();
1417 /// assert!(diff.iter().next().is_some());
1418 /// ```
1419 #[inline]
1420 pub fn diff(&self, a: &Frontiers, b: &Frontiers) -> LoroResult<DiffBatch> {
1421 self.doc.diff(a, b).map(|x| x.into())
1422 }
1423
1424 /// Check if the doc contains the target container.
1425 ///
1426 /// A root container always exists, while a normal container exists
1427 /// if it has ever been created on the doc.
1428 ///
1429 /// # Examples
1430 /// ```
1431 /// use loro::{LoroDoc, LoroText, LoroList, ExportMode};
1432 ///
1433 /// let doc = LoroDoc::new();
1434 /// doc.set_peer_id(1);
1435 /// let map = doc.get_map("map");
1436 /// map.insert_container("text", LoroText::new()).unwrap();
1437 /// map.insert_container("list", LoroList::new()).unwrap();
1438 ///
1439 /// // Root map container exists
1440 /// assert!(doc.has_container(&"cid:root-map:Map".try_into().unwrap()));
1441 /// // Text container exists
1442 /// assert!(doc.has_container(&"cid:0@1:Text".try_into().unwrap()));
1443 /// // List container exists
1444 /// assert!(doc.has_container(&"cid:1@1:List".try_into().unwrap()));
1445 ///
1446 /// let doc2 = LoroDoc::new();
1447 /// // Containers exist as long as the history or doc state includes them
1448 /// doc.detach();
1449 /// doc2.import(&doc.export(ExportMode::all_updates()).unwrap()).unwrap();
1450 /// assert!(doc2.has_container(&"cid:root-map:Map".try_into().unwrap()));
1451 /// assert!(doc2.has_container(&"cid:0@1:Text".try_into().unwrap()));
1452 /// assert!(doc2.has_container(&"cid:1@1:List".try_into().unwrap()));
1453 /// ```
1454 pub fn has_container(&self, container_id: &ContainerID) -> bool {
1455 self.doc.has_container(container_id)
1456 }
1457
1458 /// Subscribe to the first commit from a peer. Operations performed on the `LoroDoc` within this callback
1459 /// will be merged into the current commit.
1460 ///
1461 /// Subscribe to the first commit event from each peer.
1462 ///
1463 /// The callback is triggered only once per peer when they make their first commit to the document locally.
1464 /// This is particularly useful for managing peer-to-user mappings or initialization logic.
1465 ///
1466 /// **Auto-unsubscription**: If the callback returns `false`, the subscription will be
1467 /// automatically removed, providing a convenient way to implement one-time or conditional
1468 /// subscriptions in Rust.
1469 ///
1470 /// # Parameters
1471 /// - `callback`: Function that receives `&FirstCommitFromPeerPayload` and returns `bool`
1472 /// - Return `true` to keep the subscription active
1473 /// - Return `false` to automatically unsubscribe
1474 ///
1475 /// # Use Cases
1476 /// - Initialize peer-specific data structures
1477 /// - Map peer IDs to user information
1478 ///
1479 /// # Example
1480 /// ```rust
1481 /// use loro::LoroDoc;
1482 /// use std::sync::{Arc, Mutex};
1483 ///
1484 /// let doc = LoroDoc::new();
1485 /// doc.set_peer_id(0).unwrap();
1486 ///
1487 /// let new_peers = Arc::new(Mutex::new(Vec::new()));
1488 /// let peers_clone = new_peers.clone();
1489 /// let peer_count = Arc::new(Mutex::new(0));
1490 /// let count_clone = peer_count.clone();
1491 ///
1492 /// // Track first 5 new peers, then auto-unsubscribe
1493 /// let sub = doc.subscribe_first_commit_from_peer(Box::new(move |payload| {
1494 /// peers_clone.lock().unwrap().push(payload.peer);
1495 /// let mut count = count_clone.lock().unwrap();
1496 /// *count += 1;
1497 /// *count < 5 // Auto-unsubscribe after tracking 5 peers
1498 /// }));
1499 ///
1500 /// // This will trigger the callback for peer 0
1501 /// doc.get_text("text").insert(0, "hello").unwrap();
1502 /// doc.commit();
1503 ///
1504 /// // Switch to a new peer and commit - triggers callback again
1505 /// doc.set_peer_id(1).unwrap();
1506 /// doc.get_text("text").insert(0, "world").unwrap();
1507 /// doc.commit();
1508 /// ```
1509 pub fn subscribe_first_commit_from_peer(
1510 &self,
1511 callback: FirstCommitFromPeerCallback,
1512 ) -> Subscription {
1513 self.doc.subscribe_first_commit_from_peer(callback)
1514 }
1515
1516 /// Subscribe to pre-commit events.
1517 ///
1518 /// The callback is triggered when changes are about to be committed but before they're
1519 /// applied to the OpLog. This allows you to modify commit metadata such as timestamps
1520 /// and messages, or perform validation before changes are finalized.
1521 ///
1522 /// **Auto-unsubscription**: If the callback returns `false`, the subscription will be
1523 /// automatically removed, providing a convenient way to implement one-time or conditional
1524 /// subscriptions in Rust.
1525 ///
1526 /// Pitfall: `commit()` can be triggered implicitly by `import`, `export`, and `checkout`.
1527 /// This hook still runs for those commits, which is helpful for annotating metadata
1528 /// even for implicit commits.
1529 ///
1530 /// # Parameters
1531 /// - `callback`: Function that receives `&PreCommitCallbackPayload` and returns `bool`
1532 /// - Return `true` to keep the subscription active
1533 /// - Return `false` to automatically unsubscribe
1534 /// - The payload contains:
1535 /// - `change_meta`: Metadata about the commit
1536 /// - `modifier`: Interface to modify commit properties
1537 ///
1538 /// # Use Cases
1539 /// - Add commit message prefixes or formatting
1540 /// - Adjust timestamps for consistent ordering
1541 /// - Log or audit commit operations
1542 /// - Implement commit validation or approval workflows
1543 ///
1544 /// # Example
1545 /// ```rust
1546 /// use loro::LoroDoc;
1547 /// use std::sync::{Arc, Mutex};
1548 ///
1549 /// let doc = LoroDoc::new();
1550 /// let commit_count = Arc::new(Mutex::new(0));
1551 /// let count_clone = commit_count.clone();
1552 ///
1553 /// // Add timestamps and auto-unsubscribe after 5 commits
1554 /// let sub = doc.subscribe_pre_commit(Box::new(move |payload| {
1555 /// // Add a prefix to commit messages
1556 /// let new_message = format!("Auto: {}", payload.change_meta.message());
1557 /// payload.modifier.set_message(&new_message);
1558 ///
1559 /// let mut count = count_clone.lock().unwrap();
1560 /// *count += 1;
1561 /// *count < 5 // Auto-unsubscribe after 5 commits
1562 /// }));
1563 ///
1564 /// doc.get_text("text").insert(0, "hello").unwrap();
1565 /// doc.commit();
1566 /// ```
1567 pub fn subscribe_pre_commit(&self, callback: PreCommitCallback) -> Subscription {
1568 self.doc.subscribe_pre_commit(callback)
1569 }
1570
1571 /// Delete all content from a root container and hide it from the document.
1572 ///
1573 /// When a root container is empty and hidden:
1574 /// - It won't show up in `get_deep_value()` results
1575 /// - It won't be included in document snapshots
1576 ///
1577 /// Only works on root containers (containers without parents).
1578 pub fn delete_root_container(&self, cid: ContainerID) {
1579 self.doc.delete_root_container(cid);
1580 }
1581
1582 /// Set whether to hide empty root containers.
1583 ///
1584 /// # Example
1585 /// ```
1586 /// use loro::LoroDoc;
1587 ///
1588 /// let doc = LoroDoc::new();
1589 /// let map = doc.get_map("map");
1590 /// dbg!(doc.get_deep_value()); // {"map": {}}
1591 /// doc.set_hide_empty_root_containers(true);
1592 /// dbg!(doc.get_deep_value()); // {}
1593 /// ```
1594 pub fn set_hide_empty_root_containers(&self, hide: bool) {
1595 self.doc.set_hide_empty_root_containers(hide);
1596 }
1597}
1598
1599/// It's used to prevent the user from implementing the trait directly.
1600#[allow(private_bounds)]
1601trait SealedTrait {}
1602
1603/// The common trait for all the containers.
1604/// It's used internally, you can't implement it directly.
1605#[allow(private_bounds)]
1606pub trait ContainerTrait: SealedTrait {
1607 /// The handler of the container.
1608 type Handler: HandlerTrait;
1609 /// Get the ID of the container.
1610 fn id(&self) -> ContainerID;
1611 /// Convert the container to a [Container].
1612 fn to_container(&self) -> Container;
1613 /// Convert the container to a handler.
1614 fn to_handler(&self) -> Self::Handler;
1615 /// Convert the handler to a container.
1616 fn from_handler(handler: Self::Handler) -> Self;
1617 /// Try to convert the container to the handler.
1618 fn try_from_container(container: Container) -> Option<Self>
1619 where
1620 Self: Sized;
1621 /// Whether the container is attached to a document.
1622 fn is_attached(&self) -> bool;
1623 /// If a detached container is attached, this method will return its corresponding attached handler.
1624 fn get_attached(&self) -> Option<Self>
1625 where
1626 Self: Sized;
1627 /// Whether the container is deleted.
1628 fn is_deleted(&self) -> bool;
1629 /// Get the doc of the container.
1630 fn doc(&self) -> Option<LoroDoc>;
1631 /// Subscribe to the container.
1632 ///
1633 /// If the Container is detached, this method will return `None`.
1634 fn subscribe(&self, callback: Subscriber) -> Option<Subscription> {
1635 self.doc().map(|doc| doc.subscribe(&self.id(), callback))
1636 }
1637}
1638
1639/// LoroList container. It's used to model arrays.
1640///
1641/// It can have sub containers.
1642///
1643/// Important: choose the right structure.
1644/// - Use `LoroList` for ordered collections where elements are appended/inserted/deleted.
1645/// - Use `LoroMovableList` when frequent reordering (drag-and-drop) is needed.
1646/// - Use `LoroMap` for keyed records and coordinates.
1647///
1648/// ```no_run
1649/// // Bad: coordinates in a list can diverge under concurrency
1650/// // let coord = doc.get_list("coord");
1651/// // coord.insert(0, 10).unwrap(); // x
1652/// // coord.insert(1, 20).unwrap(); // y
1653///
1654/// // Good: use a map for labeled fields
1655/// // let coord = doc.get_map("coord");
1656/// // coord.insert("x", 10).unwrap();
1657/// // coord.insert("y", 20).unwrap();
1658/// ```
1659///
1660/// ```
1661/// # use loro::{LoroDoc, ContainerType, ToJson};
1662/// # use serde_json::json;
1663/// let doc = LoroDoc::new();
1664/// let list = doc.get_list("list");
1665/// list.insert(0, 123).unwrap();
1666/// list.insert(1, "h").unwrap();
1667/// assert_eq!(
1668/// doc.get_deep_value().to_json_value(),
1669/// json!({
1670/// "list": [123, "h"]
1671/// })
1672/// );
1673/// ```
1674#[derive(Clone, Debug)]
1675pub struct LoroList {
1676 handler: InnerListHandler,
1677}
1678
1679impl SealedTrait for LoroList {}
1680impl ContainerTrait for LoroList {
1681 type Handler = InnerListHandler;
1682
1683 fn id(&self) -> ContainerID {
1684 self.handler.id()
1685 }
1686
1687 fn to_container(&self) -> Container {
1688 Container::List(self.clone())
1689 }
1690
1691 fn to_handler(&self) -> Self::Handler {
1692 self.handler.clone()
1693 }
1694
1695 fn from_handler(handler: Self::Handler) -> Self {
1696 Self { handler }
1697 }
1698
1699 fn is_attached(&self) -> bool {
1700 self.handler.is_attached()
1701 }
1702
1703 fn get_attached(&self) -> Option<Self> {
1704 self.handler.get_attached().map(Self::from_handler)
1705 }
1706
1707 fn try_from_container(container: Container) -> Option<Self> {
1708 container.into_list().ok()
1709 }
1710
1711 fn is_deleted(&self) -> bool {
1712 self.handler.is_deleted()
1713 }
1714
1715 fn doc(&self) -> Option<LoroDoc> {
1716 self.handler.doc().map(|doc| {
1717 doc.start_auto_commit();
1718 LoroDoc::_new(doc)
1719 })
1720 }
1721}
1722
1723impl LoroList {
1724 /// Create a new container that is detached from the document.
1725 ///
1726 /// The edits on a detached container will not be persisted.
1727 /// To attach the container to the document, please insert it into an attached container.
1728 pub fn new() -> Self {
1729 Self {
1730 handler: InnerListHandler::new_detached(),
1731 }
1732 }
1733
1734 /// Whether the container is attached to a document
1735 ///
1736 /// The edits on a detached container will not be persisted.
1737 /// To attach the container to the document, please insert it into an attached container.
1738 pub fn is_attached(&self) -> bool {
1739 self.handler.is_attached()
1740 }
1741
1742 /// Insert a value at the given position.
1743 pub fn insert(&self, pos: usize, v: impl Into<LoroValue>) -> LoroResult<()> {
1744 self.handler.insert(pos, v)
1745 }
1746
1747 /// Delete values at the given position.
1748 #[inline]
1749 pub fn delete(&self, pos: usize, len: usize) -> LoroResult<()> {
1750 self.handler.delete(pos, len)
1751 }
1752
1753 /// Get the value at the given position.
1754 #[inline]
1755 pub fn get(&self, index: usize) -> Option<ValueOrContainer> {
1756 self.handler.get_(index).map(ValueOrContainer::from)
1757 }
1758
1759 /// Get the deep value of the container.
1760 #[inline]
1761 pub fn get_deep_value(&self) -> LoroValue {
1762 self.handler.get_deep_value()
1763 }
1764
1765 /// Get the shallow value of the container.
1766 ///
1767 /// This does not convert the state of sub-containers; instead, it represents them as [LoroValue::Container].
1768 #[inline]
1769 pub fn get_value(&self) -> LoroValue {
1770 self.handler.get_value()
1771 }
1772
1773 /// Pop the last element of the list.
1774 #[inline]
1775 pub fn pop(&self) -> LoroResult<Option<LoroValue>> {
1776 self.handler.pop()
1777 }
1778
1779 /// Push a value to the list.
1780 #[inline]
1781 pub fn push(&self, v: impl Into<LoroValue>) -> LoroResult<()> {
1782 self.handler.push(v.into())
1783 }
1784
1785 /// Push a container to the list.
1786 #[inline]
1787 pub fn push_container<C: ContainerTrait>(&self, child: C) -> LoroResult<C> {
1788 let pos = self.handler.len();
1789 Ok(C::from_handler(
1790 self.handler.insert_container(pos, child.to_handler())?,
1791 ))
1792 }
1793
1794 /// Iterate over the elements of the list.
1795 pub fn for_each<I>(&self, mut f: I)
1796 where
1797 I: FnMut(ValueOrContainer),
1798 {
1799 self.handler.for_each(&mut |v| {
1800 f(ValueOrContainer::from(v));
1801 })
1802 }
1803
1804 /// Get the length of the list.
1805 #[inline]
1806 pub fn len(&self) -> usize {
1807 self.handler.len()
1808 }
1809
1810 /// Whether the list is empty.
1811 #[inline]
1812 pub fn is_empty(&self) -> bool {
1813 self.handler.is_empty()
1814 }
1815
1816 /// Insert a container with the given type at the given index.
1817 ///
1818 /// # Example
1819 ///
1820 /// ```
1821 /// # use loro::{LoroDoc, ContainerType, LoroText, ToJson};
1822 /// # use serde_json::json;
1823 /// let doc = LoroDoc::new();
1824 /// let list = doc.get_list("m");
1825 /// let text = list.insert_container(0, LoroText::new()).unwrap();
1826 /// text.insert(0, "12");
1827 /// text.insert(0, "0");
1828 /// assert_eq!(doc.get_deep_value().to_json_value(), json!({"m": ["012"]}));
1829 /// ```
1830 #[inline]
1831 pub fn insert_container<C: ContainerTrait>(&self, pos: usize, child: C) -> LoroResult<C> {
1832 Ok(C::from_handler(
1833 self.handler.insert_container(pos, child.to_handler())?,
1834 ))
1835 }
1836
1837 /// Get the cursor at the given position.
1838 ///
1839 /// Using "index" to denote cursor positions can be unstable, as positions may
1840 /// shift with document edits. To reliably represent a position or range within
1841 /// a document, it is more effective to leverage the unique ID of each item/character
1842 /// in a List CRDT or Text CRDT.
1843 ///
1844 /// Loro optimizes State metadata by not storing the IDs of deleted elements. This
1845 /// approach complicates tracking cursors since they rely on these IDs. The solution
1846 /// recalculates position by replaying relevant history to update stable positions
1847 /// accurately. To minimize the performance impact of history replay, the system
1848 /// updates cursor info to reference only the IDs of currently present elements,
1849 /// thereby reducing the need for replay.
1850 ///
1851 /// # Example
1852 ///
1853 /// ```
1854 /// use loro::LoroDoc;
1855 /// use loro_internal::cursor::Side;
1856 ///
1857 /// let doc = LoroDoc::new();
1858 /// let list = doc.get_list("list");
1859 /// list.insert(0, 0).unwrap();
1860 /// let cursor = list.get_cursor(0, Side::Middle).unwrap();
1861 /// assert_eq!(doc.get_cursor_pos(&cursor).unwrap().current.pos, 0);
1862 /// list.insert(0, 0).unwrap();
1863 /// assert_eq!(doc.get_cursor_pos(&cursor).unwrap().current.pos, 1);
1864 /// list.insert(0, 0).unwrap();
1865 /// list.insert(0, 0).unwrap();
1866 /// assert_eq!(doc.get_cursor_pos(&cursor).unwrap().current.pos, 3);
1867 /// list.insert(4, 0).unwrap();
1868 /// assert_eq!(doc.get_cursor_pos(&cursor).unwrap().current.pos, 3);
1869 /// ```
1870 pub fn get_cursor(&self, pos: usize, side: Side) -> Option<Cursor> {
1871 self.handler.get_cursor(pos, side)
1872 }
1873
1874 /// Converts the LoroList to a Vec of LoroValue.
1875 ///
1876 /// This method unwraps the internal Arc and clones the data if necessary,
1877 /// returning a Vec containing all the elements of the LoroList as LoroValue.
1878 ///
1879 /// # Returns
1880 ///
1881 /// A Vec<LoroValue> containing all elements of the LoroList.
1882 ///
1883 /// # Example
1884 ///
1885 /// ```
1886 /// use loro::{LoroDoc, LoroValue};
1887 ///
1888 /// let doc = LoroDoc::new();
1889 /// let list = doc.get_list("my_list");
1890 /// list.insert(0, 1).unwrap();
1891 /// list.insert(1, "hello").unwrap();
1892 /// list.insert(2, true).unwrap();
1893 ///
1894 /// let vec = list.to_vec();
1895 /// ```
1896 pub fn to_vec(&self) -> Vec<LoroValue> {
1897 self.get_value().into_list().unwrap().unwrap()
1898 }
1899
1900 /// Delete all elements in the list.
1901 pub fn clear(&self) -> LoroResult<()> {
1902 self.handler.clear()
1903 }
1904
1905 /// Get the ID of the list item at the given position.
1906 pub fn get_id_at(&self, pos: usize) -> Option<ID> {
1907 self.handler.get_id_at(pos)
1908 }
1909}
1910
1911impl Default for LoroList {
1912 fn default() -> Self {
1913 Self::new()
1914 }
1915}
1916
1917/// LoroMap container.
1918///
1919/// It's LWW(Last-Write-Win) Map. It can support Multi-Value Map in the future.
1920///
1921/// # Example
1922/// ```
1923/// # use loro::{LoroDoc, ToJson, ExpandType, LoroText, LoroValue};
1924/// # use serde_json::json;
1925/// let doc = LoroDoc::new();
1926/// let map = doc.get_map("map");
1927/// map.insert("key", "value").unwrap();
1928/// map.insert("true", true).unwrap();
1929/// map.insert("null", LoroValue::Null).unwrap();
1930/// map.insert("deleted", LoroValue::Null).unwrap();
1931/// map.delete("deleted").unwrap();
1932/// let text = map
1933/// .insert_container("text", LoroText::new()).unwrap();
1934/// text.insert(0, "Hello world!").unwrap();
1935/// assert_eq!(
1936/// doc.get_deep_value().to_json_value(),
1937/// json!({
1938/// "map": {
1939/// "key": "value",
1940/// "true": true,
1941/// "null": null,
1942/// "text": "Hello world!"
1943/// }
1944/// })
1945/// );
1946/// ```
1947#[derive(Clone, Debug)]
1948pub struct LoroMap {
1949 handler: InnerMapHandler,
1950}
1951
1952impl SealedTrait for LoroMap {}
1953impl ContainerTrait for LoroMap {
1954 type Handler = InnerMapHandler;
1955
1956 fn id(&self) -> ContainerID {
1957 self.handler.id()
1958 }
1959
1960 fn to_container(&self) -> Container {
1961 Container::Map(self.clone())
1962 }
1963
1964 fn to_handler(&self) -> Self::Handler {
1965 self.handler.clone()
1966 }
1967
1968 fn from_handler(handler: Self::Handler) -> Self {
1969 Self { handler }
1970 }
1971
1972 fn is_attached(&self) -> bool {
1973 self.handler.is_attached()
1974 }
1975
1976 fn get_attached(&self) -> Option<Self> {
1977 self.handler.get_attached().map(Self::from_handler)
1978 }
1979
1980 fn try_from_container(container: Container) -> Option<Self> {
1981 container.into_map().ok()
1982 }
1983
1984 fn is_deleted(&self) -> bool {
1985 self.handler.is_deleted()
1986 }
1987
1988 fn doc(&self) -> Option<LoroDoc> {
1989 self.handler.doc().map(|doc| {
1990 doc.start_auto_commit();
1991 LoroDoc::_new(doc)
1992 })
1993 }
1994}
1995
1996impl LoroMap {
1997 /// Create a new container that is detached from the document.
1998 ///
1999 /// The edits on a detached container will not be persisted.
2000 /// To attach the container to the document, please insert it into an attached container.
2001 pub fn new() -> Self {
2002 Self {
2003 handler: InnerMapHandler::new_detached(),
2004 }
2005 }
2006
2007 /// Whether the container is attached to a document.
2008 pub fn is_attached(&self) -> bool {
2009 self.handler.is_attached()
2010 }
2011
2012 /// Delete a key-value pair from the map.
2013 pub fn delete(&self, key: &str) -> LoroResult<()> {
2014 self.handler.delete(key)
2015 }
2016
2017 /// Iterate over the key-value pairs of the map.
2018 pub fn for_each<I>(&self, mut f: I)
2019 where
2020 I: FnMut(&str, ValueOrContainer),
2021 {
2022 self.handler.for_each(|k, v| {
2023 f(k, ValueOrContainer::from(v));
2024 })
2025 }
2026
2027 /// Insert a key-value pair into the map.
2028 ///
2029 /// > **Note**: When calling `map.set(key, value)` on a LoroMap, if `map.get(key)` already returns `value`,
2030 /// > the operation will be a no-op (no operation recorded) to avoid unnecessary updates.
2031 pub fn insert(&self, key: &str, value: impl Into<LoroValue>) -> LoroResult<()> {
2032 self.handler.insert(key, value)
2033 }
2034
2035 /// Get the length of the map.
2036 pub fn len(&self) -> usize {
2037 self.handler.len()
2038 }
2039
2040 /// Whether the map is empty.
2041 pub fn is_empty(&self) -> bool {
2042 self.handler.is_empty()
2043 }
2044
2045 /// Get the value of the map with the given key.
2046 pub fn get(&self, key: &str) -> Option<ValueOrContainer> {
2047 self.handler.get_(key).map(ValueOrContainer::from)
2048 }
2049
2050 /// Insert a container with the given type at the given key.
2051 ///
2052 /// # Example
2053 ///
2054 /// ```
2055 /// # use loro::{LoroDoc, LoroText, ContainerType, ToJson};
2056 /// # use serde_json::json;
2057 /// let doc = LoroDoc::new();
2058 /// let map = doc.get_map("m");
2059 /// let text = map.insert_container("t", LoroText::new()).unwrap();
2060 /// text.insert(0, "12");
2061 /// text.insert(0, "0");
2062 /// assert_eq!(doc.get_deep_value().to_json_value(), json!({"m": {"t": "012"}}));
2063 /// ```
2064 ///
2065 /// Pitfalls:
2066 /// - Concurrently inserting different containers at the same map key on different peers
2067 /// can result in one overwriting the other rather than merging. Prefer initializing
2068 /// heavy/primary child containers when initializing the map.
2069 pub fn insert_container<C: ContainerTrait>(&self, key: &str, child: C) -> LoroResult<C> {
2070 Ok(C::from_handler(
2071 self.handler.insert_container(key, child.to_handler())?,
2072 ))
2073 }
2074
2075 /// Get the shallow value of the map.
2076 ///
2077 /// It will not convert the state of sub-containers, but represent them as [LoroValue::Container].
2078 pub fn get_value(&self) -> LoroValue {
2079 self.handler.get_value()
2080 }
2081
2082 /// Get the deep value of the map.
2083 ///
2084 /// It will convert the state of sub-containers into a nested JSON value.
2085 pub fn get_deep_value(&self) -> LoroValue {
2086 self.handler.get_deep_value()
2087 }
2088
2089 /// Get or create a container with the given key.
2090 ///
2091 /// Pitfalls:
2092 /// - If other peers concurrently create a different container at the same key, their state
2093 /// may be overwritten. See the note in [`insert_container`].
2094 pub fn get_or_create_container<C: ContainerTrait>(&self, key: &str, child: C) -> LoroResult<C> {
2095 Ok(C::from_handler(
2096 self.handler
2097 .get_or_create_container(key, child.to_handler())?,
2098 ))
2099 }
2100
2101 /// Delete all key-value pairs in the map.
2102 pub fn clear(&self) -> LoroResult<()> {
2103 self.handler.clear()
2104 }
2105
2106 /// Get the keys of the map.
2107 pub fn keys(&self) -> impl Iterator<Item = InternalString> + '_ {
2108 self.handler.keys()
2109 }
2110
2111 /// Get the values of the map.
2112 pub fn values(&self) -> impl Iterator<Item = ValueOrContainer> + '_ {
2113 self.handler.values().map(ValueOrContainer::from)
2114 }
2115
2116 /// Get the peer id of the last editor on the given entry
2117 pub fn get_last_editor(&self, key: &str) -> Option<PeerID> {
2118 self.handler.get_last_editor(key)
2119 }
2120}
2121
2122impl Default for LoroMap {
2123 fn default() -> Self {
2124 Self::new()
2125 }
2126}
2127
2128/// LoroText container. It's used to model plaintext/richtext.
2129///
2130/// Indexing and lengths:
2131/// - Rust APIs default to Unicode scalar positions for `insert`/`delete` and `slice`.
2132/// - For byte-based integration, use `insert_utf8`/`delete_utf8`.
2133/// - For UTF-16 code unit offsets (e.g., integrating with JavaScript), use
2134/// `insert_utf16`/`delete_utf16`/`slice_utf16` and related helpers.
2135/// - You can inspect `len_unicode`, `len_utf8`, and `len_utf16` depending on your needs.
2136///
2137/// # Example (emoji)
2138/// ```
2139/// use loro::LoroDoc;
2140/// let doc = LoroDoc::new();
2141/// let text = doc.get_text("text");
2142/// text.insert(0, "Hello 😀 World").unwrap();
2143/// assert_eq!(text.len_unicode(), 13); // visible characters
2144/// assert!(text.len_utf16() >= text.len_unicode()); // emoji may count as 2 in UTF-16
2145/// // Delete the emoji safely by Unicode indices
2146/// let start = 6; // after "Hello "
2147/// text.delete(start, 1).unwrap();
2148/// assert_eq!(text.to_string(), "Hello World");
2149/// ```
2150#[derive(Clone, Debug)]
2151pub struct LoroText {
2152 handler: InnerTextHandler,
2153}
2154
2155impl SealedTrait for LoroText {}
2156impl ContainerTrait for LoroText {
2157 type Handler = InnerTextHandler;
2158
2159 fn id(&self) -> ContainerID {
2160 self.handler.id()
2161 }
2162
2163 fn to_container(&self) -> Container {
2164 Container::Text(self.clone())
2165 }
2166
2167 fn to_handler(&self) -> Self::Handler {
2168 self.handler.clone()
2169 }
2170
2171 fn from_handler(handler: Self::Handler) -> Self {
2172 Self { handler }
2173 }
2174
2175 fn is_attached(&self) -> bool {
2176 self.handler.is_attached()
2177 }
2178
2179 fn get_attached(&self) -> Option<Self> {
2180 self.handler.get_attached().map(Self::from_handler)
2181 }
2182
2183 fn try_from_container(container: Container) -> Option<Self> {
2184 container.into_text().ok()
2185 }
2186
2187 fn is_deleted(&self) -> bool {
2188 self.handler.is_deleted()
2189 }
2190
2191 fn doc(&self) -> Option<LoroDoc> {
2192 self.handler.doc().map(|doc| {
2193 doc.start_auto_commit();
2194 LoroDoc::_new(doc)
2195 })
2196 }
2197}
2198
2199impl LoroText {
2200 /// Create a new container that is detached from the document.
2201 ///
2202 /// The edits on a detached container will not be persisted.
2203 /// To attach the container to the document, please insert it into an attached container.
2204 pub fn new() -> Self {
2205 Self {
2206 handler: InnerTextHandler::new_detached(),
2207 }
2208 }
2209
2210 /// Whether the container is attached to a document
2211 ///
2212 /// The edits on a detached container will not be persisted.
2213 /// To attach the container to the document, please insert it into an attached container.
2214 pub fn is_attached(&self) -> bool {
2215 self.handler.is_attached()
2216 }
2217
2218 /// Iterate over contiguous text chunks.
2219 ///
2220 /// The callback function will be called for each contiguous text segment (internal span),
2221 /// not necessarily a single character. If you need per-character iteration, iterate the
2222 /// returned `&str` within the callback.
2223 /// If the callback returns `false`, the iteration will stop.
2224 ///
2225 /// Limitation: you cannot access or alter the doc state when iterating.
2226 /// If you need to access or alter the doc state, please use `to_string` instead.
2227 pub fn iter(&self, callback: impl FnMut(&str) -> bool) {
2228 self.handler.iter(callback);
2229 }
2230
2231 /// Insert a string at the given unicode position.
2232 pub fn insert(&self, pos: usize, s: &str) -> LoroResult<()> {
2233 self.handler.insert_unicode(pos, s)
2234 }
2235
2236 /// Insert a string at the given utf-8 position.
2237 pub fn insert_utf8(&self, pos: usize, s: &str) -> LoroResult<()> {
2238 self.handler.insert_utf8(pos, s)
2239 }
2240
2241 /// Insert a string at the given UTF-16 code unit position.
2242 ///
2243 /// This is useful when working with JavaScript or other UTF-16-based index systems.
2244 pub fn insert_utf16(&self, pos: usize, s: &str) -> LoroResult<()> {
2245 self.handler.insert_utf16(pos, s)
2246 }
2247
2248 /// Delete a range of text at the given unicode position with unicode length.
2249 pub fn delete(&self, pos: usize, len: usize) -> LoroResult<()> {
2250 self.handler.delete_unicode(pos, len)
2251 }
2252
2253 /// Delete a range of text at the given utf-8 position with utf-8 length.
2254 pub fn delete_utf8(&self, pos: usize, len: usize) -> LoroResult<()> {
2255 self.handler.delete_utf8(pos, len)
2256 }
2257
2258 /// Delete a range of text at the given UTF-16 code unit position with UTF-16 length.
2259 pub fn delete_utf16(&self, pos: usize, len: usize) -> LoroResult<()> {
2260 self.handler.delete_utf16(pos, len)
2261 }
2262
2263 /// Get a string slice at the given Unicode range
2264 pub fn slice(&self, start_index: usize, end_index: usize) -> LoroResult<String> {
2265 self.handler
2266 .slice(start_index, end_index, cursor::PosType::Unicode)
2267 }
2268
2269 /// Get a string slice using UTF-16 code unit offsets.
2270 pub fn slice_utf16(&self, start_index: usize, end_index: usize) -> LoroResult<String> {
2271 self.handler.slice_utf16(start_index, end_index)
2272 }
2273
2274 /// Get the rich-text delta within a range.
2275 ///
2276 /// The range is expressed in the coordinate system selected by `pos_type`:
2277 /// Unicode scalar indices (`PosType::Unicode`), UTF-8 bytes, UTF-16 units, etc.
2278 /// The returned [`TextDelta`] segments contain only inserts and preserve the
2279 /// attributes active inside the slice.
2280 ///
2281 /// # Errors
2282 /// Returns [`LoroError::OutOfBound`](crate::LoroError::OutOfBound) when the range
2283 /// exceeds the current text length in the chosen coordinate system, or
2284 /// [`LoroError::EndIndexLessThanStartIndex`](crate::LoroError::EndIndexLessThanStartIndex)
2285 /// if `end < start`.
2286 ///
2287 /// # Examples
2288 /// ```
2289 /// use loro::{LoroDoc, TextDelta};
2290 /// use loro::cursor::PosType;
2291 ///
2292 /// let doc = LoroDoc::new();
2293 /// let text = doc.get_text("text");
2294 /// text.insert(0, "A😀BC").unwrap();
2295 /// text.mark(0..2, "bold", true).unwrap(); // A and 😀
2296 ///
2297 /// let delta = text.slice_delta(1, 3, PosType::Unicode).unwrap();
2298 /// assert_eq!(delta.len(), 2);
2299 /// if let TextDelta::Insert { insert, attributes } = &delta[0] {
2300 /// assert_eq!(insert, "😀");
2301 /// assert_eq!(attributes.as_ref().unwrap().get("bold").unwrap(), &true.into());
2302 /// } else {
2303 /// unreachable!();
2304 /// }
2305 /// if let TextDelta::Insert { insert, attributes } = &delta[1] {
2306 /// assert_eq!(insert, "B");
2307 /// assert!(attributes.is_none());
2308 /// } else {
2309 /// unreachable!();
2310 /// }
2311 /// ```
2312 pub fn slice_delta(
2313 &self,
2314 start: usize,
2315 end: usize,
2316 pos_type: cursor::PosType,
2317 ) -> LoroResult<Vec<TextDelta>> {
2318 self.handler.slice_delta(start, end, pos_type)
2319 }
2320
2321 /// Get the characters at given unicode position.
2322 pub fn char_at(&self, pos: usize) -> LoroResult<char> {
2323 self.handler.char_at(pos, cursor::PosType::Unicode)
2324 }
2325
2326 /// Delete specified character and insert string at the same position at given unicode position.
2327 pub fn splice(&self, pos: usize, len: usize, s: &str) -> LoroResult<String> {
2328 self.handler.splice(pos, len, s, cursor::PosType::Unicode)
2329 }
2330
2331 /// Delete specified range and insert a string at the same UTF-16 position.
2332 pub fn splice_utf16(&self, pos: usize, len: usize, s: &str) -> LoroResult<()> {
2333 self.handler.splice_utf16(pos, len, s)
2334 }
2335
2336 /// Whether the text container is empty.
2337 pub fn is_empty(&self) -> bool {
2338 self.handler.is_empty()
2339 }
2340
2341 /// Get the length of the text container in UTF-8.
2342 pub fn len_utf8(&self) -> usize {
2343 self.handler.len_utf8()
2344 }
2345
2346 /// Get the length of the text container in Unicode.
2347 pub fn len_unicode(&self) -> usize {
2348 self.handler.len_unicode()
2349 }
2350
2351 /// Get the length of the text container in UTF-16.
2352 pub fn len_utf16(&self) -> usize {
2353 self.handler.len_utf16()
2354 }
2355
2356 /// Update the current text based on the provided text.
2357 ///
2358 /// It will calculate the minimal difference and apply it to the current text.
2359 /// It uses Myers' diff algorithm to compute the optimal difference.
2360 ///
2361 /// This could take a long time for large texts (e.g. > 50_000 characters).
2362 /// In that case, you should use `updateByLine` instead.
2363 ///
2364 /// # Example
2365 /// ```rust
2366 /// use loro::LoroDoc;
2367 ///
2368 /// let doc = LoroDoc::new();
2369 /// let text = doc.get_text("text");
2370 /// text.insert(0, "Hello").unwrap();
2371 /// text.update("Hello World", Default::default()).unwrap();
2372 /// assert_eq!(text.to_string(), "Hello World");
2373 /// ```
2374 ///
2375 pub fn update(&self, text: &str, options: UpdateOptions) -> Result<(), UpdateTimeoutError> {
2376 self.handler.update(text, options)
2377 }
2378
2379 /// Update the current text based on the provided text.
2380 ///
2381 /// This update calculation is line-based, which will be more efficient but less precise.
2382 pub fn update_by_line(
2383 &self,
2384 text: &str,
2385 options: UpdateOptions,
2386 ) -> Result<(), UpdateTimeoutError> {
2387 self.handler.update_by_line(text, options)
2388 }
2389
2390 /// Apply a [delta](https://quilljs.com/docs/delta/) to the text container.
2391 pub fn apply_delta(&self, delta: &[TextDelta]) -> LoroResult<()> {
2392 self.handler.apply_delta(delta)
2393 }
2394
2395 /// Mark a range of text with a key-value pair.
2396 ///
2397 /// The range uses Unicode scalar indices; use [`mark_utf8`] for UTF-8 byte offsets.
2398 ///
2399 /// You can use it to create a highlight, make a range of text bold, or add a link to a range of text.
2400 ///
2401 /// You can specify the `expand` option to set the behavior when inserting text at the boundary of the range.
2402 ///
2403 /// - `after`(default): when inserting text right after the given range, the mark will be expanded to include the inserted text
2404 /// - `before`: when inserting text right before the given range, the mark will be expanded to include the inserted text
2405 /// - `none`: the mark will not be expanded to include the inserted text at the boundaries
2406 /// - `both`: when inserting text either right before or right after the given range, the mark will be expanded to include the inserted text
2407 ///
2408 /// *You should make sure that a key is always associated with the same expand type.*
2409 pub fn mark(
2410 &self,
2411 range: Range<usize>,
2412 key: &str,
2413 value: impl Into<LoroValue>,
2414 ) -> LoroResult<()> {
2415 self.handler.mark(
2416 range.start,
2417 range.end,
2418 key,
2419 value.into(),
2420 cursor::PosType::Unicode,
2421 )
2422 }
2423
2424 /// Mark a range of text with a key-value pair using UTF-8 byte offsets.
2425 pub fn mark_utf8(
2426 &self,
2427 range: Range<usize>,
2428 key: &str,
2429 value: impl Into<LoroValue>,
2430 ) -> LoroResult<()> {
2431 self.handler.mark(
2432 range.start,
2433 range.end,
2434 key,
2435 value.into(),
2436 cursor::PosType::Bytes,
2437 )
2438 }
2439
2440 /// Mark a range of text with a key-value pair using UTF-16 code unit offsets.
2441 pub fn mark_utf16(
2442 &self,
2443 range: Range<usize>,
2444 key: &str,
2445 value: impl Into<LoroValue>,
2446 ) -> LoroResult<()> {
2447 self.handler.mark(
2448 range.start,
2449 range.end,
2450 key,
2451 value.into(),
2452 cursor::PosType::Utf16,
2453 )
2454 }
2455
2456 /// Unmark a range of text with a key and a value.
2457 ///
2458 /// You can use it to remove highlights, bolds or links
2459 ///
2460 /// You can specify the `expand` option to set the behavior when inserting text at the boundary of the range.
2461 ///
2462 /// **Note: You should specify the same expand type as when you mark the text.**
2463 ///
2464 /// - `after`(default): when inserting text right after the given range, the mark will be expanded to include the inserted text
2465 /// - `before`: when inserting text right before the given range, the mark will be expanded to include the inserted text
2466 /// - `none`: the mark will not be expanded to include the inserted text at the boundaries
2467 /// - `both`: when inserting text either right before or right after the given range, the mark will be expanded to include the inserted text
2468 ///
2469 /// *You should make sure that a key is always associated with the same expand type.*
2470 ///
2471 /// Note: you cannot delete unmergeable annotations like comments by this method.
2472 pub fn unmark(&self, range: Range<usize>, key: &str) -> LoroResult<()> {
2473 self.handler
2474 .unmark(range.start, range.end, key, cursor::PosType::Unicode)
2475 }
2476
2477 /// Unmark a range of text with a key and a value using UTF-16 code unit offsets.
2478 pub fn unmark_utf16(&self, range: Range<usize>, key: &str) -> LoroResult<()> {
2479 self.handler
2480 .unmark(range.start, range.end, key, cursor::PosType::Utf16)
2481 }
2482
2483 /// Get the text in [Delta](https://quilljs.com/docs/delta/) format.
2484 ///
2485 /// # Example
2486 /// ```
2487 /// use loro::{LoroDoc, ToJson, ExpandType, TextDelta};
2488 /// use serde_json::json;
2489 /// use rustc_hash::FxHashMap;
2490 ///
2491 /// let doc = LoroDoc::new();
2492 /// let text = doc.get_text("text");
2493 /// text.insert(0, "Hello world!").unwrap();
2494 /// text.mark(0..5, "bold", true).unwrap();
2495 /// assert_eq!(
2496 /// text.to_delta(),
2497 /// vec![
2498 /// TextDelta::Insert {
2499 /// insert: "Hello".to_string(),
2500 /// attributes: Some(FxHashMap::from_iter([("bold".to_string(), true.into())])),
2501 /// },
2502 /// TextDelta::Insert {
2503 /// insert: " world!".to_string(),
2504 /// attributes: None,
2505 /// },
2506 /// ]
2507 /// );
2508 /// text.unmark(3..5, "bold").unwrap();
2509 /// assert_eq!(
2510 /// text.to_delta(),
2511 /// vec![
2512 /// TextDelta::Insert {
2513 /// insert: "Hel".to_string(),
2514 /// attributes: Some(FxHashMap::from_iter([("bold".to_string(), true.into())])),
2515 /// },
2516 /// TextDelta::Insert {
2517 /// insert: "lo world!".to_string(),
2518 /// attributes: None,
2519 /// },
2520 /// ]
2521 /// );
2522 /// ```
2523 pub fn to_delta(&self) -> Vec<TextDelta> {
2524 let delta = self.handler.get_richtext_value().into_list().unwrap();
2525 delta
2526 .iter()
2527 .map(|x| {
2528 let map = x.as_map().unwrap();
2529 let insert = map.get("insert").unwrap().as_string().unwrap().to_string();
2530 let attributes = map
2531 .get("attributes")
2532 .map(|v| v.as_map().unwrap().deref().clone());
2533 TextDelta::Insert { insert, attributes }
2534 })
2535 .collect()
2536 }
2537
2538 /// Get the rich text value in [Delta](https://quilljs.com/docs/delta/) format.
2539 ///
2540 /// # Example
2541 /// ```
2542 /// # use loro::{LoroDoc, ToJson, ExpandType, TextDelta};
2543 /// # use serde_json::json;
2544 ///
2545 /// let doc = LoroDoc::new();
2546 /// let text = doc.get_text("text");
2547 /// text.insert(0, "Hello world!").unwrap();
2548 /// text.mark(0..5, "bold", true).unwrap();
2549 /// assert_eq!(
2550 /// text.get_richtext_value().to_json_value(),
2551 /// json!([
2552 /// { "insert": "Hello", "attributes": {"bold": true} },
2553 /// { "insert": " world!" },
2554 /// ])
2555 /// );
2556 /// text.unmark(3..5, "bold").unwrap();
2557 /// assert_eq!(
2558 /// text.get_richtext_value().to_json_value(),
2559 /// json!([
2560 /// { "insert": "Hel", "attributes": {"bold": true} },
2561 /// { "insert": "lo world!" },
2562 /// ])
2563 /// );
2564 /// ```
2565 pub fn get_richtext_value(&self) -> LoroValue {
2566 self.handler.get_richtext_value()
2567 }
2568
2569 /// Get the text content of the text container.
2570 #[allow(clippy::inherent_to_string)]
2571 pub fn to_string(&self) -> String {
2572 self.handler.to_string()
2573 }
2574
2575 /// Get the cursor at the given position in the given Unicode position.
2576 ///
2577 /// Using "index" to denote cursor positions can be unstable, as positions may
2578 /// shift with document edits. To reliably represent a position or range within
2579 /// a document, it is more effective to leverage the unique ID of each item/character
2580 /// in a List CRDT or Text CRDT.
2581 ///
2582 /// Loro optimizes State metadata by not storing the IDs of deleted elements. This
2583 /// approach complicates tracking cursors since they rely on these IDs. The solution
2584 /// recalculates position by replaying relevant history to update stable positions
2585 /// accurately. To minimize the performance impact of history replay, the system
2586 /// updates cursor info to reference only the IDs of currently present elements,
2587 /// thereby reducing the need for replay.
2588 ///
2589 /// # Example
2590 ///
2591 /// ```
2592 /// # use loro::{LoroDoc, ToJson};
2593 /// let doc = LoroDoc::new();
2594 /// let text = &doc.get_text("text");
2595 /// text.insert(0, "01234").unwrap();
2596 /// let pos = text.get_cursor(5, Default::default()).unwrap();
2597 /// assert_eq!(doc.get_cursor_pos(&pos).unwrap().current.pos, 5);
2598 /// text.insert(0, "01234").unwrap();
2599 /// assert_eq!(doc.get_cursor_pos(&pos).unwrap().current.pos, 10);
2600 /// text.delete(0, 10).unwrap();
2601 /// assert_eq!(doc.get_cursor_pos(&pos).unwrap().current.pos, 0);
2602 /// text.insert(0, "01234").unwrap();
2603 /// assert_eq!(doc.get_cursor_pos(&pos).unwrap().current.pos, 5);
2604 /// ```
2605 pub fn get_cursor(&self, pos: usize, side: Side) -> Option<Cursor> {
2606 self.handler.get_cursor(pos, side)
2607 }
2608
2609 /// Whether the text container is deleted.
2610 pub fn is_deleted(&self) -> bool {
2611 self.handler.is_deleted()
2612 }
2613
2614 /// Convert a position between coordinate systems (Unicode, UTF-16, UTF-8 bytes, Event).
2615 ///
2616 /// Returns `None` when the position is out of bounds or the conversion isn't supported.
2617 pub fn convert_pos(
2618 &self,
2619 index: usize,
2620 from: cursor::PosType,
2621 to: cursor::PosType,
2622 ) -> Option<usize> {
2623 self.handler.convert_pos(index, from, to)
2624 }
2625
2626 /// Push a string to the end of the text container.
2627 pub fn push_str(&self, s: &str) -> LoroResult<()> {
2628 self.handler.push_str(s)
2629 }
2630
2631 /// Get the editor of the text at the given position.
2632 ///
2633 /// Returns `None` if the position is out of bounds or attribution is unavailable.
2634 ///
2635 /// # Example
2636 /// ```
2637 /// use loro::LoroDoc;
2638 /// let doc = LoroDoc::new();
2639 /// let t = doc.get_text("t");
2640 /// t.insert(0, "hi").unwrap();
2641 /// let who = t.get_editor_at_unicode_pos(0);
2642 /// assert!(who.is_some());
2643 /// ```
2644 pub fn get_editor_at_unicode_pos(&self, pos: usize) -> Option<PeerID> {
2645 self.handler
2646 .get_cursor(pos, Side::Middle)
2647 .map(|x| x.id.unwrap().peer)
2648 }
2649}
2650
2651impl Default for LoroText {
2652 fn default() -> Self {
2653 Self::new()
2654 }
2655}
2656
2657/// LoroTree container. It's used to model movable trees.
2658///
2659/// You may use it to model directories, outline or other movable hierarchical data.
2660///
2661/// Learn more at https://loro.dev/docs/tutorial/tree
2662#[derive(Clone, Debug)]
2663pub struct LoroTree {
2664 handler: InnerTreeHandler,
2665}
2666
2667impl SealedTrait for LoroTree {}
2668impl ContainerTrait for LoroTree {
2669 type Handler = InnerTreeHandler;
2670
2671 fn id(&self) -> ContainerID {
2672 self.handler.id()
2673 }
2674
2675 fn to_container(&self) -> Container {
2676 Container::Tree(self.clone())
2677 }
2678
2679 fn to_handler(&self) -> Self::Handler {
2680 self.handler.clone()
2681 }
2682
2683 fn from_handler(handler: Self::Handler) -> Self {
2684 Self { handler }
2685 }
2686
2687 fn is_attached(&self) -> bool {
2688 self.handler.is_attached()
2689 }
2690
2691 fn get_attached(&self) -> Option<Self> {
2692 self.handler.get_attached().map(Self::from_handler)
2693 }
2694
2695 fn try_from_container(container: Container) -> Option<Self> {
2696 container.into_tree().ok()
2697 }
2698
2699 fn is_deleted(&self) -> bool {
2700 self.handler.is_deleted()
2701 }
2702 fn doc(&self) -> Option<LoroDoc> {
2703 self.handler.doc().map(|doc| {
2704 doc.start_auto_commit();
2705 LoroDoc::_new(doc)
2706 })
2707 }
2708}
2709
2710/// A tree node in the [LoroTree].
2711#[derive(Debug, Clone)]
2712pub struct TreeNode {
2713 /// ID of the tree node.
2714 pub id: TreeID,
2715 /// ID of the parent tree node.
2716 /// If the node is deleted this value is TreeParentId::Deleted.
2717 /// If you checkout to a version before the node is created, this value is TreeParentId::Unexist.
2718 pub parent: TreeParentId,
2719 /// Fraction index of the node
2720 pub fractional_index: FractionalIndex,
2721 /// The current index of the node in its parent's children list.
2722 pub index: usize,
2723}
2724
2725impl LoroTree {
2726 /// Create a new container that is detached from the document.
2727 ///
2728 /// The edits on a detached container will not be persisted.
2729 /// To attach the container to the document, please insert it into an attached container.
2730 pub fn new() -> Self {
2731 Self {
2732 handler: InnerTreeHandler::new_detached(),
2733 }
2734 }
2735
2736 /// Whether the container is attached to a document
2737 ///
2738 /// The edits on a detached container will not be persisted.
2739 /// To attach the container to the document, please insert it into an attached container.
2740 pub fn is_attached(&self) -> bool {
2741 self.handler.is_attached()
2742 }
2743
2744 /// Create a new tree node and return the [`TreeID`].
2745 ///
2746 /// If the `parent` is `None`, the created node is the root of a tree.
2747 /// Otherwise, the created node is a child of the parent tree node.
2748 ///
2749 /// # Example
2750 ///
2751 /// ```rust
2752 /// use loro::LoroDoc;
2753 ///
2754 /// let doc = LoroDoc::new();
2755 /// let tree = doc.get_tree("tree");
2756 /// // create a root
2757 /// let root = tree.create(None).unwrap();
2758 /// // create a new child
2759 /// let child = tree.create(root).unwrap();
2760 /// ```
2761 pub fn create<T: Into<TreeParentId>>(&self, parent: T) -> LoroResult<TreeID> {
2762 self.handler.create(parent.into())
2763 }
2764
2765 /// Get the root nodes of the forest.
2766 pub fn roots(&self) -> Vec<TreeID> {
2767 self.handler.roots()
2768 }
2769
2770 /// Create a new tree node at the given index and return the [`TreeID`].
2771 ///
2772 /// If the `parent` is `None`, the created node is the root of a tree.
2773 /// If the `index` is greater than the number of children of the parent, error will be returned.
2774 ///
2775 /// # Example
2776 ///
2777 /// ```rust
2778 /// use loro::LoroDoc;
2779 ///
2780 /// let doc = LoroDoc::new();
2781 /// let tree = doc.get_tree("tree");
2782 /// // enable generate fractional index
2783 /// tree.enable_fractional_index(0);
2784 /// // create a root
2785 /// let root = tree.create(None).unwrap();
2786 /// // create a new child at index 0
2787 /// let child = tree.create_at(root, 0).unwrap();
2788 /// ```
2789 pub fn create_at<T: Into<TreeParentId>>(&self, parent: T, index: usize) -> LoroResult<TreeID> {
2790 if !self.handler.is_fractional_index_enabled() {
2791 return Err(LoroTreeError::FractionalIndexNotEnabled.into());
2792 }
2793 self.handler.create_at(parent.into(), index)
2794 }
2795
2796 /// Move the `target` node to be a child of the `parent` node.
2797 ///
2798 /// If the `parent` is `None`, the `target` node will be a root.
2799 ///
2800 /// # Example
2801 ///
2802 /// ```rust
2803 /// use loro::LoroDoc;
2804 ///
2805 /// let doc = LoroDoc::new();
2806 /// let tree = doc.get_tree("tree");
2807 /// let root = tree.create(None).unwrap();
2808 /// let root2 = tree.create(None).unwrap();
2809 /// // move `root2` to be a child of `root`.
2810 /// tree.mov(root2, root).unwrap();
2811 /// ```
2812 pub fn mov<T: Into<TreeParentId>>(&self, target: TreeID, parent: T) -> LoroResult<()> {
2813 self.handler.mov(target, parent.into())
2814 }
2815
2816 /// Move the `target` node to be a child of the `parent` node at the given index.
2817 /// If the `parent` is `None`, the `target` node will be a root.
2818 ///
2819 /// # Example
2820 ///
2821 /// ```rust
2822 /// use loro::LoroDoc;
2823 ///
2824 /// let doc = LoroDoc::new();
2825 /// let tree = doc.get_tree("tree");
2826 /// // enable generate fractional index
2827 /// tree.enable_fractional_index(0);
2828 /// let root = tree.create(None).unwrap();
2829 /// let root2 = tree.create(None).unwrap();
2830 /// // move `root2` to be a child of `root` at index 0.
2831 /// tree.mov_to(root2, root, 0).unwrap();
2832 /// ```
2833 pub fn mov_to<T: Into<TreeParentId>>(
2834 &self,
2835 target: TreeID,
2836 parent: T,
2837 to: usize,
2838 ) -> LoroResult<()> {
2839 if !self.handler.is_fractional_index_enabled() {
2840 return Err(LoroTreeError::FractionalIndexNotEnabled.into());
2841 }
2842 self.handler.move_to(target, parent.into(), to)
2843 }
2844
2845 /// Move the `target` node to be a child after the `after` node with the same parent.
2846 ///
2847 /// # Example
2848 ///
2849 /// ```rust
2850 /// use loro::LoroDoc;
2851 ///
2852 /// let doc = LoroDoc::new();
2853 /// let tree = doc.get_tree("tree");
2854 /// // enable generate fractional index
2855 /// tree.enable_fractional_index(0);
2856 /// let root = tree.create(None).unwrap();
2857 /// let root2 = tree.create(None).unwrap();
2858 /// // move `root` to be a child after `root2`.
2859 /// tree.mov_after(root, root2).unwrap();
2860 /// ```
2861 pub fn mov_after(&self, target: TreeID, after: TreeID) -> LoroResult<()> {
2862 if !self.handler.is_fractional_index_enabled() {
2863 return Err(LoroTreeError::FractionalIndexNotEnabled.into());
2864 }
2865 self.handler.mov_after(target, after)
2866 }
2867
2868 /// Move the `target` node to be a child before the `before` node with the same parent.
2869 ///
2870 /// # Example
2871 ///
2872 /// ```rust
2873 /// use loro::LoroDoc;
2874 ///
2875 /// let doc = LoroDoc::new();
2876 /// let tree = doc.get_tree("tree");
2877 /// // enable generate fractional index
2878 /// tree.enable_fractional_index(0);
2879 /// let root = tree.create(None).unwrap();
2880 /// let root2 = tree.create(None).unwrap();
2881 /// // move `root` to be a child before `root2`.
2882 /// tree.mov_before(root, root2).unwrap();
2883 /// ```
2884 pub fn mov_before(&self, target: TreeID, before: TreeID) -> LoroResult<()> {
2885 if !self.handler.is_fractional_index_enabled() {
2886 return Err(LoroTreeError::FractionalIndexNotEnabled.into());
2887 }
2888 self.handler.mov_before(target, before)
2889 }
2890
2891 /// Delete a tree node.
2892 ///
2893 /// Note: If the deleted node has children, the children do not appear in the state
2894 /// rather than actually being deleted.
2895 ///
2896 /// # Example
2897 ///
2898 /// ```rust
2899 /// use loro::LoroDoc;
2900 ///
2901 /// let doc = LoroDoc::new();
2902 /// let tree = doc.get_tree("tree");
2903 /// let root = tree.create(None).unwrap();
2904 /// tree.delete(root).unwrap();
2905 /// ```
2906 pub fn delete(&self, target: TreeID) -> LoroResult<()> {
2907 self.handler.delete(target)
2908 }
2909
2910 /// Get the associated metadata map handler of a tree node.
2911 ///
2912 /// # Example
2913 /// ```rust
2914 /// use loro::LoroDoc;
2915 ///
2916 /// let doc = LoroDoc::new();
2917 /// let tree = doc.get_tree("tree");
2918 /// let root = tree.create(None).unwrap();
2919 /// let root_meta = tree.get_meta(root).unwrap();
2920 /// root_meta.insert("color", "red");
2921 /// ```
2922 pub fn get_meta(&self, target: TreeID) -> LoroResult<LoroMap> {
2923 self.handler
2924 .get_meta(target)
2925 .map(|h| LoroMap { handler: h })
2926 }
2927
2928 /// Return the parent of target node.
2929 ///
2930 /// - If the target node does not exist, return `None`.
2931 /// - If the target node is a root node, return `Some(None)`.
2932 pub fn parent(&self, target: TreeID) -> Option<TreeParentId> {
2933 self.handler.get_node_parent(&target)
2934 }
2935
2936 /// Return whether target node exists. including deleted node.
2937 pub fn contains(&self, target: TreeID) -> bool {
2938 self.handler.contains(target)
2939 }
2940
2941 /// Return whether target node is deleted.
2942 ///
2943 /// # Errors
2944 ///
2945 /// - If the target node does not exist, return `LoroTreeError::TreeNodeNotExist`.
2946 pub fn is_node_deleted(&self, target: &TreeID) -> LoroResult<bool> {
2947 self.handler.is_node_deleted(target)
2948 }
2949
2950 /// Return all nodes, including deleted nodes
2951 pub fn nodes(&self) -> Vec<TreeID> {
2952 self.handler.nodes()
2953 }
2954
2955 /// Return all nodes, if `with_deleted` is true, the deleted nodes will be included.
2956 pub fn get_nodes(&self, with_deleted: bool) -> Vec<TreeNode> {
2957 let mut ans = self.handler.get_nodes_under(TreeParentId::Root);
2958 if with_deleted {
2959 ans.extend(self.handler.get_nodes_under(TreeParentId::Deleted));
2960 }
2961 ans.into_iter()
2962 .map(|x| TreeNode {
2963 id: x.id,
2964 parent: x.parent,
2965 fractional_index: x.fractional_index,
2966 index: x.index,
2967 })
2968 .collect()
2969 }
2970
2971 /// Return all children of the target node.
2972 ///
2973 /// If the parent node does not exist, return `None`.
2974 pub fn children<T: Into<TreeParentId>>(&self, parent: T) -> Option<Vec<TreeID>> {
2975 self.handler.children(&parent.into())
2976 }
2977
2978 /// Return the number of children of the target node.
2979 pub fn children_num<T: Into<TreeParentId>>(&self, parent: T) -> Option<usize> {
2980 let parent: TreeParentId = parent.into();
2981 self.handler.children_num(&parent)
2982 }
2983
2984 /// Return the fractional index of the target node with hex format.
2985 pub fn fractional_index(&self, target: TreeID) -> Option<String> {
2986 self.handler
2987 .get_position_by_tree_id(&target)
2988 .map(|x| x.to_string())
2989 }
2990
2991 /// Return the hierarchy array of the forest.
2992 ///
2993 /// Note: the metadata will be not resolved. So if you don't only care about hierarchy
2994 /// but also the metadata, you should use [TreeHandler::get_value_with_meta()].
2995 pub fn get_value(&self) -> LoroValue {
2996 self.handler.get_value()
2997 }
2998
2999 /// Return the hierarchy array of the forest, each node is with metadata.
3000 pub fn get_value_with_meta(&self) -> LoroValue {
3001 self.handler.get_deep_value()
3002 }
3003
3004 // This method is used for testing only.
3005 #[doc(hidden)]
3006 #[allow(non_snake_case)]
3007 pub fn __internal__next_tree_id(&self) -> TreeID {
3008 self.handler.__internal__next_tree_id()
3009 }
3010
3011 /// Whether the fractional index is enabled.
3012 pub fn is_fractional_index_enabled(&self) -> bool {
3013 self.handler.is_fractional_index_enabled()
3014 }
3015
3016 /// Enable fractional index for Tree Position.
3017 ///
3018 /// The jitter is used to avoid conflicts when multiple users are creating the node at the same position.
3019 /// value 0 is default, which means no jitter, any value larger than 0 will enable jitter.
3020 ///
3021 /// Generally speaking, jitter will affect the growth rate of document size.
3022 /// [Read more about it](https://www.loro.dev/blog/movable-tree#implementation-and-encoding-size)
3023 #[inline]
3024 pub fn enable_fractional_index(&self, jitter: u8) {
3025 self.handler.enable_fractional_index(jitter);
3026 }
3027
3028 /// Disable the fractional index generation when you don't need the Tree's siblings to be sorted.
3029 /// The fractional index will always be set to the same default value 0.
3030 ///
3031 /// After calling this, you cannot use `tree.moveTo()`, `tree.moveBefore()`, `tree.moveAfter()`,
3032 /// and `tree.createAt()`.
3033 #[inline]
3034 pub fn disable_fractional_index(&self) {
3035 self.handler.disable_fractional_index();
3036 }
3037
3038 /// Whether the tree is empty.
3039 ///
3040 #[inline]
3041 pub fn is_empty(&self) -> bool {
3042 self.handler.is_empty()
3043 }
3044
3045 /// Get the last move id of the target node.
3046 pub fn get_last_move_id(&self, target: &TreeID) -> Option<ID> {
3047 self.handler.get_last_move_id(target)
3048 }
3049}
3050
3051impl Default for LoroTree {
3052 fn default() -> Self {
3053 Self::new()
3054 }
3055}
3056
3057/// [LoroMovableList container](https://loro.dev/docs/tutorial/list)
3058///
3059/// It is used to model movable ordered lists.
3060///
3061/// Using a combination of insert and delete operations, one can simulate set and move
3062/// operations on a List. However, this approach fails in concurrent editing scenarios.
3063/// For example, if the same element is set or moved concurrently, the simulation would
3064/// result in the deletion of the original element and the insertion of two new elements,
3065/// which does not meet expectations.
3066#[derive(Clone, Debug)]
3067pub struct LoroMovableList {
3068 handler: InnerMovableListHandler,
3069}
3070
3071impl SealedTrait for LoroMovableList {}
3072impl ContainerTrait for LoroMovableList {
3073 type Handler = InnerMovableListHandler;
3074
3075 fn id(&self) -> ContainerID {
3076 self.handler.id()
3077 }
3078
3079 fn to_container(&self) -> Container {
3080 Container::MovableList(self.clone())
3081 }
3082
3083 fn to_handler(&self) -> Self::Handler {
3084 self.handler.clone()
3085 }
3086
3087 fn from_handler(handler: Self::Handler) -> Self {
3088 Self { handler }
3089 }
3090
3091 fn try_from_container(container: Container) -> Option<Self>
3092 where
3093 Self: Sized,
3094 {
3095 match container {
3096 Container::MovableList(x) => Some(x),
3097 _ => None,
3098 }
3099 }
3100
3101 fn is_attached(&self) -> bool {
3102 self.handler.is_attached()
3103 }
3104
3105 fn get_attached(&self) -> Option<Self>
3106 where
3107 Self: Sized,
3108 {
3109 self.handler.get_attached().map(Self::from_handler)
3110 }
3111
3112 fn is_deleted(&self) -> bool {
3113 self.handler.is_deleted()
3114 }
3115
3116 fn doc(&self) -> Option<LoroDoc> {
3117 self.handler.doc().map(|doc| {
3118 doc.start_auto_commit();
3119 LoroDoc::_new(doc)
3120 })
3121 }
3122}
3123
3124impl LoroMovableList {
3125 /// Create a new container that is detached from the document.
3126 ///
3127 /// The edits on a detached container will not be persisted.
3128 /// To attach the container to the document, please insert it into an attached container.
3129 pub fn new() -> LoroMovableList {
3130 Self {
3131 handler: InnerMovableListHandler::new_detached(),
3132 }
3133 }
3134
3135 /// Whether the container is attached to a document
3136 ///
3137 /// The edits on a detached container will not be persisted.
3138 /// To attach the container to the document, please insert it into an attached container.
3139 pub fn is_attached(&self) -> bool {
3140 self.handler.is_attached()
3141 }
3142
3143 /// Insert a value at the given position.
3144 pub fn insert(&self, pos: usize, v: impl Into<LoroValue>) -> LoroResult<()> {
3145 self.handler.insert(pos, v)
3146 }
3147
3148 /// Delete the value at the given position.
3149 pub fn delete(&self, pos: usize, len: usize) -> LoroResult<()> {
3150 self.handler.delete(pos, len)
3151 }
3152
3153 /// Get the value at the given position.
3154 pub fn get(&self, index: usize) -> Option<ValueOrContainer> {
3155 self.handler.get_(index).map(ValueOrContainer::from)
3156 }
3157
3158 /// Get the length of the list.
3159 pub fn len(&self) -> usize {
3160 self.handler.len()
3161 }
3162
3163 /// Whether the list is empty.
3164 #[must_use]
3165 pub fn is_empty(&self) -> bool {
3166 self.len() == 0
3167 }
3168
3169 /// Get the shallow value of the list.
3170 ///
3171 /// It will not convert the state of sub-containers, but represent them as [LoroValue::Container].
3172 pub fn get_value(&self) -> LoroValue {
3173 self.handler.get_value()
3174 }
3175
3176 /// Get the deep value of the list.
3177 ///
3178 /// It will convert the state of sub-containers into a nested JSON value.
3179 pub fn get_deep_value(&self) -> LoroValue {
3180 self.handler.get_deep_value()
3181 }
3182
3183 /// Pop the last element of the list.
3184 pub fn pop(&self) -> LoroResult<Option<ValueOrContainer>> {
3185 let ans = self.handler.pop_()?.map(ValueOrContainer::from);
3186 Ok(ans)
3187 }
3188
3189 /// Push a value to the end of the list.
3190 pub fn push(&self, v: impl Into<LoroValue>) -> LoroResult<()> {
3191 self.handler.push(v.into())
3192 }
3193
3194 /// Push a container to the end of the list.
3195 pub fn push_container<C: ContainerTrait>(&self, child: C) -> LoroResult<C> {
3196 let pos = self.handler.len();
3197 Ok(C::from_handler(
3198 self.handler.insert_container(pos, child.to_handler())?,
3199 ))
3200 }
3201
3202 /// Set the value at the given position.
3203 ///
3204 /// # Example
3205 /// ```
3206 /// use loro::{LoroDoc, ToJson};
3207 /// use serde_json::json;
3208 /// let doc = LoroDoc::new();
3209 /// let ml = doc.get_movable_list("ml");
3210 /// ml.insert(0, "a").unwrap();
3211 /// ml.set(0, "b").unwrap();
3212 /// assert_eq!(ml.get_deep_value().to_json_value(), json!(["b"]));
3213 /// ```
3214 pub fn set(&self, pos: usize, value: impl Into<LoroValue>) -> LoroResult<()> {
3215 self.handler.set(pos, value.into())
3216 }
3217
3218 /// Move the value at the given position to the given position.
3219 ///
3220 /// # Example
3221 /// ```
3222 /// use loro::{LoroDoc, ToJson};
3223 /// use serde_json::json;
3224 /// let doc = LoroDoc::new();
3225 /// let ml = doc.get_movable_list("ml");
3226 /// ml.insert(0, "a").unwrap();
3227 /// ml.insert(1, "b").unwrap();
3228 /// ml.insert(2, "c").unwrap();
3229 /// ml.mov(0, 2).unwrap();
3230 /// assert_eq!(ml.get_deep_value().to_json_value(), json!(["b","c","a"]));
3231 /// ```
3232 pub fn mov(&self, from: usize, to: usize) -> LoroResult<()> {
3233 self.handler.mov(from, to)
3234 }
3235
3236 /// Insert a container at the given position.
3237 pub fn insert_container<C: ContainerTrait>(&self, pos: usize, child: C) -> LoroResult<C> {
3238 Ok(C::from_handler(
3239 self.handler.insert_container(pos, child.to_handler())?,
3240 ))
3241 }
3242
3243 /// Set the container at the given position.
3244 pub fn set_container<C: ContainerTrait>(&self, pos: usize, child: C) -> LoroResult<C> {
3245 Ok(C::from_handler(
3246 self.handler.set_container(pos, child.to_handler())?,
3247 ))
3248 }
3249
3250 /// Log the internal state of the list.
3251 pub fn log_internal_state(&self) {
3252 info!(
3253 "movable_list internal state: {}",
3254 self.handler.log_internal_state()
3255 )
3256 }
3257
3258 /// Get the cursor at the given position.
3259 ///
3260 /// Using "index" to denote cursor positions can be unstable, as positions may
3261 /// shift with document edits. To reliably represent a position or range within
3262 /// a document, it is more effective to leverage the unique ID of each item/character
3263 /// in a List CRDT or Text CRDT.
3264 ///
3265 /// Loro optimizes State metadata by not storing the IDs of deleted elements. This
3266 /// approach complicates tracking cursors since they rely on these IDs. The solution
3267 /// recalculates position by replaying relevant history to update stable positions
3268 /// accurately. To minimize the performance impact of history replay, the system
3269 /// updates cursor info to reference only the IDs of currently present elements,
3270 /// thereby reducing the need for replay.
3271 ///
3272 /// # Example
3273 ///
3274 /// ```
3275 /// use loro::LoroDoc;
3276 /// use loro_internal::cursor::Side;
3277 ///
3278 /// let doc = LoroDoc::new();
3279 /// let list = doc.get_movable_list("list");
3280 /// list.insert(0, 0).unwrap();
3281 /// let cursor = list.get_cursor(0, Side::Middle).unwrap();
3282 /// assert_eq!(doc.get_cursor_pos(&cursor).unwrap().current.pos, 0);
3283 /// list.insert(0, 0).unwrap();
3284 /// assert_eq!(doc.get_cursor_pos(&cursor).unwrap().current.pos, 1);
3285 /// list.insert(0, 0).unwrap();
3286 /// list.insert(0, 0).unwrap();
3287 /// assert_eq!(doc.get_cursor_pos(&cursor).unwrap().current.pos, 3);
3288 /// list.insert(4, 0).unwrap();
3289 /// assert_eq!(doc.get_cursor_pos(&cursor).unwrap().current.pos, 3);
3290 /// ```
3291 pub fn get_cursor(&self, pos: usize, side: Side) -> Option<Cursor> {
3292 self.handler.get_cursor(pos, side)
3293 }
3294
3295 /// Get the elements of the list as a vector of LoroValues.
3296 ///
3297 /// This method returns a vector containing all the elements in the list as LoroValues.
3298 /// It provides a convenient way to access the entire contents of the LoroMovableList
3299 /// as a standard Rust vector.
3300 ///
3301 /// # Returns
3302 ///
3303 /// A `Vec<LoroValue>` containing all elements of the list.
3304 ///
3305 /// # Example
3306 ///
3307 /// ```
3308 /// use loro::LoroDoc;
3309 ///
3310 /// let doc = LoroDoc::new();
3311 /// let list = doc.get_movable_list("mylist");
3312 /// list.insert(0, 1).unwrap();
3313 /// list.insert(1, "hello").unwrap();
3314 /// list.insert(2, true).unwrap();
3315 ///
3316 /// let vec = list.to_vec();
3317 /// assert_eq!(vec.len(), 3);
3318 /// assert_eq!(vec[0], 1.into());
3319 /// assert_eq!(vec[1], "hello".into());
3320 /// assert_eq!(vec[2], true.into());
3321 /// ```
3322 pub fn to_vec(&self) -> Vec<LoroValue> {
3323 self.get_value().into_list().unwrap().unwrap()
3324 }
3325
3326 /// Delete all elements in the list.
3327 pub fn clear(&self) -> LoroResult<()> {
3328 self.handler.clear()
3329 }
3330
3331 /// Iterate over the elements of the list.
3332 pub fn for_each<I>(&self, mut f: I)
3333 where
3334 I: FnMut(ValueOrContainer),
3335 {
3336 self.handler.for_each(&mut |v| {
3337 f(ValueOrContainer::from(v));
3338 })
3339 }
3340
3341 /// Get the creator of the list item at the given position.
3342 pub fn get_creator_at(&self, pos: usize) -> Option<PeerID> {
3343 self.handler.get_creator_at(pos)
3344 }
3345
3346 /// Get the last mover of the list item at the given position.
3347 pub fn get_last_mover_at(&self, pos: usize) -> Option<PeerID> {
3348 self.handler.get_last_mover_at(pos)
3349 }
3350
3351 /// Get the last editor of the list item at the given position.
3352 pub fn get_last_editor_at(&self, pos: usize) -> Option<PeerID> {
3353 self.handler.get_last_editor_at(pos)
3354 }
3355}
3356
3357impl Default for LoroMovableList {
3358 fn default() -> Self {
3359 Self::new()
3360 }
3361}
3362
3363/// Unknown container.
3364#[derive(Clone, Debug)]
3365pub struct LoroUnknown {
3366 handler: InnerUnknownHandler,
3367}
3368
3369impl SealedTrait for LoroUnknown {}
3370impl ContainerTrait for LoroUnknown {
3371 type Handler = InnerUnknownHandler;
3372
3373 fn id(&self) -> ContainerID {
3374 self.handler.id()
3375 }
3376
3377 fn to_container(&self) -> Container {
3378 Container::Unknown(self.clone())
3379 }
3380
3381 fn to_handler(&self) -> Self::Handler {
3382 self.handler.clone()
3383 }
3384
3385 fn from_handler(handler: Self::Handler) -> Self {
3386 Self { handler }
3387 }
3388
3389 fn try_from_container(container: Container) -> Option<Self>
3390 where
3391 Self: Sized,
3392 {
3393 match container {
3394 Container::Unknown(x) => Some(x),
3395 _ => None,
3396 }
3397 }
3398
3399 fn is_attached(&self) -> bool {
3400 self.handler.is_attached()
3401 }
3402
3403 fn get_attached(&self) -> Option<Self>
3404 where
3405 Self: Sized,
3406 {
3407 self.handler.get_attached().map(Self::from_handler)
3408 }
3409
3410 fn is_deleted(&self) -> bool {
3411 self.handler.is_deleted()
3412 }
3413
3414 fn doc(&self) -> Option<LoroDoc> {
3415 self.handler.doc().map(|doc| {
3416 doc.start_auto_commit();
3417 LoroDoc::_new(doc)
3418 })
3419 }
3420}
3421
3422use enum_as_inner::EnumAsInner;
3423#[cfg(feature = "jsonpath")]
3424use loro_internal::jsonpath::jsonpath_impl::JsonPathError;
3425
3426/// All the CRDT containers supported by Loro.
3427#[derive(Clone, Debug, EnumAsInner)]
3428pub enum Container {
3429 /// [LoroList container](https://loro.dev/docs/tutorial/list)
3430 List(LoroList),
3431 /// [LoroMap container](https://loro.dev/docs/tutorial/map)
3432 Map(LoroMap),
3433 /// [LoroText container](https://loro.dev/docs/tutorial/text)
3434 Text(LoroText),
3435 /// [LoroTree container]
3436 Tree(LoroTree),
3437 /// [LoroMovableList container](https://loro.dev/docs/tutorial/list)
3438 MovableList(LoroMovableList),
3439 #[cfg(feature = "counter")]
3440 /// [LoroCounter container]
3441 Counter(counter::LoroCounter),
3442 /// Unknown container
3443 Unknown(LoroUnknown),
3444}
3445
3446impl SealedTrait for Container {}
3447impl ContainerTrait for Container {
3448 type Handler = loro_internal::handler::Handler;
3449
3450 fn id(&self) -> ContainerID {
3451 match self {
3452 Container::List(x) => x.id(),
3453 Container::Map(x) => x.id(),
3454 Container::Text(x) => x.id(),
3455 Container::Tree(x) => x.id(),
3456 Container::MovableList(x) => x.id(),
3457 #[cfg(feature = "counter")]
3458 Container::Counter(x) => x.id(),
3459 Container::Unknown(x) => x.id(),
3460 }
3461 }
3462
3463 fn to_container(&self) -> Container {
3464 self.clone()
3465 }
3466
3467 fn to_handler(&self) -> Self::Handler {
3468 match self {
3469 Container::List(x) => Self::Handler::List(x.to_handler()),
3470 Container::Map(x) => Self::Handler::Map(x.to_handler()),
3471 Container::Text(x) => Self::Handler::Text(x.to_handler()),
3472 Container::Tree(x) => Self::Handler::Tree(x.to_handler()),
3473 Container::MovableList(x) => Self::Handler::MovableList(x.to_handler()),
3474 #[cfg(feature = "counter")]
3475 Container::Counter(x) => Self::Handler::Counter(x.to_handler()),
3476 Container::Unknown(x) => Self::Handler::Unknown(x.to_handler()),
3477 }
3478 }
3479
3480 fn from_handler(handler: Self::Handler) -> Self {
3481 match handler {
3482 InnerHandler::Text(x) => Container::Text(LoroText { handler: x }),
3483 InnerHandler::Map(x) => Container::Map(LoroMap { handler: x }),
3484 InnerHandler::List(x) => Container::List(LoroList { handler: x }),
3485 InnerHandler::MovableList(x) => Container::MovableList(LoroMovableList { handler: x }),
3486 InnerHandler::Tree(x) => Container::Tree(LoroTree { handler: x }),
3487 #[cfg(feature = "counter")]
3488 InnerHandler::Counter(x) => Container::Counter(counter::LoroCounter { handler: x }),
3489 InnerHandler::Unknown(x) => Container::Unknown(LoroUnknown { handler: x }),
3490 }
3491 }
3492
3493 fn is_attached(&self) -> bool {
3494 match self {
3495 Container::List(x) => x.is_attached(),
3496 Container::Map(x) => x.is_attached(),
3497 Container::Text(x) => x.is_attached(),
3498 Container::Tree(x) => x.is_attached(),
3499 Container::MovableList(x) => x.is_attached(),
3500 #[cfg(feature = "counter")]
3501 Container::Counter(x) => x.is_attached(),
3502 Container::Unknown(x) => x.is_attached(),
3503 }
3504 }
3505
3506 fn get_attached(&self) -> Option<Self> {
3507 match self {
3508 Container::List(x) => x.get_attached().map(Container::List),
3509 Container::MovableList(x) => x.get_attached().map(Container::MovableList),
3510 Container::Map(x) => x.get_attached().map(Container::Map),
3511 Container::Text(x) => x.get_attached().map(Container::Text),
3512 Container::Tree(x) => x.get_attached().map(Container::Tree),
3513 #[cfg(feature = "counter")]
3514 Container::Counter(x) => x.get_attached().map(Container::Counter),
3515 Container::Unknown(x) => x.get_attached().map(Container::Unknown),
3516 }
3517 }
3518
3519 fn try_from_container(container: Container) -> Option<Self>
3520 where
3521 Self: Sized,
3522 {
3523 Some(container)
3524 }
3525
3526 fn is_deleted(&self) -> bool {
3527 match self {
3528 Container::List(x) => x.is_deleted(),
3529 Container::Map(x) => x.is_deleted(),
3530 Container::Text(x) => x.is_deleted(),
3531 Container::Tree(x) => x.is_deleted(),
3532 Container::MovableList(x) => x.is_deleted(),
3533 #[cfg(feature = "counter")]
3534 Container::Counter(x) => x.is_deleted(),
3535 Container::Unknown(x) => x.is_deleted(),
3536 }
3537 }
3538 fn doc(&self) -> Option<LoroDoc> {
3539 match self {
3540 Container::List(x) => x.doc(),
3541 Container::Map(x) => x.doc(),
3542 Container::Text(x) => x.doc(),
3543 Container::Tree(x) => x.doc(),
3544 Container::MovableList(x) => x.doc(),
3545 #[cfg(feature = "counter")]
3546 Container::Counter(x) => x.doc(),
3547 Container::Unknown(x) => x.doc(),
3548 }
3549 }
3550}
3551
3552impl Container {
3553 /// Create a detached container of the given type.
3554 ///
3555 /// A detached container is a container that is not attached to a document.
3556 /// The edits on a detached container will not be persisted.
3557 /// To attach the container to the document, please insert it into an attached container.
3558 pub fn new(kind: ContainerType) -> Self {
3559 match kind {
3560 ContainerType::List => Container::List(LoroList::new()),
3561 ContainerType::MovableList => Container::MovableList(LoroMovableList::new()),
3562 ContainerType::Map => Container::Map(LoroMap::new()),
3563 ContainerType::Text => Container::Text(LoroText::new()),
3564 ContainerType::Tree => Container::Tree(LoroTree::new()),
3565 #[cfg(feature = "counter")]
3566 ContainerType::Counter => Container::Counter(counter::LoroCounter::new()),
3567 ContainerType::Unknown(_) => unreachable!(),
3568 }
3569 }
3570
3571 /// Get the type of the container.
3572 pub fn get_type(&self) -> ContainerType {
3573 match self {
3574 Container::List(_) => ContainerType::List,
3575 Container::MovableList(_) => ContainerType::MovableList,
3576 Container::Map(_) => ContainerType::Map,
3577 Container::Text(_) => ContainerType::Text,
3578 Container::Tree(_) => ContainerType::Tree,
3579 #[cfg(feature = "counter")]
3580 Container::Counter(_) => ContainerType::Counter,
3581 Container::Unknown(x) => x.handler.id().container_type(),
3582 }
3583 }
3584}
3585
3586impl From<InnerHandler> for Container {
3587 fn from(value: InnerHandler) -> Self {
3588 match value {
3589 InnerHandler::Text(x) => Container::Text(LoroText { handler: x }),
3590 InnerHandler::Map(x) => Container::Map(LoroMap { handler: x }),
3591 InnerHandler::List(x) => Container::List(LoroList { handler: x }),
3592 InnerHandler::Tree(x) => Container::Tree(LoroTree { handler: x }),
3593 InnerHandler::MovableList(x) => Container::MovableList(LoroMovableList { handler: x }),
3594 #[cfg(feature = "counter")]
3595 InnerHandler::Counter(x) => Container::Counter(counter::LoroCounter { handler: x }),
3596 InnerHandler::Unknown(x) => Container::Unknown(LoroUnknown { handler: x }),
3597 }
3598 }
3599}
3600
3601/// It's a type that can be either a value or a container.
3602#[derive(Debug, Clone, EnumAsInner)]
3603pub enum ValueOrContainer {
3604 /// A value.
3605 Value(LoroValue),
3606 /// A container.
3607 Container(Container),
3608}
3609
3610impl ValueOrContainer {
3611 /// Get the deep value of the value or container.
3612 pub fn get_deep_value(&self) -> LoroValue {
3613 match self {
3614 ValueOrContainer::Value(v) => v.clone(),
3615 ValueOrContainer::Container(c) => match c {
3616 Container::List(c) => c.get_deep_value(),
3617 Container::Map(c) => c.get_deep_value(),
3618 Container::Text(c) => c.to_string().into(),
3619 Container::Tree(c) => c.get_value(),
3620 Container::MovableList(c) => c.get_deep_value(),
3621 #[cfg(feature = "counter")]
3622 Container::Counter(c) => c.get_value().into(),
3623 Container::Unknown(_) => LoroValue::Null,
3624 },
3625 }
3626 }
3627
3628 pub(crate) fn into_value_or_handler(self) -> ValueOrHandler {
3629 match self {
3630 ValueOrContainer::Value(v) => ValueOrHandler::Value(v),
3631 ValueOrContainer::Container(c) => ValueOrHandler::Handler(c.to_handler()),
3632 }
3633 }
3634}
3635
3636/// UndoManager can be used to undo and redo the changes made to the document with a certain peer.
3637///
3638/// Notes & pitfalls:
3639/// - Local-only: undo/redo affects only local operations from the bound peer; it does not revert
3640/// remote edits. For global rollback, use time travel (`checkout`/`revert_to`).
3641/// - Peer identity: keep the `peer_id` stable while an `UndoManager` is in use. Changing peer IDs
3642/// can disrupt undo grouping/semantics.
3643/// - Grouping: you may want to tune the merge interval and exclude origins to group related edits.
3644#[derive(Debug)]
3645#[repr(transparent)]
3646pub struct UndoManager(InnerUndoManager);
3647
3648impl UndoManager {
3649 /// Create a new UndoManager.
3650 pub fn new(doc: &LoroDoc) -> Self {
3651 let inner = InnerUndoManager::new(&doc.doc);
3652 inner.set_max_undo_steps(100);
3653 Self(inner)
3654 }
3655
3656 /// Undo the last change made by the peer.
3657 pub fn undo(&mut self) -> LoroResult<bool> {
3658 self.0.undo()
3659 }
3660
3661 /// Redo the last change made by the peer.
3662 pub fn redo(&mut self) -> LoroResult<bool> {
3663 self.0.redo()
3664 }
3665
3666 /// Record a new checkpoint.
3667 pub fn record_new_checkpoint(&mut self) -> LoroResult<()> {
3668 self.0.record_new_checkpoint()
3669 }
3670
3671 /// Whether the undo manager can undo.
3672 pub fn can_undo(&self) -> bool {
3673 self.0.can_undo()
3674 }
3675
3676 /// Whether the undo manager can redo.
3677 pub fn can_redo(&self) -> bool {
3678 self.0.can_redo()
3679 }
3680
3681 /// How many times the undo manager can undo.
3682 pub fn undo_count(&self) -> usize {
3683 self.0.undo_count()
3684 }
3685
3686 /// How many times the undo manager can redo.
3687 pub fn redo_count(&self) -> usize {
3688 self.0.redo_count()
3689 }
3690
3691 /// If a local event's origin matches the given prefix, it will not be recorded in the
3692 /// undo stack.
3693 pub fn add_exclude_origin_prefix(&mut self, prefix: &str) {
3694 self.0.add_exclude_origin_prefix(prefix)
3695 }
3696
3697 /// Set the maximum number of undo steps. The default value is 100.
3698 pub fn set_max_undo_steps(&mut self, size: usize) {
3699 self.0.set_max_undo_steps(size)
3700 }
3701
3702 /// Set the merge interval in ms. The default value is 0, which means no merge.
3703 pub fn set_merge_interval(&mut self, interval: i64) {
3704 self.0.set_merge_interval(interval)
3705 }
3706
3707 /// Set the listener for push events.
3708 /// The listener will be called when a new undo/redo item is pushed into the stack.
3709 pub fn set_on_push(&mut self, on_push: Option<OnPush>) {
3710 if let Some(on_push) = on_push {
3711 self.0.set_on_push(Some(Box::new(move |u, c, e| {
3712 on_push(u, c, e.map(|x| x.into()))
3713 })));
3714 } else {
3715 self.0.set_on_push(None);
3716 }
3717 }
3718
3719 /// Set the listener for pop events.
3720 /// The listener will be called when an undo/redo item is popped from the stack.
3721 pub fn set_on_pop(&mut self, on_pop: Option<OnPop>) {
3722 self.0.set_on_pop(on_pop);
3723 }
3724
3725 /// Clear the undo stack and the redo stack
3726 pub fn clear(&self) {
3727 self.0.clear();
3728 }
3729
3730 /// Will start a new group of changes, all subsequent changes will be merged
3731 /// into a new item on the undo stack. If we receive remote changes, we determine
3732 /// wether or not they are conflicting. If the remote changes are conflicting
3733 /// we split the undo item and close the group. If there are no conflict
3734 /// in changed container ids we continue the group merge.
3735 pub fn group_start(&mut self) -> LoroResult<()> {
3736 self.0.group_start()
3737 }
3738
3739 /// Ends the current group, calling UndoManager::undo() after this will
3740 /// undo all changes that occurred during the group.
3741 pub fn group_end(&mut self) {
3742 self.0.group_end();
3743 }
3744
3745 /// Get the peer id of the undo manager.
3746 pub fn peer(&self) -> PeerID {
3747 self.0.peer()
3748 }
3749
3750 /// Get the metadata of the top undo stack item, if any.
3751 pub fn top_undo_meta(&self) -> Option<UndoItemMeta> {
3752 self.0.top_undo_meta()
3753 }
3754
3755 /// Get the metadata of the top redo stack item, if any.
3756 pub fn top_redo_meta(&self) -> Option<UndoItemMeta> {
3757 self.0.top_redo_meta()
3758 }
3759
3760 /// Get the value associated with the top undo stack item, if any.
3761 pub fn top_undo_value(&self) -> Option<LoroValue> {
3762 self.0.top_undo_value()
3763 }
3764
3765 /// Get the value associated with the top redo stack item, if any.
3766 pub fn top_redo_value(&self) -> Option<LoroValue> {
3767 self.0.top_redo_value()
3768 }
3769}
3770/// When a undo/redo item is pushed, the undo manager will call the on_push callback to get the meta data of the undo item.
3771/// The returned cursors will be recorded for a new pushed undo item.
3772pub type OnPush =
3773 Box<dyn for<'a> Fn(UndoOrRedo, CounterSpan, Option<DiffEvent>) -> UndoItemMeta + Send + Sync>;