Implement visual component of mouse selections

This adds the ability to click and drag with the mouse and have the
effect of visually selecting text. The ability to copy the selection
into a clipboard buffer is not yet implemented.
This commit is contained in:
Joe Wilm 2016-12-22 13:43:06 -05:00
parent fd11660c0a
commit 6e708d2119
9 changed files with 515 additions and 55 deletions

View file

@ -18,13 +18,14 @@ use std::sync::mpsc;
use parking_lot::{MutexGuard};
use font;
use Rgb;
use ansi::Color;
use cli;
use config::Config;
use font;
use meter::Meter;
use renderer::{GlyphCache, QuadRenderer};
use selection::Selection;
use term::{Term, SizeInfo};
use window::{self, Size, Pixels, Window, SetInnerSize};
@ -211,7 +212,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) {
pub fn draw(&mut self, mut terminal: MutexGuard<Term>, config: &Config, selection: &Selection) {
// This is a hack since sometimes we get stuck waiting for events
// in the main loop otherwise.
//
@ -237,7 +238,7 @@ impl Display {
api.clear();
// Draw the grid
api.render_cells(terminal.renderable_cells(), glyph_cache);
api.render_cells(terminal.renderable_cells(selection), glyph_cache);
});
}

View file

@ -9,6 +9,7 @@ use glutin;
use config::Config;
use display::OnResize;
use input;
use selection::Selection;
use sync::FairMutex;
use term::{Term, SizeInfo};
use window::Window;
@ -20,6 +21,7 @@ pub struct Processor<N> {
terminal: Arc<FairMutex<Term>>,
resize_tx: mpsc::Sender<(u32, u32)>,
ref_test: bool,
pub selection: Selection,
}
/// Notify that the terminal was resized
@ -54,6 +56,7 @@ impl<N: input::Notify> Processor<N> {
input_processor: input_processor,
resize_tx: resize_tx,
ref_test: ref_test,
selection: Default::default(),
}
}
@ -97,6 +100,8 @@ impl<N: input::Notify> Processor<N> {
let processor = &mut self.input_processor;
let notifier = &mut self.notifier;
self.selection.clear();
processor.process_key(state, key, mods, notifier, *terminal.mode(), string);
},
glutin::Event::MouseInput(state, button) => {
@ -104,11 +109,21 @@ impl<N: input::Notify> Processor<N> {
let processor = &mut self.input_processor;
let notifier = &mut self.notifier;
processor.mouse_input(state, button, notifier, &terminal);
processor.mouse_input(&mut self.selection, state, button, notifier, &terminal);
*wakeup_request = true;
},
glutin::Event::MouseMoved(x, y) => {
if x > 0 && y > 0 {
self.input_processor.mouse_moved(x as u32, y as u32);
let terminal = self.terminal.lock();
self.input_processor.mouse_moved(
&mut self.selection,
*terminal.mode(),
x as u32,
y as u32
);
if !self.selection.is_empty() {
*wakeup_request = true;
}
}
},
glutin::Event::Focused(true) => {
@ -116,7 +131,6 @@ impl<N: input::Notify> Processor<N> {
terminal.dirty = true;
},
glutin::Event::MouseWheel(scroll_delta, touch_phase) => {
let terminal = self.terminal.lock();
let processor = &mut self.input_processor;
let notifier = &mut self.notifier;
@ -124,7 +138,6 @@ impl<N: input::Notify> Processor<N> {
notifier,
scroll_delta,
touch_phase,
&terminal
);
},
glutin::Event::Awakened => {

View file

@ -24,10 +24,16 @@ use std::borrow::ToOwned;
use std::cmp::Ordering;
use std::iter::IntoIterator;
use std::ops::{Deref, DerefMut, Range, RangeTo, RangeFrom, RangeFull, Index, IndexMut};
use std::ops::RangeInclusive;
use std::slice::{self, Iter, IterMut};
use index::{self, Cursor};
/// Convert a type to a linear index range.
pub trait ToRange {
fn to_range(&self, columns: index::Column) -> RangeInclusive<index::Linear>;
}
/// Represents the terminal display contents
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
pub struct Grid<T> {

View file

@ -15,11 +15,19 @@
//! Line and Column newtypes for strongly typed tty/grid/terminal APIs
/// Indexing types and implementations for Grid and Line
use std::cmp::{Ord, Ordering};
use std::fmt;
use std::iter::Step;
use std::mem;
use std::ops::{self, Deref, Add};
/// The side of a cell
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Side {
Left,
Right
}
/// Index in the grid using row, column notation
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
pub struct Cursor {
@ -27,6 +35,32 @@ pub struct Cursor {
pub col: Column,
}
/// Location
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize, PartialOrd)]
pub struct Location {
pub line: Line,
pub col: Column,
}
impl Location {
pub fn new(line: Line, col: Column) -> Location {
Location { line: line, col: col }
}
}
impl Ord for Location {
fn cmp(&self, other: &Location) -> Ordering {
use std::cmp::Ordering::*;
match (self.line.cmp(&other.line), self.col.cmp(&other.col)) {
(Equal, Equal) => Equal,
(Equal, ord) => ord,
(ord, Equal) => ord,
(Less, _) => Less,
(Greater, _) => Greater,
}
}
}
/// A line
///
/// Newtype to avoid passing values incorrectly
@ -51,6 +85,18 @@ impl fmt::Display for Column {
}
}
/// A linear index
///
/// Newtype to avoid passing values incorrectly
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd, Serialize, Deserialize)]
pub struct Linear(pub usize);
impl fmt::Display for Linear {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Linear({})", self.0)
}
}
/// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
/// file at the top-level directory of this distribution and at
/// http://rust-lang.org/COPYRIGHT.
@ -281,3 +327,19 @@ macro_rules! ops {
ops!(Line, Line);
ops!(Column, Column);
ops!(Linear, Linear);
#[cfg(test)]
mod tests {
use super::{Line, Column, Location};
#[test]
fn location_ordering() {
assert!(Location::new(Line(0), Column(0)) == Location::new(Line(0), Column(0)));
assert!(Location::new(Line(1), Column(0)) > Location::new(Line(0), Column(0)));
assert!(Location::new(Line(0), Column(1)) > Location::new(Line(0), Column(0)));
assert!(Location::new(Line(1), Column(1)) > Location::new(Line(0), Column(0)));
assert!(Location::new(Line(1), Column(1)) > Location::new(Line(0), Column(1)));
assert!(Location::new(Line(1), Column(1)) > Location::new(Line(1), Column(0)));
}
}

View file

@ -24,7 +24,6 @@
//!
//! TODO handling xmodmap would be good
use std::borrow::Cow;
use std::mem;
use copypasta::{Clipboard, Load};
use glutin::{ElementState, VirtualKeyCode, MouseButton};
@ -33,7 +32,8 @@ use glutin::{TouchPhase, MouseScrollDelta};
use config::Config;
use event_loop;
use index::{Line, Column};
use index::{Line, Column, Side, Location};
use selection::Selection;
use term::mode::{self, TermMode};
use term::{self, Term};
@ -58,6 +58,7 @@ pub struct Mouse {
scroll_px: i32,
line: Line,
column: Column,
cell_side: Side
}
impl Default for Mouse {
@ -65,10 +66,11 @@ impl Default for Mouse {
Mouse {
x: 0,
y: 0,
left_button_state: ElementState::Pressed,
left_button_state: ElementState::Released,
scroll_px: 0,
line: Line(0),
column: Column(0),
cell_side: Side::Left,
}
}
}
@ -259,7 +261,7 @@ impl Processor {
}
#[inline]
pub fn mouse_moved(&mut self, x: u32, y: u32) {
pub fn mouse_moved(&mut self, selection: &mut Selection, mode: TermMode, x: u32, y: u32) {
// Record mouse position within window. Pixel coordinates are *not*
// translated to grid coordinates here since grid coordinates are rarely
// needed and the mouse position updates frequently.
@ -267,11 +269,26 @@ impl Processor {
self.mouse.y = y;
if let Some((line, column)) = self.size_info.pixels_to_coords(x as usize, y as usize) {
// Swap values for following comparison
let _line = mem::replace(&mut self.mouse.line, line);
let _column = mem::replace(&mut self.mouse.column, column);
self.mouse.line = line;
self.mouse.column = column;
// TODO process changes
let cell_x = x as usize % self.size_info.cell_width as usize;
let half_cell_width = (self.size_info.cell_width / 2.0) as usize;
self.mouse.cell_side = if cell_x > half_cell_width {
Side::Right
} else {
Side::Left
};
if self.mouse.left_button_state == ElementState::Pressed &&
!mode.contains(mode::MOUSE_REPORT_CLICK)
{
selection.update(Location {
line: line,
col: column
}, self.mouse.cell_side);
}
}
}
@ -279,36 +296,42 @@ impl Processor {
&mut self,
button: u8,
notifier: &mut N,
terminal: &Term
) {
if terminal.mode().contains(mode::MOUSE_REPORT_CLICK) {
let (line, column) = terminal.pixels_to_coords(
self.mouse.x as usize,
self.mouse.y as usize
).unwrap();
let (line, column) = (self.mouse.line, self.mouse.column);
if line < Line(223) && column < Column(223) {
let msg = vec![
'\x1b' as u8,
'[' as u8,
'M' as u8,
32 + button,
32 + 1 + column.0 as u8,
32 + 1 + line.0 as u8,
];
notifier.notify(msg);
}
if line < Line(223) && column < Column(223) {
let msg = vec![
'\x1b' as u8,
'[' as u8,
'M' as u8,
32 + button,
32 + 1 + column.0 as u8,
32 + 1 + line.0 as u8,
];
notifier.notify(msg);
}
}
pub fn on_mouse_press<N: Notify>(&mut self, notifier: &mut N, terminal: &Term) {
self.mouse_report(0, notifier, terminal);
pub fn on_mouse_press<N: Notify>(
&mut self,
notifier: &mut N,
terminal: &Term,
selection: &mut Selection
) {
if terminal.mode().contains(mode::MOUSE_REPORT_CLICK) {
self.mouse_report(0, notifier);
return;
}
selection.clear();
}
pub fn on_mouse_release<N: Notify>(&mut self, notifier: &mut N, terminal: &Term) {
self.mouse_report(3, notifier, terminal);
if terminal.mode().contains(mode::MOUSE_REPORT_CLICK) {
self.mouse_report(3, notifier);
return;
}
}
pub fn on_mouse_wheel<N: Notify>(
@ -316,7 +339,6 @@ impl Processor {
notifier: &mut N,
delta: MouseScrollDelta,
phase: TouchPhase,
terminal: &Term
) {
match delta {
MouseScrollDelta::LineDelta(_columns, lines) => {
@ -327,7 +349,7 @@ impl Processor {
};
for _ in 0..(lines.abs() as usize) {
self.mouse_report(code, notifier, terminal);
self.mouse_report(code, notifier);
}
},
MouseScrollDelta::PixelDelta(_x, y) => {
@ -338,8 +360,7 @@ impl Processor {
},
TouchPhase::Moved => {
self.mouse.scroll_px += y as i32;
let size = terminal.size_info();
let height = size.cell_height as i32;
let height = self.size_info.cell_height as i32;
while self.mouse.scroll_px.abs() >= height {
let button = if self.mouse.scroll_px > 0 {
@ -350,7 +371,7 @@ impl Processor {
65
};
self.mouse_report(button, notifier, terminal);
self.mouse_report(button, notifier);
}
},
_ => (),
@ -361,6 +382,7 @@ impl Processor {
pub fn mouse_input<N: Notify>(
&mut self,
selection: &mut Selection,
state: ElementState,
button: MouseButton,
notifier: &mut N,
@ -372,7 +394,7 @@ impl Processor {
self.mouse.left_button_state = state;
match state {
ElementState::Pressed => {
self.on_mouse_press(notifier, terminal);
self.on_mouse_press(notifier, terminal, selection);
},
ElementState::Released => {
self.on_mouse_release(notifier, terminal);

View file

@ -15,6 +15,7 @@
//! Alacritty - The GPU Enhanced Terminal
#![feature(range_contains)]
#![feature(inclusive_range_syntax)]
#![feature(inclusive_range)]
#![feature(drop_types_in_const)]
#![feature(step_trait)]
#![feature(plugin)]
@ -62,6 +63,7 @@ pub mod index;
pub mod input;
pub mod meter;
pub mod renderer;
pub mod selection;
pub mod sync;
pub mod term;
pub mod tty;

View file

@ -150,7 +150,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);
display.draw(terminal, &config, &processor.selection);
}
// Begin shutdown if the flag was raised.

340
src/selection.rs Normal file
View file

@ -0,0 +1,340 @@
// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! State management for a selection in the grid
//!
//! A selection should start when the mouse is clicked, and it should be
//! finalized when the button is released. The selection should be cleared
//! 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::ops::RangeInclusive;
use index::{Location, Column, Side, Linear};
use grid::ToRange;
/// The area selected
///
/// Contains all the logic for processing mouse position events and providing
/// necessary info the the renderer.
#[derive(Debug)]
pub enum Selection {
/// No current selection or start of a selection
Empty,
Active {
start: Location,
end: Location,
start_side: Side,
end_side: Side
},
}
impl Default for Selection {
fn default() -> Selection {
Selection::Empty
}
}
impl Selection {
/// Create a selection in the default state
#[inline]
pub fn new() -> Selection {
Default::default()
}
/// Clear the active selection
pub fn clear(&mut self) {
mem::replace(self, Selection::Empty);
}
pub fn is_empty(&self) -> bool {
match *self {
Selection::Empty => true,
_ => false
}
}
pub fn update(&mut self, location: Location, 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
}
},
Selection::Active { start, start_side, .. } => {
// Update ends
Selection::Active {
start: start,
start_side: start_side,
end: location,
end_side: side
}
}
};
mem::replace(self, selection);
}
pub fn span(&self) -> 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 && start_side != end_side {
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::Empty => None
}
}
}
/// How to interpret the locations of a Span.
#[derive(Debug, Eq, PartialEq)]
pub enum SpanType {
/// Includes the beginning and end locations
Inclusive,
/// Exclude both beginning and end
Exclusive,
/// Excludes last cell of selection
ExcludeTail,
/// Excludes first cell of selection
ExcludeFront,
}
/// Represents a span of selected cells
#[derive(Debug, Eq, PartialEq)]
pub struct Span {
front: Location,
tail: Location,
/// The type says whether ends are included or not.
ty: SpanType,
}
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, cols: Column) -> RangeInclusive<Linear> {
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))
};
start...end
}
}
/// Tests for selection
///
/// There are comments on all of the tests describing the selection. Pictograms
/// are used to avoid ambiguity. Grid cells are represented by a [ ]. Only
/// cells that are comletely covered are counted in a selection. Ends are
/// represented by `B` and `E` for begin and end, respectively. A selected cell
/// looks like [XX], [BX] (at the start), [XB] (at the end), [XE] (at the end),
/// and [EX] (at the start), or [BE] for a single cell. Partially selected cells
/// look like [ B] and [E ].
#[cfg(test)]
mod test {
use index::{Line, Column, Side, Location};
use super::{Selection, Span, SpanType};
/// Test case of single cell selection
///
/// 1. [ ]
/// 2. [B ]
/// 3. [BE]
#[test]
fn single_cell_left_to_right() {
let location = Location { line: Line(0), col: Column(0) };
let mut selection = Selection::Empty;
selection.update(location, Side::Left);
selection.update(location, Side::Right);
assert_eq!(selection.span().unwrap(), Span {
ty: SpanType::Inclusive,
front: location,
tail: location
});
}
/// Test case of single cell selection
///
/// 1. [ ]
/// 2. [ B]
/// 3. [EB]
#[test]
fn single_cell_right_to_left() {
let location = Location { line: Line(0), col: Column(0) };
let mut selection = Selection::Empty;
selection.update(location, Side::Right);
selection.update(location, Side::Left);
assert_eq!(selection.span().unwrap(), Span {
ty: SpanType::Inclusive,
front: location,
tail: location
});
}
/// Test adjacent cell selection from left to right
///
/// 1. [ ][ ]
/// 2. [ B][ ]
/// 3. [ B][E ]
#[test]
fn between_adjacent_cells_left_to_right() {
let mut selection = Selection::Empty;
selection.update(Location::new(Line(0), Column(0)), Side::Right);
selection.update(Location::new(Line(0), Column(1)), Side::Left);
assert_eq!(selection.span(), None);
}
/// Test adjacent cell selection from right to left
///
/// 1. [ ][ ]
/// 2. [ ][B ]
/// 3. [ E][B ]
#[test]
fn between_adjacent_cells_right_to_left() {
let mut selection = Selection::Empty;
selection.update(Location::new(Line(0), Column(1)), Side::Left);
selection.update(Location::new(Line(0), Column(0)), Side::Right);
assert_eq!(selection.span(), None);
}
/// Test selection across adjacent lines
///
///
/// 1. [ ][ ][ ][ ][ ]
/// [ ][ ][ ][ ][ ]
/// 2. [ ][ ][ ][ ][ ]
/// [ ][ B][ ][ ][ ]
/// 3. [ ][ E][XX][XX][XX]
/// [XX][XB][ ][ ][ ]
#[test]
fn across_adjacent_lines_upward_final_cell_exclusive() {
let mut selection = Selection::Empty;
selection.update(Location::new(Line(1), Column(1)), Side::Right);
selection.update(Location::new(Line(0), Column(1)), Side::Right);
assert_eq!(selection.span().unwrap(), Span {
front: Location::new(Line(0), Column(1)),
tail: Location::new(Line(1), Column(1)),
ty: SpanType::ExcludeFront
});
}
/// Test selection across adjacent lines
///
///
/// 1. [ ][ ][ ][ ][ ]
/// [ ][ ][ ][ ][ ]
/// 2. [ ][ B][ ][ ][ ]
/// [ ][ ][ ][ ][ ]
/// 3. [ ][ B][XX][XX][XX]
/// [XX][XE][ ][ ][ ]
/// 4. [ ][ B][XX][XX][XX]
/// [XE][ ][ ][ ][ ]
#[test]
fn selection_bigger_then_smaller() {
let mut selection = Selection::Empty;
selection.update(Location::new(Line(0), Column(1)), Side::Right);
selection.update(Location::new(Line(1), Column(1)), Side::Right);
selection.update(Location::new(Line(1), Column(0)), Side::Right);
assert_eq!(selection.span().unwrap(), Span {
front: Location::new(Line(0), Column(1)),
tail: Location::new(Line(1), Column(0)),
ty: SpanType::ExcludeFront
});
}
}

View file

@ -13,15 +13,15 @@
// limitations under the License.
//
//! Exports the `Term` type which is a high-level API for the Grid
use std::ops::{Deref, Range};
use std::ops::{Deref, Range, RangeInclusive};
use std::ptr;
use std::cmp;
use std::io;
use ansi::{self, Attr, Handler};
use grid::{Grid, ClearRegion};
use index::{Cursor, Column, Line};
use ansi::{Color, NamedColor};
use ansi::{self, Color, NamedColor, Attr, Handler};
use grid::{Grid, ClearRegion, ToRange};
use index::{self, Cursor, Column, Line, Linear};
use selection::Selection;
pub mod cell;
pub use self::cell::Cell;
@ -40,9 +40,9 @@ pub struct RenderableCellsIter<'a> {
mode: TermMode,
line: Line,
column: Column,
selection: Option<RangeInclusive<index::Linear>>,
}
impl<'a> RenderableCellsIter<'a> {
/// Create the renderable cells iterator
///
@ -51,14 +51,19 @@ impl<'a> RenderableCellsIter<'a> {
fn new<'b>(
grid: &'b mut Grid<Cell>,
cursor: &'b Cursor,
mode: TermMode
mode: TermMode,
selection: &Selection,
) -> RenderableCellsIter<'b> {
let selection = selection.span()
.map(|span| span.to_range(grid.num_cols()));
RenderableCellsIter {
grid: grid,
cursor: cursor,
mode: mode,
line: Line(0),
column: Column(0),
selection: selection,
}.initialize()
}
@ -117,16 +122,24 @@ impl<'a> Iterator for RenderableCellsIter<'a> {
let column = self.column;
let cell = &self.grid[line][column];
let index = Linear(line.0 * self.grid.num_cols().0 + column.0);
// Update state for next iteration
self.column += 1;
let selected = self.selection.as_ref()
.map(|range| range.contains(index))
.unwrap_or(false);
// Skip empty cells
if cell.is_empty() {
if cell.is_empty() && !selected {
continue;
}
// fg, bg are dependent on INVERSE flag
let (fg, bg) = if cell.flags.contains(cell::INVERSE) {
let invert = cell.flags.contains(cell::INVERSE) || selected;
let (fg, bg) = if invert {
(&cell.bg, &cell.fg)
} else {
(&cell.fg, &cell.bg)
@ -319,8 +332,8 @@ impl Term {
/// A renderable cell is any cell which has content other than the default
/// background color. Cells with an alternate background color are
/// considered renderable as are cells with any text content.
pub fn renderable_cells(&mut self) -> RenderableCellsIter {
RenderableCellsIter::new(&mut self.grid, &self.cursor, self.mode)
pub fn renderable_cells(&mut self, selection: &Selection) -> RenderableCellsIter {
RenderableCellsIter::new(&mut self.grid, &self.cursor, self.mode, selection)
}
/// Resize terminal to new dimensions
@ -932,6 +945,7 @@ mod bench {
use std::path::Path;
use grid::Grid;
use selection::Selection;
use super::{SizeInfo, Term};
use super::cell::Cell;
@ -972,7 +986,7 @@ mod bench {
mem::swap(&mut terminal.grid, &mut grid);
b.iter(|| {
let iter = terminal.renderable_cells();
let iter = terminal.renderable_cells(&Selection::Empty);
for cell in iter {
test::black_box(cell);
}