1use alloc::collections::BTreeMap;
48use alloc::vec::Vec;
49use core::sync::atomic::{AtomicU64, Ordering};
50
51use crate::dom::{DomId, DomNodeId, NodeId};
52use crate::geom::{LogicalPosition, LogicalRect};
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
59pub struct ContentIndex {
60 pub run_index: u32,
62 pub item_index: u32,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
71#[repr(C)]
72pub struct GraphemeClusterId {
73 pub source_run: u32,
75 pub start_byte_in_run: u32,
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
82#[repr(C)]
83pub enum CursorAffinity {
84 Leading,
86 Trailing,
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
92#[repr(C)]
93pub struct TextCursor {
94 pub cluster_id: GraphemeClusterId,
96 pub affinity: CursorAffinity,
98}
99
100impl_option!(
101 TextCursor,
102 OptionTextCursor,
103 [Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd]
104);
105
106#[derive(Debug, PartialOrd, Ord, Clone, Copy, PartialEq, Eq, Hash)]
109#[repr(C)]
110pub struct SelectionRange {
111 pub start: TextCursor,
112 pub end: TextCursor,
113}
114
115impl_option!(
116 SelectionRange,
117 OptionSelectionRange,
118 [Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd]
119);
120
121impl_vec!(SelectionRange, SelectionRangeVec, SelectionRangeVecDestructor, SelectionRangeVecDestructorType, SelectionRangeVecSlice, OptionSelectionRange);
122impl_vec_debug!(SelectionRange, SelectionRangeVec);
123impl_vec_clone!(
124 SelectionRange,
125 SelectionRangeVec,
126 SelectionRangeVecDestructor
127);
128impl_vec_partialeq!(SelectionRange, SelectionRangeVec);
129impl_vec_partialord!(SelectionRange, SelectionRangeVec);
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
133#[repr(C, u8)]
134pub enum Selection {
135 Cursor(TextCursor),
136 Range(SelectionRange),
137}
138
139impl_option!(
140 Selection,
141 OptionSelection,
142 [Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord]
143);
144
145impl_vec!(Selection, SelectionVec, SelectionVecDestructor, SelectionVecDestructorType, SelectionVecSlice, OptionSelection);
146impl_vec_debug!(Selection, SelectionVec);
147impl_vec_clone!(Selection, SelectionVec, SelectionVecDestructor);
148impl_vec_partialeq!(Selection, SelectionVec);
149impl_vec_partialord!(Selection, SelectionVec);
150
151#[derive(Debug, Clone, PartialEq)]
153#[repr(C)]
154pub struct SelectionState {
155 pub selections: SelectionVec,
157 pub node_id: DomNodeId,
159}
160
161impl SelectionState {
162 pub fn add(&mut self, new_selection: Selection) {
164 let mut selections: Vec<Selection> = self.selections.as_ref().to_vec();
167 selections.push(new_selection);
168 selections.sort_unstable();
169 selections.dedup(); self.selections = selections.into();
171 }
172
173}
174
175impl_option!(
176 SelectionState,
177 OptionSelectionState,
178 copy = false,
179 clone = false,
180 [Debug, Clone, PartialEq]
181);
182
183#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
192#[repr(C)]
193pub struct SelectionId {
194 pub inner: u64,
195}
196
197impl SelectionId {
198 pub fn new() -> Self {
200 static COUNTER: AtomicU64 = AtomicU64::new(1);
201 Self { inner: COUNTER.fetch_add(1, Ordering::Relaxed) }
202 }
203}
204
205impl Default for SelectionId {
208 fn default() -> Self {
209 Self::new()
210 }
211}
212
213impl_option!(
214 SelectionId,
215 OptionSelectionId,
216 [Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord]
217);
218
219impl_vec!(SelectionId, SelectionIdVec, SelectionIdVecDestructor, SelectionIdVecDestructorType, SelectionIdVecSlice, OptionSelectionId);
220impl_vec_debug!(SelectionId, SelectionIdVec);
221impl_vec_clone!(SelectionId, SelectionIdVec, SelectionIdVecDestructor);
222impl_vec_partialeq!(SelectionId, SelectionIdVec);
223impl_vec_partialord!(SelectionId, SelectionIdVec);
224
225#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
227#[repr(C)]
228pub struct IdentifiedSelection {
229 pub id: SelectionId,
230 pub selection: Selection,
231}
232
233impl_option!(
234 IdentifiedSelection,
235 OptionIdentifiedSelection,
236 [Debug, Clone, Copy, PartialEq, Eq, Hash]
237);
238
239impl_vec!(IdentifiedSelection, IdentifiedSelectionVec, IdentifiedSelectionVecDestructor, IdentifiedSelectionVecDestructorType, IdentifiedSelectionVecSlice, OptionIdentifiedSelection);
240impl_vec_debug!(IdentifiedSelection, IdentifiedSelectionVec);
241impl_vec_clone!(IdentifiedSelection, IdentifiedSelectionVec, IdentifiedSelectionVecDestructor);
242impl_vec_partialeq!(IdentifiedSelection, IdentifiedSelectionVec);
243
244#[derive(Debug, Clone, PartialEq, Eq)]
257pub struct MultiCursorState {
258 pub selections: Vec<IdentifiedSelection>,
260 pub primary_id: SelectionId,
264 pub node_id: DomNodeId,
266 pub contenteditable_key: u64,
268}
269
270impl MultiCursorState {
271 #[must_use] pub fn new_with_cursor(cursor: TextCursor, node_id: DomNodeId, contenteditable_key: u64) -> Self {
273 let id = SelectionId::new();
274 Self {
275 selections: vec![IdentifiedSelection {
276 id,
277 selection: Selection::Cursor(cursor),
278 }],
279 primary_id: id,
280 node_id,
281 contenteditable_key,
282 }
283 }
284
285 #[must_use]
288 pub fn add_cursor(&mut self, cursor: TextCursor) -> SelectionId {
289 let id = SelectionId::new();
290 self.selections.push(IdentifiedSelection {
291 id,
292 selection: Selection::Cursor(cursor),
293 });
294 self.primary_id = id;
295 self.merge_overlapping();
296 id
297 }
298
299 #[must_use]
302 pub fn add_selection(&mut self, range: SelectionRange) -> SelectionId {
303 let id = SelectionId::new();
304 self.selections.push(IdentifiedSelection {
305 id,
306 selection: Selection::Range(range),
307 });
308 self.primary_id = id;
309 self.merge_overlapping();
310 id
311 }
312
313 #[must_use]
315 pub fn remove_selection(&mut self, id: SelectionId) -> bool {
316 let len_before = self.selections.len();
317 self.selections.retain(|s| s.id != id);
318 let removed = self.selections.len() < len_before;
319 if removed {
320 self.ensure_primary_valid();
322 }
323 removed
324 }
325
326 #[must_use] pub fn get_primary(&self) -> Option<&IdentifiedSelection> {
331 let pid = self.primary_id;
332 self.selections
333 .iter()
334 .find(|s| s.id == pid)
335 .or_else(|| self.selections.last())
336 }
337
338 pub fn get_primary_mut(&mut self) -> Option<&mut IdentifiedSelection> {
340 let pid = self.primary_id;
341 if let Some(pos) = self.selections.iter().position(|s| s.id == pid) {
342 return self.selections.get_mut(pos);
343 }
344 self.selections.last_mut()
345 }
346
347 fn ensure_primary_valid(&mut self) {
350 let pid = self.primary_id;
351 if !self.selections.iter().any(|s| s.id == pid) {
352 if let Some(last) = self.selections.last() {
353 self.primary_id = last.id;
354 }
355 }
356 }
357
358 #[must_use] pub fn get_primary_cursor(&self) -> Option<TextCursor> {
360 self.get_primary().map(|s| match &s.selection {
361 Selection::Cursor(c) => *c,
362 Selection::Range(r) => r.end,
363 })
364 }
365
366 #[must_use] pub fn to_selections(&self) -> Vec<Selection> {
368 self.selections.iter().map(|s| s.selection).collect()
369 }
370
371 pub fn update_from_edit_result(&mut self, new_selections: &[Selection]) {
375 let old_ids: Vec<SelectionId> = self.selections.iter().map(|s| s.id).collect();
376 self.selections.clear();
377 for (i, sel) in new_selections.iter().enumerate() {
378 let id = old_ids.get(i).copied().unwrap_or_else(SelectionId::new);
379 self.selections.push(IdentifiedSelection {
380 id,
381 selection: *sel,
382 });
383 }
384 self.ensure_primary_valid();
386 }
388
389 pub fn set_single_cursor(&mut self, cursor: TextCursor) {
391 let id = self.selections.last().map_or_else(SelectionId::new, |primary| primary.id);
392 self.selections.clear();
393 self.selections.push(IdentifiedSelection {
394 id,
395 selection: Selection::Cursor(cursor),
396 });
397 self.primary_id = id;
398 }
399
400 pub fn set_single_range(&mut self, range: SelectionRange) {
402 let id = self.selections.last().map_or_else(SelectionId::new, |primary| primary.id);
403 self.selections.clear();
404 self.selections.push(IdentifiedSelection {
405 id,
406 selection: Selection::Range(range),
407 });
408 self.primary_id = id;
409 }
410
411 #[must_use] pub const fn len(&self) -> usize {
413 self.selections.len()
414 }
415
416 #[must_use] pub const fn is_empty(&self) -> bool {
418 self.selections.is_empty()
419 }
420
421 pub fn merge_overlapping(&mut self) {
423 if self.selections.len() <= 1 {
424 return;
425 }
426
427 let primary = self.primary_id;
429 let mut new_primary = primary;
430
431 self.selections.sort_by(|a, b| {
433 let pos_a = selection_start_pos(&a.selection);
434 let pos_b = selection_start_pos(&b.selection);
435 pos_a.cmp(&pos_b)
436 });
437
438 let mut merged: Vec<IdentifiedSelection> = Vec::with_capacity(self.selections.len());
441 for sel in self.selections.drain(..) {
442 if let Some(last) = merged.last_mut() {
443 let last_end = selection_end_pos(&last.selection);
444 let cur_start = selection_start_pos(&sel.selection);
445 if cur_start <= last_end {
446 let new_start = selection_start_pos(&last.selection);
448 let cur_end = selection_end_pos(&sel.selection);
449 let new_end = if cur_end > last_end { cur_end } else { last_end };
450 if new_start == new_end {
451 last.selection = Selection::Cursor(new_start);
452 } else {
453 last.selection = Selection::Range(SelectionRange {
454 start: new_start,
455 end: new_end,
456 });
457 }
458 let inherits_primary =
466 last.id == primary || sel.id == primary || last.id == new_primary;
467 last.id = sel.id;
469 if inherits_primary {
470 new_primary = sel.id;
471 }
472 continue;
473 }
474 }
475 merged.push(sel);
476 }
477 self.selections = merged;
478
479 self.primary_id = new_primary;
481 self.ensure_primary_valid();
482 }
483
484 pub fn move_all_cursors(
490 &mut self,
491 extend_selection: bool,
492 move_fn: impl Fn(&TextCursor) -> TextCursor,
493 ) {
494 for sel in &mut self.selections {
495 match &sel.selection {
496 Selection::Cursor(c) => {
497 let new_cursor = move_fn(c);
498 if extend_selection {
499 if *c != new_cursor {
500 sel.selection = Selection::Range(SelectionRange {
501 start: *c,
502 end: new_cursor,
503 });
504 }
505 } else {
506 sel.selection = Selection::Cursor(new_cursor);
507 }
508 }
509 Selection::Range(r) => {
510 if extend_selection {
511 let new_end = move_fn(&r.end);
512 if r.start == new_end {
513 sel.selection = Selection::Cursor(r.start);
514 } else {
515 sel.selection = Selection::Range(SelectionRange {
516 start: r.start,
517 end: new_end,
518 });
519 }
520 } else {
521 let (lo, hi) = if r.start <= r.end {
530 (r.start, r.end)
531 } else {
532 (r.end, r.start)
533 };
534 let probe = move_fn(&r.end);
535 let collapsed = if probe >= r.end { hi } else { lo };
536 sel.selection = Selection::Cursor(collapsed);
537 }
538 }
539 }
540 }
541 self.merge_overlapping();
542 }
543
544 pub fn remap_node_ids(
548 &mut self,
549 dom_id: DomId,
550 node_id_map: &BTreeMap<NodeId, NodeId>,
551 ) {
552 if self.node_id.dom != dom_id {
553 return;
554 }
555 if let Some(old_node_id) = self.node_id.node.into_crate_internal() {
556 if let Some(&new_node_id) = node_id_map.get(&old_node_id) {
557 self.node_id.node = crate::styled_dom::NodeHierarchyItemId::from_crate_internal(Some(new_node_id));
558 } else {
559 self.selections.clear();
561 }
562 }
563 }
564}
565
566fn selection_start_pos(sel: &Selection) -> TextCursor {
568 match sel {
569 Selection::Cursor(c) => *c,
570 Selection::Range(r) => {
571 if r.start <= r.end { r.start } else { r.end }
572 }
573 }
574}
575
576fn selection_end_pos(sel: &Selection) -> TextCursor {
578 match sel {
579 Selection::Cursor(c) => *c,
580 Selection::Range(r) => {
581 if r.end >= r.start { r.end } else { r.start }
582 }
583 }
584}
585
586#[derive(Debug, Clone, Copy, PartialEq, Eq)]
599pub struct SelectionAnchor {
600 pub ifc_root_node_id: NodeId,
603
604 pub cursor: TextCursor,
606
607 pub char_bounds: LogicalRect,
610
611 pub mouse_position: LogicalPosition,
613}
614
615#[derive(Debug, Clone, Copy, PartialEq, Eq)]
619pub struct SelectionFocus {
620 pub ifc_root_node_id: NodeId,
623
624 pub cursor: TextCursor,
626
627 pub mouse_position: LogicalPosition,
629}
630
631#[derive(Debug, Clone, PartialEq, Eq)]
649pub struct TextSelection {
650 pub dom_id: DomId,
652
653 pub anchor: SelectionAnchor,
655
656 pub focus: SelectionFocus,
658
659 pub affected_nodes: BTreeMap<NodeId, SelectionRange>,
665
666 pub is_forward: bool,
669}
670
671impl TextSelection {
672 #[must_use] pub fn new_collapsed(
674 dom_id: DomId,
675 ifc_root_node_id: NodeId,
676 cursor: TextCursor,
677 char_bounds: LogicalRect,
678 mouse_position: LogicalPosition,
679 ) -> Self {
680 let anchor = SelectionAnchor {
681 ifc_root_node_id,
682 cursor,
683 char_bounds,
684 mouse_position,
685 };
686
687 let focus = SelectionFocus {
688 ifc_root_node_id,
689 cursor,
690 mouse_position,
691 };
692
693 let mut affected_nodes = BTreeMap::new();
695 affected_nodes.insert(ifc_root_node_id, SelectionRange {
696 start: cursor,
697 end: cursor,
698 });
699
700 Self {
701 dom_id,
702 anchor,
703 focus,
704 affected_nodes,
705 is_forward: true, }
707 }
708
709 #[must_use] pub fn is_collapsed(&self) -> bool {
711 self.anchor.ifc_root_node_id == self.focus.ifc_root_node_id
712 && self.anchor.cursor == self.focus.cursor
713 }
714
715 #[must_use] pub fn get_range_for_node(&self, ifc_root_node_id: &NodeId) -> Option<&SelectionRange> {
718 self.affected_nodes.get(ifc_root_node_id)
719 }
720
721}
722
723impl_option!(
724 TextSelection,
725 OptionTextSelection,
726 copy = false,
727 clone = false,
728 [Debug, Clone, PartialEq, Eq]
729);
730
731#[cfg(test)]
732mod audit_tests {
733 use super::*;
734
735 fn cursor(byte: u32) -> TextCursor {
736 TextCursor {
737 cluster_id: GraphemeClusterId { source_run: 0, start_byte_in_run: byte },
738 affinity: CursorAffinity::Leading,
739 }
740 }
741
742 fn state(byte: u32) -> MultiCursorState {
743 MultiCursorState::new_with_cursor(cursor(byte), DomNodeId::ROOT, 0)
744 }
745
746 #[test]
747 fn primary_tracked_by_id_not_vec_position() {
748 let mut mc = state(100);
749 let b = mc.add_cursor(cursor(0));
752 assert_eq!(mc.len(), 2);
753 assert_eq!(mc.get_primary().unwrap().id, b);
756 assert_eq!(mc.get_primary_cursor().unwrap(), cursor(0));
757 }
758
759 #[test]
760 fn merge_preserves_primary() {
761 let mut mc = state(5);
762 let _b = mc.add_cursor(cursor(5)); assert_eq!(mc.len(), 1);
764 let primary = mc.get_primary().unwrap();
766 assert_eq!(primary.id, mc.selections[0].id);
767 }
768
769 #[test]
770 fn removing_primary_repoints_it() {
771 let mut mc = state(0);
772 let b = mc.add_cursor(cursor(10)); assert_eq!(mc.get_primary().unwrap().id, b);
774 assert!(mc.remove_selection(b));
775 let p = mc.get_primary().unwrap();
777 assert!(mc.selections.iter().any(|s| s.id == p.id));
778 }
779}
780
781#[cfg(test)]
782mod autotest_generated {
783 use super::*;
784 use crate::geom::LogicalSize;
785 use crate::styled_dom::NodeHierarchyItemId;
786
787 fn c(byte: u32) -> TextCursor {
793 TextCursor {
794 cluster_id: GraphemeClusterId {
795 source_run: 0,
796 start_byte_in_run: byte,
797 },
798 affinity: CursorAffinity::Leading,
799 }
800 }
801
802 fn c_full(run: u32, byte: u32, affinity: CursorAffinity) -> TextCursor {
804 TextCursor {
805 cluster_id: GraphemeClusterId {
806 source_run: run,
807 start_byte_in_run: byte,
808 },
809 affinity,
810 }
811 }
812
813 fn rng(a: u32, b: u32) -> SelectionRange {
814 SelectionRange {
815 start: c(a),
816 end: c(b),
817 }
818 }
819
820 fn dom_node(index: usize) -> DomNodeId {
821 DomNodeId {
822 dom: DomId::ROOT_ID,
823 node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(index))),
824 }
825 }
826
827 fn state(byte: u32) -> MultiCursorState {
828 MultiCursorState::new_with_cursor(c(byte), DomNodeId::ROOT, 0)
829 }
830
831 fn empty_state() -> MultiCursorState {
834 MultiCursorState {
835 selections: Vec::new(),
836 primary_id: SelectionId::new(),
837 node_id: DomNodeId::ROOT,
838 contenteditable_key: 0,
839 }
840 }
841
842 fn ident(id: SelectionId, sel: Selection) -> IdentifiedSelection {
843 IdentifiedSelection { id, selection: sel }
844 }
845
846 fn assert_sorted_nonoverlapping(mc: &MultiCursorState) {
852 for w in mc.selections.windows(2) {
853 let prev_end = selection_end_pos(&w[0].selection);
854 let next_start = selection_start_pos(&w[1].selection);
855 assert!(
856 next_start > prev_end,
857 "selections must be sorted and non-overlapping after merge: {:?} then {:?}",
858 w[0],
859 w[1]
860 );
861 }
862 }
863
864 fn assert_primary_resolves(mc: &MultiCursorState) {
867 if mc.is_empty() {
868 assert!(mc.get_primary().is_none());
869 assert!(mc.get_primary_cursor().is_none());
870 } else {
871 let p = mc.get_primary().expect("non-empty state must have a primary");
872 assert!(
873 mc.selections.iter().any(|s| s.id == p.id),
874 "get_primary() returned a selection not in the vec"
875 );
876 assert_eq!(
877 mc.primary_id, p.id,
878 "primary_id must name an existing selection (not fall back to last)"
879 );
880 }
881 }
882
883 fn assert_ids_unique(mc: &MultiCursorState) {
885 for (i, a) in mc.selections.iter().enumerate() {
886 for b in mc.selections.iter().skip(i + 1) {
887 assert_ne!(a.id, b.id, "duplicate SelectionId in state");
888 }
889 }
890 }
891
892 #[test]
897 fn selection_id_new_is_unique_and_strictly_increasing() {
898 let mut prev = SelectionId::new();
899 assert!(prev.inner > 0, "counter starts at 1, never the 0 sentinel");
900 for _ in 0..1000 {
901 let next = SelectionId::new();
902 assert!(
905 next.inner > prev.inner,
906 "SelectionId counter must be strictly monotonic"
907 );
908 assert_ne!(next, prev);
909 prev = next;
910 }
911 }
912
913 #[test]
914 fn selection_id_default_mints_a_fresh_id() {
915 let a = SelectionId::default();
917 let b = SelectionId::default();
918 let d = SelectionId::new();
919 assert_ne!(a, b);
920 assert_ne!(b, d);
921 assert!(a.inner > 0 && b.inner > 0);
922 }
923
924 #[test]
929 fn selection_state_add_dedups_identical_cursors() {
930 let mut st = SelectionState {
931 selections: Vec::<Selection>::new().into(),
932 node_id: DomNodeId::ROOT,
933 };
934 for _ in 0..100 {
935 st.add(Selection::Cursor(c(42)));
936 }
937 assert_eq!(st.selections.as_ref().len(), 1);
938 assert_eq!(st.selections.as_ref()[0], Selection::Cursor(c(42)));
939 }
940
941 #[test]
942 fn selection_state_add_sorts_descending_input_ascending() {
943 let mut st = SelectionState {
944 selections: Vec::<Selection>::new().into(),
945 node_id: DomNodeId::ROOT,
946 };
947 for byte in [90u32, 10, 50, 0, 70] {
948 st.add(Selection::Cursor(c(byte)));
949 }
950 let got: Vec<Selection> = st.selections.as_ref().to_vec();
951 assert_eq!(got.len(), 5);
952 let want: Vec<Selection> = [0u32, 10, 50, 70, 90]
953 .iter()
954 .map(|b| Selection::Cursor(c(*b)))
955 .collect();
956 assert_eq!(got, want);
957 }
958
959 #[test]
960 fn selection_state_add_boundary_and_reversed_ranges_do_not_panic() {
961 let mut st = SelectionState {
962 selections: Vec::<Selection>::new().into(),
963 node_id: DomNodeId::ROOT,
964 };
965 st.add(Selection::Cursor(c_full(
968 u32::MAX,
969 u32::MAX,
970 CursorAffinity::Trailing,
971 )));
972 st.add(Selection::Cursor(c_full(0, 0, CursorAffinity::Leading)));
973 st.add(Selection::Range(SelectionRange {
974 start: c_full(u32::MAX, u32::MAX, CursorAffinity::Trailing),
975 end: c_full(0, 0, CursorAffinity::Leading),
976 }));
977 st.add(Selection::Range(rng(0, u32::MAX)));
978 assert_eq!(st.selections.as_ref().len(), 4);
980 let got: Vec<Selection> = st.selections.as_ref().to_vec();
982 let mut sorted = got.clone();
983 sorted.sort_unstable();
984 assert_eq!(got, sorted);
985 }
986
987 #[test]
988 fn selection_state_add_cursor_and_range_at_same_pos_are_distinct() {
989 let mut st = SelectionState {
990 selections: Vec::<Selection>::new().into(),
991 node_id: DomNodeId::ROOT,
992 };
993 st.add(Selection::Range(rng(5, 5)));
994 st.add(Selection::Cursor(c(5)));
995 assert_eq!(st.selections.as_ref().len(), 2);
998 assert_eq!(st.selections.as_ref()[0], Selection::Cursor(c(5)));
1000 }
1001
1002 #[test]
1007 fn new_with_cursor_invariants_hold() {
1008 let node = dom_node(7);
1009 let mc = MultiCursorState::new_with_cursor(c(3), node, 0xDEAD_BEEF);
1010 assert_eq!(mc.len(), 1);
1011 assert!(!mc.is_empty());
1012 assert_eq!(mc.selections.len(), mc.len());
1013 assert_eq!(mc.primary_id, mc.selections[0].id);
1014 assert_eq!(mc.node_id, node);
1015 assert_eq!(mc.contenteditable_key, 0xDEAD_BEEF);
1016 assert_eq!(mc.get_primary_cursor(), Some(c(3)));
1017 assert_eq!(mc.to_selections(), vec![Selection::Cursor(c(3))]);
1018 assert_primary_resolves(&mc);
1019 }
1020
1021 #[test]
1022 fn new_with_cursor_extreme_args_do_not_panic() {
1023 let mc = MultiCursorState::new_with_cursor(
1024 c_full(u32::MAX, u32::MAX, CursorAffinity::Trailing),
1025 dom_node(usize::MAX / 4),
1026 u64::MAX,
1027 );
1028 assert_eq!(mc.len(), 1);
1029 assert_eq!(mc.contenteditable_key, u64::MAX);
1030 assert_eq!(
1031 mc.get_primary_cursor(),
1032 Some(c_full(u32::MAX, u32::MAX, CursorAffinity::Trailing))
1033 );
1034 assert_primary_resolves(&mc);
1035 }
1036
1037 #[test]
1038 fn two_states_get_distinct_ids() {
1039 let a = state(0);
1040 let b = state(0);
1041 assert_ne!(a.primary_id, b.primary_id);
1042 }
1043
1044 #[test]
1049 fn add_cursor_at_same_position_merges_to_one() {
1050 let mut mc = state(5);
1051 let b = mc.add_cursor(c(5));
1052 assert_eq!(mc.len(), 1);
1053 assert_eq!(mc.selections[0].selection, Selection::Cursor(c(5)));
1054 assert_eq!(mc.selections[0].id, b, "merge keeps the newer id");
1055 assert_primary_resolves(&mc);
1056 assert_ids_unique(&mc);
1057 }
1058
1059 #[test]
1060 fn add_cursor_distinct_positions_stay_separate_and_sorted() {
1061 let mut mc = state(30);
1062 let _ = mc.add_cursor(c(10));
1063 let last = mc.add_cursor(c(20));
1064 assert_eq!(mc.len(), 3);
1065 assert_eq!(mc.to_selections(), vec![
1067 Selection::Cursor(c(10)),
1068 Selection::Cursor(c(20)),
1069 Selection::Cursor(c(30)),
1070 ]);
1071 assert_eq!(mc.get_primary().unwrap().id, last);
1074 assert_eq!(mc.get_primary_cursor(), Some(c(20)));
1075 assert_sorted_nonoverlapping(&mc);
1076 assert_primary_resolves(&mc);
1077 assert_ids_unique(&mc);
1078 }
1079
1080 #[test]
1081 fn add_cursor_same_byte_different_affinity_does_not_merge() {
1082 let mut mc = MultiCursorState::new_with_cursor(
1086 c_full(0, 4, CursorAffinity::Leading),
1087 DomNodeId::ROOT,
1088 0,
1089 );
1090 let _ = mc.add_cursor(c_full(0, 4, CursorAffinity::Trailing));
1091 assert_eq!(mc.len(), 2);
1092 assert_sorted_nonoverlapping(&mc);
1093 assert_primary_resolves(&mc);
1094 }
1095
1096 #[test]
1097 fn add_selection_overlapping_ranges_merge_into_union() {
1098 let mut mc = empty_state();
1099 let _ = mc.add_selection(rng(0, 10));
1100 let _ = mc.add_selection(rng(5, 20));
1101 assert_eq!(mc.len(), 1);
1102 assert_eq!(mc.selections[0].selection, Selection::Range(rng(0, 20)));
1103 assert_primary_resolves(&mc);
1104 }
1105
1106 #[test]
1107 fn add_selection_touching_ranges_merge() {
1108 let mut mc = empty_state();
1110 let _ = mc.add_selection(rng(0, 10));
1111 let _ = mc.add_selection(rng(10, 20));
1112 assert_eq!(mc.len(), 1);
1113 assert_eq!(mc.selections[0].selection, Selection::Range(rng(0, 20)));
1114 }
1115
1116 #[test]
1117 fn add_selection_disjoint_ranges_stay_separate() {
1118 let mut mc = empty_state();
1119 let _ = mc.add_selection(rng(0, 10));
1120 let _ = mc.add_selection(rng(11, 20));
1121 assert_eq!(mc.len(), 2);
1122 assert_sorted_nonoverlapping(&mc);
1123 assert_primary_resolves(&mc);
1124 }
1125
1126 #[test]
1127 fn add_selection_reversed_range_is_normalized_for_merging() {
1128 let mut mc = empty_state();
1130 let _ = mc.add_selection(SelectionRange {
1131 start: c(20),
1132 end: c(5),
1133 });
1134 let _ = mc.add_cursor(c(10));
1136 assert_eq!(mc.len(), 1);
1137 assert_eq!(mc.selections[0].selection, Selection::Range(rng(5, 20)));
1139 assert_primary_resolves(&mc);
1140 }
1141
1142 #[test]
1143 fn add_selection_at_u32_max_boundary_does_not_overflow() {
1144 let mut mc = empty_state();
1145 let _ = mc.add_selection(rng(u32::MAX - 1, u32::MAX));
1146 let _ = mc.add_cursor(c(u32::MAX));
1147 assert_eq!(mc.len(), 1);
1148 assert_eq!(
1149 mc.selections[0].selection,
1150 Selection::Range(rng(u32::MAX - 1, u32::MAX))
1151 );
1152 assert_primary_resolves(&mc);
1153 }
1154
1155 #[test]
1156 fn add_cursor_stress_500_distinct_positions() {
1157 let mut mc = state(0);
1158 for i in 1..=500u32 {
1159 let _ = mc.add_cursor(c(i * 2));
1160 }
1161 assert_eq!(mc.len(), 501);
1162 assert_sorted_nonoverlapping(&mc);
1163 assert_primary_resolves(&mc);
1164 assert_ids_unique(&mc);
1165 }
1166
1167 #[test]
1168 fn add_cursor_stress_same_position_never_grows() {
1169 let mut mc = state(9);
1170 for _ in 0..300 {
1171 let _ = mc.add_cursor(c(9));
1172 }
1173 assert_eq!(mc.len(), 1, "identical cursors must always collapse");
1174 assert_primary_resolves(&mc);
1175 }
1176
1177 #[test]
1182 fn remove_selection_unknown_id_returns_false_and_changes_nothing() {
1183 let mut mc = state(1);
1184 let before = mc.clone();
1185 let ghost = SelectionId::new(); assert!(!mc.remove_selection(ghost));
1187 assert_eq!(mc, before);
1188 }
1189
1190 #[test]
1191 fn remove_selection_twice_second_call_returns_false() {
1192 let mut mc = state(0);
1193 let b = mc.add_cursor(c(10));
1194 assert!(mc.remove_selection(b));
1195 assert!(!mc.remove_selection(b));
1196 assert_eq!(mc.len(), 1);
1197 assert_primary_resolves(&mc);
1198 }
1199
1200 #[test]
1201 fn remove_all_selections_leaves_a_safe_empty_state() {
1202 let mut mc = state(0);
1203 let b = mc.add_cursor(c(10));
1204 let a = mc.selections.iter().find(|s| s.id != b).unwrap().id;
1205 assert!(mc.remove_selection(a));
1206 assert!(mc.remove_selection(b));
1207 assert!(mc.is_empty());
1208 assert_eq!(mc.len(), 0);
1209 assert!(mc.get_primary().is_none());
1210 assert!(mc.get_primary_cursor().is_none());
1211 assert!(mc.to_selections().is_empty());
1212 mc.merge_overlapping();
1214 mc.move_all_cursors(true, |cur| *cur);
1215 assert!(mc.is_empty());
1216 }
1217
1218 #[test]
1219 fn remove_primary_from_three_repoints_to_a_survivor() {
1220 let mut mc = state(0);
1221 let _ = mc.add_cursor(c(10));
1222 let p = mc.add_cursor(c(20)); assert_eq!(mc.get_primary().unwrap().id, p);
1224 assert!(mc.remove_selection(p));
1225 assert_eq!(mc.len(), 2);
1226 assert_primary_resolves(&mc);
1227 }
1228
1229 #[test]
1234 fn empty_state_getters_return_none_without_panicking() {
1235 let mut mc = empty_state();
1236 assert!(mc.is_empty());
1237 assert_eq!(mc.len(), 0);
1238 assert!(mc.get_primary().is_none());
1239 assert!(mc.get_primary_mut().is_none());
1240 assert!(mc.get_primary_cursor().is_none());
1241 assert!(mc.to_selections().is_empty());
1242 mc.merge_overlapping(); mc.ensure_primary_valid(); assert!(mc.is_empty());
1245 }
1246
1247 #[test]
1248 fn get_primary_falls_back_to_last_when_primary_id_is_dangling() {
1249 let mut mc = state(0);
1250 let _ = mc.add_cursor(c(10));
1251 mc.primary_id = SelectionId::new(); let p = mc.get_primary().expect("must fall back, not return None");
1253 assert_eq!(p.id, mc.selections.last().unwrap().id);
1254 assert_eq!(mc.get_primary_cursor(), Some(c(10)));
1255 }
1256
1257 #[test]
1258 fn ensure_primary_valid_adopts_last_id_when_dangling() {
1259 let mut mc = state(0);
1260 let _ = mc.add_cursor(c(10));
1261 let dangling = SelectionId::new();
1262 mc.primary_id = dangling;
1263 mc.ensure_primary_valid();
1264 assert_ne!(mc.primary_id, dangling);
1265 assert_eq!(mc.primary_id, mc.selections.last().unwrap().id);
1266 let fixed = mc.primary_id;
1268 mc.ensure_primary_valid();
1269 assert_eq!(mc.primary_id, fixed);
1270 }
1271
1272 #[test]
1273 fn ensure_primary_valid_on_empty_leaves_id_untouched() {
1274 let mut mc = empty_state();
1275 let before = mc.primary_id;
1276 mc.ensure_primary_valid();
1277 assert_eq!(mc.primary_id, before, "nothing to adopt — id must not change");
1278 assert!(mc.get_primary().is_none());
1279 }
1280
1281 #[test]
1282 fn get_primary_cursor_of_a_range_is_its_end_field() {
1283 let mut mc = empty_state();
1284 mc.set_single_range(rng(3, 9));
1285 assert_eq!(mc.get_primary_cursor(), Some(c(9)));
1286
1287 let mut back = empty_state();
1291 back.set_single_range(SelectionRange {
1292 start: c(9),
1293 end: c(3),
1294 });
1295 assert_eq!(back.get_primary_cursor(), Some(c(3)));
1296 }
1297
1298 #[test]
1299 fn get_primary_mut_mutation_is_visible_through_get_primary() {
1300 let mut mc = state(0);
1301 let p = mc.add_cursor(c(50));
1302 {
1303 let prim = mc.get_primary_mut().expect("primary exists");
1304 assert_eq!(prim.id, p);
1305 prim.selection = Selection::Range(rng(50, 60));
1306 }
1307 assert_eq!(
1308 mc.get_primary().unwrap().selection,
1309 Selection::Range(rng(50, 60))
1310 );
1311 assert_eq!(mc.get_primary_cursor(), Some(c(60)));
1312 }
1313
1314 #[test]
1315 fn get_primary_mut_falls_back_to_last_when_dangling() {
1316 let mut mc = state(0);
1317 let _ = mc.add_cursor(c(10));
1318 mc.primary_id = SelectionId::new();
1319 let last_id = mc.selections.last().unwrap().id;
1320 let prim = mc.get_primary_mut().expect("fallback to last");
1321 assert_eq!(prim.id, last_id);
1322 }
1323
1324 #[test]
1325 fn to_selections_matches_the_internal_order_and_len() {
1326 let mut mc = state(30);
1327 let _ = mc.add_cursor(c(10));
1328 let _ = mc.add_selection(rng(15, 20));
1329 let sels = mc.to_selections();
1330 assert_eq!(sels.len(), mc.len());
1331 let inner: Vec<Selection> = mc.selections.iter().map(|s| s.selection).collect();
1332 assert_eq!(sels, inner);
1333 }
1334
1335 #[test]
1336 fn len_and_is_empty_always_agree() {
1337 let mut mc = empty_state();
1338 assert!(mc.is_empty() && mc.is_empty());
1339 let _ = mc.add_cursor(c(1));
1340 assert!(!mc.is_empty() && mc.len() == 1);
1341 for i in 2..20u32 {
1342 let _ = mc.add_cursor(c(i * 3));
1343 }
1344 assert_eq!(mc.len(), mc.selections.len());
1345 assert_eq!(mc.is_empty(), mc.is_empty());
1346 assert!(!mc.is_empty());
1347 }
1348
1349 #[test]
1354 fn update_from_edit_result_with_empty_slice_clears_everything() {
1355 let mut mc = state(0);
1356 let _ = mc.add_cursor(c(10));
1357 mc.update_from_edit_result(&[]);
1358 assert!(mc.is_empty());
1359 assert!(mc.get_primary().is_none());
1360 assert!(mc.get_primary_cursor().is_none());
1361 }
1362
1363 #[test]
1364 fn update_from_edit_result_preserves_ids_by_index_and_mints_extras() {
1365 let mut mc = state(0);
1366 let _ = mc.add_cursor(c(10));
1367 let old: Vec<SelectionId> = mc.selections.iter().map(|s| s.id).collect();
1368 assert_eq!(old.len(), 2);
1369
1370 mc.update_from_edit_result(&[
1371 Selection::Cursor(c(1)),
1372 Selection::Cursor(c(2)),
1373 Selection::Cursor(c(3)),
1374 Selection::Range(rng(4, 8)),
1375 ]);
1376 assert_eq!(mc.len(), 4);
1377 assert_eq!(mc.selections[0].id, old[0], "id preserved by index");
1378 assert_eq!(mc.selections[1].id, old[1], "id preserved by index");
1379 assert_ids_unique(&mc);
1380 assert_primary_resolves(&mc);
1381 assert_eq!(mc.selections[3].selection, Selection::Range(rng(4, 8)));
1382 }
1383
1384 #[test]
1385 fn update_from_edit_result_shrinking_keeps_primary_resolvable() {
1386 let mut mc = state(0);
1387 let _ = mc.add_cursor(c(10));
1388 let _ = mc.add_cursor(c(20)); mc.update_from_edit_result(&[Selection::Cursor(c(99))]);
1390 assert_eq!(mc.len(), 1);
1391 assert_primary_resolves(&mc);
1394 assert_eq!(mc.get_primary_cursor(), Some(c(99)));
1395 }
1396
1397 #[test]
1398 fn update_from_edit_result_does_not_merge_overlaps() {
1399 let mut mc = state(0);
1401 mc.update_from_edit_result(&[
1402 Selection::Range(rng(0, 10)),
1403 Selection::Range(rng(5, 15)),
1404 ]);
1405 assert_eq!(mc.len(), 2, "update must NOT merge");
1406 mc.merge_overlapping();
1408 assert_eq!(mc.len(), 1);
1409 assert_eq!(mc.selections[0].selection, Selection::Range(rng(0, 15)));
1410 assert_primary_resolves(&mc);
1411 }
1412
1413 #[test]
1414 fn update_from_edit_result_with_1000_selections() {
1415 let mut mc = state(0);
1416 let big: Vec<Selection> = (0..1000u32).map(|i| Selection::Cursor(c(i * 4))).collect();
1417 mc.update_from_edit_result(&big);
1418 assert_eq!(mc.len(), 1000);
1419 assert_ids_unique(&mc);
1420 assert_primary_resolves(&mc);
1421 assert_eq!(mc.to_selections(), big);
1422 }
1423
1424 #[test]
1429 fn set_single_cursor_collapses_all_selections() {
1430 let mut mc = state(0);
1431 let _ = mc.add_cursor(c(10));
1432 let _ = mc.add_selection(rng(20, 30));
1433 mc.set_single_cursor(c(7));
1434 assert_eq!(mc.len(), 1);
1435 assert_eq!(mc.selections[0].selection, Selection::Cursor(c(7)));
1436 assert_primary_resolves(&mc);
1437 assert_eq!(mc.get_primary_cursor(), Some(c(7)));
1438 }
1439
1440 #[test]
1441 fn set_single_range_collapses_all_selections() {
1442 let mut mc = state(0);
1443 let _ = mc.add_cursor(c(10));
1444 mc.set_single_range(rng(u32::MAX - 2, u32::MAX));
1445 assert_eq!(mc.len(), 1);
1446 assert_eq!(
1447 mc.selections[0].selection,
1448 Selection::Range(rng(u32::MAX - 2, u32::MAX))
1449 );
1450 assert_primary_resolves(&mc);
1451 }
1452
1453 #[test]
1454 fn set_single_cursor_on_empty_state_mints_a_fresh_id() {
1455 let mut mc = empty_state();
1456 let stale = mc.primary_id;
1457 mc.set_single_cursor(c(1));
1458 assert_eq!(mc.len(), 1);
1459 assert_ne!(mc.primary_id, stale, "no last element -> a new id is minted");
1460 assert_primary_resolves(&mc);
1461 }
1462
1463 #[test]
1464 fn set_single_range_on_empty_state_mints_a_fresh_id() {
1465 let mut mc = empty_state();
1466 mc.set_single_range(rng(0, 0));
1467 assert_eq!(mc.len(), 1);
1468 assert_eq!(mc.selections[0].selection, Selection::Range(rng(0, 0)));
1469 assert_primary_resolves(&mc);
1470 }
1471
1472 #[test]
1473 fn set_single_cursor_is_idempotent() {
1474 let mut mc = state(0);
1475 mc.set_single_cursor(c(5));
1476 let first = mc.clone();
1477 mc.set_single_cursor(c(5));
1478 assert_eq!(mc, first, "re-setting the same cursor must reuse the id");
1479 }
1480
1481 #[test]
1486 fn merge_overlapping_on_empty_and_single_is_a_noop() {
1487 let mut e = empty_state();
1488 e.merge_overlapping();
1489 assert!(e.is_empty());
1490
1491 let mut one = state(3);
1492 let before = one.clone();
1493 one.merge_overlapping();
1494 assert_eq!(one, before);
1495 }
1496
1497 #[test]
1498 fn merge_overlapping_collapses_a_whole_chain() {
1499 let mut mc = empty_state();
1500 let ids: Vec<SelectionId> = (0..4).map(|_| SelectionId::new()).collect();
1501 mc.selections = vec![
1502 ident(ids[0], Selection::Range(rng(25, 40))),
1503 ident(ids[1], Selection::Range(rng(0, 10))),
1504 ident(ids[2], Selection::Range(rng(12, 30))),
1505 ident(ids[3], Selection::Range(rng(5, 15))),
1506 ];
1507 mc.primary_id = ids[3];
1508 mc.merge_overlapping();
1509 assert_eq!(mc.len(), 1);
1511 assert_eq!(mc.selections[0].selection, Selection::Range(rng(0, 40)));
1512 assert_sorted_nonoverlapping(&mc);
1513 assert_primary_resolves(&mc);
1514 }
1515
1516 #[test]
1517 fn merge_overlapping_keeps_disjoint_selections_and_sorts_them() {
1518 let mut mc = empty_state();
1519 let ids: Vec<SelectionId> = (0..3).map(|_| SelectionId::new()).collect();
1520 mc.selections = vec![
1521 ident(ids[0], Selection::Cursor(c(100))),
1522 ident(ids[1], Selection::Range(rng(0, 5))),
1523 ident(ids[2], Selection::Cursor(c(50))),
1524 ];
1525 mc.primary_id = ids[0];
1526 mc.merge_overlapping();
1527 assert_eq!(mc.len(), 3);
1528 assert_eq!(mc.to_selections(), vec![
1529 Selection::Range(rng(0, 5)),
1530 Selection::Cursor(c(50)),
1531 Selection::Cursor(c(100)),
1532 ]);
1533 assert_sorted_nonoverlapping(&mc);
1534 assert_eq!(mc.primary_id, ids[0]);
1536 assert_eq!(mc.get_primary_cursor(), Some(c(100)));
1537 }
1538
1539 #[test]
1540 fn merge_overlapping_zero_width_merge_yields_a_cursor_not_a_range() {
1541 let mut mc = empty_state();
1542 let ids: Vec<SelectionId> = (0..2).map(|_| SelectionId::new()).collect();
1543 mc.selections = vec![
1544 ident(ids[0], Selection::Cursor(c(8))),
1545 ident(ids[1], Selection::Range(rng(8, 8))),
1546 ];
1547 mc.primary_id = ids[1];
1548 mc.merge_overlapping();
1549 assert_eq!(mc.len(), 1);
1550 assert_eq!(mc.selections[0].selection, Selection::Cursor(c(8)));
1552 assert_primary_resolves(&mc);
1553 }
1554
1555 #[test]
1556 fn merge_overlapping_is_idempotent() {
1557 let mut mc = empty_state();
1558 let ids: Vec<SelectionId> = (0..5).map(|_| SelectionId::new()).collect();
1559 mc.selections = vec![
1560 ident(ids[0], Selection::Range(rng(0, 10))),
1561 ident(ids[1], Selection::Cursor(c(5))),
1562 ident(ids[2], Selection::Range(rng(30, 20))), ident(ids[3], Selection::Cursor(c(100))),
1564 ident(ids[4], Selection::Range(rng(99, 101))),
1565 ];
1566 mc.primary_id = ids[2];
1567 mc.merge_overlapping();
1568 let once = mc.clone();
1569 mc.merge_overlapping();
1570 assert_eq!(mc, once, "merge_overlapping must be a fixed point");
1571 assert_sorted_nonoverlapping(&mc);
1572 assert_primary_resolves(&mc);
1573 }
1574
1575 #[test]
1576 fn merge_overlapping_adversarial_200_selections_keeps_invariants() {
1577 let mut mc = empty_state();
1578 let mut seed: u32 = 0x1234_5678;
1579 let mut next = || {
1580 seed = seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
1581 seed
1582 };
1583 let mut sels = Vec::new();
1584 for i in 0..200u32 {
1585 let a = next() % 1000;
1586 let b = next() % 1000;
1587 let sel = match i % 4 {
1588 0 => Selection::Cursor(c(a)),
1589 1 => Selection::Range(SelectionRange {
1590 start: c(a),
1591 end: c(b),
1592 }), 2 => Selection::Range(rng(a.min(b), a.max(b))),
1594 _ => Selection::Cursor(c_full(
1595 0,
1596 a,
1597 if b % 2 == 0 {
1598 CursorAffinity::Leading
1599 } else {
1600 CursorAffinity::Trailing
1601 },
1602 )),
1603 };
1604 sels.push(ident(SelectionId::new(), sel));
1605 }
1606 sels.push(ident(SelectionId::new(), Selection::Cursor(c(0))));
1608 sels.push(ident(SelectionId::new(), Selection::Cursor(c(u32::MAX))));
1609 sels.push(ident(
1610 SelectionId::new(),
1611 Selection::Range(rng(u32::MAX - 1, u32::MAX)),
1612 ));
1613 mc.primary_id = sels[7].id;
1614 mc.selections = sels;
1615
1616 mc.merge_overlapping();
1617
1618 assert!(!mc.is_empty());
1619 assert!(mc.len() <= 203);
1620 assert_sorted_nonoverlapping(&mc);
1621 assert_primary_resolves(&mc);
1622 assert_ids_unique(&mc);
1623 }
1624
1625 #[test]
1626 fn merge_overlapping_primary_inside_a_chain_still_resolves() {
1627 let mut mc = empty_state();
1630 let ids: Vec<SelectionId> = (0..4).map(|_| SelectionId::new()).collect();
1631 mc.selections = vec![
1632 ident(ids[0], Selection::Cursor(c(0))),
1633 ident(ids[1], Selection::Cursor(c(0))),
1634 ident(ids[2], Selection::Cursor(c(0))),
1635 ident(ids[3], Selection::Cursor(c(100))),
1636 ];
1637 mc.primary_id = ids[0];
1638 mc.merge_overlapping();
1639
1640 assert_eq!(mc.len(), 2);
1641 assert!(
1643 mc.selections.iter().any(|s| s.id == mc.primary_id),
1644 "primary_id must name a surviving selection"
1645 );
1646 assert!(mc.get_primary().is_some());
1647 }
1648
1649 #[test]
1650 fn merge_overlapping_primary_should_follow_its_merge_chain() {
1651 let mut mc = empty_state();
1659 let ids: Vec<SelectionId> = (0..4).map(|_| SelectionId::new()).collect();
1660 mc.selections = vec![
1661 ident(ids[0], Selection::Cursor(c(0))),
1662 ident(ids[1], Selection::Cursor(c(0))),
1663 ident(ids[2], Selection::Cursor(c(0))),
1664 ident(ids[3], Selection::Cursor(c(100))),
1665 ];
1666 mc.primary_id = ids[0];
1667 mc.merge_overlapping();
1668 assert_eq!(
1669 mc.get_primary_cursor(),
1670 Some(c(0)),
1671 "primary jumped to an unrelated selection after the merge"
1672 );
1673 }
1674
1675 #[test]
1680 fn move_all_cursors_identity_leaves_positions_unchanged() {
1681 let mut mc = state(0);
1682 let _ = mc.add_cursor(c(10));
1683 let _ = mc.add_cursor(c(20));
1684 let before = mc.to_selections();
1685 mc.move_all_cursors(false, |cur| *cur);
1686 assert_eq!(mc.to_selections(), before);
1687 assert_eq!(mc.len(), 3);
1688 assert_primary_resolves(&mc);
1689 }
1690
1691 #[test]
1692 fn move_all_cursors_extend_with_no_movement_keeps_a_cursor() {
1693 let mut mc = state(4);
1696 mc.move_all_cursors(true, |cur| *cur);
1697 assert_eq!(mc.len(), 1);
1698 assert_eq!(mc.selections[0].selection, Selection::Cursor(c(4)));
1699 }
1700
1701 #[test]
1702 fn move_all_cursors_extend_turns_a_cursor_into_a_range() {
1703 let mut mc = state(10);
1704 mc.move_all_cursors(true, |cur| c(cur.cluster_id.start_byte_in_run + 5));
1705 assert_eq!(mc.len(), 1);
1706 assert_eq!(mc.selections[0].selection, Selection::Range(rng(10, 15)));
1707 assert_eq!(mc.get_primary_cursor(), Some(c(15)));
1709 }
1710
1711 #[test]
1712 fn move_all_cursors_bare_forward_arrow_collapses_range_to_max_boundary() {
1713 let mut mc = empty_state();
1714 mc.set_single_range(rng(3, 9));
1715 mc.move_all_cursors(false, |cur| c(cur.cluster_id.start_byte_in_run + 1));
1716 assert_eq!(mc.len(), 1);
1717 assert_eq!(mc.selections[0].selection, Selection::Cursor(c(9)));
1719 }
1720
1721 #[test]
1722 fn move_all_cursors_bare_backward_arrow_collapses_range_to_min_boundary() {
1723 let mut mc = empty_state();
1724 mc.set_single_range(rng(3, 9));
1725 mc.move_all_cursors(false, |cur| {
1726 c(cur.cluster_id.start_byte_in_run.saturating_sub(1))
1727 });
1728 assert_eq!(mc.len(), 1);
1729 assert_eq!(mc.selections[0].selection, Selection::Cursor(c(3)));
1730 }
1731
1732 #[test]
1733 fn move_all_cursors_collapses_a_backwards_range_by_direction_not_field_order() {
1734 let mut mc = empty_state();
1737 mc.set_single_range(SelectionRange {
1738 start: c(9),
1739 end: c(3),
1740 });
1741 mc.move_all_cursors(false, |cur| c(cur.cluster_id.start_byte_in_run + 1));
1742 assert_eq!(mc.selections[0].selection, Selection::Cursor(c(9)));
1743
1744 let mut back = empty_state();
1745 back.set_single_range(SelectionRange {
1746 start: c(9),
1747 end: c(3),
1748 });
1749 back.move_all_cursors(false, |cur| {
1750 c(cur.cluster_id.start_byte_in_run.saturating_sub(1))
1751 });
1752 assert_eq!(back.selections[0].selection, Selection::Cursor(c(3)));
1753 }
1754
1755 #[test]
1756 fn move_all_cursors_extend_back_onto_the_anchor_collapses_to_a_cursor() {
1757 let mut mc = empty_state();
1758 mc.set_single_range(rng(3, 4));
1759 mc.move_all_cursors(true, |_| c(3));
1761 assert_eq!(mc.len(), 1);
1762 assert_eq!(mc.selections[0].selection, Selection::Cursor(c(3)));
1763 }
1764
1765 #[test]
1766 fn move_all_cursors_constant_move_fn_merges_everything_into_one() {
1767 let mut mc = state(0);
1768 for i in 1..5u32 {
1769 let _ = mc.add_cursor(c(i * 10));
1770 }
1771 assert_eq!(mc.len(), 5);
1772 mc.move_all_cursors(false, |_| c(7));
1773 assert_eq!(mc.len(), 1, "colliding cursors must be merged afterwards");
1774 assert_eq!(mc.selections[0].selection, Selection::Cursor(c(7)));
1775 assert_primary_resolves(&mc);
1776 assert_sorted_nonoverlapping(&mc);
1777 }
1778
1779 #[test]
1780 fn move_all_cursors_saturating_at_u32_max_does_not_overflow() {
1781 let mut mc = empty_state();
1782 let ids: Vec<SelectionId> = (0..2).map(|_| SelectionId::new()).collect();
1783 mc.selections = vec![
1784 ident(ids[0], Selection::Cursor(c(u32::MAX - 1))),
1785 ident(ids[1], Selection::Cursor(c(u32::MAX))),
1786 ];
1787 mc.primary_id = ids[1];
1788 mc.move_all_cursors(false, |cur| {
1789 c(cur.cluster_id.start_byte_in_run.saturating_add(1))
1790 });
1791 assert_eq!(mc.len(), 1);
1793 assert_eq!(mc.selections[0].selection, Selection::Cursor(c(u32::MAX)));
1794 assert_primary_resolves(&mc);
1795 }
1796
1797 #[test]
1798 fn move_all_cursors_on_empty_state_does_not_panic() {
1799 let mut mc = empty_state();
1800 mc.move_all_cursors(false, |cur| *cur);
1801 mc.move_all_cursors(true, |_| c(u32::MAX));
1802 assert!(mc.is_empty());
1803 }
1804
1805 #[test]
1806 fn move_all_cursors_stress_keeps_invariants() {
1807 let mut mc = state(0);
1808 for i in 1..100u32 {
1809 let _ = mc.add_cursor(c(i * 5));
1810 }
1811 for _ in 0..10 {
1812 mc.move_all_cursors(false, |cur| {
1813 c(cur.cluster_id.start_byte_in_run % 7)
1815 });
1816 assert_sorted_nonoverlapping(&mc);
1817 assert_primary_resolves(&mc);
1818 assert_ids_unique(&mc);
1819 }
1820 assert!(mc.len() <= 7);
1821 }
1822
1823 #[test]
1828 fn remap_node_ids_for_a_different_dom_is_a_noop() {
1829 let mut mc = MultiCursorState::new_with_cursor(c(1), dom_node(5), 0);
1830 mc.node_id.dom = DomId { inner: 7 };
1831 let before = mc.clone();
1832 let mut map = BTreeMap::new();
1833 map.insert(NodeId::new(5), NodeId::new(9));
1834 mc.remap_node_ids(DomId::ROOT_ID, &map);
1835 assert_eq!(mc, before, "a foreign DomId must not touch this state");
1836 }
1837
1838 #[test]
1839 fn remap_node_ids_rewrites_a_surviving_node() {
1840 let mut mc = MultiCursorState::new_with_cursor(c(1), dom_node(5), 0);
1841 let mut map = BTreeMap::new();
1842 map.insert(NodeId::new(5), NodeId::new(9));
1843 mc.remap_node_ids(DomId::ROOT_ID, &map);
1844 assert_eq!(
1845 mc.node_id.node.into_crate_internal(),
1846 Some(NodeId::new(9))
1847 );
1848 assert_eq!(mc.len(), 1, "selections survive a successful remap");
1849 assert_primary_resolves(&mc);
1850 }
1851
1852 #[test]
1853 fn remap_node_ids_clears_selections_when_the_node_was_removed() {
1854 let mut mc = MultiCursorState::new_with_cursor(c(1), dom_node(5), 0);
1855 let _ = mc.add_cursor(c(20));
1856 let map: BTreeMap<NodeId, NodeId> = BTreeMap::new(); mc.remap_node_ids(DomId::ROOT_ID, &map);
1858 assert!(mc.is_empty(), "a removed node must drop its selections");
1859 assert!(mc.get_primary().is_none());
1860 assert_eq!(
1862 mc.node_id.node.into_crate_internal(),
1863 Some(NodeId::new(5))
1864 );
1865 }
1866
1867 #[test]
1868 fn remap_node_ids_with_a_none_node_is_a_noop() {
1869 let mut mc = state(3);
1872 let map: BTreeMap<NodeId, NodeId> = BTreeMap::new();
1873 mc.remap_node_ids(DomId::ROOT_ID, &map);
1874 assert_eq!(mc.len(), 1);
1875 assert_eq!(mc.node_id.node, NodeHierarchyItemId::NONE);
1876 assert_primary_resolves(&mc);
1877 }
1878
1879 #[test]
1880 fn remap_node_ids_handles_large_node_indices() {
1881 let big = 1_000_000usize;
1882 let mut mc = MultiCursorState::new_with_cursor(c(1), dom_node(big), 0);
1883 let mut map = BTreeMap::new();
1884 map.insert(NodeId::new(big), NodeId::new(big * 2));
1885 mc.remap_node_ids(DomId::ROOT_ID, &map);
1886 assert_eq!(
1887 mc.node_id.node.into_crate_internal(),
1888 Some(NodeId::new(big * 2))
1889 );
1890 }
1891
1892 #[test]
1893 fn remap_node_ids_twice_is_stable() {
1894 let mut mc = MultiCursorState::new_with_cursor(c(1), dom_node(5), 0);
1895 let mut map = BTreeMap::new();
1896 map.insert(NodeId::new(5), NodeId::new(9));
1897 map.insert(NodeId::new(9), NodeId::new(9)); mc.remap_node_ids(DomId::ROOT_ID, &map);
1899 mc.remap_node_ids(DomId::ROOT_ID, &map);
1900 assert_eq!(
1901 mc.node_id.node.into_crate_internal(),
1902 Some(NodeId::new(9))
1903 );
1904 assert_eq!(mc.len(), 1);
1905 }
1906
1907 #[test]
1912 fn selection_pos_helpers_normalize_reversed_ranges() {
1913 let forward = Selection::Range(rng(3, 9));
1914 assert_eq!(selection_start_pos(&forward), c(3));
1915 assert_eq!(selection_end_pos(&forward), c(9));
1916
1917 let backward = Selection::Range(SelectionRange {
1918 start: c(9),
1919 end: c(3),
1920 });
1921 assert_eq!(selection_start_pos(&backward), c(3));
1922 assert_eq!(selection_end_pos(&backward), c(9));
1923
1924 let cursor = Selection::Cursor(c(5));
1925 assert_eq!(selection_start_pos(&cursor), c(5));
1926 assert_eq!(selection_end_pos(&cursor), c(5));
1927 }
1928
1929 #[test]
1930 fn selection_pos_helpers_start_never_exceeds_end() {
1931 let mut seed: u32 = 0xACE1_BEEF;
1932 let mut next = || {
1933 seed = seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
1934 seed
1935 };
1936 let extremes = [0u32, 1, u32::MAX - 1, u32::MAX];
1937 let mut cases: Vec<Selection> = Vec::new();
1938 for a in extremes {
1939 for b in extremes {
1940 cases.push(Selection::Range(SelectionRange {
1941 start: c_full(a, b, CursorAffinity::Trailing),
1942 end: c_full(b, a, CursorAffinity::Leading),
1943 }));
1944 cases.push(Selection::Cursor(c_full(a, b, CursorAffinity::Leading)));
1945 }
1946 }
1947 for _ in 0..200 {
1948 cases.push(Selection::Range(SelectionRange {
1949 start: c(next()),
1950 end: c(next()),
1951 }));
1952 }
1953 for sel in &cases {
1954 assert!(
1955 selection_start_pos(sel) <= selection_end_pos(sel),
1956 "start must never sort after end: {sel:?}"
1957 );
1958 }
1959 }
1960
1961 #[test]
1962 fn selection_pos_helpers_respect_affinity_ordering() {
1963 let sel = Selection::Range(SelectionRange {
1965 start: c_full(0, 4, CursorAffinity::Trailing),
1966 end: c_full(0, 4, CursorAffinity::Leading),
1967 });
1968 assert_eq!(
1969 selection_start_pos(&sel),
1970 c_full(0, 4, CursorAffinity::Leading)
1971 );
1972 assert_eq!(
1973 selection_end_pos(&sel),
1974 c_full(0, 4, CursorAffinity::Trailing)
1975 );
1976 }
1977
1978 fn rect(x: f32, y: f32, w: f32, h: f32) -> LogicalRect {
1983 LogicalRect::new(LogicalPosition::new(x, y), LogicalSize::new(w, h))
1984 }
1985
1986 #[test]
1987 fn new_collapsed_invariants_hold() {
1988 let node = NodeId::new(3);
1989 let sel = TextSelection::new_collapsed(
1990 DomId::ROOT_ID,
1991 node,
1992 c(7),
1993 rect(1.0, 2.0, 3.0, 4.0),
1994 LogicalPosition::new(5.0, 6.0),
1995 );
1996 assert!(sel.is_collapsed());
1997 assert!(sel.is_forward);
1998 assert_eq!(sel.dom_id, DomId::ROOT_ID);
1999 assert_eq!(sel.anchor.ifc_root_node_id, node);
2000 assert_eq!(sel.focus.ifc_root_node_id, node);
2001 assert_eq!(sel.anchor.cursor, c(7));
2002 assert_eq!(sel.focus.cursor, c(7));
2003 assert_eq!(sel.affected_nodes.len(), 1);
2004 assert_eq!(
2006 sel.get_range_for_node(&node),
2007 Some(&SelectionRange {
2008 start: c(7),
2009 end: c(7),
2010 })
2011 );
2012 }
2013
2014 #[test]
2015 fn new_collapsed_with_non_finite_geometry_does_not_panic() {
2016 let node = NodeId::new(0);
2017 let sel = TextSelection::new_collapsed(
2018 DomId::ROOT_ID,
2019 node,
2020 c_full(u32::MAX, u32::MAX, CursorAffinity::Trailing),
2021 rect(f32::NAN, f32::INFINITY, f32::NEG_INFINITY, f32::MAX),
2022 LogicalPosition::new(f32::NAN, f32::NEG_INFINITY),
2023 );
2024 assert!(sel.is_collapsed());
2026 assert!(sel.get_range_for_node(&node).is_some());
2027 assert!(sel.anchor.char_bounds.origin.x.is_nan());
2028 }
2029
2030 #[test]
2031 fn get_range_for_node_returns_none_for_an_unaffected_node() {
2032 let sel = TextSelection::new_collapsed(
2033 DomId::ROOT_ID,
2034 NodeId::new(3),
2035 c(0),
2036 rect(0.0, 0.0, 0.0, 0.0),
2037 LogicalPosition::new(0.0, 0.0),
2038 );
2039 assert!(sel.get_range_for_node(&NodeId::new(4)).is_none());
2040 assert!(sel.get_range_for_node(&NodeId::new(0)).is_none());
2041 assert!(sel.get_range_for_node(&NodeId::new(usize::MAX)).is_none());
2042 }
2043
2044 #[test]
2045 fn get_range_for_node_on_an_empty_map_returns_none() {
2046 let node = NodeId::new(3);
2047 let mut sel = TextSelection::new_collapsed(
2048 DomId::ROOT_ID,
2049 node,
2050 c(0),
2051 rect(0.0, 0.0, 0.0, 0.0),
2052 LogicalPosition::new(0.0, 0.0),
2053 );
2054 sel.affected_nodes.clear();
2055 assert!(sel.get_range_for_node(&node).is_none());
2056 assert!(sel.is_collapsed(), "collapsedness does not depend on the map");
2057 }
2058
2059 #[test]
2060 fn is_collapsed_is_false_when_the_focus_cursor_moves() {
2061 let node = NodeId::new(3);
2062 let mut sel = TextSelection::new_collapsed(
2063 DomId::ROOT_ID,
2064 node,
2065 c(7),
2066 rect(0.0, 0.0, 1.0, 1.0),
2067 LogicalPosition::new(0.0, 0.0),
2068 );
2069 assert!(sel.is_collapsed());
2070 sel.focus.cursor = c(8);
2071 assert!(!sel.is_collapsed());
2072 }
2073
2074 #[test]
2075 fn is_collapsed_is_false_when_the_focus_crosses_into_another_ifc() {
2076 let mut sel = TextSelection::new_collapsed(
2077 DomId::ROOT_ID,
2078 NodeId::new(3),
2079 c(7),
2080 rect(0.0, 0.0, 1.0, 1.0),
2081 LogicalPosition::new(0.0, 0.0),
2082 );
2083 sel.focus.ifc_root_node_id = NodeId::new(4); assert!(
2085 !sel.is_collapsed(),
2086 "same cursor offset in a different IFC is not a collapsed selection"
2087 );
2088 }
2089
2090 #[test]
2091 fn is_collapsed_only_looks_at_cursors_not_at_mouse_position() {
2092 let node = NodeId::new(1);
2093 let mut sel = TextSelection::new_collapsed(
2094 DomId::ROOT_ID,
2095 node,
2096 c(2),
2097 rect(0.0, 0.0, 1.0, 1.0),
2098 LogicalPosition::new(0.0, 0.0),
2099 );
2100 sel.focus.mouse_position = LogicalPosition::new(999.0, -999.0);
2101 assert!(sel.is_collapsed());
2102 }
2103}