From 63bcb4601198790ccdb96ffec4a360ce3080e685 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Thu, 15 Jun 2017 21:43:28 -0700 Subject: [PATCH] Implement semantic and line selection dragging Unlike the regular selection that is by cell, these selection modes highlight either semantic groupings or entire lines while the mouse is dragged. --- src/display.rs | 2 +- src/event.rs | 54 ++++--- src/grid.rs | 2 +- src/input.rs | 15 +- src/main.rs | 2 +- src/selection.rs | 382 ++++++++++++++++++++++++++++++++--------------- src/term/mod.rs | 133 ++++++++--------- 7 files changed, 373 insertions(+), 217 deletions(-) diff --git a/src/display.rs b/src/display.rs index d8c10d18..00427e34 100644 --- a/src/display.rs +++ b/src/display.rs @@ -277,7 +277,7 @@ impl Display { /// A reference to Term whose state is being drawn must be provided. /// /// This call may block if vsync is enabled - pub fn draw(&mut self, mut terminal: MutexGuard, config: &Config, selection: &Selection) { + pub fn draw(&mut self, mut terminal: MutexGuard, config: &Config, selection: Option<&Selection>) { // Clear dirty flag terminal.dirty = !terminal.visual_bell.completed(); diff --git a/src/event.rs b/src/event.rs index 2d50234d..abbad0ef 100644 --- a/src/event.rs +++ b/src/event.rs @@ -33,7 +33,7 @@ pub trait Notify { pub struct ActionContext<'a, N: 'a> { pub notifier: &'a mut N, pub terminal: &'a mut Term, - pub selection: &'a mut Selection, + pub selection: &'a mut Option, pub size_info: &'a SizeInfo, pub mouse: &'a mut Mouse, } @@ -52,32 +52,46 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { } fn copy_selection(&self, buffer: ::copypasta::Buffer) { - if let Some(selection) = self.selection.span() { - let buf = self.terminal.string_from_selection(&selection); - if !buf.is_empty() { - Clipboard::new() - .and_then(|mut clipboard| clipboard.store(buf, buffer)) - .unwrap_or_else(|err| { - warn!("Error storing selection to clipboard. {}", Red(err)); - }); - } + if let &mut Some(ref selection) = self.selection { + selection.to_span(self.terminal as &Term) + .map(|span| { + let buf = self.terminal.string_from_selection(&span); + if !buf.is_empty() { + Clipboard::new() + .and_then(|mut clipboard| clipboard.store(buf, buffer)) + .unwrap_or_else(|err| { + warn!("Error storing selection to clipboard. {}", Red(err)); + }); + } + }); } } fn clear_selection(&mut self) { - self.selection.clear(); + *self.selection = None; } fn update_selection(&mut self, point: Point, side: Side) { - self.selection.update(point, side); + // Update selection if one exists + if let &mut Some(ref mut selection) = self.selection { + selection.update(point, side); + return; + } + + // Otherwise, start a regular selection + self.simple_selection(point, side); + } + + fn simple_selection(&mut self, point: Point, side: Side) { + *self.selection = Some(Selection::simple(point, side)); } fn semantic_selection(&mut self, point: Point) { - self.terminal.semantic_selection(&mut self.selection, point) + *self.selection = Some(Selection::semantic(point, self.terminal as &Term)); } fn line_selection(&mut self, point: Point) { - self.terminal.line_selection(&mut self.selection, point) + *self.selection = Some(Selection::lines(point)); } fn mouse_coords(&self) -> Option { @@ -141,7 +155,7 @@ pub struct Processor { resize_tx: mpsc::Sender<(u32, u32)>, ref_test: bool, size_info: SizeInfo, - pub selection: Selection, + pub selection: Option, hide_cursor_when_typing: bool, hide_cursor: bool, } @@ -178,7 +192,7 @@ impl Processor { resize_tx: resize_tx, ref_test: ref_test, mouse: Default::default(), - selection: Default::default(), + selection: None, size_info: size_info, hide_cursor_when_typing: config.hide_cursor_when_typing(), hide_cursor: false, @@ -243,7 +257,13 @@ impl Processor { *hide_cursor = false; processor.mouse_moved(x as u32, y as u32); - if !processor.ctx.selection.is_empty() { + if !processor.ctx.selection.is_none() { + // TODO this should only be passed if the selection changes + // which happens when the point/side change *and* the mouse + // is still pressed. + // + // Right now, this causes lots of unnecessary redraws while + // selection is active. processor.ctx.terminal.dirty = true; } }, diff --git a/src/grid.rs b/src/grid.rs index cdb93c96..f630045a 100644 --- a/src/grid.rs +++ b/src/grid.rs @@ -30,7 +30,7 @@ use index::{self, Point, Line, Column, IndexRange, RangeInclusive}; /// Convert a type to a linear index range. pub trait ToRange { - fn to_range(&self, columns: index::Column) -> RangeInclusive; + fn to_range(&self) -> RangeInclusive; } /// Bidirection iterator diff --git a/src/input.rs b/src/input.rs index de8bca45..920e55f5 100644 --- a/src/input.rs +++ b/src/input.rs @@ -54,9 +54,10 @@ pub trait ActionContext { fn size_info(&self) -> SizeInfo; fn copy_selection(&self, Buffer); fn clear_selection(&mut self); - fn update_selection(&mut self, Point, Side); - fn semantic_selection(&mut self, Point); - fn line_selection(&mut self, Point); + fn update_selection(&mut self, point: Point, side: Side); + fn simple_selection(&mut self, point: Point, side: Side); + fn semantic_selection(&mut self, point: Point); + fn line_selection(&mut self, point: Point); fn mouse_mut(&mut self) -> &mut Mouse; fn mouse_coords(&self) -> Option; } @@ -494,7 +495,6 @@ mod tests { use term::{SizeInfo, Term, TermMode, mode}; use event::{Mouse, ClickState}; use config::{self, Config, ClickHandler}; - use selection::Selection; use index::{Point, Side}; use super::{Action, Binding, Processor}; @@ -510,7 +510,7 @@ mod tests { struct ActionContext<'a> { pub terminal: &'a mut Term, - pub selection: &'a mut Selection, + pub selection: Option<&'a mut Selection>, pub size_info: &'a SizeInfo, pub mouse: &'a mut Mouse, pub last_action: MultiClick, @@ -578,15 +578,16 @@ mod tests { padding_y: 0.0, }; + use ::ansi::TermInfo; + let mut terminal = Term::new(&config, size); let mut mouse = Mouse::default(); - let mut selection = Selection::new(); mouse.click_state = $initial_state; let context = ActionContext { terminal: &mut terminal, - selection: &mut selection, + selection: None, mouse: &mut mouse, size_info: &size, last_action: MultiClick::None, diff --git a/src/main.rs b/src/main.rs index f4ee72b6..9db5ce58 100644 --- a/src/main.rs +++ b/src/main.rs @@ -179,7 +179,7 @@ fn run(mut config: Config, options: cli::Options) -> Result<(), Box> { display.handle_resize(&mut terminal, &mut [&mut pty, &mut processor]); // Draw the current state of the terminal - display.draw(terminal, &config, &processor.selection); + display.draw(terminal, &config, processor.selection.as_ref()); } // Begin shutdown if the flag was raised. diff --git a/src/selection.rs b/src/selection.rs index 3e5b799c..868ded7b 100644 --- a/src/selection.rs +++ b/src/selection.rs @@ -19,141 +19,285 @@ //! 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::mem; +use std::cmp::min; +use std::cmp::max; use index::{Point, Column, RangeInclusive, Side, Linear, Line}; -use grid::ToRange; +use grid::{Grid, ToRange}; +use term::Cell; -/// The area selected +/// Describes a region of a 2-dimensional area /// -/// Contains all the logic for processing mouse position events and providing -/// necessary info the the renderer. -#[derive(Debug)] +/// 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 +/// selected without any expansion. [`semantic`] mode expands the initial selection to the nearest +/// semantic escape char in either direction. [`lines`] will always select entire lines. +/// +/// Calls to [`update`] operate different based on the selection kind. The [`simple`] mode does +/// nothing special, simply tracks points and sides. [`semantic`] will continue to expand out to +/// semantic boundaries as the selection point changes. Similarly, [`lines`] will always expand the +/// new point to encompass entire lines. +/// +/// [`simple`]: enum.Selection.html#method.simple +/// [`semantic`]: enum.Selection.html#method.semantic +/// [`lines`]: enum.Selection.html#method.lines pub enum Selection { - /// No current selection or start of a selection - Empty, - - Active { - start: Point, - end: Point, - start_side: Side, - end_side: Side + Simple { + /// The region representing start and end of cursor movement + region: Region, }, + Semantic { + /// The region representing start and end of cursor movement + region: Region, + + /// When begining 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 + }, + Lines { + /// The region representing start and end of cursor movement + region: Region, + + /// The line under the initial point. This is always selected regardless + /// of which way the cursor is moved. + initial_line: Line + } } -impl Default for Selection { - fn default() -> Selection { - Selection::Empty +pub struct Region { + start: T, + end: T +} + +/// A Point and side within that point. +pub struct Anchor { + point: Point, + side: Side, +} + +impl Anchor { + fn new(point: Point, side: Side) -> Anchor { + Anchor { point: point, side: side } } } +/// A type that can expand a given point to a region +/// +/// Usually this is implemented for some 2-D array type since +/// 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; + /// Find the nearest semantic boundary _to the point_ of provided point. + fn semantic_search_right(&self, _: Point) -> Point; +} + +/// A type that has 2-dimensional boundaries +pub trait Dimensions { + /// Get the size of the area + fn dimensions(&self) -> Point; +} + impl Selection { - /// Create a selection in the default state - #[inline] - pub fn new() -> Selection { - Default::default() + pub fn simple(location: Point, side: Side) -> Selection { + Selection::Simple { + region: Region { + start: Anchor::new(location, side), + end: Anchor::new(location, side) + } + } } - /// Clear the active selection - pub fn clear(&mut self) { - mem::replace(self, Selection::Empty); + pub fn semantic(point: Point, grid: G) -> Selection { + let (start, end) = (grid.semantic_search_left(point), grid.semantic_search_right(point)); + Selection::Semantic { + region: Region { + start: point, + end: point, + }, + initial_expansion: Region { + start: start, + end: end + } + } } - pub fn is_empty(&self) -> bool { - match *self { - Selection::Empty => true, - _ => false + pub fn lines(point: Point) -> Selection { + Selection::Lines { + region: Region { + start: point, + end: point + }, + initial_line: point.line } } pub fn update(&mut self, location: Point, side: Side) { - let selection = mem::replace(self, Selection::Empty); - let selection = match selection { - Selection::Empty => { - // Start a selection - Selection::Active { - start: location, - end: location, - start_side: side, - end_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::Active { start, start_side, .. } => { - // Update ends - Selection::Active { - start: start, - start_side: start_side, - end: location, - end_side: side - } + Selection::Semantic { ref mut region, .. } => { + region.end = location; + }, + Selection::Lines { ref mut region, .. } => { + region.end = location; } - }; - - mem::replace(self, selection); + } } - pub fn span(&self) -> Option { + pub fn to_span(&self, grid: G) -> Option { match *self { - Selection::Active { ref start, ref end, ref start_side, ref end_side } => { - 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)); - - // Single-cell selections are a special case - if start == end { - if start_side == end_side { - return None; - } else { - return Some(Span { - ty: SpanType::Inclusive, - front: *front, - tail: *tail - }); - } - } - - // 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; - } - - Some(match (*front_side, *tail_side) { - // [FX][XX][XT] - (Side::Left, Side::Right) => Span { - front: *front, - tail: *tail, - ty: SpanType::Inclusive - }, - // [ F][XX][T ] - (Side::Right, Side::Left) => Span { - front: *front, - tail: *tail, - ty: SpanType::Exclusive - }, - // [FX][XX][T ] - (Side::Left, Side::Left) => Span { - front: *front, - tail: *tail, - ty: SpanType::ExcludeTail - }, - // [ F][XX][XT] - (Side::Right, Side::Right) => Span { - front: *front, - tail: *tail, - ty: SpanType::ExcludeFront - }, - }) + Selection::Simple { ref region } => { + Selection::span_simple(grid, region) }, - Selection::Empty => None + Selection::Semantic { ref region, ref initial_expansion } => { + Selection::span_semantic(grid, region, initial_expansion) + }, + Selection::Lines { ref region, ref initial_line } => { + Selection::span_lines(grid, region, initial_line) + } } } + fn span_semantic( + grid: G, + region: &Region, + initial_expansion: &Region + ) -> Option + 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) + } else { + (region.end, region.start) + }; + + // Update start of selection *if* front has moved beyond initial start + if front < start { + start = grid.semantic_search_left(front); + } + + // Update end of selection *if* tail has moved beyond initial end. + if tail > end { + end = grid.semantic_search_right(tail); + } + + Some(Span { + cols: grid.dimensions().col, + front: start, + tail: end, + ty: SpanType::Inclusive, + }) + } + + fn span_lines( grid: G, region: &Region, initial_line: &Line) -> 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 + }; + let mut end = Point { + col: grid.dimensions().col - 1, + 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.line = min(start.line, region.start.line); + end.line = max(end.line, region.end.line); + } else { + // Start is below end + start.line = min(start.line, region.end.line); + end.line = max(end.line, region.start.line); + } + + Some(Span { + cols: grid.dimensions().col, + front: start, + tail: end, + ty: SpanType::Inclusive + }) + } + + fn span_simple(grid: G, region: &Region) -> Option { + 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)); + + // Single-cell selections are a special case + if start == end { + if start_side == end_side { + return None; + } else { + return Some(Span { + cols: cols, + ty: SpanType::Inclusive, + front: front, + tail: tail + }); + } + } + + // 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; + } + + Some(match (front_side, tail_side) { + // [FX][XX][XT] + (Side::Left, Side::Right) => Span { + cols: cols, + front: front, + tail: tail, + ty: SpanType::Inclusive + }, + // [ F][XX][T ] + (Side::Right, Side::Left) => Span { + cols: cols, + front: front, + tail: tail, + ty: SpanType::Exclusive + }, + // [FX][XX][T ] + (Side::Left, Side::Left) => Span { + cols: cols, + front: front, + tail: tail, + ty: SpanType::ExcludeTail + }, + // [ F][XX][XT] + (Side::Right, Side::Right) => Span { + cols: cols, + front: front, + tail: tail, + ty: SpanType::ExcludeFront + }, + }) + } } /// How to interpret the locations of a Span. @@ -177,20 +321,21 @@ pub enum SpanType { pub struct Span { front: Point, tail: Point, + cols: Column, /// The type says whether ends are included or not. ty: SpanType, } impl Span { - pub fn to_locations(&self, cols: Column) -> (Point, Point) { + pub fn to_locations(&self) -> (Point, Point) { match self.ty { SpanType::Inclusive => (self.front, self.tail), SpanType::Exclusive => { - (Span::wrap_start(self.front, cols), Span::wrap_end(self.tail, cols)) + (Span::wrap_start(self.front, self.cols), Span::wrap_end(self.tail, self.cols)) }, - SpanType::ExcludeFront => (Span::wrap_start(self.front, cols), self.tail), - SpanType::ExcludeTail => (self.front, Span::wrap_end(self.tail, cols)) + SpanType::ExcludeFront => (Span::wrap_start(self.front, self.cols), self.tail), + SpanType::ExcludeTail => (self.front, Span::wrap_end(self.tail, self.cols)) } } @@ -236,7 +381,8 @@ impl Span { } impl ToRange for Span { - fn to_range(&self, cols: Column) -> RangeInclusive { + 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); @@ -273,7 +419,7 @@ mod test { #[test] fn single_cell_left_to_right() { let location = Point { line: Line(0), col: Column(0) }; - let mut selection = Selection::Empty; + let mut selection = Selection::new(Line(1), Column(1)); selection.update(location, Side::Left); selection.update(location, Side::Right); @@ -292,7 +438,7 @@ mod test { #[test] fn single_cell_right_to_left() { let location = Point { line: Line(0), col: Column(0) }; - let mut selection = Selection::Empty; + let mut selection = Selection::new(Line(1), Column(1)); selection.update(location, Side::Right); selection.update(location, Side::Left); @@ -310,7 +456,7 @@ mod test { /// 3. [ B][E ] #[test] fn between_adjacent_cells_left_to_right() { - let mut selection = Selection::Empty; + let mut selection = Selection::new(Line(1), Column(2)); selection.update(Point::new(Line(0), Column(0)), Side::Right); selection.update(Point::new(Line(0), Column(1)), Side::Left); @@ -324,7 +470,7 @@ mod test { /// 3. [ E][B ] #[test] fn between_adjacent_cells_right_to_left() { - let mut selection = Selection::Empty; + let mut selection = Selection::new(Line(1), Column(2)); selection.update(Point::new(Line(0), Column(1)), Side::Left); selection.update(Point::new(Line(0), Column(0)), Side::Right); @@ -342,7 +488,7 @@ mod test { /// [XX][XB][ ][ ][ ] #[test] fn across_adjacent_lines_upward_final_cell_exclusive() { - let mut selection = Selection::Empty; + let mut selection = Selection::new(Line(2), Column(5)); selection.update(Point::new(Line(1), Column(1)), Side::Right); selection.update(Point::new(Line(0), Column(1)), Side::Right); @@ -366,7 +512,7 @@ mod test { /// [XE][ ][ ][ ][ ] #[test] fn selection_bigger_then_smaller() { - let mut selection = Selection::Empty; + let mut selection = Selection::new(Line(2), Column(5)); selection.update(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); diff --git a/src/term/mod.rs b/src/term/mod.rs index bbc3f1aa..1a79d309 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -25,7 +25,7 @@ use unicode_width::UnicodeWidthChar; use ansi::{self, Color, NamedColor, Attr, Handler, CharsetIndex, StandardCharset, CursorStyle}; use grid::{BidirectionalIterator, Grid, ClearRegion, ToRange, Indexed}; use index::{self, Point, Column, Line, Linear, IndexRange, Contains, RangeInclusive, Side}; -use selection::{Span, Selection}; +use selection::{self, Span, Selection}; use config::{Config, VisualBellAnimation}; use Rgb; @@ -34,6 +34,55 @@ pub mod color; pub use self::cell::Cell; use self::cell::LineLength; +impl<'a> selection::SemanticSearch for &'a Term { + 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); + + while let Some(cell) = iter.prev() { + if self.semantic_escape_chars.contains(cell.c) { + break; + } + + if iter.cur.col == last_col && !cell.flags.contains(cell::WRAPLINE) { + break; // cut off if on new line or hit escape char + } + + point = iter.cur; + } + + 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); + + while let Some(cell) = iter.next() { + if self.semantic_escape_chars.contains(cell.c) { + break; + } + + point = iter.cur; + + if iter.cur.col == last_col && !cell.flags.contains(cell::WRAPLINE) { + break; // cut off if on new line or hit escape char + } + } + + point + } +} + +impl<'a> selection::Dimensions for &'a Term { + fn dimensions(&self) -> Point { + Point { + col: self.grid.num_cols(), + line: self.grid.num_lines() + } + } +} + /// Iterator that yields cells needing render /// /// Yields cells that require work to be displayed (that is, not a an empty @@ -66,12 +115,9 @@ impl<'a> RenderableCellsIter<'a> { colors: &'b color::List, mode: TermMode, config: &'b Config, - selection: &Selection, + selection: Option>, cursor_style: CursorStyle, ) -> RenderableCellsIter<'b> { - let selection = selection.span() - .map(|span| span.to_range(grid.num_cols())); - let cursor_index = Linear(cursor.line.0 * grid.num_cols().0 + cursor.col.0); RenderableCellsIter { @@ -737,64 +783,6 @@ impl Term { self.dirty } - pub fn line_selection(&self, selection: &mut Selection, point: Point) { - selection.clear(); - selection.update(Point { - line: point.line, - col: Column(0), - }, Side::Left); - selection.update(Point { - line: point.line, - col: self.grid.num_cols() - Column(1), - }, Side::Right); - } - - pub fn semantic_selection(&self, selection: &mut Selection, point: Point) { - let mut side_left = Point { - line: point.line, - col: point.col - }; - let mut side_right = Point { - line: point.line, - col: point.col - }; - - let mut left_iter = self.grid.iter_from(point); - let mut right_iter = self.grid.iter_from(point); - - let last_col = self.grid.num_cols() - Column(1); - - while let Some(cell) = left_iter.prev() { - if self.semantic_escape_chars.contains(cell.c) { - break; - } - - if left_iter.cur.col == last_col && !cell.flags.contains(cell::WRAPLINE) { - break; // cut off if on new line or hit escape char - } - - side_left.col = left_iter.cur.col; - side_left.line = left_iter.cur.line; - } - - while let Some(cell) = right_iter.next() { - if self.semantic_escape_chars.contains(cell.c) { - break; - } - - side_right.col = right_iter.cur.col; - side_right.line = right_iter.cur.line; - - if right_iter.cur.col == last_col && !cell.flags.contains(cell::WRAPLINE) { - break; // cut off if on new line or hit escape char - } - } - - selection.clear(); - selection.update(side_left, Side::Left); - selection.update(side_right, Side::Right); - } - pub fn string_from_selection(&self, span: &Span) -> String { /// Need a generic push() for the Append trait trait PushChar { @@ -882,7 +870,7 @@ impl Term { let mut res = String::new(); - let (start, end) = span.to_locations(self.grid.num_cols()); + let (start, end) = span.to_locations(); let line_count = end.line - start.line; match line_count { @@ -944,8 +932,11 @@ impl Term { pub fn renderable_cells<'b>( &'b self, config: &'b Config, - selection: &'b Selection + selection: Option<&'b Selection>, ) -> RenderableCellsIter { + let selection = selection.and_then(|s| s.to_span(self)) + .map(|span| span.to_range()); + RenderableCellsIter::new( &self.grid, &self.cursor.point, @@ -1850,19 +1841,19 @@ mod tests { mem::swap(&mut term.semantic_escape_chars, &mut escape_chars); { - let mut selection = Selection::new(); + let mut selection = Selection::new(grid.num_lines(), grid.num_cols()); term.semantic_selection(&mut selection, Point { line: Line(0), col: Column(1) }); assert_eq!(term.string_from_selection(&selection.span().unwrap()), "aa"); } { - let mut selection = Selection::new(); + let mut selection = Selection::new(grid.num_lines(), grid.num_cols()); term.semantic_selection(&mut selection, Point { line: Line(0), col: Column(4) }); assert_eq!(term.string_from_selection(&selection.span().unwrap()), "aaa"); } { - let mut selection = Selection::new(); + let mut selection = Selection::new(grid.num_lines(), grid.num_cols()); term.semantic_selection(&mut selection, Point { line: Line(1), col: Column(1) }); assert_eq!(term.string_from_selection(&selection.span().unwrap()), "aaa"); } @@ -1889,7 +1880,7 @@ mod tests { mem::swap(&mut term.grid, &mut grid); - let mut selection = Selection::new(); + let mut selection = Selection::new(grid.num_lines(), grid.num_cols()); term.line_selection(&mut selection, Point { line: Line(0), col: Column(3) }); match selection.span() { Some(span) => assert_eq!(term.string_from_selection(&span), "\"aa\"a"), @@ -1944,7 +1935,6 @@ mod benches { use std::path::Path; use grid::Grid; - use selection::Selection; use config::Config; use super::{SizeInfo, Term}; @@ -1983,13 +1973,12 @@ mod benches { let size: SizeInfo = json::from_str(&serialized_size).unwrap(); let config = Config::default(); - let selection = Selection::Empty; let mut terminal = Term::new(&config, size); mem::swap(&mut terminal.grid, &mut grid); b.iter(|| { - let iter = terminal.renderable_cells(&config, &selection); + let iter = terminal.renderable_cells(&config, None); for cell in iter { test::black_box(cell); }