use std::cmp::Ordering;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Cursor {
pub position: (usize, usize),
pub anchor: Option<(usize, usize)>,
}
impl Cursor {
pub fn new(position: (usize, usize)) -> Self {
Self { position, anchor: None }
}
pub fn has_selection(&self) -> bool {
match self.anchor {
Some(a) => a != self.position,
None => false,
}
}
pub fn clear_selection(&mut self) {
self.anchor = None;
}
pub fn set_anchor(&mut self) {
self.anchor = Some(self.position);
}
pub fn selection_range(&self) -> Option<((usize, usize), (usize, usize))> {
let anchor = self.anchor?;
if anchor == self.position {
return None;
}
Some(normalise(anchor, self.position))
}
}
#[derive(Debug, Clone)]
pub struct CursorSet {
cursors: Vec<Cursor>,
primary_idx: usize,
}
impl CursorSet {
pub fn new(pos: (usize, usize)) -> Self {
Self { cursors: vec![Cursor::new(pos)], primary_idx: 0 }
}
pub fn primary(&self) -> &Cursor {
&self.cursors[self.primary_idx]
}
pub fn primary_mut(&mut self) -> &mut Cursor {
&mut self.cursors[self.primary_idx]
}
pub fn primary_position(&self) -> (usize, usize) {
self.cursors[self.primary_idx].position
}
pub fn len(&self) -> usize {
self.cursors.len()
}
pub fn is_multi(&self) -> bool {
self.cursors.len() > 1
}
pub fn iter(&self) -> impl Iterator<Item = &Cursor> {
self.cursors.iter()
}
pub fn as_slice(&self) -> &[Cursor] {
&self.cursors
}
pub fn as_mut_slice(&mut self) -> &mut [Cursor] {
&mut self.cursors
}
pub fn add_cursor(&mut self, pos: (usize, usize)) {
let cursor = Cursor::new(pos);
self.cursors.push(cursor);
self.primary_idx = self.cursors.len() - 1;
self.sort_and_merge();
}
pub fn add_cursor_with_selection(&mut self, cursor: Cursor) {
self.cursors.push(cursor);
self.primary_idx = self.cursors.len() - 1;
self.sort_and_merge();
}
pub fn remove_all_but_primary(&mut self) {
let primary = self.cursors[self.primary_idx].clone();
self.cursors.clear();
self.cursors.push(primary);
self.primary_idx = 0;
}
pub fn set_single(&mut self, pos: (usize, usize)) {
self.cursors.clear();
self.cursors.push(Cursor::new(pos));
self.primary_idx = 0;
}
pub fn clear_all_selections(&mut self) {
for c in &mut self.cursors {
c.clear_selection();
}
}
pub fn sort_and_merge(&mut self) {
if self.cursors.len() <= 1 {
return;
}
let primary_orig = self.primary_idx;
let mut tagged: Vec<(usize, Cursor)> =
self.cursors.drain(..).enumerate().collect();
tagged.sort_by(|a, b| {
let a_min = min_pos(&a.1);
let b_min = min_pos(&b.1);
cmp_pos(a_min, b_min)
});
let mut merged: Vec<(usize, Cursor)> = Vec::with_capacity(tagged.len());
for entry in tagged {
if let Some(last) = merged.last_mut()
&& cursors_overlap(&last.1, &entry.1)
{
let keep_new = entry.0 == primary_orig;
merge_into(&mut last.1, &entry.1);
if keep_new {
last.0 = entry.0;
last.1.position = entry.1.position;
}
continue;
}
merged.push(entry);
}
self.primary_idx = 0;
self.cursors.clear();
for (i, (orig_idx, cursor)) in merged.into_iter().enumerate() {
if orig_idx == primary_orig {
self.primary_idx = i;
}
self.cursors.push(cursor);
}
}
}
fn cmp_pos(a: (usize, usize), b: (usize, usize)) -> Ordering {
a.0.cmp(&b.0).then(a.1.cmp(&b.1))
}
fn normalise(
a: (usize, usize),
b: (usize, usize),
) -> ((usize, usize), (usize, usize)) {
if cmp_pos(a, b) == Ordering::Greater { (b, a) } else { (a, b) }
}
fn min_pos(c: &Cursor) -> (usize, usize) {
match c.anchor {
Some(a) => {
if cmp_pos(a, c.position) == Ordering::Less {
a
} else {
c.position
}
}
None => c.position,
}
}
fn max_pos(c: &Cursor) -> (usize, usize) {
match c.anchor {
Some(a) => {
if cmp_pos(a, c.position) == Ordering::Greater {
a
} else {
c.position
}
}
None => c.position,
}
}
fn cursors_overlap(a: &Cursor, b: &Cursor) -> bool {
let a_max = max_pos(a);
let b_min = min_pos(b);
cmp_pos(a_max, b_min) != Ordering::Less
}
fn merge_into(dst: &mut Cursor, src: &Cursor) {
let combined_min = {
let a = min_pos(dst);
let b = min_pos(src);
if cmp_pos(a, b) == Ordering::Less { a } else { b }
};
let combined_max = {
let a = max_pos(dst);
let b = max_pos(src);
if cmp_pos(a, b) == Ordering::Greater { a } else { b }
};
if dst.has_selection() || src.has_selection() {
dst.anchor = Some(combined_min);
dst.position = combined_max;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_single_cursor() {
let cs = CursorSet::new((3, 5));
assert_eq!(cs.len(), 1);
assert!(!cs.is_multi());
assert_eq!(cs.primary_position(), (3, 5));
}
#[test]
fn test_add_cursor() {
let mut cs = CursorSet::new((0, 0));
cs.add_cursor((2, 3));
assert_eq!(cs.len(), 2);
assert!(cs.is_multi());
assert_eq!(cs.primary_position(), (2, 3));
}
#[test]
fn test_add_duplicate_cursor_merges() {
let mut cs = CursorSet::new((1, 5));
cs.add_cursor((1, 5));
assert_eq!(cs.len(), 1);
}
#[test]
fn test_sort_order() {
let mut cs = CursorSet::new((5, 0));
cs.add_cursor((1, 0));
cs.add_cursor((3, 0));
let positions: Vec<_> = cs.iter().map(|c| c.position).collect();
assert_eq!(positions, vec![(1, 0), (3, 0), (5, 0)]);
}
#[test]
fn test_remove_all_but_primary() {
let mut cs = CursorSet::new((0, 0));
cs.add_cursor((1, 0));
cs.add_cursor((2, 0));
assert_eq!(cs.len(), 3);
cs.remove_all_but_primary();
assert_eq!(cs.len(), 1);
assert_eq!(cs.primary_position(), (2, 0));
}
#[test]
fn test_cursor_selection_range() {
let mut c = Cursor::new((1, 5));
assert!(c.selection_range().is_none());
c.anchor = Some((1, 2));
assert_eq!(c.selection_range(), Some(((1, 2), (1, 5))));
}
#[test]
fn test_cursor_selection_range_reversed() {
let mut c = Cursor::new((1, 2));
c.anchor = Some((1, 8));
assert_eq!(c.selection_range(), Some(((1, 2), (1, 8))));
}
#[test]
fn test_overlapping_selections_merge() {
let mut cs = CursorSet::new((0, 0));
cs.primary_mut().anchor = Some((0, 0));
cs.primary_mut().position = (0, 5);
let mut c2 = Cursor::new((0, 8));
c2.anchor = Some((0, 3));
cs.add_cursor_with_selection(c2);
assert_eq!(cs.len(), 1);
assert_eq!(cs.primary().selection_range(), Some(((0, 0), (0, 8))));
}
#[test]
fn test_clear_all_selections() {
let mut cs = CursorSet::new((0, 0));
cs.primary_mut().anchor = Some((0, 5));
let mut c2 = Cursor::new((1, 0));
c2.anchor = Some((1, 3));
cs.add_cursor_with_selection(c2);
cs.clear_all_selections();
for c in cs.iter() {
assert!(!c.has_selection());
}
}
#[test]
fn test_set_single() {
let mut cs = CursorSet::new((0, 0));
cs.add_cursor((1, 0));
cs.add_cursor((2, 0));
cs.set_single((5, 5));
assert_eq!(cs.len(), 1);
assert_eq!(cs.primary_position(), (5, 5));
}
#[test]
fn test_primary_tracked_through_sort() {
let mut cs = CursorSet::new((5, 0)); cs.add_cursor((1, 0)); assert_eq!(cs.primary_position(), (1, 0));
assert_eq!(cs.primary().position, (1, 0));
}
}