Skip to main content

MultiCursorState

Struct MultiCursorState 

Source
pub struct MultiCursorState {
    pub selections: Vec<IdentifiedSelection>,
    pub primary_id: SelectionId,
    pub node_id: DomNodeId,
    pub contenteditable_key: u64,
}
Expand description

Multi-cursor state for a contenteditable element (Sublime Text style).

Replaces the split CursorManager + SelectionManager pattern for text editing. Supports multiple simultaneous cursors/selections, each with a stable ID.

§Invariants

  • selections is sorted by owner, then by position, and non-overlapping within one owner.
  • The primary selection is identified by the stable primary_id, NOT by vector position: merge_overlapping() re-sorts selections by position, so “last index” is not the most-recently-added cursor.
  • After any mutation, merge_overlapping() is called to maintain invariants.

§Who a selection belongs to (U3)

A selection is identified by an OWNER-SCOPED id: (owner, id). The engine acts on the SelectionOwner::LOCAL set and on nothing else:

  • the PRIMARY is always local - get_primary never answers with a peer’s selection, and primary_id is re-pointed only at a local one;
  • the EDIT SET (to_selections, what typing / Backspace / paste apply to) is the local set, and update_from_edit_result writes back to it alone, leaving every peer’s entry untouched;
  • a plain click (set_single_cursor / set_single_range) collapses the LOCAL set to one and keeps the peers in view;
  • cursor movement (move_all_cursors*) moves local carets only;
  • the platform’s idea of “the selection” (selectedTextRange, the IME’s marked range, the Android selection bridge) is the local primary.

Peers’ selections are DISPLAY-ONLY SNAPSHOTS: they enter through set_owner_selections, leave through remove_owner, and are painted in their owner’s colour. They are not shifted by a local edit either - the sync layer that carried the snapshot here is the one that knows how the edit moved the peer’s caret, and it replaces the snapshot. Anything that walks selections directly and means “what is the user doing” must go through Self::local_selections; walking the whole list is right only for painting.

Fields§

§selections: Vec<IdentifiedSelection>

Sorted by position, non-overlapping. Primary is tracked via primary_id.

§primary_id: SelectionId

Stable ID of the primary selection (most recently added/set). Survives the position sort in merge_overlapping, which would otherwise make the vector’s last element (position-last) masquerade as the primary.

§node_id: DomNodeId

The DOM node this multi-cursor state applies to.

§contenteditable_key: u64

Stable key that survives DOM rebuilds (from calculate_contenteditable_key).

Implementations§

Source§

impl MultiCursorState

Source

pub fn new_with_cursor( cursor: TextCursor, node_id: DomNodeId, contenteditable_key: u64, ) -> Self

Create a new MultiCursorState with a single cursor.

Source

pub fn add_cursor(&mut self, cursor: TextCursor) -> SelectionId

Add a cursor, merging if it overlaps with existing selections. Returns the SelectionId of the new (or merged) cursor.

Source

pub fn add_selection(&mut self, range: SelectionRange) -> SelectionId

Add a selection range, merging if it overlaps. Returns the SelectionId of the new (or merged) selection.

Source

pub fn remove_selection(&mut self, id: SelectionId) -> bool

Remove a selection by its stable ID. Returns true if found and removed.

Source

pub fn local_selections(&self) -> impl Iterator<Item = &IdentifiedSelection>

The LOCAL participant’s selections - the ones the engine edits, moves and reports (U3). Peers’ snapshots are excluded.

Source

pub fn local_selections_mut( &mut self, ) -> impl Iterator<Item = &mut IdentifiedSelection>

Source

pub fn local_len(&self) -> usize

How many carets the LOCAL user is typing into. This - not Self::len

  • is the count a “one line per cursor” paste or a “multi-cursor mode” decision wants; a peer’s caret is not somewhere the local user types.
Source

pub fn get_primary(&self) -> Option<&IdentifiedSelection>

Get the primary selection (the most recently added/set, tracked by primary_id — NOT the vector’s last element, which position-sorting reorders). Falls back to the last LOCAL selection if primary_id was somehow lost - never to a peer’s: the list is owner-sorted, so a plain last() was a peer’s entry whenever one existed, and everything that reads “the selection” (IME, the platform selection, copy) would have been answering with someone else’s.

Source

pub fn get_primary_mut(&mut self) -> Option<&mut IdentifiedSelection>

Get a mutable reference to the primary selection (see get_primary).

Source

pub fn get_primary_cursor(&self) -> Option<TextCursor>

Get the primary cursor position (for scroll-into-view, IME, etc.)

Source

pub fn to_selections(&self) -> Vec<Selection>

The EDIT SET: the LOCAL selections, as a Vec<Selection> for edit_text(). Peers’ carets are excluded (U3) - with them included, a local keystroke was applied at every peer’s caret as well, because multi-cursor editing inserts at every selection it is handed.

Source

pub fn update_from_edit_result(&mut self, new_selections: &[Selection])

Update the LOCAL selections from the result of edit_text().

