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.
This commit is contained in:
Joe Wilm 2017-06-15 21:43:28 -07:00 committed by Joe Wilm
parent 3dfac443c7
commit 63bcb46011
7 changed files with 373 additions and 217 deletions

View File

@ -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<Term>, config: &Config, selection: &Selection) {
pub fn draw(&mut self, mut terminal: MutexGuard<Term>, config: &Config, selection: Option<&Selection>) {
// Clear dirty flag
terminal.dirty = !terminal.visual_bell.completed();

View File

@ -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<Selection>,
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<Point> {
@ -141,7 +155,7 @@ pub struct Processor<N> {
resize_tx: mpsc::Sender<(u32, u32)>,
ref_test: bool,
size_info: SizeInfo,
pub selection: Selection,
pub selection: Option<Selection>,
hide_cursor_when_typing: bool,
hide_cursor: bool,
}
@ -178,7 +192,7 @@ impl<N: Notify> Processor<N> {
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<N: Notify> Processor<N> {
*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;
}
},

View File

@ -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<index::Linear>;
fn to_range(&self) -> RangeInclusive<index::Linear>;
}
/// Bidirection iterator

View File

@ -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<Point>;
}
@ -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,

View File

@ -179,7 +179,7 @@ fn run(mut config: Config, options: cli::Options) -> Result<(), Box<Error>> {
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.

View File

@ -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<Anchor>,
},
Semantic {
/// The region representing start and end of cursor movement
region: Region<Point>,
/// 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<Point>
},
Lines {
/// The region representing start and end of cursor movement
region: Region<Point>,
/// 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<T> {
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<G: SemanticSearch>(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<Span> {
pub fn to_span<G: SemanticSearch + Dimensions>(&self, grid: G) -> Option<Span> {
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<G>(
grid: G,
region: &Region<Point>,
initial_expansion: &Region<Point>
) -> 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)
} 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<G>( grid: G, region: &Region<Point>, initial_line: &Line) -> 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),
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<G: Dimensions>(grid: G, region: &Region<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));
// 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<Linear> {
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);
@ -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);

View File

@ -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<RangeInclusive<index::Linear>>,
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);
}