use std::convert::TryFrom;
use std::mem;
use std::ops::{Bound, Range, RangeBounds};
use crate::grid::Dimensions;
use crate::index::{Column, Line, Point, Side};
use crate::term::Term;
#[derive(Debug, Copy, Clone, PartialEq)]
struct Anchor {
point: Point<usize>,
side: Side,
}
impl Anchor {
fn new(point: Point<usize>, side: Side) -> Anchor {
Anchor { point, side }
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct SelectionRange<L = usize> {
pub start: Point<L>,
pub end: Point<L>,
pub is_block: bool,
}
impl<L> SelectionRange<L> {
pub fn new(start: Point<L>, end: Point<L>, is_block: bool) -> Self {
Self { start, end, is_block }
}
pub fn contains(&self, col: Column, line: L) -> bool
where
L: PartialEq + PartialOrd,
{
self.start.line <= line
&& self.end.line >= line
&& (self.start.col <= col || (self.start.line != line && !self.is_block))
&& (self.end.col >= col || (self.end.line != line && !self.is_block))
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum SelectionType {
Simple,
Block,
Semantic,
Lines,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Selection {
pub ty: SelectionType,
region: Range<Anchor>,
}
impl Selection {
pub fn new(ty: SelectionType, location: Point<usize>, side: Side) -> Selection {
Self {
region: Range { start: Anchor::new(location, side), end: Anchor::new(location, side) },
ty,
}
}
pub fn update(&mut self, point: Point<usize>, side: Side) {
self.region.end = Anchor::new(point, side);
}
pub fn rotate<D: Dimensions>(
mut self,
dimensions: &D,
range: &Range<Line>,
delta: isize,
) -> Option<Selection> {
let num_lines = dimensions.screen_lines().0;
let num_cols = dimensions.cols().0;
let range_bottom = range.start.0;
let range_top = range.end.0;
let (mut start, mut end) = (&mut self.region.start, &mut self.region.end);
if Selection::points_need_swap(start.point, end.point) {
mem::swap(&mut start, &mut end);
}
if (start.point.line < range_top || range_top == num_lines)
&& start.point.line >= range_bottom
{
start.point.line = usize::try_from(start.point.line as isize + delta).unwrap_or(0);
if start.point.line < range_bottom && end.point.line >= range_bottom {
return None;
}
if start.point.line >= range_top && range_top != num_lines {
if self.ty != SelectionType::Block {
start.point.col = Column(0);
start.side = Side::Left;
}
start.point.line = range_top - 1;
}
}
if (end.point.line < range_top || range_top == num_lines) && end.point.line >= range_bottom
{
end.point.line = usize::try_from(end.point.line as isize + delta).unwrap_or(0);
if end.point.line > start.point.line {
return None;
}
if end.point.line < range_bottom {
if self.ty != SelectionType::Block {
end.point.col = Column(num_cols - 1);
end.side = Side::Right;
}
end.point.line = range_bottom;
}
}
Some(self)
}
pub fn is_empty(&self) -> bool {
match self.ty {
SelectionType::Simple => {
let (mut start, mut end) = (self.region.start, self.region.end);
if Self::points_need_swap(start.point, end.point) {
mem::swap(&mut start, &mut end);
}
start == end
|| (start.side == Side::Right
&& end.side == Side::Left
&& (start.point.line == end.point.line)
&& start.point.col + 1 == end.point.col)
},
SelectionType::Block => {
let (start, end) = (self.region.start, self.region.end);
(start.point.col == end.point.col && start.side == end.side)
|| (start.point.col + 1 == end.point.col
&& start.side == Side::Right
&& end.side == Side::Left)
|| (end.point.col + 1 == start.point.col
&& start.side == Side::Left
&& end.side == Side::Right)
},
SelectionType::Semantic | SelectionType::Lines => false,
}
}
pub fn intersects_range<R: RangeBounds<usize>>(&self, range: R) -> bool {
let mut start = self.region.start.point.line;
let mut end = self.region.end.point.line;
if Self::points_need_swap(self.region.start.point, self.region.end.point) {
mem::swap(&mut start, &mut end);
}
let range_start = match range.start_bound() {
Bound::Included(&range_start) => range_start,
Bound::Excluded(&range_start) => range_start.saturating_add(1),
Bound::Unbounded => 0,
};
let range_end = match range.end_bound() {
Bound::Included(&range_end) => range_end,
Bound::Excluded(&range_end) => range_end.saturating_sub(1),
Bound::Unbounded => usize::max_value(),
};
range_start <= start && range_end >= end
}
pub fn include_all(&mut self) {
let (start, end) = (self.region.start.point, self.region.end.point);
let (start_side, end_side) = match self.ty {
SelectionType::Block
if start.col > end.col || (start.col == end.col && start.line < end.line) =>
{
(Side::Right, Side::Left)
},
SelectionType::Block => (Side::Left, Side::Right),
_ if Self::points_need_swap(start, end) => (Side::Right, Side::Left),
_ => (Side::Left, Side::Right),
};
self.region.start.side = start_side;
self.region.end.side = end_side;
}
pub fn to_range<T>(&self, term: &Term<T>) -> Option<SelectionRange> {
let grid = term.grid();
let num_cols = grid.cols();
let mut start = self.region.start;
let mut end = self.region.end;
if Self::points_need_swap(start.point, end.point) {
mem::swap(&mut start, &mut end);
}
let is_block = self.ty == SelectionType::Block;
let (start, end) = Self::grid_clamp(start, end, is_block, grid.total_lines()).ok()?;
match self.ty {
SelectionType::Simple => self.range_simple(start, end, num_cols),
SelectionType::Block => self.range_block(start, end),
SelectionType::Semantic => Self::range_semantic(term, start.point, end.point),
SelectionType::Lines => Self::range_lines(term, start.point, end.point),
}
}
fn points_need_swap(start: Point<usize>, end: Point<usize>) -> bool {
start.line < end.line || start.line == end.line && start.col > end.col
}
fn grid_clamp(
mut start: Anchor,
end: Anchor,
is_block: bool,
lines: usize,
) -> Result<(Anchor, Anchor), ()> {
if start.point.line >= lines {
if end.point.line >= lines {
return Err(());
}
if !is_block {
start.side = Side::Left;
start.point.col = Column(0);
}
start.point.line = lines - 1;
}
Ok((start, end))
}
fn range_semantic<T>(
term: &Term<T>,
mut start: Point<usize>,
mut end: Point<usize>,
) -> Option<SelectionRange> {
if start == end {
if let Some(matching) = term.bracket_search(start) {
if (matching.line == start.line && matching.col < start.col)
|| (matching.line > start.line)
{
start = matching;
} else {
end = matching;
}
return Some(SelectionRange { start, end, is_block: false });
}
}
start = term.semantic_search_left(start);
end = term.semantic_search_right(end);
Some(SelectionRange { start, end, is_block: false })
}
fn range_lines<T>(
term: &Term<T>,
mut start: Point<usize>,
mut end: Point<usize>,
) -> Option<SelectionRange> {
start = term.line_search_left(start);
end = term.line_search_right(end);
Some(SelectionRange { start, end, is_block: false })
}
fn range_simple(
&self,
mut start: Anchor,
mut end: Anchor,
num_cols: Column,
) -> Option<SelectionRange> {
if self.is_empty() {
return None;
}
if end.side == Side::Left && start.point != end.point {
if end.point.col == Column(0) {
end.point.col = num_cols - 1;
end.point.line += 1;
} else {
end.point.col -= 1;
}
}
if start.side == Side::Right && start.point != end.point {
start.point.col += 1;
if start.point.col == num_cols {
start.point = Point::new(start.point.line.saturating_sub(1), Column(0));
}
}
Some(SelectionRange { start: start.point, end: end.point, is_block: false })
}
fn range_block(&self, mut start: Anchor, mut end: Anchor) -> Option<SelectionRange> {
if self.is_empty() {
return None;
}
if start.point.col > end.point.col {
mem::swap(&mut start.side, &mut end.side);
mem::swap(&mut start.point.col, &mut end.point.col);
}
if end.side == Side::Left && start.point != end.point && end.point.col.0 > 0 {
end.point.col -= 1;
}
if start.side == Side::Right && start.point != end.point {
start.point.col += 1;
}
Some(SelectionRange { start: start.point, end: end.point, is_block: true })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::MockConfig;
use crate::event::{Event, EventListener};
use crate::index::{Column, Line, Point, Side};
use crate::term::{SizeInfo, Term};
struct Mock;
impl EventListener for Mock {
fn send_event(&self, _event: Event) {}
}
fn term(height: usize, width: usize) -> Term<Mock> {
let size = SizeInfo {
width: width as f32,
height: height as f32,
cell_width: 1.0,
cell_height: 1.0,
padding_x: 0.0,
padding_y: 0.0,
dpr: 1.0,
};
Term::new(&MockConfig::default(), &size, Mock)
}
#[test]
fn single_cell_left_to_right() {
let location = Point { line: 0, col: Column(0) };
let mut selection = Selection::new(SelectionType::Simple, location, Side::Left);
selection.update(location, Side::Right);
assert_eq!(selection.to_range(&term(1, 1)).unwrap(), SelectionRange {
start: location,
end: location,
is_block: false
});
}
#[test]
fn single_cell_right_to_left() {
let location = Point { line: 0, col: Column(0) };
let mut selection = Selection::new(SelectionType::Simple, location, Side::Right);
selection.update(location, Side::Left);
assert_eq!(selection.to_range(&term(1, 1)).unwrap(), SelectionRange {
start: location,
end: location,
is_block: false
});
}
#[test]
fn between_adjacent_cells_left_to_right() {
let mut selection =
Selection::new(SelectionType::Simple, Point::new(0, Column(0)), Side::Right);
selection.update(Point::new(0, Column(1)), Side::Left);
assert_eq!(selection.to_range(&term(1, 2)), None);
}
#[test]
fn between_adjacent_cells_right_to_left() {
let mut selection =
Selection::new(SelectionType::Simple, Point::new(0, Column(1)), Side::Left);
selection.update(Point::new(0, Column(0)), Side::Right);
assert_eq!(selection.to_range(&term(1, 2)), None);
}
#[test]
fn across_adjacent_lines_upward_final_cell_exclusive() {
let mut selection =
Selection::new(SelectionType::Simple, Point::new(1, Column(1)), Side::Right);
selection.update(Point::new(0, Column(1)), Side::Right);
assert_eq!(selection.to_range(&term(2, 5)).unwrap(), SelectionRange {
start: Point::new(1, Column(2)),
end: Point::new(0, Column(1)),
is_block: false,
});
}
#[test]
fn selection_bigger_then_smaller() {
let mut selection =
Selection::new(SelectionType::Simple, Point::new(0, Column(1)), Side::Right);
selection.update(Point::new(1, Column(1)), Side::Right);
selection.update(Point::new(1, Column(0)), Side::Right);
assert_eq!(selection.to_range(&term(2, 5)).unwrap(), SelectionRange {
start: Point::new(1, Column(1)),
end: Point::new(0, Column(1)),
is_block: false,
});
}
#[test]
fn line_selection() {
let size = (Line(10), Column(5));
let mut selection =
Selection::new(SelectionType::Lines, Point::new(0, Column(1)), Side::Left);
selection.update(Point::new(5, Column(1)), Side::Right);
selection = selection.rotate(&size, &(Line(0)..size.0), 7).unwrap();
assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange {
start: Point::new(9, Column(0)),
end: Point::new(7, Column(4)),
is_block: false,
});
}
#[test]
fn semantic_selection() {
let size = (Line(10), Column(5));
let mut selection =
Selection::new(SelectionType::Semantic, Point::new(0, Column(3)), Side::Left);
selection.update(Point::new(5, Column(1)), Side::Right);
selection = selection.rotate(&size, &(Line(0)..size.0), 7).unwrap();
assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange {
start: Point::new(9, Column(0)),
end: Point::new(7, Column(3)),
is_block: false,
});
}
#[test]
fn simple_selection() {
let size = (Line(10), Column(5));
let mut selection =
Selection::new(SelectionType::Simple, Point::new(0, Column(3)), Side::Right);
selection.update(Point::new(5, Column(1)), Side::Right);
selection = selection.rotate(&size, &(Line(0)..size.0), 7).unwrap();
assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange {
start: Point::new(9, Column(0)),
end: Point::new(7, Column(3)),
is_block: false,
});
}
#[test]
fn block_selection() {
let size = (Line(10), Column(5));
let mut selection =
Selection::new(SelectionType::Block, Point::new(0, Column(3)), Side::Right);
selection.update(Point::new(5, Column(1)), Side::Right);
selection = selection.rotate(&size, &(Line(0)..size.0), 7).unwrap();
assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange {
start: Point::new(9, Column(2)),
end: Point::new(7, Column(3)),
is_block: true
});
}
#[test]
fn simple_is_empty() {
let mut selection =
Selection::new(SelectionType::Simple, Point::new(0, Column(0)), Side::Right);
assert!(selection.is_empty());
selection.update(Point::new(0, Column(1)), Side::Left);
assert!(selection.is_empty());
selection.update(Point::new(1, Column(0)), Side::Right);
assert!(!selection.is_empty());
}
#[test]
fn block_is_empty() {
let mut selection =
Selection::new(SelectionType::Block, Point::new(0, Column(0)), Side::Right);
assert!(selection.is_empty());
selection.update(Point::new(0, Column(1)), Side::Left);
assert!(selection.is_empty());
selection.update(Point::new(0, Column(1)), Side::Right);
assert!(!selection.is_empty());
selection.update(Point::new(1, Column(0)), Side::Right);
assert!(selection.is_empty());
selection.update(Point::new(1, Column(1)), Side::Left);
assert!(selection.is_empty());
selection.update(Point::new(1, Column(1)), Side::Right);
assert!(!selection.is_empty());
}
#[test]
fn rotate_in_region_up() {
let size = (Line(10), Column(5));
let mut selection =
Selection::new(SelectionType::Simple, Point::new(2, Column(3)), Side::Right);
selection.update(Point::new(5, Column(1)), Side::Right);
selection = selection.rotate(&size, &(Line(1)..(size.0 - 1)), 4).unwrap();
assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange {
start: Point::new(8, Column(0)),
end: Point::new(6, Column(3)),
is_block: false,
});
}
#[test]
fn rotate_in_region_down() {
let size = (Line(10), Column(5));
let mut selection =
Selection::new(SelectionType::Simple, Point::new(5, Column(3)), Side::Right);
selection.update(Point::new(8, Column(1)), Side::Left);
selection = selection.rotate(&size, &(Line(1)..(size.0 - 1)), -5).unwrap();
assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange {
start: Point::new(3, Column(1)),
end: Point::new(1, size.1 - 1),
is_block: false,
});
}
#[test]
fn rotate_in_region_up_block() {
let size = (Line(10), Column(5));
let mut selection =
Selection::new(SelectionType::Block, Point::new(2, Column(3)), Side::Right);
selection.update(Point::new(5, Column(1)), Side::Right);
selection = selection.rotate(&size, &(Line(1)..(size.0 - 1)), 4).unwrap();
assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange {
start: Point::new(8, Column(2)),
end: Point::new(6, Column(3)),
is_block: true,
});
}
#[test]
fn range_intersection() {
let mut selection =
Selection::new(SelectionType::Lines, Point::new(6, Column(1)), Side::Left);
selection.update(Point::new(3, Column(1)), Side::Right);
assert!(selection.intersects_range(..));
assert!(selection.intersects_range(2..));
assert!(selection.intersects_range(2..=4));
assert!(selection.intersects_range(2..=7));
assert!(selection.intersects_range(4..=5));
assert!(selection.intersects_range(5..8));
assert!(!selection.intersects_range(..=2));
assert!(!selection.intersects_range(7..=8));
}
}