diff --git a/src/event.rs b/src/event.rs index aa193e63..37a17f58 100644 --- a/src/event.rs +++ b/src/event.rs @@ -81,34 +81,33 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { } fn update_selection(&mut self, point: Point, side: Side) { + self.terminal.dirty = true; + let point = self.terminal.visible_to_buffer(point); // Update selection if one exists - let mut had_selection = false; // borrowck if let Some(ref mut selection) = *self.terminal.selection_mut() { selection.update(point, side); - had_selection = true; - } - - if had_selection { // borrowck - self.terminal.dirty = true; return; } // Otherwise, start a regular selection - self.simple_selection(point, side); + *self.terminal.selection_mut() = Some(Selection::simple(point, side)); } fn simple_selection(&mut self, point: Point, side: Side) { + let point = self.terminal.visible_to_buffer(point); *self.terminal.selection_mut() = Some(Selection::simple(point, side)); self.terminal.dirty = true; } fn semantic_selection(&mut self, point: Point) { + let point = self.terminal.visible_to_buffer(point); *self.terminal.selection_mut() = Some(Selection::semantic(point, &*self.terminal)); self.terminal.dirty = true; } fn line_selection(&mut self, point: Point) { + let point = self.terminal.visible_to_buffer(point); *self.terminal.selection_mut() = Some(Selection::lines(point)); self.terminal.dirty = true; } diff --git a/src/grid/mod.rs b/src/grid/mod.rs index cf66a420..7f648f46 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -17,7 +17,7 @@ use std::cmp::{min, max, Ordering}; use std::ops::{Deref, Range, Index, IndexMut, RangeTo, RangeFrom, RangeFull}; -use index::{self, Point, Line, Column, IndexRange, RangeInclusive}; +use index::{self, Point, Line, Column, IndexRange}; use selection::Selection; mod row; @@ -29,11 +29,6 @@ mod tests; mod storage; use self::storage::Storage; -/// Convert a type to a linear index range. -pub trait ToRange { - fn to_range(&self) -> RangeInclusive; -} - /// Bidirection iterator pub trait BidirectionalIterator: Iterator { fn prev(&mut self) -> Option; @@ -105,8 +100,17 @@ pub struct Grid { } pub struct GridIterator<'a, T: 'a> { + /// Immutable grid reference grid: &'a Grid, - pub cur: Point, + + /// Current position of the iterator within the grid. + pub cur: Point, + + /// Bottom of screen (buffer) + bot: usize, + + /// Top of screen (buffer) + top: usize, } impl Grid { @@ -135,6 +139,28 @@ impl Grid { } } + pub fn visible_to_buffer(&self, point: Point) -> Point { + Point { + line: self.visible_line_to_buffer(point.line), + col: point.col + } + } + + pub fn buffer_to_visible(&self, point: Point) -> Point { + Point { + line: self.buffer_line_to_visible(point.line), + col: point.col + } + } + + pub fn buffer_line_to_visible(&self, line: usize) -> Line { + self.offset_to_line(line - self.display_offset) + } + + pub fn visible_line_to_buffer(&self, line: Line) -> usize { + self.line_to_offset(line) + self.display_offset + } + pub fn scroll_display(&mut self, count: isize) { self.display_offset = min( max((self.display_offset as isize) + count, 0isize) as usize, @@ -224,6 +250,7 @@ impl Grid { let prev = self.lines; + self.selection = None; self.raw.rotate(*prev as isize - *target as isize); self.raw.set_visible_lines(target); self.lines = target; @@ -240,6 +267,12 @@ impl Grid { *(self.num_lines() - line - 1) } + pub fn offset_to_line(&self, offset: usize) -> Line { + assert!(offset < *self.num_lines()); + + self.lines - offset - 1 + } + #[inline] pub fn scroll_down(&mut self, region: &Range, positions: index::Line) { // Whether or not there is a scrolling region active, as long as it @@ -251,6 +284,9 @@ impl Grid { // Rotate the entire line buffer. If there's a scrolling region // active, the bottom lines are restored in the next step. self.raw.rotate_up(*positions); + if let Some(ref mut selection) = self.selection { + selection.rotate(-(*positions as isize)); + } self.decrease_scroll_limit(*positions); @@ -292,6 +328,9 @@ impl Grid { // Rotate the entire line buffer. If there's a scrolling region // active, the bottom lines are restored in the next step. self.raw.rotate(-(*positions as isize)); + if let Some(ref mut selection) = self.selection { + selection.rotate(*positions as isize); + } // Now, restore any lines outside the scroll region for idx in (*region.end .. *self.num_lines()).rev() { @@ -334,10 +373,12 @@ impl Grid { self.cols } - pub fn iter_from(&self, point: Point) -> GridIterator { + pub fn iter_from(&self, point: Point) -> GridIterator { GridIterator { grid: self, cur: point, + bot: self.display_offset, + top: self.display_offset + *self.num_lines() - 1, } } @@ -369,15 +410,12 @@ impl<'a, T> Iterator for GridIterator<'a, T> { type Item = &'a T; fn next(&mut self) -> Option { - let last_line = self.grid.num_lines() - Line(1); let last_col = self.grid.num_cols() - Column(1); match self.cur { - Point { line, col } if - (line == last_line) && - (col == last_col) => None, + Point { line, col } if (line == self.bot) && (col == last_col) => None, Point { col, .. } if (col == last_col) => { - self.cur.line += Line(1); + self.cur.line -= 1; self.cur.col = Column(0); Some(&self.grid[self.cur.line][self.cur.col]) }, @@ -394,9 +432,9 @@ impl<'a, T> BidirectionalIterator for GridIterator<'a, T> { let num_cols = self.grid.num_cols(); match self.cur { - Point { line: Line(0), col: Column(0) } => None, + Point { line, col: Column(0) } if line == self.top => None, Point { col: Column(0), .. } => { - self.cur.line -= Line(1); + self.cur.line += 1; self.cur.col = num_cols - Column(1); Some(&self.grid[self.cur.line][self.cur.col]) }, diff --git a/src/index.rs b/src/index.rs index 418faff2..ada5f5b1 100644 --- a/src/index.rs +++ b/src/index.rs @@ -28,13 +28,13 @@ pub enum Side { /// Index in the grid using row, column notation #[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize, PartialOrd)] -pub struct Point { - pub line: Line, +pub struct Point { + pub line: L, pub col: Column, } -impl Point { - pub fn new(line: Line, col: Column) -> Point { +impl Point { + pub fn new(line: L, col: Column) -> Point { Point { line, col } } } diff --git a/src/selection.rs b/src/selection.rs index b5bf5493..31787bbb 100644 --- a/src/selection.rs +++ b/src/selection.rs @@ -21,8 +21,7 @@ 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 /// @@ -47,32 +46,32 @@ pub enum Selection { }, Semantic { /// The region representing start and end of cursor movement - region: Range, + region: Range>, /// 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: Range + initial_expansion: Range> }, Lines { /// The region representing start and end of cursor movement - region: Range, + region: Range>, /// The line under the initial point. This is always selected regardless /// of which way the cursor is moved. - initial_line: Line + initial_line: usize } } /// A Point and side within that point. #[derive(Debug, Clone)] pub struct Anchor { - point: Point, + point: Point, side: Side, } impl Anchor { - fn new(point: Point, side: Side) -> Anchor { + fn new(point: Point, side: Side) -> Anchor { Anchor { point, side } } } @@ -83,9 +82,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) -> Point; /// Find the nearest semantic boundary _to the point_ of provided point. - fn semantic_search_right(&self, _: Point) -> Point; + fn semantic_search_right(&self, _: Point) -> Point; } /// A type that has 2-dimensional boundaries @@ -95,7 +94,7 @@ pub trait Dimensions { } impl Selection { - pub fn simple(location: Point, side: Side) -> Selection { + pub fn simple(location: Point, side: Side) -> Selection { Selection::Simple { region: Range { start: Anchor::new(location, side), @@ -104,7 +103,27 @@ impl Selection { } } - pub fn semantic(point: Point, grid: &G) -> Selection { + 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, ref mut initial_expansion } => { + region.start.line = (region.start.line as isize + offset) as usize; + region.end.line = (region.end.line as isize + offset) as usize; + initial_expansion.start.line = (initial_expansion.start.line as isize + offset) as usize; + initial_expansion.end.line = (initial_expansion.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, grid: &G) -> Selection { let (start, end) = (grid.semantic_search_left(point), grid.semantic_search_right(point)); Selection::Semantic { region: Range { @@ -118,7 +137,7 @@ impl Selection { } } - pub fn lines(point: Point) -> Selection { + pub fn lines(point: Point) -> Selection { Selection::Lines { region: Range { start: point, @@ -128,7 +147,7 @@ impl Selection { } } - pub fn update(&mut self, location: Point, side: Side) { + 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 } => { @@ -151,14 +170,14 @@ impl Selection { Selection::span_semantic(grid, region, initial_expansion) }, Selection::Lines { ref region, ref initial_line } => { - Selection::span_lines(grid, region, initial_line) + Selection::span_lines(grid, region, *initial_line) } } } fn span_semantic( grid: &G, - region: &Range, - initial_expansion: &Range + region: &Range>, + initial_expansion: &Range> ) -> Option where G: SemanticSearch + Dimensions { @@ -172,14 +191,20 @@ impl Selection { (region.end, region.start) }; - // Update start of selection *if* front has moved beyond initial start - if front < start { + println!("BEFORE front={:?}, start={:?}, tail={:?}, end={:?}", front, start, tail, end); + + if front < tail && front.line == tail.line { start = grid.semantic_search_left(front); + end = grid.semantic_search_right(tail); + } else { + start = grid.semantic_search_right(front); + end = 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); + println!("AFTER front={:?}, start={:?}, tail={:?}, end={:?}", front, start, tail, end); + + if start > end { + ::std::mem::swap(&mut start, &mut end); } Some(Span { @@ -190,27 +215,27 @@ impl Selection { }) } - fn span_lines(grid: &G, region: &Range, initial_line: &Line) -> Option + fn span_lines(grid: &G, region: &Range>, initial_line: usize) -> Option where G: Dimensions { // First, create start and end points based on initial line and the grid // dimensions. let mut start = Point { - col: Column(0), - line: *initial_line + col: grid.dimensions().col - 1, + line: initial_line }; let mut end = Point { - col: grid.dimensions().col - 1, - line: *initial_line + 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); } @@ -313,27 +338,37 @@ pub enum SpanType { /// Represents a span of selected cells #[derive(Debug, Eq, PartialEq)] pub struct Span { - front: Point, - tail: Point, + front: Point, + tail: Point, 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, + /// End point towards top of buffer + pub end: Point, +} + 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, cols: Column) -> Point { if start.col == cols - 1 { Point { line: start.line + 1, @@ -345,8 +380,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, cols: Column) -> Point { + if end.col == Column(0) && end.line != 0 { Point { line: end.line - 1, col: cols @@ -358,37 +393,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 { - 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 @@ -422,8 +426,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) -> Point { unimplemented!(); } + fn semantic_search_right(&self, _: Point) -> Point { unimplemented!(); } } /// Test case of single cell selection @@ -433,7 +437,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); @@ -452,7 +456,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); @@ -471,8 +475,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); } @@ -484,8 +488,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); } @@ -501,13 +505,13 @@ mod test { /// [XX][XB][ ][ ][ ] #[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)), + front: Point::new(0, Column(1)), + tail: Point::new(1, Column(1)), ty: SpanType::ExcludeFront }); } @@ -525,14 +529,14 @@ mod test { /// [XE][ ][ ][ ][ ] #[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)), + front: Point::new(0, Column(1)), + tail: Point::new(1, Column(0)), ty: SpanType::ExcludeFront }); } diff --git a/src/term/mod.rs b/src/term/mod.rs index e9a4582a..83b9e7da 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -24,9 +24,9 @@ use unicode_width::UnicodeWidthChar; use font::{self, Size}; use ansi::{self, Color, NamedColor, Attr, Handler, CharsetIndex, StandardCharset, CursorStyle}; -use grid::{BidirectionalIterator, Grid, ToRange, Indexed, IndexRegion, DisplayIter}; -use index::{self, Point, Column, Line, IndexRange, Contains, RangeInclusive}; -use selection::{self, Span, Selection}; +use grid::{BidirectionalIterator, Grid, Indexed, IndexRegion, DisplayIter}; +use index::{self, Point, Column, Line, IndexRange, Contains, RangeInclusive, Linear}; +use selection::{self, Selection, Locations}; use config::{Config, VisualBellAnimation}; use {MouseCursor, Rgb}; use copypasta::{Clipboard, Load, Store}; @@ -37,7 +37,7 @@ pub use self::cell::Cell; use self::cell::LineLength; impl selection::SemanticSearch for Term { - fn semantic_search_left(&self, mut point: Point) -> Point { + fn semantic_search_left(&self, mut point: Point) -> Point { let mut iter = self.grid.iter_from(point); let last_col = self.grid.num_cols() - Column(1); @@ -56,7 +56,7 @@ impl selection::SemanticSearch for Term { point } - fn semantic_search_right(&self, mut point: Point) -> Point { + fn semantic_search_right(&self, mut point: Point) -> Point { let mut iter = self.grid.iter_from(point); let last_col = self.grid.num_cols() - Column(1); @@ -116,12 +116,37 @@ impl<'a> RenderableCellsIter<'a> { colors: &'b color::List, mode: TermMode, config: &'b Config, - selection: Option>, + selection: Option, cursor_style: CursorStyle, ) -> RenderableCellsIter<'b> { let cursor_offset = grid.line_to_offset(cursor.line); let inner = grid.display_iter(); + let selection = selection.map(|loc| { + // start and end *lines* are swapped as we switch from buffer to + // Line coordinates. + let mut end = Point { + line: grid.buffer_line_to_visible(loc.start.line), + col: loc.start.col + }; + let mut start = Point { + line: grid.buffer_line_to_visible(loc.end.line), + col: loc.end.col + }; + + if start > end { + ::std::mem::swap(&mut start, &mut end); + } + + println!("start={:?}, end={:?}", start, end); + + let cols = grid.num_cols(); + let start = Linear(start.line.0 * cols.0 + start.col.0); + let end = Linear(end.line.0 * cols.0 + end.col.0); + + RangeInclusive::new(start, end) + }); + RenderableCellsIter { cursor: cursor, cursor_offset: cursor_offset, @@ -350,12 +375,13 @@ impl<'a> Iterator for RenderableCellsIter<'a> { } else { let cell = self.inner.next()?; + let index = Linear(cell.line.0 * self.grid.num_cols().0 + cell.column.0); + // XXX (jwilm) selection temp disabled // - // let selected = self.selection.as_ref() - // .map(|range| range.contains_(index)) - // .unwrap_or(false); - let selected = false; + let selected = self.selection.as_ref() + .map(|range| range.contains_(index)) + .unwrap_or(false); // Skip empty cells if cell.is_empty() && !selected { @@ -876,7 +902,7 @@ impl Term { /// Need a generic push() for the Append trait trait PushChar { fn push_char(&mut self, c: char); - fn maybe_newline(&mut self, grid: &Grid, line: Line, ending: Column) { + fn maybe_newline(&mut self, grid: &Grid, line: usize, ending: Column) { if ending != Column(0) && !grid[line][ending - 1].flags.contains(cell::Flags::WRAPLINE) { self.push_char('\n'); } @@ -893,14 +919,14 @@ impl Term { use std::ops::Range; trait Append : PushChar { - fn append(&mut self, grid: &Grid, line: Line, cols: Range) -> Option>; + fn append(&mut self, grid: &Grid, line: usize, cols: Range) -> Option>; } impl Append for String { fn append( &mut self, grid: &Grid, - line: Line, + line: usize, cols: Range ) -> Option> { let grid_line = &grid[line]; @@ -932,43 +958,54 @@ impl Term { let mut res = String::new(); - let (start, end) = span.to_locations(); + let Locations { mut start, mut end } = span.to_locations(); + + if start > end { + ::std::mem::swap(&mut start, &mut end); + } + let line_count = end.line - start.line; let max_col = Column(usize::max_value() - 1); match line_count { // Selection within single line - Line(0) => { + 0 => { res.append(&self.grid, start.line, start.col..end.col); }, // Selection ends on line following start - Line(1) => { - // Starting line - res.append(&self.grid, start.line, start.col..max_col); - + 1 => { // Ending line - res.append(&self.grid, end.line, Column(0)..end.col); + res.append(&self.grid, end.line, end.col..max_col); + + // Starting line + res.append(&self.grid, start.line, Column(0)..start.col); + }, // Multi line selection _ => { - // Starting line - res.append(&self.grid, start.line, start.col..max_col); + // Ending line + res.append(&self.grid, end.line, end.col..max_col); - let middle_range = IndexRange::from((start.line + 1)..(end.line)); + let middle_range = (start.line + 1)..(end.line); for line in middle_range { res.append(&self.grid, line, Column(0)..max_col); } - // Ending line - res.append(&self.grid, end.line, Column(0)..end.col); + // Starting line + res.append(&self.grid, start.line, Column(0)..(start.col + 1)); + } } Some(res) } + pub(crate) fn visible_to_buffer(&self, point: Point) -> Point { + self.grid.visible_to_buffer(point) + } + /// Convert the given pixel values to a grid coordinate /// /// The mouse coordinates are expected to be relative to the top left. The @@ -997,8 +1034,12 @@ impl Term { config: &'b Config, window_focused: bool, ) -> RenderableCellsIter { - let selection = self.grid.selection.as_ref().and_then(|s| s.to_span(self)) - .map(|span| span.to_range()); + let selection = self.grid.selection.as_ref() + .and_then(|s| s.to_span(self)) + .map(|span| { + // println!("span={:?}, locations={:?}", span, span.to_locations()); + span.to_locations() + }); let cursor = if window_focused { self.cursor_style.unwrap_or(self.default_cursor_style) } else {