1
0
Fork 0
mirror of https://github.com/alacritty/alacritty.git synced 2024-11-11 13:51:01 -05:00
alacritty/alacritty_terminal/src/selection.rs

730 lines
25 KiB
Rust
Raw Normal View History

2020-05-05 18:50:23 -04:00
//! State management for a selection in the grid.
//!
//! A selection should start when the mouse is clicked, and it should be
//! finalized when the button is released. The selection should be cleared
//! when text is added/removed/scrolled on the screen. The selection should
//! also be cleared if the user clicks off of the selection.
use std::convert::TryFrom;
use std::mem;
use std::ops::{Bound, Range, RangeBounds};
use crate::ansi::CursorShape;
use crate::grid::{Dimensions, Grid, GridCell};
use crate::index::{Column, Line, Point, Side};
use crate::term::cell::Flags;
use crate::term::Term;
/// A Point and side within that point.
#[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 }
}
}
/// Represents a range of selected cells.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct SelectionRange<L = usize> {
/// Start point, top left of the selection.
pub start: Point<L>,
/// End point, bottom right of the selection.
pub end: Point<L>,
/// Whether this selection is a block selection.
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 }
}
/// Check if a point lies within the selection.
pub fn contains(&self, point: Point<L>) -> bool
where
L: PartialEq + PartialOrd,
{
self.start.line <= point.line
&& self.end.line >= point.line
&& (self.start.col <= point.col || (self.start.line != point.line && !self.is_block))
&& (self.end.col >= point.col || (self.end.line != point.line && !self.is_block))
}
}
impl SelectionRange<Line> {
/// Check if the cell at a point is part of the selection.
pub fn contains_cell<T>(
&self,
grid: &Grid<T>,
point: Point,
cursor_point: Point,
cursor_shape: CursorShape,
) -> bool
where
T: GridCell,
{
// Do not invert block cursor at selection boundaries.
if cursor_shape == CursorShape::Block
&& cursor_point == point
&& (self.start == point
|| self.end == point
|| (self.is_block
&& ((self.start.line == point.line && self.end.col == point.col)
|| (self.end.line == point.line && self.start.col == point.col))))
{
return false;
}
// Point itself is selected.
if self.contains(point) {
return true;
}
let num_cols = grid.cols();
// Convert to absolute coordinates to adjust for the display offset.
let buffer_point = grid.visible_to_buffer(point);
let cell = &grid[buffer_point];
// Check if wide char's spacers are selected.
if cell.flags().contains(Flags::WIDE_CHAR) {
let prev = point.sub(num_cols, 1);
let buffer_prev = grid.visible_to_buffer(prev);
let next = point.add(num_cols, 1);
// Check trailing spacer.
self.contains(next)
// Check line-wrapping, leading spacer.
|| (grid[buffer_prev].flags().contains(Flags::LEADING_WIDE_CHAR_SPACER)
&& self.contains(prev))
} else {
false
}
}
}
/// Different kinds of selection.
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum SelectionType {
Simple,
Block,
Semantic,
Lines,
}
/// Describes a region of a 2-dimensional area.
///
/// Used to track a text selection. There are four supported modes, each with its own constructor:
/// [`simple`], [`block`], [`semantic`], and [`lines`]. The [`simple`] mode precisely tracks which
/// cells are selected without any expansion. [`block`] will select rectangular regions.
/// [`semantic`] mode expands the initial selection to the nearest semantic escape char in either
/// direction. [`lines`] will always select entire lines.
///
/// Calls to [`update`] operate different based on the selection kind. The [`simple`] and [`block`]
/// mode do nothing special, simply track points and sides. [`semantic`] will continue to expand
/// out to semantic boundaries as the selection point changes. Similarly, [`lines`] will always
/// expand the new point to encompass entire lines.
///
/// [`simple`]: enum.Selection.html#method.simple
/// [`block`]: enum.Selection.html#method.block
/// [`semantic`]: enum.Selection.html#method.semantic
/// [`lines`]: enum.Selection.html#method.lines
/// [`update`]: enum.Selection.html#method.update
#[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,
}
}
/// Update the end of the selection.
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);
}
2020-05-05 18:50:23 -04:00
// Rotate start of selection.
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);
2020-05-05 18:50:23 -04:00
// If end is within the same region, delete selection once start rotates out.
if start.point.line < range_bottom && end.point.line >= range_bottom {
return None;
}
2020-05-05 18:50:23 -04:00
// Clamp selection to start of region.
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;
}
}
2020-05-05 18:50:23 -04:00
// Rotate end of selection.
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);
2020-05-05 18:50:23 -04:00
// Delete selection if end has overtaken the start.
if end.point.line > start.point.line {
return None;
}
2020-05-05 18:50:23 -04:00
// Clamp selection to end of region.
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);
}
// Simple selection is empty when the points are identical
2020-05-05 18:50:23 -04:00
// or two adjacent cells have the sides right -> left.
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);
// Block selection is empty when the points' columns and sides are identical
// or two cells with adjacent columns have the sides right -> left,
// regardless of their lines
(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,
}
}
/// Check whether selection contains any point in a given range.
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
}
/// Expand selection sides to include all cells.
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;
}
/// Convert selection to grid coordinates.
pub fn to_range<T>(&self, term: &Term<T>) -> Option<SelectionRange> {
let grid = term.grid();
let num_cols = grid.cols();
2020-05-05 18:50:23 -04:00
// Order start above the end.
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);
}
2020-05-05 18:50:23 -04:00
// Clamp to inside the grid buffer.
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),
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-20 21:44:38 -05:00
SelectionType::Semantic => Some(Self::range_semantic(term, start.point, end.point)),
SelectionType::Lines => Some(Self::range_lines(term, start.point, end.point)),
}
}
2020-05-05 18:50:23 -04:00
/// Bring start and end points in the correct order.
fn points_need_swap(start: Point<usize>, end: Point<usize>) -> bool {
start.line < end.line || start.line == end.line && start.col > end.col
}
/// Clamp selection inside grid to prevent OOB.
fn grid_clamp(
mut start: Anchor,
end: Anchor,
is_block: bool,
lines: usize,
) -> Result<(Anchor, Anchor), ()> {
2020-05-05 18:50:23 -04:00
// Clamp selection inside of grid to prevent OOB.
if start.point.line >= lines {
2020-05-05 18:50:23 -04:00
// Remove selection if it is fully out of the grid.
if end.point.line >= lines {
return Err(());
}
2020-05-05 18:50:23 -04:00
// Clamp to grid if it is still partially visible.
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>,
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-20 21:44:38 -05:00
) -> 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;
}
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-20 21:44:38 -05:00
return SelectionRange { start, end, is_block: false };
}
}
start = term.semantic_search_left(start);
end = term.semantic_search_right(end);
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-20 21:44:38 -05:00
SelectionRange { start, end, is_block: false }
}
fn range_lines<T>(
term: &Term<T>,
mut start: Point<usize>,
mut end: Point<usize>,
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-20 21:44:38 -05:00
) -> SelectionRange {
start = term.line_search_left(start);
end = term.line_search_right(end);
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-20 21:44:38 -05:00
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;
}
2020-05-05 18:50:23 -04:00
// Remove last cell if selection ends to the left of a cell.
if end.side == Side::Left && start.point != end.point {
2020-05-05 18:50:23 -04:00
// Special case when selection ends to left of first cell.
if end.point.col == Column(0) {
end.point.col = num_cols - 1;
end.point.line += 1;
} else {
end.point.col -= 1;
}
}
2020-05-05 18:50:23 -04:00
// Remove first cell if selection starts at the right of a cell.
if start.side == Side::Right && start.point != end.point {
start.point.col += 1;
2020-05-05 18:50:23 -04:00
// Wrap to next line when selection starts to the right of last column.
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;
}
2020-05-05 18:50:23 -04:00
// Always go top-left -> bottom-right.
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);
}
2020-05-05 18:50:23 -04:00
// Remove last cell if selection ends to the left of a cell.
if end.side == Side::Left && start.point != end.point && end.point.col.0 > 0 {
end.point.col -= 1;
}
2020-05-05 18:50:23 -04:00
// Remove first cell if selection starts at the right of a cell.
if start.side == Side::Right && start.point != end.point {
start.point.col += 1;
}
Some(SelectionRange { start: start.point, end: end.point, is_block: true })
}
}
/// Tests for selection.
///
/// There are comments on all of the tests describing the selection. Pictograms
/// are used to avoid ambiguity. Grid cells are represented by a [ ]. Only
2017-10-30 11:03:58 -04:00
/// cells that are completely covered are counted in a selection. Ends are
/// represented by `B` and `E` for begin and end, respectively. A selected cell
/// looks like [XX], [BX] (at the start), [XB] (at the end), [XE] (at the end),
/// and [EX] (at the start), or [BE] for a single cell. Partially selected cells
/// look like [ B] and [E ].
#[cfg(test)]
2020-01-28 07:32:35 -05:00
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::new(width as f32, height as f32, 1.0, 1.0, 0.0, 0.0, false);
Term::new(&MockConfig::default(), size, Mock)
2017-06-17 13:29:59 -04:00
}
/// Test case of single cell selection.
///
/// 1. [ ]
/// 2. [B ]
/// 3. [BE]
#[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, 2)).unwrap(), SelectionRange {
start: location,
end: location,
is_block: false
});
}
/// Test case of single cell selection.
///
/// 1. [ ]
/// 2. [ B]
/// 3. [EB]
#[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, 2)).unwrap(), SelectionRange {
start: location,
end: location,
is_block: false
});
}
/// Test adjacent cell selection from left to right.
///
/// 1. [ ][ ]
/// 2. [ B][ ]
/// 3. [ B][E ]
#[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 adjacent cell selection from right to left.
///
/// 1. [ ][ ]
/// 2. [ ][B ]
/// 3. [ E][B ]
#[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 selection across adjacent lines.
///
/// 1. [ ][ ][ ][ ][ ]
/// [ ][ ][ ][ ][ ]
/// 2. [ ][ B][ ][ ][ ]
/// [ ][ ][ ][ ][ ]
/// 3. [ ][ B][XX][XX][XX]
/// [XX][XE][ ][ ][ ]
#[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 selection across adjacent lines.
///
/// 1. [ ][ ][ ][ ][ ]
/// [ ][ ][ ][ ][ ]
/// 2. [ ][ ][ ][ ][ ]
/// [ ][ B][ ][ ][ ]
/// 3. [ ][ E][XX][XX][XX]
/// [XX][XB][ ][ ][ ]
/// 4. [ E][XX][XX][XX][XX]
/// [XX][XB][ ][ ][ ]
#[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));
}
}