aboutsummaryrefslogtreecommitdiff
path: root/alacritty_terminal/src
diff options
context:
space:
mode:
Diffstat (limited to 'alacritty_terminal/src')
-rw-r--r--alacritty_terminal/src/grid/mod.rs18
-rw-r--r--alacritty_terminal/src/selection.rs567
-rw-r--r--alacritty_terminal/src/term/mod.rs51
3 files changed, 317 insertions, 319 deletions
diff --git a/alacritty_terminal/src/grid/mod.rs b/alacritty_terminal/src/grid/mod.rs
index 53f7ebea..9c26fac0 100644
--- a/alacritty_terminal/src/grid/mod.rs
+++ b/alacritty_terminal/src/grid/mod.rs
@@ -158,22 +158,16 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
}
}
- pub fn buffer_to_visible(&self, point: impl Into<Point<usize>>) -> Point<usize> {
+ pub fn buffer_to_visible(&self, point: impl Into<Point<usize>>) -> Option<Point<usize>> {
let mut point = point.into();
- let offset = point.line.saturating_sub(self.display_offset);
-
- if point.line < self.display_offset {
- point.col = self.num_cols();
- point.line = self.num_lines().0 - 1;
- } else if offset >= *self.num_lines() {
- point.col = Column(0);
- point.line = 0;
- } else {
- point.line = self.lines.0 - offset - 1;
+ if point.line < self.display_offset || point.line >= self.display_offset + self.lines.0 {
+ return None;
}
- point
+ point.line = self.lines.0 + self.display_offset - point.line - 1;
+
+ Some(point)
}
pub fn visible_to_buffer(&self, point: Point) -> Point<usize> {
diff --git a/alacritty_terminal/src/selection.rs b/alacritty_terminal/src/selection.rs
index 1c747d99..95614502 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<usize>,
+ side: Side,
+}
+
+impl Anchor {
+ fn new(point: Point<usize>, side: Side) -> Anchor {
+ Anchor { point, side }
+ }
+}
+
+/// Represents a range of selected cells.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub struct SelectionRange<L = usize> {
+ /// Start point, top left of the selection.
+ pub start: Point<L>,
+ /// End point, bottom right of the selection.
+ pub end: Point<L>,
+ /// Whether this selection is a block selection.
+ pub is_block: bool,
+}
+
+impl<L> SelectionRange<L> {
+ pub fn new(start: Point<L>, end: Point<L>, is_block: bool) -> Self {
+ Self { start, end, is_block }
+ }
+
+ pub fn contains(&self, col: Column, line: L) -> bool
+ where
+ L: PartialEq + PartialOrd,
+ {
+ self.start.line <= line
+ && self.end.line >= line
+ && (self.start.col <= col || (self.start.line != line && !self.is_block))
+ && (self.end.col >= col || (self.end.line != line && !self.is_block))
+ }
+}
+
+/// Describes a region of a 2-dimensional area.
///
/// Used to track a text selection. There are three supported modes, each with its own constructor:
/// [`simple`], [`semantic`], and [`lines`]. The [`simple`] mode precisely tracks which cells are
@@ -43,96 +85,61 @@ use crate::term::{Search, Term};
#[derive(Debug, Clone, PartialEq)]
pub enum Selection {
Simple {
- /// The region representing start and end of cursor movement
+ /// The region representing start and end of cursor movement.
region: Range<Anchor>,
},
Block {
- /// The region representing start and end of cursor movement
+ /// The region representing start and end of cursor movement.
region: Range<Anchor>,
},
Semantic {
- /// The region representing start and end of cursor movement
- region: Range<Point<isize>>,
+ /// The region representing start and end of cursor movement.
+ region: Range<Point<usize>>,
},
Lines {
- /// The region representing start and end of cursor movement
- region: Range<Point<isize>>,
+ /// The region representing start and end of cursor movement.
+ region: Range<Point<usize>>,
},
}
-/// A Point and side within that point.
-#[derive(Debug, Clone, PartialEq)]
-pub struct Anchor {
- point: Point<isize>,
- side: Side,
-}
-
-impl Anchor {
- fn new(point: Point<isize>, side: Side) -> Anchor {
- Anchor { point, side }
- }
-}
-
-/// A type that has 2-dimensional boundaries
-pub trait Dimensions {
- /// Get the size of the area
- fn dimensions(&self) -> Point;
-}
-
impl Selection {
- pub fn rotate(&mut self, offset: isize) {
- match *self {
- Selection::Simple { ref mut region } | Selection::Block { ref mut region } => {
- region.start.point.line += offset;
- region.end.point.line += offset;
- },
- Selection::Semantic { ref mut region } | Selection::Lines { ref mut region } => {
- region.start.line += offset;
- region.end.line += offset;
- },
- }
- }
-
pub fn simple(location: Point<usize>, side: Side) -> Selection {
Selection::Simple {
- region: Range {
- start: Anchor::new(location.into(), side),
- end: Anchor::new(location.into(), side),
- },
+ region: Range { start: Anchor::new(location, side), end: Anchor::new(location, side) },
}
}
pub fn block(location: Point<usize>, side: Side) -> Selection {
Selection::Block {
- region: Range {
- start: Anchor::new(location.into(), side),
- end: Anchor::new(location.into(), side),
- },
+ region: Range { start: Anchor::new(location, side), end: Anchor::new(location, side) },
}
}
pub fn semantic(point: Point<usize>) -> Selection {
- Selection::Semantic { region: Range { start: point.into(), end: point.into() } }
+ Selection::Semantic { region: Range { start: point, end: point } }
}
pub fn lines(point: Point<usize>) -> Selection {
- Selection::Lines { region: Range { start: point.into(), end: point.into() } }
+ Selection::Lines { region: Range { start: point, end: point } }
}
pub fn update(&mut self, location: Point<usize>, side: Side) {
- // Always update the `end`; can normalize later during span generation.
- match *self {
- Selection::Simple { ref mut region } | Selection::Block { ref mut region } => {
- region.end = Anchor::new(location.into(), side);
- },
- Selection::Semantic { ref mut region } | Selection::Lines { ref mut region } => {
- region.end = location.into();
- },
+ let (_, end) = self.points_mut();
+ *end = location;
+
+ if let Some((_, end_side)) = self.sides_mut() {
+ *end_side = side;
}
}
+ pub fn rotate(&mut self, offset: isize) {
+ let (start, end) = self.points_mut();
+ start.line = usize::try_from(start.line as isize + offset).unwrap_or(0);
+ end.line = usize::try_from(end.line as isize + offset).unwrap_or(0);
+ }
+
pub fn is_empty(&self) -> bool {
- match *self {
+ match self {
Selection::Simple { ref region } => {
let (start, end) =
if Selection::points_need_swap(region.start.point, region.end.point) {
@@ -165,266 +172,260 @@ impl Selection {
}
}
- pub fn to_span<T>(&self, term: &Term<T>) -> Option<Span> {
- // Get both sides of the selection
- let (mut start, mut end) = match *self {
- Selection::Simple { ref region } | Selection::Block { ref region } => {
- (region.start.point, region.end.point)
- },
- Selection::Semantic { ref region } | Selection::Lines { ref region } => {
- (region.start, region.end)
- },
- };
+ /// Convert selection to grid coordinates.
+ pub fn to_range<T>(&self, term: &Term<T>) -> Option<SelectionRange> {
+ let grid = term.grid();
+ let num_cols = grid.num_cols();
- // Order the start/end
- let needs_swap = Selection::points_need_swap(start, end);
- if needs_swap {
- std::mem::swap(&mut start, &mut end);
- }
+ // Get selection boundaries
+ let points = self.points();
+ let (start, end) = (*points.0, *points.1);
- // 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)?;
+ // 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);
- 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)
- };
+ // 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 })
+ };
- 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)
- };
+ // Clamp to inside the grid buffer
+ let (start, end) = Self::grid_clamp(start, end, self.is_block(), grid.len()).ok()?;
- 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),
+ 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 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 selection across fullwidth cells
+ range.map(|range| Self::range_expand_fullwidth(term, range))
+ }
- // 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;
- }
+ /// 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;
}
}
- // 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)
+ // 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)
{
- span.end = span.end.add(num_cols.0, 1, true);
+ range.start = prev;
}
+ }
+ }
- // 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);
- }
+ // 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);
+ }
+
+ // Expand from wide char to wide char spacer
+ if flag_at(range.end, Flags::WIDE_CHAR) {
+ range.end = range.end.add(num_cols.0, 1, true);
}
+ }
- span
- })
+ range
}
// Bring start and end points in the correct order
- fn points_need_swap(start: Point<isize>, end: Point<isize>) -> bool {
+ fn points_need_swap(start: Point<usize>, end: Point<usize>) -> bool {
start.line < end.line || start.line == end.line && start.col > end.col
}
- // Clamp selection inside the grid to prevent out of bounds errors
+ /// Clamp selection inside grid to prevent OOB.
fn grid_clamp(
- mut start: Point<isize>,
- mut end: Point<isize>,
- lines: isize,
- cols: Column,
- ) -> Option<(Point<isize>, Point<isize>)> {
- if start.line >= lines {
- // Don't show selection above visible region
- if end.line >= lines {
- return None;
+ mut start: Anchor,
+ end: Anchor,
+ is_block: bool,
+ lines: usize,
+ ) -> Result<(Anchor, Anchor), ()> {
+ // Clamp selection inside of grid to prevent OOB
+ if start.point.line >= lines {
+ // Remove selection if it is fully out of the grid
+ if end.point.line >= lines {
+ return Err(());
}
- // Clamp selection above viewport to visible region
- start.line = lines - 1;
- start.col = Column(0);
- }
-
- if end.line < 0 {
- // Don't show selection below visible region
- if start.line < 0 {
- return None;
+ // Clamp to grid if it is still partially visible
+ if !is_block {
+ start.side = Side::Left;
+ start.point.col = Column(0);
}
-
- // Clamp selection below viewport to visible region
- end.line = 0;
- end.col = cols - 1;
+ start.point.line = lines - 1;
}
- Some((start, end))
+ Ok((start, end))
}
- fn span_semantic<T>(term: &T, start: Point<isize>, end: Point<isize>) -> Option<Span>
- where
- T: Search + Dimensions,
- {
- let (start, end) = if start == end {
- if let Some(end) = term.bracket_search(start.into()) {
- (start.into(), end)
- } else {
- (term.semantic_search_left(start.into()), term.semantic_search_right(end.into()))
+ fn range_semantic<T>(
+ term: &Term<T>,
+ mut start: Point<usize>,
+ mut end: Point<usize>,
+ ) -> Option<SelectionRange> {
+ if start == end {
+ if let Some(matching) = term.bracket_search(start) {
+ if (matching.line == start.line && matching.col < start.col)
+ || (matching.line > start.line)
+ {
+ start = matching;
+ } else {
+ end = matching;
+ }
+
+ return Some(SelectionRange { start, end, is_block: false });
}
- } else {
- (term.semantic_search_left(start.into()), term.semantic_search_right(end.into()))
- };
+ }
+
+ start = term.semantic_search_left(start);
+ end = term.semantic_search_right(end);
- Some(Span { start, end, is_block: false })
+ Some(SelectionRange { start, end, is_block: false })
}
- fn span_lines<T>(term: &T, start: Point<isize>, end: Point<isize>) -> Option<Span>
- where
- T: Search,
- {
- let start = term.line_search_left(start.into());
- let end = term.line_search_right(end.into());
+ fn range_lines<T>(
+ term: &Term<T>,
+ mut start: Point<usize>,
+ mut end: Point<usize>,
+ ) -> Option<SelectionRange> {
+ start = term.line_search_left(start);
+ end = term.line_search_right(end);
- Some(Span { start, end, is_block: false })
+ Some(SelectionRange { start, end, is_block: false })
}
- fn span_simple<T>(
+ fn range_simple(
&self,
- term: &T,
- mut start: Point<isize>,
- mut end: Point<isize>,
- start_side: Side,
- end_side: Side,
- ) -> Option<Span>
- where
- T: Dimensions,
- {
+ mut start: Anchor,
+ mut end: Anchor,
+ num_cols: Column,
+ ) -> Option<SelectionRange> {
if self.is_empty() {
return None;
}
// Remove last cell if selection ends to the left of a cell
- if end_side == Side::Left && start != end {
+ if end.side == Side::Left && start.point != end.point {
// Special case when selection ends to left of first cell
- if end.col == Column(0) {
- end.col = term.dimensions().col - 1;
- end.line += 1;
+ if end.point.col == Column(0) {
+ end.point.col = num_cols;
+ end.point.line += 1;
} else {
- end.col -= 1;
+ end.point.col -= 1;
}
}
// Remove first cell if selection starts at the right of a cell
- if start_side == Side::Right && start != end {
- start.col += 1;
+ if start.side == Side::Right && start.point != end.point {
+ start.point.col += 1;
}
- // Return the selection with all cells inclusive
- Some(Span { start: start.into(), end: end.into(), is_block: false })
+ Some(SelectionRange { start: start.point, end: end.point, is_block: false })
}
- fn span_block(
- &self,
- mut start: Point<isize>,
- mut end: Point<isize>,
- mut start_side: Side,
- mut end_side: Side,
- ) -> Option<Span> {
+ fn range_block(&self, mut start: Anchor, mut end: Anchor) -> Option<SelectionRange> {
if self.is_empty() {
return None;
}
// Always go top-left -> bottom-right
- if start.col > end.col {
- std::mem::swap(&mut start_side, &mut end_side);
- std::mem::swap(&mut start.col, &mut end.col);
+ if start.point.col > end.point.col {
+ mem::swap(&mut start.side, &mut end.side);
+ mem::swap(&mut start.point.col, &mut end.point.col);
}
// Remove last cell if selection ends to the left of a cell
- if end_side == Side::Left && start != end && end.col.0 > 0 {
- end.col -= 1;
+ if end.side == Side::Left && start.point != end.point && end.point.col.0 > 0 {
+ end.point.col -= 1;
}
// Remove first cell if selection starts at the right of a cell
- if start_side == Side::Right && start != end {
- start.col += 1;
+ if start.side == Side::Right && start.point != end.point {
+ start.point.col += 1;
}
- // Return the selection with all cells inclusive
- Some(Span { start: start.into(), end: end.into(), is_block: true })
+ Some(SelectionRange { start: start.point, end: end.point, is_block: true })
}
-}
-/// Represents a span of selected cells
-#[derive(Copy, Clone, Debug, Eq, PartialEq)]
-pub struct Span {
- /// Start point from bottom of buffer
- pub start: Point<usize>,
- /// End point towards top of buffer
- pub end: Point<usize>,
- /// Whether this selection is a block selection
- pub is_block: bool,
-}
+ 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)
+ },
+ }
+ }
-pub struct SelectionRange {
- start: Point,
- end: Point,
- is_block: bool,
-}
+ 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)
+ },
+ }
+ }
-impl SelectionRange {
- pub fn new(start: Point, end: Point, is_block: bool) -> Self {
- Self { start, end, is_block }
+ 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,
+ }
}
- 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))
+ 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,
+ }
}
}
-/// 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 9cd1f75b..e1f8fb30 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<T> Search for Term<T> {
}
}
-impl<T> selection::Dimensions for Term<T> {
- fn dimensions(&self) -> Point {
- let line = if self.mode.contains(TermMode::ALT_SCREEN) {
- self.grid.num_lines()
- } else {
- Line(self.grid.len())
- };
- Point { col: self.grid.num_cols(), line }
- }
-}
-
/// A key for caching cursor glyphs
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash, Deserialize)]
pub struct CursorKey {
@@ -214,7 +203,7 @@ pub struct RenderableCellsIter<'a, C> {
cursor_style: CursorStyle,
config: &'a Config<C>,
colors: &'a color::List,
- selection: Option<SelectionRange>,
+ selection: Option<SelectionRange<Line>>,
}
impl<'a, C> RenderableCellsIter<'a, C> {
@@ -225,30 +214,46 @@ impl<'a, C> RenderableCellsIter<'a, C> {
fn new<'b, T>(
term: &'b Term<T>,
config: &'b Config<C>,
- selection: Option<Span>,
+ selection: Option<SelectionRange>,
mut cursor_style: CursorStyle,
) -> RenderableCellsIter<'b, C> {
let grid = &term.grid;
+ let num_cols = grid.num_cols();
+ let num_lines = grid.num_lines();
let cursor_offset = grid.line_to_offset(term.cursor.point.line);
let inner = grid.display_iter();
- let selection_range = selection.map(|span| {
+ let selection_range = selection.and_then(|span| {
let (limit_start, limit_end) = if span.is_block {
(span.start.col, span.end.col)
} else {
- (Column(0), term.cols() - 1)
+ (Column(0), num_cols - 1)
};
// Get on-screen lines of the selection's locations
- let mut start = term.buffer_to_visible(span.start);
- let mut end = term.buffer_to_visible(span.end);
+ let start = term.buffer_to_visible(span.start);
+ let end = term.buffer_to_visible(span.end);
+
+ // Clamp visible selection to the viewport
+ let (mut start, mut end) = match (start, end) {
+ (Some(start), Some(end)) => (start, end),
+ (Some(start), None) => {
+ let end = Point::new(num_lines.0 - 1, num_cols - 1);
+ (start, end)
+ },
+ (None, Some(end)) => {
+ let start = Point::new(0, Column(0));
+ (start, end)
+ },
+ (None, None) => return None,
+ };
// Trim start/end with partially visible block selection
start.col = max(limit_start, start.col);
end.col = min(limit_end, end.col);
- SelectionRange::new(start.into(), end.into(), span.is_block)
+ Some(SelectionRange::new(start.into(), end.into(), span.is_block))
});
// Load cursor glyph
@@ -256,7 +261,7 @@ impl<'a, C> RenderableCellsIter<'a, C> {
let cursor_visible = term.mode.contains(TermMode::SHOW_CURSOR) && grid.contains(cursor);
let cursor_key = if cursor_visible {
let is_wide =
- grid[cursor].flags.contains(Flags::WIDE_CHAR) && (cursor.col + 1) < grid.num_cols();
+ grid[cursor].flags.contains(Flags::WIDE_CHAR) && (cursor.col + 1) < num_cols;
Some(CursorKey { style: cursor_style, is_wide })
} else {
// Use hidden cursor so text will not get inverted
@@ -938,7 +943,7 @@ impl<T> Term<T> {
/// Convert the active selection to a String.
pub fn selection_to_string(&self) -> Option<String> {
let selection = self.grid.selection.clone()?;
- let Span { start, end, is_block } = selection.to_span(self)?;
+ let SelectionRange { start, end, is_block } = selection.to_range(self)?;
let mut res = String::new();
@@ -1019,7 +1024,7 @@ impl<T> Term<T> {
self.grid.visible_to_buffer(point)
}
- pub fn buffer_to_visible(&self, point: impl Into<Point<usize>>) -> Point<usize> {
+ pub fn buffer_to_visible(&self, point: impl Into<Point<usize>>) -> Option<Point<usize>> {
self.grid.buffer_to_visible(point)
}
@@ -1043,7 +1048,7 @@ impl<T> Term<T> {
/// background color. Cells with an alternate background color are
/// considered renderable as are cells with any text content.
pub fn renderable_cells<'b, C>(&'b self, config: &'b Config<C>) -> RenderableCellsIter<'_, C> {
- let selection = self.grid.selection.as_ref().and_then(|s| s.to_span(self));
+ let selection = self.grid.selection.as_ref().and_then(|s| s.to_range(self));
let cursor = if self.is_focused || !config.cursor.unfocused_hollow() {
self.cursor_style.unwrap_or(self.default_cursor_style)