From c84cd0fdb0335fa873f397e95a0a728be0fe15f5 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Tue, 21 Jan 2020 00:56:10 +0100 Subject: [PATCH] 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. --- CHANGELOG.md | 3 + alacritty_terminal/src/grid/mod.rs | 18 +- alacritty_terminal/src/selection.rs | 599 ++++++++++++++-------------- alacritty_terminal/src/term/mod.rs | 51 +-- 4 files changed, 336 insertions(+), 335 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c56c1c..adbd2b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/alacritty_terminal/src/grid/mod.rs b/alacritty_terminal/src/grid/mod.rs index 53f7ebe..9c26fac 100644 --- a/alacritty_terminal/src/grid/mod.rs +++ b/alacritty_terminal/src/grid/mod.rs @@ -158,22 +158,16 @@ impl Grid { } } - pub fn buffer_to_visible(&self, point: impl Into>) -> Point { + pub fn buffer_to_visible(&self, point: impl Into>) -> Option> { 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 { diff --git a/alacritty_terminal/src/selection.rs b/alacritty_terminal/src/selection.rs index 1c747d9..9561450 100644 --- a/alacritty_terminal/src/selection.rs +++ b/alacritty_terminal/src/selection.rs @@ -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, + side: Side, +} + +impl Anchor { + fn new(point: Point, side: Side) -> Anchor { + Anchor { point, side } + } +} + +/// Represents a range of selected cells. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct SelectionRange { + /// Start point, top left of the selection. + pub start: Point, + /// End point, bottom right of the selection. + pub end: Point, + /// Whether this selection is a block selection. + pub 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: 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, }, Block { - /// The region representing start and end of cursor movement + /// The region representing start and end of cursor movement. region: Range, }, Semantic { - /// The region representing start and end of cursor movement - region: Range>, + /// The region representing start and end of cursor movement. + region: Range>, }, Lines { - /// The region representing start and end of cursor movement - region: Range>, + /// The region representing start and end of cursor movement. + region: Range>, }, } -/// A Point and side within that point. -#[derive(Debug, Clone, PartialEq)] -pub struct Anchor { - point: Point, - side: Side, -} - -impl Anchor { - fn new(point: Point, 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, 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, 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) -> Selection { - Selection::Semantic { region: Range { start: point.into(), end: point.into() } } + Selection::Semantic { region: Range { start: point, end: point } } } pub fn lines(point: Point) -> 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, 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(&self, term: &Term) -> Option { - // 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(&self, term: &Term) -> Option { + 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(term: &Term, 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, 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, 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, end: Point) -> bool { + fn points_need_swap(start: Point, end: Point) -> 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, - mut end: Point, - lines: isize, - cols: Column, - ) -> Option<(Point, Point)> { - 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( + term: &Term, + mut start: Point, + mut end: Point, + ) -> Option { + 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(term: &T, start: Point, end: Point) -> Option - 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( + term: &Term, + mut start: Point, + mut end: Point, + ) -> Option { + 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(term: &T, start: Point, end: Point) -> Option - 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( + fn range_simple( &self, - term: &T, - mut start: Point, - mut end: Point, - start_side: Side, - end_side: Side, - ) -> Option - where - T: Dimensions, - { + mut start: Anchor, + mut end: Anchor, + num_cols: Column, + ) -> Option { 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, - mut end: Point, - mut start_side: Side, - mut end_side: Side, - ) -> Option { + fn range_block(&self, mut start: Anchor, mut end: Anchor) -> Option { 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, &Point) { + match self { + Self::Simple { ref region } | Self::Block { ref region } => { + (®ion.start.point, ®ion.end.point) + }, + Self::Semantic { ref region } | Self::Lines { ref region } => { + (®ion.start, ®ion.end) + }, + } + } + + fn points_mut(&mut self) -> (&mut Point, &mut Point) { + 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((®ion.start.side, ®ion.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, - /// End point towards top of buffer - pub end: Point, - /// 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, diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index 9cd1f75..e1f8fb3 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -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 Search for Term { } } -impl selection::Dimensions for Term { - 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, colors: &'a color::List, - selection: Option, + selection: Option>, } impl<'a, C> RenderableCellsIter<'a, C> { @@ -225,30 +214,46 @@ impl<'a, C> RenderableCellsIter<'a, C> { fn new<'b, T>( term: &'b Term, config: &'b Config, - selection: Option, + selection: Option, 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 Term { /// Convert the active selection to a String. pub fn selection_to_string(&self) -> Option { 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 Term { self.grid.visible_to_buffer(point) } - pub fn buffer_to_visible(&self, point: impl Into>) -> Point { + pub fn buffer_to_visible(&self, point: impl Into>) -> Option> { self.grid.buffer_to_visible(point) } @@ -1043,7 +1048,7 @@ impl Term { /// 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) -> 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)