lattice_text/text

A plain-text CRDT backed by lattice_sequence.

Text is stored as a sequence of single-grapheme items: every inserted string is split into graphemes and each grapheme becomes one sequence item, so indices, anchors, and length are all grapheme-based. Insert, delete, merge, and delta operations delegate to lattice_sequence. Use lattice_sequence/sequence directly when you need a generic list CRDT.

Example

import lattice_core/replica_id
import lattice_text/text

let doc = text.new(replica_id.new("node-a"))
text.value(doc)  // -> ""

Types

An error returned when a grapheme range does not satisfy 0 <= start <= end <= length.

pub type RangeError {
  RangeOutOfBounds(start: Int, end: Int, length: Int)
}

Constructors

  • RangeOutOfBounds(start: Int, end: Int, length: Int)
pub opaque type Text

Values

pub fn anchor_at(
  text: Text,
  index: Int,
  bias: sequence.Bias,
) -> sequence.Anchor

Create an anchor at the gap before the grapheme at index.

Anchors are stable positions that survive concurrent edits and merges: resolve one back to a current grapheme index with resolve_anchor. Before bias glues the anchor to the grapheme at index, so inserts at the gap push it right; After bias glues it to the grapheme at index - 1, so inserts at the gap land after it.

Examples

let doc = text.new(replica_id.new("A")) |> text.insert(0, "hello")
let cursor = text.anchor_at(doc, 5, sequence.After)
let doc = text.insert(doc, 0, "say ")
text.resolve_anchor(doc, cursor)  // -> 9
pub fn anchor_from_json(
  json_string: String,
) -> Result(sequence.Anchor, json.DecodeError)

Decode an anchor from a JSON string produced by anchor_to_json.

pub fn anchor_to_json(anchor: sequence.Anchor) -> json.Json

Encode an anchor as a self-describing JSON value.

pub fn append(text: Text, value: String) -> Text

Insert a value at the end of the text. Appending is always valid, so no try_ variant exists.

Examples

text.new(replica_id.new("A"))
|> text.insert(0, "ab")
|> text.append("cd")
|> text.value()
// -> "abcd"
pub fn append_with_delta(
  text: Text,
  value: String,
) -> #(Text, Text)

Append a value and return both the updated text and insertion delta.

pub fn compact(
  text: Text,
  stable: version_vector.VersionVector,
) -> #(Text, sequence.ForwardingMap)

Compact everything at or below a stability frontier.

Delegates to sequence.compact: stable tombstones are dropped, runs of stable graphemes are merged into compact blocks, and every dropped ID gets a forwarding entry so anchors and rebased operations still resolve. See lattice_sequence/sequence.compact for the stability contract.

pub fn delete(text: Text, index: Int) -> Text

Delete the value at the visible character index.

Panics with DeleteIndexOutOfBounds when index is outside [0, length). Use try_delete_with_delta to handle an untrusted index without crashing.

pub fn delete_range(text: Text, start: Int, end: Int) -> Text

Delete the graphemes in [start, end).

Examples

text.new(replica_id.new("A"))
|> text.insert(0, "abcd")
|> text.delete_range(1, 3)
|> text.value()
// -> "ad"

Panics with RangeOutOfBounds when [start, end) is not a valid range in [0, length]. Use try_delete_range_with_delta to handle untrusted bounds without crashing.

pub fn delete_range_with_delta(
  text: Text,
  start: Int,
  end: Int,
) -> #(Text, Text)

Delete a grapheme range and return both the updated text and deletion delta.

Panics with RangeOutOfBounds when [start, end) is not a valid range in [0, length]. Use try_delete_range_with_delta to handle untrusted bounds without crashing.

pub fn delete_with_delta(text: Text, index: Int) -> #(Text, Text)

Delete a value and return both the updated text and deletion delta.

Panics with DeleteIndexOutOfBounds when index is outside [0, length). Use try_delete_with_delta to handle an untrusted index without crashing.

pub fn end_anchor() -> sequence.Anchor

Create an anchor at the end of the text. Always resolves to the current grapheme length, tracking growth.

pub fn from_json(
  json_string: String,
) -> Result(Text, json.DecodeError)

Decode text from the canonical sequence JSON envelope.

pub fn frontier(text: Text) -> version_vector.VersionVector

The stability frontier this text was last compacted at.

pub fn insert(text: Text, index: Int, value: String) -> Text

Insert a value at the visible character index.

Panics with IndexOutOfBounds when index is outside [0, length]. Use try_insert_with_delta to handle an untrusted index without crashing.

pub fn insert_with_delta(
  text: Text,
  index: Int,
  value: String,
) -> #(Text, Text)

Insert a value and return both the updated text and insertion delta.

Panics with IndexOutOfBounds when index is outside [0, length]. Use try_insert_with_delta to handle an untrusted index without crashing.

pub fn length(text: Text) -> Int

Count the visible graphemes in the text.

Examples

text.new(replica_id.new("A"))
|> text.insert(0, "a👍")
|> text.length()
// -> 2
pub fn merge(a: Text, b: Text) -> Text

Merge two text CRDT states.

pub fn move(text: Text, from_index: Int, to_index: Int) -> Text

Move the grapheme at from_index to to_index.

The to_index is interpreted after removing the grapheme from from_index.

