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)