loro/lib.rs
1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3#![warn(missing_debug_implementations)]
4use event::DiffBatch;
5use event::{DiffEvent, Subscriber};
6use fxhash::FxHashSet;
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, PreCommitCallback,
17};
18pub use loro_internal::sync;
19pub use loro_internal::undo::{OnPop, UndoItemMeta, UndoOrRedo};
20use loro_internal::version::shrink_frontiers;
21pub use loro_internal::version::ImVersionVector;
22use loro_internal::DocState;
23use loro_internal::LoroDoc as InnerLoroDoc;
24use loro_internal::OpLog;
25use loro_internal::{
26 handler::Handler as InnerHandler, ListHandler as InnerListHandler,
27 MapHandler as InnerMapHandler, MovableListHandler as InnerMovableListHandler,
28 TextHandler as InnerTextHandler, TreeHandler as InnerTreeHandler,
29 UnknownHandler as InnerUnknownHandler,
30};
31use std::cmp::Ordering;
32use std::ops::ControlFlow;
33use std::ops::Deref;
34use std::ops::Range;
35use std::sync::Arc;
36use tracing::info;
37
38pub use loro_internal::diff::diff_impl::UpdateOptions;
39pub use loro_internal::diff::diff_impl::UpdateTimeoutError;
40pub use loro_internal::subscription::LocalUpdateCallback;
41pub use loro_internal::subscription::PeerIdUpdateCallback;
42pub use loro_internal::ChangeMeta;
43pub use loro_internal::LORO_VERSION;
44pub mod event;
45pub use loro_internal::awareness;
46pub use loro_internal::change::Timestamp;
47pub use loro_internal::configure::Configure;
48pub use loro_internal::configure::{StyleConfig, StyleConfigMap};
49pub use loro_internal::container::richtext::ExpandType;
50pub use loro_internal::container::{ContainerID, ContainerType, IntoContainerId};
51pub use loro_internal::cursor;
52pub use loro_internal::delta::{TreeDeltaItem, TreeDiff, TreeDiffItem, TreeExternalDiff};
53pub use loro_internal::encoding::ImportBlobMetadata;
54pub use loro_internal::encoding::{EncodedBlobMode, ExportMode};
55pub use loro_internal::event::{EventTriggerKind, Index};
56pub use loro_internal::handler::TextDelta;
57pub use loro_internal::json;
58pub use loro_internal::json::{
59 FutureOp as JsonFutureOp, FutureOpWrapper as JsonFutureOpWrapper, JsonChange, JsonOp,
60 JsonOpContent, JsonSchema, ListOp as JsonListOp, MapOp as JsonMapOp,
61 MovableListOp as JsonMovableListOp, TextOp as JsonTextOp, TreeOp as JsonTreeOp,
62};
63pub use loro_internal::kv_store::{KvStore, MemKvStore};
64pub use loro_internal::loro::CommitOptions;
65pub use loro_internal::loro::DocAnalysis;
66pub use loro_internal::oplog::FrontiersNotIncluded;
67pub use loro_internal::undo;
68pub use loro_internal::version::{Frontiers, VersionRange, VersionVector, VersionVectorDiff};
69pub use loro_internal::ApplyDiff;
70pub use loro_internal::Subscription;
71pub use loro_internal::UndoManager as InnerUndoManager;
72pub use loro_internal::{loro_value, to_value};
73pub use loro_internal::{
74 Counter, CounterSpan, FractionalIndex, IdLp, IdSpan, Lamport, PeerID, TreeID, TreeParentId, ID,
75};
76pub use loro_internal::{
77 LoroBinaryValue, LoroEncodeError, LoroError, LoroListValue, LoroMapValue, LoroResult,
78 LoroStringValue, LoroTreeError, LoroValue, ToJson,
79};
80pub use loro_kv_store as kv_store;
81
82#[cfg(feature = "jsonpath")]
83pub use loro_internal::jsonpath;
84#[cfg(feature = "jsonpath")]
85pub use loro_internal::jsonpath::JsonPathError;
86
87#[cfg(feature = "counter")]
88mod counter;
89#[cfg(feature = "counter")]
90pub use counter::LoroCounter;
91
92/// `LoroDoc` is the entry for the whole document.
93/// When it's dropped, all the associated [`Handler`]s will be invalidated.
94///
95/// **Important:** Loro is a pure library and does not handle network protocols.
96/// It is the responsibility of the user to manage the storage, loading, and synchronization
97/// of the bytes exported by Loro in a manner suitable for their specific environment.
98#[derive(Debug)]
99pub struct LoroDoc {
100 doc: InnerLoroDoc,
101 // This field is here to prevent some weird issues in debug mode
102 #[cfg(debug_assertions)]
103 _temp: u8,
104}
105
106impl Default for LoroDoc {
107 fn default() -> Self {
108 Self::new()
109 }
110}
111
112impl Clone for LoroDoc {
113 fn clone(&self) -> Self {
114 let doc = self.doc.clone();
115 LoroDoc::_new(doc)
116 }
117}
118
119impl LoroDoc {
120 #[inline(always)]
121 fn _new(doc: InnerLoroDoc) -> Self {
122 Self {
123 doc,
124 #[cfg(debug_assertions)]
125 _temp: 0,
126 }
127 }
128
129 /// Create a new `LoroDoc` instance.
130 #[inline]
131 pub fn new() -> Self {
132 let doc = InnerLoroDoc::default();
133 doc.start_auto_commit();
134
135 LoroDoc::_new(doc)
136 }
137
138 /// Duplicate the document with a different PeerID
139 ///
140 /// The time complexity and space complexity of this operation are both O(n),
141 ///
142 /// When called in detached mode, it will fork at the current state frontiers.
143 /// It will have the same effect as `fork_at(&self.state_frontiers())`.
144 #[inline]
145 pub fn fork(&self) -> Self {
146 let doc = self.doc.fork();
147 LoroDoc::_new(doc)
148 }
149
150 /// Fork the document at the given frontiers.
151 ///
152 /// The created doc will only contain the history before the specified frontiers.
153 pub fn fork_at(&self, frontiers: &Frontiers) -> LoroDoc {
154 let new_doc = self.doc.fork_at(frontiers);
155 new_doc.start_auto_commit();
156 LoroDoc::_new(new_doc)
157 }
158
159 /// Get the configurations of the document.
160 #[inline]
161 pub fn config(&self) -> &Configure {
162 self.doc.config()
163 }
164
165 /// Get `Change` at the given id.
166 ///
167 /// `Change` is a grouped continuous operations that share the same id, timestamp, commit message.
168 ///
169 /// - The id of the `Change` is the id of its first op.
170 /// - The second op's id is `{ peer: change.id.peer, counter: change.id.counter + 1 }`
171 ///
172 /// The same applies on `Lamport`:
173 ///
174 /// - The lamport of the `Change` is the lamport of its first op.
175 /// - The second op's lamport is `change.lamport + 1`
176 ///
177 /// The length of the `Change` is how many operations it contains
178 pub fn get_change(&self, id: ID) -> Option<ChangeMeta> {
179 let change = self.doc.oplog().lock().unwrap().get_change_at(id)?;
180 Some(ChangeMeta::from_change(&change))
181 }
182
183 /// Decodes the metadata for an imported blob from the provided bytes.
184 #[inline]
185 pub fn decode_import_blob_meta(
186 bytes: &[u8],
187 check_checksum: bool,
188 ) -> LoroResult<ImportBlobMetadata> {
189 InnerLoroDoc::decode_import_blob_meta(bytes, check_checksum)
190 }
191
192 /// Set whether to record the timestamp of each change. Default is `false`.
193 ///
194 /// If enabled, the Unix timestamp will be recorded for each change automatically.
195 ///
196 /// You can set each timestamp manually when committing a change.
197 ///
198 /// NOTE: Timestamps are forced to be in ascending order.
199 /// If you commit a new change with a timestamp that is less than the existing one,
200 /// the largest existing timestamp will be used instead.
201 #[inline]
202 pub fn set_record_timestamp(&self, record: bool) {
203 self.doc.set_record_timestamp(record);
204 }
205
206 /// Enables editing in detached mode, which is disabled by default.
207 ///
208 /// The doc enter detached mode after calling `detach` or checking out a non-latest version.
209 ///
210 /// # Important Notes:
211 ///
212 /// - This mode uses a different PeerID for each checkout.
213 /// - Ensure no concurrent operations share the same PeerID if set manually.
214 /// - Importing does not affect the document's state or version; changes are
215 /// recorded in the [OpLog] only. Call `checkout` to apply changes.
216 #[inline]
217 pub fn set_detached_editing(&self, enable: bool) {
218 self.doc.set_detached_editing(enable);
219 }
220
221 /// Whether editing the doc in detached mode is allowed, which is disabled by
222 /// default.
223 ///
224 /// The doc enter detached mode after calling `detach` or checking out a non-latest version.
225 ///
226 /// # Important Notes:
227 ///
228 /// - This mode uses a different PeerID for each checkout.
229 /// - Ensure no concurrent operations share the same PeerID if set manually.
230 /// - Importing does not affect the document's state or version; changes are
231 /// recorded in the [OpLog] only. Call `checkout` to apply changes.
232 #[inline]
233 pub fn is_detached_editing_enabled(&self) -> bool {
234 self.doc.is_detached_editing_enabled()
235 }
236
237 /// Set the interval of mergeable changes, **in seconds**.
238 ///
239 /// If two continuous local changes are within the interval, they will be merged into one change.
240 /// The default value is 1000 seconds.
241 ///
242 /// By default, we record timestamps in seconds for each change. So if the merge interval is 1, and changes A and B
243 /// have timestamps of 3 and 4 respectively, then they will be merged into one change
244 #[inline]
245 pub fn set_change_merge_interval(&self, interval: i64) {
246 self.doc.set_change_merge_interval(interval);
247 }
248
249 /// Set the rich text format configuration of the document.
250 ///
251 /// You need to config it if you use rich text `mark` method.
252 /// Specifically, you need to config the `expand` property of each style.
253 ///
254 /// Expand is used to specify the behavior of expanding when new text is inserted at the
255 /// beginning or end of the style.
256 #[inline]
257 pub fn config_text_style(&self, text_style: StyleConfigMap) {
258 self.doc.config_text_style(text_style)
259 }
260
261 /// Configures the default text style for the document.
262 ///
263 /// This method sets the default text style configuration for the document when using LoroText.
264 /// If `None` is provided, the default style is reset.
265 ///
266 /// # Parameters
267 ///
268 /// - `text_style`: The style configuration to set as the default. `None` to reset.
269 pub fn config_default_text_style(&self, text_style: Option<StyleConfig>) {
270 self.doc.config_default_text_style(text_style);
271 }
272
273 /// Attach the document state to the latest known version.
274 ///
275 /// > The document becomes detached during a `checkout` operation.
276 /// > Being `detached` implies that the `DocState` is not synchronized with the latest version of the `OpLog`.
277 /// > In a detached state, the document is not editable, and any `import` operations will be
278 /// > recorded in the `OpLog` without being applied to the `DocState`.
279 #[inline]
280 pub fn attach(&self) {
281 self.doc.attach()
282 }
283
284 /// Checkout the `DocState` to a specific version.
285 ///
286 /// The document becomes detached during a `checkout` operation.
287 /// Being `detached` implies that the `DocState` is not synchronized with the latest version of the `OpLog`.
288 /// In a detached state, the document is not editable, and any `import` operations will be
289 /// recorded in the `OpLog` without being applied to the `DocState`.
290 ///
291 /// You should call `attach` to attach the `DocState` to the latest version of `OpLog`.
292 #[inline]
293 pub fn checkout(&self, frontiers: &Frontiers) -> LoroResult<()> {
294 self.doc.checkout(frontiers)
295 }
296
297 /// Checkout the `DocState` to the latest version.
298 ///
299 /// > The document becomes detached during a `checkout` operation.
300 /// > Being `detached` implies that the `DocState` is not synchronized with the latest version of the `OpLog`.
301 /// > In a detached state, the document is not editable, and any `import` operations will be
302 /// > recorded in the `OpLog` without being applied to the `DocState`.
303 ///
304 /// This has the same effect as `attach`.
305 #[inline]
306 pub fn checkout_to_latest(&self) {
307 self.doc.checkout_to_latest()
308 }
309
310 /// Compare the frontiers with the current OpLog's version.
311 ///
312 /// If `other` contains any version that's not contained in the current OpLog, return [Ordering::Less].
313 #[inline]
314 pub fn cmp_with_frontiers(&self, other: &Frontiers) -> Ordering {
315 self.doc.cmp_with_frontiers(other)
316 }
317
318 /// Compare two frontiers.
319 ///
320 /// If the frontiers are not included in the document, return [`FrontiersNotIncluded`].
321 #[inline]
322 pub fn cmp_frontiers(
323 &self,
324 a: &Frontiers,
325 b: &Frontiers,
326 ) -> Result<Option<Ordering>, FrontiersNotIncluded> {
327 self.doc.cmp_frontiers(a, b)
328 }
329
330 /// Force the document enter the detached mode.
331 ///
332 /// In this mode, when you importing new updates, the [loro_internal::DocState] will not be changed.
333 ///
334 /// Learn more at https://loro.dev/docs/advanced/doc_state_and_oplog#attacheddetached-status
335 #[inline]
336 pub fn detach(&self) {
337 self.doc.detach()
338 }
339
340 /// Import a batch of updates/snapshot.
341 ///
342 /// The data can be in arbitrary order. The import result will be the same.
343 #[inline]
344 pub fn import_batch(&self, bytes: &[Vec<u8>]) -> LoroResult<ImportStatus> {
345 self.doc.import_batch(bytes)
346 }
347
348 /// Get a [LoroMovableList] by container id.
349 ///
350 /// If the provided id is string, it will be converted into a root container id with the name of the string.
351 #[inline]
352 pub fn get_movable_list<I: IntoContainerId>(&self, id: I) -> LoroMovableList {
353 LoroMovableList {
354 handler: self.doc.get_movable_list(id),
355 }
356 }
357
358 /// Get a [LoroList] by container id.
359 ///
360 /// If the provided id is string, it will be converted into a root container id with the name of the string.
361 #[inline]
362 pub fn get_list<I: IntoContainerId>(&self, id: I) -> LoroList {
363 LoroList {
364 handler: self.doc.get_list(id),
365 }
366 }
367
368 /// Get a [LoroMap] by container id.
369 ///
370 /// If the provided id is string, it will be converted into a root container id with the name of the string.
371 #[inline]
372 pub fn get_map<I: IntoContainerId>(&self, id: I) -> LoroMap {
373 LoroMap {
374 handler: self.doc.get_map(id),
375 }
376 }
377
378 /// Get a [LoroText] by container id.
379 ///
380 /// If the provided id is string, it will be converted into a root container id with the name of the string.
381 #[inline]
382 pub fn get_text<I: IntoContainerId>(&self, id: I) -> LoroText {
383 LoroText {
384 handler: self.doc.get_text(id),
385 }
386 }
387
388 /// Get a [LoroTree] by container id.
389 ///
390 /// If the provided id is string, it will be converted into a root container id with the name of the string.
391 #[inline]
392 pub fn get_tree<I: IntoContainerId>(&self, id: I) -> LoroTree {
393 LoroTree {
394 handler: self.doc.get_tree(id),
395 }
396 }
397
398 #[cfg(feature = "counter")]
399 /// Get a [LoroCounter] by container id.
400 ///
401 /// If the provided id is string, it will be converted into a root container id with the name of the string.
402 #[inline]
403 pub fn get_counter<I: IntoContainerId>(&self, id: I) -> LoroCounter {
404 LoroCounter {
405 handler: self.doc.get_counter(id),
406 }
407 }
408
409 /// Commit the cumulative auto commit transaction.
410 ///
411 /// There is a transaction behind every operation.
412 /// The events will be emitted after a transaction is committed. A transaction is committed when:
413 ///
414 /// - `doc.commit()` is called.
415 /// - `doc.export(mode)` is called.
416 /// - `doc.import(data)` is called.
417 /// - `doc.checkout(version)` is called.
418 #[inline]
419 pub fn commit(&self) {
420 self.doc.commit_then_renew();
421 }
422
423 /// Commit the cumulative auto commit transaction with custom configure.
424 ///
425 /// There is a transaction behind every operation.
426 /// It will automatically commit when users invoke export or import.
427 /// The event will be sent after a transaction is committed
428 #[inline]
429 pub fn commit_with(&self, options: CommitOptions) {
430 self.doc.commit_with(options);
431 }
432
433 /// Set commit message for the current uncommitted changes
434 ///
435 /// It will be persisted.
436 pub fn set_next_commit_message(&self, msg: &str) {
437 self.doc.set_next_commit_message(msg)
438 }
439
440 /// Set `origin` for the current uncommitted changes, it can be used to track the source of changes in an event.
441 ///
442 /// It will NOT be persisted.
443 pub fn set_next_commit_origin(&self, origin: &str) {
444 self.doc.set_next_commit_origin(origin)
445 }
446
447 /// Set the timestamp of the next commit.
448 ///
449 /// It will be persisted and stored in the `OpLog`.
450 /// You can get the timestamp from the [`Change`] type.
451 pub fn set_next_commit_timestamp(&self, timestamp: Timestamp) {
452 self.doc.set_next_commit_timestamp(timestamp)
453 }
454
455 /// Set the options of the next commit.
456 ///
457 /// It will be used when the next commit is performed.
458 pub fn set_next_commit_options(&self, options: CommitOptions) {
459 self.doc.set_next_commit_options(options);
460 }
461
462 /// Clear the options of the next commit.
463 pub fn clear_next_commit_options(&self) {
464 self.doc.clear_next_commit_options();
465 }
466
467 /// Whether the document is in detached mode, where the [loro_internal::DocState] is not
468 /// synchronized with the latest version of the [loro_internal::OpLog].
469 #[inline]
470 pub fn is_detached(&self) -> bool {
471 self.doc.is_detached()
472 }
473
474 /// Import updates/snapshot exported by [`LoroDoc::export_snapshot`] or [`LoroDoc::export_from`].
475 #[inline]
476 pub fn import(&self, bytes: &[u8]) -> Result<ImportStatus, LoroError> {
477 self.doc.import_with(bytes, "".into())
478 }
479
480 /// Import updates/snapshot exported by [`LoroDoc::export_snapshot`] or [`LoroDoc::export_from`].
481 ///
482 /// It marks the import with a custom `origin` string. It can be used to track the import source
483 /// in the generated events.
484 #[inline]
485 pub fn import_with(&self, bytes: &[u8], origin: &str) -> Result<ImportStatus, LoroError> {
486 self.doc.import_with(bytes, origin.into())
487 }
488
489 /// Import the json schema updates.
490 ///
491 /// only supports backward compatibility but not forward compatibility.
492 #[inline]
493 pub fn import_json_updates<T: TryInto<JsonSchema>>(
494 &self,
495 json: T,
496 ) -> Result<ImportStatus, LoroError> {
497 self.doc.import_json_updates(json)
498 }
499
500 /// Export the current state with json-string format of the document.
501 #[inline]
502 pub fn export_json_updates(
503 &self,
504 start_vv: &VersionVector,
505 end_vv: &VersionVector,
506 ) -> JsonSchema {
507 self.doc.export_json_updates(start_vv, end_vv, true)
508 }
509
510 /// Export the current state with json-string format of the document, without peer compression.
511 ///
512 /// Compared to [`export_json_updates`], this method does not compress the peer IDs in the updates.
513 /// So the operations are easier to be processed by application code.
514 #[inline]
515 pub fn export_json_updates_without_peer_compression(
516 &self,
517 start_vv: &VersionVector,
518 end_vv: &VersionVector,
519 ) -> JsonSchema {
520 self.doc.export_json_updates(start_vv, end_vv, false)
521 }
522
523 /// Exports changes within the specified ID span to JSON schema format.
524 ///
525 /// The JSON schema format produced by this method is identical to the one generated by `export_json_updates`.
526 /// It ensures deterministic output, making it ideal for hash calculations and integrity checks.
527 ///
528 /// This method can also export pending changes from the uncommitted transaction that have not yet been applied to the OpLog.
529 ///
530 /// This method will NOT trigger a new commit implicitly.
531 ///
532 /// # Example
533 /// ```
534 /// use loro::{LoroDoc, IdSpan};
535 ///
536 /// let doc = LoroDoc::new();
537 /// doc.set_peer_id(0).unwrap();
538 /// doc.get_text("text").insert(0, "a").unwrap();
539 /// doc.commit();
540 /// let doc_clone = doc.clone();
541 /// let _sub = doc.subscribe_pre_commit(Box::new(move |e| {
542 /// let changes = doc_clone.export_json_in_id_span(IdSpan::new(
543 /// 0,
544 /// 0,
545 /// e.change_meta.id.counter + e.change_meta.len as i32,
546 /// ));
547 /// // 2 because commit one and the uncommit one
548 /// assert_eq!(changes.len(), 2);
549 /// true
550 /// }));
551 /// doc.get_text("text").insert(0, "b").unwrap();
552 /// let changes = doc.export_json_in_id_span(IdSpan::new(0, 0, 2));
553 /// assert_eq!(changes.len(), 1);
554 /// doc.commit();
555 /// // change merged
556 /// assert_eq!(changes.len(), 1);
557 /// ```
558 pub fn export_json_in_id_span(&self, id_span: IdSpan) -> Vec<JsonChange> {
559 self.doc.export_json_in_id_span(id_span)
560 }
561
562 /// Export all the ops not included in the given `VersionVector`
563 #[deprecated(
564 since = "1.0.0",
565 note = "Use `export` with `ExportMode::Updates` instead"
566 )]
567 #[inline]
568 pub fn export_from(&self, vv: &VersionVector) -> Vec<u8> {
569 self.doc.export_from(vv)
570 }
571
572 /// Export the current state and history of the document.
573 #[deprecated(
574 since = "1.0.0",
575 note = "Use `export` with `ExportMode::Snapshot` instead"
576 )]
577 #[inline]
578 pub fn export_snapshot(&self) -> Vec<u8> {
579 self.doc.export_snapshot().unwrap()
580 }
581
582 /// Convert `Frontiers` into `VersionVector`
583 #[inline]
584 pub fn frontiers_to_vv(&self, frontiers: &Frontiers) -> Option<VersionVector> {
585 self.doc.frontiers_to_vv(frontiers)
586 }
587
588 /// Minimize the frontiers by removing the unnecessary entries.
589 pub fn minimize_frontiers(&self, frontiers: &Frontiers) -> Result<Frontiers, ID> {
590 self.with_oplog(|oplog| shrink_frontiers(frontiers, oplog.dag()))
591 }
592
593 /// Convert `VersionVector` into `Frontiers`
594 #[inline]
595 pub fn vv_to_frontiers(&self, vv: &VersionVector) -> Frontiers {
596 self.doc.vv_to_frontiers(vv)
597 }
598
599 /// Access the `OpLog`.
600 ///
601 /// NOTE: Please be ware that the API in `OpLog` is unstable
602 #[inline]
603 pub fn with_oplog<R>(&self, f: impl FnOnce(&OpLog) -> R) -> R {
604 let oplog = self.doc.oplog().lock().unwrap();
605 f(&oplog)
606 }
607
608 /// Access the `DocState`.
609 ///
610 /// NOTE: Please be ware that the API in `DocState` is unstable
611 #[inline]
612 pub fn with_state<R>(&self, f: impl FnOnce(&mut DocState) -> R) -> R {
613 let mut state = self.doc.app_state().lock().unwrap();
614 f(&mut state)
615 }
616
617 /// Get the `VersionVector` version of `OpLog`
618 #[inline]
619 pub fn oplog_vv(&self) -> VersionVector {
620 self.doc.oplog_vv()
621 }
622
623 /// Get the `VersionVector` version of `DocState`
624 #[inline]
625 pub fn state_vv(&self) -> VersionVector {
626 self.doc.state_vv()
627 }
628
629 /// The doc only contains the history since this version
630 ///
631 /// This is empty if the doc is not shallow.
632 ///
633 /// The ops included by the shallow history start version vector are not in the doc.
634 #[inline]
635 pub fn shallow_since_vv(&self) -> ImVersionVector {
636 self.doc.shallow_since_vv()
637 }
638
639 /// The doc only contains the history since this version
640 ///
641 /// This is empty if the doc is not shallow.
642 ///
643 /// The ops included by the shallow history start frontiers are not in the doc.
644 #[inline]
645 pub fn shallow_since_frontiers(&self) -> Frontiers {
646 self.doc.shallow_since_frontiers()
647 }
648
649 /// Get the total number of operations in the `OpLog`
650 #[inline]
651 pub fn len_ops(&self) -> usize {
652 self.doc.len_ops()
653 }
654
655 /// Get the total number of changes in the `OpLog`
656 #[inline]
657 pub fn len_changes(&self) -> usize {
658 self.doc.len_changes()
659 }
660
661 /// Get the shallow value of the document.
662 #[inline]
663 pub fn get_value(&self) -> LoroValue {
664 self.doc.get_value()
665 }
666
667 /// Get the entire state of the current DocState
668 #[inline]
669 pub fn get_deep_value(&self) -> LoroValue {
670 self.doc.get_deep_value()
671 }
672
673 /// Get the entire state of the current DocState with container id
674 pub fn get_deep_value_with_id(&self) -> LoroValue {
675 self.doc
676 .app_state()
677 .lock()
678 .unwrap()
679 .get_deep_value_with_id()
680 }
681
682 /// Get the `Frontiers` version of `OpLog`
683 #[inline]
684 pub fn oplog_frontiers(&self) -> Frontiers {
685 self.doc.oplog_frontiers()
686 }
687
688 /// Get the `Frontiers` version of `DocState`
689 ///
690 /// Learn more about [`Frontiers`](https://loro.dev/docs/advanced/version_deep_dive)
691 #[inline]
692 pub fn state_frontiers(&self) -> Frontiers {
693 self.doc.state_frontiers()
694 }
695
696 /// Get the PeerID
697 #[inline]
698 pub fn peer_id(&self) -> PeerID {
699 self.doc.peer_id()
700 }
701
702 /// Change the PeerID
703 ///
704 /// NOTE: You need to make sure there is no chance two peer have the same PeerID.
705 /// If it happens, the document will be corrupted.
706 #[inline]
707 pub fn set_peer_id(&self, peer: PeerID) -> LoroResult<()> {
708 self.doc.set_peer_id(peer)
709 }
710
711 /// Subscribe the events of a container.
712 ///
713 /// The callback will be invoked after a transaction that change the container.
714 /// Returns a subscription that can be used to unsubscribe.
715 ///
716 /// The events will be emitted after a transaction is committed. A transaction is committed when:
717 ///
718 /// - `doc.commit()` is called.
719 /// - `doc.export(mode)` is called.
720 /// - `doc.import(data)` is called.
721 /// - `doc.checkout(version)` is called.
722 ///
723 /// # Example
724 ///
725 /// ```
726 /// # use loro::LoroDoc;
727 /// # use std::sync::{atomic::AtomicBool, Arc};
728 /// # use loro::{event::DiffEvent, LoroResult, TextDelta};
729 /// #
730 /// let doc = LoroDoc::new();
731 /// let text = doc.get_text("text");
732 /// let ran = Arc::new(AtomicBool::new(false));
733 /// let ran2 = ran.clone();
734 /// let sub = doc.subscribe(
735 /// &text.id(),
736 /// Arc::new(move |event| {
737 /// assert!(event.triggered_by.is_local());
738 /// for event in event.events {
739 /// let delta = event.diff.as_text().unwrap();
740 /// let d = TextDelta::Insert {
741 /// insert: "123".into(),
742 /// attributes: Default::default(),
743 /// };
744 /// assert_eq!(delta, &vec![d]);
745 /// ran2.store(true, std::sync::atomic::Ordering::Relaxed);
746 /// }
747 /// }),
748 /// );
749 /// text.insert(0, "123").unwrap();
750 /// doc.commit();
751 /// assert!(ran.load(std::sync::atomic::Ordering::Relaxed));
752 /// // unsubscribe
753 /// sub.unsubscribe();
754 /// ```
755 #[inline]
756 pub fn subscribe(&self, container_id: &ContainerID, callback: Subscriber) -> Subscription {
757 self.doc.subscribe(
758 container_id,
759 Arc::new(move |e| {
760 callback(DiffEvent::from(e));
761 }),
762 )
763 }
764
765 /// Subscribe all the events.
766 ///
767 /// The callback will be invoked when any part of the [loro_internal::DocState] is changed.
768 /// Returns a subscription that can be used to unsubscribe.
769 ///
770 /// The events will be emitted after a transaction is committed. A transaction is committed when:
771 ///
772 /// - `doc.commit()` is called.
773 /// - `doc.export(mode)` is called.
774 /// - `doc.import(data)` is called.
775 /// - `doc.checkout(version)` is called.
776 #[inline]
777 pub fn subscribe_root(&self, callback: Subscriber) -> Subscription {
778 // self.doc.subscribe_root(callback)
779 self.doc.subscribe_root(Arc::new(move |e| {
780 callback(DiffEvent::from(e));
781 }))
782 }
783
784 /// Subscribe the local update of the document.
785 pub fn subscribe_local_update(&self, callback: LocalUpdateCallback) -> Subscription {
786 self.doc.subscribe_local_update(callback)
787 }
788
789 /// Subscribe the peer id change of the document.
790 pub fn subscribe_peer_id_change(&self, callback: PeerIdUpdateCallback) -> Subscription {
791 self.doc.subscribe_peer_id_change(callback)
792 }
793
794 /// Estimate the size of the document states in memory.
795 #[inline]
796 pub fn log_estimate_size(&self) {
797 self.doc.log_estimated_size();
798 }
799
800 /// Check the correctness of the document state by comparing it with the state
801 /// calculated by applying all the history.
802 #[inline]
803 pub fn check_state_correctness_slow(&self) {
804 self.doc.check_state_diff_calc_consistency_slow()
805 }
806
807 /// Get the handler by the path.
808 #[inline]
809 pub fn get_by_path(&self, path: &[Index]) -> Option<ValueOrContainer> {
810 self.doc.get_by_path(path).map(ValueOrContainer::from)
811 }
812
813 /// Get the handler by the string path.
814 ///
815 /// The path can be specified in different ways depending on the container type:
816 ///
817 /// For Tree:
818 /// 1. Using node IDs: `tree/{node_id}/property`
819 /// 2. Using indices: `tree/0/1/property`
820 ///
821 /// For List and MovableList:
822 /// - Using indices: `list/0` or `list/1/property`
823 ///
824 /// For Map:
825 /// - Using keys: `map/key` or `map/nested/property`
826 ///
827 /// For tree structures, index-based paths follow depth-first traversal order.
828 /// The indices start from 0 and represent the position of a node among its siblings.
829 ///
830 /// # Examples
831 /// ```
832 /// # use loro::{LoroDoc, LoroValue};
833 /// let doc = LoroDoc::new();
834 ///
835 /// // Tree example
836 /// let tree = doc.get_tree("tree");
837 /// let root = tree.create(None).unwrap();
838 /// tree.get_meta(root).unwrap().insert("name", "root").unwrap();
839 /// // Access tree by ID or index
840 /// let name1 = doc.get_by_str_path(&format!("tree/{}/name", root)).unwrap().into_value().unwrap();
841 /// let name2 = doc.get_by_str_path("tree/0/name").unwrap().into_value().unwrap();
842 /// assert_eq!(name1, name2);
843 ///
844 /// // List example
845 /// let list = doc.get_list("list");
846 /// list.insert(0, "first").unwrap();
847 /// list.insert(1, "second").unwrap();
848 /// // Access list by index
849 /// let item = doc.get_by_str_path("list/0");
850 /// assert_eq!(item.unwrap().into_value().unwrap().into_string().unwrap(), "first".into());
851 ///
852 /// // Map example
853 /// let map = doc.get_map("map");
854 /// map.insert("key", "value").unwrap();
855 /// // Access map by key
856 /// let value = doc.get_by_str_path("map/key");
857 /// assert_eq!(value.unwrap().into_value().unwrap().into_string().unwrap(), "value".into());
858 ///
859 /// // MovableList example
860 /// let mlist = doc.get_movable_list("mlist");
861 /// mlist.insert(0, "item").unwrap();
862 /// // Access movable list by index
863 /// let item = doc.get_by_str_path("mlist/0");
864 /// assert_eq!(item.unwrap().into_value().unwrap().into_string().unwrap(), "item".into());
865 /// ```
866 #[inline]
867 pub fn get_by_str_path(&self, path: &str) -> Option<ValueOrContainer> {
868 self.doc.get_by_str_path(path).map(ValueOrContainer::from)
869 }
870
871 /// Get the absolute position of the given cursor.
872 ///
873 /// # Example
874 ///
875 /// ```
876 /// # use loro::{LoroDoc, ToJson};
877 /// let doc = LoroDoc::new();
878 /// let text = &doc.get_text("text");
879 /// text.insert(0, "01234").unwrap();
880 /// let pos = text.get_cursor(5, Default::default()).unwrap();
881 /// assert_eq!(doc.get_cursor_pos(&pos).unwrap().current.pos, 5);
882 /// text.insert(0, "01234").unwrap();
883 /// assert_eq!(doc.get_cursor_pos(&pos).unwrap().current.pos, 10);
884 /// text.delete(0, 10).unwrap();
885 /// assert_eq!(doc.get_cursor_pos(&pos).unwrap().current.pos, 0);
886 /// text.insert(0, "01234").unwrap();
887 /// assert_eq!(doc.get_cursor_pos(&pos).unwrap().current.pos, 5);
888 /// ```
889 #[inline]
890 pub fn get_cursor_pos(
891 &self,
892 cursor: &Cursor,
893 ) -> Result<PosQueryResult, CannotFindRelativePosition> {
894 self.doc.query_pos(cursor)
895 }
896
897 /// Get the inner LoroDoc ref.
898 #[inline]
899 pub fn inner(&self) -> &InnerLoroDoc {
900 &self.doc
901 }
902
903 /// Whether the history cache is built.
904 #[inline]
905 pub fn has_history_cache(&self) -> bool {
906 self.doc.has_history_cache()
907 }
908
909 /// Free the history cache that is used for making checkout faster.
910 ///
911 /// If you use checkout that switching to an old/concurrent version, the history cache will be built.
912 /// You can free it by calling this method.
913 #[inline]
914 pub fn free_history_cache(&self) {
915 self.doc.free_history_cache()
916 }
917
918 /// Free the cached diff calculator that is used for checkout.
919 #[inline]
920 pub fn free_diff_calculator(&self) {
921 self.doc.free_diff_calculator()
922 }
923
924 /// Encoded all ops and history cache to bytes and store them in the kv store.
925 ///
926 /// This will free up the memory that used by parsed ops
927 #[inline]
928 pub fn compact_change_store(&self) {
929 self.doc.compact_change_store()
930 }
931
932 /// Export the document in the given mode.
933 pub fn export(&self, mode: ExportMode) -> Result<Vec<u8>, LoroEncodeError> {
934 self.doc.export(mode)
935 }
936
937 /// Analyze the container info of the doc
938 ///
939 /// This is used for development and debugging. It can be slow.
940 pub fn analyze(&self) -> DocAnalysis {
941 self.doc.analyze()
942 }
943
944 /// Get the path from the root to the container
945 pub fn get_path_to_container(&self, id: &ContainerID) -> Option<Vec<(ContainerID, Index)>> {
946 self.doc.get_path_to_container(id)
947 }
948
949 /// Evaluate a JSONPath expression on the document and return matching values or handlers.
950 ///
951 /// This method allows querying the document structure using JSONPath syntax.
952 /// It returns a vector of `ValueOrHandler` which can represent either primitive values
953 /// or container handlers, depending on what the JSONPath expression matches.
954 ///
955 /// # Arguments
956 ///
957 /// * `path` - A string slice containing the JSONPath expression to evaluate.
958 ///
959 /// # Returns
960 ///
961 /// A `Result` containing either:
962 /// - `Ok(Vec<ValueOrHandler>)`: A vector of matching values or handlers.
963 /// - `Err(String)`: An error message if the JSONPath expression is invalid or evaluation fails.
964 ///
965 /// # Example
966 ///
967 /// ```
968 /// # use loro::{LoroDoc, ToJson};
969 ///
970 /// let doc = LoroDoc::new();
971 /// let map = doc.get_map("users");
972 /// map.insert("alice", 30).unwrap();
973 /// map.insert("bob", 25).unwrap();
974 ///
975 /// let result = doc.jsonpath("$.users.alice").unwrap();
976 /// assert_eq!(result.len(), 1);
977 /// assert_eq!(result[0].as_value().unwrap().to_json_value(), serde_json::json!(30));
978 /// ```
979 #[inline]
980 #[cfg(feature = "jsonpath")]
981 pub fn jsonpath(&self, path: &str) -> Result<Vec<ValueOrContainer>, JsonPathError> {
982 self.doc
983 .jsonpath(path)
984 .map(|vec| vec.into_iter().map(ValueOrContainer::from).collect())
985 }
986
987 /// Get the number of operations in the pending transaction.
988 ///
989 /// The pending transaction is the one that is not committed yet. It will be committed
990 /// after calling `doc.commit()`, `doc.export(mode)` or `doc.checkout(version)`.
991 pub fn get_pending_txn_len(&self) -> usize {
992 self.doc.get_pending_txn_len()
993 }
994
995 /// Traverses the ancestors of the Change containing the given ID, including itself.
996 ///
997 /// This method visits all ancestors in causal order, from the latest to the oldest,
998 /// based on their Lamport timestamps.
999 ///
1000 /// # Arguments
1001 ///
1002 /// * `ids` - The IDs of the Change to start the traversal from.
1003 /// * `f` - A mutable function that is called for each ancestor. It can return `ControlFlow::Break(())` to stop the traversal.
1004 pub fn travel_change_ancestors(
1005 &self,
1006 ids: &[ID],
1007 f: &mut dyn FnMut(ChangeMeta) -> ControlFlow<()>,
1008 ) -> Result<(), ChangeTravelError> {
1009 self.doc.travel_change_ancestors(ids, f)
1010 }
1011
1012 /// Check if the doc contains the full history.
1013 pub fn is_shallow(&self) -> bool {
1014 self.doc.is_shallow()
1015 }
1016
1017 /// Gets container IDs modified in the given ID range.
1018 ///
1019 /// **NOTE:** This method will implicitly commit.
1020 ///
1021 /// This method can be used in conjunction with `doc.travel_change_ancestors()` to traverse
1022 /// the history and identify all changes that affected specific containers.
1023 ///
1024 /// # Arguments
1025 ///
1026 /// * `id` - The starting ID of the change range
1027 /// * `len` - The length of the change range to check
1028 pub fn get_changed_containers_in(&self, id: ID, len: usize) -> FxHashSet<ContainerID> {
1029 self.doc.get_changed_containers_in(id, len)
1030 }
1031
1032 /// Find the operation id spans that between the `from` version and the `to` version.
1033 #[inline]
1034 pub fn find_id_spans_between(&self, from: &Frontiers, to: &Frontiers) -> VersionVectorDiff {
1035 self.doc.find_id_spans_between(from, to)
1036 }
1037
1038 /// Revert the current document state back to the target version
1039 ///
1040 /// Internally, it will generate a series of local operations that can revert the
1041 /// current doc to the target version. It will calculate the diff between the current
1042 /// state and the target state, and apply the diff to the current state.
1043 #[inline]
1044 pub fn revert_to(&self, version: &Frontiers) -> LoroResult<()> {
1045 self.doc.revert_to(version)
1046 }
1047
1048 /// Apply a diff to the current document state.
1049 ///
1050 /// Internally, it will apply the diff to the current state.
1051 #[inline]
1052 pub fn apply_diff(&self, diff: DiffBatch) -> LoroResult<()> {
1053 self.doc.apply_diff(diff.into())
1054 }
1055
1056 /// Calculate the diff between two versions
1057 #[inline]
1058 pub fn diff(&self, a: &Frontiers, b: &Frontiers) -> LoroResult<DiffBatch> {
1059 self.doc.diff(a, b).map(|x| x.into())
1060 }
1061
1062 /// Check if the doc contains the target container.
1063 ///
1064 /// A root container always exists, while a normal container exists
1065 /// if it has ever been created on the doc.
1066 ///
1067 /// # Examples
1068 /// ```
1069 /// use loro::{LoroDoc, LoroText, LoroList, ExportMode};
1070 ///
1071 /// let doc = LoroDoc::new();
1072 /// doc.set_peer_id(1);
1073 /// let map = doc.get_map("map");
1074 /// map.insert_container("text", LoroText::new()).unwrap();
1075 /// map.insert_container("list", LoroList::new()).unwrap();
1076 ///
1077 /// // Root map container exists
1078 /// assert!(doc.has_container(&"cid:root-map:Map".try_into().unwrap()));
1079 /// // Text container exists
1080 /// assert!(doc.has_container(&"cid:0@1:Text".try_into().unwrap()));
1081 /// // List container exists
1082 /// assert!(doc.has_container(&"cid:1@1:List".try_into().unwrap()));
1083 ///
1084 /// let doc2 = LoroDoc::new();
1085 /// // Containers exist as long as the history or doc state includes them
1086 /// doc.detach();
1087 /// doc2.import(&doc.export(ExportMode::all_updates()).unwrap()).unwrap();
1088 /// assert!(doc2.has_container(&"cid:root-map:Map".try_into().unwrap()));
1089 /// assert!(doc2.has_container(&"cid:0@1:Text".try_into().unwrap()));
1090 /// assert!(doc2.has_container(&"cid:1@1:List".try_into().unwrap()));
1091 /// ```
1092 pub fn has_container(&self, container_id: &ContainerID) -> bool {
1093 self.doc.has_container(container_id)
1094 }
1095
1096 /// Subscribe to the first commit from a peer. Operations performed on the `LoroDoc` within this callback
1097 /// will be merged into the current commit.
1098 ///
1099 /// This is useful for managing the relationship between `PeerID` and user information.
1100 /// For example, you could store user names in a `LoroMap` using `PeerID` as the key and the `UserID` as the value.
1101 ///
1102 /// # Example
1103 /// ```
1104 /// use loro::LoroDoc;
1105 /// use std::sync::{Arc, Mutex};
1106 ///
1107 /// let doc = LoroDoc::new();
1108 /// doc.set_peer_id(0).unwrap();
1109 /// let p = Arc::new(Mutex::new(vec![]));
1110 /// let p2 = Arc::clone(&p);
1111 /// let sub = doc.subscribe_first_commit_from_peer(Box::new(move |e| {
1112 /// p2.try_lock().unwrap().push(e.peer);
1113 /// true
1114 /// }));
1115 /// doc.get_text("text").insert(0, "a").unwrap();
1116 /// doc.commit();
1117 /// doc.get_text("text").insert(0, "b").unwrap();
1118 /// doc.commit();
1119 /// doc.set_peer_id(1).unwrap();
1120 /// doc.get_text("text").insert(0, "c").unwrap();
1121 /// doc.commit();
1122 /// sub.unsubscribe();
1123 /// assert_eq!(p.try_lock().unwrap().as_slice(), &[0, 1]);
1124 /// ```
1125 pub fn subscribe_first_commit_from_peer(
1126 &self,
1127 callback: FirstCommitFromPeerCallback,
1128 ) -> Subscription {
1129 self.doc.subscribe_first_commit_from_peer(callback)
1130 }
1131
1132 /// Subscribe to the pre-commit event.
1133 ///
1134 /// The callback will be called when the changes are committed but not yet applied to the OpLog.
1135 /// You can modify the commit message and timestamp in the callback by [`ChangeModifier`].
1136 ///
1137 /// # Example
1138 /// ```rust
1139 /// use loro::LoroDoc;
1140 ///
1141 /// let doc = LoroDoc::new();
1142 /// let doc_clone = doc.clone();
1143 /// let sub = doc.subscribe_pre_commit(Box::new(move |e| {
1144 /// e.modifier
1145 /// .set_timestamp(e.change_meta.timestamp + 1)
1146 /// .set_message(&format!("prefix\n{}", e.change_meta.message()));
1147 /// true
1148 /// }));
1149 /// doc.get_text("text").insert(0, "a").unwrap();
1150 /// doc.commit();
1151 /// ```
1152 pub fn subscribe_pre_commit(&self, callback: PreCommitCallback) -> Subscription {
1153 self.doc.subscribe_pre_commit(callback)
1154 }
1155}
1156
1157/// It's used to prevent the user from implementing the trait directly.
1158#[allow(private_bounds)]
1159trait SealedTrait {}
1160
1161/// The common trait for all the containers.
1162/// It's used internally, you can't implement it directly.
1163#[allow(private_bounds)]
1164pub trait ContainerTrait: SealedTrait {
1165 /// The handler of the container.
1166 type Handler: HandlerTrait;
1167 /// Convert the container to a [Container].
1168 fn to_container(&self) -> Container;
1169 /// Convert the container to a handler.
1170 fn to_handler(&self) -> Self::Handler;
1171 /// Convert the handler to a container.
1172 fn from_handler(handler: Self::Handler) -> Self;
1173 /// Try to convert the container to the handler.
1174 fn try_from_container(container: Container) -> Option<Self>
1175 where
1176 Self: Sized;
1177 /// Whether the container is attached to a document.
1178 fn is_attached(&self) -> bool;
1179 /// If a detached container is attached, this method will return its corresponding attached handler.
1180 fn get_attached(&self) -> Option<Self>
1181 where
1182 Self: Sized;
1183 /// Whether the container is deleted.
1184 fn is_deleted(&self) -> bool;
1185 /// Get the doc of the container.
1186 fn doc(&self) -> Option<LoroDoc>;
1187}
1188
1189/// LoroList container. It's used to model array.
1190///
1191/// It can have sub containers.
1192///
1193/// ```
1194/// # use loro::{LoroDoc, ContainerType, ToJson};
1195/// # use serde_json::json;
1196/// let doc = LoroDoc::new();
1197/// let list = doc.get_list("list");
1198/// list.insert(0, 123).unwrap();
1199/// list.insert(1, "h").unwrap();
1200/// assert_eq!(
1201/// doc.get_deep_value().to_json_value(),
1202/// json!({
1203/// "list": [123, "h"]
1204/// })
1205/// );
1206/// ```
1207#[derive(Clone, Debug)]
1208pub struct LoroList {
1209 handler: InnerListHandler,
1210}
1211
1212impl SealedTrait for LoroList {}
1213impl ContainerTrait for LoroList {
1214 type Handler = InnerListHandler;
1215 fn to_container(&self) -> Container {
1216 Container::List(self.clone())
1217 }
1218
1219 fn to_handler(&self) -> Self::Handler {
1220 self.handler.clone()
1221 }
1222
1223 fn from_handler(handler: Self::Handler) -> Self {
1224 Self { handler }
1225 }
1226
1227 fn is_attached(&self) -> bool {
1228 self.handler.is_attached()
1229 }
1230
1231 fn get_attached(&self) -> Option<Self> {
1232 self.handler.get_attached().map(Self::from_handler)
1233 }
1234
1235 fn try_from_container(container: Container) -> Option<Self> {
1236 container.into_list().ok()
1237 }
1238
1239 fn is_deleted(&self) -> bool {
1240 self.handler.is_deleted()
1241 }
1242
1243 fn doc(&self) -> Option<LoroDoc> {
1244 self.handler.doc().map(LoroDoc::_new)
1245 }
1246}
1247
1248impl LoroList {
1249 /// Create a new container that is detached from the document.
1250 ///
1251 /// The edits on a detached container will not be persisted.
1252 /// To attach the container to the document, please insert it into an attached container.
1253 pub fn new() -> Self {
1254 Self {
1255 handler: InnerListHandler::new_detached(),
1256 }
1257 }
1258
1259 /// Whether the container is attached to a document
1260 ///
1261 /// The edits on a detached container will not be persisted.
1262 /// To attach the container to the document, please insert it into an attached container.
1263 pub fn is_attached(&self) -> bool {
1264 self.handler.is_attached()
1265 }
1266
1267 /// Insert a value at the given position.
1268 pub fn insert(&self, pos: usize, v: impl Into<LoroValue>) -> LoroResult<()> {
1269 self.handler.insert(pos, v)
1270 }
1271
1272 /// Delete values at the given position.
1273 #[inline]
1274 pub fn delete(&self, pos: usize, len: usize) -> LoroResult<()> {
1275 self.handler.delete(pos, len)
1276 }
1277
1278 /// Get the value at the given position.
1279 #[inline]
1280 pub fn get(&self, index: usize) -> Option<ValueOrContainer> {
1281 self.handler.get_(index).map(ValueOrContainer::from)
1282 }
1283
1284 /// Get the deep value of the container.
1285 #[inline]
1286 pub fn get_deep_value(&self) -> LoroValue {
1287 self.handler.get_deep_value()
1288 }
1289
1290 /// Get the shallow value of the container.
1291 ///
1292 /// This does not convert the state of sub-containers; instead, it represents them as [LoroValue::Container].
1293 #[inline]
1294 pub fn get_value(&self) -> LoroValue {
1295 self.handler.get_value()
1296 }
1297
1298 /// Get the ID of the container.
1299 #[inline]
1300 pub fn id(&self) -> ContainerID {
1301 self.handler.id().clone()
1302 }
1303
1304 /// Pop the last element of the list.
1305 #[inline]
1306 pub fn pop(&self) -> LoroResult<Option<LoroValue>> {
1307 self.handler.pop()
1308 }
1309
1310 /// Push a value to the list.
1311 #[inline]
1312 pub fn push(&self, v: impl Into<LoroValue>) -> LoroResult<()> {
1313 self.handler.push(v.into())
1314 }
1315
1316 /// Push a container to the list.
1317 #[inline]
1318 pub fn push_container<C: ContainerTrait>(&self, child: C) -> LoroResult<C> {
1319 let pos = self.handler.len();
1320 Ok(C::from_handler(
1321 self.handler.insert_container(pos, child.to_handler())?,
1322 ))
1323 }
1324
1325 /// Iterate over the elements of the list.
1326 pub fn for_each<I>(&self, mut f: I)
1327 where
1328 I: FnMut(ValueOrContainer),
1329 {
1330 self.handler.for_each(&mut |v| {
1331 f(ValueOrContainer::from(v));
1332 })
1333 }
1334
1335 /// Get the length of the list.
1336 #[inline]
1337 pub fn len(&self) -> usize {
1338 self.handler.len()
1339 }
1340
1341 /// Whether the list is empty.
1342 #[inline]
1343 pub fn is_empty(&self) -> bool {
1344 self.handler.is_empty()
1345 }
1346
1347 /// Insert a container with the given type at the given index.
1348 ///
1349 /// # Example
1350 ///
1351 /// ```
1352 /// # use loro::{LoroDoc, ContainerType, LoroText, ToJson};
1353 /// # use serde_json::json;
1354 /// let doc = LoroDoc::new();
1355 /// let list = doc.get_list("m");
1356 /// let text = list.insert_container(0, LoroText::new()).unwrap();
1357 /// text.insert(0, "12");
1358 /// text.insert(0, "0");
1359 /// assert_eq!(doc.get_deep_value().to_json_value(), json!({"m": ["012"]}));
1360 /// ```
1361 #[inline]
1362 pub fn insert_container<C: ContainerTrait>(&self, pos: usize, child: C) -> LoroResult<C> {
1363 Ok(C::from_handler(
1364 self.handler.insert_container(pos, child.to_handler())?,
1365 ))
1366 }
1367
1368 /// Get the cursor at the given position.
1369 ///
1370 /// Using "index" to denote cursor positions can be unstable, as positions may
1371 /// shift with document edits. To reliably represent a position or range within
1372 /// a document, it is more effective to leverage the unique ID of each item/character
1373 /// in a List CRDT or Text CRDT.
1374 ///
1375 /// Loro optimizes State metadata by not storing the IDs of deleted elements. This
1376 /// approach complicates tracking cursors since they rely on these IDs. The solution
1377 /// recalculates position by replaying relevant history to update stable positions
1378 /// accurately. To minimize the performance impact of history replay, the system
1379 /// updates cursor info to reference only the IDs of currently present elements,
1380 /// thereby reducing the need for replay.
1381 ///
1382 /// # Example
1383 ///
1384 /// ```
1385 /// use loro::LoroDoc;
1386 /// use loro_internal::cursor::Side;
1387 ///
1388 /// let doc = LoroDoc::new();
1389 /// let list = doc.get_list("list");
1390 /// list.insert(0, 0).unwrap();
1391 /// let cursor = list.get_cursor(0, Side::Middle).unwrap();
1392 /// assert_eq!(doc.get_cursor_pos(&cursor).unwrap().current.pos, 0);
1393 /// list.insert(0, 0).unwrap();
1394 /// assert_eq!(doc.get_cursor_pos(&cursor).unwrap().current.pos, 1);
1395 /// list.insert(0, 0).unwrap();
1396 /// list.insert(0, 0).unwrap();
1397 /// assert_eq!(doc.get_cursor_pos(&cursor).unwrap().current.pos, 3);
1398 /// list.insert(4, 0).unwrap();
1399 /// assert_eq!(doc.get_cursor_pos(&cursor).unwrap().current.pos, 3);
1400 /// ```
1401 pub fn get_cursor(&self, pos: usize, side: Side) -> Option<Cursor> {
1402 self.handler.get_cursor(pos, side)
1403 }
1404
1405 /// Converts the LoroList to a Vec of LoroValue.
1406 ///
1407 /// This method unwraps the internal Arc and clones the data if necessary,
1408 /// returning a Vec containing all the elements of the LoroList as LoroValue.
1409 ///
1410 /// # Returns
1411 ///
1412 /// A Vec<LoroValue> containing all elements of the LoroList.
1413 ///
1414 /// # Example
1415 ///
1416 /// ```
1417 /// use loro::{LoroDoc, LoroValue};
1418 ///
1419 /// let doc = LoroDoc::new();
1420 /// let list = doc.get_list("my_list");
1421 /// list.insert(0, 1).unwrap();
1422 /// list.insert(1, "hello").unwrap();
1423 /// list.insert(2, true).unwrap();
1424 ///
1425 /// let vec = list.to_vec();
1426 /// ```
1427 pub fn to_vec(&self) -> Vec<LoroValue> {
1428 self.get_value().into_list().unwrap().unwrap()
1429 }
1430
1431 /// Delete all elements in the list.
1432 pub fn clear(&self) -> LoroResult<()> {
1433 self.handler.clear()
1434 }
1435
1436 /// Get the ID of the list item at the given position.
1437 pub fn get_id_at(&self, pos: usize) -> Option<ID> {
1438 self.handler.get_id_at(pos)
1439 }
1440}
1441
1442impl Default for LoroList {
1443 fn default() -> Self {
1444 Self::new()
1445 }
1446}
1447
1448/// LoroMap container.
1449///
1450/// It's LWW(Last-Write-Win) Map. It can support Multi-Value Map in the future.
1451///
1452/// # Example
1453/// ```
1454/// # use loro::{LoroDoc, ToJson, ExpandType, LoroText, LoroValue};
1455/// # use serde_json::json;
1456/// let doc = LoroDoc::new();
1457/// let map = doc.get_map("map");
1458/// map.insert("key", "value").unwrap();
1459/// map.insert("true", true).unwrap();
1460/// map.insert("null", LoroValue::Null).unwrap();
1461/// map.insert("deleted", LoroValue::Null).unwrap();
1462/// map.delete("deleted").unwrap();
1463/// let text = map
1464/// .insert_container("text", LoroText::new()).unwrap();
1465/// text.insert(0, "Hello world!").unwrap();
1466/// assert_eq!(
1467/// doc.get_deep_value().to_json_value(),
1468/// json!({
1469/// "map": {
1470/// "key": "value",
1471/// "true": true,
1472/// "null": null,
1473/// "text": "Hello world!"
1474/// }
1475/// })
1476/// );
1477/// ```
1478#[derive(Clone, Debug)]
1479pub struct LoroMap {
1480 handler: InnerMapHandler,
1481}
1482
1483impl SealedTrait for LoroMap {}
1484impl ContainerTrait for LoroMap {
1485 type Handler = InnerMapHandler;
1486
1487 fn to_container(&self) -> Container {
1488 Container::Map(self.clone())
1489 }
1490
1491 fn to_handler(&self) -> Self::Handler {
1492 self.handler.clone()
1493 }
1494
1495 fn from_handler(handler: Self::Handler) -> Self {
1496 Self { handler }
1497 }
1498
1499 fn is_attached(&self) -> bool {
1500 self.handler.is_attached()
1501 }
1502
1503 fn get_attached(&self) -> Option<Self> {
1504 self.handler.get_attached().map(Self::from_handler)
1505 }
1506
1507 fn try_from_container(container: Container) -> Option<Self> {
1508 container.into_map().ok()
1509 }
1510
1511 fn is_deleted(&self) -> bool {
1512 self.handler.is_deleted()
1513 }
1514
1515 fn doc(&self) -> Option<LoroDoc> {
1516 self.handler.doc().map(LoroDoc::_new)
1517 }
1518}
1519
1520impl LoroMap {
1521 /// Create a new container that is detached from the document.
1522 ///
1523 /// The edits on a detached container will not be persisted.
1524 /// To attach the container to the document, please insert it into an attached container.
1525 pub fn new() -> Self {
1526 Self {
1527 handler: InnerMapHandler::new_detached(),
1528 }
1529 }
1530
1531 /// Whether the container is attached to a document.
1532 pub fn is_attached(&self) -> bool {
1533 self.handler.is_attached()
1534 }
1535
1536 /// Delete a key-value pair from the map.
1537 pub fn delete(&self, key: &str) -> LoroResult<()> {
1538 self.handler.delete(key)
1539 }
1540
1541 /// Iterate over the key-value pairs of the map.
1542 pub fn for_each<I>(&self, mut f: I)
1543 where
1544 I: FnMut(&str, ValueOrContainer),
1545 {
1546 self.handler.for_each(|k, v| {
1547 f(k, ValueOrContainer::from(v));
1548 })
1549 }
1550
1551 /// Insert a key-value pair into the map.
1552 ///
1553 /// > **Note**: When calling `map.set(key, value)` on a LoroMap, if `map.get(key)` already returns `value`,
1554 /// > the operation will be a no-op (no operation recorded) to avoid unnecessary updates.
1555 pub fn insert(&self, key: &str, value: impl Into<LoroValue>) -> LoroResult<()> {
1556 self.handler.insert(key, value)
1557 }
1558
1559 /// Get the length of the map.
1560 pub fn len(&self) -> usize {
1561 self.handler.len()
1562 }
1563
1564 /// Get the ID of the map.
1565 pub fn id(&self) -> ContainerID {
1566 self.handler.id().clone()
1567 }
1568
1569 /// Whether the map is empty.
1570 pub fn is_empty(&self) -> bool {
1571 self.handler.is_empty()
1572 }
1573
1574 /// Get the value of the map with the given key.
1575 pub fn get(&self, key: &str) -> Option<ValueOrContainer> {
1576 self.handler.get_(key).map(ValueOrContainer::from)
1577 }
1578
1579 /// Insert a container with the given type at the given key.
1580 ///
1581 /// # Example
1582 ///
1583 /// ```
1584 /// # use loro::{LoroDoc, LoroText, ContainerType, ToJson};
1585 /// # use serde_json::json;
1586 /// let doc = LoroDoc::new();
1587 /// let map = doc.get_map("m");
1588 /// let text = map.insert_container("t", LoroText::new()).unwrap();
1589 /// text.insert(0, "12");
1590 /// text.insert(0, "0");
1591 /// assert_eq!(doc.get_deep_value().to_json_value(), json!({"m": {"t": "012"}}));
1592 /// ```
1593 pub fn insert_container<C: ContainerTrait>(&self, key: &str, child: C) -> LoroResult<C> {
1594 Ok(C::from_handler(
1595 self.handler.insert_container(key, child.to_handler())?,
1596 ))
1597 }
1598
1599 /// Get the shallow value of the map.
1600 ///
1601 /// It will not convert the state of sub-containers, but represent them as [LoroValue::Container].
1602 pub fn get_value(&self) -> LoroValue {
1603 self.handler.get_value()
1604 }
1605
1606 /// Get the deep value of the map.
1607 ///
1608 /// It will convert the state of sub-containers into a nested JSON value.
1609 pub fn get_deep_value(&self) -> LoroValue {
1610 self.handler.get_deep_value()
1611 }
1612
1613 /// Get or create a container with the given key.
1614 pub fn get_or_create_container<C: ContainerTrait>(&self, key: &str, child: C) -> LoroResult<C> {
1615 Ok(C::from_handler(
1616 self.handler
1617 .get_or_create_container(key, child.to_handler())?,
1618 ))
1619 }
1620
1621 /// Delete all key-value pairs in the map.
1622 pub fn clear(&self) -> LoroResult<()> {
1623 self.handler.clear()
1624 }
1625
1626 /// Get the keys of the map.
1627 pub fn keys(&self) -> impl Iterator<Item = InternalString> + '_ {
1628 self.handler.keys()
1629 }
1630
1631 /// Get the values of the map.
1632 pub fn values(&self) -> impl Iterator<Item = ValueOrContainer> + '_ {
1633 self.handler.values().map(ValueOrContainer::from)
1634 }
1635
1636 /// Get the peer id of the last editor on the given entry
1637 pub fn get_last_editor(&self, key: &str) -> Option<PeerID> {
1638 self.handler.get_last_editor(key)
1639 }
1640}
1641
1642impl Default for LoroMap {
1643 fn default() -> Self {
1644 Self::new()
1645 }
1646}
1647
1648/// LoroText container. It's used to model plaintext/richtext.
1649#[derive(Clone, Debug)]
1650pub struct LoroText {
1651 handler: InnerTextHandler,
1652}
1653
1654impl SealedTrait for LoroText {}
1655impl ContainerTrait for LoroText {
1656 type Handler = InnerTextHandler;
1657
1658 fn to_container(&self) -> Container {
1659 Container::Text(self.clone())
1660 }
1661
1662 fn to_handler(&self) -> Self::Handler {
1663 self.handler.clone()
1664 }
1665
1666 fn from_handler(handler: Self::Handler) -> Self {
1667 Self { handler }
1668 }
1669
1670 fn is_attached(&self) -> bool {
1671 self.handler.is_attached()
1672 }
1673
1674 fn get_attached(&self) -> Option<Self> {
1675 self.handler.get_attached().map(Self::from_handler)
1676 }
1677
1678 fn try_from_container(container: Container) -> Option<Self> {
1679 container.into_text().ok()
1680 }
1681
1682 fn is_deleted(&self) -> bool {
1683 self.handler.is_deleted()
1684 }
1685
1686 fn doc(&self) -> Option<LoroDoc> {
1687 self.handler.doc().map(LoroDoc::_new)
1688 }
1689}
1690
1691impl LoroText {
1692 /// Create a new container that is detached from the document.
1693 ///
1694 /// The edits on a detached container will not be persisted.
1695 /// To attach the container to the document, please insert it into an attached container.
1696 pub fn new() -> Self {
1697 Self {
1698 handler: InnerTextHandler::new_detached(),
1699 }
1700 }
1701
1702 /// Whether the container is attached to a document
1703 ///
1704 /// The edits on a detached container will not be persisted.
1705 /// To attach the container to the document, please insert it into an attached container.
1706 pub fn is_attached(&self) -> bool {
1707 self.handler.is_attached()
1708 }
1709
1710 /// Get the [ContainerID] of the text container.
1711 pub fn id(&self) -> ContainerID {
1712 self.handler.id().clone()
1713 }
1714
1715 /// Iterate each span(internal storage unit) of the text.
1716 ///
1717 /// The callback function will be called for each character in the text.
1718 /// If the callback returns `false`, the iteration will stop.
1719 ///
1720 /// Limitation: you cannot access or alter the doc state when iterating.
1721 /// If you need to access or alter the doc state, please use `to_string` instead.
1722 pub fn iter(&self, callback: impl FnMut(&str) -> bool) {
1723 self.handler.iter(callback);
1724 }
1725
1726 /// Insert a string at the given unicode position.
1727 pub fn insert(&self, pos: usize, s: &str) -> LoroResult<()> {
1728 self.handler.insert_unicode(pos, s)
1729 }
1730
1731 /// Insert a string at the given utf-8 position.
1732 pub fn insert_utf8(&self, pos: usize, s: &str) -> LoroResult<()> {
1733 self.handler.insert_utf8(pos, s)
1734 }
1735
1736 /// Delete a range of text at the given unicode position with unicode length.
1737 pub fn delete(&self, pos: usize, len: usize) -> LoroResult<()> {
1738 self.handler.delete_unicode(pos, len)
1739 }
1740
1741 /// Delete a range of text at the given utf-8 position with utf-8 length.
1742 pub fn delete_utf8(&self, pos: usize, len: usize) -> LoroResult<()> {
1743 self.handler.delete_utf8(pos, len)
1744 }
1745
1746 /// Get a string slice at the given Unicode range
1747 pub fn slice(&self, start_index: usize, end_index: usize) -> LoroResult<String> {
1748 self.handler.slice(start_index, end_index)
1749 }
1750
1751 /// Get the characters at given unicode position.
1752 pub fn char_at(&self, pos: usize) -> LoroResult<char> {
1753 self.handler.char_at(pos)
1754 }
1755
1756 /// Delete specified character and insert string at the same position at given unicode position.
1757 pub fn splice(&self, pos: usize, len: usize, s: &str) -> LoroResult<String> {
1758 self.handler.splice(pos, len, s)
1759 }
1760
1761 /// Whether the text container is empty.
1762 pub fn is_empty(&self) -> bool {
1763 self.handler.is_empty()
1764 }
1765
1766 /// Get the length of the text container in UTF-8.
1767 pub fn len_utf8(&self) -> usize {
1768 self.handler.len_utf8()
1769 }
1770
1771 /// Get the length of the text container in Unicode.
1772 pub fn len_unicode(&self) -> usize {
1773 self.handler.len_unicode()
1774 }
1775
1776 /// Get the length of the text container in UTF-16.
1777 pub fn len_utf16(&self) -> usize {
1778 self.handler.len_utf16()
1779 }
1780
1781 /// Update the current text based on the provided text.
1782 ///
1783 /// It will calculate the minimal difference and apply it to the current text.
1784 /// It uses Myers' diff algorithm to compute the optimal difference.
1785 ///
1786 /// This could take a long time for large texts (e.g. > 50_000 characters).
1787 /// In that case, you should use `updateByLine` instead.
1788 ///
1789 /// # Example
1790 /// ```rust
1791 /// use loro::LoroDoc;
1792 ///
1793 /// let doc = LoroDoc::new();
1794 /// let text = doc.get_text("text");
1795 /// text.insert(0, "Hello").unwrap();
1796 /// text.update("Hello World", Default::default()).unwrap();
1797 /// assert_eq!(text.to_string(), "Hello World");
1798 /// ```
1799 ///
1800 pub fn update(&self, text: &str, options: UpdateOptions) -> Result<(), UpdateTimeoutError> {
1801 self.handler.update(text, options)
1802 }
1803
1804 /// Update the current text based on the provided text.
1805 ///
1806 /// This update calculation is line-based, which will be more efficient but less precise.
1807 pub fn update_by_line(
1808 &self,
1809 text: &str,
1810 options: UpdateOptions,
1811 ) -> Result<(), UpdateTimeoutError> {
1812 self.handler.update_by_line(text, options)
1813 }
1814
1815 /// Apply a [delta](https://quilljs.com/docs/delta/) to the text container.
1816 pub fn apply_delta(&self, delta: &[TextDelta]) -> LoroResult<()> {
1817 self.handler.apply_delta(delta)
1818 }
1819
1820 /// Mark a range of text with a key-value pair.
1821 ///
1822 /// You can use it to create a highlight, make a range of text bold, or add a link to a range of text.
1823 ///
1824 /// You can specify the `expand` option to set the behavior when inserting text at the boundary of the range.
1825 ///
1826 /// - `after`(default): when inserting text right after the given range, the mark will be expanded to include the inserted text
1827 /// - `before`: when inserting text right before the given range, the mark will be expanded to include the inserted text
1828 /// - `none`: the mark will not be expanded to include the inserted text at the boundaries
1829 /// - `both`: when inserting text either right before or right after the given range, the mark will be expanded to include the inserted text
1830 ///
1831 /// *You should make sure that a key is always associated with the same expand type.*
1832 pub fn mark(
1833 &self,
1834 range: Range<usize>,
1835 key: &str,
1836 value: impl Into<LoroValue>,
1837 ) -> LoroResult<()> {
1838 self.handler.mark(range.start, range.end, key, value.into())
1839 }
1840
1841 /// Unmark a range of text with a key and a value.
1842 ///
1843 /// You can use it to remove highlights, bolds or links
1844 ///
1845 /// You can specify the `expand` option to set the behavior when inserting text at the boundary of the range.
1846 ///
1847 /// **Note: You should specify the same expand type as when you mark the text.**
1848 ///
1849 /// - `after`(default): when inserting text right after the given range, the mark will be expanded to include the inserted text
1850 /// - `before`: when inserting text right before the given range, the mark will be expanded to include the inserted text
1851 /// - `none`: the mark will not be expanded to include the inserted text at the boundaries
1852 /// - `both`: when inserting text either right before or right after the given range, the mark will be expanded to include the inserted text
1853 ///
1854 /// *You should make sure that a key is always associated with the same expand type.*
1855 ///
1856 /// Note: you cannot delete unmergeable annotations like comments by this method.
1857 pub fn unmark(&self, range: Range<usize>, key: &str) -> LoroResult<()> {
1858 self.handler.unmark(range.start, range.end, key)
1859 }
1860
1861 /// Get the text in [Delta](https://quilljs.com/docs/delta/) format.
1862 ///
1863 /// # Example
1864 /// ```
1865 /// use loro::{LoroDoc, ToJson, ExpandType, TextDelta};
1866 /// use serde_json::json;
1867 /// use fxhash::FxHashMap;
1868 ///
1869 /// let doc = LoroDoc::new();
1870 /// let text = doc.get_text("text");
1871 /// text.insert(0, "Hello world!").unwrap();
1872 /// text.mark(0..5, "bold", true).unwrap();
1873 /// assert_eq!(
1874 /// text.to_delta(),
1875 /// vec![
1876 /// TextDelta::Insert {
1877 /// insert: "Hello".to_string(),
1878 /// attributes: Some(FxHashMap::from_iter([("bold".to_string(), true.into())])),
1879 /// },
1880 /// TextDelta::Insert {
1881 /// insert: " world!".to_string(),
1882 /// attributes: None,
1883 /// },
1884 /// ]
1885 /// );
1886 /// text.unmark(3..5, "bold").unwrap();
1887 /// assert_eq!(
1888 /// text.to_delta(),
1889 /// vec![
1890 /// TextDelta::Insert {
1891 /// insert: "Hel".to_string(),
1892 /// attributes: Some(FxHashMap::from_iter([("bold".to_string(), true.into())])),
1893 /// },
1894 /// TextDelta::Insert {
1895 /// insert: "lo world!".to_string(),
1896 /// attributes: None,
1897 /// },
1898 /// ]
1899 /// );
1900 /// ```
1901 pub fn to_delta(&self) -> Vec<TextDelta> {
1902 let delta = self.handler.get_richtext_value().into_list().unwrap();
1903 delta
1904 .iter()
1905 .map(|x| {
1906 let map = x.as_map().unwrap();
1907 let insert = map.get("insert").unwrap().as_string().unwrap().to_string();
1908 let attributes = map
1909 .get("attributes")
1910 .map(|v| v.as_map().unwrap().deref().clone());
1911 TextDelta::Insert { insert, attributes }
1912 })
1913 .collect()
1914 }
1915
1916 /// Get the rich text value in [Delta](https://quilljs.com/docs/delta/) format.
1917 ///
1918 /// # Example
1919 /// ```
1920 /// # use loro::{LoroDoc, ToJson, ExpandType, TextDelta};
1921 /// # use serde_json::json;
1922 ///
1923 /// let doc = LoroDoc::new();
1924 /// let text = doc.get_text("text");
1925 /// text.insert(0, "Hello world!").unwrap();
1926 /// text.mark(0..5, "bold", true).unwrap();
1927 /// assert_eq!(
1928 /// text.get_richtext_value().to_json_value(),
1929 /// json!([
1930 /// { "insert": "Hello", "attributes": {"bold": true} },
1931 /// { "insert": " world!" },
1932 /// ])
1933 /// );
1934 /// text.unmark(3..5, "bold").unwrap();
1935 /// assert_eq!(
1936 /// text.get_richtext_value().to_json_value(),
1937 /// json!([
1938 /// { "insert": "Hel", "attributes": {"bold": true} },
1939 /// { "insert": "lo world!" },
1940 /// ])
1941 /// );
1942 /// ```
1943 pub fn get_richtext_value(&self) -> LoroValue {
1944 self.handler.get_richtext_value()
1945 }
1946
1947 /// Get the text content of the text container.
1948 #[allow(clippy::inherent_to_string)]
1949 pub fn to_string(&self) -> String {
1950 self.handler.to_string()
1951 }
1952
1953 /// Get the cursor at the given position in the given Unicode position.
1954 ///
1955 /// Using "index" to denote cursor positions can be unstable, as positions may
1956 /// shift with document edits. To reliably represent a position or range within
1957 /// a document, it is more effective to leverage the unique ID of each item/character
1958 /// in a List CRDT or Text CRDT.
1959 ///
1960 /// Loro optimizes State metadata by not storing the IDs of deleted elements. This
1961 /// approach complicates tracking cursors since they rely on these IDs. The solution
1962 /// recalculates position by replaying relevant history to update stable positions
1963 /// accurately. To minimize the performance impact of history replay, the system
1964 /// updates cursor info to reference only the IDs of currently present elements,
1965 /// thereby reducing the need for replay.
1966 ///
1967 /// # Example
1968 ///
1969 /// ```
1970 /// # use loro::{LoroDoc, ToJson};
1971 /// let doc = LoroDoc::new();
1972 /// let text = &doc.get_text("text");
1973 /// text.insert(0, "01234").unwrap();
1974 /// let pos = text.get_cursor(5, Default::default()).unwrap();
1975 /// assert_eq!(doc.get_cursor_pos(&pos).unwrap().current.pos, 5);
1976 /// text.insert(0, "01234").unwrap();
1977 /// assert_eq!(doc.get_cursor_pos(&pos).unwrap().current.pos, 10);
1978 /// text.delete(0, 10).unwrap();
1979 /// assert_eq!(doc.get_cursor_pos(&pos).unwrap().current.pos, 0);
1980 /// text.insert(0, "01234").unwrap();
1981 /// assert_eq!(doc.get_cursor_pos(&pos).unwrap().current.pos, 5);
1982 /// ```
1983 pub fn get_cursor(&self, pos: usize, side: Side) -> Option<Cursor> {
1984 self.handler.get_cursor(pos, side)
1985 }
1986
1987 /// Whether the text container is deleted.
1988 pub fn is_deleted(&self) -> bool {
1989 self.handler.is_deleted()
1990 }
1991
1992 /// Push a string to the end of the text container.
1993 pub fn push_str(&self, s: &str) -> LoroResult<()> {
1994 self.handler.push_str(s)
1995 }
1996
1997 /// Get the editor of the text at the given position.
1998 pub fn get_editor_at_unicode_pos(&self, pos: usize) -> Option<PeerID> {
1999 self.handler
2000 .get_cursor(pos, Side::Middle)
2001 .map(|x| x.id.unwrap().peer)
2002 }
2003}
2004
2005impl Default for LoroText {
2006 fn default() -> Self {
2007 Self::new()
2008 }
2009}
2010
2011/// LoroTree container. It's used to model movable trees.
2012///
2013/// You may use it to model directories, outline or other movable hierarchical data.
2014///
2015/// Learn more at https://loro.dev/docs/tutorial/tree
2016#[derive(Clone, Debug)]
2017pub struct LoroTree {
2018 handler: InnerTreeHandler,
2019}
2020
2021impl SealedTrait for LoroTree {}
2022impl ContainerTrait for LoroTree {
2023 type Handler = InnerTreeHandler;
2024
2025 fn to_container(&self) -> Container {
2026 Container::Tree(self.clone())
2027 }
2028
2029 fn to_handler(&self) -> Self::Handler {
2030 self.handler.clone()
2031 }
2032
2033 fn from_handler(handler: Self::Handler) -> Self {
2034 Self { handler }
2035 }
2036
2037 fn is_attached(&self) -> bool {
2038 self.handler.is_attached()
2039 }
2040
2041 fn get_attached(&self) -> Option<Self> {
2042 self.handler.get_attached().map(Self::from_handler)
2043 }
2044
2045 fn try_from_container(container: Container) -> Option<Self> {
2046 container.into_tree().ok()
2047 }
2048
2049 fn is_deleted(&self) -> bool {
2050 self.handler.is_deleted()
2051 }
2052 fn doc(&self) -> Option<LoroDoc> {
2053 self.handler.doc().map(LoroDoc::_new)
2054 }
2055}
2056
2057/// A tree node in the [LoroTree].
2058#[derive(Debug, Clone)]
2059pub struct TreeNode {
2060 /// ID of the tree node.
2061 pub id: TreeID,
2062 /// ID of the parent tree node.
2063 /// If the node is deleted this value is TreeParentId::Deleted.
2064 /// If you checkout to a version before the node is created, this value is TreeParentId::Unexist.
2065 pub parent: TreeParentId,
2066 /// Fraction index of the node
2067 pub fractional_index: FractionalIndex,
2068 /// The current index of the node in its parent's children list.
2069 pub index: usize,
2070}
2071
2072impl LoroTree {
2073 /// Create a new container that is detached from the document.
2074 ///
2075 /// The edits on a detached container will not be persisted.
2076 /// To attach the container to the document, please insert it into an attached container.
2077 pub fn new() -> Self {
2078 Self {
2079 handler: InnerTreeHandler::new_detached(),
2080 }
2081 }
2082
2083 /// Whether the container is attached to a document
2084 ///
2085 /// The edits on a detached container will not be persisted.
2086 /// To attach the container to the document, please insert it into an attached container.
2087 pub fn is_attached(&self) -> bool {
2088 self.handler.is_attached()
2089 }
2090
2091 /// Create a new tree node and return the [`TreeID`].
2092 ///
2093 /// If the `parent` is `None`, the created node is the root of a tree.
2094 /// Otherwise, the created node is a child of the parent tree node.
2095 ///
2096 /// # Example
2097 ///
2098 /// ```rust
2099 /// use loro::LoroDoc;
2100 ///
2101 /// let doc = LoroDoc::new();
2102 /// let tree = doc.get_tree("tree");
2103 /// // create a root
2104 /// let root = tree.create(None).unwrap();
2105 /// // create a new child
2106 /// let child = tree.create(root).unwrap();
2107 /// ```
2108 pub fn create<T: Into<TreeParentId>>(&self, parent: T) -> LoroResult<TreeID> {
2109 self.handler.create(parent.into())
2110 }
2111
2112 /// Get the root nodes of the forest.
2113 pub fn roots(&self) -> Vec<TreeID> {
2114 self.handler.roots()
2115 }
2116
2117 /// Create a new tree node at the given index and return the [`TreeID`].
2118 ///
2119 /// If the `parent` is `None`, the created node is the root of a tree.
2120 /// If the `index` is greater than the number of children of the parent, error will be returned.
2121 ///
2122 /// # Example
2123 ///
2124 /// ```rust
2125 /// use loro::LoroDoc;
2126 ///
2127 /// let doc = LoroDoc::new();
2128 /// let tree = doc.get_tree("tree");
2129 /// // enable generate fractional index
2130 /// tree.enable_fractional_index(0);
2131 /// // create a root
2132 /// let root = tree.create(None).unwrap();
2133 /// // create a new child at index 0
2134 /// let child = tree.create_at(root, 0).unwrap();
2135 /// ```
2136 pub fn create_at<T: Into<TreeParentId>>(&self, parent: T, index: usize) -> LoroResult<TreeID> {
2137 if !self.handler.is_fractional_index_enabled() {
2138 return Err(LoroTreeError::FractionalIndexNotEnabled.into());
2139 }
2140 self.handler.create_at(parent.into(), index)
2141 }
2142
2143 /// Move the `target` node to be a child of the `parent` node.
2144 ///
2145 /// If the `parent` is `None`, the `target` node will be a root.
2146 ///
2147 /// # Example
2148 ///
2149 /// ```rust
2150 /// use loro::LoroDoc;
2151 ///
2152 /// let doc = LoroDoc::new();
2153 /// let tree = doc.get_tree("tree");
2154 /// let root = tree.create(None).unwrap();
2155 /// let root2 = tree.create(None).unwrap();
2156 /// // move `root2` to be a child of `root`.
2157 /// tree.mov(root2, root).unwrap();
2158 /// ```
2159 pub fn mov<T: Into<TreeParentId>>(&self, target: TreeID, parent: T) -> LoroResult<()> {
2160 self.handler.mov(target, parent.into())
2161 }
2162
2163 /// Move the `target` node to be a child of the `parent` node at the given index.
2164 /// If the `parent` is `None`, the `target` node will be a root.
2165 ///
2166 /// # Example
2167 ///
2168 /// ```rust
2169 /// use loro::LoroDoc;
2170 ///
2171 /// let doc = LoroDoc::new();
2172 /// let tree = doc.get_tree("tree");
2173 /// // enable generate fractional index
2174 /// tree.enable_fractional_index(0);
2175 /// let root = tree.create(None).unwrap();
2176 /// let root2 = tree.create(None).unwrap();
2177 /// // move `root2` to be a child of `root` at index 0.
2178 /// tree.mov_to(root2, root, 0).unwrap();
2179 /// ```
2180 pub fn mov_to<T: Into<TreeParentId>>(
2181 &self,
2182 target: TreeID,
2183 parent: T,
2184 to: usize,
2185 ) -> LoroResult<()> {
2186 if !self.handler.is_fractional_index_enabled() {
2187 return Err(LoroTreeError::FractionalIndexNotEnabled.into());
2188 }
2189 self.handler.move_to(target, parent.into(), to)
2190 }
2191
2192 /// Move the `target` node to be a child after the `after` node with the same parent.
2193 ///
2194 /// # Example
2195 ///
2196 /// ```rust
2197 /// use loro::LoroDoc;
2198 ///
2199 /// let doc = LoroDoc::new();
2200 /// let tree = doc.get_tree("tree");
2201 /// // enable generate fractional index
2202 /// tree.enable_fractional_index(0);
2203 /// let root = tree.create(None).unwrap();
2204 /// let root2 = tree.create(None).unwrap();
2205 /// // move `root` to be a child after `root2`.
2206 /// tree.mov_after(root, root2).unwrap();
2207 /// ```
2208 pub fn mov_after(&self, target: TreeID, after: TreeID) -> LoroResult<()> {
2209 if !self.handler.is_fractional_index_enabled() {
2210 return Err(LoroTreeError::FractionalIndexNotEnabled.into());
2211 }
2212 self.handler.mov_after(target, after)
2213 }
2214
2215 /// Move the `target` node to be a child before the `before` node with the same parent.
2216 ///
2217 /// # Example
2218 ///
2219 /// ```rust
2220 /// use loro::LoroDoc;
2221 ///
2222 /// let doc = LoroDoc::new();
2223 /// let tree = doc.get_tree("tree");
2224 /// // enable generate fractional index
2225 /// tree.enable_fractional_index(0);
2226 /// let root = tree.create(None).unwrap();
2227 /// let root2 = tree.create(None).unwrap();
2228 /// // move `root` to be a child before `root2`.
2229 /// tree.mov_before(root, root2).unwrap();
2230 /// ```
2231 pub fn mov_before(&self, target: TreeID, before: TreeID) -> LoroResult<()> {
2232 if !self.handler.is_fractional_index_enabled() {
2233 return Err(LoroTreeError::FractionalIndexNotEnabled.into());
2234 }
2235 self.handler.mov_before(target, before)
2236 }
2237
2238 /// Delete a tree node.
2239 ///
2240 /// Note: If the deleted node has children, the children do not appear in the state
2241 /// rather than actually being deleted.
2242 ///
2243 /// # Example
2244 ///
2245 /// ```rust
2246 /// use loro::LoroDoc;
2247 ///
2248 /// let doc = LoroDoc::new();
2249 /// let tree = doc.get_tree("tree");
2250 /// let root = tree.create(None).unwrap();
2251 /// tree.delete(root).unwrap();
2252 /// ```
2253 pub fn delete(&self, target: TreeID) -> LoroResult<()> {
2254 self.handler.delete(target)
2255 }
2256
2257 /// Get the associated metadata map handler of a tree node.
2258 ///
2259 /// # Example
2260 /// ```rust
2261 /// use loro::LoroDoc;
2262 ///
2263 /// let doc = LoroDoc::new();
2264 /// let tree = doc.get_tree("tree");
2265 /// let root = tree.create(None).unwrap();
2266 /// let root_meta = tree.get_meta(root).unwrap();
2267 /// root_meta.insert("color", "red");
2268 /// ```
2269 pub fn get_meta(&self, target: TreeID) -> LoroResult<LoroMap> {
2270 self.handler
2271 .get_meta(target)
2272 .map(|h| LoroMap { handler: h })
2273 }
2274
2275 /// Return the parent of target node.
2276 ///
2277 /// - If the target node does not exist, return `None`.
2278 /// - If the target node is a root node, return `Some(None)`.
2279 pub fn parent(&self, target: TreeID) -> Option<TreeParentId> {
2280 self.handler.get_node_parent(&target)
2281 }
2282
2283 /// Return whether target node exists. including deleted node.
2284 pub fn contains(&self, target: TreeID) -> bool {
2285 self.handler.contains(target)
2286 }
2287
2288 /// Return whether target node is deleted.
2289 ///
2290 /// # Errors
2291 ///
2292 /// - If the target node does not exist, return `LoroTreeError::TreeNodeNotExist`.
2293 pub fn is_node_deleted(&self, target: &TreeID) -> LoroResult<bool> {
2294 self.handler.is_node_deleted(target)
2295 }
2296
2297 /// Return all nodes, including deleted nodes
2298 pub fn nodes(&self) -> Vec<TreeID> {
2299 self.handler.nodes()
2300 }
2301
2302 /// Return all nodes, if `with_deleted` is true, the deleted nodes will be included.
2303 pub fn get_nodes(&self, with_deleted: bool) -> Vec<TreeNode> {
2304 let mut ans = self.handler.get_nodes_under(TreeParentId::Root);
2305 if with_deleted {
2306 ans.extend(self.handler.get_nodes_under(TreeParentId::Deleted));
2307 }
2308 ans.into_iter()
2309 .map(|x| TreeNode {
2310 id: x.id,
2311 parent: x.parent,
2312 fractional_index: x.fractional_index,
2313 index: x.index,
2314 })
2315 .collect()
2316 }
2317
2318 /// Return all children of the target node.
2319 ///
2320 /// If the parent node does not exist, return `None`.
2321 pub fn children<T: Into<TreeParentId>>(&self, parent: T) -> Option<Vec<TreeID>> {
2322 self.handler.children(&parent.into())
2323 }
2324
2325 /// Return the number of children of the target node.
2326 pub fn children_num<T: Into<TreeParentId>>(&self, parent: T) -> Option<usize> {
2327 let parent: TreeParentId = parent.into();
2328 self.handler.children_num(&parent)
2329 }
2330
2331 /// Return container id of the tree.
2332 pub fn id(&self) -> ContainerID {
2333 self.handler.id()
2334 }
2335
2336 /// Return the fractional index of the target node with hex format.
2337 pub fn fractional_index(&self, target: TreeID) -> Option<String> {
2338 self.handler
2339 .get_position_by_tree_id(&target)
2340 .map(|x| x.to_string())
2341 }
2342
2343 /// Return the hierarchy array of the forest.
2344 ///
2345 /// Note: the metadata will be not resolved. So if you don't only care about hierarchy
2346 /// but also the metadata, you should use [TreeHandler::get_value_with_meta()].
2347 pub fn get_value(&self) -> LoroValue {
2348 self.handler.get_value()
2349 }
2350
2351 /// Return the hierarchy array of the forest, each node is with metadata.
2352 pub fn get_value_with_meta(&self) -> LoroValue {
2353 self.handler.get_deep_value()
2354 }
2355
2356 // This method is used for testing only.
2357 #[doc(hidden)]
2358 #[allow(non_snake_case)]
2359 pub fn __internal__next_tree_id(&self) -> TreeID {
2360 self.handler.__internal__next_tree_id()
2361 }
2362
2363 /// Whether the fractional index is enabled.
2364 pub fn is_fractional_index_enabled(&self) -> bool {
2365 self.handler.is_fractional_index_enabled()
2366 }
2367
2368 /// Enable fractional index for Tree Position.
2369 ///
2370 /// The jitter is used to avoid conflicts when multiple users are creating the node at the same position.
2371 /// value 0 is default, which means no jitter, any value larger than 0 will enable jitter.
2372 ///
2373 /// Generally speaking, jitter will affect the growth rate of document size.
2374 /// [Read more about it](https://www.loro.dev/blog/movable-tree#implementation-and-encoding-size)
2375 #[inline]
2376 pub fn enable_fractional_index(&self, jitter: u8) {
2377 self.handler.enable_fractional_index(jitter);
2378 }
2379
2380 /// Disable the fractional index generation when you don't need the Tree's siblings to be sorted.
2381 /// The fractional index will always be set to the same default value 0.
2382 ///
2383 /// After calling this, you cannot use `tree.moveTo()`, `tree.moveBefore()`, `tree.moveAfter()`,
2384 /// and `tree.createAt()`.
2385 #[inline]
2386 pub fn disable_fractional_index(&self) {
2387 self.handler.disable_fractional_index();
2388 }
2389
2390 /// Whether the tree is empty.
2391 ///
2392 #[inline]
2393 pub fn is_empty(&self) -> bool {
2394 self.handler.is_empty()
2395 }
2396
2397 /// Get the last move id of the target node.
2398 pub fn get_last_move_id(&self, target: &TreeID) -> Option<ID> {
2399 self.handler.get_last_move_id(target)
2400 }
2401}
2402
2403impl Default for LoroTree {
2404 fn default() -> Self {
2405 Self::new()
2406 }
2407}
2408
2409/// [LoroMovableList container](https://loro.dev/docs/tutorial/list)
2410///
2411/// It is used to model movable ordered lists.
2412///
2413/// Using a combination of insert and delete operations, one can simulate set and move
2414/// operations on a List. However, this approach fails in concurrent editing scenarios.
2415/// For example, if the same element is set or moved concurrently, the simulation would
2416/// result in the deletion of the original element and the insertion of two new elements,
2417/// which does not meet expectations.
2418#[derive(Clone, Debug)]
2419pub struct LoroMovableList {
2420 handler: InnerMovableListHandler,
2421}
2422
2423impl SealedTrait for LoroMovableList {}
2424impl ContainerTrait for LoroMovableList {
2425 type Handler = InnerMovableListHandler;
2426
2427 fn to_container(&self) -> Container {
2428 Container::MovableList(self.clone())
2429 }
2430
2431 fn to_handler(&self) -> Self::Handler {
2432 self.handler.clone()
2433 }
2434
2435 fn from_handler(handler: Self::Handler) -> Self {
2436 Self { handler }
2437 }
2438
2439 fn try_from_container(container: Container) -> Option<Self>
2440 where
2441 Self: Sized,
2442 {
2443 match container {
2444 Container::MovableList(x) => Some(x),
2445 _ => None,
2446 }
2447 }
2448
2449 fn is_attached(&self) -> bool {
2450 self.handler.is_attached()
2451 }
2452
2453 fn get_attached(&self) -> Option<Self>
2454 where
2455 Self: Sized,
2456 {
2457 self.handler.get_attached().map(Self::from_handler)
2458 }
2459
2460 fn is_deleted(&self) -> bool {
2461 self.handler.is_deleted()
2462 }
2463 fn doc(&self) -> Option<LoroDoc> {
2464 self.handler.doc().map(LoroDoc::_new)
2465 }
2466}
2467
2468impl LoroMovableList {
2469 /// Create a new container that is detached from the document.
2470 ///
2471 /// The edits on a detached container will not be persisted.
2472 /// To attach the container to the document, please insert it into an attached container.
2473 pub fn new() -> LoroMovableList {
2474 Self {
2475 handler: InnerMovableListHandler::new_detached(),
2476 }
2477 }
2478
2479 /// Get the container id.
2480 pub fn id(&self) -> ContainerID {
2481 self.handler.id().clone()
2482 }
2483
2484 /// Whether the container is attached to a document
2485 ///
2486 /// The edits on a detached container will not be persisted.
2487 /// To attach the container to the document, please insert it into an attached container.
2488 pub fn is_attached(&self) -> bool {
2489 self.handler.is_attached()
2490 }
2491
2492 /// Insert a value at the given position.
2493 pub fn insert(&self, pos: usize, v: impl Into<LoroValue>) -> LoroResult<()> {
2494 self.handler.insert(pos, v)
2495 }
2496
2497 /// Delete the value at the given position.
2498 pub fn delete(&self, pos: usize, len: usize) -> LoroResult<()> {
2499 self.handler.delete(pos, len)
2500 }
2501
2502 /// Get the value at the given position.
2503 pub fn get(&self, index: usize) -> Option<ValueOrContainer> {
2504 self.handler.get_(index).map(ValueOrContainer::from)
2505 }
2506
2507 /// Get the length of the list.
2508 pub fn len(&self) -> usize {
2509 self.handler.len()
2510 }
2511
2512 /// Whether the list is empty.
2513 #[must_use]
2514 pub fn is_empty(&self) -> bool {
2515 self.len() == 0
2516 }
2517
2518 /// Get the shallow value of the list.
2519 ///
2520 /// It will not convert the state of sub-containers, but represent them as [LoroValue::Container].
2521 pub fn get_value(&self) -> LoroValue {
2522 self.handler.get_value()
2523 }
2524
2525 /// Get the deep value of the list.
2526 ///
2527 /// It will convert the state of sub-containers into a nested JSON value.
2528 pub fn get_deep_value(&self) -> LoroValue {
2529 self.handler.get_deep_value()
2530 }
2531
2532 /// Pop the last element of the list.
2533 pub fn pop(&self) -> LoroResult<Option<ValueOrContainer>> {
2534 let ans = self.handler.pop_()?.map(ValueOrContainer::from);
2535 Ok(ans)
2536 }
2537
2538 /// Push a value to the end of the list.
2539 pub fn push(&self, v: impl Into<LoroValue>) -> LoroResult<()> {
2540 self.handler.push(v.into())
2541 }
2542
2543 /// Push a container to the end of the list.
2544 pub fn push_container<C: ContainerTrait>(&self, child: C) -> LoroResult<C> {
2545 let pos = self.handler.len();
2546 Ok(C::from_handler(
2547 self.handler.insert_container(pos, child.to_handler())?,
2548 ))
2549 }
2550
2551 /// Set the value at the given position.
2552 pub fn set(&self, pos: usize, value: impl Into<LoroValue>) -> LoroResult<()> {
2553 self.handler.set(pos, value.into())
2554 }
2555
2556 /// Move the value at the given position to the given position.
2557 pub fn mov(&self, from: usize, to: usize) -> LoroResult<()> {
2558 self.handler.mov(from, to)
2559 }
2560
2561 /// Insert a container at the given position.
2562 pub fn insert_container<C: ContainerTrait>(&self, pos: usize, child: C) -> LoroResult<C> {
2563 Ok(C::from_handler(
2564 self.handler.insert_container(pos, child.to_handler())?,
2565 ))
2566 }
2567
2568 /// Set the container at the given position.
2569 pub fn set_container<C: ContainerTrait>(&self, pos: usize, child: C) -> LoroResult<C> {
2570 Ok(C::from_handler(
2571 self.handler.set_container(pos, child.to_handler())?,
2572 ))
2573 }
2574
2575 /// Log the internal state of the list.
2576 pub fn log_internal_state(&self) {
2577 info!(
2578 "movable_list internal state: {}",
2579 self.handler.log_internal_state()
2580 )
2581 }
2582
2583 /// Get the cursor at the given position.
2584 ///
2585 /// Using "index" to denote cursor positions can be unstable, as positions may
2586 /// shift with document edits. To reliably represent a position or range within
2587 /// a document, it is more effective to leverage the unique ID of each item/character
2588 /// in a List CRDT or Text CRDT.
2589 ///
2590 /// Loro optimizes State metadata by not storing the IDs of deleted elements. This
2591 /// approach complicates tracking cursors since they rely on these IDs. The solution
2592 /// recalculates position by replaying relevant history to update stable positions
2593 /// accurately. To minimize the performance impact of history replay, the system
2594 /// updates cursor info to reference only the IDs of currently present elements,
2595 /// thereby reducing the need for replay.
2596 ///
2597 /// # Example
2598 ///
2599 /// ```
2600 /// use loro::LoroDoc;
2601 /// use loro_internal::cursor::Side;
2602 ///
2603 /// let doc = LoroDoc::new();
2604 /// let list = doc.get_movable_list("list");
2605 /// list.insert(0, 0).unwrap();
2606 /// let cursor = list.get_cursor(0, Side::Middle).unwrap();
2607 /// assert_eq!(doc.get_cursor_pos(&cursor).unwrap().current.pos, 0);
2608 /// list.insert(0, 0).unwrap();
2609 /// assert_eq!(doc.get_cursor_pos(&cursor).unwrap().current.pos, 1);
2610 /// list.insert(0, 0).unwrap();
2611 /// list.insert(0, 0).unwrap();
2612 /// assert_eq!(doc.get_cursor_pos(&cursor).unwrap().current.pos, 3);
2613 /// list.insert(4, 0).unwrap();
2614 /// assert_eq!(doc.get_cursor_pos(&cursor).unwrap().current.pos, 3);
2615 /// ```
2616 pub fn get_cursor(&self, pos: usize, side: Side) -> Option<Cursor> {
2617 self.handler.get_cursor(pos, side)
2618 }
2619
2620 /// Get the elements of the list as a vector of LoroValues.
2621 ///
2622 /// This method returns a vector containing all the elements in the list as LoroValues.
2623 /// It provides a convenient way to access the entire contents of the LoroMovableList
2624 /// as a standard Rust vector.
2625 ///
2626 /// # Returns
2627 ///
2628 /// A `Vec<LoroValue>` containing all elements of the list.
2629 ///
2630 /// # Example
2631 ///
2632 /// ```
2633 /// use loro::LoroDoc;
2634 ///
2635 /// let doc = LoroDoc::new();
2636 /// let list = doc.get_movable_list("mylist");
2637 /// list.insert(0, 1).unwrap();
2638 /// list.insert(1, "hello").unwrap();
2639 /// list.insert(2, true).unwrap();
2640 ///
2641 /// let vec = list.to_vec();
2642 /// assert_eq!(vec.len(), 3);
2643 /// assert_eq!(vec[0], 1.into());
2644 /// assert_eq!(vec[1], "hello".into());
2645 /// assert_eq!(vec[2], true.into());
2646 /// ```
2647 pub fn to_vec(&self) -> Vec<LoroValue> {
2648 self.get_value().into_list().unwrap().unwrap()
2649 }
2650
2651 /// Delete all elements in the list.
2652 pub fn clear(&self) -> LoroResult<()> {
2653 self.handler.clear()
2654 }
2655
2656 /// Iterate over the elements of the list.
2657 pub fn for_each<I>(&self, mut f: I)
2658 where
2659 I: FnMut(ValueOrContainer),
2660 {
2661 self.handler.for_each(&mut |v| {
2662 f(ValueOrContainer::from(v));
2663 })
2664 }
2665
2666 /// Get the creator of the list item at the given position.
2667 pub fn get_creator_at(&self, pos: usize) -> Option<PeerID> {
2668 self.handler.get_creator_at(pos)
2669 }
2670
2671 /// Get the last mover of the list item at the given position.
2672 pub fn get_last_mover_at(&self, pos: usize) -> Option<PeerID> {
2673 self.handler.get_last_mover_at(pos)
2674 }
2675
2676 /// Get the last editor of the list item at the given position.
2677 pub fn get_last_editor_at(&self, pos: usize) -> Option<PeerID> {
2678 self.handler.get_last_editor_at(pos)
2679 }
2680}
2681
2682impl Default for LoroMovableList {
2683 fn default() -> Self {
2684 Self::new()
2685 }
2686}
2687
2688/// Unknown container.
2689#[derive(Clone, Debug)]
2690pub struct LoroUnknown {
2691 handler: InnerUnknownHandler,
2692}
2693
2694impl LoroUnknown {
2695 /// Get the container id.
2696 pub fn id(&self) -> ContainerID {
2697 self.handler.id().clone()
2698 }
2699}
2700
2701impl SealedTrait for LoroUnknown {}
2702impl ContainerTrait for LoroUnknown {
2703 type Handler = InnerUnknownHandler;
2704
2705 fn to_container(&self) -> Container {
2706 Container::Unknown(self.clone())
2707 }
2708
2709 fn to_handler(&self) -> Self::Handler {
2710 self.handler.clone()
2711 }
2712
2713 fn from_handler(handler: Self::Handler) -> Self {
2714 Self { handler }
2715 }
2716
2717 fn try_from_container(container: Container) -> Option<Self>
2718 where
2719 Self: Sized,
2720 {
2721 match container {
2722 Container::Unknown(x) => Some(x),
2723 _ => None,
2724 }
2725 }
2726
2727 fn is_attached(&self) -> bool {
2728 self.handler.is_attached()
2729 }
2730
2731 fn get_attached(&self) -> Option<Self>
2732 where
2733 Self: Sized,
2734 {
2735 self.handler.get_attached().map(Self::from_handler)
2736 }
2737
2738 fn is_deleted(&self) -> bool {
2739 self.handler.is_deleted()
2740 }
2741 fn doc(&self) -> Option<LoroDoc> {
2742 self.handler.doc().map(LoroDoc::_new)
2743 }
2744}
2745
2746use enum_as_inner::EnumAsInner;
2747
2748/// All the CRDT containers supported by Loro.
2749#[derive(Clone, Debug, EnumAsInner)]
2750pub enum Container {
2751 /// [LoroList container](https://loro.dev/docs/tutorial/list)
2752 List(LoroList),
2753 /// [LoroMap container](https://loro.dev/docs/tutorial/map)
2754 Map(LoroMap),
2755 /// [LoroText container](https://loro.dev/docs/tutorial/text)
2756 Text(LoroText),
2757 /// [LoroTree container]
2758 Tree(LoroTree),
2759 /// [LoroMovableList container](https://loro.dev/docs/tutorial/list)
2760 MovableList(LoroMovableList),
2761 #[cfg(feature = "counter")]
2762 /// [LoroCounter container]
2763 Counter(counter::LoroCounter),
2764 /// Unknown container
2765 Unknown(LoroUnknown),
2766}
2767
2768impl SealedTrait for Container {}
2769impl ContainerTrait for Container {
2770 type Handler = loro_internal::handler::Handler;
2771
2772 fn to_container(&self) -> Container {
2773 self.clone()
2774 }
2775
2776 fn to_handler(&self) -> Self::Handler {
2777 match self {
2778 Container::List(x) => Self::Handler::List(x.to_handler()),
2779 Container::Map(x) => Self::Handler::Map(x.to_handler()),
2780 Container::Text(x) => Self::Handler::Text(x.to_handler()),
2781 Container::Tree(x) => Self::Handler::Tree(x.to_handler()),
2782 Container::MovableList(x) => Self::Handler::MovableList(x.to_handler()),
2783 #[cfg(feature = "counter")]
2784 Container::Counter(x) => Self::Handler::Counter(x.to_handler()),
2785 Container::Unknown(x) => Self::Handler::Unknown(x.to_handler()),
2786 }
2787 }
2788
2789 fn from_handler(handler: Self::Handler) -> Self {
2790 match handler {
2791 InnerHandler::Text(x) => Container::Text(LoroText { handler: x }),
2792 InnerHandler::Map(x) => Container::Map(LoroMap { handler: x }),
2793 InnerHandler::List(x) => Container::List(LoroList { handler: x }),
2794 InnerHandler::MovableList(x) => Container::MovableList(LoroMovableList { handler: x }),
2795 InnerHandler::Tree(x) => Container::Tree(LoroTree { handler: x }),
2796 #[cfg(feature = "counter")]
2797 InnerHandler::Counter(x) => Container::Counter(counter::LoroCounter { handler: x }),
2798 InnerHandler::Unknown(x) => Container::Unknown(LoroUnknown { handler: x }),
2799 }
2800 }
2801
2802 fn is_attached(&self) -> bool {
2803 match self {
2804 Container::List(x) => x.is_attached(),
2805 Container::Map(x) => x.is_attached(),
2806 Container::Text(x) => x.is_attached(),
2807 Container::Tree(x) => x.is_attached(),
2808 Container::MovableList(x) => x.is_attached(),
2809 #[cfg(feature = "counter")]
2810 Container::Counter(x) => x.is_attached(),
2811 Container::Unknown(x) => x.is_attached(),
2812 }
2813 }
2814
2815 fn get_attached(&self) -> Option<Self> {
2816 match self {
2817 Container::List(x) => x.get_attached().map(Container::List),
2818 Container::MovableList(x) => x.get_attached().map(Container::MovableList),
2819 Container::Map(x) => x.get_attached().map(Container::Map),
2820 Container::Text(x) => x.get_attached().map(Container::Text),
2821 Container::Tree(x) => x.get_attached().map(Container::Tree),
2822 #[cfg(feature = "counter")]
2823 Container::Counter(x) => x.get_attached().map(Container::Counter),
2824 Container::Unknown(x) => x.get_attached().map(Container::Unknown),
2825 }
2826 }
2827
2828 fn try_from_container(container: Container) -> Option<Self>
2829 where
2830 Self: Sized,
2831 {
2832 Some(container)
2833 }
2834
2835 fn is_deleted(&self) -> bool {
2836 match self {
2837 Container::List(x) => x.is_deleted(),
2838 Container::Map(x) => x.is_deleted(),
2839 Container::Text(x) => x.is_deleted(),
2840 Container::Tree(x) => x.is_deleted(),
2841 Container::MovableList(x) => x.is_deleted(),
2842 #[cfg(feature = "counter")]
2843 Container::Counter(x) => x.is_deleted(),
2844 Container::Unknown(x) => x.is_deleted(),
2845 }
2846 }
2847 fn doc(&self) -> Option<LoroDoc> {
2848 match self {
2849 Container::List(x) => x.doc(),
2850 Container::Map(x) => x.doc(),
2851 Container::Text(x) => x.doc(),
2852 Container::Tree(x) => x.doc(),
2853 Container::MovableList(x) => x.doc(),
2854 #[cfg(feature = "counter")]
2855 Container::Counter(x) => x.doc(),
2856 Container::Unknown(x) => x.doc(),
2857 }
2858 }
2859}
2860
2861impl Container {
2862 /// Create a detached container of the given type.
2863 ///
2864 /// A detached container is a container that is not attached to a document.
2865 /// The edits on a detached container will not be persisted.
2866 /// To attach the container to the document, please insert it into an attached container.
2867 pub fn new(kind: ContainerType) -> Self {
2868 match kind {
2869 ContainerType::List => Container::List(LoroList::new()),
2870 ContainerType::MovableList => Container::MovableList(LoroMovableList::new()),
2871 ContainerType::Map => Container::Map(LoroMap::new()),
2872 ContainerType::Text => Container::Text(LoroText::new()),
2873 ContainerType::Tree => Container::Tree(LoroTree::new()),
2874 #[cfg(feature = "counter")]
2875 ContainerType::Counter => Container::Counter(counter::LoroCounter::new()),
2876 ContainerType::Unknown(_) => unreachable!(),
2877 }
2878 }
2879
2880 /// Get the type of the container.
2881 pub fn get_type(&self) -> ContainerType {
2882 match self {
2883 Container::List(_) => ContainerType::List,
2884 Container::MovableList(_) => ContainerType::MovableList,
2885 Container::Map(_) => ContainerType::Map,
2886 Container::Text(_) => ContainerType::Text,
2887 Container::Tree(_) => ContainerType::Tree,
2888 #[cfg(feature = "counter")]
2889 Container::Counter(_) => ContainerType::Counter,
2890 Container::Unknown(x) => x.handler.id().container_type(),
2891 }
2892 }
2893
2894 /// Get the id of the container.
2895 pub fn id(&self) -> ContainerID {
2896 match self {
2897 Container::List(x) => x.id(),
2898 Container::MovableList(x) => x.id(),
2899 Container::Map(x) => x.id(),
2900 Container::Text(x) => x.id(),
2901 Container::Tree(x) => x.id(),
2902 #[cfg(feature = "counter")]
2903 Container::Counter(x) => x.id(),
2904 Container::Unknown(x) => x.handler.id(),
2905 }
2906 }
2907}
2908
2909impl From<InnerHandler> for Container {
2910 fn from(value: InnerHandler) -> Self {
2911 match value {
2912 InnerHandler::Text(x) => Container::Text(LoroText { handler: x }),
2913 InnerHandler::Map(x) => Container::Map(LoroMap { handler: x }),
2914 InnerHandler::List(x) => Container::List(LoroList { handler: x }),
2915 InnerHandler::Tree(x) => Container::Tree(LoroTree { handler: x }),
2916 InnerHandler::MovableList(x) => Container::MovableList(LoroMovableList { handler: x }),
2917 #[cfg(feature = "counter")]
2918 InnerHandler::Counter(x) => Container::Counter(counter::LoroCounter { handler: x }),
2919 InnerHandler::Unknown(x) => Container::Unknown(LoroUnknown { handler: x }),
2920 }
2921 }
2922}
2923
2924/// It's a type that can be either a value or a container.
2925#[derive(Debug, Clone, EnumAsInner)]
2926pub enum ValueOrContainer {
2927 /// A value.
2928 Value(LoroValue),
2929 /// A container.
2930 Container(Container),
2931}
2932
2933impl ValueOrContainer {
2934 /// Get the deep value of the value or container.
2935 pub fn get_deep_value(&self) -> LoroValue {
2936 match self {
2937 ValueOrContainer::Value(v) => v.clone(),
2938 ValueOrContainer::Container(c) => match c {
2939 Container::List(c) => c.get_deep_value(),
2940 Container::Map(c) => c.get_deep_value(),
2941 Container::Text(c) => c.to_string().into(),
2942 Container::Tree(c) => c.get_value(),
2943 Container::MovableList(c) => c.get_deep_value(),
2944 #[cfg(feature = "counter")]
2945 Container::Counter(c) => c.get_value().into(),
2946 Container::Unknown(_) => LoroValue::Null,
2947 },
2948 }
2949 }
2950
2951 pub(crate) fn into_value_or_handler(self) -> ValueOrHandler {
2952 match self {
2953 ValueOrContainer::Value(v) => ValueOrHandler::Value(v),
2954 ValueOrContainer::Container(c) => ValueOrHandler::Handler(c.to_handler()),
2955 }
2956 }
2957}
2958
2959/// UndoManager can be used to undo and redo the changes made to the document with a certain peer.
2960#[derive(Debug)]
2961#[repr(transparent)]
2962pub struct UndoManager(InnerUndoManager);
2963
2964impl UndoManager {
2965 /// Create a new UndoManager.
2966 pub fn new(doc: &LoroDoc) -> Self {
2967 let mut inner = InnerUndoManager::new(&doc.doc);
2968 inner.set_max_undo_steps(100);
2969 Self(inner)
2970 }
2971
2972 /// Undo the last change made by the peer.
2973 pub fn undo(&mut self) -> LoroResult<bool> {
2974 self.0.undo()
2975 }
2976
2977 /// Redo the last change made by the peer.
2978 pub fn redo(&mut self) -> LoroResult<bool> {
2979 self.0.redo()
2980 }
2981
2982 /// Record a new checkpoint.
2983 pub fn record_new_checkpoint(&mut self) -> LoroResult<()> {
2984 self.0.record_new_checkpoint()
2985 }
2986
2987 /// Whether the undo manager can undo.
2988 pub fn can_undo(&self) -> bool {
2989 self.0.can_undo()
2990 }
2991
2992 /// Whether the undo manager can redo.
2993 pub fn can_redo(&self) -> bool {
2994 self.0.can_redo()
2995 }
2996
2997 /// If a local event's origin matches the given prefix, it will not be recorded in the
2998 /// undo stack.
2999 pub fn add_exclude_origin_prefix(&mut self, prefix: &str) {
3000 self.0.add_exclude_origin_prefix(prefix)
3001 }
3002
3003 /// Set the maximum number of undo steps. The default value is 100.
3004 pub fn set_max_undo_steps(&mut self, size: usize) {
3005 self.0.set_max_undo_steps(size)
3006 }
3007
3008 /// Set the merge interval in ms. The default value is 0, which means no merge.
3009 pub fn set_merge_interval(&mut self, interval: i64) {
3010 self.0.set_merge_interval(interval)
3011 }
3012
3013 /// Set the listener for push events.
3014 /// The listener will be called when a new undo/redo item is pushed into the stack.
3015 pub fn set_on_push(&mut self, on_push: Option<OnPush>) {
3016 if let Some(on_push) = on_push {
3017 self.0.set_on_push(Some(Box::new(move |u, c, e| {
3018 on_push(u, c, e.map(|x| x.into()))
3019 })));
3020 } else {
3021 self.0.set_on_push(None);
3022 }
3023 }
3024
3025 /// Set the listener for pop events.
3026 /// The listener will be called when an undo/redo item is popped from the stack.
3027 pub fn set_on_pop(&mut self, on_pop: Option<OnPop>) {
3028 self.0.set_on_pop(on_pop);
3029 }
3030
3031 /// Clear the undo stack and the redo stack
3032 pub fn clear(&self) {
3033 self.0.clear();
3034 }
3035}
3036/// 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.
3037/// The returned cursors will be recorded for a new pushed undo item.
3038pub type OnPush =
3039 Box<dyn for<'a> Fn(UndoOrRedo, CounterSpan, Option<DiffEvent>) -> UndoItemMeta + Send + Sync>;