mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-18 13:55:23 -05:00
parent
7925ac4918
commit
bdd28f4766
3 changed files with 219 additions and 136 deletions
|
@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Backwards bracket selection
|
||||
- Stack overflow when printing shader creation error
|
||||
- Underline position for bitmap fonts
|
||||
- Selection rotating outside of scrolling region
|
||||
|
||||
### Removed
|
||||
|
||||
|
|
|
@ -492,6 +492,9 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
|
|||
|
||||
#[inline]
|
||||
pub fn scroll_down(&mut self, region: &Range<Line>, positions: Line, template: &T) {
|
||||
let num_lines = self.num_lines().0;
|
||||
let num_cols = self.num_cols().0;
|
||||
|
||||
// Whether or not there is a scrolling region active, as long as it
|
||||
// starts at the top, we can do a full rotation which just involves
|
||||
// changing the start index.
|
||||
|
@ -501,9 +504,10 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
|
|||
// Rotate the entire line buffer. If there's a scrolling region
|
||||
// active, the bottom lines are restored in the next step.
|
||||
self.raw.rotate_up(*positions);
|
||||
if let Some(ref mut selection) = self.selection {
|
||||
selection.rotate(-(*positions as isize));
|
||||
}
|
||||
self.selection = self
|
||||
.selection
|
||||
.take()
|
||||
.and_then(|s| s.rotate(num_lines, num_cols, region, -(*positions as isize)));
|
||||
|
||||
self.decrease_scroll_limit(*positions);
|
||||
|
||||
|
@ -518,6 +522,12 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
|
|||
self.raw[i].reset(&template);
|
||||
}
|
||||
} else {
|
||||
// Rotate selection to track content
|
||||
self.selection = self
|
||||
.selection
|
||||
.take()
|
||||
.and_then(|s| s.rotate(num_lines, num_cols, region, -(*positions as isize)));
|
||||
|
||||
// Subregion rotation
|
||||
for line in IndexRange((region.start + positions)..region.end).rev() {
|
||||
self.raw.swap_lines(line, line - positions);
|
||||
|
@ -533,11 +543,13 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
|
|||
///
|
||||
/// This is the performance-sensitive part of scrolling.
|
||||
pub fn scroll_up(&mut self, region: &Range<Line>, positions: Line, template: &T) {
|
||||
let num_lines = self.num_lines().0;
|
||||
let num_cols = self.num_cols().0;
|
||||
|
||||
if region.start == Line(0) {
|
||||
// Update display offset when not pinned to active area
|
||||
if self.display_offset != 0 {
|
||||
self.display_offset =
|
||||
min(self.display_offset + *positions, self.len() - self.num_lines().0);
|
||||
self.display_offset = min(self.display_offset + *positions, self.len() - num_lines);
|
||||
}
|
||||
|
||||
self.increase_scroll_limit(*positions, template);
|
||||
|
@ -545,15 +557,16 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
|
|||
// Rotate the entire line buffer. If there's a scrolling region
|
||||
// active, the bottom lines are restored in the next step.
|
||||
self.raw.rotate(-(*positions as isize));
|
||||
if let Some(ref mut selection) = self.selection {
|
||||
selection.rotate(*positions as isize);
|
||||
}
|
||||
self.selection = self
|
||||
.selection
|
||||
.take()
|
||||
.and_then(|s| s.rotate(num_lines, num_cols, region, *positions as isize));
|
||||
|
||||
// This next loop swaps "fixed" lines outside of a scroll region
|
||||
// back into place after the rotation. The work is done in buffer-
|
||||
// space rather than terminal-space to avoid redundant
|
||||
// transformations.
|
||||
let fixed_lines = *self.num_lines() - *region.end;
|
||||
let fixed_lines = num_lines - *region.end;
|
||||
|
||||
for i in 0..fixed_lines {
|
||||
self.raw.swap(i, i + *positions);
|
||||
|
@ -566,6 +579,12 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
|
|||
self.raw[i + fixed_lines].reset(&template);
|
||||
}
|
||||
} else {
|
||||
// Rotate selection to track content
|
||||
self.selection = self
|
||||
.selection
|
||||
.take()
|
||||
.and_then(|s| s.rotate(num_lines, num_cols, region, *positions as isize));
|
||||
|
||||
// Subregion rotation
|
||||
for line in IndexRange(region.start..(region.end - positions)) {
|
||||
self.raw.swap_lines(line, line + positions);
|
||||
|
|
|
@ -22,12 +22,12 @@ use std::convert::TryFrom;
|
|||
use std::mem;
|
||||
use std::ops::Range;
|
||||
|
||||
use crate::index::{Column, Point, Side};
|
||||
use crate::index::{Column, Line, Point, Side};
|
||||
use crate::term::cell::Flags;
|
||||
use crate::term::{Search, Term};
|
||||
|
||||
/// A Point and side within that point.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct Anchor {
|
||||
point: Point<usize>,
|
||||
side: Side,
|
||||
|
@ -66,87 +66,147 @@ impl<L> SelectionRange<L> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Different kinds of selection.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
enum SelectionType {
|
||||
Simple,
|
||||
Block,
|
||||
Semantic,
|
||||
Lines,
|
||||
}
|
||||
|
||||
/// Describes a region of a 2-dimensional area.
|
||||
///
|
||||
/// 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.
|
||||
/// Used to track a text selection. There are four supported modes, each with its own constructor:
|
||||
/// [`simple`], [`block`], [`semantic`], and [`lines`]. The [`simple`] mode precisely tracks which
|
||||
/// cells are selected without any expansion. [`block`] will select rectangular regions.
|
||||
/// [`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.
|
||||
/// Calls to [`update`] operate different based on the selection kind. The [`simple`] and [`block`]
|
||||
/// mode do nothing special, simply track 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
|
||||
/// [`block`]: enum.Selection.html#method.block
|
||||
/// [`semantic`]: enum.Selection.html#method.semantic
|
||||
/// [`lines`]: enum.Selection.html#method.lines
|
||||
/// [`update`]: enum.Selection.html#method.update
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Selection {
|
||||
Simple {
|
||||
/// The region representing start and end of cursor movement.
|
||||
region: Range<Anchor>,
|
||||
},
|
||||
Block {
|
||||
/// The region representing start and end of cursor movement.
|
||||
region: Range<Anchor>,
|
||||
},
|
||||
Semantic {
|
||||
/// The region representing start and end of cursor movement.
|
||||
region: Range<Point<usize>>,
|
||||
},
|
||||
Lines {
|
||||
/// The region representing start and end of cursor movement.
|
||||
region: Range<Point<usize>>,
|
||||
},
|
||||
pub struct Selection {
|
||||
region: Range<Anchor>,
|
||||
ty: SelectionType,
|
||||
}
|
||||
|
||||
impl Selection {
|
||||
pub fn simple(location: Point<usize>, side: Side) -> Selection {
|
||||
Selection::Simple {
|
||||
Self {
|
||||
region: Range { start: Anchor::new(location, side), end: Anchor::new(location, side) },
|
||||
ty: SelectionType::Simple,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn block(location: Point<usize>, side: Side) -> Selection {
|
||||
Selection::Block {
|
||||
Self {
|
||||
region: Range { start: Anchor::new(location, side), end: Anchor::new(location, side) },
|
||||
ty: SelectionType::Block,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn semantic(point: Point<usize>) -> Selection {
|
||||
Selection::Semantic { region: Range { start: point, end: point } }
|
||||
pub fn semantic(location: Point<usize>) -> Selection {
|
||||
Self {
|
||||
region: Range {
|
||||
start: Anchor::new(location, Side::Left),
|
||||
end: Anchor::new(location, Side::Right),
|
||||
},
|
||||
ty: SelectionType::Semantic,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lines(point: Point<usize>) -> Selection {
|
||||
Selection::Lines { region: Range { start: point, end: point } }
|
||||
pub fn lines(location: Point<usize>) -> Selection {
|
||||
Self {
|
||||
region: Range {
|
||||
start: Anchor::new(location, Side::Left),
|
||||
end: Anchor::new(location, Side::Right),
|
||||
},
|
||||
ty: SelectionType::Lines,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, location: Point<usize>, side: Side) {
|
||||
let (_, end) = self.points_mut();
|
||||
*end = location;
|
||||
|
||||
if let Some((_, end_side)) = self.sides_mut() {
|
||||
*end_side = side;
|
||||
}
|
||||
self.region.end.point = location;
|
||||
self.region.end.side = side;
|
||||
}
|
||||
|
||||
pub fn rotate(&mut self, offset: isize) {
|
||||
let (start, end) = self.points_mut();
|
||||
start.line = usize::try_from(start.line as isize + offset).unwrap_or(0);
|
||||
end.line = usize::try_from(end.line as isize + offset).unwrap_or(0);
|
||||
pub fn rotate(
|
||||
mut self,
|
||||
num_lines: usize,
|
||||
num_cols: usize,
|
||||
scrolling_region: &Range<Line>,
|
||||
offset: isize,
|
||||
) -> Option<Selection> {
|
||||
// Convert scrolling region from viewport to buffer coordinates
|
||||
let region_start = num_lines - scrolling_region.start.0;
|
||||
let region_end = num_lines - scrolling_region.end.0;
|
||||
|
||||
let (mut start, mut end) = (&mut self.region.start, &mut self.region.end);
|
||||
if Self::points_need_swap(start.point, end.point) {
|
||||
mem::swap(&mut start, &mut end);
|
||||
}
|
||||
|
||||
// Rotate start of selection
|
||||
if (start.point.line < region_start || region_start == num_lines)
|
||||
&& start.point.line >= region_end
|
||||
{
|
||||
start.point.line = usize::try_from(start.point.line as isize + offset).unwrap_or(0);
|
||||
|
||||
// If end is within the same region, delete selection once start rotates out
|
||||
if start.point.line < region_end && end.point.line >= region_end {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Clamp selection to start of region
|
||||
if start.point.line >= region_start && region_start != num_lines {
|
||||
if self.ty != SelectionType::Block {
|
||||
start.point.col = Column(0);
|
||||
start.side = Side::Left;
|
||||
}
|
||||
start.point.line = region_start - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Rotate end of selection
|
||||
if (end.point.line < region_start || region_start == num_lines)
|
||||
&& end.point.line >= region_end
|
||||
{
|
||||
end.point.line = usize::try_from(end.point.line as isize + offset).unwrap_or(0);
|
||||
|
||||
// Delete selection if end has overtaken the start
|
||||
if end.point.line > start.point.line {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Clamp selection to end of region
|
||||
if end.point.line < region_end {
|
||||
if self.ty != SelectionType::Block {
|
||||
end.point.col = Column(num_cols - 1);
|
||||
end.side = Side::Right;
|
||||
}
|
||||
end.point.line = region_end;
|
||||
}
|
||||
}
|
||||
|
||||
Some(self)
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
Selection::Simple { ref region } => {
|
||||
let (start, end) =
|
||||
if Selection::points_need_swap(region.start.point, region.end.point) {
|
||||
(®ion.end, ®ion.start)
|
||||
} else {
|
||||
(®ion.start, ®ion.end)
|
||||
};
|
||||
match self.ty {
|
||||
SelectionType::Simple => {
|
||||
let (mut start, mut end) = (self.region.start, self.region.end);
|
||||
if Selection::points_need_swap(start.point, end.point) {
|
||||
mem::swap(&mut start, &mut end);
|
||||
}
|
||||
|
||||
// Simple selection is empty when the points are identical
|
||||
// or two adjacent cells have the sides right -> left
|
||||
|
@ -156,7 +216,9 @@ impl Selection {
|
|||
&& (start.point.line == end.point.line)
|
||||
&& start.point.col + 1 == end.point.col)
|
||||
},
|
||||
Selection::Block { region: Range { ref start, ref end } } => {
|
||||
SelectionType::Block => {
|
||||
let (start, end) = (self.region.start, self.region.end);
|
||||
|
||||
// Block selection is empty when the points' columns and sides are identical
|
||||
// or two cells with adjacent columns have the sides right -> left,
|
||||
// regardless of their lines
|
||||
|
@ -168,7 +230,7 @@ impl Selection {
|
|||
&& start.side == Side::Left
|
||||
&& end.side == Side::Right)
|
||||
},
|
||||
Selection::Semantic { .. } | Selection::Lines { .. } => false,
|
||||
SelectionType::Semantic | SelectionType::Lines => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -177,29 +239,21 @@ impl Selection {
|
|||
let grid = term.grid();
|
||||
let num_cols = grid.num_cols();
|
||||
|
||||
// Get selection boundaries
|
||||
let points = self.points();
|
||||
let (start, end) = (*points.0, *points.1);
|
||||
|
||||
// Get selection sides, falling back to `Side::Left` if it will not be used
|
||||
let sides = self.sides().unwrap_or((&Side::Left, &Side::Left));
|
||||
let (start_side, end_side) = (*sides.0, *sides.1);
|
||||
|
||||
// Order start above the end
|
||||
let (start, end) = if Self::points_need_swap(start, end) {
|
||||
(Anchor { point: end, side: end_side }, Anchor { point: start, side: start_side })
|
||||
} else {
|
||||
(Anchor { point: start, side: start_side }, Anchor { point: end, side: end_side })
|
||||
};
|
||||
let (mut start, mut end) = (self.region.start, self.region.end);
|
||||
if Self::points_need_swap(start.point, end.point) {
|
||||
mem::swap(&mut start, &mut end);
|
||||
}
|
||||
|
||||
// Clamp to inside the grid buffer
|
||||
let (start, end) = Self::grid_clamp(start, end, self.is_block(), grid.len()).ok()?;
|
||||
let is_block = self.ty == SelectionType::Block;
|
||||
let (start, end) = Self::grid_clamp(start, end, is_block, grid.len()).ok()?;
|
||||
|
||||
let range = match self {
|
||||
Self::Simple { .. } => self.range_simple(start, end, num_cols),
|
||||
Self::Block { .. } => self.range_block(start, end),
|
||||
Self::Semantic { .. } => Self::range_semantic(term, start.point, end.point),
|
||||
Self::Lines { .. } => Self::range_lines(term, start.point, end.point),
|
||||
let range = match self.ty {
|
||||
SelectionType::Simple => self.range_simple(start, end, num_cols),
|
||||
SelectionType::Block => self.range_block(start, end),
|
||||
SelectionType::Semantic => Self::range_semantic(term, start.point, end.point),
|
||||
SelectionType::Lines => Self::range_lines(term, start.point, end.point),
|
||||
};
|
||||
|
||||
// Expand selection across fullwidth cells
|
||||
|
@ -376,53 +430,6 @@ impl Selection {
|
|||
|
||||
Some(SelectionRange { start: start.point, end: end.point, is_block: true })
|
||||
}
|
||||
|
||||
fn points(&self) -> (&Point<usize>, &Point<usize>) {
|
||||
match self {
|
||||
Self::Simple { ref region } | Self::Block { ref region } => {
|
||||
(®ion.start.point, ®ion.end.point)
|
||||
},
|
||||
Self::Semantic { ref region } | Self::Lines { ref region } => {
|
||||
(®ion.start, ®ion.end)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn points_mut(&mut self) -> (&mut Point<usize>, &mut Point<usize>) {
|
||||
match self {
|
||||
Self::Simple { ref mut region } | Self::Block { ref mut region } => {
|
||||
(&mut region.start.point, &mut region.end.point)
|
||||
},
|
||||
Self::Semantic { ref mut region } | Self::Lines { ref mut region } => {
|
||||
(&mut region.start, &mut region.end)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn sides(&self) -> Option<(&Side, &Side)> {
|
||||
match self {
|
||||
Self::Simple { ref region } | Self::Block { ref region } => {
|
||||
Some((®ion.start.side, ®ion.end.side))
|
||||
},
|
||||
Self::Semantic { .. } | Self::Lines { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn sides_mut(&mut self) -> Option<(&mut Side, &mut Side)> {
|
||||
match self {
|
||||
Self::Simple { ref mut region } | Self::Block { ref mut region } => {
|
||||
Some((&mut region.start.side, &mut region.end.side))
|
||||
},
|
||||
Self::Semantic { .. } | Self::Lines { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_block(&self) -> bool {
|
||||
match self {
|
||||
Self::Block { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests for selection.
|
||||
|
@ -572,11 +579,13 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn line_selection() {
|
||||
let num_lines = 10;
|
||||
let num_cols = 5;
|
||||
let mut selection = Selection::lines(Point::new(0, Column(1)));
|
||||
selection.update(Point::new(5, Column(1)), Side::Right);
|
||||
selection.rotate(7);
|
||||
selection = selection.rotate(num_lines, num_cols, &(Line(0)..Line(num_lines)), 7).unwrap();
|
||||
|
||||
assert_eq!(selection.to_range(&term(5, 10)).unwrap(), SelectionRange {
|
||||
assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange {
|
||||
start: Point::new(9, Column(0)),
|
||||
end: Point::new(7, Column(4)),
|
||||
is_block: false,
|
||||
|
@ -585,11 +594,13 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn semantic_selection() {
|
||||
let num_lines = 10;
|
||||
let num_cols = 5;
|
||||
let mut selection = Selection::semantic(Point::new(0, Column(3)));
|
||||
selection.update(Point::new(5, Column(1)), Side::Right);
|
||||
selection.rotate(7);
|
||||
selection = selection.rotate(num_lines, num_cols, &(Line(0)..Line(num_lines)), 7).unwrap();
|
||||
|
||||
assert_eq!(selection.to_range(&term(5, 10)).unwrap(), SelectionRange {
|
||||
assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange {
|
||||
start: Point::new(9, Column(0)),
|
||||
end: Point::new(7, Column(3)),
|
||||
is_block: false,
|
||||
|
@ -598,11 +609,13 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn simple_selection() {
|
||||
let num_lines = 10;
|
||||
let num_cols = 5;
|
||||
let mut selection = Selection::simple(Point::new(0, Column(3)), Side::Right);
|
||||
selection.update(Point::new(5, Column(1)), Side::Right);
|
||||
selection.rotate(7);
|
||||
selection = selection.rotate(num_lines, num_cols, &(Line(0)..Line(num_lines)), 7).unwrap();
|
||||
|
||||
assert_eq!(selection.to_range(&term(5, 10)).unwrap(), SelectionRange {
|
||||
assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange {
|
||||
start: Point::new(9, Column(0)),
|
||||
end: Point::new(7, Column(3)),
|
||||
is_block: false,
|
||||
|
@ -611,11 +624,13 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn block_selection() {
|
||||
let num_lines = 10;
|
||||
let num_cols = 5;
|
||||
let mut selection = Selection::block(Point::new(0, Column(3)), Side::Right);
|
||||
selection.update(Point::new(5, Column(1)), Side::Right);
|
||||
selection.rotate(7);
|
||||
selection = selection.rotate(num_lines, num_cols, &(Line(0)..Line(num_lines)), 7).unwrap();
|
||||
|
||||
assert_eq!(selection.to_range(&term(5, 10)).unwrap(), SelectionRange {
|
||||
assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange {
|
||||
start: Point::new(9, Column(2)),
|
||||
end: Point::new(7, Column(3)),
|
||||
is_block: true
|
||||
|
@ -667,4 +682,52 @@ mod test {
|
|||
selection.update(Point::new(1, Column(1)), Side::Right);
|
||||
assert!(!selection.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rotate_in_region_up() {
|
||||
let num_lines = 10;
|
||||
let num_cols = 5;
|
||||
let mut selection = Selection::simple(Point::new(2, Column(3)), Side::Right);
|
||||
selection.update(Point::new(5, Column(1)), Side::Right);
|
||||
selection =
|
||||
selection.rotate(num_lines, num_cols, &(Line(1)..Line(num_lines - 1)), 4).unwrap();
|
||||
|
||||
assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange {
|
||||
start: Point::new(8, Column(0)),
|
||||
end: Point::new(6, Column(3)),
|
||||
is_block: false,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rotate_in_region_down() {
|
||||
let num_lines = 10;
|
||||
let num_cols = 5;
|
||||
let mut selection = Selection::simple(Point::new(5, Column(3)), Side::Right);
|
||||
selection.update(Point::new(8, Column(1)), Side::Left);
|
||||
selection =
|
||||
selection.rotate(num_lines, num_cols, &(Line(1)..Line(num_lines - 1)), -5).unwrap();
|
||||
|
||||
assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange {
|
||||
start: Point::new(3, Column(1)),
|
||||
end: Point::new(1, Column(num_cols - 1)),
|
||||
is_block: false,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rotate_in_region_up_block() {
|
||||
let num_lines = 10;
|
||||
let num_cols = 5;
|
||||
let mut selection = Selection::block(Point::new(2, Column(3)), Side::Right);
|
||||
selection.update(Point::new(5, Column(1)), Side::Right);
|
||||
selection =
|
||||
selection.rotate(num_lines, num_cols, &(Line(1)..Line(num_lines - 1)), 4).unwrap();
|
||||
|
||||
assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange {
|
||||
start: Point::new(8, Column(2)),
|
||||
end: Point::new(6, Column(3)),
|
||||
is_block: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue