pub struct LoroDoc { /* private fields */ }Implementations§
Source§impl LoroDoc
impl LoroDoc
pub fn new() -> Self
pub fn fork(&self) -> Arc<Self>
pub fn fork_at(&self, frontiers: &Frontiers) -> Arc<Self>
Sourcepub fn get_change(&self, id: ID) -> Option<ChangeMeta>
pub fn get_change(&self, id: ID) -> Option<ChangeMeta>
Get Change at the given id.
Change is a grouped continuous operations that share the same id, timestamp, commit message.
- The id of the
Changeis the id of its first op. - The second op’s id is
{ peer: change.id.peer, counter: change.id.counter + 1 }
The same applies on Lamport:
- The lamport of the
Changeis the lamport of its first op. - The second op’s lamport is
change.lamport + 1
The length of the Change is how many operations it contains
Sourcepub fn set_record_timestamp(&self, record: bool)
pub fn set_record_timestamp(&self, record: bool)
Set whether to record the timestamp of each change. Default is false.
If enabled, the Unix timestamp will be recorded for each change automatically.
You can set each timestamp manually when committing a change.
NOTE: Timestamps are forced to be in ascending order. If you commit a new change with a timestamp that is less than the existing one, the largest existing timestamp will be used instead.
Sourcepub fn set_change_merge_interval(&self, interval: i64)
pub fn set_change_merge_interval(&self, interval: i64)
Set the interval of mergeable changes, in seconds.
If two continuous local changes are within the interval, they will be merged into one change. The default value is 1000 seconds.
By default, we record timestamps in seconds for each change. So if the merge interval is 1, and changes A and B have timestamps of 3 and 4 respectively, then they will be merged into one change
Sourcepub fn config_text_style(&self, text_style: Arc<StyleConfigMap>)
pub fn config_text_style(&self, text_style: Arc<StyleConfigMap>)
Set the rich text format configuration of the document.
You need to config it if you use rich text mark method.
Specifically, you need to config the expand property of each style.
Expand is used to specify the behavior of expanding when new text is inserted at the beginning or end of the style.
Sourcepub fn config_default_text_style(&self, text_style: Option<StyleConfig>)
pub fn config_default_text_style(&self, text_style: Option<StyleConfig>)
Configures the default text style for the document.
This method sets the default text style configuration for the document when using LoroText.
If None is provided, the default style is reset.
§Parameters
text_style: The style configuration to set as the default.Noneto reset.
Sourcepub fn attach(&self)
pub fn attach(&self)
Attach the document state to the latest known version.
The document becomes detached during a
checkoutoperation. Beingdetachedimplies that theDocStateis not synchronized with the latest version of theOpLog. In a detached state, the document is not editable, and anyimportoperations will be recorded in theOpLogwithout being applied to theDocState.
Sourcepub fn checkout(&self, frontiers: &Frontiers) -> LoroResult<()>
pub fn checkout(&self, frontiers: &Frontiers) -> LoroResult<()>
Checkout the DocState to a specific version.
The document becomes detached during a
checkoutoperation. Beingdetachedimplies that theDocStateis not synchronized with the latest version of theOpLog. In a detached state, the document is not editable, and anyimportoperations will be recorded in theOpLogwithout being applied to theDocState.
You should call attach to attach the DocState to the latest version of OpLog.
Sourcepub fn checkout_to_latest(&self)
pub fn checkout_to_latest(&self)
Checkout the DocState to the latest version.
The document becomes detached during a
checkoutoperation. Beingdetachedimplies that theDocStateis not synchronized with the latest version of theOpLog. In a detached state, the document is not editable, and anyimportoperations will be recorded in theOpLogwithout being applied to theDocState.
This has the same effect as attach.
Sourcepub fn cmp_with_frontiers(&self, other: &Frontiers) -> Ordering
pub fn cmp_with_frontiers(&self, other: &Frontiers) -> Ordering
Compare the frontiers with the current OpLog’s version.
If other contains any version that’s not contained in the current OpLog, return Ordering::Less.
pub fn cmp_frontiers( &self, a: &Frontiers, b: &Frontiers, ) -> Result<Option<Ordering>, FrontiersNotIncluded>
Sourcepub fn detach(&self)
pub fn detach(&self)
Force the document enter the detached mode.
In this mode, when you importing new updates, the [loro_internal::DocState] will not be changed.
Learn more at https://loro.dev/docs/advanced/doc_state_and_oplog#attacheddetached-status
Sourcepub fn import_batch(&self, bytes: &[Vec<u8>]) -> Result<ImportStatus, LoroError>
pub fn import_batch(&self, bytes: &[Vec<u8>]) -> Result<ImportStatus, LoroError>
Import a batch of updates/snapshot.
The data can be in arbitrary order. The import result will be the same.
pub fn get_movable_list( &self, id: Arc<dyn ContainerIdLike>, ) -> Arc<LoroMovableList>
pub fn get_list(&self, id: Arc<dyn ContainerIdLike>) -> Arc<LoroList>
pub fn get_map(&self, id: Arc<dyn ContainerIdLike>) -> Arc<LoroMap>
pub fn get_text(&self, id: Arc<dyn ContainerIdLike>) -> Arc<LoroText>
pub fn get_tree(&self, id: Arc<dyn ContainerIdLike>) -> Arc<LoroTree>
pub fn get_counter(&self, id: Arc<dyn ContainerIdLike>) -> Arc<LoroCounter>
pub fn get_container( &self, id: &ContainerID, ) -> Option<Arc<dyn ValueOrContainer>>
Sourcepub fn commit(&self)
pub fn commit(&self)
Commit the cumulative auto commit transaction.
There is a transaction behind every operation. The events will be emitted after a transaction is committed. A transaction is committed when:
doc.commit()is called.doc.exportFrom(version)is called.doc.import(data)is called.doc.checkout(version)is called.
pub fn commit_with(&self, options: CommitOptions)
Sourcepub fn set_next_commit_message(&self, msg: &str)
pub fn set_next_commit_message(&self, msg: &str)
Set commit message for the current uncommitted changes
It will be persisted.
Sourcepub fn set_next_commit_origin(&self, origin: &str)
pub fn set_next_commit_origin(&self, origin: &str)
Set origin for the current uncommitted changes, it can be used to track the source of changes in an event.
It will NOT be persisted.
Sourcepub fn set_next_commit_timestamp(&self, timestamp: i64)
pub fn set_next_commit_timestamp(&self, timestamp: i64)
Set the timestamp of the next commit.
It will be persisted and stored in the OpLog.
You can get the timestamp from the [Change] type.
Sourcepub fn set_next_commit_options(&self, options: CommitOptions)
pub fn set_next_commit_options(&self, options: CommitOptions)
Set the options of the next commit.
It will be used when the next commit is performed.
Sourcepub fn clear_next_commit_options(&self)
pub fn clear_next_commit_options(&self)
Clear the options of the next commit.
Sourcepub fn is_detached(&self) -> bool
pub fn is_detached(&self) -> bool
Whether the document is in detached mode, where the [loro_internal::DocState] is not synchronized with the latest version of the [loro_internal::OpLog].
Sourcepub fn import(&self, bytes: &[u8]) -> Result<ImportStatus, LoroError>
pub fn import(&self, bytes: &[u8]) -> Result<ImportStatus, LoroError>
Import updates/snapshot exported by LoroDoc::export_snapshot or [LoroDoc::export_from].
Sourcepub fn import_with(
&self,
bytes: &[u8],
origin: &str,
) -> Result<ImportStatus, LoroError>
pub fn import_with( &self, bytes: &[u8], origin: &str, ) -> Result<ImportStatus, LoroError>
Import updates/snapshot exported by LoroDoc::export_snapshot or [LoroDoc::export_from].
It marks the import with a custom origin string. It can be used to track the import source
in the generated events.
pub fn import_json_updates(&self, json: &str) -> Result<ImportStatus, LoroError>
Sourcepub fn export_json_updates(
&self,
start_vv: &VersionVector,
end_vv: &VersionVector,
) -> String
pub fn export_json_updates( &self, start_vv: &VersionVector, end_vv: &VersionVector, ) -> String
Export the current state with json-string format of the document.
Sourcepub fn export_json_updates_without_peer_compression(
&self,
start_vv: &VersionVector,
end_vv: &VersionVector,
) -> String
pub fn export_json_updates_without_peer_compression( &self, start_vv: &VersionVector, end_vv: &VersionVector, ) -> String
Export the current state with json-string format of the document, without peer compression.
Compared to [export_json_updates], this method does not compress the peer IDs in the updates.
So the operations are easier to be processed by application code.
Sourcepub fn redact_json_updates(
&self,
json: &str,
version_range: &VersionRange,
) -> Result<String, LoroError>
pub fn redact_json_updates( &self, json: &str, version_range: &VersionRange, ) -> Result<String, LoroError>
Redacts sensitive content in JSON updates within the specified version range.
This function allows you to share document history while removing potentially sensitive content. It preserves the document structure and collaboration capabilities while replacing content with placeholders according to these redaction rules:
- Preserves delete and move operations
- Replaces text insertion content with the Unicode replacement character
- Substitutes list and map insert values with null
- Maintains structure of child containers
- Replaces text mark values with null
- Preserves map keys and text annotation keys
Sourcepub fn export_json_in_id_span(&self, id_span: IdSpan) -> Vec<String>
pub fn export_json_in_id_span(&self, id_span: IdSpan) -> Vec<String>
Export the readable [Change]s in the given IdSpan
Sourcepub fn export_updates(
&self,
vv: &VersionVector,
) -> Result<Vec<u8>, LoroEncodeError>
pub fn export_updates( &self, vv: &VersionVector, ) -> Result<Vec<u8>, LoroEncodeError>
Export all the ops not included in the given VersionVector
Sourcepub fn export_snapshot(&self) -> Result<Vec<u8>, LoroEncodeError>
pub fn export_snapshot(&self) -> Result<Vec<u8>, LoroEncodeError>
Export the current state and history of the document.
pub fn export_snapshot_at( &self, frontiers: &Frontiers, ) -> Result<Vec<u8>, LoroEncodeError>
pub fn frontiers_to_vv( &self, frontiers: &Frontiers, ) -> Option<Arc<VersionVector>>
pub fn minimize_frontiers(&self, frontiers: &Frontiers) -> FrontiersOrID
pub fn vv_to_frontiers(&self, vv: &VersionVector) -> Arc<Frontiers>
pub fn oplog_vv(&self) -> Arc<VersionVector>
pub fn state_vv(&self) -> Arc<VersionVector>
Sourcepub fn shallow_since_vv(&self) -> Arc<VersionVector>
pub fn shallow_since_vv(&self) -> Arc<VersionVector>
Get the VersionVector of the start of the shallow history
The ops included by the shallow history are not in the doc.
Sourcepub fn len_changes(&self) -> u64
pub fn len_changes(&self) -> u64
Get the total number of changes in the OpLog
pub fn get_deep_value(&self) -> LoroValue
Sourcepub fn get_deep_value_with_id(&self) -> LoroValue
pub fn get_deep_value_with_id(&self) -> LoroValue
Get the current state with container id of the doc
pub fn oplog_frontiers(&self) -> Arc<Frontiers>
pub fn state_frontiers(&self) -> Arc<Frontiers>
Sourcepub fn set_peer_id(&self, peer: PeerID) -> LoroResult<()>
pub fn set_peer_id(&self, peer: PeerID) -> LoroResult<()>
Change the PeerID
NOTE: You need ot make sure there is no chance two peer have the same PeerID. If it happens, the document will be corrupted.
pub fn subscribe( &self, container_id: &ContainerID, subscriber: Arc<dyn Subscriber>, ) -> Arc<Subscription>
pub fn subscribe_root( &self, subscriber: Arc<dyn Subscriber>, ) -> Arc<Subscription>
Sourcepub fn subscribe_local_update(
&self,
callback: Arc<dyn LocalUpdateCallback>,
) -> Arc<Subscription>
pub fn subscribe_local_update( &self, callback: Arc<dyn LocalUpdateCallback>, ) -> Arc<Subscription>
Subscribe the local update of the document.
Sourcepub fn check_state_correctness_slow(&self)
pub fn check_state_correctness_slow(&self)
Check the correctness of the document state by comparing it with the state calculated by applying all the history.
pub fn get_by_path(&self, path: &[Index]) -> Option<Arc<dyn ValueOrContainer>>
Sourcepub fn get_by_str_path(&self, path: &str) -> Option<Arc<dyn ValueOrContainer>>
pub fn get_by_str_path(&self, path: &str) -> Option<Arc<dyn ValueOrContainer>>
The path can be specified in different ways depending on the container type:
For Tree:
- Using node IDs:
tree/{node_id}/property - Using indices:
tree/0/1/property
For List and MovableList:
- Using indices:
list/0orlist/1/property
For Map:
- Using keys:
map/keyormap/nested/property
For tree structures, index-based paths follow depth-first traversal order. The indices start from 0 and represent the position of a node among its siblings.
§Examples
let doc = LoroDoc::new();
// Tree example
let tree = doc.get_tree("tree");
let root = tree.create(None).unwrap();
tree.get_meta(root).unwrap().insert("name", "root").unwrap();
// Access tree by ID or index
let name1 = doc.get_by_str_path(&format!("tree/{}/name", root)).unwrap().into_value().unwrap();
let name2 = doc.get_by_str_path("tree/0/name").unwrap().into_value().unwrap();
assert_eq!(name1, name2);
// List example
let list = doc.get_list("list");
list.insert(0, "first").unwrap();
list.insert(1, "second").unwrap();
// Access list by index
let item = doc.get_by_str_path("list/0");
assert_eq!(item.unwrap().into_value().unwrap().into_string().unwrap(), "first".into());
// Map example
let map = doc.get_map("map");
map.insert("key", "value").unwrap();
// Access map by key
let value = doc.get_by_str_path("map/key");
assert_eq!(value.unwrap().into_value().unwrap().into_string().unwrap(), "value".into());
// MovableList example
let mlist = doc.get_movable_list("mlist");
mlist.insert(0, "item").unwrap();
// Access movable list by index
let item = doc.get_by_str_path("mlist/0");
assert_eq!(item.unwrap().into_value().unwrap().into_string().unwrap(), "item".into());pub fn get_cursor_pos( &self, cursor: &Cursor, ) -> Result<PosQueryResult, CannotFindRelativePosition>
Sourcepub fn has_history_cache(&self) -> bool
pub fn has_history_cache(&self) -> bool
Whether the history cache is built.
Sourcepub fn free_history_cache(&self)
pub fn free_history_cache(&self)
Free the history cache that is used for making checkout faster.
If you use checkout that switching to an old/concurrent version, the history cache will be built. You can free it by calling this method.
Sourcepub fn free_diff_calculator(&self)
pub fn free_diff_calculator(&self)
Free the cached diff calculator that is used for checkout.
Sourcepub fn compact_change_store(&self)
pub fn compact_change_store(&self)
Encoded all ops and history cache to bytes and store them in the kv store.
The parsed ops will be dropped
Sourcepub fn export_updates_in_range(
&self,
spans: &[IdSpan],
) -> Result<Vec<u8>, LoroEncodeError>
pub fn export_updates_in_range( &self, spans: &[IdSpan], ) -> Result<Vec<u8>, LoroEncodeError>
Export the document in the given mode.
pub fn export_shallow_snapshot( &self, frontiers: &Frontiers, ) -> Result<Vec<u8>, LoroEncodeError>
pub fn export_state_only( &self, frontiers: Option<Arc<Frontiers>>, ) -> Result<Vec<u8>, LoroEncodeError>
Sourcepub fn analyze(&self) -> DocAnalysis
pub fn analyze(&self) -> DocAnalysis
Analyze the container info of the doc
This is used for development and debugging. It can be slow.
Sourcepub fn get_path_to_container(
&self,
id: &ContainerID,
) -> Option<Vec<ContainerPath>>
pub fn get_path_to_container( &self, id: &ContainerID, ) -> Option<Vec<ContainerPath>>
Get the path from the root to the container
Sourcepub fn jsonpath(
&self,
path: &str,
) -> Result<Vec<Arc<dyn ValueOrContainer>>, JsonPathError>
pub fn jsonpath( &self, path: &str, ) -> Result<Vec<Arc<dyn ValueOrContainer>>, JsonPathError>
Evaluate a JSONPath expression on the document and return matching values or handlers.
This method allows querying the document structure using JSONPath syntax.
It returns a vector of ValueOrHandler which can represent either primitive values
or container handlers, depending on what the JSONPath expression matches.
§Arguments
path- A string slice containing the JSONPath expression to evaluate.
§Returns
A Result containing either:
Ok(Vec<ValueOrHandler>): A vector of matching values or handlers.Err(String): An error message if the JSONPath expression is invalid or evaluation fails.
Sourcepub fn subscribe_jsonpath(
&self,
path: &str,
callback: Arc<dyn JsonPathSubscriber>,
) -> LoroResult<Arc<Subscription>>
pub fn subscribe_jsonpath( &self, path: &str, callback: Arc<dyn JsonPathSubscriber>, ) -> LoroResult<Arc<Subscription>>
Subscribe to updates that may affect the given JSONPath query.
The callback may fire false positives; it is intended as a lightweight signal so callers can debounce or throttle before running an expensive JSONPath query themselves.
pub fn travel_change_ancestors( &self, ids: &[ID], f: Arc<dyn ChangeAncestorsTraveler>, ) -> Result<(), ChangeTravelError>
pub fn get_changed_containers_in(&self, id: ID, len: u32) -> Vec<ContainerID>
pub fn is_shallow(&self) -> bool
pub fn get_pending_txn_len(&self) -> u32
Sourcepub fn find_id_spans_between(
&self,
from: &Frontiers,
to: &Frontiers,
) -> VersionVectorDiff
pub fn find_id_spans_between( &self, from: &Frontiers, to: &Frontiers, ) -> VersionVectorDiff
Find the operation id spans that between the from version and the to version.
Sourcepub fn revert_to(&self, version: &Frontiers) -> LoroResult<()>
pub fn revert_to(&self, version: &Frontiers) -> LoroResult<()>
Revert the current document state back to the target version
Internally, it will generate a series of local operations that can revert the current doc to the target version. It will calculate the diff between the current state and the target state, and apply the diff to the current state.
Sourcepub fn apply_diff(&self, diff: &DiffBatch) -> LoroResult<()>
pub fn apply_diff(&self, diff: &DiffBatch) -> LoroResult<()>
Apply a diff to the current document state.
Internally, it will apply the diff to the current state.
Sourcepub fn diff(&self, a: &Frontiers, b: &Frontiers) -> LoroResult<Arc<DiffBatch>>
pub fn diff(&self, a: &Frontiers, b: &Frontiers) -> LoroResult<Arc<DiffBatch>>
Calculate the diff between two versions
Sourcepub fn has_container(&self, id: &ContainerID) -> bool
pub fn has_container(&self, id: &ContainerID) -> bool
Check if the doc contains the target container.
A root container always exists, while a normal container exists if it has ever been created on the doc.
Sourcepub fn subscribe_first_commit_from_peer(
&self,
subscriber: Arc<dyn FirstCommitFromPeerCallback>,
) -> Arc<Subscription>
pub fn subscribe_first_commit_from_peer( &self, subscriber: Arc<dyn FirstCommitFromPeerCallback>, ) -> Arc<Subscription>
Subscribe to the first commit from a peer. Operations performed on the LoroDoc within this callback
will be merged into the current commit.
This is useful for managing the relationship between PeerID and user information.
For example, you could store user names in a LoroMap using PeerID as the key and the UserID as the value.
Sourcepub fn subscribe_pre_commit(
&self,
callback: Arc<dyn PreCommitCallback>,
) -> Arc<Subscription>
pub fn subscribe_pre_commit( &self, callback: Arc<dyn PreCommitCallback>, ) -> Arc<Subscription>
Subscribe to the pre-commit event.
The callback will be called when the changes are committed but not yet applied to the OpLog.
You can modify the commit message and timestamp in the callback by ChangeModifier.
Sourcepub fn set_hide_empty_root_containers(&self, hide: bool)
pub fn set_hide_empty_root_containers(&self, hide: bool)
Set whether to hide empty root containers.
Sourcepub fn delete_root_container(&self, cid: ContainerID)
pub fn delete_root_container(&self, cid: ContainerID)
Delete all content from a root container and hide it from the document.
When a root container is empty and hidden:
- It won’t show up in
get_deep_value()results - It won’t be included in document snapshots
Only works on root containers (containers without parents).
Methods from Deref<Target = InnerLoroDoc>§
Sourcepub fn fork(&self) -> LoroDoc
pub fn fork(&self) -> LoroDoc
Duplicate the document with a different PeerID
The time complexity and space complexity of this operation are both O(n),
When called in detached mode, it will fork at the current state frontiers.
It will have the same effect as fork_at(&self.state_frontiers()).
Sourcepub fn fork_at(&self, frontiers: &Frontiers) -> LoroDoc
pub fn fork_at(&self, frontiers: &Frontiers) -> LoroDoc
Fork the document at the given frontiers.
The created doc will only contain the history before the specified frontiers.
Sourcepub fn get_change(&self, id: ID) -> Option<ChangeMeta>
pub fn get_change(&self, id: ID) -> Option<ChangeMeta>
Get Change at the given id.
Change is a grouped continuous operations that share the same id, timestamp, commit message.
- The id of the
Changeis the id of its first op. - The second op’s id is
{ peer: change.id.peer, counter: change.id.counter + 1 }
The same applies on Lamport:
- The lamport of the
Changeis the lamport of its first op. - The second op’s lamport is
change.lamport + 1
The length of the Change is how many operations it contains
Sourcepub fn set_record_timestamp(&self, record: bool)
pub fn set_record_timestamp(&self, record: bool)
Set whether to record the timestamp of each change. Default is false.
If enabled, the Unix timestamp will be recorded for each change automatically.
You can also set a timestamp explicitly via [set_next_commit_timestamp].
Important: this is a runtime configuration. It is not serialized into updates or
snapshots. You must reapply it for each new LoroDoc you create or load.
NOTE: Timestamps are forced to be in ascending order. If you commit a new change with a timestamp earlier than the latest, the largest existing timestamp will be used instead.
§Example
use loro::LoroDoc;
let doc = LoroDoc::new();
doc.set_record_timestamp(true);
doc.get_text("t").insert(0, "hi").unwrap();
doc.commit();Sourcepub fn set_detached_editing(&self, enable: bool)
pub fn set_detached_editing(&self, enable: bool)
Enables editing in detached mode, which is disabled by default.
The doc enter detached mode after calling detach or checking out a non-latest version.
§Important Notes:
- This mode uses a different PeerID for each checkout.
- Ensure no concurrent operations share the same PeerID if set manually.
- Importing does not affect the document’s state or version; changes are
recorded in the OpLog only. Call
checkoutto apply changes.
§Example
use loro::LoroDoc;
let doc = LoroDoc::new();
let v0 = doc.state_frontiers();
// Make some edits…
doc.get_text("t").insert(0, "Hello").unwrap();
doc.commit();
// Travel back and enable detached editing
doc.checkout(&v0).unwrap();
assert!(doc.is_detached());
doc.set_detached_editing(true);
doc.get_text("t").insert(0, "old").unwrap();
// Later, re-attach to see latest again
doc.attach();Sourcepub fn is_detached_editing_enabled(&self) -> bool
pub fn is_detached_editing_enabled(&self) -> bool
Whether editing the doc in detached mode is allowed, which is disabled by default.
The doc enter detached mode after calling detach or checking out a non-latest version.
§Important Notes:
- This mode uses a different PeerID for each checkout.
- Ensure no concurrent operations share the same PeerID if set manually.
- Importing does not affect the document’s state or version; changes are
recorded in the OpLog only. Call
checkoutto apply changes.
Sourcepub fn set_change_merge_interval(&self, interval: i64)
pub fn set_change_merge_interval(&self, interval: i64)
Set the interval of mergeable changes, in seconds.
If two continuous local changes are within the interval, they will be merged into one change. The default value is 1000 seconds.
By default, we record timestamps in seconds for each change. So if the merge interval is 1, and changes A and B have timestamps of 3 and 4 respectively, then they will be merged into one change.
Sourcepub fn config_text_style(&self, text_style: StyleConfigMap)
pub fn config_text_style(&self, text_style: StyleConfigMap)
Set the rich text format configuration of the document.
Configure the expand behavior for marks used by LoroText::mark/LoroText::unmark.
This controls how marks grow when text is inserted at their boundaries.
after(default): inserts just after the range expand the markbefore: inserts just before the range expand the markboth: inserts on either side expand the marknone: do not expand at boundaries
§Example
use loro::{LoroDoc, StyleConfigMap, StyleConfig, ExpandType};
let doc = LoroDoc::new();
let mut styles = StyleConfigMap::new();
styles.insert("bold".into(), StyleConfig { expand: ExpandType::After });
doc.config_text_style(styles);Sourcepub fn config_default_text_style(&self, text_style: Option<StyleConfig>)
pub fn config_default_text_style(&self, text_style: Option<StyleConfig>)
Configures the default text style for the document.
This method sets the default text style configuration for the document when using LoroText.
If None is provided, the default style is reset.
§Parameters
text_style: The style configuration to set as the default.Noneto reset.
§Example
use loro::{LoroDoc, StyleConfig, ExpandType};
let doc = LoroDoc::new();
doc.config_default_text_style(Some(StyleConfig { expand: ExpandType::After }));Sourcepub fn attach(&self)
pub fn attach(&self)
Attach the document state to the latest known version.
The document becomes detached during a
checkoutoperation. Beingdetachedimplies that theDocStateis not synchronized with the latest version of theOpLog. In a detached state, the document is not editable, and anyimportoperations will be recorded in theOpLogwithout being applied to theDocState.
Sourcepub fn checkout(&self, frontiers: &Frontiers) -> Result<(), LoroError>
pub fn checkout(&self, frontiers: &Frontiers) -> Result<(), LoroError>
Checkout the DocState to a specific version.
The document becomes detached during a checkout operation.
Being detached implies that the DocState is not synchronized with the latest version of the OpLog.
In a detached state, the document is not editable, and any import operations will be
recorded in the OpLog without being applied to the DocState.
You should call attach (or checkout_to_latest) to reattach the DocState to the latest version of OpLog.
If you need to edit while detached, enable [set_detached_editing(true)], but note it uses a different
PeerID per checkout.
Sourcepub fn checkout_to_latest(&self)
pub fn checkout_to_latest(&self)
Checkout the DocState to the latest version.
The document becomes detached during a
checkoutoperation. Beingdetachedimplies that theDocStateis not synchronized with the latest version of theOpLog. In a detached state, the document is not editable, and anyimportoperations will be recorded in theOpLogwithout being applied to theDocState.
This has the same effect as attach.
Sourcepub fn cmp_with_frontiers(&self, other: &Frontiers) -> Ordering
pub fn cmp_with_frontiers(&self, other: &Frontiers) -> Ordering
Compare the frontiers with the current OpLog’s version.
If other contains any version that’s not contained in the current OpLog, return Ordering::Less.
Sourcepub fn cmp_frontiers(
&self,
a: &Frontiers,
b: &Frontiers,
) -> Result<Option<Ordering>, FrontiersNotIncluded>
pub fn cmp_frontiers( &self, a: &Frontiers, b: &Frontiers, ) -> Result<Option<Ordering>, FrontiersNotIncluded>
Compare two frontiers.
If the frontiers are not included in the document, return FrontiersNotIncluded.
Sourcepub fn detach(&self)
pub fn detach(&self)
Force the document enter the detached mode.
In this mode, importing new updates only records them in the OpLog; the loro_internal::DocState is not updated until you reattach.
Learn more at https://loro.dev/docs/advanced/doc_state_and_oplog#attacheddetached-status
Sourcepub fn import_batch(&self, bytes: &[Vec<u8>]) -> Result<ImportStatus, LoroError>
pub fn import_batch(&self, bytes: &[Vec<u8>]) -> Result<ImportStatus, LoroError>
Import a batch of updates/snapshot.
The data can be in arbitrary order. The import result will be the same.
Auto-commit: same as [import], this finalizes the current transaction first.
§Example
use loro::{LoroDoc, ExportMode};
let a = LoroDoc::new();
a.get_text("t").insert(0, "A").unwrap();
let u1 = a.export(ExportMode::all_updates()).unwrap();
a.get_text("t").insert(1, "B").unwrap();
let u2 = a.export(ExportMode::all_updates()).unwrap();
let b = LoroDoc::new();
let status = b.import_batch(&[u2, u1]).unwrap(); // arbitrary order
assert!(status.pending.is_none());Sourcepub fn get_container(&self, id: ContainerID) -> Option<Container>
pub fn get_container(&self, id: ContainerID) -> Option<Container>
Get a Container by container id.
Sourcepub fn get_movable_list<I>(&self, id: I) -> LoroMovableListwhere
I: IntoContainerId,
pub fn get_movable_list<I>(&self, id: I) -> LoroMovableListwhere
I: IntoContainerId,
Get a LoroMovableList by container id.
If the provided id is string, it will be converted into a root container id with the name of the string.
Sourcepub fn get_list<I>(&self, id: I) -> LoroListwhere
I: IntoContainerId,
pub fn get_list<I>(&self, id: I) -> LoroListwhere
I: IntoContainerId,
Get a LoroList by container id.
If the provided id is string, it will be converted into a root container id with the name of the string.
Note: creating/accessing a root container does not record history; creating nested
containers (e.g., Map::insert_container) does.
Sourcepub fn get_map<I>(&self, id: I) -> LoroMapwhere
I: IntoContainerId,
pub fn get_map<I>(&self, id: I) -> LoroMapwhere
I: IntoContainerId,
Get a LoroMap by container id.
If the provided id is string, it will be converted into a root container id with the name of the string.
Note: creating/accessing a root container does not record history; creating nested
containers (e.g., Map::insert_container) does.
Sourcepub fn get_text<I>(&self, id: I) -> LoroTextwhere
I: IntoContainerId,
pub fn get_text<I>(&self, id: I) -> LoroTextwhere
I: IntoContainerId,
Get a LoroText by container id.
If the provided id is string, it will be converted into a root container id with the name of the string.
Note: creating/accessing a root container does not record history; creating nested
containers (e.g., Map::insert_container) does.
Sourcepub fn get_tree<I>(&self, id: I) -> LoroTreewhere
I: IntoContainerId,
pub fn get_tree<I>(&self, id: I) -> LoroTreewhere
I: IntoContainerId,
Get a LoroTree by container id.
If the provided id is string, it will be converted into a root container id with the name of the string.
Note: creating/accessing a root container does not record history; creating nested
containers (e.g., Map::insert_container) does.
Sourcepub fn get_counter<I>(&self, id: I) -> LoroCounterwhere
I: IntoContainerId,
pub fn get_counter<I>(&self, id: I) -> LoroCounterwhere
I: IntoContainerId,
Get a LoroCounter by container id.
If the provided id is string, it will be converted into a root container id with the name of the string.
Sourcepub fn commit(&self)
pub fn commit(&self)
Commit the cumulative auto commit transaction.
There is a transaction behind every operation. The events will be emitted after a transaction is committed. A transaction is committed when:
doc.commit()is called.doc.export(mode)is called.doc.import(data)is called.doc.checkout(version)is called.
Note: Loro transactions are not ACID database transactions. There is no rollback or
isolation; they are a grouping mechanism for events/history. For interactive undo/redo,
use UndoManager.
Empty-commit behavior: this method is an explicit commit. If the pending transaction is empty, any previously set next-commit options (message/timestamp/origin) are swallowed and will not carry over.
Sourcepub fn commit_with(&self, options: CommitOptions)
pub fn commit_with(&self, options: CommitOptions)
Commit the cumulative auto commit transaction with custom options.
There is a transaction behind every operation. It will automatically commit when users invoke export or import. The event will be sent after a transaction is committed
See also: [set_next_commit_message], [set_next_commit_origin],
[set_next_commit_timestamp]. Commit messages are persisted and replicate to peers;
origins are local-only metadata.
Empty-commit behavior: this method is an explicit commit. If the pending
transaction is empty, the provided options are swallowed and will not carry over.
For implicit commits triggered by export/checkout (commit barriers),
message/timestamp/origin from an empty transaction are preserved for the next commit.
Sourcepub fn set_next_commit_message(&self, msg: &str)
pub fn set_next_commit_message(&self, msg: &str)
Set commit message for the current uncommitted changes
It will be persisted.
Sourcepub fn set_next_commit_origin(&self, origin: &str)
pub fn set_next_commit_origin(&self, origin: &str)
Set origin for the current uncommitted changes, it can be used to track the source of changes in an event.
It will NOT be persisted.
Sourcepub fn set_next_commit_timestamp(&self, timestamp: i64)
pub fn set_next_commit_timestamp(&self, timestamp: i64)
Set the timestamp of the next commit.
It will be persisted and stored in the OpLog.
You can get the timestamp from the [Change] type.
Sourcepub fn set_next_commit_options(&self, options: CommitOptions)
pub fn set_next_commit_options(&self, options: CommitOptions)
Set the options of the next commit.
It will be used when the next commit is performed.
§Example
use loro::{LoroDoc, CommitOptions};
let doc = LoroDoc::new();
doc.set_next_commit_options(CommitOptions::new().origin("ui").commit_msg("tagged"));
doc.get_text("t").insert(0, "x").unwrap();
doc.commit();Sourcepub fn clear_next_commit_options(&self)
pub fn clear_next_commit_options(&self)
Clear the options of the next commit.
Sourcepub fn is_detached(&self) -> bool
pub fn is_detached(&self) -> bool
Whether the document is in detached mode, where the loro_internal::DocState is not synchronized with the latest version of the loro_internal::OpLog.
Sourcepub fn import(&self, bytes: &[u8]) -> Result<ImportStatus, LoroError>
pub fn import(&self, bytes: &[u8]) -> Result<ImportStatus, LoroError>
Import data exported by LoroDoc::export.
Use ExportMode::Snapshot for full-state snapshots, or
ExportMode::all_updates / ExportMode::updates for updates.
§Example
use loro::{LoroDoc, ExportMode};
let a = LoroDoc::new();
a.get_text("text").insert(0, "Hello").unwrap();
let updates = a.export(ExportMode::all_updates()).unwrap();
let b = LoroDoc::new();
b.import(&updates).unwrap();
assert_eq!(a.get_deep_value(), b.get_deep_value());Pitfalls:
- Missing dependencies: check the returned
ImportStatus. Ifpendingis non-empty, fetch those missing ranges (e.g., usingexport(ExportMode::updates(&doc.oplog_vv()))) and re-import. - Auto-commit:
importfinalizes the current transaction before applying incoming data.
Sourcepub fn import_with(
&self,
bytes: &[u8],
origin: &str,
) -> Result<ImportStatus, LoroError>
pub fn import_with( &self, bytes: &[u8], origin: &str, ) -> Result<ImportStatus, LoroError>
Import data exported by LoroDoc::export and mark it with a custom origin.
The origin string will be attached to the ensuing change event, which is handy
for telemetry or filtering.
Pitfalls:
- Same as [
import]: verifyImportStatus.pendingand fetch dependencies if needed.
Sourcepub fn import_json_updates<T>(&self, json: T) -> Result<ImportStatus, LoroError>where
T: TryInto<JsonSchema>,
pub fn import_json_updates<T>(&self, json: T) -> Result<ImportStatus, LoroError>where
T: TryInto<JsonSchema>,
Import the json schema updates.
§Example
use loro::{LoroDoc, VersionVector};
let a = LoroDoc::new();
a.get_text("t").insert(0, "hi").unwrap();
a.commit();
let json = a.export_json_updates(&VersionVector::default(), &a.oplog_vv());
let b = LoroDoc::new();
b.import_json_updates(json).unwrap();
assert_eq!(a.get_deep_value(), b.get_deep_value());Sourcepub fn export_json_updates(
&self,
start_vv: &VersionVector,
end_vv: &VersionVector,
) -> JsonSchema
pub fn export_json_updates( &self, start_vv: &VersionVector, end_vv: &VersionVector, ) -> JsonSchema
Export the current state with json-string format of the document.
§Example
use loro::{LoroDoc, VersionVector};
let doc = LoroDoc::new();
let start = VersionVector::default();
let end = doc.oplog_vv();
let json = doc.export_json_updates(&start, &end);Sourcepub fn export_json_updates_without_peer_compression(
&self,
start_vv: &VersionVector,
end_vv: &VersionVector,
) -> JsonSchema
pub fn export_json_updates_without_peer_compression( &self, start_vv: &VersionVector, end_vv: &VersionVector, ) -> JsonSchema
Export the current state with json-string format of the document, without peer compression.
Compared to [export_json_updates], this method does not compress the peer IDs in the updates.
So the operations are easier to be processed by application code.
§Example
use loro::{LoroDoc, VersionVector};
let doc = LoroDoc::new();
let start = VersionVector::default();
let end = doc.oplog_vv();
let json = doc.export_json_updates_without_peer_compression(&start, &end);Sourcepub fn export_json_in_id_span(&self, id_span: IdSpan) -> Vec<JsonChange>
pub fn export_json_in_id_span(&self, id_span: IdSpan) -> Vec<JsonChange>
Exports changes within the specified ID span to JSON schema format.
The JSON schema format produced by this method is identical to the one generated by export_json_updates.
It ensures deterministic output, making it ideal for hash calculations and integrity checks.
This method can also export pending changes from the uncommitted transaction that have not yet been applied to the OpLog.
This method will NOT trigger a new commit implicitly.
§Example
use loro::{LoroDoc, IdSpan};
let doc = LoroDoc::new();
doc.set_peer_id(0).unwrap();
doc.get_text("text").insert(0, "a").unwrap();
doc.commit();
let doc_clone = doc.clone();
let _sub = doc.subscribe_pre_commit(Box::new(move |e| {
let changes = doc_clone.export_json_in_id_span(IdSpan::new(
0,
0,
e.change_meta.id.counter + e.change_meta.len as i32,
));
// 2 because commit one and the uncommit one
assert_eq!(changes.len(), 2);
true
}));
doc.get_text("text").insert(0, "b").unwrap();
let changes = doc.export_json_in_id_span(IdSpan::new(0, 0, 2));
assert_eq!(changes.len(), 1);
doc.commit();
// change merged
assert_eq!(changes.len(), 1);Sourcepub fn frontiers_to_vv(&self, frontiers: &Frontiers) -> Option<VersionVector>
pub fn frontiers_to_vv(&self, frontiers: &Frontiers) -> Option<VersionVector>
Convert Frontiers into VersionVector
Returns None if the frontiers are not included by this doc’s OpLog.
§Example
use loro::LoroDoc;
let doc = LoroDoc::new();
let f = doc.state_frontiers();
let vv = doc.frontiers_to_vv(&f);
assert!(vv.is_some());Sourcepub fn minimize_frontiers(&self, frontiers: &Frontiers) -> Result<Frontiers, ID>
pub fn minimize_frontiers(&self, frontiers: &Frontiers) -> Result<Frontiers, ID>
Minimize the frontiers by removing the unnecessary entries.
Returns Err(ID) if any frontier is not included by this doc’s history.
§Example
use loro::LoroDoc;
let doc = LoroDoc::new();
let f = doc.state_frontiers();
let _minimized = doc.minimize_frontiers(&f).unwrap();Sourcepub fn vv_to_frontiers(&self, vv: &VersionVector) -> Frontiers
pub fn vv_to_frontiers(&self, vv: &VersionVector) -> Frontiers
Convert VersionVector into Frontiers
Sourcepub fn with_oplog<R>(&self, f: impl FnOnce(&OpLog) -> R) -> R
pub fn with_oplog<R>(&self, f: impl FnOnce(&OpLog) -> R) -> R
Access the OpLog.
NOTE: The API in OpLog is unstable. Keep the closure short; avoid calling methods
that might re-enter the document while holding the lock.
Sourcepub fn with_state<R>(&self, f: impl FnOnce(&mut DocState) -> R) -> R
pub fn with_state<R>(&self, f: impl FnOnce(&mut DocState) -> R) -> R
Access the DocState.
NOTE: The API in DocState is unstable. Keep the closure short; avoid calling methods
that might re-enter the document while holding the lock.
Sourcepub fn oplog_vv(&self) -> VersionVector
pub fn oplog_vv(&self) -> VersionVector
Get the VersionVector version of OpLog
Sourcepub fn state_vv(&self) -> VersionVector
pub fn state_vv(&self) -> VersionVector
Get the VersionVector version of DocState
Sourcepub fn shallow_since_vv(&self) -> ImVersionVector
pub fn shallow_since_vv(&self) -> ImVersionVector
The doc only contains the history since this version
This is empty if the doc is not shallow.
The ops included by the shallow history start version vector are not in the doc.
Sourcepub fn shallow_since_frontiers(&self) -> Frontiers
pub fn shallow_since_frontiers(&self) -> Frontiers
The doc only contains the history since this version
This is empty if the doc is not shallow.
The ops included by the shallow history start frontiers are not in the doc.
Sourcepub fn len_changes(&self) -> usize
pub fn len_changes(&self) -> usize
Get the total number of changes in the OpLog
Sourcepub fn get_deep_value(&self) -> LoroValue
pub fn get_deep_value(&self) -> LoroValue
Get the entire state of the current DocState
Sourcepub fn get_deep_value_with_id(&self) -> LoroValue
pub fn get_deep_value_with_id(&self) -> LoroValue
Get the entire state of the current DocState with container id
Sourcepub fn oplog_frontiers(&self) -> Frontiers
pub fn oplog_frontiers(&self) -> Frontiers
Get the Frontiers version of OpLog.
Sourcepub fn state_frontiers(&self) -> Frontiers
pub fn state_frontiers(&self) -> Frontiers
Get the Frontiers version of DocState.
When detached or during checkout, state_frontiers() may differ from oplog_frontiers().
Learn more about Frontiers.
§Example
use loro::LoroDoc;
let doc = LoroDoc::new();
let before = doc.state_frontiers();
doc.get_text("t").insert(0, "x").unwrap();
let after = doc.state_frontiers();
assert_ne!(before, after);Sourcepub fn set_peer_id(&self, peer: u64) -> Result<(), LoroError>
pub fn set_peer_id(&self, peer: u64) -> Result<(), LoroError>
Change the PeerID
Pitfalls:
- Never reuse the same PeerID across concurrent writers (multiple tabs/devices). Duplicate PeerIDs can produce conflicting OpIDs and corrupt the document.
- Do not assign a fixed PeerID to a user or device without strict single-ownership locking. Prefer the default random PeerID per process/session.
Sourcepub fn subscribe(
&self,
container_id: &ContainerID,
callback: Arc<dyn for<'a> Fn(DiffEvent<'a>) + Sync + Send>,
) -> Subscription
pub fn subscribe( &self, container_id: &ContainerID, callback: Arc<dyn for<'a> Fn(DiffEvent<'a>) + Sync + Send>, ) -> Subscription
Subscribe the events of a container.
The callback will be invoked after a transaction that change the container. Returns a subscription that can be used to unsubscribe.
The events will be emitted after a transaction is committed. A transaction is committed when:
doc.commit()is called.doc.export(mode)is called.doc.import(data)is called.doc.checkout(version)is called.
§Example
let doc = LoroDoc::new();
let text = doc.get_text("text");
let ran = Arc::new(AtomicBool::new(false));
let ran2 = ran.clone();
let sub = doc.subscribe(
&text.id(),
Arc::new(move |event| {
assert!(event.triggered_by.is_local());
for event in event.events {
let delta = event.diff.as_text().unwrap();
let d = TextDelta::Insert {
insert: "123".into(),
attributes: Default::default(),
};
assert_eq!(delta, &vec![d]);
ran2.store(true, std::sync::atomic::Ordering::Relaxed);
}
}),
);
text.insert(0, "123").unwrap();
doc.commit();
assert!(ran.load(std::sync::atomic::Ordering::Relaxed));
// unsubscribe
sub.unsubscribe();Sourcepub fn subscribe_root(
&self,
callback: Arc<dyn for<'a> Fn(DiffEvent<'a>) + Sync + Send>,
) -> Subscription
pub fn subscribe_root( &self, callback: Arc<dyn for<'a> Fn(DiffEvent<'a>) + Sync + Send>, ) -> Subscription
Subscribe all the events.
The callback will be invoked when any part of the loro_internal::DocState is changed. Returns a subscription that can be used to unsubscribe.
The events will be emitted after a transaction is committed. A transaction is committed when:
doc.commit()is called.doc.export(mode)is called.doc.import(data)is called.doc.checkout(version)is called.
Sourcepub fn subscribe_local_update(
&self,
callback: Box<dyn Fn(&Vec<u8>) -> bool + Sync + Send>,
) -> Subscription
pub fn subscribe_local_update( &self, callback: Box<dyn Fn(&Vec<u8>) -> bool + Sync + Send>, ) -> Subscription
Subscribe to local document updates.
The callback receives encoded update bytes whenever local changes are committed. This is useful for syncing changes to other document instances or persisting updates.
Auto-unsubscription: If the callback returns false, the subscription will be
automatically removed, providing a convenient way to implement one-time or conditional
subscriptions in Rust.
§Parameters
callback: Function that receives&Vec<u8>(encoded updates) and returnsbool- Return
trueto keep the subscription active - Return
falseto automatically unsubscribe
- Return
§Example
use loro::LoroDoc;
use std::sync::{Arc, Mutex};
let doc = LoroDoc::new();
let updates = Arc::new(Mutex::new(Vec::new()));
let updates_clone = updates.clone();
let count = Arc::new(Mutex::new(0));
let count_clone = count.clone();
// Subscribe and collect first 3 updates, then auto-unsubscribe
let sub = doc.subscribe_local_update(Box::new(move |bytes| {
updates_clone.lock().unwrap().push(bytes.clone());
let mut c = count_clone.lock().unwrap();
*c += 1;
*c < 3 // Auto-unsubscribe after 3 updates
}));
doc.get_text("text").insert(0, "hello").unwrap();
doc.commit();Sourcepub fn subscribe_peer_id_change(
&self,
callback: Box<dyn Fn(&ID) -> bool + Sync + Send>,
) -> Subscription
pub fn subscribe_peer_id_change( &self, callback: Box<dyn Fn(&ID) -> bool + Sync + Send>, ) -> Subscription
Subscribe to peer ID changes in the document.
The callback is triggered whenever the document’s peer ID is modified. This is useful for tracking identity changes and updating related state accordingly.
Auto-unsubscription: If the callback returns false, the subscription will be
automatically removed, providing a convenient way to implement one-time or conditional
subscriptions in Rust.
§Parameters
callback: Function that receives&ID(the new peer ID) and returnsbool- Return
trueto keep the subscription active - Return
falseto automatically unsubscribe
- Return
§Example
use loro::LoroDoc;
use std::sync::{Arc, Mutex};
let doc = LoroDoc::new();
let peer_changes = Arc::new(Mutex::new(Vec::new()));
let changes_clone = peer_changes.clone();
let sub = doc.subscribe_peer_id_change(Box::new(move |new_peer_id| {
changes_clone.lock().unwrap().push(*new_peer_id);
true // Keep subscription active
}));
doc.set_peer_id(42).unwrap();
doc.set_peer_id(100).unwrap();Sourcepub fn check_state_correctness_slow(&self)
pub fn check_state_correctness_slow(&self)
Check the correctness of the document state by comparing it with the state calculated by applying all the history.
Sourcepub fn get_by_path(&self, path: &[Index]) -> Option<ValueOrContainer>
pub fn get_by_path(&self, path: &[Index]) -> Option<ValueOrContainer>
Get the handler by the path.
Sourcepub fn get_by_str_path(&self, path: &str) -> Option<ValueOrContainer>
pub fn get_by_str_path(&self, path: &str) -> Option<ValueOrContainer>
Get the handler by the string path.
The path can be specified in different ways depending on the container type:
For Tree:
- Using node IDs:
tree/{node_id}/property - Using indices:
tree/0/1/property
For List and MovableList:
- Using indices:
list/0orlist/1/property
For Map:
- Using keys:
map/keyormap/nested/property
For tree structures, index-based paths follow depth-first traversal order. The indices start from 0 and represent the position of a node among its siblings.
§Examples
let doc = LoroDoc::new();
// Tree example
let tree = doc.get_tree("tree");
let root = tree.create(None).unwrap();
tree.get_meta(root).unwrap().insert("name", "root").unwrap();
// Access tree by ID or index
let name1 = doc.get_by_str_path(&format!("tree/{}/name", root)).unwrap().into_value().unwrap();
let name2 = doc.get_by_str_path("tree/0/name").unwrap().into_value().unwrap();
assert_eq!(name1, name2);
// List example
let list = doc.get_list("list");
list.insert(0, "first").unwrap();
list.insert(1, "second").unwrap();
// Access list by index
let item = doc.get_by_str_path("list/0");
assert_eq!(item.unwrap().into_value().unwrap().into_string().unwrap(), "first".into());
// Map example
let map = doc.get_map("map");
map.insert("key", "value").unwrap();
// Access map by key
let value = doc.get_by_str_path("map/key");
assert_eq!(value.unwrap().into_value().unwrap().into_string().unwrap(), "value".into());
// MovableList example
let mlist = doc.get_movable_list("mlist");
mlist.insert(0, "item").unwrap();
// Access movable list by index
let item = doc.get_by_str_path("mlist/0");
assert_eq!(item.unwrap().into_value().unwrap().into_string().unwrap(), "item".into());Sourcepub fn get_cursor_pos(
&self,
cursor: &Cursor,
) -> Result<PosQueryResult, CannotFindRelativePosition>
pub fn get_cursor_pos( &self, cursor: &Cursor, ) -> Result<PosQueryResult, CannotFindRelativePosition>
Get the absolute position of the given cursor.
§Example
let doc = LoroDoc::new();
let text = &doc.get_text("text");
text.insert(0, "01234").unwrap();
let pos = text.get_cursor(5, Default::default()).unwrap();
assert_eq!(doc.get_cursor_pos(&pos).unwrap().current.pos, 5);
text.insert(0, "01234").unwrap();
assert_eq!(doc.get_cursor_pos(&pos).unwrap().current.pos, 10);
text.delete(0, 10).unwrap();
assert_eq!(doc.get_cursor_pos(&pos).unwrap().current.pos, 0);
text.insert(0, "01234").unwrap();
assert_eq!(doc.get_cursor_pos(&pos).unwrap().current.pos, 5);Sourcepub fn has_history_cache(&self) -> bool
pub fn has_history_cache(&self) -> bool
Whether the history cache is built.
Sourcepub fn free_history_cache(&self)
pub fn free_history_cache(&self)
Free the history cache that is used for making checkout faster.
If you use checkout that switching to an old/concurrent version, the history cache will be built. You can free it by calling this method.
Sourcepub fn free_diff_calculator(&self)
pub fn free_diff_calculator(&self)
Free the cached diff calculator that is used for checkout.
Sourcepub fn compact_change_store(&self)
pub fn compact_change_store(&self)
Encoded all ops and history cache to bytes and store them in the kv store.
This will free up the memory that used by parsed ops
Sourcepub fn export(&self, mode: ExportMode<'_>) -> Result<Vec<u8>, LoroEncodeError>
pub fn export(&self, mode: ExportMode<'_>) -> Result<Vec<u8>, LoroEncodeError>
Export the document in the given mode.
Common modes:
ExportMode::Snapshot: full state + historyExportMode::all_updates(): all known ops- [
ExportMode::updates(&VersionVector)]: ops since a specific version - [
ExportMode::shallow_snapshot(..)]: GC’d snapshot starting at frontiers - [
ExportMode::updates_in_range(..)]: ops in specific ID spans
Important notes:
- Auto-commit:
exportfinalizes the current transaction before producing bytes. - Shallow snapshots: peers cannot import updates from before the shallow start.
- Performance: exporting fresh snapshots periodically can reduce import time for new peers.
§Examples
use loro::{ExportMode, LoroDoc};
let doc = LoroDoc::new();
doc.get_text("text").insert(0, "Hello").unwrap();
// 1) Full snapshot
let snapshot = doc.export(ExportMode::Snapshot).unwrap();
// 2) All updates
let all = doc.export(ExportMode::all_updates()).unwrap();
// 3) Updates from another peer’s version vector
let vv = doc.oplog_vv();
let delta = doc.export(ExportMode::updates(&vv)).unwrap();
assert!(!delta.is_empty());Sourcepub fn analyze(&self) -> DocAnalysis
pub fn analyze(&self) -> DocAnalysis
Analyze the container info of the doc
This is used for development and debugging. It can be slow.
Sourcepub fn get_path_to_container(
&self,
id: &ContainerID,
) -> Option<Vec<(ContainerID, Index)>>
pub fn get_path_to_container( &self, id: &ContainerID, ) -> Option<Vec<(ContainerID, Index)>>
Get the path from the root to the container
Sourcepub fn jsonpath(
&self,
path: &str,
) -> Result<Vec<ValueOrContainer>, JsonPathError>
pub fn jsonpath( &self, path: &str, ) -> Result<Vec<ValueOrContainer>, JsonPathError>
Evaluate a JSONPath expression on the document and return matching values or handlers.
This method allows querying the document structure using JSONPath syntax.
It returns a vector of ValueOrHandler which can represent either primitive values
or container handlers, depending on what the JSONPath expression matches.
§Arguments
path- A string slice containing the JSONPath expression to evaluate.
§Returns
A Result containing either:
Ok(Vec<ValueOrHandler>): A vector of matching values or handlers.Err(String): An error message if the JSONPath expression is invalid or evaluation fails.
§Example
let doc = LoroDoc::new();
let map = doc.get_map("users");
map.insert("alice", 30).unwrap();
map.insert("bob", 25).unwrap();
let result = doc.jsonpath("$.users.alice").unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].as_value().unwrap().to_json_value(), serde_json::json!(30));Sourcepub fn subscribe_jsonpath(
&self,
jsonpath: &str,
callback: Arc<dyn Fn() + Sync + Send>,
) -> Result<Subscription, LoroError>
pub fn subscribe_jsonpath( &self, jsonpath: &str, callback: Arc<dyn Fn() + Sync + Send>, ) -> Result<Subscription, LoroError>
Subscribe to updates that might affect the given JSONPath query.
The callback:
- may fire false positives (never false negatives) to stay lightweight;
- does not include the query result so the caller can debounce/throttle and run JSONPath themselves if desired;
- can be debounced/throttled before executing an expensive JSONPath read.
Sourcepub fn get_pending_txn_len(&self) -> usize
pub fn get_pending_txn_len(&self) -> usize
Get the number of operations in the pending transaction.
The pending transaction is the one that is not committed yet. It will be committed
after calling doc.commit(), doc.export(mode) or doc.checkout(version).
Sourcepub fn travel_change_ancestors(
&self,
ids: &[ID],
f: &mut dyn FnMut(ChangeMeta) -> ControlFlow<()>,
) -> Result<(), ChangeTravelError>
pub fn travel_change_ancestors( &self, ids: &[ID], f: &mut dyn FnMut(ChangeMeta) -> ControlFlow<()>, ) -> Result<(), ChangeTravelError>
Traverses the ancestors of the Change containing the given ID, including itself.
This method visits all ancestors in causal order, from the latest to the oldest, based on their Lamport timestamps.
§Arguments
ids- The IDs of the Change to start the traversal from.f- A mutable function that is called for each ancestor. It can returnControlFlow::Break(())to stop the traversal.
Sourcepub fn is_shallow(&self) -> bool
pub fn is_shallow(&self) -> bool
Check if the doc contains the full history.
Sourcepub fn get_changed_containers_in(
&self,
id: ID,
len: usize,
) -> HashSet<ContainerID, FxBuildHasher>
pub fn get_changed_containers_in( &self, id: ID, len: usize, ) -> HashSet<ContainerID, FxBuildHasher>
Gets container IDs modified in the given ID range.
Pitfalls:
- This method will implicitly commit the current transaction to ensure the change range is finalized.
This method can be used in conjunction with doc.travel_change_ancestors() to traverse
the history and identify all changes that affected specific containers.
§Arguments
id- The starting ID of the change rangelen- The length of the change range to check
Sourcepub fn find_id_spans_between(
&self,
from: &Frontiers,
to: &Frontiers,
) -> VersionVectorDiff
pub fn find_id_spans_between( &self, from: &Frontiers, to: &Frontiers, ) -> VersionVectorDiff
Find the operation id spans that between the from version and the to version.
Useful for exporting just the changes in a range, e.g., in response to a subscription.
§Example
use loro::LoroDoc;
let doc = LoroDoc::new();
let a = doc.state_frontiers();
doc.get_text("t").insert(0, "x").unwrap();
doc.commit();
let b = doc.state_frontiers();
let spans = doc.find_id_spans_between(&a, &b);
assert!(!spans.forward.is_empty());Sourcepub fn revert_to(&self, version: &Frontiers) -> Result<(), LoroError>
pub fn revert_to(&self, version: &Frontiers) -> Result<(), LoroError>
Revert the current document state back to the target version
Internally, it will generate a series of local operations that can revert the current doc to the target version. It will calculate the diff between the current state and the target state, and apply the diff to the current state.
Pitfalls:
- The target frontiers must be included by the document’s history. If the document is shallow and the target is before the shallow start, revert will fail.
§Example
use loro::LoroDoc;
let doc = LoroDoc::new();
let t = doc.get_text("text");
t.insert(0, "Hello").unwrap();
let v0 = doc.state_frontiers();
t.insert(5, ", world").unwrap();
doc.commit();
doc.revert_to(&v0).unwrap();
assert_eq!(t.to_string(), "Hello");Sourcepub fn apply_diff(&self, diff: DiffBatch) -> Result<(), LoroError>
pub fn apply_diff(&self, diff: DiffBatch) -> Result<(), LoroError>
Apply a diff to the current document state.
Internally, it will apply the diff to the current state.
Sourcepub fn diff(&self, a: &Frontiers, b: &Frontiers) -> Result<DiffBatch, LoroError>
pub fn diff(&self, a: &Frontiers, b: &Frontiers) -> Result<DiffBatch, LoroError>
Calculate the diff between two versions.
§Example
use loro::{LoroDoc};
let doc = LoroDoc::new();
let t = doc.get_text("text");
let a = doc.state_frontiers();
t.insert(0, "a").unwrap();
let b = doc.state_frontiers();
let diff = doc.diff(&a, &b).unwrap();
assert!(diff.iter().next().is_some());Sourcepub fn has_container(&self, container_id: &ContainerID) -> bool
pub fn has_container(&self, container_id: &ContainerID) -> bool
Check if the doc contains the target container.
A root container always exists, while a normal container exists if it has ever been created on the doc.
§Examples
use loro::{LoroDoc, LoroText, LoroList, ExportMode};
let doc = LoroDoc::new();
doc.set_peer_id(1);
let map = doc.get_map("map");
map.insert_container("text", LoroText::new()).unwrap();
map.insert_container("list", LoroList::new()).unwrap();
// Root map container exists
assert!(doc.has_container(&"cid:root-map:Map".try_into().unwrap()));
// Text container exists
assert!(doc.has_container(&"cid:0@1:Text".try_into().unwrap()));
// List container exists
assert!(doc.has_container(&"cid:1@1:List".try_into().unwrap()));
let doc2 = LoroDoc::new();
// Containers exist as long as the history or doc state includes them
doc.detach();
doc2.import(&doc.export(ExportMode::all_updates()).unwrap()).unwrap();
assert!(doc2.has_container(&"cid:root-map:Map".try_into().unwrap()));
assert!(doc2.has_container(&"cid:0@1:Text".try_into().unwrap()));
assert!(doc2.has_container(&"cid:1@1:List".try_into().unwrap()));Sourcepub fn subscribe_first_commit_from_peer(
&self,
callback: Box<dyn Fn(&FirstCommitFromPeerPayload) -> bool + Sync + Send>,
) -> Subscription
pub fn subscribe_first_commit_from_peer( &self, callback: Box<dyn Fn(&FirstCommitFromPeerPayload) -> bool + Sync + Send>, ) -> Subscription
Subscribe to the first commit from a peer. Operations performed on the LoroDoc within this callback
will be merged into the current commit.
Subscribe to the first commit event from each peer.
The callback is triggered only once per peer when they make their first commit to the document locally. This is particularly useful for managing peer-to-user mappings or initialization logic.
Auto-unsubscription: If the callback returns false, the subscription will be
automatically removed, providing a convenient way to implement one-time or conditional
subscriptions in Rust.
§Parameters
callback: Function that receives&FirstCommitFromPeerPayloadand returnsbool- Return
trueto keep the subscription active - Return
falseto automatically unsubscribe
- Return
§Use Cases
- Initialize peer-specific data structures
- Map peer IDs to user information
§Example
use loro::LoroDoc;
use std::sync::{Arc, Mutex};
let doc = LoroDoc::new();
doc.set_peer_id(0).unwrap();
let new_peers = Arc::new(Mutex::new(Vec::new()));
let peers_clone = new_peers.clone();
let peer_count = Arc::new(Mutex::new(0));
let count_clone = peer_count.clone();
// Track first 5 new peers, then auto-unsubscribe
let sub = doc.subscribe_first_commit_from_peer(Box::new(move |payload| {
peers_clone.lock().unwrap().push(payload.peer);
let mut count = count_clone.lock().unwrap();
*count += 1;
*count < 5 // Auto-unsubscribe after tracking 5 peers
}));
// This will trigger the callback for peer 0
doc.get_text("text").insert(0, "hello").unwrap();
doc.commit();
// Switch to a new peer and commit - triggers callback again
doc.set_peer_id(1).unwrap();
doc.get_text("text").insert(0, "world").unwrap();
doc.commit();Sourcepub fn subscribe_pre_commit(
&self,
callback: Box<dyn Fn(&PreCommitCallbackPayload) -> bool + Sync + Send>,
) -> Subscription
pub fn subscribe_pre_commit( &self, callback: Box<dyn Fn(&PreCommitCallbackPayload) -> bool + Sync + Send>, ) -> Subscription
Subscribe to pre-commit events.
The callback is triggered when changes are about to be committed but before they’re applied to the OpLog. This allows you to modify commit metadata such as timestamps and messages, or perform validation before changes are finalized.
Auto-unsubscription: If the callback returns false, the subscription will be
automatically removed, providing a convenient way to implement one-time or conditional
subscriptions in Rust.
Pitfall: commit() can be triggered implicitly by import, export, and checkout.
This hook still runs for those commits, which is helpful for annotating metadata
even for implicit commits.
§Parameters
callback: Function that receives&PreCommitCallbackPayloadand returnsbool- Return
trueto keep the subscription active - Return
falseto automatically unsubscribe
- Return
- The payload contains:
change_meta: Metadata about the commitmodifier: Interface to modify commit properties
§Use Cases
- Add commit message prefixes or formatting
- Adjust timestamps for consistent ordering
- Log or audit commit operations
- Implement commit validation or approval workflows
§Example
use loro::LoroDoc;
use std::sync::{Arc, Mutex};
let doc = LoroDoc::new();
let commit_count = Arc::new(Mutex::new(0));
let count_clone = commit_count.clone();
// Add timestamps and auto-unsubscribe after 5 commits
let sub = doc.subscribe_pre_commit(Box::new(move |payload| {
// Add a prefix to commit messages
let new_message = format!("Auto: {}", payload.change_meta.message());
payload.modifier.set_message(&new_message);
let mut count = count_clone.lock().unwrap();
*count += 1;
*count < 5 // Auto-unsubscribe after 5 commits
}));
doc.get_text("text").insert(0, "hello").unwrap();
doc.commit();Sourcepub fn delete_root_container(&self, cid: ContainerID)
pub fn delete_root_container(&self, cid: ContainerID)
Delete all content from a root container and hide it from the document.
When a root container is empty and hidden:
- It won’t show up in
get_deep_value()results - It won’t be included in document snapshots
Only works on root containers (containers without parents).
Sourcepub fn set_hide_empty_root_containers(&self, hide: bool)
pub fn set_hide_empty_root_containers(&self, hide: bool)
Set whether to hide empty root containers.
§Example
use loro::LoroDoc;
let doc = LoroDoc::new();
let map = doc.get_map("map");
dbg!(doc.get_deep_value()); // {"map": {}}
doc.set_hide_empty_root_containers(true);
dbg!(doc.get_deep_value()); // {}Trait Implementations§
Auto Trait Implementations§
impl Freeze for LoroDoc
impl RefUnwindSafe for LoroDoc
impl Send for LoroDoc
impl Sync for LoroDoc
impl Unpin for LoroDoc
impl UnwindSafe for LoroDoc
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T, UT> HandleAlloc<UT> for T
impl<T, UT> HandleAlloc<UT> for T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> Paint for Twhere
T: ?Sized,
impl<T> Paint for Twhere
T: ?Sized,
Source§fn fg(&self, value: Color) -> Painted<&T>
fn fg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the foreground set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like red() and
green(), which have the same functionality but are
pithier.
§Example
Set foreground color to white using fg():
use yansi::{Paint, Color};
painted.fg(Color::White);Set foreground color to white using white().
use yansi::Paint;
painted.white();Source§fn bright_black(&self) -> Painted<&T>
fn bright_black(&self) -> Painted<&T>
Source§fn bright_red(&self) -> Painted<&T>
fn bright_red(&self) -> Painted<&T>
Source§fn bright_green(&self) -> Painted<&T>
fn bright_green(&self) -> Painted<&T>
Source§fn bright_yellow(&self) -> Painted<&T>
fn bright_yellow(&self) -> Painted<&T>
Source§fn bright_blue(&self) -> Painted<&T>
fn bright_blue(&self) -> Painted<&T>
Source§fn bright_magenta(&self) -> Painted<&T>
fn bright_magenta(&self) -> Painted<&T>
Source§fn bright_cyan(&self) -> Painted<&T>
fn bright_cyan(&self) -> Painted<&T>
Source§fn bright_white(&self) -> Painted<&T>
fn bright_white(&self) -> Painted<&T>
Source§fn bg(&self, value: Color) -> Painted<&T>
fn bg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the background set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like on_red() and
on_green(), which have the same functionality but
are pithier.
§Example
Set background color to red using fg():
use yansi::{Paint, Color};
painted.bg(Color::Red);Set background color to red using on_red().
use yansi::Paint;
painted.on_red();Source§fn on_primary(&self) -> Painted<&T>
fn on_primary(&self) -> Painted<&T>
Source§fn on_magenta(&self) -> Painted<&T>
fn on_magenta(&self) -> Painted<&T>
Source§fn on_bright_black(&self) -> Painted<&T>
fn on_bright_black(&self) -> Painted<&T>
Source§fn on_bright_red(&self) -> Painted<&T>
fn on_bright_red(&self) -> Painted<&T>
Source§fn on_bright_green(&self) -> Painted<&T>
fn on_bright_green(&self) -> Painted<&T>
Source§fn on_bright_yellow(&self) -> Painted<&T>
fn on_bright_yellow(&self) -> Painted<&T>
Source§fn on_bright_blue(&self) -> Painted<&T>
fn on_bright_blue(&self) -> Painted<&T>
Source§fn on_bright_magenta(&self) -> Painted<&T>
fn on_bright_magenta(&self) -> Painted<&T>
Source§fn on_bright_cyan(&self) -> Painted<&T>
fn on_bright_cyan(&self) -> Painted<&T>
Source§fn on_bright_white(&self) -> Painted<&T>
fn on_bright_white(&self) -> Painted<&T>
Source§fn attr(&self, value: Attribute) -> Painted<&T>
fn attr(&self, value: Attribute) -> Painted<&T>
Enables the styling Attribute value.
This method should be used rarely. Instead, prefer to use
attribute-specific builder methods like bold() and
underline(), which have the same functionality
but are pithier.
§Example
Make text bold using attr():
use yansi::{Paint, Attribute};
painted.attr(Attribute::Bold);Make text bold using using bold().
use yansi::Paint;
painted.bold();Source§fn rapid_blink(&self) -> Painted<&T>
fn rapid_blink(&self) -> Painted<&T>
Source§fn quirk(&self, value: Quirk) -> Painted<&T>
fn quirk(&self, value: Quirk) -> Painted<&T>
Enables the yansi Quirk value.
This method should be used rarely. Instead, prefer to use quirk-specific
builder methods like mask() and
wrap(), which have the same functionality but are
pithier.
§Example
Enable wrapping using .quirk():
use yansi::{Paint, Quirk};
painted.quirk(Quirk::Wrap);Enable wrapping using wrap().
use yansi::Paint;
painted.wrap();Source§fn clear(&self) -> Painted<&T>
👎Deprecated since 1.0.1: renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
fn clear(&self) -> Painted<&T>
resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.Source§fn whenever(&self, value: Condition) -> Painted<&T>
fn whenever(&self, value: Condition) -> Painted<&T>
Conditionally enable styling based on whether the Condition value
applies. Replaces any previous condition.
See the crate level docs for more details.
§Example
Enable styling painted only when both stdout and stderr are TTYs:
use yansi::{Paint, Condition};
painted.red().on_yellow().whenever(Condition::STDOUTERR_ARE_TTY);