Preserves existing local IDs where possible (by index among the local entries), assigns new IDs for extras. Peers’ entries are carried over UNTOUCHED, owner and id included: rebuilding the whole list as local used to absorb every peer into the local user after the first keystroke. Their POSITIONS are then moved across the edit by Self::shift_peers_across (U3-a), by the caller that knows the text delta.

Source

pub fn shift_peers_across(&mut self, changes: &[RunTextChange])

Move the PEERS’ selections across a change to the text (U3-a).

update_from_edit_result carries peers over verbatim because the edit result knows nothing about them; the caller that knows what the edit did to the text - one RunTextChange per changed run, all relative to the OLD text - applies it here, and a peer’s caret moves with the text it was anchored in, per the user’s ruling (see RunTextChange::transform). Local selections are not touched: the edit result already placed them. A peer RANGE has both ends moved and may collapse when the change spans it.

The sync layer still owns the semantics of CONCURRENT edits; this is only the local user’s own change, which the peer will also receive.

Source

pub fn shift_all_across(&mut self, changes: &[RunTextChange])

Move EVERY selection, the local ones included, across a change to the text that nobody’s edit placed them for (U3-b): the app’s new generation carried different text for the node - a remote participant’s edit applied through the app’s model, an app-side rewrite - and each caret keeps its logical anchor in it.

Source

pub fn shift_peers_across_diff(&mut self, diff: &RunTextDiff)

shift_peers_across for a diff that may also have changed the run count (U3-a-i).

Source

pub fn shift_all_across_diff(&mut self, diff: &RunTextDiff)

shift_all_across for a diff that may also have changed the run count (U3-a-i).

Source

pub fn set_single_cursor(&mut self, cursor: TextCursor)

Collapse the LOCAL selections to a single cursor (a plain click without Ctrl). Peers’ selections stay: a click is not a message to them.

Source

pub fn set_single_range(&mut self, range: SelectionRange)

Collapse the LOCAL selections to a single range. Peers stay, as above.

Source

pub const fn len(&self) -> usize

Number of selections of EVERY owner - what is painted. For how many carets the local user types into, see Self::local_len.

Source

pub const fn is_empty(&self) -> bool

Whether there are no selections (should not normally happen).

Source

pub fn merge_overlapping(&mut self)

Sort selections by position and merge any that overlap.

Source

pub fn set_owner_selections( &mut self, owner: SelectionOwner, selections: &[Selection], ) -> bool

Replace everything ONE participant owns (U1).

The injection point for a shared editing session: a peer’s cursor arrives over the network, and this makes it the whole of what that peer has selected. Replacing rather than merging is deliberate - a remote participant’s state is a SNAPSHOT, and adding to it would leave stale carets behind whenever a message was missed.

Refuses to touch SelectionOwner::LOCAL: the local caret is the engine’s, and letting an app overwrite it through this door would make every text-editing invariant the engine maintains someone else’s problem. Returns false in that case.

Source

pub fn remove_owner(&mut self, owner: SelectionOwner) -> usize

Forget a participant - they left, or their connection dropped.

Returns how many selections went. LOCAL is refused for the same reason as above; removing it would leave the document with no caret.

Source

pub fn owners(&self) -> Vec<SelectionOwner>

Every participant with a selection right now, LOCAL included.

Source

pub fn move_all_cursors( &mut self, extend_selection: bool, move_fn: impl Fn(&TextCursor) -> TextCursor, )

Move all cursors using a movement function. Merges collisions afterward.

move_fn takes a TextCursor and returns the new TextCursor after movement. If extend_selection is true, the anchor stays and only the focus moves, creating or extending a range.

A bare (non-extending) move over an active range COLLAPSES to the range boundary — the arrow-key rule. Use Self::move_all_cursors_with for steps where that is wrong (Home/End, document jumps).

Source

pub fn move_all_cursors_with( &mut self, extend_selection: bool, collapse_range_to_boundary: bool, move_fn: impl Fn(&TextCursor) -> TextCursor, )

Self::move_all_cursors, with control over what a bare move does to an active range.

collapse_range_to_boundary is the arrow-key rule: Left/Right with a selection put the caret on the selection’s edge and go no further. Every OTHER step — Home/End, Ctrl+Home/End, a visual line, a word — is a MOVEMENT and must be performed: collapsing them to the nearest edge is how pressing End with text selected used to leave the caret sitting at the end of the selection instead of the end of the line.

Source

pub fn remap_node_ids( &mut self, dom_id: DomId, node_id_map: &BTreeMap<NodeId, NodeId>, )

Remap the NodeId in node_id after DOM reconciliation.

If the node was removed (not in the map), the multi-cursor state is cleared.

Trait Implementations§

Source§

impl Clone for MultiCursorState

Source§

fn clone(&self) -> MultiCursorState

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for MultiCursorState

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for MultiCursorState

Source§

impl PartialEq for MultiCursorState

Source§

fn eq(&self, other: &MultiCursorState) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for MultiCursorState

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.