diff --git a/src/display.rs b/src/display.rs index a6b6528d..e12d1c8f 100644 --- a/src/display.rs +++ b/src/display.rs @@ -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, config: &Config) { + pub fn draw(&mut self, mut terminal: MutexGuard, 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); }); } diff --git a/src/event.rs b/src/event.rs index b9a8551d..79fe9737 100644 --- a/src/event.rs +++ b/src/event.rs @@ -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 { terminal: Arc>, resize_tx: mpsc::Sender<(u32, u32)>, ref_test: bool, + pub selection: Selection, } /// Notify that the terminal was resized @@ -54,6 +56,7 @@ impl Processor { input_processor: input_processor, resize_tx: resize_tx, ref_test: ref_test, + selection: Default::default(), } } @@ -97,6 +100,8 @@ impl Processor { 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 Processor { 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 Processor { 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 Processor { notifier, scroll_delta, touch_phase, - &terminal ); }, glutin::Event::Awakened => { diff --git a/src/grid.rs b/src/grid.rs index d8b701b9..07255cb4 100644 --- a/src/grid.rs +++ b/src/grid.rs @@ -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; +} + /// Represents the terminal display contents #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] pub struct Grid { diff --git a/src/index.rs b/src/index.rs index dc98be1e..61b59405 100644 --- a/src/index.rs +++ b/src/index.rs @@ -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))); + } +} diff --git a/src/input.rs b/src/input.rs index 3645097e..750352cb 100644 --- a/src/input.rs +++ b/src/input.rs @@ -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(&mut self, notifier: &mut N, terminal: &Term) { - self.mouse_report(0, notifier, terminal); + pub fn on_mouse_press( + &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(&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( @@ -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( &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); diff --git a/src/lib.rs b/src/lib.rs index d9e1d063..e060da04 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/main.rs b/src/main.rs index 18a7e322..54f4abc8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -150,7 +150,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); + display.draw(terminal, &config, &processor.selection); } // Begin shutdown if the flag was raised. diff --git a/src/selection.rs b/src/selection.rs new file mode 100644 index 00000000..6c927967 --- /dev/null +++ b/src/selection.rs @@ -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 { + 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 { + 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 + }); + } +} diff --git a/src/term/mod.rs b/src/term/mod.rs index 777c3bce..b25e8382 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -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>, } - impl<'a> RenderableCellsIter<'a> { /// Create the renderable cells iterator /// @@ -51,14 +51,19 @@ impl<'a> RenderableCellsIter<'a> { fn new<'b>( grid: &'b mut Grid, 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); }