#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Selection {
pub anchor: usize,
pub head: usize,
}
impl Selection {
pub fn cursor(at: usize) -> Self {
Self {
anchor: at,
head: at,
}
}
pub fn collapsed(self) -> bool {
self.anchor == self.head
}
pub fn range(self) -> (usize, usize) {
(self.anchor.min(self.head), self.anchor.max(self.head))
}
}
#[derive(Debug, Clone)]
pub struct SelectionSet {
primary: Selection,
extras: Vec<Selection>,
}
impl Default for SelectionSet {
fn default() -> Self {
Self {
primary: Selection::cursor(0),
extras: Vec::new(),
}
}
}
impl SelectionSet {
pub fn primary(&self) -> Selection {
self.primary
}
pub fn heads(&self) -> Vec<usize> {
std::iter::once(self.primary.head)
.chain(self.extras.iter().map(|s| s.head))
.collect()
}
pub fn extra_heads(&self) -> &[Selection] {
&self.extras
}
pub fn count(&self) -> usize {
1 + self.extras.len()
}
pub fn set_head(&mut self, head: usize) {
self.primary.head = head;
}
pub fn collapse_primary(&mut self, at: usize) {
self.primary = Selection::cursor(at);
}
pub fn stretch_primary(&mut self, anchor: usize, head: usize) {
self.primary = Selection { anchor, head };
}
pub fn toggle_extra(&mut self) {
if let Some(i) = self.extras.iter().position(|s| s.head == self.primary.head) {
self.extras.remove(i);
} else {
self.extras.push(self.primary);
}
self.normalize();
}
pub fn plant_extra(&mut self, at: usize) {
if at != self.primary.head && !self.extras.iter().any(|s| s.head == at) {
self.extras.push(Selection::cursor(at));
}
self.normalize();
}
pub fn normalize(&mut self) {
self.extras.sort_by_key(|s| s.head);
self.extras.dedup();
}
pub fn collapse_extras(&mut self) {
self.extras.clear();
}
pub fn set_extras(&mut self, heads: impl IntoIterator<Item = usize>) {
self.extras = heads.into_iter().map(Selection::cursor).collect();
self.normalize();
}
pub fn remap(&mut self, at: usize, delta: isize) {
let shift = |p: &mut usize| {
if *p >= at {
*p = (*p as isize + delta).max(0) as usize;
}
};
shift(&mut self.primary.head);
shift(&mut self.primary.anchor);
for s in &mut self.extras {
shift(&mut s.head);
shift(&mut s.anchor);
}
self.normalize();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn invariants_hold() {
let mut s = SelectionSet::default();
s.set_head(5);
s.toggle_extra(); assert_eq!(s.count(), 2);
s.plant_extra(2);
s.plant_extra(2); assert_eq!(s.count(), 3);
assert_eq!(s.heads(), vec![5, 2, 5]);
s.toggle_extra(); assert_eq!(s.count(), 2);
assert_eq!(s.heads(), vec![5, 2]);
s.collapse_extras();
assert_eq!(s.count(), 1);
}
#[test]
fn remap_shifts_past_the_edit() {
let mut s = SelectionSet::default();
s.collapse_primary(10);
s.plant_extra(20);
s.remap(5, 3); assert_eq!(s.heads(), vec![13, 23]);
s.remap(5, -3);
assert_eq!(s.heads(), vec![10, 20]);
}
}