terminal_emulator/
selection.rs

1// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! State management for a selection in the grid
16//!
17//! A selection should start when the mouse is clicked, and it should be
18//! finalized when the button is released. The selection should be cleared
19//! when text is added/removed/scrolled on the screen. The selection should
20//! also be cleared if the user clicks off of the selection.
21use std::cmp::{max, min};
22use std::ops::Range;
23
24use crate::index::{Column, Point, Side};
25use crate::term::Search;
26
27/// Describes a region of a 2-dimensional area
28///
29/// Used to track a text selection. There are three supported modes, each with its own constructor:
30/// [`simple`], [`semantic`], and [`lines`]. The [`simple`] mode precisely tracks which cells are
31/// selected without any expansion. [`semantic`] mode expands the initial selection to the nearest
32/// semantic escape char in either direction. [`lines`] will always select entire lines.
33///
34/// Calls to [`update`] operate different based on the selection kind. The [`simple`] mode does
35/// nothing special, simply tracks points and sides. [`semantic`] will continue to expand out to
36/// semantic boundaries as the selection point changes. Similarly, [`lines`] will always expand the
37/// new point to encompass entire lines.
38///
39/// [`simple`]: enum.Selection.html#method.simple
40/// [`semantic`]: enum.Selection.html#method.semantic
41/// [`lines`]: enum.Selection.html#method.lines
42#[derive(Debug, Clone, PartialEq)]
43pub enum Selection {
44    Simple {
45        /// The region representing start and end of cursor movement
46        region: Range<Anchor>,
47    },
48    Semantic {
49        /// The region representing start and end of cursor movement
50        region: Range<Point<isize>>,
51    },
52    Lines {
53        /// The region representing start and end of cursor movement
54        region: Range<Point<isize>>,
55
56        /// The line under the initial point. This is always selected regardless
57        /// of which way the cursor is moved.
58        initial_line: isize,
59    },
60}
61
62/// A Point and side within that point.
63#[derive(Debug, Clone, PartialEq)]
64pub struct Anchor {
65    point: Point<isize>,
66    side: Side,
67}
68
69impl Anchor {
70    fn new(point: Point<isize>, side: Side) -> Anchor {
71        Anchor { point, side }
72    }
73}
74
75/// A type that has 2-dimensional boundaries
76pub trait Dimensions {
77    /// Get the size of the area
78    fn dimensions(&self) -> Point;
79}
80
81impl Selection {
82    pub fn simple(location: Point<usize>, side: Side) -> Selection {
83        Selection::Simple {
84            region: Range {
85                start: Anchor::new(location.into(), side),
86                end: Anchor::new(location.into(), side),
87            },
88        }
89    }
90
91    pub fn rotate(&mut self, offset: isize) {
92        match *self {
93            Selection::Simple { ref mut region } => {
94                region.start.point.line += offset;
95                region.end.point.line += offset;
96            }
97            Selection::Semantic { ref mut region } => {
98                region.start.line += offset;
99                region.end.line += offset;
100            }
101            Selection::Lines {
102                ref mut region,
103                ref mut initial_line,
104            } => {
105                region.start.line += offset;
106                region.end.line += offset;
107                *initial_line += offset;
108            }
109        }
110    }
111
112    pub fn semantic(point: Point<usize>) -> Selection {
113        Selection::Semantic {
114            region: Range {
115                start: point.into(),
116                end: point.into(),
117            },
118        }
119    }
120
121    pub fn lines(point: Point<usize>) -> Selection {
122        Selection::Lines {
123            region: Range {
124                start: point.into(),
125                end: point.into(),
126            },
127            initial_line: point.line as isize,
128        }
129    }
130
131    pub fn update(&mut self, location: Point<usize>, side: Side) {
132        // Always update the `end`; can normalize later during span generation.
133        match *self {
134            Selection::Simple { ref mut region } => {
135                region.end = Anchor::new(location.into(), side);
136            }
137            Selection::Semantic { ref mut region } | Selection::Lines { ref mut region, .. } => {
138                region.end = location.into();
139            }
140        }
141    }
142
143    pub fn to_span<G>(&self, grid: &G, alt_screen: bool) -> Option<Span>
144    where
145        G: Search + Dimensions,
146    {
147        match *self {
148            Selection::Simple { ref region } => Selection::span_simple(grid, region, alt_screen),
149            Selection::Semantic { ref region } => {
150                Selection::span_semantic(grid, region, alt_screen)
151            }
152            Selection::Lines {
153                ref region,
154                initial_line,
155            } => Selection::span_lines(grid, region, initial_line, alt_screen),
156        }
157    }
158
159    pub fn is_empty(&self) -> bool {
160        match *self {
161            Selection::Simple { ref region } => {
162                region.start == region.end && region.start.side == region.end.side
163            }
164            Selection::Semantic { .. } | Selection::Lines { .. } => false,
165        }
166    }
167
168    fn span_semantic<G>(grid: &G, region: &Range<Point<isize>>, alt_screen: bool) -> Option<Span>
169    where
170        G: Search + Dimensions,
171    {
172        let cols = grid.dimensions().col;
173        let lines = grid.dimensions().line.0 as isize;
174
175        // Normalize ordering of selected cells
176        let (mut front, mut tail) = if region.start < region.end {
177            (region.start, region.end)
178        } else {
179            (region.end, region.start)
180        };
181
182        if alt_screen {
183            Selection::alt_screen_clamp(&mut front, &mut tail, lines, cols)?;
184        }
185
186        let (mut start, mut end) = if front < tail && front.line == tail.line {
187            (
188                grid.semantic_search_left(front.into()),
189                grid.semantic_search_right(tail.into()),
190            )
191        } else {
192            (
193                grid.semantic_search_right(front.into()),
194                grid.semantic_search_left(tail.into()),
195            )
196        };
197
198        if start > end {
199            ::std::mem::swap(&mut start, &mut end);
200        }
201
202        Some(Span {
203            cols,
204            front: start,
205            tail: end,
206            ty: SpanType::Inclusive,
207        })
208    }
209
210    fn span_lines<G>(
211        grid: &G,
212        region: &Range<Point<isize>>,
213        initial_line: isize,
214        alt_screen: bool,
215    ) -> Option<Span>
216    where
217        G: Dimensions,
218    {
219        let cols = grid.dimensions().col;
220        let lines = grid.dimensions().line.0 as isize;
221
222        // First, create start and end points based on initial line and the grid
223        // dimensions.
224        let mut start = Point {
225            col: cols - 1,
226            line: initial_line,
227        };
228        let mut end = Point {
229            col: Column(0),
230            line: initial_line,
231        };
232
233        // Now, expand lines based on where cursor started and ended.
234        if region.start.line < region.end.line {
235            // Start is below end
236            start.line = min(start.line, region.start.line);
237            end.line = max(end.line, region.end.line);
238        } else {
239            // Start is above end
240            start.line = min(start.line, region.end.line);
241            end.line = max(end.line, region.start.line);
242        }
243
244        if alt_screen {
245            Selection::alt_screen_clamp(&mut start, &mut end, lines, cols)?;
246        }
247
248        Some(Span {
249            cols,
250            front: start.into(),
251            tail: end.into(),
252            ty: SpanType::Inclusive,
253        })
254    }
255
256    fn span_simple<G>(grid: &G, region: &Range<Anchor>, alt_screen: bool) -> Option<Span>
257    where
258        G: Dimensions,
259    {
260        let start = region.start.point;
261        let start_side = region.start.side;
262        let end = region.end.point;
263        let end_side = region.end.side;
264        let cols = grid.dimensions().col;
265        let lines = grid.dimensions().line.0 as isize;
266
267        // Make sure front is always the "bottom" and tail is always the "top"
268        let (mut front, mut tail, front_side, tail_side) =
269            if start.line > end.line || start.line == end.line && start.col <= end.col {
270                // Selected upward; start/end are swapped
271                (end, start, end_side, start_side)
272            } else {
273                // Selected downward; no swapping
274                (start, end, start_side, end_side)
275            };
276
277        // No selection for single cell with identical sides or two cell with right+left sides
278        if (front == tail && front_side == tail_side)
279            || (tail_side == Side::Right
280                && front_side == Side::Left
281                && front.line == tail.line
282                && front.col == tail.col + 1)
283        {
284            return None;
285        }
286
287        // Remove last cell if selection ends to the left of a cell
288        if front_side == Side::Left && start != end {
289            // Special case when selection starts to left of first cell
290            if front.col == Column(0) {
291                front.col = cols - 1;
292                front.line += 1;
293            } else {
294                front.col -= 1;
295            }
296        }
297
298        // Remove first cell if selection starts at the right of a cell
299        if tail_side == Side::Right && front != tail {
300            tail.col += 1;
301        }
302
303        if alt_screen {
304            Selection::alt_screen_clamp(&mut front, &mut tail, lines, cols)?;
305        }
306
307        // Return the selection with all cells inclusive
308        Some(Span {
309            cols,
310            front: front.into(),
311            tail: tail.into(),
312            ty: SpanType::Inclusive,
313        })
314    }
315
316    // Clamp selection in the alternate screen to the visible region
317    fn alt_screen_clamp(
318        front: &mut Point<isize>,
319        tail: &mut Point<isize>,
320        lines: isize,
321        cols: Column,
322    ) -> Option<()> {
323        if tail.line >= lines {
324            // Don't show selection above visible region
325            if front.line >= lines {
326                return None;
327            }
328
329            // Clamp selection above viewport to visible region
330            tail.line = lines - 1;
331            tail.col = Column(0);
332        }
333
334        if front.line < 0 {
335            // Don't show selection below visible region
336            if tail.line < 0 {
337                return None;
338            }
339
340            // Clamp selection below viewport to visible region
341            front.line = 0;
342            front.col = cols - 1;
343        }
344
345        Some(())
346    }
347}
348
349/// How to interpret the locations of a Span.
350#[derive(Debug, Eq, PartialEq)]
351pub enum SpanType {
352    /// Includes the beginning and end locations
353    Inclusive,
354
355    /// Exclude both beginning and end
356    Exclusive,
357
358    /// Excludes last cell of selection
359    ExcludeTail,
360
361    /// Excludes first cell of selection
362    ExcludeFront,
363}
364
365/// Represents a span of selected cells
366#[derive(Debug, Eq, PartialEq)]
367pub struct Span {
368    front: Point<usize>,
369    tail: Point<usize>,
370    cols: Column,
371
372    /// The type says whether ends are included or not.
373    ty: SpanType,
374}
375
376#[derive(Debug)]
377pub struct Locations {
378    /// Start point from bottom of buffer
379    pub start: Point<usize>,
380    /// End point towards top of buffer
381    pub end: Point<usize>,
382}
383
384impl Span {
385    pub fn to_locations(&self) -> Locations {
386        let (start, end) = match self.ty {
387            SpanType::Inclusive => (self.front, self.tail),
388            SpanType::Exclusive => (
389                Span::wrap_start(self.front, self.cols),
390                Span::wrap_end(self.tail, self.cols),
391            ),
392            SpanType::ExcludeFront => (Span::wrap_start(self.front, self.cols), self.tail),
393            SpanType::ExcludeTail => (self.front, Span::wrap_end(self.tail, self.cols)),
394        };
395
396        Locations { start, end }
397    }
398
399    fn wrap_start(mut start: Point<usize>, cols: Column) -> Point<usize> {
400        if start.col == cols - 1 {
401            Point {
402                line: start.line + 1,
403                col: Column(0),
404            }
405        } else {
406            start.col += 1;
407            start
408        }
409    }
410
411    fn wrap_end(end: Point<usize>, cols: Column) -> Point<usize> {
412        if end.col == Column(0) && end.line != 0 {
413            Point {
414                line: end.line - 1,
415                col: cols,
416            }
417        } else {
418            Point {
419                line: end.line,
420                col: end.col - 1,
421            }
422        }
423    }
424}
425
426/// Tests for selection
427///
428/// There are comments on all of the tests describing the selection. Pictograms
429/// are used to avoid ambiguity. Grid cells are represented by a [  ]. Only
430/// cells that are completely covered are counted in a selection. Ends are
431/// represented by `B` and `E` for begin and end, respectively.  A selected cell
432/// looks like [XX], [BX] (at the start), [XB] (at the end), [XE] (at the end),
433/// and [EX] (at the start), or [BE] for a single cell. Partially selected cells
434/// look like [ B] and [E ].
435#[cfg(test)]
436mod test {
437    use super::{Selection, Span, SpanType};
438    use crate::index::{Column, Line, Point, Side};
439
440    struct Dimensions(Point);
441    impl super::Dimensions for Dimensions {
442        fn dimensions(&self) -> Point {
443            self.0
444        }
445    }
446
447    impl Dimensions {
448        pub fn new(line: usize, col: usize) -> Self {
449            Dimensions(Point {
450                line: Line(line),
451                col: Column(col),
452            })
453        }
454    }
455
456    impl super::Search for Dimensions {
457        fn semantic_search_left(&self, point: Point<usize>) -> Point<usize> {
458            point
459        }
460        fn semantic_search_right(&self, point: Point<usize>) -> Point<usize> {
461            point
462        }
463        fn url_search(&self, _: Point<usize>) -> Option<String> {
464            None
465        }
466    }
467
468    /// Test case of single cell selection
469    ///
470    /// 1. [  ]
471    /// 2. [B ]
472    /// 3. [BE]
473    #[test]
474    fn single_cell_left_to_right() {
475        let location = Point {
476            line: 0,
477            col: Column(0),
478        };
479        let mut selection = Selection::simple(location, Side::Left);
480        selection.update(location, Side::Right);
481
482        assert_eq!(
483            selection.to_span(&Dimensions::new(1, 1), false).unwrap(),
484            Span {
485                cols: Column(1),
486                ty: SpanType::Inclusive,
487                front: location,
488                tail: location
489            }
490        );
491    }
492
493    /// Test case of single cell selection
494    ///
495    /// 1. [  ]
496    /// 2. [ B]
497    /// 3. [EB]
498    #[test]
499    fn single_cell_right_to_left() {
500        let location = Point {
501            line: 0,
502            col: Column(0),
503        };
504        let mut selection = Selection::simple(location, Side::Right);
505        selection.update(location, Side::Left);
506
507        assert_eq!(
508            selection.to_span(&Dimensions::new(1, 1), false).unwrap(),
509            Span {
510                cols: Column(1),
511                ty: SpanType::Inclusive,
512                front: location,
513                tail: location
514            }
515        );
516    }
517
518    /// Test adjacent cell selection from left to right
519    ///
520    /// 1. [  ][  ]
521    /// 2. [ B][  ]
522    /// 3. [ B][E ]
523    #[test]
524    fn between_adjacent_cells_left_to_right() {
525        let mut selection = Selection::simple(Point::new(0, Column(0)), Side::Right);
526        selection.update(Point::new(0, Column(1)), Side::Left);
527
528        assert_eq!(selection.to_span(&Dimensions::new(1, 2), false), None);
529    }
530
531    /// Test adjacent cell selection from right to left
532    ///
533    /// 1. [  ][  ]
534    /// 2. [  ][B ]
535    /// 3. [ E][B ]
536    #[test]
537    fn between_adjacent_cells_right_to_left() {
538        let mut selection = Selection::simple(Point::new(0, Column(1)), Side::Left);
539        selection.update(Point::new(0, Column(0)), Side::Right);
540
541        assert_eq!(selection.to_span(&Dimensions::new(1, 2), false), None);
542    }
543
544    /// Test selection across adjacent lines
545    ///
546    ///
547    /// 1.  [  ][  ][  ][  ][  ]
548    ///     [  ][  ][  ][  ][  ]
549    /// 2.  [  ][ B][  ][  ][  ]
550    ///     [  ][  ][  ][  ][  ]
551    /// 3.  [  ][ B][XX][XX][XX]
552    ///     [XX][XE][  ][  ][  ]
553    #[test]
554    fn across_adjacent_lines_upward_final_cell_exclusive() {
555        let mut selection = Selection::simple(Point::new(1, Column(1)), Side::Right);
556        selection.update(Point::new(0, Column(1)), Side::Right);
557
558        assert_eq!(
559            selection.to_span(&Dimensions::new(2, 5), false).unwrap(),
560            Span {
561                cols: Column(5),
562                front: Point::new(0, Column(1)),
563                tail: Point::new(1, Column(2)),
564                ty: SpanType::Inclusive,
565            }
566        );
567    }
568
569    /// Test selection across adjacent lines
570    ///
571    ///
572    /// 1.  [  ][  ][  ][  ][  ]
573    ///     [  ][  ][  ][  ][  ]
574    /// 2.  [  ][  ][  ][  ][  ]
575    ///     [  ][ B][  ][  ][  ]
576    /// 3.  [  ][ E][XX][XX][XX]
577    ///     [XX][XB][  ][  ][  ]
578    /// 4.  [ E][XX][XX][XX][XX]
579    ///     [XX][XB][  ][  ][  ]
580    #[test]
581    fn selection_bigger_then_smaller() {
582        let mut selection = Selection::simple(Point::new(0, Column(1)), Side::Right);
583        selection.update(Point::new(1, Column(1)), Side::Right);
584        selection.update(Point::new(1, Column(0)), Side::Right);
585
586        assert_eq!(
587            selection.to_span(&Dimensions::new(2, 5), false).unwrap(),
588            Span {
589                cols: Column(5),
590                front: Point::new(0, Column(1)),
591                tail: Point::new(1, Column(1)),
592                ty: SpanType::Inclusive,
593            }
594        );
595    }
596
597    #[test]
598    fn alt_scren_lines() {
599        let mut selection = Selection::lines(Point::new(0, Column(0)));
600        selection.update(Point::new(5, Column(3)), Side::Right);
601        selection.rotate(-3);
602
603        assert_eq!(
604            selection.to_span(&Dimensions::new(10, 5), true).unwrap(),
605            Span {
606                cols: Column(5),
607                front: Point::new(0, Column(4)),
608                tail: Point::new(2, Column(0)),
609                ty: SpanType::Inclusive,
610            }
611        );
612    }
613
614    #[test]
615    fn alt_screen_semantic() {
616        let mut selection = Selection::semantic(Point::new(0, Column(0)));
617        selection.update(Point::new(5, Column(3)), Side::Right);
618        selection.rotate(-3);
619
620        assert_eq!(
621            selection.to_span(&Dimensions::new(10, 5), true).unwrap(),
622            Span {
623                cols: Column(5),
624                front: Point::new(0, Column(4)),
625                tail: Point::new(2, Column(3)),
626                ty: SpanType::Inclusive,
627            }
628        );
629    }
630
631    #[test]
632    fn alt_screen_simple() {
633        let mut selection = Selection::simple(Point::new(0, Column(0)), Side::Right);
634        selection.update(Point::new(5, Column(3)), Side::Right);
635        selection.rotate(-3);
636
637        assert_eq!(
638            selection.to_span(&Dimensions::new(10, 5), true).unwrap(),
639            Span {
640                cols: Column(5),
641                front: Point::new(0, Column(4)),
642                tail: Point::new(2, Column(4)),
643                ty: SpanType::Inclusive,
644            }
645        );
646    }
647}