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 if last.id == primary || sel.id == primary {
461 new_primary = sel.id;
462 }
463 last.id = sel.id;
465 continue;
466 }
467 }
468 merged.push(sel);
469 }
470 self.selections = merged;
471
472 self.primary_id = new_primary;
474 self.ensure_primary_valid();
475 }
476
477 pub fn move_all_cursors(
483 &mut self,
484 extend_selection: bool,
485 move_fn: impl Fn(&TextCursor) -> TextCursor,
486 ) {
487 for sel in &mut self.selections {
488 match &sel.selection {
489 Selection::Cursor(c) => {
490 let new_cursor = move_fn(c);
491 if extend_selection {
492 if *c != new_cursor {
493 sel.selection = Selection::Range(SelectionRange {
494 start: *c,
495 end: new_cursor,
496 });
497 }
498 } else {
499 sel.selection = Selection::Cursor(new_cursor);
500 }
501 }
502 Selection::Range(r) => {
503 if extend_selection {
504 let new_end = move_fn(&r.end);
505 if r.start == new_end {
506 sel.selection = Selection::Cursor(r.start);
507 } else {
508 sel.selection = Selection::Range(SelectionRange {
509 start: r.start,
510 end: new_end,
511 });
512 }
513 } else {
514 let (lo, hi) = if r.start <= r.end {
523 (r.start, r.end)
524 } else {
525 (r.end, r.start)
526 };
527 let probe = move_fn(&r.end);
528 let collapsed = if probe >= r.end { hi } else { lo };
529 sel.selection = Selection::Cursor(collapsed);
530 }
531 }
532 }
533 }
534 self.merge_overlapping();
535 }
536
537 pub fn remap_node_ids(
541 &mut self,
542 dom_id: DomId,
543 node_id_map: &BTreeMap<NodeId, NodeId>,
544 ) {
545 if self.node_id.dom != dom_id {
546 return;
547 }
548 if let Some(old_node_id) = self.node_id.node.into_crate_internal() {
549 if let Some(&new_node_id) = node_id_map.get(&old_node_id) {
550 self.node_id.node = crate::styled_dom::NodeHierarchyItemId::from_crate_internal(Some(new_node_id));
551 } else {
552 self.selections.clear();
554 }
555 }
556 }
557}
558
559fn selection_start_pos(sel: &Selection) -> TextCursor {
561 match sel {
562 Selection::Cursor(c) => *c,
563 Selection::Range(r) => {
564 if r.start <= r.end { r.start } else { r.end }
565 }
566 }
567}
568
569fn selection_end_pos(sel: &Selection) -> TextCursor {
571 match sel {
572 Selection::Cursor(c) => *c,
573 Selection::Range(r) => {
574 if r.end >= r.start { r.end } else { r.start }
575 }
576 }
577}
578
579#[derive(Debug, Clone, Copy, PartialEq, Eq)]
592pub struct SelectionAnchor {
593 pub ifc_root_node_id: NodeId,
596
597 pub cursor: TextCursor,
599
600 pub char_bounds: LogicalRect,
603
604 pub mouse_position: LogicalPosition,
606}
607
608#[derive(Debug, Clone, Copy, PartialEq, Eq)]
612pub struct SelectionFocus {
613 pub ifc_root_node_id: NodeId,
616
617 pub cursor: TextCursor,
619
620 pub mouse_position: LogicalPosition,
622}
623
624#[derive(Debug, Clone, PartialEq, Eq)]
642pub struct TextSelection {
643 pub dom_id: DomId,
645
646 pub anchor: SelectionAnchor,
648
649 pub focus: SelectionFocus,
651
652 pub affected_nodes: BTreeMap<NodeId, SelectionRange>,
658
659 pub is_forward: bool,
662}
663
664impl TextSelection {
665 #[must_use] pub fn new_collapsed(
667 dom_id: DomId,
668 ifc_root_node_id: NodeId,
669 cursor: TextCursor,
670 char_bounds: LogicalRect,
671 mouse_position: LogicalPosition,
672 ) -> Self {
673 let anchor = SelectionAnchor {
674 ifc_root_node_id,
675 cursor,
676 char_bounds,
677 mouse_position,
678 };
679
680 let focus = SelectionFocus {
681 ifc_root_node_id,
682 cursor,
683 mouse_position,
684 };
685
686 let mut affected_nodes = BTreeMap::new();
688 affected_nodes.insert(ifc_root_node_id, SelectionRange {
689 start: cursor,
690 end: cursor,
691 });
692
693 Self {
694 dom_id,
695 anchor,
696 focus,
697 affected_nodes,
698 is_forward: true, }
700 }
701
702 #[must_use] pub fn is_collapsed(&self) -> bool {
704 self.anchor.ifc_root_node_id == self.focus.ifc_root_node_id
705 && self.anchor.cursor == self.focus.cursor
706 }
707
708 #[must_use] pub fn get_range_for_node(&self, ifc_root_node_id: &NodeId) -> Option<&SelectionRange> {
711 self.affected_nodes.get(ifc_root_node_id)
712 }
713
714}
715
716impl_option!(
717 TextSelection,
718 OptionTextSelection,
719 copy = false,
720 clone = false,
721 [Debug, Clone, PartialEq, Eq]
722);
723
724#[cfg(test)]
725mod audit_tests {
726 use super::*;
727
728 fn cursor(byte: u32) -> TextCursor {
729 TextCursor {
730 cluster_id: GraphemeClusterId { source_run: 0, start_byte_in_run: byte },
731 affinity: CursorAffinity::Leading,
732 }
733 }
734
735 fn state(byte: u32) -> MultiCursorState {
736 MultiCursorState::new_with_cursor(cursor(byte), DomNodeId::ROOT, 0)
737 }
738
739 #[test]
740 fn primary_tracked_by_id_not_vec_position() {
741 let mut mc = state(100);
742 let b = mc.add_cursor(cursor(0));
745 assert_eq!(mc.len(), 2);
746 assert_eq!(mc.get_primary().unwrap().id, b);
749 assert_eq!(mc.get_primary_cursor().unwrap(), cursor(0));
750 }
751
752 #[test]
753 fn merge_preserves_primary() {
754 let mut mc = state(5);
755 let _b = mc.add_cursor(cursor(5)); assert_eq!(mc.len(), 1);
757 let primary = mc.get_primary().unwrap();
759 assert_eq!(primary.id, mc.selections[0].id);
760 }
761
762 #[test]
763 fn removing_primary_repoints_it() {
764 let mut mc = state(0);
765 let b = mc.add_cursor(cursor(10)); assert_eq!(mc.get_primary().unwrap().id, b);
767 assert!(mc.remove_selection(b));
768 let p = mc.get_primary().unwrap();
770 assert!(mc.selections.iter().any(|s| s.id == p.id));
771 }
772}
773
774#[cfg(test)]
775mod autotest_generated {
776 use super::*;
777 use crate::geom::LogicalSize;
778 use crate::styled_dom::NodeHierarchyItemId;
779
780 fn c(byte: u32) -> TextCursor {
786 TextCursor {
787 cluster_id: GraphemeClusterId {
788 source_run: 0,
789 start_byte_in_run: byte,
790 },
791 affinity: CursorAffinity::Leading,
792 }
793 }
794
795 fn c_full(run: u32, byte: u32, affinity: CursorAffinity) -> TextCursor {
797 TextCursor {
798 cluster_id: GraphemeClusterId {
799 source_run: run,
800 start_byte_in_run: byte,
801 },
802 affinity,
803 }
804 }
805
806 fn rng(a: u32, b: u32) -> SelectionRange {
807 SelectionRange {
808 start: c(a),
809 end: c(b),
810 }
811 }
812
813 fn dom_node(index: usize) -> DomNodeId {
814 DomNodeId {
815 dom: DomId::ROOT_ID,
816 node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(index))),
817 }
818 }
819
820 fn state(byte: u32) -> MultiCursorState {
821 MultiCursorState::new_with_cursor(c(byte), DomNodeId::ROOT, 0)
822 }
823
824 fn empty_state() -> MultiCursorState {
827 MultiCursorState {
828 selections: Vec::new(),
829 primary_id: SelectionId::new(),
830 node_id: DomNodeId::ROOT,
831 contenteditable_key: 0,
832 }
833 }
834
835 fn ident(id: SelectionId, sel: Selection) -> IdentifiedSelection {
836 IdentifiedSelection { id, selection: sel }
837 }
838
839 fn assert_sorted_nonoverlapping(mc: &MultiCursorState) {
845 for w in mc.selections.windows(2) {
846 let prev_end = selection_end_pos(&w[0].selection);
847 let next_start = selection_start_pos(&w[1].selection);
848 assert!(
849 next_start > prev_end,
850 "selections must be sorted and non-overlapping after merge: {:?} then {:?}",
851 w[0],
852 w[1]
853 );
854 }
855 }
856
857 fn assert_primary_resolves(mc: &MultiCursorState) {
860 if mc.is_empty() {
861 assert!(mc.get_primary().is_none());
862 assert!(mc.get_primary_cursor().is_none());
863 } else {
864 let p = mc.get_primary().expect("non-empty state must have a primary");
865 assert!(
866 mc.selections.iter().any(|s| s.id == p.id),
867 "get_primary() returned a selection not in the vec"
868 );
869 assert_eq!(
870 mc.primary_id, p.id,
871 "primary_id must name an existing selection (not fall back to last)"
872 );
873 }
874 }
875
876 fn assert_ids_unique(mc: &MultiCursorState) {
878 for (i, a) in mc.selections.iter().enumerate() {
879 for b in mc.selections.iter().skip(i + 1) {
880 assert_ne!(a.id, b.id, "duplicate SelectionId in state");
881 }
882 }
883 }
884
885 #[test]
890 fn selection_id_new_is_unique_and_strictly_increasing() {
891 let mut prev = SelectionId::new();
892 assert!(prev.inner > 0, "counter starts at 1, never the 0 sentinel");
893 for _ in 0..1000 {
894 let next = SelectionId::new();
895 assert!(
898 next.inner > prev.inner,
899 "SelectionId counter must be strictly monotonic"
900 );
901 assert_ne!(next, prev);
902 prev = next;
903 }
904 }
905
906 #[test]
907 fn selection_id_default_mints_a_fresh_id() {
908 let a = SelectionId::default();
910 let b = SelectionId::default();
911 let d = SelectionId::new();
912 assert_ne!(a, b);
913 assert_ne!(b, d);
914 assert!(a.inner > 0 && b.inner > 0);
915 }
916
917 #[test]
922 fn selection_state_add_dedups_identical_cursors() {
923 let mut st = SelectionState {
924 selections: Vec::<Selection>::new().into(),
925 node_id: DomNodeId::ROOT,
926 };
927 for _ in 0..100 {
928 st.add(Selection::Cursor(c(42)));
929 }
930 assert_eq!(st.selections.as_ref().len(), 1);
931 assert_eq!(st.selections.as_ref()[0], Selection::Cursor(c(42)));
932 }
933
934 #[test]
935 fn selection_state_add_sorts_descending_input_ascending() {
936 let mut st = SelectionState {
937 selections: Vec::<Selection>::new().into(),
938 node_id: DomNodeId::ROOT,
939 };
940 for byte in [90u32, 10, 50, 0, 70] {
941 st.add(Selection::Cursor(c(byte)));
942 }
943 let got: Vec<Selection> = st.selections.as_ref().to_vec();
944 assert_eq!(got.len(), 5);
945 let want: Vec<Selection> = [0u32, 10, 50, 70, 90]
946 .iter()
947 .map(|b| Selection::Cursor(c(*b)))
948 .collect();
949 assert_eq!(got, want);
950 }
951
952 #[test]
953 fn selection_state_add_boundary_and_reversed_ranges_do_not_panic() {
954 let mut st = SelectionState {
955 selections: Vec::<Selection>::new().into(),
956 node_id: DomNodeId::ROOT,
957 };
958 st.add(Selection::Cursor(c_full(
961 u32::MAX,
962 u32::MAX,
963 CursorAffinity::Trailing,
964 )));
965 st.add(Selection::Cursor(c_full(0, 0, CursorAffinity::Leading)));
966 st.add(Selection::Range(SelectionRange {
967 start: c_full(u32::MAX, u32::MAX, CursorAffinity::Trailing),
968 end: c_full(0, 0, CursorAffinity::Leading),
969 }));
970 st.add(Selection::Range(rng(0, u32::MAX)));
971 assert_eq!(st.selections.as_ref().len(), 4);
973 let got: Vec<Selection> = st.selections.as_ref().to_vec();
975 let mut sorted = got.clone();
976 sorted.sort_unstable();
977 assert_eq!(got, sorted);
978 }
979
980 #[test]
981 fn selection_state_add_cursor_and_range_at_same_pos_are_distinct() {
982 let mut st = SelectionState {
983 selections: Vec::<Selection>::new().into(),
984 node_id: DomNodeId::ROOT,
985 };
986 st.add(Selection::Range(rng(5, 5)));
987 st.add(Selection::Cursor(c(5)));
988 assert_eq!(st.selections.as_ref().len(), 2);
991 assert_eq!(st.selections.as_ref()[0], Selection::Cursor(c(5)));
993 }
994
995 #[test]
1000 fn new_with_cursor_invariants_hold() {
1001 let node = dom_node(7);
1002 let mc = MultiCursorState::new_with_cursor(c(3), node, 0xDEAD_BEEF);
1003 assert_eq!(mc.len(), 1);
1004 assert!(!mc.is_empty());
1005 assert_eq!(mc.selections.len(), mc.len());
1006 assert_eq!(mc.primary_id, mc.selections[0].id);
1007 assert_eq!(mc.node_id, node);
1008 assert_eq!(mc.contenteditable_key, 0xDEAD_BEEF);
1009 assert_eq!(mc.get_primary_cursor(), Some(c(3)));
1010 assert_eq!(mc.to_selections(), vec![Selection::Cursor(c(3))]);
1011 assert_primary_resolves(&mc);
1012 }
1013
1014 #[test]
1015 fn new_with_cursor_extreme_args_do_not_panic() {
1016 let mc = MultiCursorState::new_with_cursor(
1017 c_full(u32::MAX, u32::MAX, CursorAffinity::Trailing),
1018 dom_node(usize::MAX / 4),
1019 u64::MAX,
1020 );
1021 assert_eq!(mc.len(), 1);
1022 assert_eq!(mc.contenteditable_key, u64::MAX);
1023 assert_eq!(
1024 mc.get_primary_cursor(),
1025 Some(c_full(u32::MAX, u32::MAX, CursorAffinity::Trailing))
1026 );
1027 assert_primary_resolves(&mc);
1028 }
1029
1030 #[test]
1031 fn two_states_get_distinct_ids() {
1032 let a = state(0);
1033 let b = state(0);
1034 assert_ne!(a.primary_id, b.primary_id);
1035 }
1036
1037 #[test]
1042 fn add_cursor_at_same_position_merges_to_one() {
1043 let mut mc = state(5);
1044 let b = mc.add_cursor(c(5));
1045 assert_eq!(mc.len(), 1);
1046 assert_eq!(mc.selections[0].selection, Selection::Cursor(c(5)));
1047 assert_eq!(mc.selections[0].id, b, "merge keeps the newer id");
1048 assert_primary_resolves(&mc);
1049 assert_ids_unique(&mc);
1050 }
1051
1052 #[test]
1053 fn add_cursor_distinct_positions_stay_separate_and_sorted() {
1054 let mut mc = state(30);
1055 let _ = mc.add_cursor(c(10));
1056 let last = mc.add_cursor(c(20));
1057 assert_eq!(mc.len(), 3);
1058 assert_eq!(mc.to_selections(), vec![
1060 Selection::Cursor(c(10)),
1061 Selection::Cursor(c(20)),
1062 Selection::Cursor(c(30)),
1063 ]);
1064 assert_eq!(mc.get_primary().unwrap().id, last);
1067 assert_eq!(mc.get_primary_cursor(), Some(c(20)));
1068 assert_sorted_nonoverlapping(&mc);
1069 assert_primary_resolves(&mc);
1070 assert_ids_unique(&mc);
1071 }
1072
1073 #[test]
1074 fn add_cursor_same_byte_different_affinity_does_not_merge() {
1075 let mut mc = MultiCursorState::new_with_cursor(
1079 c_full(0, 4, CursorAffinity::Leading),
1080 DomNodeId::ROOT,
1081 0,
1082 );
1083 let _ = mc.add_cursor(c_full(0, 4, CursorAffinity::Trailing));
1084 assert_eq!(mc.len(), 2);
1085 assert_sorted_nonoverlapping(&mc);
1086 assert_primary_resolves(&mc);
1087 }
1088
1089 #[test]
1090 fn add_selection_overlapping_ranges_merge_into_union() {
1091 let mut mc = empty_state();
1092 let _ = mc.add_selection(rng(0, 10));
1093 let _ = mc.add_selection(rng(5, 20));
1094 assert_eq!(mc.len(), 1);
1095 assert_eq!(mc.selections[0].selection, Selection::Range(rng(0, 20)));
1096 assert_primary_resolves(&mc);
1097 }
1098
1099 #[test]
1100 fn add_selection_touching_ranges_merge() {
1101 let mut mc = empty_state();
1103 let _ = mc.add_selection(rng(0, 10));
1104 let _ = mc.add_selection(rng(10, 20));
1105 assert_eq!(mc.len(), 1);
1106 assert_eq!(mc.selections[0].selection, Selection::Range(rng(0, 20)));
1107 }
1108
1109 #[test]
1110 fn add_selection_disjoint_ranges_stay_separate() {
1111 let mut mc = empty_state();
1112 let _ = mc.add_selection(rng(0, 10));
1113 let _ = mc.add_selection(rng(11, 20));
1114 assert_eq!(mc.len(), 2);
1115 assert_sorted_nonoverlapping(&mc);
1116 assert_primary_resolves(&mc);
1117 }
1118
1119 #[test]
1120 fn add_selection_reversed_range_is_normalized_for_merging() {
1121 let mut mc = empty_state();
1123 let _ = mc.add_selection(SelectionRange {
1124 start: c(20),
1125 end: c(5),
1126 });
1127 let _ = mc.add_cursor(c(10));
1129 assert_eq!(mc.len(), 1);
1130 assert_eq!(mc.selections[0].selection, Selection::Range(rng(5, 20)));
1132 assert_primary_resolves(&mc);
1133 }
1134
1135 #[test]
1136 fn add_selection_at_u32_max_boundary_does_not_overflow() {
1137 let mut mc = empty_state();
1138 let _ = mc.add_selection(rng(u32::MAX - 1, u32::MAX));
1139 let _ = mc.add_cursor(c(u32::MAX));
1140 assert_eq!(mc.len(), 1);
1141 assert_eq!(
1142 mc.selections[0].selection,
1143 Selection::Range(rng(u32::MAX - 1, u32::MAX))
1144 );
1145 assert_primary_resolves(&mc);
1146 }
1147
1148 #[test]
1149 fn add_cursor_stress_500_distinct_positions() {
1150 let mut mc = state(0);
1151 for i in 1..=500u32 {
1152 let _ = mc.add_cursor(c(i * 2));
1153 }
1154 assert_eq!(mc.len(), 501);
1155 assert_sorted_nonoverlapping(&mc);
1156 assert_primary_resolves(&mc);
1157 assert_ids_unique(&mc);
1158 }
1159
1160 #[test]
1161 fn add_cursor_stress_same_position_never_grows() {
1162 let mut mc = state(9);
1163 for _ in 0..300 {
1164 let _ = mc.add_cursor(c(9));
1165 }
1166 assert_eq!(mc.len(), 1, "identical cursors must always collapse");
1167 assert_primary_resolves(&mc);
1168 }
1169
1170 #[test]
1175 fn remove_selection_unknown_id_returns_false_and_changes_nothing() {
1176 let mut mc = state(1);
1177 let before = mc.clone();
1178 let ghost = SelectionId::new(); assert!(!mc.remove_selection(ghost));
1180 assert_eq!(mc, before);
1181 }
1182
1183 #[test]
1184 fn remove_selection_twice_second_call_returns_false() {
1185 let mut mc = state(0);
1186 let b = mc.add_cursor(c(10));
1187 assert!(mc.remove_selection(b));
1188 assert!(!mc.remove_selection(b));
1189 assert_eq!(mc.len(), 1);
1190 assert_primary_resolves(&mc);
1191 }
1192
1193 #[test]
1194 fn remove_all_selections_leaves_a_safe_empty_state() {
1195 let mut mc = state(0);
1196 let b = mc.add_cursor(c(10));
1197 let a = mc.selections.iter().find(|s| s.id != b).unwrap().id;
1198 assert!(mc.remove_selection(a));
1199 assert!(mc.remove_selection(b));
1200 assert!(mc.is_empty());
1201 assert_eq!(mc.len(), 0);
1202 assert!(mc.get_primary().is_none());
1203 assert!(mc.get_primary_cursor().is_none());
1204 assert!(mc.to_selections().is_empty());
1205 mc.merge_overlapping();
1207 mc.move_all_cursors(true, |cur| *cur);
1208 assert!(mc.is_empty());
1209 }
1210
1211 #[test]
1212 fn remove_primary_from_three_repoints_to_a_survivor() {
1213 let mut mc = state(0);
1214 let _ = mc.add_cursor(c(10));
1215 let p = mc.add_cursor(c(20)); assert_eq!(mc.get_primary().unwrap().id, p);
1217 assert!(mc.remove_selection(p));
1218 assert_eq!(mc.len(), 2);
1219 assert_primary_resolves(&mc);
1220 }
1221
1222 #[test]
1227 fn empty_state_getters_return_none_without_panicking() {
1228 let mut mc = empty_state();
1229 assert!(mc.is_empty());
1230 assert_eq!(mc.len(), 0);
1231 assert!(mc.get_primary().is_none());
1232 assert!(mc.get_primary_mut().is_none());
1233 assert!(mc.get_primary_cursor().is_none());
1234 assert!(mc.to_selections().is_empty());
1235 mc.merge_overlapping(); mc.ensure_primary_valid(); assert!(mc.is_empty());
1238 }
1239
1240 #[test]
1241 fn get_primary_falls_back_to_last_when_primary_id_is_dangling() {
1242 let mut mc = state(0);
1243 let _ = mc.add_cursor(c(10));
1244 mc.primary_id = SelectionId::new(); let p = mc.get_primary().expect("must fall back, not return None");
1246 assert_eq!(p.id, mc.selections.last().unwrap().id);
1247 assert_eq!(mc.get_primary_cursor(), Some(c(10)));
1248 }
1249
1250 #[test]
1251 fn ensure_primary_valid_adopts_last_id_when_dangling() {
1252 let mut mc = state(0);
1253 let _ = mc.add_cursor(c(10));
1254 let dangling = SelectionId::new();
1255 mc.primary_id = dangling;
1256 mc.ensure_primary_valid();
1257 assert_ne!(mc.primary_id, dangling);
1258 assert_eq!(mc.primary_id, mc.selections.last().unwrap().id);
1259 let fixed = mc.primary_id;
1261 mc.ensure_primary_valid();
1262 assert_eq!(mc.primary_id, fixed);
1263 }
1264
1265 #[test]
1266 fn ensure_primary_valid_on_empty_leaves_id_untouched() {
1267 let mut mc = empty_state();
1268 let before = mc.primary_id;
1269 mc.ensure_primary_valid();
1270 assert_eq!(mc.primary_id, before, "nothing to adopt — id must not change");
1271 assert!(mc.get_primary().is_none());
1272 }
1273
1274 #[test]
1275 fn get_primary_cursor_of_a_range_is_its_end_field() {
1276 let mut mc = empty_state();
1277 mc.set_single_range(rng(3, 9));
1278 assert_eq!(mc.get_primary_cursor(), Some(c(9)));
1279
1280 let mut back = empty_state();
1284 back.set_single_range(SelectionRange {
1285 start: c(9),
1286 end: c(3),
1287 });
1288 assert_eq!(back.get_primary_cursor(), Some(c(3)));
1289 }
1290
1291 #[test]
1292 fn get_primary_mut_mutation_is_visible_through_get_primary() {
1293 let mut mc = state(0);
1294 let p = mc.add_cursor(c(50));
1295 {
1296 let prim = mc.get_primary_mut().expect("primary exists");
1297 assert_eq!(prim.id, p);
1298 prim.selection = Selection::Range(rng(50, 60));
1299 }
1300 assert_eq!(
1301 mc.get_primary().unwrap().selection,
1302 Selection::Range(rng(50, 60))
1303 );
1304 assert_eq!(mc.get_primary_cursor(), Some(c(60)));
1305 }
1306
1307 #[test]
1308 fn get_primary_mut_falls_back_to_last_when_dangling() {
1309 let mut mc = state(0);
1310 let _ = mc.add_cursor(c(10));
1311 mc.primary_id = SelectionId::new();
1312 let last_id = mc.selections.last().unwrap().id;
1313 let prim = mc.get_primary_mut().expect("fallback to last");
1314 assert_eq!(prim.id, last_id);
1315 }
1316
1317 #[test]
1318 fn to_selections_matches_the_internal_order_and_len() {
1319 let mut mc = state(30);
1320 let _ = mc.add_cursor(c(10));
1321 let _ = mc.add_selection(rng(15, 20));
1322 let sels = mc.to_selections();
1323 assert_eq!(sels.len(), mc.len());
1324 let inner: Vec<Selection> = mc.selections.iter().map(|s| s.selection).collect();
1325 assert_eq!(sels, inner);
1326 }
1327
1328 #[test]
1329 fn len_and_is_empty_always_agree() {
1330 let mut mc = empty_state();
1331 assert!(mc.is_empty() && mc.len() == 0);
1332 let _ = mc.add_cursor(c(1));
1333 assert!(!mc.is_empty() && mc.len() == 1);
1334 for i in 2..20u32 {
1335 let _ = mc.add_cursor(c(i * 3));
1336 }
1337 assert_eq!(mc.len(), mc.selections.len());
1338 assert_eq!(mc.is_empty(), mc.len() == 0);
1339 assert!(!mc.is_empty());
1340 }
1341
1342 #[test]
1347 fn update_from_edit_result_with_empty_slice_clears_everything() {
1348 let mut mc = state(0);
1349 let _ = mc.add_cursor(c(10));
1350 mc.update_from_edit_result(&[]);
1351 assert!(mc.is_empty());
1352 assert!(mc.get_primary().is_none());
1353 assert!(mc.get_primary_cursor().is_none());
1354 }
1355
1356 #[test]
1357 fn update_from_edit_result_preserves_ids_by_index_and_mints_extras() {
1358 let mut mc = state(0);
1359 let _ = mc.add_cursor(c(10));
1360 let old: Vec<SelectionId> = mc.selections.iter().map(|s| s.id).collect();
1361 assert_eq!(old.len(), 2);
1362
1363 mc.update_from_edit_result(&[
1364 Selection::Cursor(c(1)),
1365 Selection::Cursor(c(2)),
1366 Selection::Cursor(c(3)),
1367 Selection::Range(rng(4, 8)),
1368 ]);
1369 assert_eq!(mc.len(), 4);
1370 assert_eq!(mc.selections[0].id, old[0], "id preserved by index");
1371 assert_eq!(mc.selections[1].id, old[1], "id preserved by index");
1372 assert_ids_unique(&mc);
1373 assert_primary_resolves(&mc);
1374 assert_eq!(mc.selections[3].selection, Selection::Range(rng(4, 8)));
1375 }
1376
1377 #[test]
1378 fn update_from_edit_result_shrinking_keeps_primary_resolvable() {
1379 let mut mc = state(0);
1380 let _ = mc.add_cursor(c(10));
1381 let _ = mc.add_cursor(c(20)); mc.update_from_edit_result(&[Selection::Cursor(c(99))]);
1383 assert_eq!(mc.len(), 1);
1384 assert_primary_resolves(&mc);
1387 assert_eq!(mc.get_primary_cursor(), Some(c(99)));
1388 }
1389
1390 #[test]
1391 fn update_from_edit_result_does_not_merge_overlaps() {
1392 let mut mc = state(0);
1394 mc.update_from_edit_result(&[
1395 Selection::Range(rng(0, 10)),
1396 Selection::Range(rng(5, 15)),
1397 ]);
1398 assert_eq!(mc.len(), 2, "update must NOT merge");
1399 mc.merge_overlapping();
1401 assert_eq!(mc.len(), 1);
1402 assert_eq!(mc.selections[0].selection, Selection::Range(rng(0, 15)));
1403 assert_primary_resolves(&mc);
1404 }
1405
1406 #[test]
1407 fn update_from_edit_result_with_1000_selections() {
1408 let mut mc = state(0);
1409 let big: Vec<Selection> = (0..1000u32).map(|i| Selection::Cursor(c(i * 4))).collect();
1410 mc.update_from_edit_result(&big);
1411 assert_eq!(mc.len(), 1000);
1412 assert_ids_unique(&mc);
1413 assert_primary_resolves(&mc);
1414 assert_eq!(mc.to_selections(), big);
1415 }
1416
1417 #[test]
1422 fn set_single_cursor_collapses_all_selections() {
1423 let mut mc = state(0);
1424 let _ = mc.add_cursor(c(10));
1425 let _ = mc.add_selection(rng(20, 30));
1426 mc.set_single_cursor(c(7));
1427 assert_eq!(mc.len(), 1);
1428 assert_eq!(mc.selections[0].selection, Selection::Cursor(c(7)));
1429 assert_primary_resolves(&mc);
1430 assert_eq!(mc.get_primary_cursor(), Some(c(7)));
1431 }
1432
1433 #[test]
1434 fn set_single_range_collapses_all_selections() {
1435 let mut mc = state(0);
1436 let _ = mc.add_cursor(c(10));
1437 mc.set_single_range(rng(u32::MAX - 2, u32::MAX));
1438 assert_eq!(mc.len(), 1);
1439 assert_eq!(
1440 mc.selections[0].selection,
1441 Selection::Range(rng(u32::MAX - 2, u32::MAX))
1442 );
1443 assert_primary_resolves(&mc);
1444 }
1445
1446 #[test]
1447 fn set_single_cursor_on_empty_state_mints_a_fresh_id() {
1448 let mut mc = empty_state();
1449 let stale = mc.primary_id;
1450 mc.set_single_cursor(c(1));
1451 assert_eq!(mc.len(), 1);
1452 assert_ne!(mc.primary_id, stale, "no last element -> a new id is minted");
1453 assert_primary_resolves(&mc);
1454 }
1455
1456 #[test]
1457 fn set_single_range_on_empty_state_mints_a_fresh_id() {
1458 let mut mc = empty_state();
1459 mc.set_single_range(rng(0, 0));
1460 assert_eq!(mc.len(), 1);
1461 assert_eq!(mc.selections[0].selection, Selection::Range(rng(0, 0)));
1462 assert_primary_resolves(&mc);
1463 }
1464
1465 #[test]
1466 fn set_single_cursor_is_idempotent() {
1467 let mut mc = state(0);
1468 mc.set_single_cursor(c(5));
1469 let first = mc.clone();
1470 mc.set_single_cursor(c(5));
1471 assert_eq!(mc, first, "re-setting the same cursor must reuse the id");
1472 }
1473
1474 #[test]
1479 fn merge_overlapping_on_empty_and_single_is_a_noop() {
1480 let mut e = empty_state();
1481 e.merge_overlapping();
1482 assert!(e.is_empty());
1483
1484 let mut one = state(3);
1485 let before = one.clone();
1486 one.merge_overlapping();
1487 assert_eq!(one, before);
1488 }
1489
1490 #[test]
1491 fn merge_overlapping_collapses_a_whole_chain() {
1492 let mut mc = empty_state();
1493 let ids: Vec<SelectionId> = (0..4).map(|_| SelectionId::new()).collect();
1494 mc.selections = vec![
1495 ident(ids[0], Selection::Range(rng(25, 40))),
1496 ident(ids[1], Selection::Range(rng(0, 10))),
1497 ident(ids[2], Selection::Range(rng(12, 30))),
1498 ident(ids[3], Selection::Range(rng(5, 15))),
1499 ];
1500 mc.primary_id = ids[3];
1501 mc.merge_overlapping();
1502 assert_eq!(mc.len(), 1);
1504 assert_eq!(mc.selections[0].selection, Selection::Range(rng(0, 40)));
1505 assert_sorted_nonoverlapping(&mc);
1506 assert_primary_resolves(&mc);
1507 }
1508
1509 #[test]
1510 fn merge_overlapping_keeps_disjoint_selections_and_sorts_them() {
1511 let mut mc = empty_state();
1512 let ids: Vec<SelectionId> = (0..3).map(|_| SelectionId::new()).collect();
1513 mc.selections = vec![
1514 ident(ids[0], Selection::Cursor(c(100))),
1515 ident(ids[1], Selection::Range(rng(0, 5))),
1516 ident(ids[2], Selection::Cursor(c(50))),
1517 ];
1518 mc.primary_id = ids[0];
1519 mc.merge_overlapping();
1520 assert_eq!(mc.len(), 3);
1521 assert_eq!(mc.to_selections(), vec![
1522 Selection::Range(rng(0, 5)),
1523 Selection::Cursor(c(50)),
1524 Selection::Cursor(c(100)),
1525 ]);
1526 assert_sorted_nonoverlapping(&mc);
1527 assert_eq!(mc.primary_id, ids[0]);
1529 assert_eq!(mc.get_primary_cursor(), Some(c(100)));
1530 }
1531
1532 #[test]
1533 fn merge_overlapping_zero_width_merge_yields_a_cursor_not_a_range() {
1534 let mut mc = empty_state();
1535 let ids: Vec<SelectionId> = (0..2).map(|_| SelectionId::new()).collect();
1536 mc.selections = vec![
1537 ident(ids[0], Selection::Cursor(c(8))),
1538 ident(ids[1], Selection::Range(rng(8, 8))),
1539 ];
1540 mc.primary_id = ids[1];
1541 mc.merge_overlapping();
1542 assert_eq!(mc.len(), 1);
1543 assert_eq!(mc.selections[0].selection, Selection::Cursor(c(8)));
1545 assert_primary_resolves(&mc);
1546 }
1547
1548 #[test]
1549 fn merge_overlapping_is_idempotent() {
1550 let mut mc = empty_state();
1551 let ids: Vec<SelectionId> = (0..5).map(|_| SelectionId::new()).collect();
1552 mc.selections = vec![
1553 ident(ids[0], Selection::Range(rng(0, 10))),
1554 ident(ids[1], Selection::Cursor(c(5))),
1555 ident(ids[2], Selection::Range(rng(30, 20))), ident(ids[3], Selection::Cursor(c(100))),
1557 ident(ids[4], Selection::Range(rng(99, 101))),
1558 ];
1559 mc.primary_id = ids[2];
1560 mc.merge_overlapping();
1561 let once = mc.clone();
1562 mc.merge_overlapping();
1563 assert_eq!(mc, once, "merge_overlapping must be a fixed point");
1564 assert_sorted_nonoverlapping(&mc);
1565 assert_primary_resolves(&mc);
1566 }
1567
1568 #[test]
1569 fn merge_overlapping_adversarial_200_selections_keeps_invariants() {
1570 let mut mc = empty_state();
1571 let mut seed: u32 = 0x1234_5678;
1572 let mut next = || {
1573 seed = seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
1574 seed
1575 };
1576 let mut sels = Vec::new();
1577 for i in 0..200u32 {
1578 let a = next() % 1000;
1579 let b = next() % 1000;
1580 let sel = match i % 4 {
1581 0 => Selection::Cursor(c(a)),
1582 1 => Selection::Range(SelectionRange {
1583 start: c(a),
1584 end: c(b),
1585 }), 2 => Selection::Range(rng(a.min(b), a.max(b))),
1587 _ => Selection::Cursor(c_full(
1588 0,
1589 a,
1590 if b % 2 == 0 {
1591 CursorAffinity::Leading
1592 } else {
1593 CursorAffinity::Trailing
1594 },
1595 )),
1596 };
1597 sels.push(ident(SelectionId::new(), sel));
1598 }
1599 sels.push(ident(SelectionId::new(), Selection::Cursor(c(0))));
1601 sels.push(ident(SelectionId::new(), Selection::Cursor(c(u32::MAX))));
1602 sels.push(ident(
1603 SelectionId::new(),
1604 Selection::Range(rng(u32::MAX - 1, u32::MAX)),
1605 ));
1606 mc.primary_id = sels[7].id;
1607 mc.selections = sels;
1608
1609 mc.merge_overlapping();
1610
1611 assert!(!mc.is_empty());
1612 assert!(mc.len() <= 203);
1613 assert_sorted_nonoverlapping(&mc);
1614 assert_primary_resolves(&mc);
1615 assert_ids_unique(&mc);
1616 }
1617
1618 #[test]
1619 fn merge_overlapping_primary_inside_a_chain_still_resolves() {
1620 let mut mc = empty_state();
1623 let ids: Vec<SelectionId> = (0..4).map(|_| SelectionId::new()).collect();
1624 mc.selections = vec![
1625 ident(ids[0], Selection::Cursor(c(0))),
1626 ident(ids[1], Selection::Cursor(c(0))),
1627 ident(ids[2], Selection::Cursor(c(0))),
1628 ident(ids[3], Selection::Cursor(c(100))),
1629 ];
1630 mc.primary_id = ids[0];
1631 mc.merge_overlapping();
1632
1633 assert_eq!(mc.len(), 2);
1634 assert!(
1636 mc.selections.iter().any(|s| s.id == mc.primary_id),
1637 "primary_id must name a surviving selection"
1638 );
1639 assert!(mc.get_primary().is_some());
1640 }
1641
1642 #[test]
1643 #[ignore = "known bug: 3+-link merge chain loses the primary; see report"]
1644 fn merge_overlapping_primary_should_follow_its_merge_chain() {
1645 let mut mc = empty_state();
1653 let ids: Vec<SelectionId> = (0..4).map(|_| SelectionId::new()).collect();
1654 mc.selections = vec![
1655 ident(ids[0], Selection::Cursor(c(0))),
1656 ident(ids[1], Selection::Cursor(c(0))),
1657 ident(ids[2], Selection::Cursor(c(0))),
1658 ident(ids[3], Selection::Cursor(c(100))),
1659 ];
1660 mc.primary_id = ids[0];
1661 mc.merge_overlapping();
1662 assert_eq!(
1663 mc.get_primary_cursor(),
1664 Some(c(0)),
1665 "primary jumped to an unrelated selection after the merge"
1666 );
1667 }
1668
1669 #[test]
1674 fn move_all_cursors_identity_leaves_positions_unchanged() {
1675 let mut mc = state(0);
1676 let _ = mc.add_cursor(c(10));
1677 let _ = mc.add_cursor(c(20));
1678 let before = mc.to_selections();
1679 mc.move_all_cursors(false, |cur| *cur);
1680 assert_eq!(mc.to_selections(), before);
1681 assert_eq!(mc.len(), 3);
1682 assert_primary_resolves(&mc);
1683 }
1684
1685 #[test]
1686 fn move_all_cursors_extend_with_no_movement_keeps_a_cursor() {
1687 let mut mc = state(4);
1690 mc.move_all_cursors(true, |cur| *cur);
1691 assert_eq!(mc.len(), 1);
1692 assert_eq!(mc.selections[0].selection, Selection::Cursor(c(4)));
1693 }
1694
1695 #[test]
1696 fn move_all_cursors_extend_turns_a_cursor_into_a_range() {
1697 let mut mc = state(10);
1698 mc.move_all_cursors(true, |cur| c(cur.cluster_id.start_byte_in_run + 5));
1699 assert_eq!(mc.len(), 1);
1700 assert_eq!(mc.selections[0].selection, Selection::Range(rng(10, 15)));
1701 assert_eq!(mc.get_primary_cursor(), Some(c(15)));
1703 }
1704
1705 #[test]
1706 fn move_all_cursors_bare_forward_arrow_collapses_range_to_max_boundary() {
1707 let mut mc = empty_state();
1708 mc.set_single_range(rng(3, 9));
1709 mc.move_all_cursors(false, |cur| c(cur.cluster_id.start_byte_in_run + 1));
1710 assert_eq!(mc.len(), 1);
1711 assert_eq!(mc.selections[0].selection, Selection::Cursor(c(9)));
1713 }
1714
1715 #[test]
1716 fn move_all_cursors_bare_backward_arrow_collapses_range_to_min_boundary() {
1717 let mut mc = empty_state();
1718 mc.set_single_range(rng(3, 9));
1719 mc.move_all_cursors(false, |cur| {
1720 c(cur.cluster_id.start_byte_in_run.saturating_sub(1))
1721 });
1722 assert_eq!(mc.len(), 1);
1723 assert_eq!(mc.selections[0].selection, Selection::Cursor(c(3)));
1724 }
1725
1726 #[test]
1727 fn move_all_cursors_collapses_a_backwards_range_by_direction_not_field_order() {
1728 let mut mc = empty_state();
1731 mc.set_single_range(SelectionRange {
1732 start: c(9),
1733 end: c(3),
1734 });
1735 mc.move_all_cursors(false, |cur| c(cur.cluster_id.start_byte_in_run + 1));
1736 assert_eq!(mc.selections[0].selection, Selection::Cursor(c(9)));
1737
1738 let mut back = empty_state();
1739 back.set_single_range(SelectionRange {
1740 start: c(9),
1741 end: c(3),
1742 });
1743 back.move_all_cursors(false, |cur| {
1744 c(cur.cluster_id.start_byte_in_run.saturating_sub(1))
1745 });
1746 assert_eq!(back.selections[0].selection, Selection::Cursor(c(3)));
1747 }
1748
1749 #[test]
1750 fn move_all_cursors_extend_back_onto_the_anchor_collapses_to_a_cursor() {
1751 let mut mc = empty_state();
1752 mc.set_single_range(rng(3, 4));
1753 mc.move_all_cursors(true, |_| c(3));
1755 assert_eq!(mc.len(), 1);
1756 assert_eq!(mc.selections[0].selection, Selection::Cursor(c(3)));
1757 }
1758
1759 #[test]
1760 fn move_all_cursors_constant_move_fn_merges_everything_into_one() {
1761 let mut mc = state(0);
1762 for i in 1..5u32 {
1763 let _ = mc.add_cursor(c(i * 10));
1764 }
1765 assert_eq!(mc.len(), 5);
1766 mc.move_all_cursors(false, |_| c(7));
1767 assert_eq!(mc.len(), 1, "colliding cursors must be merged afterwards");
1768 assert_eq!(mc.selections[0].selection, Selection::Cursor(c(7)));
1769 assert_primary_resolves(&mc);
1770 assert_sorted_nonoverlapping(&mc);
1771 }
1772
1773 #[test]
1774 fn move_all_cursors_saturating_at_u32_max_does_not_overflow() {
1775 let mut mc = empty_state();
1776 let ids: Vec<SelectionId> = (0..2).map(|_| SelectionId::new()).collect();
1777 mc.selections = vec![
1778 ident(ids[0], Selection::Cursor(c(u32::MAX - 1))),
1779 ident(ids[1], Selection::Cursor(c(u32::MAX))),
1780 ];
1781 mc.primary_id = ids[1];
1782 mc.move_all_cursors(false, |cur| {
1783 c(cur.cluster_id.start_byte_in_run.saturating_add(1))
1784 });
1785 assert_eq!(mc.len(), 1);
1787 assert_eq!(mc.selections[0].selection, Selection::Cursor(c(u32::MAX)));
1788 assert_primary_resolves(&mc);
1789 }
1790
1791 #[test]
1792 fn move_all_cursors_on_empty_state_does_not_panic() {
1793 let mut mc = empty_state();
1794 mc.move_all_cursors(false, |cur| *cur);
1795 mc.move_all_cursors(true, |_| c(u32::MAX));
1796 assert!(mc.is_empty());
1797 }
1798
1799 #[test]
1800 fn move_all_cursors_stress_keeps_invariants() {
1801 let mut mc = state(0);
1802 for i in 1..100u32 {
1803 let _ = mc.add_cursor(c(i * 5));
1804 }
1805 for _ in 0..10 {
1806 mc.move_all_cursors(false, |cur| {
1807 c(cur.cluster_id.start_byte_in_run % 7)
1809 });
1810 assert_sorted_nonoverlapping(&mc);
1811 assert_primary_resolves(&mc);
1812 assert_ids_unique(&mc);
1813 }
1814 assert!(mc.len() <= 7);
1815 }
1816
1817 #[test]
1822 fn remap_node_ids_for_a_different_dom_is_a_noop() {
1823 let mut mc = MultiCursorState::new_with_cursor(c(1), dom_node(5), 0);
1824 mc.node_id.dom = DomId { inner: 7 };
1825 let before = mc.clone();
1826 let mut map = BTreeMap::new();
1827 map.insert(NodeId::new(5), NodeId::new(9));
1828 mc.remap_node_ids(DomId::ROOT_ID, &map);
1829 assert_eq!(mc, before, "a foreign DomId must not touch this state");
1830 }
1831
1832 #[test]
1833 fn remap_node_ids_rewrites_a_surviving_node() {
1834 let mut mc = MultiCursorState::new_with_cursor(c(1), dom_node(5), 0);
1835 let mut map = BTreeMap::new();
1836 map.insert(NodeId::new(5), NodeId::new(9));
1837 mc.remap_node_ids(DomId::ROOT_ID, &map);
1838 assert_eq!(
1839 mc.node_id.node.into_crate_internal(),
1840 Some(NodeId::new(9))
1841 );
1842 assert_eq!(mc.len(), 1, "selections survive a successful remap");
1843 assert_primary_resolves(&mc);
1844 }
1845
1846 #[test]
1847 fn remap_node_ids_clears_selections_when_the_node_was_removed() {
1848 let mut mc = MultiCursorState::new_with_cursor(c(1), dom_node(5), 0);
1849 let _ = mc.add_cursor(c(20));
1850 let map: BTreeMap<NodeId, NodeId> = BTreeMap::new(); mc.remap_node_ids(DomId::ROOT_ID, &map);
1852 assert!(mc.is_empty(), "a removed node must drop its selections");
1853 assert!(mc.get_primary().is_none());
1854 assert_eq!(
1856 mc.node_id.node.into_crate_internal(),
1857 Some(NodeId::new(5))
1858 );
1859 }
1860
1861 #[test]
1862 fn remap_node_ids_with_a_none_node_is_a_noop() {
1863 let mut mc = state(3);
1866 let map: BTreeMap<NodeId, NodeId> = BTreeMap::new();
1867 mc.remap_node_ids(DomId::ROOT_ID, &map);
1868 assert_eq!(mc.len(), 1);
1869 assert_eq!(mc.node_id.node, NodeHierarchyItemId::NONE);
1870 assert_primary_resolves(&mc);
1871 }
1872
1873 #[test]
1874 fn remap_node_ids_handles_large_node_indices() {
1875 let big = 1_000_000usize;
1876 let mut mc = MultiCursorState::new_with_cursor(c(1), dom_node(big), 0);
1877 let mut map = BTreeMap::new();
1878 map.insert(NodeId::new(big), NodeId::new(big * 2));
1879 mc.remap_node_ids(DomId::ROOT_ID, &map);
1880 assert_eq!(
1881 mc.node_id.node.into_crate_internal(),
1882 Some(NodeId::new(big * 2))
1883 );
1884 }
1885
1886 #[test]
1887 fn remap_node_ids_twice_is_stable() {
1888 let mut mc = MultiCursorState::new_with_cursor(c(1), dom_node(5), 0);
1889 let mut map = BTreeMap::new();
1890 map.insert(NodeId::new(5), NodeId::new(9));
1891 map.insert(NodeId::new(9), NodeId::new(9)); mc.remap_node_ids(DomId::ROOT_ID, &map);
1893 mc.remap_node_ids(DomId::ROOT_ID, &map);
1894 assert_eq!(
1895 mc.node_id.node.into_crate_internal(),
1896 Some(NodeId::new(9))
1897 );
1898 assert_eq!(mc.len(), 1);
1899 }
1900
1901 #[test]
1906 fn selection_pos_helpers_normalize_reversed_ranges() {
1907 let forward = Selection::Range(rng(3, 9));
1908 assert_eq!(selection_start_pos(&forward), c(3));
1909 assert_eq!(selection_end_pos(&forward), c(9));
1910
1911 let backward = Selection::Range(SelectionRange {
1912 start: c(9),
1913 end: c(3),
1914 });
1915 assert_eq!(selection_start_pos(&backward), c(3));
1916 assert_eq!(selection_end_pos(&backward), c(9));
1917
1918 let cursor = Selection::Cursor(c(5));
1919 assert_eq!(selection_start_pos(&cursor), c(5));
1920 assert_eq!(selection_end_pos(&cursor), c(5));
1921 }
1922
1923 #[test]
1924 fn selection_pos_helpers_start_never_exceeds_end() {
1925 let mut seed: u32 = 0xACE1_BEEF;
1926 let mut next = || {
1927 seed = seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
1928 seed
1929 };
1930 let extremes = [0u32, 1, u32::MAX - 1, u32::MAX];
1931 let mut cases: Vec<Selection> = Vec::new();
1932 for a in extremes {
1933 for b in extremes {
1934 cases.push(Selection::Range(SelectionRange {
1935 start: c_full(a, b, CursorAffinity::Trailing),
1936 end: c_full(b, a, CursorAffinity::Leading),
1937 }));
1938 cases.push(Selection::Cursor(c_full(a, b, CursorAffinity::Leading)));
1939 }
1940 }
1941 for _ in 0..200 {
1942 cases.push(Selection::Range(SelectionRange {
1943 start: c(next()),
1944 end: c(next()),
1945 }));
1946 }
1947 for sel in &cases {
1948 assert!(
1949 selection_start_pos(sel) <= selection_end_pos(sel),
1950 "start must never sort after end: {sel:?}"
1951 );
1952 }
1953 }
1954
1955 #[test]
1956 fn selection_pos_helpers_respect_affinity_ordering() {
1957 let sel = Selection::Range(SelectionRange {
1959 start: c_full(0, 4, CursorAffinity::Trailing),
1960 end: c_full(0, 4, CursorAffinity::Leading),
1961 });
1962 assert_eq!(
1963 selection_start_pos(&sel),
1964 c_full(0, 4, CursorAffinity::Leading)
1965 );
1966 assert_eq!(
1967 selection_end_pos(&sel),
1968 c_full(0, 4, CursorAffinity::Trailing)
1969 );
1970 }
1971
1972 fn rect(x: f32, y: f32, w: f32, h: f32) -> LogicalRect {
1977 LogicalRect::new(LogicalPosition::new(x, y), LogicalSize::new(w, h))
1978 }
1979
1980 #[test]
1981 fn new_collapsed_invariants_hold() {
1982 let node = NodeId::new(3);
1983 let sel = TextSelection::new_collapsed(
1984 DomId::ROOT_ID,
1985 node,
1986 c(7),
1987 rect(1.0, 2.0, 3.0, 4.0),
1988 LogicalPosition::new(5.0, 6.0),
1989 );
1990 assert!(sel.is_collapsed());
1991 assert!(sel.is_forward);
1992 assert_eq!(sel.dom_id, DomId::ROOT_ID);
1993 assert_eq!(sel.anchor.ifc_root_node_id, node);
1994 assert_eq!(sel.focus.ifc_root_node_id, node);
1995 assert_eq!(sel.anchor.cursor, c(7));
1996 assert_eq!(sel.focus.cursor, c(7));
1997 assert_eq!(sel.affected_nodes.len(), 1);
1998 assert_eq!(
2000 sel.get_range_for_node(&node),
2001 Some(&SelectionRange {
2002 start: c(7),
2003 end: c(7),
2004 })
2005 );
2006 }
2007
2008 #[test]
2009 fn new_collapsed_with_non_finite_geometry_does_not_panic() {
2010 let node = NodeId::new(0);
2011 let sel = TextSelection::new_collapsed(
2012 DomId::ROOT_ID,
2013 node,
2014 c_full(u32::MAX, u32::MAX, CursorAffinity::Trailing),
2015 rect(f32::NAN, f32::INFINITY, f32::NEG_INFINITY, f32::MAX),
2016 LogicalPosition::new(f32::NAN, f32::NEG_INFINITY),
2017 );
2018 assert!(sel.is_collapsed());
2020 assert!(sel.get_range_for_node(&node).is_some());
2021 assert!(sel.anchor.char_bounds.origin.x.is_nan());
2022 }
2023
2024 #[test]
2025 fn get_range_for_node_returns_none_for_an_unaffected_node() {
2026 let sel = TextSelection::new_collapsed(
2027 DomId::ROOT_ID,
2028 NodeId::new(3),
2029 c(0),
2030 rect(0.0, 0.0, 0.0, 0.0),
2031 LogicalPosition::new(0.0, 0.0),
2032 );
2033 assert!(sel.get_range_for_node(&NodeId::new(4)).is_none());
2034 assert!(sel.get_range_for_node(&NodeId::new(0)).is_none());
2035 assert!(sel.get_range_for_node(&NodeId::new(usize::MAX)).is_none());
2036 }
2037
2038 #[test]
2039 fn get_range_for_node_on_an_empty_map_returns_none() {
2040 let node = NodeId::new(3);
2041 let mut sel = TextSelection::new_collapsed(
2042 DomId::ROOT_ID,
2043 node,
2044 c(0),
2045 rect(0.0, 0.0, 0.0, 0.0),
2046 LogicalPosition::new(0.0, 0.0),
2047 );
2048 sel.affected_nodes.clear();
2049 assert!(sel.get_range_for_node(&node).is_none());
2050 assert!(sel.is_collapsed(), "collapsedness does not depend on the map");
2051 }
2052
2053 #[test]
2054 fn is_collapsed_is_false_when_the_focus_cursor_moves() {
2055 let node = NodeId::new(3);
2056 let mut sel = TextSelection::new_collapsed(
2057 DomId::ROOT_ID,
2058 node,
2059 c(7),
2060 rect(0.0, 0.0, 1.0, 1.0),
2061 LogicalPosition::new(0.0, 0.0),
2062 );
2063 assert!(sel.is_collapsed());
2064 sel.focus.cursor = c(8);
2065 assert!(!sel.is_collapsed());
2066 }
2067
2068 #[test]
2069 fn is_collapsed_is_false_when_the_focus_crosses_into_another_ifc() {
2070 let mut sel = TextSelection::new_collapsed(
2071 DomId::ROOT_ID,
2072 NodeId::new(3),
2073 c(7),
2074 rect(0.0, 0.0, 1.0, 1.0),
2075 LogicalPosition::new(0.0, 0.0),
2076 );
2077 sel.focus.ifc_root_node_id = NodeId::new(4); assert!(
2079 !sel.is_collapsed(),
2080 "same cursor offset in a different IFC is not a collapsed selection"
2081 );
2082 }
2083
2084 #[test]
2085 fn is_collapsed_only_looks_at_cursors_not_at_mouse_position() {
2086 let node = NodeId::new(1);
2087 let mut sel = TextSelection::new_collapsed(
2088 DomId::ROOT_ID,
2089 node,
2090 c(2),
2091 rect(0.0, 0.0, 1.0, 1.0),
2092 LogicalPosition::new(0.0, 0.0),
2093 );
2094 sel.focus.mouse_position = LogicalPosition::new(999.0, -999.0);
2095 assert!(sel.is_collapsed());
2096 }
2097}