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 - CLI parameters discarded when config is reload
- Blurred icons in KDE task switcher (alacritty.ico is now high-res) - Blurred icons in KDE task switcher (alacritty.ico is now high-res)
- Consecutive builds failing on macOS due to preexisting `/Application` symlink - 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 ### 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 mut point = point.into();
let offset = point.line.saturating_sub(self.display_offset); if point.line < self.display_offset || point.line >= self.display_offset + self.lines.0 {
return None;
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;
} }
point point.line = self.lines.0 + self.display_offset - point.line - 1;
Some(point)
} }
pub fn visible_to_buffer(&self, point: Point) -> Point<usize> { 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 //! finalized when the button is released. The selection should be cleared
//! when text is added/removed/scrolled on the screen. The selection should //! when text is added/removed/scrolled on the screen. The selection should
//! also be cleared if the user clicks off of the selection. //! also be cleared if the user clicks off of the selection.
use std::convert::TryFrom;
use std::mem;
use std::ops::Range; 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::cell::Flags;
use crate::term::{Search, Term}; 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: /// 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 /// [`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)] #[derive(Debug, Clone, PartialEq)]
pub enum Selection { pub enum Selection {
Simple { Simple {
/// The region representing start and end of cursor movement /// The region representing start and end of cursor movement.
region: Range<Anchor>, region: Range<Anchor>,
}, },
Block { Block {
/// The region representing start and end of cursor movement /// The region representing start and end of cursor movement.
region: Range<Anchor>, region: Range<Anchor>,
}, },
Semantic { Semantic {
/// The region representing start and end of cursor movement /// The region representing start and end of cursor movement.
region: Range<Point<isize>>, region: Range<Point<usize>>,
}, },
Lines { Lines {
/// The region representing start and end of cursor movement /// The region representing start and end of cursor movement.
region: Range<Point<isize>>, 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 { 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 { pub fn simple(location: Point<usize>, side: Side) -> Selection {
Selection::Simple { Selection::Simple {
region: Range { region: Range { start: Anchor::new(location, side), end: Anchor::new(location, side) },
start: Anchor::new(location.into(), side),
end: Anchor::new(location.into(), side),
},
} }
} }
pub fn block(location: Point<usize>, side: Side) -> Selection { pub fn block(location: Point<usize>, side: Side) -> Selection {
Selection::Block { Selection::Block {
region: Range { region: Range { start: Anchor::new(location, side), end: Anchor::new(location, side) },
start: Anchor::new(location.into(), side),
end: Anchor::new(location.into(), side),
},
} }
} }
pub fn semantic(point: Point<usize>) -> Selection { 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 { 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) { pub fn update(&mut self, location: Point<usize>, side: Side) {
// Always update the `end`; can normalize later during span generation. let (_, end) = self.points_mut();
match *self { *end = location;
Selection::Simple { ref mut region } | Selection::Block { ref mut region } => {
region.end = Anchor::new(location.into(), side); if let Some((_, end_side)) = self.sides_mut() {
}, *end_side = side;
Selection::Semantic { ref mut region } | Selection::Lines { ref mut region } => {
region.end = location.into();
},
} }
} }
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 { pub fn is_empty(&self) -> bool {
match *self { match self {
Selection::Simple { ref region } => { Selection::Simple { ref region } => {
let (start, end) = let (start, end) =
if Selection::points_need_swap(region.start.point, region.end.point) { 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> { /// Convert selection to grid coordinates.
// Get both sides of the selection pub fn to_range<T>(&self, term: &Term<T>) -> Option<SelectionRange> {
let (mut start, mut end) = match *self { let grid = term.grid();
Selection::Simple { ref region } | Selection::Block { ref region } => { let num_cols = grid.num_cols();
(region.start.point, region.end.point)
}, // Get selection boundaries
Selection::Semantic { ref region } | Selection::Lines { ref region } => { let points = self.points();
(region.start, region.end) 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 // Clamp to inside the grid buffer
let needs_swap = Selection::points_need_swap(start, end); let (start, end) = Self::grid_clamp(start, end, self.is_block(), grid.len()).ok()?;
if needs_swap {
std::mem::swap(&mut start, &mut end); 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 // Include all double-width cells and placeholders at bottom right of selection
let num_cols = term.dimensions().col; if range.end.line != 0 || range.end.col < num_cols {
let num_lines = term.dimensions().line.0 as isize; // Expand from wide char spacer for linewrapping to wide char
let (start, end) = Selection::grid_clamp(start, end, num_lines, num_cols)?; if (range.end.line + 1 != grid.len() || range.end.col.0 != 0)
&& flag_at(range.end, Flags::WIDE_CHAR_SPACER)
let span = match *self { && !flag_at(range.end.sub(num_cols.0, 1, true), Flags::WIDE_CHAR)
Selection::Simple { ref region } => { {
let (start_side, end_side) = if needs_swap { range.end = range.end.add(num_cols.0, 1, true);
(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 // Expand from wide char to wide char spacer
if span.end.line != 0 || span.end.col < num_cols { if flag_at(range.end, Flags::WIDE_CHAR) {
// Expand from wide char spacer for linewrapping to wide char range.end = range.end.add(num_cols.0, 1, true);
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);
}
} }
}
span range
})
} }
// Bring start and end points in the correct order // 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 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( fn grid_clamp(
mut start: Point<isize>, mut start: Anchor,
mut end: Point<isize>, end: Anchor,
lines: isize, is_block: bool,
cols: Column, lines: usize,
) -> Option<(Point<isize>, Point<isize>)> { ) -> Result<(Anchor, Anchor), ()> {
if start.line >= lines { // Clamp selection inside of grid to prevent OOB
// Don't show selection above visible region if start.point.line >= lines {
if end.line >= lines { // Remove selection if it is fully out of the grid
return None; if end.point.line >= lines {
return Err(());
} }
// Clamp selection above viewport to visible region // Clamp to grid if it is still partially visible
start.line = lines - 1; if !is_block {
start.col = Column(0); start.side = Side::Left;
start.point.col = Column(0);
}
start.point.line = lines - 1;
} }
if end.line < 0 { Ok((start, end))
// Don't show selection below visible region }
if start.line < 0 {
return None;
}
// Clamp selection below viewport to visible region fn range_semantic<T>(
end.line = 0; term: &Term<T>,
end.col = cols - 1; 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> fn range_lines<T>(
where term: &Term<T>,
T: Search + Dimensions, mut start: Point<usize>,
{ mut end: Point<usize>,
let (start, end) = if start == end { ) -> Option<SelectionRange> {
if let Some(end) = term.bracket_search(start.into()) { start = term.line_search_left(start);
(start.into(), end) end = term.line_search_right(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()))
};
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> fn range_simple(
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>(
&self, &self,
term: &T, mut start: Anchor,
mut start: Point<isize>, mut end: Anchor,
mut end: Point<isize>, num_cols: Column,
start_side: Side, ) -> Option<SelectionRange> {
end_side: Side,
) -> Option<Span>
where
T: Dimensions,
{
if self.is_empty() { if self.is_empty() {
return None; return None;
} }
// Remove last cell if selection ends to the left of a cell // 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 // Special case when selection ends to left of first cell
if end.col == Column(0) { if end.point.col == Column(0) {
end.col = term.dimensions().col - 1; end.point.col = num_cols;
end.line += 1; end.point.line += 1;
} else { } else {
end.col -= 1; end.point.col -= 1;
} }
} }
// Remove first cell if selection starts at the right of a cell // Remove first cell if selection starts at the right of a cell
if start_side == Side::Right && start != end { if start.side == Side::Right && start.point != end.point {
start.col += 1; start.point.col += 1;
} }
// Return the selection with all cells inclusive Some(SelectionRange { start: start.point, end: end.point, is_block: false })
Some(Span { start: start.into(), end: end.into(), is_block: false })
} }
fn span_block( fn range_block(&self, mut start: Anchor, mut end: Anchor) -> Option<SelectionRange> {
&self,
mut start: Point<isize>,
mut end: Point<isize>,
mut start_side: Side,
mut end_side: Side,
) -> Option<Span> {
if self.is_empty() { if self.is_empty() {
return None; return None;
} }
// Always go top-left -> bottom-right // Always go top-left -> bottom-right
if start.col > end.col { if start.point.col > end.point.col {
std::mem::swap(&mut start_side, &mut end_side); mem::swap(&mut start.side, &mut end.side);
std::mem::swap(&mut start.col, &mut end.col); mem::swap(&mut start.point.col, &mut end.point.col);
} }
// Remove last cell if selection ends to the left of a cell // Remove last cell if selection ends to the left of a cell
if end_side == Side::Left && start != end && end.col.0 > 0 { if end.side == Side::Left && start.point != end.point && end.point.col.0 > 0 {
end.col -= 1; end.point.col -= 1;
} }
// Remove first cell if selection starts at the right of a cell // Remove first cell if selection starts at the right of a cell
if start_side == Side::Right && start != end { if start.side == Side::Right && start.point != end.point {
start.col += 1; start.point.col += 1;
} }
// Return the selection with all cells inclusive Some(SelectionRange { start: start.point, end: end.point, is_block: true })
Some(Span { start: start.into(), end: end.into(), 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 /// Tests for selection.
#[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
/// ///
/// There are comments on all of the tests describing the selection. Pictograms /// There are comments on all of the tests describing the selection. Pictograms
/// are used to avoid ambiguity. Grid cells are represented by a [ ]. Only /// are used to avoid ambiguity. Grid cells are represented by a [ ]. Only
@ -437,7 +438,7 @@ impl SelectionRange {
mod test { mod test {
use std::mem; use std::mem;
use super::{Selection, Span}; use super::{Selection, SelectionRange};
use crate::clipboard::Clipboard; use crate::clipboard::Clipboard;
use crate::config::MockConfig; use crate::config::MockConfig;
use crate::event::{Event, EventListener}; use crate::event::{Event, EventListener};
@ -464,7 +465,7 @@ mod test {
Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock) Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock)
} }
/// Test case of single cell selection /// Test case of single cell selection.
/// ///
/// 1. [ ] /// 1. [ ]
/// 2. [B ] /// 2. [B ]
@ -475,14 +476,14 @@ mod test {
let mut selection = Selection::simple(location, Side::Left); let mut selection = Selection::simple(location, Side::Left);
selection.update(location, Side::Right); 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, start: location,
end: location, end: location,
is_block: false is_block: false
}); });
} }
/// Test case of single cell selection /// Test case of single cell selection.
/// ///
/// 1. [ ] /// 1. [ ]
/// 2. [ B] /// 2. [ B]
@ -493,14 +494,14 @@ mod test {
let mut selection = Selection::simple(location, Side::Right); let mut selection = Selection::simple(location, Side::Right);
selection.update(location, Side::Left); 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, start: location,
end: location, end: location,
is_block: false is_block: false
}); });
} }
/// Test adjacent cell selection from left to right /// Test adjacent cell selection from left to right.
/// ///
/// 1. [ ][ ] /// 1. [ ][ ]
/// 2. [ B][ ] /// 2. [ B][ ]
@ -510,10 +511,10 @@ mod test {
let mut selection = Selection::simple(Point::new(0, Column(0)), Side::Right); let mut selection = Selection::simple(Point::new(0, Column(0)), Side::Right);
selection.update(Point::new(0, Column(1)), Side::Left); 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. [ ][ ] /// 1. [ ][ ]
/// 2. [ ][B ] /// 2. [ ][B ]
@ -523,11 +524,10 @@ mod test {
let mut selection = Selection::simple(Point::new(0, Column(1)), Side::Left); let mut selection = Selection::simple(Point::new(0, Column(1)), Side::Left);
selection.update(Point::new(0, Column(0)), Side::Right); 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. [ ][ ][ ][ ][ ] /// 1. [ ][ ][ ][ ][ ]
/// [ ][ ][ ][ ][ ] /// [ ][ ][ ][ ][ ]
@ -540,15 +540,14 @@ mod test {
let mut selection = Selection::simple(Point::new(1, Column(1)), Side::Right); let mut selection = Selection::simple(Point::new(1, Column(1)), Side::Right);
selection.update(Point::new(0, 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)), start: Point::new(1, Column(2)),
end: Point::new(0, Column(1)), end: Point::new(0, Column(1)),
is_block: false, is_block: false,
}); });
} }
/// Test selection across adjacent lines /// Test selection across adjacent lines.
///
/// ///
/// 1. [ ][ ][ ][ ][ ] /// 1. [ ][ ][ ][ ][ ]
/// [ ][ ][ ][ ][ ] /// [ ][ ][ ][ ][ ]
@ -564,7 +563,7 @@ mod test {
selection.update(Point::new(1, Column(1)), Side::Right); selection.update(Point::new(1, Column(1)), Side::Right);
selection.update(Point::new(1, Column(0)), 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)), start: Point::new(1, Column(1)),
end: Point::new(0, Column(1)), end: Point::new(0, Column(1)),
is_block: false, is_block: false,
@ -573,52 +572,52 @@ mod test {
#[test] #[test]
fn line_selection() { fn line_selection() {
let mut selection = Selection::lines(Point::new(0, Column(0))); let mut selection = Selection::lines(Point::new(0, Column(1)));
selection.update(Point::new(5, Column(3)), Side::Right); selection.update(Point::new(5, Column(1)), Side::Right);
selection.rotate(-3); selection.rotate(7);
assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span { assert_eq!(selection.to_range(&term(5, 10)).unwrap(), SelectionRange {
start: Point::new(2, Column(0)), start: Point::new(9, Column(0)),
end: Point::new(0, Column(4)), end: Point::new(7, Column(4)),
is_block: false, is_block: false,
}); });
} }
#[test] #[test]
fn semantic_selection() { fn semantic_selection() {
let mut selection = Selection::semantic(Point::new(0, Column(0))); let mut selection = Selection::semantic(Point::new(0, Column(3)));
selection.update(Point::new(5, Column(3)), Side::Right); selection.update(Point::new(5, Column(1)), Side::Right);
selection.rotate(-3); selection.rotate(7);
assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span { assert_eq!(selection.to_range(&term(5, 10)).unwrap(), SelectionRange {
start: Point::new(2, Column(3)), start: Point::new(9, Column(0)),
end: Point::new(0, Column(4)), end: Point::new(7, Column(3)),
is_block: false, is_block: false,
}); });
} }
#[test] #[test]
fn simple_selection() { fn simple_selection() {
let mut selection = Selection::simple(Point::new(0, Column(0)), Side::Right); let mut selection = Selection::simple(Point::new(0, Column(3)), Side::Right);
selection.update(Point::new(5, Column(3)), Side::Right); selection.update(Point::new(5, Column(1)), Side::Right);
selection.rotate(-3); selection.rotate(7);
assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span { assert_eq!(selection.to_range(&term(5, 10)).unwrap(), SelectionRange {
start: Point::new(2, Column(4)), start: Point::new(9, Column(0)),
end: Point::new(0, Column(4)), end: Point::new(7, Column(3)),
is_block: false, is_block: false,
}); });
} }
#[test] #[test]
fn block_selection() { fn block_selection() {
let mut selection = Selection::block(Point::new(0, Column(0)), Side::Right); let mut selection = Selection::block(Point::new(0, Column(3)), Side::Right);
selection.update(Point::new(5, Column(3)), Side::Right); selection.update(Point::new(5, Column(1)), Side::Right);
selection.rotate(-3); selection.rotate(7);
assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span { assert_eq!(selection.to_range(&term(5, 10)).unwrap(), SelectionRange {
start: Point::new(2, Column(4)), start: Point::new(9, Column(2)),
end: Point::new(0, Column(4)), end: Point::new(7, Column(3)),
is_block: true is_block: true
}); });
} }
@ -636,7 +635,7 @@ mod test {
let mut selection = Selection::simple(Point::new(0, Column(1)), Side::Left); let mut selection = Selection::simple(Point::new(0, Column(1)), Side::Left);
selection.update(Point::new(0, Column(8)), Side::Right); 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)), start: Point::new(0, Column(0)),
end: Point::new(0, Column(9)), end: Point::new(0, Column(9)),
is_block: false, is_block: false,

View File

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