diff options
Diffstat (limited to 'src/selection.rs')
-rw-r--r-- | src/selection.rs | 308 |
1 files changed, 134 insertions, 174 deletions
diff --git a/src/selection.rs b/src/selection.rs index ae584025..702599e3 100644 --- a/src/selection.rs +++ b/src/selection.rs @@ -19,9 +19,9 @@ //! 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::cmp::{min, max}; +use std::ops::Range; -use index::{Point, Column, RangeInclusive, Side, Linear, Line}; -use grid::ToRange; +use index::{Point, Column, Side}; /// Describes a region of a 2-dimensional area /// @@ -38,43 +38,35 @@ use grid::ToRange; /// [`simple`]: enum.Selection.html#method.simple /// [`semantic`]: enum.Selection.html#method.semantic /// [`lines`]: enum.Selection.html#method.lines +#[derive(Debug, Clone, PartialEq)] pub enum Selection { Simple { /// The region representing start and end of cursor movement - region: Region<Anchor>, + region: Range<Anchor>, }, Semantic { /// The region representing start and end of cursor movement - region: Region<Point>, - - /// When beginning a semantic selection, the grid is searched around the - /// initial point to find semantic escapes, and this initial expansion - /// marks those points. - initial_expansion: Region<Point> + region: Range<Point<usize>>, }, Lines { /// The region representing start and end of cursor movement - region: Region<Point>, + region: Range<Point<usize>>, /// The line under the initial point. This is always selected regardless /// of which way the cursor is moved. - initial_line: Line + initial_line: usize } } -pub struct Region<T> { - start: T, - end: T -} - /// A Point and side within that point. +#[derive(Debug, Clone, PartialEq)] pub struct Anchor { - point: Point, + point: Point<usize>, side: Side, } impl Anchor { - fn new(point: Point, side: Side) -> Anchor { + fn new(point: Point<usize>, side: Side) -> Anchor { Anchor { point, side } } } @@ -85,9 +77,9 @@ impl Anchor { /// points are two dimensional indices. pub trait SemanticSearch { /// Find the nearest semantic boundary _to the left_ of provided point. - fn semantic_search_left(&self, _: Point) -> Point; + fn semantic_search_left(&self, _: Point<usize>) -> Point<usize>; /// Find the nearest semantic boundary _to the point_ of provided point. - fn semantic_search_right(&self, _: Point) -> Point; + fn semantic_search_right(&self, _: Point<usize>) -> Point<usize>; } /// A type that has 2-dimensional boundaries @@ -97,32 +89,45 @@ pub trait Dimensions { } impl Selection { - pub fn simple(location: Point, side: Side) -> Selection { + pub fn simple(location: Point<usize>, side: Side) -> Selection { Selection::Simple { - region: Region { + region: Range { start: Anchor::new(location, side), end: Anchor::new(location, side) } } } - pub fn semantic<G: SemanticSearch>(point: Point, grid: &G) -> Selection { - let (start, end) = (grid.semantic_search_left(point), grid.semantic_search_right(point)); + pub fn rotate(&mut self, offset: isize) { + match *self { + Selection::Simple { ref mut region } => { + region.start.point.line = (region.start.point.line as isize + offset) as usize; + region.end.point.line = (region.end.point.line as isize + offset) as usize; + }, + Selection::Semantic { ref mut region } => { + region.start.line = (region.start.line as isize + offset) as usize; + region.end.line = (region.end.line as isize + offset) as usize; + }, + Selection::Lines { ref mut region, ref mut initial_line } => { + region.start.line = (region.start.line as isize + offset) as usize; + region.end.line = (region.end.line as isize + offset) as usize; + *initial_line = (*initial_line as isize + offset) as usize; + } + } + } + + pub fn semantic(point: Point<usize>) -> Selection { Selection::Semantic { - region: Region { + region: Range { start: point, end: point, - }, - initial_expansion: Region { - start, - end, } } } - pub fn lines(point: Point) -> Selection { + pub fn lines(point: Point<usize>) -> Selection { Selection::Lines { - region: Region { + region: Range { start: point, end: point }, @@ -130,13 +135,13 @@ impl Selection { } } - pub fn update(&mut self, location: Point, side: Side) { + 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 } => { region.end = Anchor::new(location, side); }, - Selection::Semantic { ref mut region, .. } | + Selection::Semantic { ref mut region } | Selection::Lines { ref mut region, .. } => { region.end = location; @@ -149,8 +154,8 @@ impl Selection { Selection::Simple { ref region } => { Selection::span_simple(grid, region) }, - Selection::Semantic { ref region, ref initial_expansion } => { - Selection::span_semantic(grid, region, initial_expansion) + Selection::Semantic { ref region } => { + Selection::span_semantic(grid, region) }, Selection::Lines { ref region, initial_line } => { Selection::span_lines(grid, region, initial_line) @@ -159,14 +164,10 @@ impl Selection { } fn span_semantic<G>( grid: &G, - region: &Region<Point>, - initial_expansion: &Region<Point> + region: &Range<Point<usize>>, ) -> Option<Span> where G: SemanticSearch + Dimensions { - let mut start = initial_expansion.start; - let mut end = initial_expansion.end; - // Normalize ordering of selected cells let (front, tail) = if region.start < region.end { (region.start, region.end) @@ -174,14 +175,14 @@ impl Selection { (region.end, region.start) }; - // Update start of selection *if* front has moved beyond initial start - if front < start { - start = grid.semantic_search_left(front); - } + let (mut start, mut end) = if front < tail && front.line == tail.line { + (grid.semantic_search_left(front), grid.semantic_search_right(tail)) + } else { + (grid.semantic_search_right(front), grid.semantic_search_left(tail)) + }; - // Update end of selection *if* tail has moved beyond initial end. - if tail > end { - end = grid.semantic_search_right(tail); + if start > end { + ::std::mem::swap(&mut start, &mut end); } Some(Span { @@ -192,27 +193,27 @@ impl Selection { }) } - fn span_lines<G>(grid: &G, region: &Region<Point>, initial_line: Line) -> Option<Span> + fn span_lines<G>(grid: &G, region: &Range<Point<usize>>, initial_line: usize) -> Option<Span> where G: Dimensions { // First, create start and end points based on initial line and the grid // dimensions. let mut start = Point { - col: Column(0), + col: grid.dimensions().col - 1, line: initial_line }; let mut end = Point { - col: grid.dimensions().col - 1, + col: Column(0), line: initial_line }; // Now, expand lines based on where cursor started and ended. if region.start.line < region.end.line { - // Start is above end + // Start is below end start.line = min(start.line, region.start.line); end.line = max(end.line, region.end.line); } else { - // Start is below end + // Start is above end start.line = min(start.line, region.end.line); end.line = max(end.line, region.start.line); } @@ -225,73 +226,53 @@ impl Selection { }) } - fn span_simple<G: Dimensions>(grid: &G, region: &Region<Anchor>) -> Option<Span> { + fn span_simple<G: Dimensions>(grid: &G, region: &Range<Anchor>) -> Option<Span> { let start = region.start.point; let start_side = region.start.side; let end = region.end.point; let end_side = region.end.side; let cols = grid.dimensions().col; - let (front, tail, front_side, tail_side) = if start > end { - // Selected upward; start/end are swapped - (end, start, end_side, start_side) - } else { - // Selected downward; no swapping - (start, end, start_side, end_side) - }; - - debug_assert!(!(tail < front)); + // Make sure front is always the "bottom" and tail is always the "top" + let (mut front, mut tail, front_side, tail_side) = + if start.line > end.line || start.line == end.line && start.col <= end.col { + // Selected upward; start/end are swapped + (end, start, end_side, start_side) + } else { + // Selected downward; no swapping + (start, end, start_side, end_side) + }; + + // No selection for single cell with identical sides or two cell with right+left sides + if (front == tail && front_side == tail_side) + || (tail_side == Side::Right && front_side == Side::Left && front.line == tail.line + && front.col == tail.col + 1) + { + return None; + } - // Single-cell selections are a special case - if start == end { - if start_side == end_side { - return None; + // Remove last cell if selection ends to the left of a cell + if front_side == Side::Left && start != end { + // Special case when selection starts to left of first cell + if front.col == Column(0) { + front.col = cols - 1; + front.line += 1; } else { - return Some(Span { - cols, - ty: SpanType::Inclusive, - front, - tail, - }); + front.col -= 1; } } - // The other special case is two adjacent cells with no - // selection: [ B][E ] or [ E][B ] - let adjacent = tail.line == front.line && tail.col - front.col == Column(1); - if adjacent && front_side == Side::Right && tail_side == Side::Left { - return None; + // Remove first cell if selection starts at the right of a cell + if tail_side == Side::Right && front != tail { + tail.col += 1; } - Some(match (front_side, tail_side) { - // [FX][XX][XT] - (Side::Left, Side::Right) => Span { - cols, - front, - tail, - ty: SpanType::Inclusive - }, - // [ F][XX][T ] - (Side::Right, Side::Left) => Span { - cols, - front, - tail, - ty: SpanType::Exclusive - }, - // [FX][XX][T ] - (Side::Left, Side::Left) => Span { - cols, - front, - tail, - ty: SpanType::ExcludeTail - }, - // [ F][XX][XT] - (Side::Right, Side::Right) => Span { - cols, - front, - tail, - ty: SpanType::ExcludeFront - }, + // Return the selection with all cells inclusive + Some(Span { + cols, + front, + tail, + ty: SpanType::Inclusive, }) } } @@ -315,27 +296,37 @@ pub enum SpanType { /// Represents a span of selected cells #[derive(Debug, Eq, PartialEq)] pub struct Span { - front: Point, - tail: Point, + front: Point<usize>, + tail: Point<usize>, cols: Column, /// The type says whether ends are included or not. ty: SpanType, } +#[derive(Debug)] +pub struct Locations { + /// Start point from bottom of buffer + pub start: Point<usize>, + /// End point towards top of buffer + pub end: Point<usize>, +} + impl Span { - pub fn to_locations(&self) -> (Point, Point) { - match self.ty { + pub fn to_locations(&self) -> Locations { + let (start, end) = match self.ty { SpanType::Inclusive => (self.front, self.tail), SpanType::Exclusive => { (Span::wrap_start(self.front, self.cols), Span::wrap_end(self.tail, self.cols)) }, SpanType::ExcludeFront => (Span::wrap_start(self.front, self.cols), self.tail), SpanType::ExcludeTail => (self.front, Span::wrap_end(self.tail, self.cols)) - } + }; + + Locations { start, end } } - fn wrap_start(mut start: Point, cols: Column) -> Point { + fn wrap_start(mut start: Point<usize>, cols: Column) -> Point<usize> { if start.col == cols - 1 { Point { line: start.line + 1, @@ -347,8 +338,8 @@ impl Span { } } - fn wrap_end(end: Point, cols: Column) -> Point { - if end.col == Column(0) && end.line != Line(0) { + fn wrap_end(end: Point<usize>, cols: Column) -> Point<usize> { + if end.col == Column(0) && end.line != 0 { Point { line: end.line - 1, col: cols @@ -360,37 +351,6 @@ impl Span { } } } - - #[inline] - fn exclude_start(start: Linear) -> Linear { - start + 1 - } - - #[inline] - fn exclude_end(end: Linear) -> Linear { - if end > Linear(0) { - end - 1 - } else { - end - } - } -} - -impl ToRange for Span { - fn to_range(&self) -> RangeInclusive<Linear> { - let cols = self.cols; - let start = Linear(self.front.line.0 * cols.0 + self.front.col.0); - let end = Linear(self.tail.line.0 * cols.0 + self.tail.col.0); - - let (start, end) = match self.ty { - SpanType::Inclusive => (start, end), - SpanType::Exclusive => (Span::exclude_start(start), Span::exclude_end(end)), - SpanType::ExcludeFront => (Span::exclude_start(start), end), - SpanType::ExcludeTail => (start, Span::exclude_end(end)) - }; - - RangeInclusive::new(start, end) - } } /// Tests for selection @@ -424,8 +384,8 @@ mod test { } impl super::SemanticSearch for Dimensions { - fn semantic_search_left(&self, _: Point) -> Point { unimplemented!(); } - fn semantic_search_right(&self, _: Point) -> Point { unimplemented!(); } + fn semantic_search_left(&self, _: Point<usize>) -> Point<usize> { unimplemented!(); } + fn semantic_search_right(&self, _: Point<usize>) -> Point<usize> { unimplemented!(); } } /// Test case of single cell selection @@ -435,7 +395,7 @@ mod test { /// 3. [BE] #[test] fn single_cell_left_to_right() { - let location = Point { line: Line(0), col: Column(0) }; + let location = Point { line: 0, col: Column(0) }; let mut selection = Selection::simple(location, Side::Left); selection.update(location, Side::Right); @@ -454,7 +414,7 @@ mod test { /// 3. [EB] #[test] fn single_cell_right_to_left() { - let location = Point { line: Line(0), col: Column(0) }; + let location = Point { line: 0, col: Column(0) }; let mut selection = Selection::simple(location, Side::Right); selection.update(location, Side::Left); @@ -473,8 +433,8 @@ mod test { /// 3. [ B][E ] #[test] fn between_adjacent_cells_left_to_right() { - let mut selection = Selection::simple(Point::new(Line(0), Column(0)), Side::Right); - selection.update(Point::new(Line(0), Column(1)), Side::Left); + 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(&Dimensions::new(1, 2)), None); } @@ -486,8 +446,8 @@ mod test { /// 3. [ E][B ] #[test] fn between_adjacent_cells_right_to_left() { - let mut selection = Selection::simple(Point::new(Line(0), Column(1)), Side::Left); - selection.update(Point::new(Line(0), Column(0)), Side::Right); + 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(&Dimensions::new(1, 2)), None); } @@ -497,20 +457,20 @@ mod test { /// /// 1. [ ][ ][ ][ ][ ] /// [ ][ ][ ][ ][ ] - /// 2. [ ][ ][ ][ ][ ] - /// [ ][ B][ ][ ][ ] - /// 3. [ ][ E][XX][XX][XX] - /// [XX][XB][ ][ ][ ] + /// 2. [ ][ B][ ][ ][ ] + /// [ ][ ][ ][ ][ ] + /// 3. [ ][ B][XX][XX][XX] + /// [XX][XE][ ][ ][ ] #[test] fn across_adjacent_lines_upward_final_cell_exclusive() { - let mut selection = Selection::simple(Point::new(Line(1), Column(1)), Side::Right); - selection.update(Point::new(Line(0), Column(1)), Side::Right); + let mut selection = Selection::simple(Point::new(1, Column(1)), Side::Right); + selection.update(Point::new(0, Column(1)), Side::Right); assert_eq!(selection.to_span(&Dimensions::new(2, 5)).unwrap(), Span { cols: Column(5), - front: Point::new(Line(0), Column(1)), - tail: Point::new(Line(1), Column(1)), - ty: SpanType::ExcludeFront + front: Point::new(0, Column(1)), + tail: Point::new(1, Column(2)), + ty: SpanType::Inclusive, }); } @@ -519,23 +479,23 @@ mod test { /// /// 1. [ ][ ][ ][ ][ ] /// [ ][ ][ ][ ][ ] - /// 2. [ ][ B][ ][ ][ ] - /// [ ][ ][ ][ ][ ] - /// 3. [ ][ B][XX][XX][XX] - /// [XX][XE][ ][ ][ ] - /// 4. [ ][ B][XX][XX][XX] - /// [XE][ ][ ][ ][ ] + /// 2. [ ][ ][ ][ ][ ] + /// [ ][ B][ ][ ][ ] + /// 3. [ ][ E][XX][XX][XX] + /// [XX][XB][ ][ ][ ] + /// 4. [ E][XX][XX][XX][XX] + /// [XX][XB][ ][ ][ ] #[test] fn selection_bigger_then_smaller() { - let mut selection = Selection::simple(Point::new(Line(0), Column(1)), Side::Right); - selection.update(Point::new(Line(1), Column(1)), Side::Right); - selection.update(Point::new(Line(1), Column(0)), Side::Right); + let mut selection = Selection::simple(Point::new(0, Column(1)), Side::Right); + selection.update(Point::new(1, Column(1)), Side::Right); + selection.update(Point::new(1, Column(0)), Side::Right); assert_eq!(selection.to_span(&Dimensions::new(2, 5)).unwrap(), Span { cols: Column(5), - front: Point::new(Line(0), Column(1)), - tail: Point::new(Line(1), Column(0)), - ty: SpanType::ExcludeFront + front: Point::new(0, Column(1)), + tail: Point::new(1, Column(1)), + ty: SpanType::Inclusive, }); } } |