Something went wrong. Try again.
POC of simple editor based on Automerge
Something went wrong. Try again.
2.8 kB · 82 lines
Rust
at main
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283//! Keep the caret and selection attached to the same text when remote edits arrive.//!//! A numeric position shifts when someone inserts text before it. Automerge cursors//! refer to text in the document's history, so we can resolve a new position after merging.use crate::collaborative_editor::EditorContent;use iced::widget::text_editor;use simple_editor::replica::Replica;
pub struct SelectionBookmark { caret: automerge::Cursor, selection: Option<automerge::Cursor>,}
impl SelectionBookmark { /// Call before applying remote changes, while the UI and document still agree. pub fn capture(replica: &Replica, id: &str, content: &EditorContent) -> Option<Self> { let cursor = content.cursor(); let text = content.text(); Some(Self { caret: replica.anchor(id, offset(&text, cursor.position))?, selection: cursor .selection .and_then(|position| replica.anchor(id, offset(&text, position))), }) }
/// Resolve the saved anchors against the merged document, then move the Iced selection. pub fn restore(self, replica: &Replica, id: &str, content: &mut EditorContent) { let text = content.text(); let caret_offset = replica.resolve(id, &self.caret).unwrap_or(0); let selection = self .selection .and_then(|anchor| replica.resolve(id, &anchor)) .map(|offset| position_at(&text, offset)); content.move_to(text_editor::Cursor { position: position_at(&text, caret_offset), selection, }); }}
// Iced/cosmic-text columns are UTF-8 byte offsets. Automerge's default is Unicode code points.pub fn offset(text: &str, position: text_editor::Position) -> usize { let mut count = 0; for (index, line) in text.split_inclusive('\n').enumerate() { if index == position.line { return count + line .char_indices() .take_while(|(byte, _)| *byte < position.column) .count(); } count += line.chars().count(); } count}
pub fn position_at(text: &str, offset: usize) -> text_editor::Position { let mut result = text_editor::Position { line: 0, column: 0 }; for ch in text.chars().take(offset) { if ch == '\n' { result.line += 1; result.column = 0; } else { result.column += ch.len_utf8(); } } result}
#[cfg(test)]mod tests { use super::*;
#[test] fn cursor_offsets_round_trip_unicode_and_newlines() { let text = "Привет 🦀\nsecond e\u{301}\n"; for index in 0..=text.chars().count() { assert_eq!(offset(text, position_at(text, index)), index); } }}