use crate::Position;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Selection {
Char { anchor: Position, head: Position },
Line { anchor_row: usize, head_row: usize },
Block { anchor: Position, head: Position },
}
pub type RowSpan = Option<(usize, usize)>;
impl Selection {
pub fn head(self) -> Position {
match self {
Selection::Char { head, .. } => head,
Selection::Line { head_row, .. } => Position::new(head_row, 0),
Selection::Block { head, .. } => head,
}
}
pub fn anchor(self) -> Position {
match self {
Selection::Char { anchor, .. } => anchor,
Selection::Line { anchor_row, .. } => Position::new(anchor_row, 0),
Selection::Block { anchor, .. } => anchor,
}
}
pub fn extend_to(&mut self, pos: Position) {
match self {
Selection::Char { head, .. } => *head = pos,
Selection::Line { head_row, .. } => *head_row = pos.row,
Selection::Block { head, .. } => *head = pos,
}
}
pub fn row_span(self, row: usize) -> RowSpan {
match self {
Selection::Char { anchor, head } => {
let (start, end) = order(anchor, head);
if row < start.row || row > end.row {
return None;
}
let lo = if row == start.row { start.col } else { 0 };
let hi = if row == end.row { end.col } else { usize::MAX };
Some((lo, hi))
}
Selection::Line {
anchor_row,
head_row,
} => {
let (lo, hi) = if anchor_row <= head_row {
(anchor_row, head_row)
} else {
(head_row, anchor_row)
};
if row < lo || row > hi {
None
} else {
Some((0, usize::MAX))
}
}
Selection::Block { anchor, head } => {
let (top, bot) = (anchor.row.min(head.row), anchor.row.max(head.row));
if row < top || row > bot {
return None;
}
let (left, right) = (anchor.col.min(head.col), anchor.col.max(head.col));
Some((left, right))
}
}
}
pub fn row_bounds(self) -> (usize, usize) {
match self {
Selection::Char { anchor, head } => {
let (s, e) = order(anchor, head);
(s.row, e.row)
}
Selection::Line {
anchor_row,
head_row,
} => (anchor_row.min(head_row), anchor_row.max(head_row)),
Selection::Block { anchor, head } => {
(anchor.row.min(head.row), anchor.row.max(head.row))
}
}
}
}
fn order(a: Position, b: Position) -> (Position, Position) {
if a <= b { (a, b) } else { (b, a) }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn char_single_row_inclusive() {
let sel = Selection::Char {
anchor: Position::new(0, 2),
head: Position::new(0, 5),
};
assert_eq!(sel.row_span(0), Some((2, 5)));
assert_eq!(sel.row_span(1), None);
}
#[test]
fn char_multi_row_clips_endpoints() {
let sel = Selection::Char {
anchor: Position::new(1, 3),
head: Position::new(3, 7),
};
assert_eq!(sel.row_span(0), None);
assert_eq!(sel.row_span(1), Some((3, usize::MAX)));
assert_eq!(sel.row_span(2), Some((0, usize::MAX)));
assert_eq!(sel.row_span(3), Some((0, 7)));
assert_eq!(sel.row_span(4), None);
}
#[test]
fn char_handles_reversed_endpoints() {
let sel = Selection::Char {
anchor: Position::new(3, 7),
head: Position::new(1, 3),
};
assert_eq!(sel.row_span(1), Some((3, usize::MAX)));
assert_eq!(sel.row_span(3), Some((0, 7)));
}
#[test]
fn line_covers_whole_rows_only() {
let sel = Selection::Line {
anchor_row: 5,
head_row: 7,
};
assert_eq!(sel.row_span(4), None);
assert_eq!(sel.row_span(5), Some((0, usize::MAX)));
assert_eq!(sel.row_span(6), Some((0, usize::MAX)));
assert_eq!(sel.row_span(7), Some((0, usize::MAX)));
assert_eq!(sel.row_span(8), None);
}
#[test]
fn block_inclusive_rect() {
let sel = Selection::Block {
anchor: Position::new(2, 4),
head: Position::new(5, 8),
};
for row in 2..=5 {
assert_eq!(sel.row_span(row), Some((4, 8)));
}
assert_eq!(sel.row_span(1), None);
assert_eq!(sel.row_span(6), None);
}
#[test]
fn block_normalises_corners() {
let sel = Selection::Block {
anchor: Position::new(5, 8),
head: Position::new(2, 4),
};
for row in 2..=5 {
assert_eq!(sel.row_span(row), Some((4, 8)));
}
}
#[test]
fn extend_to_updates_head() {
let mut sel = Selection::Char {
anchor: Position::new(0, 0),
head: Position::new(0, 3),
};
sel.extend_to(Position::new(2, 9));
assert_eq!(sel.head(), Position::new(2, 9));
assert_eq!(sel.anchor(), Position::new(0, 0));
}
#[test]
fn line_extend_to_drops_column() {
let mut sel = Selection::Line {
anchor_row: 1,
head_row: 1,
};
sel.extend_to(Position::new(4, 50));
assert_eq!(sel.head(), Position::new(4, 0));
}
#[test]
fn row_bounds_each_kind() {
let c = Selection::Char {
anchor: Position::new(2, 0),
head: Position::new(5, 0),
};
assert_eq!(c.row_bounds(), (2, 5));
let l = Selection::Line {
anchor_row: 7,
head_row: 3,
};
assert_eq!(l.row_bounds(), (3, 7));
let b = Selection::Block {
anchor: Position::new(8, 1),
head: Position::new(2, 9),
};
assert_eq!(b.row_bounds(), (2, 8));
}
}