summaryrefslogtreecommitdiff
path: root/src/selection.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/selection.rs')
-rw-r--r--src/selection.rs340
1 files changed, 340 insertions, 0 deletions
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
+ });
+ }
+}