Fix first cell when selection is off screen

Since the expansion of the selection was done after clamping it to the
grid, the selection would incorrectly move the clamped start over by one
cell when the start was to the right of the original column. By
resetting the side of the start point to `Left` before expanding, this
can be circumvented.

This also resolves a regression which broke backwards bracket selection.

Fixes #3223.
This commit is contained in:
Christian Duerr 2020-01-21 00:56:10 +01:00 committed by GitHub
parent 5e22512fe6
commit c84cd0fdb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 336 additions and 335 deletions

View File

@ -31,6 +31,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- CLI parameters discarded when config is reload
- Blurred icons in KDE task switcher (alacritty.ico is now high-res)
- Consecutive builds failing on macOS due to preexisting `/Application` symlink
- Block selection starting from first column after beginning leaves the scrollback
- Incorrect selection status of the first cell when selection is off screen
- Backwards bracket selection
### Removed

View File

@ -158,22 +158,16 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
}
}
pub fn buffer_to_visible(&self, point: impl Into<Point<usize>>) -> Point<usize> {
pub fn buffer_to_visible(&self, point: impl Into<Point<usize>>) -> Option<Point<usize>> {
let mut point = point.into();
let offset = point.line.saturating_sub(self.display_offset);
if point.line < self.display_offset {
point.col = self.num_cols();
point.line = self.num_lines().0 - 1;
} else if offset >= *self.num_lines() {
point.col = Column(0);
point.line = 0;
} else {
point.line = self.lines.0 - offset - 1;
if point.line < self.display_offset || point.line >= self.display_offset + self.lines.0 {
return None;
}
point
point.line = self.lines.0 + self.display_offset - point.line - 1;
Some(point)
}
pub fn visible_to_buffer(&self, point: Point) -> Point<usize> {

View File

@ -18,13 +18,55 @@
//! 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::Range;
use crate::index::{Column, Line, Point, Side};
use crate::index::{Column, Point, Side};
use crate::term::cell::Flags;
use crate::term::{Search, Term};
/// Describes a region of a 2-dimensional area
/// A Point and side within that point.
#[derive(Debug, Clone, PartialEq)]
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))
}
}
/// Describes a region of a 2-dimensional area.
///
/// Used to track a text selection. There are three supported modes, each with its own constructor:
/// [`simple`], [`semantic`], and [`lines`]. The [`simple`] mode precisely tracks which cells are
@ -43,96 +85,61 @@ use crate::term::{Search, Term};
#[derive(Debug, Clone, PartialEq)]
pub enum Selection {
Simple {
/// The region representing start and end of cursor movement
/// The region representing start and end of cursor movement.
region: Range<Anchor>,
},
Block {
/// The region representing start and end of cursor movement
/// The region representing start and end of cursor movement.
region: Range<Anchor>,
},
Semantic {
/// The region representing start and end of cursor movement
region: Range<Point<isize>>,
/// The region representing start and end of cursor movement.
region: Range<Point<usize>>,
},
Lines {
/// The region representing start and end of cursor movement
region: Range<Point<isize>>,
/// The region representing start and end of cursor movement.
region: Range<Point<usize>>,
},
}
/// A Point and side within that point.
#[derive(Debug, Clone, PartialEq)]
pub struct Anchor {
point: Point<isize>,
side: Side,
}
impl Anchor {
fn new(point: Point<isize>, side: Side) -> Anchor {
Anchor { point, side }
}
}
/// A type that has 2-dimensional boundaries
pub trait Dimensions {
/// Get the size of the area
fn dimensions(&self) -> Point;
}
impl Selection {
pub fn rotate(&mut self, offset: isize) {
match *self {
Selection::Simple { ref mut region } | Selection::Block { ref mut region } => {
region.start.point.line += offset;
region.end.point.line += offset;
},
Selection::Semantic { ref mut region } | Selection::Lines { ref mut region } => {
region.start.line += offset;
region.end.line += offset;
},
}
}
pub fn simple(location: Point<usize>, side: Side) -> Selection {
Selection::Simple {
region: Range {
start: Anchor::new(location.into(), side),
end: Anchor::new(location.into(), side),
},
region: Range { start: Anchor::new(location, side), end: Anchor::new(location, side) },
}
}
pub fn block(location: Point<usize>, side: Side) -> Selection {
Selection::Block {
region: Range {
start: Anchor::new(location.into(), side),
end: Anchor::new(location.into(), side),
},
region: Range { start: Anchor::new(location, side), end: Anchor::new(location, side) },
}
}
pub fn semantic(point: Point<usize>) -> Selection {
Selection::Semantic { region: Range { start: point.into(), end: point.into() } }
Selection::Semantic { region: Range { start: point, end: point } }
}
pub fn lines(point: Point<usize>) -> Selection {
Selection::Lines { region: Range { start: point.into(), end: point.into() } }
Selection::Lines { region: Range { start: point, end: point } }
}
pub fn update(&mut self, location: Point<usize>, side: Side) {
// Always update the `end`; can normalize later during span generation.
match *self {
Selection::Simple { ref mut region } | Selection::Block { ref mut region } => {
region.end = Anchor::new(location.into(), side);
},
Selection::Semantic { ref mut region } | Selection::Lines { ref mut region } => {
region.end = location.into();
},
let (_, end) = self.points_mut();
*end = location;
if let Some((_, end_side)) = self.sides_mut() {
*end_side = side;
}
}
pub fn rotate(&mut self, offset: isize) {
let (start, end) = self.points_mut();
start.line = usize::try_from(start.line as isize + offset).unwrap_or(0);
end.line = usize::try_from(end.line as isize + offset).unwrap_or(0);
}
pub fn is_empty(&self) -> bool {
match *self {
match self {
Selection::Simple { ref region } => {
let (start, end) =
if Selection::points_need_swap(region.start.point, region.end.point) {
@ -165,266 +172,260 @@ impl Selection {
}
}
pub fn to_span<T>(&self, term: &Term<T>) -> Option<Span> {
// Get both sides of the selection
let (mut start, mut end) = match *self {
Selection::Simple { ref region } | Selection::Block { ref region } => {
(region.start.point, region.end.point)
},
Selection::Semantic { ref region } | Selection::Lines { ref region } => {
(region.start, region.end)
},
/// 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();
// Get selection boundaries
let points = self.points();
let (start, end) = (*points.0, *points.1);
// Get selection sides, falling back to `Side::Left` if it will not be used
let sides = self.sides().unwrap_or((&Side::Left, &Side::Left));
let (start_side, end_side) = (*sides.0, *sides.1);
// Order start above the end
let (start, end) = if Self::points_need_swap(start, end) {
(Anchor { point: end, side: end_side }, Anchor { point: start, side: start_side })
} else {
(Anchor { point: start, side: start_side }, Anchor { point: end, side: end_side })
};
// Order the start/end
let needs_swap = Selection::points_need_swap(start, end);
if needs_swap {
std::mem::swap(&mut start, &mut end);
// Clamp to inside the grid buffer
let (start, end) = Self::grid_clamp(start, end, self.is_block(), grid.len()).ok()?;
let range = match self {
Self::Simple { .. } => self.range_simple(start, end, num_cols),
Self::Block { .. } => self.range_block(start, end),
Self::Semantic { .. } => Self::range_semantic(term, start.point, end.point),
Self::Lines { .. } => Self::range_lines(term, start.point, end.point),
};
// 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)
};
// 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;
}
}
// 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)
{
range.start = prev;
}
}
}
// Clamp to visible region in grid/normal
let num_cols = term.dimensions().col;
let num_lines = term.dimensions().line.0 as isize;
let (start, end) = Selection::grid_clamp(start, end, num_lines, num_cols)?;
let span = match *self {
Selection::Simple { ref region } => {
let (start_side, end_side) = if needs_swap {
(region.end.side, region.start.side)
} else {
(region.start.side, region.end.side)
};
self.span_simple(term, start, end, start_side, end_side)
},
Selection::Block { ref region } => {
let (start_side, end_side) = if needs_swap {
(region.end.side, region.start.side)
} else {
(region.start.side, region.end.side)
};
self.span_block(start, end, start_side, end_side)
},
Selection::Semantic { .. } => Selection::span_semantic(term, start, end),
Selection::Lines { .. } => Selection::span_lines(term, start, end),
};
// Expand selection across double-width cells
span.map(|mut span| {
let grid = term.grid();
// 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)
};
// Include all double-width cells and placeholders at top left of selection
if span.start.col < num_cols {
// Expand from wide char spacer to wide char
if span.start.line + 1 != grid.len() || span.start.col.0 != 0 {
let prev = span.start.sub(num_cols.0, 1, true);
if flag_at(span.start, Flags::WIDE_CHAR_SPACER)
&& flag_at(prev, Flags::WIDE_CHAR)
{
span.start = prev;
}
}
// Expand from wide char to wide char spacer for linewrapping
if span.start.line + 1 != grid.len() || span.start.col.0 != 0 {
let prev = span.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)
{
span.start = prev;
}
}
// 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);
}
// Include all double-width cells and placeholders at bottom right of selection
if span.end.line != 0 || span.end.col < num_cols {
// Expand from wide char spacer for linewrapping to wide char
if (span.end.line + 1 != grid.len() || span.end.col.0 != 0)
&& flag_at(span.end, Flags::WIDE_CHAR_SPACER)
&& !flag_at(span.end.sub(num_cols.0, 1, true), Flags::WIDE_CHAR)
{
span.end = span.end.add(num_cols.0, 1, true);
}
// Expand from wide char to wide char spacer
if flag_at(span.end, Flags::WIDE_CHAR) {
span.end = span.end.add(num_cols.0, 1, true);
}
// 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);
}
}
span
})
range
}
// Bring start and end points in the correct order
fn points_need_swap(start: Point<isize>, end: Point<isize>) -> bool {
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 the grid to prevent out of bounds errors
/// Clamp selection inside grid to prevent OOB.
fn grid_clamp(
mut start: Point<isize>,
mut end: Point<isize>,
lines: isize,
cols: Column,
) -> Option<(Point<isize>, Point<isize>)> {
if start.line >= lines {
// Don't show selection above visible region
if end.line >= lines {
return None;
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(());
}
// Clamp selection above viewport to visible region
start.line = lines - 1;
start.col = Column(0);
// 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;
}
if end.line < 0 {
// Don't show selection below visible region
if start.line < 0 {
return None;
}
Ok((start, end))
}
// Clamp selection below viewport to visible region
end.line = 0;
end.col = cols - 1;
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 });
}
}
Some((start, end))
start = term.semantic_search_left(start);
end = term.semantic_search_right(end);
Some(SelectionRange { start, end, is_block: false })
}
fn span_semantic<T>(term: &T, start: Point<isize>, end: Point<isize>) -> Option<Span>
where
T: Search + Dimensions,
{
let (start, end) = if start == end {
if let Some(end) = term.bracket_search(start.into()) {
(start.into(), end)
} else {
(term.semantic_search_left(start.into()), term.semantic_search_right(end.into()))
}
} else {
(term.semantic_search_left(start.into()), term.semantic_search_right(end.into()))
};
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(Span { start, end, is_block: false })
Some(SelectionRange { start, end, is_block: false })
}
fn span_lines<T>(term: &T, start: Point<isize>, end: Point<isize>) -> Option<Span>
where
T: Search,
{
let start = term.line_search_left(start.into());
let end = term.line_search_right(end.into());
Some(Span { start, end, is_block: false })
}
fn span_simple<T>(
fn range_simple(
&self,
term: &T,
mut start: Point<isize>,
mut end: Point<isize>,
start_side: Side,
end_side: Side,
) -> Option<Span>
where
T: Dimensions,
{
mut start: Anchor,
mut end: Anchor,
num_cols: Column,
) -> Option<SelectionRange> {
if self.is_empty() {
return None;
}
// Remove last cell if selection ends to the left of a cell
if end_side == Side::Left && start != end {
if end.side == Side::Left && start.point != end.point {
// Special case when selection ends to left of first cell
if end.col == Column(0) {
end.col = term.dimensions().col - 1;
end.line += 1;
if end.point.col == Column(0) {
end.point.col = num_cols;
end.point.line += 1;
} else {
end.col -= 1;
end.point.col -= 1;
}
}
// Remove first cell if selection starts at the right of a cell
if start_side == Side::Right && start != end {
start.col += 1;
if start.side == Side::Right && start.point != end.point {
start.point.col += 1;
}
// Return the selection with all cells inclusive
Some(Span { start: start.into(), end: end.into(), is_block: false })
Some(SelectionRange { start: start.point, end: end.point, is_block: false })
}
fn span_block(
&self,
mut start: Point<isize>,
mut end: Point<isize>,
mut start_side: Side,
mut end_side: Side,
) -> Option<Span> {
fn range_block(&self, mut start: Anchor, mut end: Anchor) -> Option<SelectionRange> {
if self.is_empty() {
return None;
}
// Always go top-left -> bottom-right
if start.col > end.col {
std::mem::swap(&mut start_side, &mut end_side);
std::mem::swap(&mut start.col, &mut end.col);
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);
}
// Remove last cell if selection ends to the left of a cell
if end_side == Side::Left && start != end && end.col.0 > 0 {
end.col -= 1;
if end.side == Side::Left && start.point != end.point && end.point.col.0 > 0 {
end.point.col -= 1;
}
// Remove first cell if selection starts at the right of a cell
if start_side == Side::Right && start != end {
start.col += 1;
if start.side == Side::Right && start.point != end.point {
start.point.col += 1;
}
// Return the selection with all cells inclusive
Some(Span { start: start.into(), end: end.into(), is_block: true })
Some(SelectionRange { start: start.point, end: end.point, is_block: true })
}
fn points(&self) -> (&Point<usize>, &Point<usize>) {
match self {
Self::Simple { ref region } | Self::Block { ref region } => {
(&region.start.point, &region.end.point)
},
Self::Semantic { ref region } | Self::Lines { ref region } => {
(&region.start, &region.end)
},
}
}
fn points_mut(&mut self) -> (&mut Point<usize>, &mut Point<usize>) {
match self {
Self::Simple { ref mut region } | Self::Block { ref mut region } => {
(&mut region.start.point, &mut region.end.point)
},
Self::Semantic { ref mut region } | Self::Lines { ref mut region } => {
(&mut region.start, &mut region.end)
},
}
}
fn sides(&self) -> Option<(&Side, &Side)> {
match self {
Self::Simple { ref region } | Self::Block { ref region } => {
Some((&region.start.side, &region.end.side))
},
Self::Semantic { .. } | Self::Lines { .. } => None,
}
}
fn sides_mut(&mut self) -> Option<(&mut Side, &mut Side)> {
match self {
Self::Simple { ref mut region } | Self::Block { ref mut region } => {
Some((&mut region.start.side, &mut region.end.side))
},
Self::Semantic { .. } | Self::Lines { .. } => None,
}
}
fn is_block(&self) -> bool {
match self {
Self::Block { .. } => true,
_ => false,
}
}
}
/// Represents a span of selected cells
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Span {
/// Start point from bottom of buffer
pub start: Point<usize>,
/// End point towards top of buffer
pub end: Point<usize>,
/// Whether this selection is a block selection
pub is_block: bool,
}
pub struct SelectionRange {
start: Point,
end: Point,
is_block: bool,
}
impl SelectionRange {
pub fn new(start: Point, end: Point, is_block: bool) -> Self {
Self { start, end, is_block }
}
pub fn contains(&self, col: Column, line: Line) -> bool {
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))
}
}
/// Tests for selection
/// 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
@ -437,7 +438,7 @@ impl SelectionRange {
mod test {
use std::mem;
use super::{Selection, Span};
use super::{Selection, SelectionRange};
use crate::clipboard::Clipboard;
use crate::config::MockConfig;
use crate::event::{Event, EventListener};
@ -464,7 +465,7 @@ mod test {
Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock)
}
/// Test case of single cell selection
/// Test case of single cell selection.
///
/// 1. [ ]
/// 2. [B ]
@ -475,14 +476,14 @@ mod test {
let mut selection = Selection::simple(location, Side::Left);
selection.update(location, Side::Right);
assert_eq!(selection.to_span(&term(1, 1)).unwrap(), Span {
assert_eq!(selection.to_range(&term(1, 1)).unwrap(), SelectionRange {
start: location,
end: location,
is_block: false
});
}
/// Test case of single cell selection
/// Test case of single cell selection.
///
/// 1. [ ]
/// 2. [ B]
@ -493,14 +494,14 @@ mod test {
let mut selection = Selection::simple(location, Side::Right);
selection.update(location, Side::Left);
assert_eq!(selection.to_span(&term(1, 1)).unwrap(), Span {
assert_eq!(selection.to_range(&term(1, 1)).unwrap(), SelectionRange {
start: location,
end: location,
is_block: false
});
}
/// Test adjacent cell selection from left to right
/// Test adjacent cell selection from left to right.
///
/// 1. [ ][ ]
/// 2. [ B][ ]
@ -510,10 +511,10 @@ mod test {
let mut selection = Selection::simple(Point::new(0, Column(0)), Side::Right);
selection.update(Point::new(0, Column(1)), Side::Left);
assert_eq!(selection.to_span(&term(2, 1)), None);
assert_eq!(selection.to_range(&term(2, 1)), None);
}
/// Test adjacent cell selection from right to left
/// Test adjacent cell selection from right to left.
///
/// 1. [ ][ ]
/// 2. [ ][B ]
@ -523,11 +524,10 @@ mod test {
let mut selection = Selection::simple(Point::new(0, Column(1)), Side::Left);
selection.update(Point::new(0, Column(0)), Side::Right);
assert_eq!(selection.to_span(&term(2, 1)), None);
assert_eq!(selection.to_range(&term(2, 1)), None);
}
/// Test selection across adjacent lines
///
/// Test selection across adjacent lines.
///
/// 1. [ ][ ][ ][ ][ ]
/// [ ][ ][ ][ ][ ]
@ -540,15 +540,14 @@ mod test {
let mut selection = Selection::simple(Point::new(1, Column(1)), Side::Right);
selection.update(Point::new(0, Column(1)), Side::Right);
assert_eq!(selection.to_span(&term(5, 2)).unwrap(), Span {
assert_eq!(selection.to_range(&term(5, 2)).unwrap(), SelectionRange {
start: Point::new(1, Column(2)),
end: Point::new(0, Column(1)),
is_block: false,
});
}
/// Test selection across adjacent lines
///
/// Test selection across adjacent lines.
///
/// 1. [ ][ ][ ][ ][ ]
/// [ ][ ][ ][ ][ ]
@ -564,7 +563,7 @@ mod test {
selection.update(Point::new(1, Column(1)), Side::Right);
selection.update(Point::new(1, Column(0)), Side::Right);
assert_eq!(selection.to_span(&term(5, 2)).unwrap(), Span {
assert_eq!(selection.to_range(&term(5, 2)).unwrap(), SelectionRange {
start: Point::new(1, Column(1)),
end: Point::new(0, Column(1)),
is_block: false,
@ -573,52 +572,52 @@ mod test {
#[test]
fn line_selection() {
let mut selection = Selection::lines(Point::new(0, Column(0)));
selection.update(Point::new(5, Column(3)), Side::Right);
selection.rotate(-3);
let mut selection = Selection::lines(Point::new(0, Column(1)));
selection.update(Point::new(5, Column(1)), Side::Right);
selection.rotate(7);
assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span {
start: Point::new(2, Column(0)),
end: Point::new(0, Column(4)),
assert_eq!(selection.to_range(&term(5, 10)).unwrap(), SelectionRange {
start: Point::new(9, Column(0)),
end: Point::new(7, Column(4)),
is_block: false,
});
}
#[test]
fn semantic_selection() {
let mut selection = Selection::semantic(Point::new(0, Column(0)));
selection.update(Point::new(5, Column(3)), Side::Right);
selection.rotate(-3);
let mut selection = Selection::semantic(Point::new(0, Column(3)));
selection.update(Point::new(5, Column(1)), Side::Right);
selection.rotate(7);
assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span {
start: Point::new(2, Column(3)),
end: Point::new(0, Column(4)),
assert_eq!(selection.to_range(&term(5, 10)).unwrap(), SelectionRange {
start: Point::new(9, Column(0)),
end: Point::new(7, Column(3)),
is_block: false,
});
}
#[test]
fn simple_selection() {
let mut selection = Selection::simple(Point::new(0, Column(0)), Side::Right);
selection.update(Point::new(5, Column(3)), Side::Right);
selection.rotate(-3);
let mut selection = Selection::simple(Point::new(0, Column(3)), Side::Right);
selection.update(Point::new(5, Column(1)), Side::Right);
selection.rotate(7);
assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span {
start: Point::new(2, Column(4)),
end: Point::new(0, Column(4)),
assert_eq!(selection.to_range(&term(5, 10)).unwrap(), SelectionRange {
start: Point::new(9, Column(0)),
end: Point::new(7, Column(3)),
is_block: false,
});
}
#[test]
fn block_selection() {
let mut selection = Selection::block(Point::new(0, Column(0)), Side::Right);
selection.update(Point::new(5, Column(3)), Side::Right);
selection.rotate(-3);
let mut selection = Selection::block(Point::new(0, Column(3)), Side::Right);
selection.update(Point::new(5, Column(1)), Side::Right);
selection.rotate(7);
assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span {
start: Point::new(2, Column(4)),
end: Point::new(0, Column(4)),
assert_eq!(selection.to_range(&term(5, 10)).unwrap(), SelectionRange {
start: Point::new(9, Column(2)),
end: Point::new(7, Column(3)),
is_block: true
});
}
@ -636,7 +635,7 @@ mod test {
let mut selection = Selection::simple(Point::new(0, Column(1)), Side::Left);
selection.update(Point::new(0, Column(8)), Side::Right);
assert_eq!(selection.to_span(&term).unwrap(), Span {
assert_eq!(selection.to_range(&term).unwrap(), SelectionRange {
start: Point::new(0, Column(0)),
end: Point::new(0, Column(9)),
is_block: false,

View File

@ -32,7 +32,7 @@ use crate::grid::{
BidirectionalIterator, DisplayIter, Grid, GridCell, IndexRegion, Indexed, Scroll,
};
use crate::index::{self, Column, IndexRange, Line, Point};
use crate::selection::{self, Selection, SelectionRange, Span};
use crate::selection::{Selection, SelectionRange};
use crate::term::cell::{Cell, Flags, LineLength};
use crate::term::color::Rgb;
#[cfg(windows)]
@ -179,17 +179,6 @@ impl<T> Search for Term<T> {
}
}
impl<T> selection::Dimensions for Term<T> {
fn dimensions(&self) -> Point {
let line = if self.mode.contains(TermMode::ALT_SCREEN) {
self.grid.num_lines()
} else {
Line(self.grid.len())
};
Point { col: self.grid.num_cols(), line }
}
}
/// A key for caching cursor glyphs
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash, Deserialize)]
pub struct CursorKey {
@ -214,7 +203,7 @@ pub struct RenderableCellsIter<'a, C> {
cursor_style: CursorStyle,
config: &'a Config<C>,
colors: &'a color::List,
selection: Option<SelectionRange>,
selection: Option<SelectionRange<Line>>,
}
impl<'a, C> RenderableCellsIter<'a, C> {
@ -225,30 +214,46 @@ impl<'a, C> RenderableCellsIter<'a, C> {
fn new<'b, T>(
term: &'b Term<T>,
config: &'b Config<C>,
selection: Option<Span>,
selection: Option<SelectionRange>,
mut cursor_style: CursorStyle,
) -> RenderableCellsIter<'b, C> {
let grid = &term.grid;
let num_cols = grid.num_cols();
let num_lines = grid.num_lines();
let cursor_offset = grid.line_to_offset(term.cursor.point.line);
let inner = grid.display_iter();
let selection_range = selection.map(|span| {
let selection_range = selection.and_then(|span| {
let (limit_start, limit_end) = if span.is_block {
(span.start.col, span.end.col)
} else {
(Column(0), term.cols() - 1)
(Column(0), num_cols - 1)
};
// Get on-screen lines of the selection's locations
let mut start = term.buffer_to_visible(span.start);
let mut end = term.buffer_to_visible(span.end);
let start = term.buffer_to_visible(span.start);
let end = term.buffer_to_visible(span.end);
// Clamp visible selection to the viewport
let (mut start, mut end) = match (start, end) {
(Some(start), Some(end)) => (start, end),
(Some(start), None) => {
let end = Point::new(num_lines.0 - 1, num_cols - 1);
(start, end)
},
(None, Some(end)) => {
let start = Point::new(0, Column(0));
(start, end)
},
(None, None) => return None,
};
// Trim start/end with partially visible block selection
start.col = max(limit_start, start.col);
end.col = min(limit_end, end.col);
SelectionRange::new(start.into(), end.into(), span.is_block)
Some(SelectionRange::new(start.into(), end.into(), span.is_block))
});
// Load cursor glyph
@ -256,7 +261,7 @@ impl<'a, C> RenderableCellsIter<'a, C> {
let cursor_visible = term.mode.contains(TermMode::SHOW_CURSOR) && grid.contains(cursor);
let cursor_key = if cursor_visible {
let is_wide =
grid[cursor].flags.contains(Flags::WIDE_CHAR) && (cursor.col + 1) < grid.num_cols();
grid[cursor].flags.contains(Flags::WIDE_CHAR) && (cursor.col + 1) < num_cols;
Some(CursorKey { style: cursor_style, is_wide })
} else {
// Use hidden cursor so text will not get inverted
@ -938,7 +943,7 @@ impl<T> Term<T> {
/// Convert the active selection to a String.
pub fn selection_to_string(&self) -> Option<String> {
let selection = self.grid.selection.clone()?;
let Span { start, end, is_block } = selection.to_span(self)?;
let SelectionRange { start, end, is_block } = selection.to_range(self)?;
let mut res = String::new();
@ -1019,7 +1024,7 @@ impl<T> Term<T> {
self.grid.visible_to_buffer(point)
}
pub fn buffer_to_visible(&self, point: impl Into<Point<usize>>) -> Point<usize> {
pub fn buffer_to_visible(&self, point: impl Into<Point<usize>>) -> Option<Point<usize>> {
self.grid.buffer_to_visible(point)
}
@ -1043,7 +1048,7 @@ impl<T> Term<T> {
/// background color. Cells with an alternate background color are
/// considered renderable as are cells with any text content.
pub fn renderable_cells<'b, C>(&'b self, config: &'b Config<C>) -> RenderableCellsIter<'_, C> {
let selection = self.grid.selection.as_ref().and_then(|s| s.to_span(self));
let selection = self.grid.selection.as_ref().and_then(|s| s.to_range(self));
let cursor = if self.is_focused || !config.cursor.unfocused_hollow() {
self.cursor_style.unwrap_or(self.default_cursor_style)