use std::cell::RefCell;
use crate::coords::Bias;
use crate::patch::Patch;
type RebaseScratch = (Vec<(u32, Bias)>, Vec<u32>);
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct SelectionId(pub usize);
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Selection {
pub id: SelectionId,
start: u32,
end: u32,
reversed: bool,
pub goal: Option<u32>,
}
impl Selection {
#[must_use]
pub fn caret(id: SelectionId, offset: u32) -> Self {
Self { id, start: offset, end: offset, reversed: false, goal: None }
}
#[must_use]
pub fn from_anchor(id: SelectionId, anchor: u32, head: u32) -> Self {
Self {
id,
start: anchor.min(head),
end: anchor.max(head),
reversed: head < anchor,
goal: None,
}
}
#[must_use]
pub fn start(&self) -> u32 {
self.start
}
#[must_use]
pub fn end(&self) -> u32 {
self.end
}
#[must_use]
pub fn head(&self) -> u32 {
if self.reversed {
self.start
} else {
self.end
}
}
#[must_use]
pub fn tail(&self) -> u32 {
if self.reversed {
self.end
} else {
self.start
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.start == self.end
}
pub fn set_head(&mut self, offset: u32) {
let tail = self.tail();
self.start = tail.min(offset);
self.end = tail.max(offset);
self.reversed = offset < tail;
self.goal = None;
}
pub fn collapse_to_head(&mut self) {
let h = self.head();
self.start = h;
self.end = h;
self.reversed = false;
}
pub fn move_to_caret(&mut self, offset: u32) {
self.start = offset;
self.end = offset;
self.reversed = false;
self.goal = None;
}
}
#[derive(Clone, Debug)]
pub struct SelectionSet {
selections: Vec<Selection>,
next_id: usize,
}
impl SelectionSet {
#[must_use]
pub fn new(offset: u32) -> Self {
Self { selections: vec![Selection::caret(SelectionId(0), offset)], next_id: 1 }
}
#[must_use]
pub fn from_offsets(offsets: &[u32]) -> Self {
assert!(!offsets.is_empty(), "a selection set is never empty");
let mut set = Self { selections: Vec::new(), next_id: 0 };
for &o in offsets {
let id = set.mint();
set.selections.push(Selection::caret(id, o));
}
set.normalize();
set
}
#[must_use]
pub fn from_ranges(ranges: &[(u32, u32)], newest: usize) -> Self {
assert!(!ranges.is_empty(), "a selection set is never empty");
assert!(newest < ranges.len(), "newest index out of bounds");
let mut set = Self { selections: Vec::new(), next_id: 0 };
for (i, &(anchor, head)) in ranges.iter().enumerate() {
if i != newest {
let id = set.mint();
set.selections.push(Selection::from_anchor(id, anchor, head));
}
}
let id = set.mint();
let (anchor, head) = ranges[newest];
set.selections.push(Selection::from_anchor(id, anchor, head));
set.normalize();
set
}
#[must_use]
pub fn all(&self) -> &[Selection] {
&self.selections
}
#[must_use]
pub fn len(&self) -> usize {
self.selections.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
false
}
#[must_use]
pub fn newest(&self) -> &Selection {
self.selections.iter().max_by_key(|s| s.id).expect("set is non-empty")
}
fn mint(&mut self) -> SelectionId {
let id = SelectionId(self.next_id);
self.next_id += 1;
id
}
pub fn add_caret(&mut self, offset: u32) {
let id = self.mint();
self.selections.push(Selection::caret(id, offset));
self.normalize();
}
pub fn add_selection(&mut self, anchor: u32, head: u32) {
let id = self.mint();
self.selections.push(Selection::from_anchor(id, anchor, head));
self.normalize();
}
pub fn set_single(&mut self, sel: Selection) {
self.selections = vec![sel];
}
pub fn collapse_to_newest(&mut self) {
let mut n = *self.newest();
n.collapse_to_head();
self.selections = vec![n];
}
pub fn collapse_to_primary(&mut self) {
let mut primary = *self.selections.iter().min_by_key(|s| s.id).expect("set is non-empty");
primary.collapse_to_head();
self.selections = vec![primary];
}
pub fn map_each(&mut self, mut f: impl FnMut(&mut Selection)) {
for s in &mut self.selections {
f(s);
}
self.normalize();
}
pub fn rebase(&mut self, patch: &Patch) {
thread_local! {
static SCRATCH: RefCell<RebaseScratch> =
const { RefCell::new((Vec::new(), Vec::new())) };
}
SCRATCH.with(|cell| {
let (queries, mapped) = &mut *cell.borrow_mut();
queries.clear();
for s in &self.selections {
if s.start == s.end {
queries.push((s.start, Bias::Right));
} else {
queries.push((s.start, Bias::Left));
queries.push((s.end, Bias::Right));
}
}
patch.map_many(&queries[..], mapped);
let mut mi = 0;
for s in &mut self.selections {
if s.start == s.end {
let o = mapped[mi];
mi += 1;
s.start = o;
s.end = o;
} else {
let (ns, ne) = (mapped[mi], mapped[mi + 1]);
mi += 2;
s.start = ns.min(ne);
s.end = ne.max(ns);
}
s.goal = None;
}
queries.clear(); });
self.normalize();
}
fn normalize(&mut self) {
self.selections.sort_by_key(|s| (s.start, s.end));
let mut w = 0;
for r in 1..self.selections.len() {
let s = self.selections[r];
if should_merge(&self.selections[w], &s) {
let prev = self.selections[w];
let newer = if s.id > prev.id { s } else { prev };
let dst = &mut self.selections[w];
dst.start = prev.start.min(s.start);
dst.end = prev.end.max(s.end);
dst.id = newer.id;
dst.reversed = newer.reversed;
dst.goal = newer.goal;
} else {
w += 1;
self.selections[w] = s;
}
}
self.selections.truncate(w + 1);
}
}
fn should_merge(a: &Selection, b: &Selection) -> bool {
debug_assert!(a.start <= b.start);
if b.start < a.end {
return true; }
if a.start == b.start {
return true; }
if b.start == a.end {
return a.is_empty() || b.is_empty();
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use crate::patch::Edit;
#[test]
fn head_tail_and_direction() {
let s = Selection::from_anchor(SelectionId(0), 2, 8);
assert_eq!((s.start(), s.end(), s.head(), s.tail()), (2, 8, 8, 2));
let r = Selection::from_anchor(SelectionId(0), 8, 2); assert_eq!((r.start(), r.end(), r.head(), r.tail()), (2, 8, 2, 8));
}
#[test]
fn set_head_flips_when_crossing_tail() {
let mut s = Selection::from_anchor(SelectionId(0), 5, 8); s.set_head(2); assert_eq!((s.start(), s.end(), s.head(), s.tail()), (2, 5, 2, 5));
}
#[test]
fn overlapping_selections_merge() {
let mut set = SelectionSet::new(0);
set.set_single(Selection::from_anchor(SelectionId(0), 0, 5));
set.add_selection(3, 9); assert_eq!(set.len(), 1);
assert_eq!((set.all()[0].start(), set.all()[0].end()), (0, 9));
}
#[test]
fn nonempty_touching_selections_do_not_merge() {
let mut set = SelectionSet::new(0);
set.set_single(Selection::from_anchor(SelectionId(0), 0, 3));
set.add_selection(3, 6); assert_eq!(set.len(), 2, "non-empty touching selections stay separate");
}
#[test]
fn caret_touching_a_boundary_merges() {
let mut set = SelectionSet::new(0);
set.set_single(Selection::from_anchor(SelectionId(0), 0, 3));
set.add_caret(3); assert_eq!(set.len(), 1);
}
#[test]
fn newest_wins_identity_on_merge() {
let mut set = SelectionSet::new(0);
set.set_single(Selection::from_anchor(SelectionId(0), 0, 5));
set.add_selection(3, 9); assert_eq!(set.all()[0].id, SelectionId(1));
}
#[test]
fn rebase_follows_an_insertion() {
let mut set = SelectionSet::new(0);
set.set_single(Selection::from_anchor(SelectionId(0), 5, 10));
let patch = Patch::single(Edit { old: 0..0, new: 0..3 });
set.rebase(&patch);
assert_eq!((set.all()[0].start(), set.all()[0].end()), (8, 13));
}
#[test]
fn rebase_collapses_a_selection_inside_a_deletion() {
let mut set = SelectionSet::new(0);
set.set_single(Selection::from_anchor(SelectionId(0), 3, 7));
let patch = Patch::single(Edit { old: 0..10, new: 0..0 });
set.rebase(&patch);
assert!(set.all()[0].is_empty());
assert_eq!(set.all()[0].start(), 0);
}
#[test]
fn from_ranges_installs_a_box_with_the_chosen_newest() {
let set = SelectionSet::from_ranges(&[(0, 3), (10, 13), (20, 23)], 2);
assert_eq!(set.len(), 3);
assert_eq!((set.newest().start(), set.newest().end()), (20, 23));
}
#[test]
fn collapse_to_primary_keeps_the_oldest_as_a_caret() {
let mut set = SelectionSet::new(0); set.add_selection(5, 9); set.add_caret(20); assert!(set.len() >= 2);
set.collapse_to_primary();
assert_eq!(set.len(), 1);
assert_eq!(set.all()[0].id, SelectionId(0), "the oldest cursor is kept");
assert!(set.all()[0].is_empty());
assert_eq!(set.all()[0].head(), 0);
}
#[test]
fn collapse_to_newest_keeps_one_caret() {
let mut set = SelectionSet::new(0);
set.add_selection(5, 9);
set.add_caret(20);
assert!(set.len() >= 2);
set.collapse_to_newest();
assert_eq!(set.len(), 1);
assert!(set.all()[0].is_empty());
}
}