term_model/
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::convert::TryFrom;
22use std::mem;
23use std::ops::Range;
24
25use crate::index::{Column, Line, Point, Side};
26use crate::term::cell::Flags;
27use crate::term::{Search, Term};
28
29/// A Point and side within that point.
30#[derive(Debug, Copy, Clone, PartialEq)]
31pub struct Anchor {
32    point: Point<usize>,
33    side: Side,
34}
35
36impl Anchor {
37    fn new(point: Point<usize>, side: Side) -> Anchor {
38        Anchor { point, side }
39    }
40}
41
42/// Represents a range of selected cells.
43#[derive(Copy, Clone, Debug, Eq, PartialEq)]
44pub struct SelectionRange<L = usize> {
45    /// Start point, top left of the selection.
46    pub start: Point<L>,
47    /// End point, bottom right of the selection.
48    pub end: Point<L>,
49    /// Whether this selection is a block selection.
50    pub is_block: bool,
51}
52
53impl<L> SelectionRange<L> {
54    pub fn new(start: Point<L>, end: Point<L>, is_block: bool) -> Self {
55        Self { start, end, is_block }
56    }
57
58    pub fn contains(&self, col: Column, line: L) -> bool
59    where
60        L: PartialEq + PartialOrd,
61    {
62        self.start.line <= line
63            && self.end.line >= line
64            && (self.start.col <= col || (self.start.line != line && !self.is_block))
65            && (self.end.col >= col || (self.end.line != line && !self.is_block))
66    }
67}
68
69/// Different kinds of selection.
70#[derive(Debug, Copy, Clone, PartialEq)]
71enum SelectionType {
72    Simple,
73    Block,
74    Semantic,
75    Lines,
76}
77
78/// Describes a region of a 2-dimensional area.
79///
80/// Used to track a text selection. There are four supported modes, each with its own constructor:
81/// [`simple`], [`block`], [`semantic`], and [`lines`]. The [`simple`] mode precisely tracks which
82/// cells are selected without any expansion. [`block`] will select rectangular regions.
83/// [`semantic`] mode expands the initial selection to the nearest semantic escape char in either
84/// direction. [`lines`] will always select entire lines.
85///
86/// Calls to [`update`] operate different based on the selection kind. The [`simple`] and [`block`]
87/// mode do nothing special, simply track points and sides. [`semantic`] will continue to expand
88/// out to semantic boundaries as the selection point changes. Similarly, [`lines`] will always
89/// expand the new point to encompass entire lines.
90///
91/// [`simple`]: enum.Selection.html#method.simple
92/// [`block`]: enum.Selection.html#method.block
93/// [`semantic`]: enum.Selection.html#method.semantic
94/// [`lines`]: enum.Selection.html#method.lines
95/// [`update`]: enum.Selection.html#method.update
96#[derive(Debug, Clone, PartialEq)]
97pub struct Selection {
98    region: Range<Anchor>,
99    ty: SelectionType,
100}
101
102impl Selection {
103    pub fn simple(location: Point<usize>, side: Side) -> Selection {
104        Self {
105            region: Range { start: Anchor::new(location, side), end: Anchor::new(location, side) },
106            ty: SelectionType::Simple,
107        }
108    }
109
110    pub fn block(location: Point<usize>, side: Side) -> Selection {
111        Self {
112            region: Range { start: Anchor::new(location, side), end: Anchor::new(location, side) },
113            ty: SelectionType::Block,
114        }
115    }
116
117    pub fn semantic(location: Point<usize>) -> Selection {
118        Self {
119            region: Range {
120                start: Anchor::new(location, Side::Left),
121                end: Anchor::new(location, Side::Right),
122            },
123            ty: SelectionType::Semantic,
124        }
125    }
126
127    pub fn lines(location: Point<usize>) -> Selection {
128        Self {
129            region: Range {
130                start: Anchor::new(location, Side::Left),
131                end: Anchor::new(location, Side::Right),
132            },
133            ty: SelectionType::Lines,
134        }
135    }
136
137    pub fn update(&mut self, location: Point<usize>, side: Side) {
138        self.region.end.point = location;
139        self.region.end.side = side;
140    }
141
142    pub fn rotate(
143        mut self,
144        num_lines: usize,
145        num_cols: usize,
146        scrolling_region: &Range<Line>,
147        offset: isize,
148    ) -> Option<Selection> {
149        // Convert scrolling region from viewport to buffer coordinates
150        let region_start = num_lines - scrolling_region.start.0;
151        let region_end = num_lines - scrolling_region.end.0;
152
153        let (mut start, mut end) = (&mut self.region.start, &mut self.region.end);
154        if Self::points_need_swap(start.point, end.point) {
155            mem::swap(&mut start, &mut end);
156        }
157
158        // Rotate start of selection
159        if (start.point.line < region_start || region_start == num_lines)
160            && start.point.line >= region_end
161        {
162            start.point.line = usize::try_from(start.point.line as isize + offset).unwrap_or(0);
163
164            // If end is within the same region, delete selection once start rotates out
165            if start.point.line < region_end && end.point.line >= region_end {
166                return None;
167            }
168
169            // Clamp selection to start of region
170            if start.point.line >= region_start && region_start != num_lines {
171                if self.ty != SelectionType::Block {
172                    start.point.col = Column(0);
173                    start.side = Side::Left;
174                }
175                start.point.line = region_start - 1;
176            }
177        }
178
179        // Rotate end of selection
180        if (end.point.line < region_start || region_start == num_lines)
181            && end.point.line >= region_end
182        {
183            end.point.line = usize::try_from(end.point.line as isize + offset).unwrap_or(0);
184
185            // Delete selection if end has overtaken the start
186            if end.point.line > start.point.line {
187                return None;
188            }
189
190            // Clamp selection to end of region
191            if end.point.line < region_end {
192                if self.ty != SelectionType::Block {
193                    end.point.col = Column(num_cols - 1);
194                    end.side = Side::Right;
195                }
196                end.point.line = region_end;
197            }
198        }
199
200        Some(self)
201    }
202
203    pub fn is_empty(&self) -> bool {
204        match self.ty {
205            SelectionType::Simple => {
206                let (mut start, mut end) = (self.region.start, self.region.end);
207                if Selection::points_need_swap(start.point, end.point) {
208                    mem::swap(&mut start, &mut end);
209                }
210
211                // Simple selection is empty when the points are identical
212                // or two adjacent cells have the sides right -> left
213                start == end
214                    || (start.side == Side::Right
215                        && end.side == Side::Left
216                        && (start.point.line == end.point.line)
217                        && start.point.col + 1 == end.point.col)
218            }
219            SelectionType::Block => {
220                let (start, end) = (self.region.start, self.region.end);
221
222                // Block selection is empty when the points' columns and sides are identical
223                // or two cells with adjacent columns have the sides right -> left,
224                // regardless of their lines
225                (start.point.col == end.point.col && start.side == end.side)
226                    || (start.point.col + 1 == end.point.col
227                        && start.side == Side::Right
228                        && end.side == Side::Left)
229                    || (end.point.col + 1 == start.point.col
230                        && start.side == Side::Left
231                        && end.side == Side::Right)
232            }
233            SelectionType::Semantic | SelectionType::Lines => false,
234        }
235    }
236
237    /// Convert selection to grid coordinates.
238    pub fn to_range<T>(&self, term: &Term<T>) -> Option<SelectionRange> {
239        let grid = term.grid();
240        let num_cols = grid.num_cols();
241
242        // Order start above the end
243        let (mut start, mut end) = (self.region.start, self.region.end);
244        if Self::points_need_swap(start.point, end.point) {
245            mem::swap(&mut start, &mut end);
246        }
247
248        // Clamp to inside the grid buffer
249        let is_block = self.ty == SelectionType::Block;
250        let (start, end) = Self::grid_clamp(start, end, is_block, grid.len()).ok()?;
251
252        let range = match self.ty {
253            SelectionType::Simple => self.range_simple(start, end, num_cols),
254            SelectionType::Block => self.range_block(start, end),
255            SelectionType::Semantic => Self::range_semantic(term, start.point, end.point),
256            SelectionType::Lines => Self::range_lines(term, start.point, end.point),
257        };
258
259        // Expand selection across fullwidth cells
260        range.map(|range| Self::range_expand_fullwidth(term, range))
261    }
262
263    /// Expand the start/end of the selection range to account for fullwidth glyphs.
264    fn range_expand_fullwidth<T>(term: &Term<T>, mut range: SelectionRange) -> SelectionRange {
265        let grid = term.grid();
266        let num_cols = grid.num_cols();
267
268        // Helper for checking if cell at `point` contains `flag`
269        let flag_at = |point: Point<usize>, flag: Flags| -> bool {
270            grid[point.line][point.col].flags.contains(flag)
271        };
272
273        // Include all double-width cells and placeholders at top left of selection
274        if range.start.col < num_cols {
275            // Expand from wide char spacer to wide char
276            if range.start.line + 1 != grid.len() || range.start.col.0 != 0 {
277                let prev = range.start.sub(num_cols.0, 1, true);
278                if flag_at(range.start, Flags::WIDE_CHAR_SPACER) && flag_at(prev, Flags::WIDE_CHAR)
279                {
280                    range.start = prev;
281                }
282            }
283
284            // Expand from wide char to wide char spacer for linewrapping
285            if range.start.line + 1 != grid.len() || range.start.col.0 != 0 {
286                let prev = range.start.sub(num_cols.0, 1, true);
287                if (prev.line + 1 != grid.len() || prev.col.0 != 0)
288                    && flag_at(prev, Flags::WIDE_CHAR_SPACER)
289                    && !flag_at(prev.sub(num_cols.0, 1, true), Flags::WIDE_CHAR)
290                {
291                    range.start = prev;
292                }
293            }
294        }
295
296        // Include all double-width cells and placeholders at bottom right of selection
297        if range.end.line != 0 || range.end.col < num_cols {
298            // Expand from wide char spacer for linewrapping to wide char
299            if (range.end.line + 1 != grid.len() || range.end.col.0 != 0)
300                && flag_at(range.end, Flags::WIDE_CHAR_SPACER)
301                && !flag_at(range.end.sub(num_cols.0, 1, true), Flags::WIDE_CHAR)
302            {
303                range.end = range.end.add(num_cols.0, 1, true);
304            }
305
306            // Expand from wide char to wide char spacer
307            if flag_at(range.end, Flags::WIDE_CHAR) {
308                range.end = range.end.add(num_cols.0, 1, true);
309            }
310        }
311
312        range
313    }
314
315    // Bring start and end points in the correct order
316    fn points_need_swap(start: Point<usize>, end: Point<usize>) -> bool {
317        start.line < end.line || start.line == end.line && start.col > end.col
318    }
319
320    /// Clamp selection inside grid to prevent OOB.
321    fn grid_clamp(
322        mut start: Anchor,
323        end: Anchor,
324        is_block: bool,
325        lines: usize,
326    ) -> Result<(Anchor, Anchor), ()> {
327        // Clamp selection inside of grid to prevent OOB
328        if start.point.line >= lines {
329            // Remove selection if it is fully out of the grid
330            if end.point.line >= lines {
331                return Err(());
332            }
333
334            // Clamp to grid if it is still partially visible
335            if !is_block {
336                start.side = Side::Left;
337                start.point.col = Column(0);
338            }
339            start.point.line = lines - 1;
340        }
341
342        Ok((start, end))
343    }
344
345    fn range_semantic<T>(
346        term: &Term<T>,
347        mut start: Point<usize>,
348        mut end: Point<usize>,
349    ) -> Option<SelectionRange> {
350        if start == end {
351            if let Some(matching) = term.bracket_search(start) {
352                if (matching.line == start.line && matching.col < start.col)
353                    || (matching.line > start.line)
354                {
355                    start = matching;
356                } else {
357                    end = matching;
358                }
359
360                return Some(SelectionRange { start, end, is_block: false });
361            }
362        }
363
364        start = term.semantic_search_left(start);
365        end = term.semantic_search_right(end);
366
367        Some(SelectionRange { start, end, is_block: false })
368    }
369
370    fn range_lines<T>(
371        term: &Term<T>,
372        mut start: Point<usize>,
373        mut end: Point<usize>,
374    ) -> Option<SelectionRange> {
375        start = term.line_search_left(start);
376        end = term.line_search_right(end);
377
378        Some(SelectionRange { start, end, is_block: false })
379    }
380
381    fn range_simple(
382        &self,
383        mut start: Anchor,
384        mut end: Anchor,
385        num_cols: Column,
386    ) -> Option<SelectionRange> {
387        if self.is_empty() {
388            return None;
389        }
390
391        // Remove last cell if selection ends to the left of a cell
392        if end.side == Side::Left && start.point != end.point {
393            // Special case when selection ends to left of first cell
394            if end.point.col == Column(0) {
395                end.point.col = num_cols - 1;
396                end.point.line += 1;
397            } else {
398                end.point.col -= 1;
399            }
400        }
401
402        // Remove first cell if selection starts at the right of a cell
403        if start.side == Side::Right && start.point != end.point {
404            start.point.col += 1;
405        }
406
407        Some(SelectionRange { start: start.point, end: end.point, is_block: false })
408    }
409
410    fn range_block(&self, mut start: Anchor, mut end: Anchor) -> Option<SelectionRange> {
411        if self.is_empty() {
412            return None;
413        }
414
415        // Always go top-left -> bottom-right
416        if start.point.col > end.point.col {
417            mem::swap(&mut start.side, &mut end.side);
418            mem::swap(&mut start.point.col, &mut end.point.col);
419        }
420
421        // Remove last cell if selection ends to the left of a cell
422        if end.side == Side::Left && start.point != end.point && end.point.col.0 > 0 {
423            end.point.col -= 1;
424        }
425
426        // Remove first cell if selection starts at the right of a cell
427        if start.side == Side::Right && start.point != end.point {
428            start.point.col += 1;
429        }
430
431        Some(SelectionRange { start: start.point, end: end.point, is_block: true })
432    }
433}
434
435/// Tests for selection.
436///
437/// There are comments on all of the tests describing the selection. Pictograms
438/// are used to avoid ambiguity. Grid cells are represented by a [  ]. Only
439/// cells that are completely covered are counted in a selection. Ends are
440/// represented by `B` and `E` for begin and end, respectively.  A selected cell
441/// looks like [XX], [BX] (at the start), [XB] (at the end), [XE] (at the end),
442/// and [EX] (at the start), or [BE] for a single cell. Partially selected cells
443/// look like [ B] and [E ].
444#[cfg(test)]
445mod test {
446    use std::mem;
447
448    use super::{Selection, SelectionRange};
449    use crate::clipboard::Clipboard;
450    use crate::config::MockConfig;
451    use crate::event::{Event, EventListener};
452    use crate::grid::Grid;
453    use crate::index::{Column, Line, Point, Side};
454    use crate::term::cell::{Cell, Flags};
455    use crate::term::{SizeInfo, Term};
456
457    struct Mock;
458    impl EventListener for Mock {
459        fn send_event(&self, _event: Event) {}
460    }
461
462    fn term(width: usize, height: usize) -> Term<Mock> {
463        let size = SizeInfo {
464            width: width as f32,
465            height: height as f32,
466            cell_width: 1.0,
467            cell_height: 1.0,
468            padding_x: 0.0,
469            padding_y: 0.0,
470            dpr: 1.0,
471        };
472        Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock)
473    }
474
475    /// Test case of single cell selection.
476    ///
477    /// 1. [  ]
478    /// 2. [B ]
479    /// 3. [BE]
480    #[test]
481    fn single_cell_left_to_right() {
482        let location = Point { line: 0, col: Column(0) };
483        let mut selection = Selection::simple(location, Side::Left);
484        selection.update(location, Side::Right);
485
486        assert_eq!(
487            selection.to_range(&term(1, 1)).unwrap(),
488            SelectionRange { start: location, end: location, is_block: false }
489        );
490    }
491
492    /// Test case of single cell selection.
493    ///
494    /// 1. [  ]
495    /// 2. [ B]
496    /// 3. [EB]
497    #[test]
498    fn single_cell_right_to_left() {
499        let location = Point { line: 0, col: Column(0) };
500        let mut selection = Selection::simple(location, Side::Right);
501        selection.update(location, Side::Left);
502
503        assert_eq!(
504            selection.to_range(&term(1, 1)).unwrap(),
505            SelectionRange { start: location, end: location, is_block: false }
506        );
507    }
508
509    /// Test adjacent cell selection from left to right.
510    ///
511    /// 1. [  ][  ]
512    /// 2. [ B][  ]
513    /// 3. [ B][E ]
514    #[test]
515    fn between_adjacent_cells_left_to_right() {
516        let mut selection = Selection::simple(Point::new(0, Column(0)), Side::Right);
517        selection.update(Point::new(0, Column(1)), Side::Left);
518
519        assert_eq!(selection.to_range(&term(2, 1)), None);
520    }
521
522    /// Test adjacent cell selection from right to left.
523    ///
524    /// 1. [  ][  ]
525    /// 2. [  ][B ]
526    /// 3. [ E][B ]
527    #[test]
528    fn between_adjacent_cells_right_to_left() {
529        let mut selection = Selection::simple(Point::new(0, Column(1)), Side::Left);
530        selection.update(Point::new(0, Column(0)), Side::Right);
531
532        assert_eq!(selection.to_range(&term(2, 1)), None);
533    }
534
535    /// Test selection across adjacent lines.
536    ///
537    /// 1.  [  ][  ][  ][  ][  ]
538    ///     [  ][  ][  ][  ][  ]
539    /// 2.  [  ][ B][  ][  ][  ]
540    ///     [  ][  ][  ][  ][  ]
541    /// 3.  [  ][ B][XX][XX][XX]
542    ///     [XX][XE][  ][  ][  ]
543    #[test]
544    fn across_adjacent_lines_upward_final_cell_exclusive() {
545        let mut selection = Selection::simple(Point::new(1, Column(1)), Side::Right);
546        selection.update(Point::new(0, Column(1)), Side::Right);
547
548        assert_eq!(
549            selection.to_range(&term(5, 2)).unwrap(),
550            SelectionRange {
551                start: Point::new(1, Column(2)),
552                end: Point::new(0, Column(1)),
553                is_block: false,
554            }
555        );
556    }
557
558    /// Test selection across adjacent lines.
559    ///
560    /// 1.  [  ][  ][  ][  ][  ]
561    ///     [  ][  ][  ][  ][  ]
562    /// 2.  [  ][  ][  ][  ][  ]
563    ///     [  ][ B][  ][  ][  ]
564    /// 3.  [  ][ E][XX][XX][XX]
565    ///     [XX][XB][  ][  ][  ]
566    /// 4.  [ E][XX][XX][XX][XX]
567    ///     [XX][XB][  ][  ][  ]
568    #[test]
569    fn selection_bigger_then_smaller() {
570        let mut selection = Selection::simple(Point::new(0, Column(1)), Side::Right);
571        selection.update(Point::new(1, Column(1)), Side::Right);
572        selection.update(Point::new(1, Column(0)), Side::Right);
573
574        assert_eq!(
575            selection.to_range(&term(5, 2)).unwrap(),
576            SelectionRange {
577                start: Point::new(1, Column(1)),
578                end: Point::new(0, Column(1)),
579                is_block: false,
580            }
581        );
582    }
583
584    #[test]
585    fn line_selection() {
586        let num_lines = 10;
587        let num_cols = 5;
588        let mut selection = Selection::lines(Point::new(0, Column(1)));
589        selection.update(Point::new(5, Column(1)), Side::Right);
590        selection = selection.rotate(num_lines, num_cols, &(Line(0)..Line(num_lines)), 7).unwrap();
591
592        assert_eq!(
593            selection.to_range(&term(num_cols, num_lines)).unwrap(),
594            SelectionRange {
595                start: Point::new(9, Column(0)),
596                end: Point::new(7, Column(4)),
597                is_block: false,
598            }
599        );
600    }
601
602    #[test]
603    fn semantic_selection() {
604        let num_lines = 10;
605        let num_cols = 5;
606        let mut selection = Selection::semantic(Point::new(0, Column(3)));
607        selection.update(Point::new(5, Column(1)), Side::Right);
608        selection = selection.rotate(num_lines, num_cols, &(Line(0)..Line(num_lines)), 7).unwrap();
609
610        assert_eq!(
611            selection.to_range(&term(num_cols, num_lines)).unwrap(),
612            SelectionRange {
613                start: Point::new(9, Column(0)),
614                end: Point::new(7, Column(3)),
615                is_block: false,
616            }
617        );
618    }
619
620    #[test]
621    fn simple_selection() {
622        let num_lines = 10;
623        let num_cols = 5;
624        let mut selection = Selection::simple(Point::new(0, Column(3)), Side::Right);
625        selection.update(Point::new(5, Column(1)), Side::Right);
626        selection = selection.rotate(num_lines, num_cols, &(Line(0)..Line(num_lines)), 7).unwrap();
627
628        assert_eq!(
629            selection.to_range(&term(num_cols, num_lines)).unwrap(),
630            SelectionRange {
631                start: Point::new(9, Column(0)),
632                end: Point::new(7, Column(3)),
633                is_block: false,
634            }
635        );
636    }
637
638    #[test]
639    fn block_selection() {
640        let num_lines = 10;
641        let num_cols = 5;
642        let mut selection = Selection::block(Point::new(0, Column(3)), Side::Right);
643        selection.update(Point::new(5, Column(1)), Side::Right);
644        selection = selection.rotate(num_lines, num_cols, &(Line(0)..Line(num_lines)), 7).unwrap();
645
646        assert_eq!(
647            selection.to_range(&term(num_cols, num_lines)).unwrap(),
648            SelectionRange {
649                start: Point::new(9, Column(2)),
650                end: Point::new(7, Column(3)),
651                is_block: true
652            }
653        );
654    }
655
656    #[test]
657    fn double_width_expansion() {
658        let mut term = term(10, 1);
659        let mut grid = Grid::new(Line(1), Column(10), 0, Cell::default());
660        grid[Line(0)][Column(0)].flags.insert(Flags::WIDE_CHAR);
661        grid[Line(0)][Column(1)].flags.insert(Flags::WIDE_CHAR_SPACER);
662        grid[Line(0)][Column(8)].flags.insert(Flags::WIDE_CHAR);
663        grid[Line(0)][Column(9)].flags.insert(Flags::WIDE_CHAR_SPACER);
664        mem::swap(term.grid_mut(), &mut grid);
665
666        let mut selection = Selection::simple(Point::new(0, Column(1)), Side::Left);
667        selection.update(Point::new(0, Column(8)), Side::Right);
668
669        assert_eq!(
670            selection.to_range(&term).unwrap(),
671            SelectionRange {
672                start: Point::new(0, Column(0)),
673                end: Point::new(0, Column(9)),
674                is_block: false,
675            }
676        );
677    }
678
679    #[test]
680    fn simple_is_empty() {
681        let mut selection = Selection::simple(Point::new(0, Column(0)), Side::Right);
682        assert!(selection.is_empty());
683        selection.update(Point::new(0, Column(1)), Side::Left);
684        assert!(selection.is_empty());
685        selection.update(Point::new(1, Column(0)), Side::Right);
686        assert!(!selection.is_empty());
687    }
688
689    #[test]
690    fn block_is_empty() {
691        let mut selection = Selection::block(Point::new(0, Column(0)), Side::Right);
692        assert!(selection.is_empty());
693        selection.update(Point::new(0, Column(1)), Side::Left);
694        assert!(selection.is_empty());
695        selection.update(Point::new(0, Column(1)), Side::Right);
696        assert!(!selection.is_empty());
697        selection.update(Point::new(1, Column(0)), Side::Right);
698        assert!(selection.is_empty());
699        selection.update(Point::new(1, Column(1)), Side::Left);
700        assert!(selection.is_empty());
701        selection.update(Point::new(1, Column(1)), Side::Right);
702        assert!(!selection.is_empty());
703    }
704
705    #[test]
706    fn rotate_in_region_up() {
707        let num_lines = 10;
708        let num_cols = 5;
709        let mut selection = Selection::simple(Point::new(2, Column(3)), Side::Right);
710        selection.update(Point::new(5, Column(1)), Side::Right);
711        selection =
712            selection.rotate(num_lines, num_cols, &(Line(1)..Line(num_lines - 1)), 4).unwrap();
713
714        assert_eq!(
715            selection.to_range(&term(num_cols, num_lines)).unwrap(),
716            SelectionRange {
717                start: Point::new(8, Column(0)),
718                end: Point::new(6, Column(3)),
719                is_block: false,
720            }
721        );
722    }
723
724    #[test]
725    fn rotate_in_region_down() {
726        let num_lines = 10;
727        let num_cols = 5;
728        let mut selection = Selection::simple(Point::new(5, Column(3)), Side::Right);
729        selection.update(Point::new(8, Column(1)), Side::Left);
730        selection =
731            selection.rotate(num_lines, num_cols, &(Line(1)..Line(num_lines - 1)), -5).unwrap();
732
733        assert_eq!(
734            selection.to_range(&term(num_cols, num_lines)).unwrap(),
735            SelectionRange {
736                start: Point::new(3, Column(1)),
737                end: Point::new(1, Column(num_cols - 1)),
738                is_block: false,
739            }
740        );
741    }
742
743    #[test]
744    fn rotate_in_region_up_block() {
745        let num_lines = 10;
746        let num_cols = 5;
747        let mut selection = Selection::block(Point::new(2, Column(3)), Side::Right);
748        selection.update(Point::new(5, Column(1)), Side::Right);
749        selection =
750            selection.rotate(num_lines, num_cols, &(Line(1)..Line(num_lines - 1)), 4).unwrap();
751
752        assert_eq!(
753            selection.to_range(&term(num_cols, num_lines)).unwrap(),
754            SelectionRange {
755                start: Point::new(8, Column(2)),
756                end: Point::new(6, Column(3)),
757                is_block: true,
758            }
759        );
760    }
761}