use crate::change::{Assoc, ChangeSet};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Range {
pub anchor: usize,
pub head: usize,
}
impl Range {
pub fn new(anchor: usize, head: usize) -> Self {
Self { anchor, head }
}
pub fn point(at: usize) -> Self {
Self { anchor: at, head: at }
}
pub fn is_empty(&self) -> bool {
self.anchor == self.head
}
pub fn start(&self) -> usize {
self.anchor.min(self.head)
}
pub fn end(&self) -> usize {
self.anchor.max(self.head)
}
pub fn len(&self) -> usize {
self.end() - self.start()
}
pub fn map(&self, changes: &ChangeSet) -> Self {
Self {
anchor: changes.map_pos(self.anchor, Assoc::After),
head: changes.map_pos(self.head, Assoc::After),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Selection {
ranges: Vec<Range>,
primary: usize,
}
impl Selection {
pub fn single(range: Range) -> Self {
Self { ranges: vec![range], primary: 0 }
}
pub fn point(at: usize) -> Self {
Self::single(Range::point(at))
}
pub fn ranges(&self) -> &[Range] {
&self.ranges
}
pub fn primary(&self) -> Range {
self.ranges[self.primary]
}
pub fn map(&self, changes: &ChangeSet) -> Self {
Self { ranges: self.ranges.iter().map(|r| r.map(changes)).collect(), primary: self.primary }
}
}
impl Default for Selection {
fn default() -> Self {
Self::point(0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_cursor_is_an_empty_range() {
let c = Range::point(4);
assert!(c.is_empty());
assert_eq!(c.len(), 0);
assert_eq!((c.start(), c.end()), (4, 4));
}
#[test]
fn a_backwards_range_keeps_its_direction_but_orders_its_bounds() {
let r = Range::new(9, 3);
assert_eq!((r.start(), r.end()), (3, 9));
assert_eq!(r.len(), 6);
assert_eq!(r.head, 3, "direction is information; it must survive");
}
#[test]
fn an_edit_before_the_cursor_pushes_it_along() {
let cursor = Range::point(10);
let insert = ChangeSet::replace(20, 0, 0, "abc");
assert_eq!(cursor.map(&insert), Range::point(13));
}
#[test]
fn an_edit_after_the_cursor_leaves_it_alone() {
let cursor = Range::point(4);
let insert = ChangeSet::replace(20, 10, 10, "abc");
assert_eq!(cursor.map(&insert), Range::point(4));
}
#[test]
fn typing_at_the_cursor_carries_it_forward() {
let cursor = Range::point(5);
let typed = ChangeSet::replace(10, 5, 5, "x");
assert_eq!(cursor.map(&typed), Range::point(6));
}
#[test]
fn a_selection_spanning_a_deletion_collapses_onto_it() {
let selection = Range::new(3, 9);
let deleted = ChangeSet::replace(20, 2, 12, "");
assert_eq!(selection.map(&deleted), Range::new(2, 2));
}
#[test]
fn every_range_in_a_selection_maps() {
let sel = Selection { ranges: vec![Range::point(1), Range::point(8)], primary: 1 };
let mapped = sel.map(&ChangeSet::replace(20, 0, 0, "xx"));
assert_eq!(mapped.ranges(), [Range::point(3), Range::point(10)]);
assert_eq!(mapped.primary(), Range::point(10), "the primary index survives");
}
#[test]
fn a_default_selection_is_a_cursor_at_the_start() {
assert_eq!(Selection::default().primary(), Range::point(0));
}
}