Examples

text.new(replica_id.new("A"))
|> text.insert(0, "abc")
|> text.move(0, 2)
|> text.value()
// -> "bca"

Panics with a MoveError when either index is out of bounds. Use try_move_with_delta to handle untrusted indices without crashing.

pub fn move_with_delta(
  text: Text,
  from_index: Int,
  to_index: Int,
) -> #(Text, Text)

Move a grapheme and return both the updated text and move delta.

Panics with a MoveError when either index is out of bounds. Use try_move_with_delta to handle untrusted indices without crashing.

pub fn new(replica_id: replica_id.ReplicaId) -> Text

Create an empty text CRDT for a replica.

pub fn remove_forwardings(
  text: Text,
  map: sequence.ForwardingMap,
) -> Text

Remove previously emitted forwarding entries from the text.

Forwardings are bounded by the host’s retention policy: keep the map returned by each compact round and expire old rounds by passing them here.

pub fn replace_range(
  text: Text,
  start: Int,
  end: Int,
  value: String,
) -> Text

Replace the graphemes in [start, end) with a value.

Examples

text.new(replica_id.new("A"))
|> text.insert(0, "abcd")
|> text.replace_range(1, 3, "XY")
|> text.value()
// -> "aXYd"

Panics with RangeOutOfBounds when [start, end) is not a valid range in [0, length]. Use try_replace_range_with_delta to handle untrusted bounds without crashing.

pub fn replace_range_with_delta(
  text: Text,
  start: Int,
  end: Int,
  value: String,
) -> #(Text, Text)

Replace a grapheme range and return both the updated text and replacement delta.

Panics with RangeOutOfBounds when [start, end) is not a valid range in [0, length]. Use try_replace_range_with_delta to handle untrusted bounds without crashing.

pub fn resolve_anchor(text: Text, anchor: sequence.Anchor) -> Int

Resolve an anchor to a current grapheme index in [0, length].

Anchors on deleted graphemes still resolve: they collapse to the gap where the grapheme used to be. Anchors follow moved graphemes. Panics with UnknownAnchorTarget when the target was never merged or was compacted away and its forwarding has expired — hosts holding anchors across compaction rounds should use try_resolve_anchor and treat failure as “re-anchor”.

pub fn start_anchor() -> sequence.Anchor

Create an anchor at the start of the text. Always resolves to 0.

pub fn substring(text: Text, start: Int, end: Int) -> String

Return the graphemes in [start, end), clamping both indexes to the text bounds. An empty range (including start > end) yields "".

Examples

text.new(replica_id.new("A"))
|> text.insert(0, "abcd")
|> text.substring(1, 3)
// -> "bc"
pub fn to_json(text: Text) -> json.Json

Encode text using the canonical sequence JSON envelope.

pub fn try_anchor_at(
  text: Text,
  index: Int,
  bias: sequence.Bias,
) -> Result(sequence.Anchor, sequence.AnchorError)

Safely create an anchor at the gap before the grapheme at index.

Valid positions are 0 <= index <= length.

pub fn try_delete_range_with_delta(
  text: Text,
  start: Int,
  end: Int,
) -> Result(#(Text, Text), RangeError)

Safely delete a grapheme range and return both the updated text and deletion delta.

pub fn try_delete_with_delta(
  text: Text,
  index: Int,
) -> Result(#(Text, Text), sequence.DeleteError)

Safely delete a value and return both the updated text and deletion delta.

pub fn try_insert_with_delta(
  text: Text,
  index: Int,
  value: String,
) -> Result(#(Text, Text), sequence.InsertError)

Safely insert a value and return both the updated text and insertion delta.

pub fn try_move_with_delta(
  text: Text,
  from_index: Int,
  to_index: Int,
) -> Result(#(Text, Text), sequence.MoveError)

Safely move a grapheme and return both the updated text and move delta.

The to_index is interpreted after removing the grapheme from from_index.

pub fn try_replace_range_with_delta(
  text: Text,
  start: Int,
  end: Int,
  value: String,
) -> Result(#(Text, Text), RangeError)

Safely replace a grapheme range and return both the updated text and replacement delta.

pub fn try_resolve_anchor(
  text: Text,
  anchor: sequence.Anchor,
) -> Result(Int, sequence.AnchorError)

Safely resolve an anchor to a current grapheme index in [0, length].

Anchors to compacted graphemes resolve through the forwarding map to the gap the grapheme left behind — semantically the same as tombstone collapse.

Returns Error(UnknownAnchorTarget) when the anchor references a grapheme this replica has never seen (created remotely and not yet merged), or one that was compacted away and whose forwarding entry has since been removed by the host’s retention policy. Either way the anchor is unusable and the holder should re-anchor.

pub fn try_substring(
  text: Text,
  start: Int,
  end: Int,
) -> Result(String, RangeError)

Return the graphemes in [start, end), or an error when the range does not satisfy 0 <= start <= end <= length.

Examples

text.new(replica_id.new("A"))
|> text.insert(0, "abc")
|> text.try_substring(0, 4)
// -> Error(text.RangeOutOfBounds(start: 0, end: 4, length: 3))
pub fn value(text: Text) -> String

Return the visible text as a single string.

pub fn values(text: Text) -> List(String)

Return the visible graphemes as a list.

Search Document