aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoe Wilm <joe@jwilm.com>2016-12-22 13:43:06 -0500
committerJoe Wilm <joe@jwilm.com>2016-12-22 13:44:13 -0500
commit6e708d2119ce0c839a89858a42a6b124a5cf48f4 (patch)
treea4ea2078153d136536587e04922f4ec841860298
parentfd11660c0a714852a3f477a6730d49b9694e1345 (diff)
downloadalacritty-6e708d2119ce0c839a89858a42a6b124a5cf48f4.tar.gz
alacritty-6e708d2119ce0c839a89858a42a6b124a5cf48f4.zip
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.
-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);
}