diff options
author | Christian Duerr <chrisduerr@users.noreply.github.com> | 2019-06-20 15:56:09 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-06-20 15:56:09 +0000 |
commit | e0a286515f12c6ceed53c74df1c10123cb0b550d (patch) | |
tree | e24c9fc28984cdc577fd87b47c36d72c82ab3368 /alacritty_terminal/src/selection.rs | |
parent | a1c70b1d68f192c3e6901095f646e17a93774746 (diff) | |
download | alacritty-e0a286515f12c6ceed53c74df1c10123cb0b550d.tar.gz alacritty-e0a286515f12c6ceed53c74df1c10123cb0b550d.zip |
Add block selection
This implements a block selection mode which can be triggered by holding
Control before starting a selection.
If text is copied using this block selection, newlines will be
automatically added to the end of the lines.
This fixes #526.
Diffstat (limited to 'alacritty_terminal/src/selection.rs')
-rw-r--r-- | alacritty_terminal/src/selection.rs | 255 |
1 files changed, 175 insertions, 80 deletions
diff --git a/alacritty_terminal/src/selection.rs b/alacritty_terminal/src/selection.rs index 7dd0be74..132c6919 100644 --- a/alacritty_terminal/src/selection.rs +++ b/alacritty_terminal/src/selection.rs @@ -20,7 +20,7 @@ //! also be cleared if the user clicks off of the selection. use std::ops::Range; -use crate::index::{Column, Point, Side}; +use crate::index::{Column, Line, Point, Side}; use crate::term::cell::Flags; use crate::term::{Search, Term}; @@ -45,6 +45,10 @@ pub enum Selection { /// The region representing start and end of cursor movement region: Range<Anchor>, }, + Block { + /// 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>>, @@ -52,10 +56,6 @@ pub enum Selection { Lines { /// The region representing start and end of cursor movement region: Range<Point<isize>>, - - /// The line under the initial point. This is always selected regardless - /// of which way the cursor is moved. - initial_line: isize, }, } @@ -79,6 +79,19 @@ pub trait Dimensions { } 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 { @@ -88,20 +101,11 @@ impl Selection { } } - pub fn rotate(&mut self, offset: isize) { - match *self { - Selection::Simple { ref mut region } => { - region.start.point.line += offset; - region.end.point.line += offset; - }, - Selection::Semantic { ref mut region } => { - region.start.line += offset; - region.end.line += offset; - }, - Selection::Lines { ref mut region, ref mut initial_line } => { - region.start.line += offset; - region.end.line += offset; - *initial_line += offset; + 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), }, } } @@ -111,29 +115,49 @@ impl Selection { } pub fn lines(point: Point<usize>) -> Selection { - Selection::Lines { - region: Range { start: point.into(), end: point.into() }, - initial_line: point.line as isize, - } + Selection::Lines { region: Range { start: point.into(), end: point.into() } } } 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::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, .. } => { + Selection::Semantic { ref mut region } | Selection::Lines { ref mut region } => { region.end = location.into(); }, } } + pub fn is_empty(&self) -> bool { + match *self { + Selection::Simple { ref region } | Selection::Block { ref region } => { + let (start, end) = + if Selection::points_need_swap(region.start.point, region.end.point) { + (®ion.end, ®ion.start) + } else { + (®ion.start, ®ion.end) + }; + + // Empty when single cell with identical sides or two cell with right+left sides + start == end + || (start.side == Side::Left + && end.side == Side::Right + && start.point.line == end.point.line + && start.point.col == end.point.col + 1) + }, + Selection::Semantic { .. } | Selection::Lines { .. } => false, + } + } + pub fn to_span(&self, term: &Term) -> Option<Span> { // Get both sides of the selection let (mut start, mut end) = match *self { - Selection::Simple { ref region } => (region.start.point, region.end.point), - Selection::Semantic { ref region } | Selection::Lines { ref region, .. } => { + 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) }, }; @@ -150,11 +174,23 @@ impl Selection { let (start, end) = Selection::grid_clamp(start, end, lines, cols)?; let span = match *self { - Selection::Simple { ref region } if needs_swap => { - Selection::span_simple(term, start, end, region.end.side, region.start.side) - }, Selection::Simple { ref region } => { - Selection::span_simple(term, start, end, region.start.side, region.end.side) + 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), @@ -180,13 +216,41 @@ impl Selection { }) } - pub fn is_empty(&self) -> bool { - match *self { - Selection::Simple { ref region } => { - region.start == region.end && region.start.side == region.end.side - }, - Selection::Semantic { .. } | Selection::Lines { .. } => false, + // Bring start and end points in the correct order + fn points_need_swap(start: Point<isize>, end: Point<isize>) -> bool { + start.line > end.line || start.line == end.line && start.col < end.col + } + + // Clamp selection inside the grid to prevent out of bounds errors + fn grid_clamp( + mut start: Point<isize>, + mut end: Point<isize>, + lines: isize, + cols: Column, + ) -> Option<(Point<isize>, Point<isize>)> { + if end.line >= lines { + // Don't show selection above visible region + if start.line >= lines { + return None; + } + + // Clamp selection above viewport to visible region + end.line = lines - 1; + end.col = Column(0); } + + if start.line < 0 { + // Don't show selection below visible region + if end.line < 0 { + return None; + } + + // Clamp selection below viewport to visible region + start.line = 0; + start.col = cols - 1; + } + + Some((start, end)) } fn span_semantic<T>(term: &T, start: Point<isize>, end: Point<isize>) -> Option<Span> @@ -203,7 +267,7 @@ impl Selection { (term.semantic_search_right(start.into()), term.semantic_search_left(end.into())) }; - Some(Span { start, end }) + Some(Span { start, end, is_block: false }) } fn span_lines<T>(term: &T, mut start: Point<isize>, mut end: Point<isize>) -> Option<Span> @@ -213,10 +277,11 @@ impl Selection { start.col = term.dimensions().col - 1; end.col = Column(0); - Some(Span { start: start.into(), end: end.into() }) + Some(Span { start: start.into(), end: end.into(), is_block: false }) } fn span_simple<T>( + &self, term: &T, mut start: Point<isize>, mut end: Point<isize>, @@ -226,13 +291,7 @@ impl Selection { where T: Dimensions, { - // No selection for single cell with identical sides or two cell with right+left sides - if (start == end && start_side == end_side) - || (end_side == Side::Right - && start_side == Side::Left - && start.line == end.line - && start.col == end.col + 1) - { + if self.is_empty() { return None; } @@ -253,54 +312,69 @@ impl Selection { } // Return the selection with all cells inclusive - Some(Span { start: start.into(), end: end.into() }) - } - - // Bring start and end points in the correct order - fn points_need_swap(start: Point<isize>, end: Point<isize>) -> bool { - start.line > end.line || start.line == end.line && start.col <= end.col + Some(Span { start: start.into(), end: end.into(), is_block: false }) } - // Clamp selection inside the grid to prevent out of bounds errors - fn grid_clamp( + fn span_block( + &self, mut start: Point<isize>, mut end: Point<isize>, - lines: isize, - cols: Column, - ) -> Option<(Point<isize>, Point<isize>)> { - if end.line >= lines { - // Don't show selection above visible region - if start.line >= lines { - return None; - } + mut start_side: Side, + mut end_side: Side, + ) -> Option<Span> { + if self.is_empty() { + return None; + } - // Clamp selection above viewport to visible region - end.line = lines - 1; - end.col = Column(0); + // Always go bottom-right -> top-left + 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.line < 0 { - // Don't show selection below visible region - if end.line < 0 { - return None; - } + // Remove last cell if selection ends to the left of a cell + if start_side == Side::Left && start != end && start.col.0 > 0 { + start.col -= 1; + } - // Clamp selection below viewport to visible region - start.line = 0; - start.col = cols - 1; + // Remove first cell if selection starts at the right of a cell + if end_side == Side::Right && start != end { + end.col += 1; } - Some((start, end)) + // Return the selection with all cells inclusive + Some(Span { start: start.into(), end: end.into(), is_block: true }) } } /// Represents a span of selected cells -#[derive(Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Span { /// Start point from bottom of buffer pub start: Point<usize>, /// End point towards top of buffer pub end: Point<usize>, + /// Whether this selection is a block selection + pub is_block: bool, +} + +pub struct SelectionRange { + start: Point, + end: Point, + is_block: bool, +} + +impl SelectionRange { + pub fn new(start: Point, end: Point, is_block: bool) -> Self { + Self { start, end, is_block } + } + + pub fn contains(&self, col: Column, line: Line) -> bool { + self.start.line <= line + && self.end.line >= line + && (self.start.col <= col || (self.start.line != line && !self.is_block)) + && (self.end.col >= col || (self.end.line != line && !self.is_block)) + } } /// Tests for selection @@ -350,7 +424,8 @@ mod test { assert_eq!(selection.to_span(&term(1, 1)).unwrap(), Span { start: location, - end: location + end: location, + is_block: false, }); } @@ -367,7 +442,8 @@ mod test { assert_eq!(selection.to_span(&term(1, 1)).unwrap(), Span { start: location, - end: location + end: location, + is_block: false, }); } @@ -414,6 +490,7 @@ mod test { assert_eq!(selection.to_span(&term(5, 2)).unwrap(), Span { start: Point::new(0, Column(1)), end: Point::new(1, Column(2)), + is_block: false, }); } @@ -437,11 +514,12 @@ mod test { assert_eq!(selection.to_span(&term(5, 2)).unwrap(), Span { start: Point::new(0, Column(1)), end: Point::new(1, Column(1)), + is_block: false, }); } #[test] - fn alt_screen_lines() { + 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); @@ -449,11 +527,12 @@ mod test { assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span { start: Point::new(0, Column(4)), end: Point::new(2, Column(0)), + is_block: false, }); } #[test] - fn alt_screen_semantic() { + 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); @@ -461,11 +540,12 @@ mod test { assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span { start: Point::new(0, Column(4)), end: Point::new(2, Column(3)), + is_block: false, }); } #[test] - fn alt_screen_simple() { + 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); @@ -473,6 +553,20 @@ mod test { assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span { start: Point::new(0, Column(4)), end: Point::new(2, Column(4)), + 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); + + assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span { + start: Point::new(0, Column(4)), + end: Point::new(2, Column(4)), + is_block: true, }); } @@ -492,6 +586,7 @@ mod test { assert_eq!(selection.to_span(&term).unwrap(), Span { start: Point::new(0, Column(9)), end: Point::new(0, Column(0)), + is_block: false, }); } } |