aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoe Wilm <joe@jwilm.com>2017-06-15 21:43:28 -0700
committerJoe Wilm <jwilm@users.noreply.github.com>2017-06-19 21:31:50 -0700
commit63bcb4601198790ccdb96ffec4a360ce3080e685 (patch)
treec2fb6452215559082df60a46c40473bbd5353c9c
parent3dfac443c7779646c96cadb06913518c458b0eb8 (diff)
downloadalacritty-63bcb4601198790ccdb96ffec4a360ce3080e685.tar.gz
alacritty-63bcb4601198790ccdb96ffec4a360ce3080e685.zip
Implement semantic and line selection dragging
Unlike the regular selection that is by cell, these selection modes highlight either semantic groupings or entire lines while the mouse is dragged.
-rw-r--r--src/display.rs2
-rw-r--r--src/event.rs54
-rw-r--r--src/grid.rs2
-rw-r--r--src/input.rs15
-rw-r--r--src/main.rs2
-rw-r--r--src/selection.rs382
-rw-r--r--src/term/mod.rs133
7 files changed, 373 insertions, 217 deletions
diff --git a/src/display.rs b/src/display.rs
index d8c10d18..00427e34 100644
--- a/src/display.rs
+++ b/src/display.rs
@@ -277,7 +277,7 @@ impl Display {
/// A reference to Term whose state is being drawn must be provided.
///
/// This call may block if vsync is enabled
- pub fn draw(&mut self, mut terminal: MutexGuard<Term>, config: &Config, selection: &Selection) {
+ pub fn draw(&mut self, mut terminal: MutexGuard<Term>, config: &Config, selection: Option<&Selection>) {
// Clear dirty flag
terminal.dirty = !terminal.visual_bell.completed();
diff --git a/src/event.rs b/src/event.rs
index 2d50234d..abbad0ef 100644
--- a/src/event.rs
+++ b/src/event.rs
@@ -33,7 +33,7 @@ pub trait Notify {
pub struct ActionContext<'a, N: 'a> {
pub notifier: &'a mut N,
pub terminal: &'a mut Term,
- pub selection: &'a mut Selection,
+ pub selection: &'a mut Option<Selection>,
pub size_info: &'a SizeInfo,
pub mouse: &'a mut Mouse,
}
@@ -52,32 +52,46 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> {
}
fn copy_selection(&self, buffer: ::copypasta::Buffer) {
- if let Some(selection) = self.selection.span() {
- let buf = self.terminal.string_from_selection(&selection);
- if !buf.is_empty() {
- Clipboard::new()
- .and_then(|mut clipboard| clipboard.store(buf, buffer))
- .unwrap_or_else(|err| {
- warn!("Error storing selection to clipboard. {}", Red(err));
- });
- }
+ if let &mut Some(ref selection) = self.selection {
+ selection.to_span(self.terminal as &Term)
+ .map(|span| {
+ let buf = self.terminal.string_from_selection(&span);
+ if !buf.is_empty() {
+ Clipboard::new()
+ .and_then(|mut clipboard| clipboard.store(buf, buffer))
+ .unwrap_or_else(|err| {
+ warn!("Error storing selection to clipboard. {}", Red(err));
+ });
+ }
+ });
}
}
fn clear_selection(&mut self) {
- self.selection.clear();
+ *self.selection = None;
}
fn update_selection(&mut self, point: Point, side: Side) {
- self.selection.update(point, side);
+ // Update selection if one exists
+ if let &mut Some(ref mut selection) = self.selection {
+ selection.update(point, side);
+ return;
+ }
+
+ // Otherwise, start a regular selection
+ self.simple_selection(point, side);
+ }
+
+ fn simple_selection(&mut self, point: Point, side: Side) {
+ *self.selection = Some(Selection::simple(point, side));
}
fn semantic_selection(&mut self, point: Point) {
- self.terminal.semantic_selection(&mut self.selection, point)
+ *self.selection = Some(Selection::semantic(point, self.terminal as &Term));
}
fn line_selection(&mut self, point: Point) {
- self.terminal.line_selection(&mut self.selection, point)
+ *self.selection = Some(Selection::lines(point));
}
fn mouse_coords(&self) -> Option<Point> {
@@ -141,7 +155,7 @@ pub struct Processor<N> {
resize_tx: mpsc::Sender<(u32, u32)>,
ref_test: bool,
size_info: SizeInfo,
- pub selection: Selection,
+ pub selection: Option<Selection>,
hide_cursor_when_typing: bool,
hide_cursor: bool,
}
@@ -178,7 +192,7 @@ impl<N: Notify> Processor<N> {
resize_tx: resize_tx,
ref_test: ref_test,
mouse: Default::default(),
- selection: Default::default(),
+ selection: None,
size_info: size_info,
hide_cursor_when_typing: config.hide_cursor_when_typing(),
hide_cursor: false,
@@ -243,7 +257,13 @@ impl<N: Notify> Processor<N> {
*hide_cursor = false;
processor.mouse_moved(x as u32, y as u32);
- if !processor.ctx.selection.is_empty() {
+ if !processor.ctx.selection.is_none() {
+ // TODO this should only be passed if the selection changes
+ // which happens when the point/side change *and* the mouse
+ // is still pressed.
+ //
+ // Right now, this causes lots of unnecessary redraws while
+ // selection is active.
processor.ctx.terminal.dirty = true;
}
},
diff --git a/src/grid.rs b/src/grid.rs
index cdb93c96..f630045a 100644
--- a/src/grid.rs
+++ b/src/grid.rs
@@ -30,7 +30,7 @@ use index::{self, Point, Line, Column, IndexRange, RangeInclusive};
/// Convert a type to a linear index range.
pub trait ToRange {
- fn to_range(&self, columns: index::Column) -> RangeInclusive<index::Linear>;
+ fn to_range(&self) -> RangeInclusive<index::Linear>;
}
/// Bidirection iterator
diff --git a/src/input.rs b/src/input.rs
index de8bca45..920e55f5 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -54,9 +54,10 @@ pub trait ActionContext {
fn size_info(&self) -> SizeInfo;
fn copy_selection(&self, Buffer);
fn clear_selection(&mut self);
- fn update_selection(&mut self, Point, Side);
- fn semantic_selection(&mut self, Point);
- fn line_selection(&mut self, Point);
+ fn update_selection(&mut self, point: Point, side: Side);
+ fn simple_selection(&mut self, point: Point, side: Side);
+ fn semantic_selection(&mut self, point: Point);
+ fn line_selection(&mut self, point: Point);
fn mouse_mut(&mut self) -> &mut Mouse;
fn mouse_coords(&self) -> Option<Point>;
}
@@ -494,7 +495,6 @@ mod tests {
use term::{SizeInfo, Term, TermMode, mode};
use event::{Mouse, ClickState};
use config::{self, Config, ClickHandler};
- use selection::Selection;
use index::{Point, Side};
use super::{Action, Binding, Processor};
@@ -510,7 +510,7 @@ mod tests {
struct ActionContext<'a> {
pub terminal: &'a mut Term,
- pub selection: &'a mut Selection,
+ pub selection: Option<&'a mut Selection>,
pub size_info: &'a SizeInfo,
pub mouse: &'a mut Mouse,
pub last_action: MultiClick,
@@ -578,15 +578,16 @@ mod tests {
padding_y: 0.0,
};
+ use ::ansi::TermInfo;
+
let mut terminal = Term::new(&config, size);
let mut mouse = Mouse::default();
- let mut selection = Selection::new();
mouse.click_state = $initial_state;
let context = ActionContext {
terminal: &mut terminal,
- selection: &mut selection,
+ selection: None,
mouse: &mut mouse,
size_info: &size,
last_action: MultiClick::None,
diff --git a/src/main.rs b/src/main.rs
index f4ee72b6..9db5ce58 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -179,7 +179,7 @@ fn run(mut config: Config, options: cli::Options) -> Result<(), Box<Error>> {
display.handle_resize(&mut terminal, &mut [&mut pty, &mut processor]);
// Draw the current state of the terminal
- display.draw(terminal, &config, &processor.selection);
+ display.draw(terminal, &config, processor.selection.as_ref());
}
// Begin shutdown if the flag was raised.
diff --git a/src/selection.rs b/src/selection.rs
index 3e5b799c..868ded7b 100644
--- a/src/selection.rs
+++ b/src/selection.rs
@@ -19,141 +19,285 @@
//! when text is added/removed/scrolled on the screen. The selection should
//! also be cleared if the user clicks off of the selection.
use std::mem;
+use std::cmp::min;
+use std::cmp::max;
use index::{Point, Column, RangeInclusive, Side, Linear, Line};
-use grid::ToRange;
+use grid::{Grid, ToRange};
+use term::Cell;
-/// The area selected
+/// Describes a region of a 2-dimensional area
///
-/// Contains all the logic for processing mouse position events and providing
-/// necessary info the the renderer.
-#[derive(Debug)]
+/// Used to track a text selection. There are three supported modes, each with its own constructor:
+/// [`simple`], [`semantic`], and [`lines`]. The [`simple`] mode precisely tracks which cells are
+/// selected without any expansion. [`semantic`] mode expands the initial selection to the nearest
+/// semantic escape char in either direction. [`lines`] will always select entire lines.
+///
+/// Calls to [`update`] operate different based on the selection kind. The [`simple`] mode does
+/// nothing special, simply tracks points and sides. [`semantic`] will continue to expand out to
+/// semantic boundaries as the selection point changes. Similarly, [`lines`] will always expand the
+/// new point to encompass entire lines.
+///
+/// [`simple`]: enum.Selection.html#method.simple
+/// [`semantic`]: enum.Selection.html#method.semantic
+/// [`lines`]: enum.Selection.html#method.lines
pub enum Selection {
- /// No current selection or start of a selection
- Empty,
-
- Active {
- start: Point,
- end: Point,
- start_side: Side,
- end_side: Side
+ Simple {
+ /// The region representing start and end of cursor movement
+ region: Region<Anchor>,
},
+ Semantic {
+ /// The region representing start and end of cursor movement
+ region: Region<Point>,
+
+ /// When begining a semantic selection, the grid is searched around the
+ /// initial point to find semantic escapes, and this initial expansion
+ /// marks those points.
+ initial_expansion: Region<Point>
+ },
+ Lines {
+ /// The region representing start and end of cursor movement
+ region: Region<Point>,
+
+ /// The line under the initial point. This is always selected regardless
+ /// of which way the cursor is moved.
+ initial_line: Line
+ }
+}
+
+pub struct Region<T> {
+ start: T,
+ end: T
+}
+
+/// A Point and side within that point.
+pub struct Anchor {
+ point: Point,
+ side: Side,
}
-impl Default for Selection {
- fn default() -> Selection {
- Selection::Empty
+impl Anchor {
+ fn new(point: Point, side: Side) -> Anchor {
+ Anchor { point: point, side: side }
}
}
+/// A type that can expand a given point to a region
+///
+/// Usually this is implemented for some 2-D array type since
+/// points are two dimensional indices.
+pub trait SemanticSearch {
+ /// Find the nearest semantic boundary _to the left_ of provided point.
+ fn semantic_search_left(&self, _: Point) -> Point;
+ /// Find the nearest semantic boundary _to the point_ of provided point.
+ fn semantic_search_right(&self, _: Point) -> Point;
+}
+
+/// A type that has 2-dimensional boundaries
+pub trait Dimensions {
+ /// Get the size of the area
+ fn dimensions(&self) -> Point;
+}
+
impl Selection {
- /// Create a selection in the default state
- #[inline]
- pub fn new() -> Selection {
- Default::default()
+ pub fn simple(location: Point, side: Side) -> Selection {
+ Selection::Simple {
+ region: Region {
+ start: Anchor::new(location, side),
+ end: Anchor::new(location, side)
+ }
+ }
}
- /// Clear the active selection
- pub fn clear(&mut self) {
- mem::replace(self, Selection::Empty);
+ pub fn semantic<G: SemanticSearch>(point: Point, grid: G) -> Selection {
+ let (start, end) = (grid.semantic_search_left(point), grid.semantic_search_right(point));
+ Selection::Semantic {
+ region: Region {
+ start: point,
+ end: point,
+ },
+ initial_expansion: Region {
+ start: start,
+ end: end
+ }
+ }
}
- pub fn is_empty(&self) -> bool {
- match *self {
- Selection::Empty => true,
- _ => false
+ pub fn lines(point: Point) -> Selection {
+ Selection::Lines {
+ region: Region {
+ start: point,
+ end: point
+ },
+ initial_line: point.line
}
}
pub fn update(&mut self, location: Point, side: Side) {
- let selection = mem::replace(self, Selection::Empty);
- let selection = match selection {
- Selection::Empty => {
- // Start a selection
- Selection::Active {
- start: location,
- end: location,
- start_side: side,
- end_side: side
- }
+ // Always update the `end`; can normalize later during span generation.
+ match *self {
+ Selection::Simple { ref mut region } => {
+ region.end = Anchor::new(location, side);
+ },
+ Selection::Semantic { ref mut region, .. } => {
+ region.end = location;
},
- Selection::Active { start, start_side, .. } => {
- // Update ends
- Selection::Active {
- start: start,
- start_side: start_side,
- end: location,
- end_side: side
- }
+ Selection::Lines { ref mut region, .. } => {
+ region.end = location;
}
- };
-
- mem::replace(self, selection);
+ }
}
- pub fn span(&self) -> Option<Span> {
+ pub fn to_span<G: SemanticSearch + Dimensions>(&self, grid: G) -> Option<Span> {
match *self {
- Selection::Active { ref start, ref end, ref start_side, ref end_side } => {
- let (front, tail, front_side, tail_side) = if *start > *end {
- // Selected upward; start/end are swapped
- (end, start, end_side, start_side)
- } else {
- // Selected downward; no swapping
- (start, end, start_side, end_side)
- };
-
- debug_assert!(!(tail < front));
-
- // Single-cell selections are a special case
- if start == end {
- if start_side == end_side {
- return None;
- } else {
- return Some(Span {
- ty: SpanType::Inclusive,
- front: *front,
- tail: *tail
- });
- }
- }
-
- // The other special case is two adjacent cells with no
- // selection: [ B][E ] or [ E][B ]
- let adjacent = tail.line == front.line && tail.col - front.col == Column(1);
- if adjacent && *front_side == Side::Right && *tail_side == Side::Left {
- return None;
- }
-
- Some(match (*front_side, *tail_side) {
- // [FX][XX][XT]
- (Side::Left, Side::Right) => Span {
- front: *front,
- tail: *tail,
- ty: SpanType::Inclusive
- },
- // [ F][XX][T ]
- (Side::Right, Side::Left) => Span {
- front: *front,
- tail: *tail,
- ty: SpanType::Exclusive
- },
- // [FX][XX][T ]
- (Side::Left, Side::Left) => Span {
- front: *front,
- tail: *tail,
- ty: SpanType::ExcludeTail
- },
- // [ F][XX][XT]
- (Side::Right, Side::Right) => Span {
- front: *front,
- tail: *tail,
- ty: SpanType::ExcludeFront
- },
- })
+ Selection::Simple { ref region } => {
+ Selection::span_simple(grid, region)
+ },
+ Selection::Semantic { ref region, ref initial_expansion } => {
+ Selection::span_semantic(grid, region, initial_expansion)
},
- Selection::Empty => None
+ Selection::Lines { ref region, ref initial_line } => {
+ Selection::span_lines(grid, region, initial_line)
+ }
}
}
+ fn span_semantic<G>(
+ grid: G,
+ region: &Region<Point>,
+ initial_expansion: &Region<Point>
+ ) -> Option<Span>
+ where G: SemanticSearch + Dimensions
+ {
+ let mut start = initial_expansion.start;
+ let mut end = initial_expansion.end;
+
+ // Normalize ordering of selected cells
+ let (front, tail) = if region.start < region.end {
+ (region.start, region.end)
+ } else {
+ (region.end, region.start)
+ };
+
+ // Update start of selection *if* front has moved beyond initial start
+ if front < start {
+ start = grid.semantic_search_left(front);
+ }
+
+ // Update end of selection *if* tail has moved beyond initial end.
+ if tail > end {
+ end = grid.semantic_search_right(tail);
+ }
+
+ Some(Span {
+ cols: grid.dimensions().col,
+ front: start,
+ tail: end,
+ ty: SpanType::Inclusive,
+ })
+ }
+
+ fn span_lines<G>( grid: G, region: &Region<Point>, initial_line: &Line) -> Option<Span>
+ where G: Dimensions
+ {
+ // First, create start and end points based on initial line and the grid
+ // dimensions.
+ let mut start = Point {
+ col: Column(0),
+ line: *initial_line
+ };
+ let mut end = Point {
+ col: grid.dimensions().col - 1,
+ line: *initial_line
+ };
+
+ // Now, expand lines based on where cursor started and ended.
+ if region.start.line < region.end.line {
+ // Start is above end
+ start.line = min(start.line, region.start.line);
+ end.line = max(end.line, region.end.line);
+ } else {
+ // Start is below end
+ start.line = min(start.line, region.end.line);
+ end.line = max(end.line, region.start.line);
+ }
+
+ Some(Span {
+ cols: grid.dimensions().col,
+ front: start,
+ tail: end,
+ ty: SpanType::Inclusive
+ })
+ }
+
+ fn span_simple<G: Dimensions>(grid: G, region: &Region<Anchor>) -> Option<Span> {
+ let start = region.start.point;
+ let start_side = region.start.side;
+ let end = region.end.point;
+ let end_side = region.end.side;
+ let cols = grid.dimensions().col;
+
+ let (front, tail, front_side, tail_side) = if start > end {
+ // Selected upward; start/end are swapped
+ (end, start, end_side, start_side)
+ } else {
+ // Selected downward; no swapping
+ (start, end, start_side, end_side)
+ };
+
+ debug_assert!(!(tail < front));
+
+ // Single-cell selections are a special case
+ if start == end {
+ if start_side == end_side {
+ return None;
+ } else {
+ return Some(Span {
+ cols: cols,
+ ty: SpanType::Inclusive,
+ front: front,
+ tail: tail
+ });
+ }
+ }
+
+ // The other special case is two adjacent cells with no
+ // selection: [ B][E ] or [ E][B ]
+ let adjacent = tail.line == front.line && tail.col - front.col == Column(1);
+ if adjacent && front_side == Side::Right && tail_side == Side::Left {
+ return None;
+ }
+
+ Some(match (front_side, tail_side) {
+ // [FX][XX][XT]
+ (Side::Left, Side::Right) => Span {
+ cols: cols,
+ front: front,
+ tail: tail,
+ ty: SpanType::Inclusive
+ },
+ // [ F][XX][T ]
+ (Side::Right, Side::Left) => Span {
+ cols: cols,
+ front: front,
+ tail: tail,
+ ty: SpanType::Exclusive
+ },
+ // [FX][XX][T ]
+ (Side::Left, Side::Left) => Span {
+ cols: cols,
+ front: front,
+ tail: tail,
+ ty: SpanType::ExcludeTail
+ },
+ // [ F][XX][XT]
+ (Side::Right, Side::Right) => Span {
+ cols: cols,
+ front: front,
+ tail: tail,
+ ty: SpanType::ExcludeFront
+ },
+ })
+ }
}
/// How to interpret the locations of a Span.
@@ -177,20 +321,21 @@ pub enum SpanType {
pub struct Span {
front: Point,
tail: Point,
+ cols: Column,
/// The type says whether ends are included or not.
ty: SpanType,
}
impl Span {
- pub fn to_locations(&self, cols: Column) -> (Point, Point) {
+ pub fn to_locations(&self) -> (Point, Point) {
match self.ty {
SpanType::Inclusive => (self.front, self.tail),
SpanType::Exclusive => {
- (Span::wrap_start(self.front, cols), Span::wrap_end(self.tail, cols))
+ (Span::wrap_start(self.front, self.cols), Span::wrap_end(self.tail, self.cols))
},
- SpanType::ExcludeFront => (Span::wrap_start(self.front, cols), self.tail),
- SpanType::ExcludeTail => (self.front, Span::wrap_end(self.tail, cols))
+ SpanType::ExcludeFront => (Span::wrap_start(self.front, self.cols), self.tail),
+ SpanType::ExcludeTail => (self.front, Span::wrap_end(self.tail, self.cols))
}
}
@@ -236,7 +381,8 @@ impl Span {
}
impl ToRange for Span {
- fn to_range(&self, cols: Column) -> RangeInclusive<Linear> {
+ fn to_range(&self) -> RangeInclusive<Linear> {
+ let cols = self.cols;
let start = Linear(self.front.line.0 * cols.0 + self.front.col.0);
let end = Linear(self.tail.line.0 * cols.0 + self.tail.col.0);
@@ -273,7 +419,7 @@ mod test {
#[test]
fn single_cell_left_to_right() {
let location = Point { line: Line(0), col: Column(0) };
- let mut selection = Selection::Empty;
+ let mut selection = Selection::new(Line(1), Column(1));
selection.update(location, Side::Left);
selection.update(location, Side::Right);
@@ -292,7 +438,7 @@ mod test {
#[test]
fn single_cell_right_to_left() {
let location = Point { line: Line(0), col: Column(0) };
- let mut selection = Selection::Empty;
+ let mut selection = Selection::new(Line(1), Column(1));
selection.update(location, Side::Right);
selection.update(location, Side::Left);
@@ -310,7 +456,7 @@ mod test {
/// 3. [ B][E ]
#[test]
fn between_adjacent_cells_left_to_right() {
- let mut selection = Selection::Empty;
+ let mut selection = Selection::new(Line(1), Column(2));
selection.update(Point::new(Line(0), Column(0)), Side::Right);
selection.update(Point::new(Line(0), Column(1)), Side::Left);
@@ -324,7 +470,7 @@ mod test {
/// 3. [ E][B ]
#[test]
fn between_adjacent_cells_right_to_left() {
- let mut selection = Selection::Empty;
+ let mut selection = Selection::new(Line(1), Column(2));
selection.update(Point::new(Line(0), Column(1)), Side::Left);
selection.update(Point::new(Line(0), Column(0)), Side::Right);
@@ -342,7 +488,7 @@ mod test {
/// [XX][XB][ ][ ][ ]
#[test]
fn across_adjacent_lines_upward_final_cell_exclusive() {
- let mut selection = Selection::Empty;
+ let mut selection = Selection::new(Line(2), Column(5));
selection.update(Point::new(Line(1), Column(1)), Side::Right);
selection.update(Point::new(Line(0), Column(1)), Side::Right);
@@ -366,7 +512,7 @@ mod test {
/// [XE][ ][ ][ ][ ]
#[test]
fn selection_bigger_then_smaller() {
- let mut selection = Selection::Empty;
+ let mut selection = Selection::new(Line(2), Column(5));
selection.update(Point::new(Line(0), Column(1)), Side::Right);
selection.update(Point::new(Line(1), Column(1)), Side::Right);
selection.update(Point::new(Line(1), Column(0)), Side::Right);
diff --git a/src/term/mod.rs b/src/term/mod.rs
index bbc3f1aa..1a79d309 100644
--- a/src/term/mod.rs
+++ b/src/term/mod.rs
@@ -25,7 +25,7 @@ use unicode_width::UnicodeWidthChar;
use ansi::{self, Color, NamedColor, Attr, Handler, CharsetIndex, StandardCharset, CursorStyle};
use grid::{BidirectionalIterator, Grid, ClearRegion, ToRange, Indexed};
use index::{self, Point, Column, Line, Linear, IndexRange, Contains, RangeInclusive, Side};
-use selection::{Span, Selection};
+use selection::{self, Span, Selection};
use config::{Config, VisualBellAnimation};
use Rgb;
@@ -34,6 +34,55 @@ pub mod color;
pub use self::cell::Cell;
use self::cell::LineLength;
+impl<'a> selection::SemanticSearch for &'a Term {
+ fn semantic_search_left(&self, mut point: Point) -> Point {
+ let mut iter = self.grid.iter_from(point);
+ let last_col = self.grid.num_cols() - Column(1);
+
+ while let Some(cell) = iter.prev() {
+ if self.semantic_escape_chars.contains(cell.c) {
+ break;
+ }
+
+ if iter.cur.col == last_col && !cell.flags.contains(cell::WRAPLINE) {
+ break; // cut off if on new line or hit escape char
+ }
+
+ point = iter.cur;
+ }
+
+ point
+ }
+
+ fn semantic_search_right(&self, mut point: Point) -> Point {
+ let mut iter = self.grid.iter_from(point);
+ let last_col = self.grid.num_cols() - Column(1);
+
+ while let Some(cell) = iter.next() {
+ if self.semantic_escape_chars.contains(cell.c) {
+ break;
+ }
+
+ point = iter.cur;
+
+ if iter.cur.col == last_col && !cell.flags.contains(cell::WRAPLINE) {
+ break; // cut off if on new line or hit escape char
+ }
+ }
+
+ point
+ }
+}
+
+impl<'a> selection::Dimensions for &'a Term {
+ fn dimensions(&self) -> Point {
+ Point {
+ col: self.grid.num_cols(),
+ line: self.grid.num_lines()
+ }
+ }
+}
+
/// Iterator that yields cells needing render
///
/// Yields cells that require work to be displayed (that is, not a an empty
@@ -66,12 +115,9 @@ impl<'a> RenderableCellsIter<'a> {
colors: &'b color::List,
mode: TermMode,
config: &'b Config,
- selection: &Selection,
+ selection: Option<RangeInclusive<index::Linear>>,
cursor_style: CursorStyle,
) -> RenderableCellsIter<'b> {
- let selection = selection.span()
- .map(|span| span.to_range(grid.num_cols()));
-
let cursor_index = Linear(cursor.line.0 * grid.num_cols().0 + cursor.col.0);
RenderableCellsIter {
@@ -737,64 +783,6 @@ impl Term {
self.dirty
}
- pub fn line_selection(&self, selection: &mut Selection, point: Point) {
- selection.clear();
- selection.update(Point {
- line: point.line,
- col: Column(0),
- }, Side::Left);
- selection.update(Point {
- line: point.line,
- col: self.grid.num_cols() - Column(1),
- }, Side::Right);
- }
-
- pub fn semantic_selection(&self, selection: &mut Selection, point: Point) {
- let mut side_left = Point {
- line: point.line,
- col: point.col
- };
- let mut side_right = Point {
- line: point.line,
- col: point.col
- };
-
- let mut left_iter = self.grid.iter_from(point);
- let mut right_iter = self.grid.iter_from(point);
-
- let last_col = self.grid.num_cols() - Column(1);
-
- while let Some(cell) = left_iter.prev() {
- if self.semantic_escape_chars.contains(cell.c) {
- break;
- }
-
- if left_iter.cur.col == last_col && !cell.flags.contains(cell::WRAPLINE) {
- break; // cut off if on new line or hit escape char
- }
-
- side_left.col = left_iter.cur.col;
- side_left.line = left_iter.cur.line;
- }
-
- while let Some(cell) = right_iter.next() {
- if self.semantic_escape_chars.contains(cell.c) {
- break;
- }
-
- side_right.col = right_iter.cur.col;
- side_right.line = right_iter.cur.line;
-
- if right_iter.cur.col == last_col && !cell.flags.contains(cell::WRAPLINE) {
- break; // cut off if on new line or hit escape char
- }
- }
-
- selection.clear();
- selection.update(side_left, Side::Left);
- selection.update(side_right, Side::Right);
- }
-
pub fn string_from_selection(&self, span: &Span) -> String {
/// Need a generic push() for the Append trait
trait PushChar {
@@ -882,7 +870,7 @@ impl Term {
let mut res = String::new();
- let (start, end) = span.to_locations(self.grid.num_cols());
+ let (start, end) = span.to_locations();
let line_count = end.line - start.line;
match line_count {
@@ -944,8 +932,11 @@ impl Term {
pub fn renderable_cells<'b>(
&'b self,
config: &'b Config,
- selection: &'b Selection
+ selection: Option<&'b Selection>,
) -> RenderableCellsIter {
+ let selection = selection.and_then(|s| s.to_span(self))
+ .map(|span| span.to_range());
+
RenderableCellsIter::new(
&self.grid,
&self.cursor.point,
@@ -1850,19 +1841,19 @@ mod tests {
mem::swap(&mut term.semantic_escape_chars, &mut escape_chars);
{
- let mut selection = Selection::new();
+ let mut selection = Selection::new(grid.num_lines(), grid.num_cols());
term.semantic_selection(&mut selection, Point { line: Line(0), col: Column(1) });
assert_eq!(term.string_from_selection(&selection.span().unwrap()), "aa");
}
{
- let mut selection = Selection::new();
+ let mut selection = Selection::new(grid.num_lines(), grid.num_cols());
term.semantic_selection(&mut selection, Point { line: Line(0), col: Column(4) });
assert_eq!(term.string_from_selection(&selection.span().unwrap()), "aaa");
}
{
- let mut selection = Selection::new();
+ let mut selection = Selection::new(grid.num_lines(), grid.num_cols());
term.semantic_selection(&mut selection, Point { line: Line(1), col: Column(1) });
assert_eq!(term.string_from_selection(&selection.span().unwrap()), "aaa");
}
@@ -1889,7 +1880,7 @@ mod tests {
mem::swap(&mut term.grid, &mut grid);
- let mut selection = Selection::new();
+ let mut selection = Selection::new(grid.num_lines(), grid.num_cols());
term.line_selection(&mut selection, Point { line: Line(0), col: Column(3) });
match selection.span() {
Some(span) => assert_eq!(term.string_from_selection(&span), "\"aa\"a"),
@@ -1944,7 +1935,6 @@ mod benches {
use std::path::Path;
use grid::Grid;
- use selection::Selection;
use config::Config;
use super::{SizeInfo, Term};
@@ -1983,13 +1973,12 @@ mod benches {
let size: SizeInfo = json::from_str(&serialized_size).unwrap();
let config = Config::default();
- let selection = Selection::Empty;
let mut terminal = Term::new(&config, size);
mem::swap(&mut terminal.grid, &mut grid);
b.iter(|| {
- let iter = terminal.renderable_cells(&config, &selection);
+ let iter = terminal.renderable_cells(&config, None);
for cell in iter {
test::black_box(cell);
}