use std::ops::Range;
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Selection {
ranges: Vec<(usize, usize)>,
anchor: Option<usize>,
cursor: Option<usize>,
}
impl Selection {
pub fn new() -> Self {
Self::default()
}
pub fn contains(&self, row: usize) -> bool {
let i = self.ranges.partition_point(|r| r.1 <= row);
self.ranges.get(i).is_some_and(|r| r.0 <= row)
}
pub fn len(&self) -> usize {
self.ranges.iter().fold(0usize, |n, r| n.saturating_add(r.1.saturating_sub(r.0)))
}
pub fn is_empty(&self) -> bool {
self.ranges.is_empty()
}
pub fn cursor(&self) -> Option<usize> {
self.cursor
}
pub fn anchor(&self) -> Option<usize> {
self.anchor
}
pub fn ranges(&self) -> impl Iterator<Item = Range<usize>> + '_ {
self.ranges.iter().map(|r| r.0..r.1)
}
pub fn iter(&self) -> impl Iterator<Item = usize> + '_ {
self.ranges().flatten()
}
pub fn select_only(&mut self, row: usize) {
self.ranges.clear();
self.ranges.push((row, row.saturating_add(1)));
self.anchor = Some(row);
self.cursor = Some(row);
}
pub fn toggle(&mut self, row: usize) {
if self.contains(row) {
self.remove(row..row.saturating_add(1));
} else {
self.insert(row..row.saturating_add(1));
}
self.anchor = Some(row);
self.cursor = Some(row);
}
pub fn extend_to(&mut self, row: usize) {
let anchor = self.anchor.unwrap_or(row);
let (lo, hi) = if anchor <= row { (anchor, row) } else { (row, anchor) };
self.ranges.clear();
self.ranges.push((lo, hi.saturating_add(1)));
self.anchor = Some(anchor);
self.cursor = Some(row);
}
pub fn select_all(&mut self, rows: usize) {
self.ranges.clear();
if rows > 0 {
self.ranges.push((0, rows));
}
}
pub fn clear(&mut self) {
*self = Self::default();
}
pub fn clamp(&mut self, rows: usize) {
self.remove(rows..usize::MAX);
self.anchor = self.anchor.filter(|a| *a < rows);
self.cursor = self.cursor.filter(|c| *c < rows);
}
pub fn insert(&mut self, rows: Range<usize>) {
if rows.is_empty() {
return;
}
let (mut lo, mut hi) = (rows.start, rows.end);
let first = self.ranges.partition_point(|r| r.1 < lo);
let last = self.ranges.partition_point(|r| r.0 <= hi);
if let Some(r) = self.ranges.get(first..last) {
if let (Some(a), Some(b)) = (r.first(), r.last()) {
lo = lo.min(a.0);
hi = hi.max(b.1);
}
self.ranges.splice(first..last, [(lo, hi)]);
}
}
pub fn remove(&mut self, rows: Range<usize>) {
if rows.is_empty() {
return;
}
let (lo, hi) = (rows.start, rows.end);
let first = self.ranges.partition_point(|r| r.1 <= lo);
let last = self.ranges.partition_point(|r| r.0 < hi);
let Some(hit) = self.ranges.get(first..last) else { return };
let mut keep = Vec::with_capacity(2);
if let Some(a) = hit.first().filter(|a| a.0 < lo) {
keep.push((a.0, lo));
}
if let Some(b) = hit.last().filter(|b| b.1 > hi) {
keep.push((hi, b.1));
}
self.ranges.splice(first..last, keep);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ranges_merge_split_and_count() {
let mut s = Selection::new();
s.insert(5..8);
s.insert(1..3);
s.insert(3..5);
assert_eq!(s.ranges().collect::<Vec<_>>(), vec![1..8]);
s.remove(4..6);
assert_eq!(s.ranges().collect::<Vec<_>>(), vec![1..4, 6..8]);
assert!(s.contains(3) && !s.contains(4) && s.contains(7) && !s.contains(8));
assert_eq!(s.len(), 5);
s.toggle(5);
assert_eq!(s.ranges().collect::<Vec<_>>(), vec![1..4, 5..8]);
s.toggle(5);
assert_eq!(s.iter().collect::<Vec<_>>(), vec![1, 2, 3, 6, 7]);
s.remove(0..usize::MAX);
assert!(s.is_empty());
}
#[test]
fn extend_keeps_anchor_and_select_all_is_one_range() {
let mut s = Selection::new();
s.select_only(10);
s.extend_to(7);
assert_eq!((s.anchor(), s.cursor(), s.len()), (Some(10), Some(7), 4));
s.extend_to(12);
assert_eq!(s.ranges().collect::<Vec<_>>(), vec![10..13]);
s.select_all(10_000_000);
assert_eq!((s.len(), s.ranges().count()), (10_000_000, 1));
s.clamp(5);
assert_eq!((s.len(), s.anchor(), s.cursor()), (5, None, None));
s.insert(usize::MAX - 1..usize::MAX);
s.toggle(usize::MAX);
let json = serde_json::to_string(&s).unwrap();
assert_eq!(serde_json::from_str::<Selection>(&json).unwrap(), s);
}
}