2016-12-22 18:43:06 +00:00
|
|
|
// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
//! 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.
|
2020-01-20 23:56:10 +00:00
|
|
|
use std::convert::TryFrom;
|
|
|
|
use std::mem;
|
2017-10-13 03:59:21 +00:00
|
|
|
use std::ops::Range;
|
2016-12-22 18:43:06 +00:00
|
|
|
|
2020-01-24 22:57:22 +00:00
|
|
|
use crate::index::{Column, Line, Point, Side};
|
2019-04-29 14:33:25 +00:00
|
|
|
use crate::term::cell::Flags;
|
|
|
|
use crate::term::{Search, Term};
|
2016-12-22 18:43:06 +00:00
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
/// A Point and side within that point.
|
2020-01-24 22:57:22 +00:00
|
|
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
2020-01-20 23:56:10 +00:00
|
|
|
pub 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 }
|
|
|
|
}
|
|
|
|
|
|
|
|
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))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-24 22:57:22 +00:00
|
|
|
/// Different kinds of selection.
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
|
|
enum SelectionType {
|
|
|
|
Simple,
|
|
|
|
Block,
|
|
|
|
Semantic,
|
|
|
|
Lines,
|
|
|
|
}
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
/// Describes a region of a 2-dimensional area.
|
2016-12-22 18:43:06 +00:00
|
|
|
///
|
2020-01-24 22:57:22 +00:00
|
|
|
/// 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.
|
2017-06-16 04:43:28 +00:00
|
|
|
///
|
2020-01-24 22:57:22 +00:00
|
|
|
/// 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.
|
2017-06-16 04:43:28 +00:00
|
|
|
///
|
|
|
|
/// [`simple`]: enum.Selection.html#method.simple
|
2020-01-24 22:57:22 +00:00
|
|
|
/// [`block`]: enum.Selection.html#method.block
|
2017-06-16 04:43:28 +00:00
|
|
|
/// [`semantic`]: enum.Selection.html#method.semantic
|
|
|
|
/// [`lines`]: enum.Selection.html#method.lines
|
2019-10-05 00:29:26 +00:00
|
|
|
/// [`update`]: enum.Selection.html#method.update
|
2018-04-28 14:15:21 +00:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2020-01-24 22:57:22 +00:00
|
|
|
pub struct Selection {
|
|
|
|
region: Range<Anchor>,
|
|
|
|
ty: SelectionType,
|
2017-06-16 04:43:28 +00:00
|
|
|
}
|
|
|
|
|
2016-12-22 18:43:06 +00:00
|
|
|
impl Selection {
|
2018-03-07 04:57:40 +00:00
|
|
|
pub fn simple(location: Point<usize>, side: Side) -> Selection {
|
2020-01-24 22:57:22 +00:00
|
|
|
Self {
|
2020-01-20 23:56:10 +00:00
|
|
|
region: Range { start: Anchor::new(location, side), end: Anchor::new(location, side) },
|
2020-01-24 22:57:22 +00:00
|
|
|
ty: SelectionType::Simple,
|
2017-06-16 04:43:28 +00:00
|
|
|
}
|
2016-12-22 18:43:06 +00:00
|
|
|
}
|
|
|
|
|
2019-06-20 15:56:09 +00:00
|
|
|
pub fn block(location: Point<usize>, side: Side) -> Selection {
|
2020-01-24 22:57:22 +00:00
|
|
|
Self {
|
2020-01-20 23:56:10 +00:00
|
|
|
region: Range { start: Anchor::new(location, side), end: Anchor::new(location, side) },
|
2020-01-24 22:57:22 +00:00
|
|
|
ty: SelectionType::Block,
|
2018-03-07 04:57:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-24 22:57:22 +00:00
|
|
|
pub fn semantic(location: Point<usize>) -> Selection {
|
|
|
|
Self {
|
|
|
|
region: Range {
|
|
|
|
start: Anchor::new(location, Side::Left),
|
|
|
|
end: Anchor::new(location, Side::Right),
|
|
|
|
},
|
|
|
|
ty: SelectionType::Semantic,
|
|
|
|
}
|
2016-12-22 18:43:06 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 22:57:22 +00:00
|
|
|
pub fn lines(location: Point<usize>) -> Selection {
|
|
|
|
Self {
|
|
|
|
region: Range {
|
|
|
|
start: Anchor::new(location, Side::Left),
|
|
|
|
end: Anchor::new(location, Side::Right),
|
|
|
|
},
|
|
|
|
ty: SelectionType::Lines,
|
|
|
|
}
|
2016-12-22 18:43:06 +00:00
|
|
|
}
|
|
|
|
|
2018-03-07 04:57:40 +00:00
|
|
|
pub fn update(&mut self, location: Point<usize>, side: Side) {
|
2020-01-24 22:57:22 +00:00
|
|
|
self.region.end.point = location;
|
|
|
|
self.region.end.side = side;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn rotate(
|
|
|
|
mut self,
|
|
|
|
num_lines: usize,
|
|
|
|
num_cols: usize,
|
|
|
|
scrolling_region: &Range<Line>,
|
|
|
|
offset: isize,
|
|
|
|
) -> Option<Selection> {
|
|
|
|
// Convert scrolling region from viewport to buffer coordinates
|
|
|
|
let region_start = num_lines - scrolling_region.start.0;
|
|
|
|
let region_end = num_lines - scrolling_region.end.0;
|
|
|
|
|
|
|
|
let (mut start, mut end) = (&mut self.region.start, &mut self.region.end);
|
|
|
|
if Self::points_need_swap(start.point, end.point) {
|
|
|
|
mem::swap(&mut start, &mut end);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Rotate start of selection
|
|
|
|
if (start.point.line < region_start || region_start == num_lines)
|
|
|
|
&& start.point.line >= region_end
|
|
|
|
{
|
|
|
|
start.point.line = usize::try_from(start.point.line as isize + offset).unwrap_or(0);
|
2020-01-20 23:56:10 +00:00
|
|
|
|
2020-01-24 22:57:22 +00:00
|
|
|
// If end is within the same region, delete selection once start rotates out
|
|
|
|
if start.point.line < region_end && end.point.line >= region_end {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clamp selection to start of region
|
|
|
|
if start.point.line >= region_start && region_start != num_lines {
|
|
|
|
if self.ty != SelectionType::Block {
|
|
|
|
start.point.col = Column(0);
|
|
|
|
start.side = Side::Left;
|
|
|
|
}
|
|
|
|
start.point.line = region_start - 1;
|
|
|
|
}
|
2017-06-16 04:43:28 +00:00
|
|
|
}
|
2016-12-22 18:43:06 +00:00
|
|
|
|
2020-01-24 22:57:22 +00:00
|
|
|
// Rotate end of selection
|
|
|
|
if (end.point.line < region_start || region_start == num_lines)
|
|
|
|
&& end.point.line >= region_end
|
|
|
|
{
|
|
|
|
end.point.line = usize::try_from(end.point.line as isize + offset).unwrap_or(0);
|
|
|
|
|
|
|
|
// Delete selection if end has overtaken the start
|
|
|
|
if end.point.line > start.point.line {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clamp selection to end of region
|
|
|
|
if end.point.line < region_end {
|
|
|
|
if self.ty != SelectionType::Block {
|
|
|
|
end.point.col = Column(num_cols - 1);
|
|
|
|
end.side = Side::Right;
|
|
|
|
}
|
|
|
|
end.point.line = region_end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Some(self)
|
2020-01-20 23:56:10 +00:00
|
|
|
}
|
|
|
|
|
2019-06-20 15:56:09 +00:00
|
|
|
pub fn is_empty(&self) -> bool {
|
2020-01-24 22:57:22 +00:00
|
|
|
match self.ty {
|
|
|
|
SelectionType::Simple => {
|
|
|
|
let (mut start, mut end) = (self.region.start, self.region.end);
|
|
|
|
if Selection::points_need_swap(start.point, end.point) {
|
|
|
|
mem::swap(&mut start, &mut end);
|
|
|
|
}
|
2019-06-20 15:56:09 +00:00
|
|
|
|
2019-09-13 23:51:14 +00:00
|
|
|
// Simple selection is empty when the points are identical
|
|
|
|
// or two adjacent cells have the sides right -> left
|
2019-06-20 15:56:09 +00:00
|
|
|
start == end
|
2019-11-03 20:59:28 +00:00
|
|
|
|| (start.side == Side::Right
|
|
|
|
&& end.side == Side::Left
|
2019-09-13 23:51:14 +00:00
|
|
|
&& (start.point.line == end.point.line)
|
2019-11-03 20:59:28 +00:00
|
|
|
&& start.point.col + 1 == end.point.col)
|
2019-06-20 15:56:09 +00:00
|
|
|
},
|
2020-01-24 22:57:22 +00:00
|
|
|
SelectionType::Block => {
|
|
|
|
let (start, end) = (self.region.start, self.region.end);
|
|
|
|
|
2019-09-13 23:51:14 +00:00
|
|
|
// 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)
|
2019-11-03 20:59:28 +00:00
|
|
|
|| (start.point.col + 1 == end.point.col
|
|
|
|
&& start.side == Side::Right
|
|
|
|
&& end.side == Side::Left)
|
|
|
|
|| (end.point.col + 1 == start.point.col
|
2019-09-13 23:51:14 +00:00
|
|
|
&& start.side == Side::Left
|
|
|
|
&& end.side == Side::Right)
|
|
|
|
},
|
2020-01-24 22:57:22 +00:00
|
|
|
SelectionType::Semantic | SelectionType::Lines => false,
|
2019-06-20 15:56:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
/// Convert selection to grid coordinates.
|
|
|
|
pub fn to_range<T>(&self, term: &Term<T>) -> Option<SelectionRange> {
|
|
|
|
let grid = term.grid();
|
|
|
|
let num_cols = grid.num_cols();
|
2019-06-06 13:04:12 +00:00
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
// Order start above the end
|
2020-01-24 22:57:22 +00:00
|
|
|
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);
|
|
|
|
}
|
2019-06-20 15:56:09 +00:00
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
// Clamp to inside the grid buffer
|
2020-01-24 22:57:22 +00:00
|
|
|
let is_block = self.ty == SelectionType::Block;
|
|
|
|
let (start, end) = Self::grid_clamp(start, end, is_block, grid.len()).ok()?;
|
|
|
|
|
|
|
|
let range = 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),
|
2019-04-29 14:33:25 +00:00
|
|
|
};
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
// Expand selection across fullwidth cells
|
|
|
|
range.map(|range| Self::range_expand_fullwidth(term, range))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Expand the start/end of the selection range to account for fullwidth glyphs.
|
|
|
|
fn range_expand_fullwidth<T>(term: &Term<T>, mut range: SelectionRange) -> SelectionRange {
|
|
|
|
let grid = term.grid();
|
|
|
|
let num_cols = grid.num_cols();
|
|
|
|
|
|
|
|
// Helper for checking if cell at `point` contains `flag`
|
|
|
|
let flag_at = |point: Point<usize>, flag: Flags| -> bool {
|
|
|
|
grid[point.line][point.col].flags.contains(flag)
|
|
|
|
};
|
2020-01-09 23:06:41 +00:00
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
// Include all double-width cells and placeholders at top left of selection
|
|
|
|
if range.start.col < num_cols {
|
|
|
|
// Expand from wide char spacer to wide char
|
|
|
|
if range.start.line + 1 != grid.len() || range.start.col.0 != 0 {
|
|
|
|
let prev = range.start.sub(num_cols.0, 1, true);
|
|
|
|
if flag_at(range.start, Flags::WIDE_CHAR_SPACER) && flag_at(prev, Flags::WIDE_CHAR)
|
|
|
|
{
|
|
|
|
range.start = prev;
|
2020-01-09 23:06:41 +00:00
|
|
|
}
|
2019-04-29 14:33:25 +00:00
|
|
|
}
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
// Expand from wide char to wide char spacer for linewrapping
|
|
|
|
if range.start.line + 1 != grid.len() || range.start.col.0 != 0 {
|
|
|
|
let prev = range.start.sub(num_cols.0, 1, true);
|
|
|
|
if (prev.line + 1 != grid.len() || prev.col.0 != 0)
|
|
|
|
&& flag_at(prev, Flags::WIDE_CHAR_SPACER)
|
|
|
|
&& !flag_at(prev.sub(num_cols.0, 1, true), Flags::WIDE_CHAR)
|
2020-01-09 23:06:41 +00:00
|
|
|
{
|
2020-01-20 23:56:10 +00:00
|
|
|
range.start = prev;
|
2020-01-09 23:06:41 +00:00
|
|
|
}
|
2020-01-20 23:56:10 +00:00
|
|
|
}
|
|
|
|
}
|
2020-01-09 23:06:41 +00:00
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
// Include all double-width cells and placeholders at bottom right of selection
|
|
|
|
if range.end.line != 0 || range.end.col < num_cols {
|
|
|
|
// Expand from wide char spacer for linewrapping to wide char
|
|
|
|
if (range.end.line + 1 != grid.len() || range.end.col.0 != 0)
|
|
|
|
&& flag_at(range.end, Flags::WIDE_CHAR_SPACER)
|
|
|
|
&& !flag_at(range.end.sub(num_cols.0, 1, true), Flags::WIDE_CHAR)
|
|
|
|
{
|
|
|
|
range.end = range.end.add(num_cols.0, 1, true);
|
2019-04-29 14:33:25 +00:00
|
|
|
}
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
// Expand from wide char to wide char spacer
|
|
|
|
if flag_at(range.end, Flags::WIDE_CHAR) {
|
|
|
|
range.end = range.end.add(num_cols.0, 1, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
range
|
2016-12-22 18:43:06 +00:00
|
|
|
}
|
2018-10-20 22:30:59 +00:00
|
|
|
|
2019-06-20 15:56:09 +00:00
|
|
|
// Bring start and end points in the correct order
|
2020-01-20 23:56:10 +00:00
|
|
|
fn points_need_swap(start: Point<usize>, end: Point<usize>) -> bool {
|
2019-11-03 20:59:28 +00:00
|
|
|
start.line < end.line || start.line == end.line && start.col > end.col
|
2019-06-20 15:56:09 +00:00
|
|
|
}
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
/// Clamp selection inside grid to prevent OOB.
|
2019-06-20 15:56:09 +00:00
|
|
|
fn grid_clamp(
|
2020-01-20 23:56:10 +00:00
|
|
|
mut start: Anchor,
|
|
|
|
end: Anchor,
|
|
|
|
is_block: bool,
|
|
|
|
lines: usize,
|
|
|
|
) -> Result<(Anchor, Anchor), ()> {
|
|
|
|
// Clamp selection inside of grid to prevent OOB
|
|
|
|
if start.point.line >= lines {
|
|
|
|
// Remove selection if it is fully out of the grid
|
|
|
|
if end.point.line >= lines {
|
|
|
|
return Err(());
|
2019-06-20 15:56:09 +00:00
|
|
|
}
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
// Clamp to grid if it is still partially visible
|
|
|
|
if !is_block {
|
|
|
|
start.side = Side::Left;
|
|
|
|
start.point.col = Column(0);
|
2019-06-20 15:56:09 +00:00
|
|
|
}
|
2020-01-20 23:56:10 +00:00
|
|
|
start.point.line = lines - 1;
|
2019-06-20 15:56:09 +00:00
|
|
|
}
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
Ok((start, end))
|
2018-10-22 19:39:26 +00:00
|
|
|
}
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
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 });
|
2019-05-11 16:15:32 +00:00
|
|
|
}
|
2020-01-20 23:56:10 +00:00
|
|
|
}
|
2017-06-16 04:43:28 +00:00
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
start = term.semantic_search_left(start);
|
|
|
|
end = term.semantic_search_right(end);
|
|
|
|
|
|
|
|
Some(SelectionRange { start, end, is_block: false })
|
2017-06-16 04:43:28 +00:00
|
|
|
}
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
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);
|
2018-10-20 22:30:59 +00:00
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
Some(SelectionRange { start, end, is_block: false })
|
2017-06-16 04:43:28 +00:00
|
|
|
}
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
fn range_simple(
|
2019-06-20 15:56:09 +00:00
|
|
|
&self,
|
2020-01-20 23:56:10 +00:00
|
|
|
mut start: Anchor,
|
|
|
|
mut end: Anchor,
|
|
|
|
num_cols: Column,
|
|
|
|
) -> Option<SelectionRange> {
|
2019-06-20 15:56:09 +00:00
|
|
|
if self.is_empty() {
|
2018-03-25 19:09:18 +00:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
2018-03-15 19:13:00 +00:00
|
|
|
// Remove last cell if selection ends to the left of a cell
|
2020-01-20 23:56:10 +00:00
|
|
|
if end.side == Side::Left && start.point != end.point {
|
2019-11-03 20:59:28 +00:00
|
|
|
// Special case when selection ends to left of first cell
|
2020-01-20 23:56:10 +00:00
|
|
|
if end.point.col == Column(0) {
|
2020-01-21 21:17:25 +00:00
|
|
|
end.point.col = num_cols - 1;
|
2020-01-20 23:56:10 +00:00
|
|
|
end.point.line += 1;
|
2018-07-21 17:17:41 +00:00
|
|
|
} else {
|
2020-01-20 23:56:10 +00:00
|
|
|
end.point.col -= 1;
|
2017-06-16 04:43:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-15 19:13:00 +00:00
|
|
|
// Remove first cell if selection starts at the right of a cell
|
2020-01-20 23:56:10 +00:00
|
|
|
if start.side == Side::Right && start.point != end.point {
|
|
|
|
start.point.col += 1;
|
2017-06-16 04:43:28 +00:00
|
|
|
}
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
Some(SelectionRange { start: start.point, end: end.point, is_block: false })
|
2019-06-06 13:04:12 +00:00
|
|
|
}
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
fn range_block(&self, mut start: Anchor, mut end: Anchor) -> Option<SelectionRange> {
|
2019-06-20 15:56:09 +00:00
|
|
|
if self.is_empty() {
|
|
|
|
return None;
|
|
|
|
}
|
2018-10-20 22:30:59 +00:00
|
|
|
|
2019-11-03 20:59:28 +00:00
|
|
|
// Always go top-left -> bottom-right
|
2020-01-20 23:56:10 +00:00
|
|
|
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);
|
2018-10-20 22:30:59 +00:00
|
|
|
}
|
|
|
|
|
2019-06-20 15:56:09 +00:00
|
|
|
// Remove last cell if selection ends to the left of a cell
|
2020-01-20 23:56:10 +00:00
|
|
|
if end.side == Side::Left && start.point != end.point && end.point.col.0 > 0 {
|
|
|
|
end.point.col -= 1;
|
2019-06-20 15:56:09 +00:00
|
|
|
}
|
2018-10-20 22:30:59 +00:00
|
|
|
|
2019-06-20 15:56:09 +00:00
|
|
|
// Remove first cell if selection starts at the right of a cell
|
2020-01-20 23:56:10 +00:00
|
|
|
if start.side == Side::Right && start.point != end.point {
|
|
|
|
start.point.col += 1;
|
2018-10-20 22:30:59 +00:00
|
|
|
}
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
Some(SelectionRange { start: start.point, end: end.point, is_block: true })
|
2018-10-20 22:30:59 +00:00
|
|
|
}
|
2018-03-07 04:57:40 +00:00
|
|
|
}
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
/// Tests for selection.
|
2016-12-22 18:43:06 +00:00
|
|
|
///
|
|
|
|
/// 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 15:03:58 +00:00
|
|
|
/// cells that are completely covered are counted in a selection. Ends are
|
2016-12-22 18:43:06 +00:00
|
|
|
/// 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 12:32:35 +00:00
|
|
|
mod tests {
|
2019-04-29 14:33:25 +00:00
|
|
|
use std::mem;
|
2017-06-17 17:29:59 +00:00
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
use super::{Selection, SelectionRange};
|
2019-04-29 14:33:25 +00:00
|
|
|
use crate::clipboard::Clipboard;
|
2019-10-05 00:29:26 +00:00
|
|
|
use crate::config::MockConfig;
|
|
|
|
use crate::event::{Event, EventListener};
|
2019-04-29 14:33:25 +00:00
|
|
|
use crate::grid::Grid;
|
|
|
|
use crate::index::{Column, Line, Point, Side};
|
|
|
|
use crate::term::cell::{Cell, Flags};
|
|
|
|
use crate::term::{SizeInfo, Term};
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
struct Mock;
|
|
|
|
impl EventListener for Mock {
|
|
|
|
fn send_event(&self, _event: Event) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn term(width: usize, height: usize) -> Term<Mock> {
|
2019-04-29 14:33:25 +00:00
|
|
|
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,
|
|
|
|
};
|
2019-10-05 00:29:26 +00:00
|
|
|
Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock)
|
2017-06-17 17:29:59 +00:00
|
|
|
}
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
/// Test case of single cell selection.
|
2016-12-22 18:43:06 +00:00
|
|
|
///
|
|
|
|
/// 1. [ ]
|
|
|
|
/// 2. [B ]
|
|
|
|
/// 3. [BE]
|
|
|
|
#[test]
|
|
|
|
fn single_cell_left_to_right() {
|
2018-03-07 04:57:40 +00:00
|
|
|
let location = Point { line: 0, col: Column(0) };
|
2017-06-17 17:29:59 +00:00
|
|
|
let mut selection = Selection::simple(location, Side::Left);
|
2016-12-22 18:43:06 +00:00
|
|
|
selection.update(location, Side::Right);
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
assert_eq!(selection.to_range(&term(1, 1)).unwrap(), SelectionRange {
|
2019-04-29 14:33:25 +00:00
|
|
|
start: location,
|
2019-06-20 15:56:09 +00:00
|
|
|
end: location,
|
2020-01-09 23:06:41 +00:00
|
|
|
is_block: false
|
2016-12-22 18:43:06 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
/// Test case of single cell selection.
|
2016-12-22 18:43:06 +00:00
|
|
|
///
|
|
|
|
/// 1. [ ]
|
|
|
|
/// 2. [ B]
|
|
|
|
/// 3. [EB]
|
|
|
|
#[test]
|
|
|
|
fn single_cell_right_to_left() {
|
2018-03-07 04:57:40 +00:00
|
|
|
let location = Point { line: 0, col: Column(0) };
|
2017-06-17 17:29:59 +00:00
|
|
|
let mut selection = Selection::simple(location, Side::Right);
|
2016-12-22 18:43:06 +00:00
|
|
|
selection.update(location, Side::Left);
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
assert_eq!(selection.to_range(&term(1, 1)).unwrap(), SelectionRange {
|
2019-04-29 14:33:25 +00:00
|
|
|
start: location,
|
2019-06-20 15:56:09 +00:00
|
|
|
end: location,
|
2020-01-09 23:06:41 +00:00
|
|
|
is_block: false
|
2016-12-22 18:43:06 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
/// Test adjacent cell selection from left to right.
|
2016-12-22 18:43:06 +00:00
|
|
|
///
|
|
|
|
/// 1. [ ][ ]
|
|
|
|
/// 2. [ B][ ]
|
|
|
|
/// 3. [ B][E ]
|
|
|
|
#[test]
|
|
|
|
fn between_adjacent_cells_left_to_right() {
|
2018-03-07 04:57:40 +00:00
|
|
|
let mut selection = Selection::simple(Point::new(0, Column(0)), Side::Right);
|
|
|
|
selection.update(Point::new(0, Column(1)), Side::Left);
|
2016-12-22 18:43:06 +00:00
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
assert_eq!(selection.to_range(&term(2, 1)), None);
|
2016-12-22 18:43:06 +00:00
|
|
|
}
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
/// Test adjacent cell selection from right to left.
|
2016-12-22 18:43:06 +00:00
|
|
|
///
|
|
|
|
/// 1. [ ][ ]
|
|
|
|
/// 2. [ ][B ]
|
|
|
|
/// 3. [ E][B ]
|
|
|
|
#[test]
|
|
|
|
fn between_adjacent_cells_right_to_left() {
|
2018-03-07 04:57:40 +00:00
|
|
|
let mut selection = Selection::simple(Point::new(0, Column(1)), Side::Left);
|
|
|
|
selection.update(Point::new(0, Column(0)), Side::Right);
|
2016-12-22 18:43:06 +00:00
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
assert_eq!(selection.to_range(&term(2, 1)), None);
|
2016-12-22 18:43:06 +00:00
|
|
|
}
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
/// Test selection across adjacent lines.
|
2016-12-22 18:43:06 +00:00
|
|
|
///
|
|
|
|
/// 1. [ ][ ][ ][ ][ ]
|
|
|
|
/// [ ][ ][ ][ ][ ]
|
2018-03-25 19:09:18 +00:00
|
|
|
/// 2. [ ][ B][ ][ ][ ]
|
|
|
|
/// [ ][ ][ ][ ][ ]
|
|
|
|
/// 3. [ ][ B][XX][XX][XX]
|
|
|
|
/// [XX][XE][ ][ ][ ]
|
2016-12-22 18:43:06 +00:00
|
|
|
#[test]
|
|
|
|
fn across_adjacent_lines_upward_final_cell_exclusive() {
|
2018-03-07 04:57:40 +00:00
|
|
|
let mut selection = Selection::simple(Point::new(1, Column(1)), Side::Right);
|
|
|
|
selection.update(Point::new(0, Column(1)), Side::Right);
|
2016-12-22 18:43:06 +00:00
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
assert_eq!(selection.to_range(&term(5, 2)).unwrap(), SelectionRange {
|
2019-11-03 20:59:28 +00:00
|
|
|
start: Point::new(1, Column(2)),
|
|
|
|
end: Point::new(0, Column(1)),
|
2019-06-20 15:56:09 +00:00
|
|
|
is_block: false,
|
2016-12-22 18:43:06 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
/// Test selection across adjacent lines.
|
2016-12-22 18:43:06 +00:00
|
|
|
///
|
|
|
|
/// 1. [ ][ ][ ][ ][ ]
|
|
|
|
/// [ ][ ][ ][ ][ ]
|
2018-03-25 19:09:18 +00:00
|
|
|
/// 2. [ ][ ][ ][ ][ ]
|
|
|
|
/// [ ][ B][ ][ ][ ]
|
|
|
|
/// 3. [ ][ E][XX][XX][XX]
|
|
|
|
/// [XX][XB][ ][ ][ ]
|
|
|
|
/// 4. [ E][XX][XX][XX][XX]
|
|
|
|
/// [XX][XB][ ][ ][ ]
|
2016-12-22 18:43:06 +00:00
|
|
|
#[test]
|
|
|
|
fn selection_bigger_then_smaller() {
|
2018-03-07 04:57:40 +00:00
|
|
|
let mut selection = Selection::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);
|
2016-12-22 18:43:06 +00:00
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
assert_eq!(selection.to_range(&term(5, 2)).unwrap(), SelectionRange {
|
2019-11-03 20:59:28 +00:00
|
|
|
start: Point::new(1, Column(1)),
|
|
|
|
end: Point::new(0, Column(1)),
|
2019-06-20 15:56:09 +00:00
|
|
|
is_block: false,
|
2016-12-22 18:43:06 +00:00
|
|
|
});
|
|
|
|
}
|
2018-10-20 22:30:59 +00:00
|
|
|
|
|
|
|
#[test]
|
2019-06-20 15:56:09 +00:00
|
|
|
fn line_selection() {
|
2020-01-24 22:57:22 +00:00
|
|
|
let num_lines = 10;
|
|
|
|
let num_cols = 5;
|
2020-01-20 23:56:10 +00:00
|
|
|
let mut selection = Selection::lines(Point::new(0, Column(1)));
|
|
|
|
selection.update(Point::new(5, Column(1)), Side::Right);
|
2020-01-24 22:57:22 +00:00
|
|
|
selection = selection.rotate(num_lines, num_cols, &(Line(0)..Line(num_lines)), 7).unwrap();
|
2018-10-20 22:30:59 +00:00
|
|
|
|
2020-01-24 22:57:22 +00:00
|
|
|
assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange {
|
2020-01-20 23:56:10 +00:00
|
|
|
start: Point::new(9, Column(0)),
|
|
|
|
end: Point::new(7, Column(4)),
|
2019-06-20 15:56:09 +00:00
|
|
|
is_block: false,
|
2018-10-20 22:30:59 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2019-06-20 15:56:09 +00:00
|
|
|
fn semantic_selection() {
|
2020-01-24 22:57:22 +00:00
|
|
|
let num_lines = 10;
|
|
|
|
let num_cols = 5;
|
2020-01-20 23:56:10 +00:00
|
|
|
let mut selection = Selection::semantic(Point::new(0, Column(3)));
|
|
|
|
selection.update(Point::new(5, Column(1)), Side::Right);
|
2020-01-24 22:57:22 +00:00
|
|
|
selection = selection.rotate(num_lines, num_cols, &(Line(0)..Line(num_lines)), 7).unwrap();
|
2018-10-20 22:30:59 +00:00
|
|
|
|
2020-01-24 22:57:22 +00:00
|
|
|
assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange {
|
2020-01-20 23:56:10 +00:00
|
|
|
start: Point::new(9, Column(0)),
|
|
|
|
end: Point::new(7, Column(3)),
|
2019-06-20 15:56:09 +00:00
|
|
|
is_block: false,
|
2018-10-20 22:30:59 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2019-06-20 15:56:09 +00:00
|
|
|
fn simple_selection() {
|
2020-01-24 22:57:22 +00:00
|
|
|
let num_lines = 10;
|
|
|
|
let num_cols = 5;
|
2020-01-20 23:56:10 +00:00
|
|
|
let mut selection = Selection::simple(Point::new(0, Column(3)), Side::Right);
|
|
|
|
selection.update(Point::new(5, Column(1)), Side::Right);
|
2020-01-24 22:57:22 +00:00
|
|
|
selection = selection.rotate(num_lines, num_cols, &(Line(0)..Line(num_lines)), 7).unwrap();
|
2018-10-20 22:30:59 +00:00
|
|
|
|
2020-01-24 22:57:22 +00:00
|
|
|
assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange {
|
2020-01-20 23:56:10 +00:00
|
|
|
start: Point::new(9, Column(0)),
|
|
|
|
end: Point::new(7, Column(3)),
|
2019-06-20 15:56:09 +00:00
|
|
|
is_block: false,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn block_selection() {
|
2020-01-24 22:57:22 +00:00
|
|
|
let num_lines = 10;
|
|
|
|
let num_cols = 5;
|
2020-01-20 23:56:10 +00:00
|
|
|
let mut selection = Selection::block(Point::new(0, Column(3)), Side::Right);
|
|
|
|
selection.update(Point::new(5, Column(1)), Side::Right);
|
2020-01-24 22:57:22 +00:00
|
|
|
selection = selection.rotate(num_lines, num_cols, &(Line(0)..Line(num_lines)), 7).unwrap();
|
2019-06-20 15:56:09 +00:00
|
|
|
|
2020-01-24 22:57:22 +00:00
|
|
|
assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange {
|
2020-01-20 23:56:10 +00:00
|
|
|
start: Point::new(9, Column(2)),
|
|
|
|
end: Point::new(7, Column(3)),
|
2020-01-09 23:06:41 +00:00
|
|
|
is_block: true
|
2019-04-29 14:33:25 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn double_width_expansion() {
|
|
|
|
let mut term = term(10, 1);
|
|
|
|
let mut grid = Grid::new(Line(1), Column(10), 0, Cell::default());
|
|
|
|
grid[Line(0)][Column(0)].flags.insert(Flags::WIDE_CHAR);
|
|
|
|
grid[Line(0)][Column(1)].flags.insert(Flags::WIDE_CHAR_SPACER);
|
|
|
|
grid[Line(0)][Column(8)].flags.insert(Flags::WIDE_CHAR);
|
|
|
|
grid[Line(0)][Column(9)].flags.insert(Flags::WIDE_CHAR_SPACER);
|
|
|
|
mem::swap(term.grid_mut(), &mut grid);
|
|
|
|
|
|
|
|
let mut selection = Selection::simple(Point::new(0, Column(1)), Side::Left);
|
|
|
|
selection.update(Point::new(0, Column(8)), Side::Right);
|
|
|
|
|
2020-01-20 23:56:10 +00:00
|
|
|
assert_eq!(selection.to_range(&term).unwrap(), SelectionRange {
|
2019-11-03 20:59:28 +00:00
|
|
|
start: Point::new(0, Column(0)),
|
|
|
|
end: Point::new(0, Column(9)),
|
2019-06-20 15:56:09 +00:00
|
|
|
is_block: false,
|
2018-10-20 22:30:59 +00:00
|
|
|
});
|
|
|
|
}
|
2019-09-13 23:51:14 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn simple_is_empty() {
|
|
|
|
let mut selection = Selection::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::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());
|
|
|
|
}
|
2020-01-24 22:57:22 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn rotate_in_region_up() {
|
|
|
|
let num_lines = 10;
|
|
|
|
let num_cols = 5;
|
|
|
|
let mut selection = Selection::simple(Point::new(2, Column(3)), Side::Right);
|
|
|
|
selection.update(Point::new(5, Column(1)), Side::Right);
|
|
|
|
selection =
|
|
|
|
selection.rotate(num_lines, num_cols, &(Line(1)..Line(num_lines - 1)), 4).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange {
|
|
|
|
start: Point::new(8, Column(0)),
|
|
|
|
end: Point::new(6, Column(3)),
|
|
|
|
is_block: false,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn rotate_in_region_down() {
|
|
|
|
let num_lines = 10;
|
|
|
|
let num_cols = 5;
|
|
|
|
let mut selection = Selection::simple(Point::new(5, Column(3)), Side::Right);
|
|
|
|
selection.update(Point::new(8, Column(1)), Side::Left);
|
|
|
|
selection =
|
|
|
|
selection.rotate(num_lines, num_cols, &(Line(1)..Line(num_lines - 1)), -5).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange {
|
|
|
|
start: Point::new(3, Column(1)),
|
|
|
|
end: Point::new(1, Column(num_cols - 1)),
|
|
|
|
is_block: false,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn rotate_in_region_up_block() {
|
|
|
|
let num_lines = 10;
|
|
|
|
let num_cols = 5;
|
|
|
|
let mut selection = Selection::block(Point::new(2, Column(3)), Side::Right);
|
|
|
|
selection.update(Point::new(5, Column(1)), Side::Right);
|
|
|
|
selection =
|
|
|
|
selection.rotate(num_lines, num_cols, &(Line(1)..Line(num_lines - 1)), 4).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange {
|
|
|
|
start: Point::new(8, Column(2)),
|
|
|
|
end: Point::new(6, Column(3)),
|
|
|
|
is_block: true,
|
|
|
|
});
|
|
|
|
}
|
2016-12-22 18:43:06 +00:00
|
|
|
}
|