pub struct Text { /* private fields */ }Expand description
Rich-text CRDT — a List<char> augmented with Peritext-style
format spans.
Text shares its underlying List’s Lamport clock for both character
ops and format ops, so every op gets a unique monotonic OpId.
Implementations§
Source§impl Text
impl Text
Sourcepub fn new_random() -> Self
pub fn new_random() -> Self
Create a new instance with a random ReplicaId from OS entropy.
See crate::new_replica_id.
Sourcepub fn replica_id(&self) -> ReplicaId
pub fn replica_id(&self) -> ReplicaId
This replica’s id.
Sourcepub fn len(&self) -> usize
pub fn len(&self) -> usize
Length of visible text in Unicode scalar values (Rust chars) —
not bytes and not grapheme clusters. Multi-char graphemes
like 👨👩👧 (5 chars) count as 5.
For grapheme-aware length, see Self::grapheme_count.
Sourcepub fn grapheme_count(&self) -> usize
pub fn grapheme_count(&self) -> usize
Length in extended grapheme clusters (UAX #29). This is the “user-perceived character” count — emoji like 👨👩👧 count as 1. Use this in user-facing position math (cursor coordinates, selection ranges).
Cost: O(N) — walks the visible string once.
Sourcepub fn grapheme_to_char_pos(&self, grapheme_pos: usize) -> usize
pub fn grapheme_to_char_pos(&self, grapheme_pos: usize) -> usize
Convert a grapheme position to the underlying char (scalar) position.
Returns self.len() if grapheme_pos >= self.grapheme_count().
Cost: O(N).
Sourcepub fn char_to_grapheme_pos(&self, char_pos: usize) -> usize
pub fn char_to_grapheme_pos(&self, char_pos: usize) -> usize
Convert a char position to the grapheme position whose first char
is at or before the given char position. Returns 0 for empty
docs and self.grapheme_count() for positions at end.
Cost: O(N).
Sourcepub fn insert_grapheme_str(&mut self, g_pos: usize, s: &str) -> Vec<TextOp>
pub fn insert_grapheme_str(&mut self, g_pos: usize, s: &str) -> Vec<TextOp>
Insert s at grapheme position g_pos. Multi-char graphemes
in s are inserted as a single contiguous run, so they cannot
be split by concurrent edits at the same boundary (the standard
non-interleaving guarantee from Fugue-Maximal).
Returns one TextOp per char inserted.
Sourcepub fn delete_grapheme(&mut self, g_pos: usize) -> Vec<TextOp>
pub fn delete_grapheme(&mut self, g_pos: usize) -> Vec<TextOp>
Delete the grapheme at grapheme position g_pos (atomically, all
of its chars). No-op if g_pos is out of bounds.
Sourcepub fn as_string(&self) -> String
pub fn as_string(&self) -> String
Render the text as a String. Format spans are not included; use
Self::iter_with_marks to access them.
Text also implements std::fmt::Display, so format!("{text}")
works.
Sourcepub fn insert(&mut self, pos: usize, ch: char) -> TextOp
pub fn insert(&mut self, pos: usize, ch: char) -> TextOp
Insert a single character at visible position pos.
Sourcepub fn insert_str(&mut self, pos: usize, s: &str) -> Vec<TextOp>
pub fn insert_str(&mut self, pos: usize, s: &str) -> Vec<TextOp>
Insert a string at visible position pos. Returns one op per char.
Sourcepub fn delete_range(&mut self, range: Range<usize>) -> Vec<TextOp>
pub fn delete_range(&mut self, range: Range<usize>) -> Vec<TextOp>
Delete a contiguous range of characters.
Sourcepub fn set_mark(&mut self, range: Range<usize>, name: &str, on: bool) -> TextOp
pub fn set_mark(&mut self, range: Range<usize>, name: &str, on: bool) -> TextOp
Set a boolean mark over a visible-position range. Returns the format op.
on = true adds the mark; on = false removes it (cancels a previous
add). When on = true and on = false ops conflict on the same
range, the one with the higher OpId wins.
Default anchors are no-expand on either side: chars typed at the
span boundaries do not inherit the mark. To get “expand right”
(typing extends bold), use Self::set_mark_with_anchors with an
explicit anchor referencing the next char.
§Panics
Panics if range.start > range.end or range.end > self.len().
Sourcepub fn set_value_mark(
&mut self,
range: Range<usize>,
name: &str,
value: Option<&str>,
) -> TextOp
pub fn set_value_mark( &mut self, range: Range<usize>, name: &str, value: Option<&str>, ) -> TextOp
Set a valued mark over a visible-position range. Pass Some(s) to
set the value, None to unset (cancel a previous set).
§Panics
Panics if range.start > range.end or range.end > self.len().
Sourcepub fn set_mark_with_rule(
&mut self,
range: Range<usize>,
name: &str,
value: SpanValue,
rule: ExpandRule,
) -> TextOp
pub fn set_mark_with_rule( &mut self, range: Range<usize>, name: &str, value: SpanValue, rule: ExpandRule, ) -> TextOp
Most general format-op API. Set a span with explicit value and stickiness rule.
§Panics
Panics if range.start > range.end or range.end > self.len().
Sourcepub fn set_mark_with_anchors(
&mut self,
start: Anchor,
end: Anchor,
name: &str,
value: SpanValue,
) -> TextOp
pub fn set_mark_with_anchors( &mut self, start: Anchor, end: Anchor, name: &str, value: SpanValue, ) -> TextOp
Set a mark with explicit anchors. Use this when you want fully-custom
stickiness or anchors that aren’t expressible via ExpandRule.
Sourcepub fn apply_inverse(&mut self, op: &TextOp) -> Option<TextOp>
pub fn apply_inverse(&mut self, op: &TextOp) -> Option<TextOp>
Apply the inverse of op as a NEW local op. Use this with a
caller-managed undo/redo stack:
let mut undo: Vec<TextOp> = Vec::new();
let mut redo: Vec<TextOp> = Vec::new();
undo.push(text.insert(0, 'H')); // type
// ... user clicks "undo" ...
if let Some(op) = undo.pop() {
if let Some(inv) = text.apply_inverse(&op) {
redo.push(inv);
}
}Char(Insert)→ tombstones the inserted character.Char(Delete)→ re-inserts the original character with a freshOpId.Mark(Set/On)→ emits aMark(Off)over the same anchored range.Mark(Set(s))→ emits aMark(Unset).Mark(Off/Unset)→ no-op (we don’t reconstruct the previous value).
Returns None if the op cannot be inverted (target not in the doc).
Sourcepub fn ops_since<'a>(
&'a self,
since: &'a VersionVector,
) -> impl Iterator<Item = &'a TextOp> + 'a
pub fn ops_since<'a>( &'a self, since: &'a VersionVector, ) -> impl Iterator<Item = &'a TextOp> + 'a
Iterate over ops not yet seen by since.
Sourcepub fn version(&self) -> &VersionVector
pub fn version(&self) -> &VersionVector
This replica’s current version vector.
Sourcepub fn iter_with_marks(&self) -> Box<dyn Iterator<Item = (char, MarkSet)> + '_>
pub fn iter_with_marks(&self) -> Box<dyn Iterator<Item = (char, MarkSet)> + '_>
Iterate over visible characters paired with their active mark set.
The mark set at each position is computed from the union of all spans
containing that position, with later (higher-OpId) spans overriding
earlier ones for the same name.
Cost: O(num_chars × num_spans). For typical documents (a few dozen
spans) this is fast; for documents with thousands of spans, consider
a more sophisticated index.
Source§impl Text
impl Text
Sourcepub fn to_delta(&self) -> Vec<DeltaOp>
pub fn to_delta(&self) -> Vec<DeltaOp>
Export the document as a Quill / Yjs Delta: a sequence of
{insert: "...", attributes: {...}} runs where each run is a
maximal contiguous span of characters with identical attributes.
This is the format produced by Quill, Y.Text.toDelta(),
Slate’s Text adapter, etc. It’s a snapshot — round-tripping
through Delta loses the underlying CRDT op log, so a from_delta →
to_delta round-trip preserves visible content but not history.
Sourcepub fn from_delta(replica: ReplicaId, deltas: &[DeltaOp]) -> Self
pub fn from_delta(replica: ReplicaId, deltas: &[DeltaOp]) -> Self
Build a Text from a Quill / Yjs Delta. Inverse of Self::to_delta.
All marks are added with ExpandRule::None stickiness. Attribute
values that don’t match bool or string are skipped.