aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/display.rs7
-rw-r--r--src/event.rs21
-rw-r--r--src/grid.rs6
-rw-r--r--src/index.rs62
-rw-r--r--src/input.rs98
-rw-r--r--src/lib.rs2
-rw-r--r--src/main.rs2
-rw-r--r--src/selection.rs340
-rw-r--r--src/term/mod.rs38
9 files changed, 518 insertions, 58 deletions
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<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);
});
}
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<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 => {
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<index::Linear>;
+}
+
/// Represents the terminal display contents
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
pub struct Grid<T> {
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);
-
- // TODO process changes
+ self.mouse.line = line;
+ self.mouse.column = column;
+
+ 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();
-
- 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);
- }
-
+ 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);
}
}
- 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);
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<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.
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<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
+ });
+ }
+}
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<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);
}