diff options
author | Christian Duerr <contact@christianduerr.com> | 2021-03-30 23:25:38 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-03-30 23:25:38 +0000 |
commit | 3bd5ac221ab3b122962063edd1f4c10f9f2d117f (patch) | |
tree | b0a367b91611e911344aec9ff35354e5a473b6aa /alacritty_terminal/src | |
parent | 974392cdc6fdf1ba0d213145ae578a9316e9d404 (diff) | |
download | alacritty-3bd5ac221ab3b122962063edd1f4c10f9f2d117f.tar.gz alacritty-3bd5ac221ab3b122962063edd1f4c10f9f2d117f.zip |
Unify the grid line indexing types
Previously Alacritty was using two different ways to reference lines in
the terminal. Either a `usize`, or a `Line(usize)`. These indexing
systems both served different purposes, but made it difficult to reason
about logic involving these systems because of its inconsistency.
To resolve this issue, a single new `Line(i32)` type has been
introduced. All existing references to lines and points now rely on
this definition of a line.
The indexing starts at the top of the terminal region with the line 0,
which matches the line 1 used by escape sequences. Each line in the
history becomes increasingly negative and the bottommost line is equal
to the number of visible lines minus one.
Having a system which goes into the negatives allows following the
escape sequence's indexing system closely, while at the same time making
it trivial to implement `Ord` for points.
The Alacritty UI crate is the only place which has a different indexing
system, since rendering and input puts the zero line at the top of the
viewport, rather than the top of the terminal region.
All instances which refer to a number of lines/columns instead of just a
single Line/Column have also been changed to use a `usize` instead. This
way a Line/Column will always refer to a specific place in the grid and
no confusion is created by having a count of lines as a possible index
into the grid storage.
Diffstat (limited to 'alacritty_terminal/src')
-rw-r--r-- | alacritty_terminal/src/ansi.rs | 46 | ||||
-rw-r--r-- | alacritty_terminal/src/grid/mod.rs | 302 | ||||
-rw-r--r-- | alacritty_terminal/src/grid/resize.rs | 134 | ||||
-rw-r--r-- | alacritty_terminal/src/grid/row.rs | 24 | ||||
-rw-r--r-- | alacritty_terminal/src/grid/storage.rs | 156 | ||||
-rw-r--r-- | alacritty_terminal/src/grid/tests.rs | 242 | ||||
-rw-r--r-- | alacritty_terminal/src/index.rs | 595 | ||||
-rw-r--r-- | alacritty_terminal/src/selection.rs | 323 | ||||
-rw-r--r-- | alacritty_terminal/src/term/cell.rs | 4 | ||||
-rw-r--r-- | alacritty_terminal/src/term/mod.rs | 448 | ||||
-rw-r--r-- | alacritty_terminal/src/term/search.rs | 261 | ||||
-rw-r--r-- | alacritty_terminal/src/tty/unix.rs | 5 | ||||
-rw-r--r-- | alacritty_terminal/src/tty/windows/conpty.rs | 9 | ||||
-rw-r--r-- | alacritty_terminal/src/vi_mode.rs | 188 |
14 files changed, 1186 insertions, 1551 deletions
diff --git a/alacritty_terminal/src/ansi.rs b/alacritty_terminal/src/ansi.rs index b46aec0b..44c92d3e 100644 --- a/alacritty_terminal/src/ansi.rs +++ b/alacritty_terminal/src/ansi.rs @@ -299,13 +299,13 @@ pub trait Handler { fn goto_col(&mut self, _: Column) {} /// Insert blank characters in current line starting from cursor. - fn insert_blank(&mut self, _: Column) {} + fn insert_blank(&mut self, _: usize) {} /// Move cursor up `rows`. - fn move_up(&mut self, _: Line) {} + fn move_up(&mut self, _: usize) {} /// Move cursor down `rows`. - fn move_down(&mut self, _: Line) {} + fn move_down(&mut self, _: usize) {} /// Identify the terminal (should write back to the pty stream). fn identify_terminal<W: io::Write>(&mut self, _: &mut W, _intermediate: Option<char>) {} @@ -320,10 +320,10 @@ pub trait Handler { fn move_backward(&mut self, _: Column) {} /// Move cursor down `rows` and set to column 1. - fn move_down_and_cr(&mut self, _: Line) {} + fn move_down_and_cr(&mut self, _: usize) {} /// Move cursor up `rows` and set to column 1. - fn move_up_and_cr(&mut self, _: Line) {} + fn move_up_and_cr(&mut self, _: usize) {} /// Put `count` tabs. fn put_tab(&mut self, _count: u16) {} @@ -352,16 +352,16 @@ pub trait Handler { fn set_horizontal_tabstop(&mut self) {} /// Scroll up `rows` rows. - fn scroll_up(&mut self, _: Line) {} + fn scroll_up(&mut self, _: usize) {} /// Scroll down `rows` rows. - fn scroll_down(&mut self, _: Line) {} + fn scroll_down(&mut self, _: usize) {} /// Insert `count` blank lines. - fn insert_blank_lines(&mut self, _: Line) {} + fn insert_blank_lines(&mut self, _: usize) {} /// Delete `count` lines. - fn delete_lines(&mut self, _: Line) {} + fn delete_lines(&mut self, _: usize) {} /// Erase `count` chars in current line following cursor. /// @@ -373,7 +373,7 @@ pub trait Handler { /// /// Deleting a character is like the delete key on the keyboard - everything /// to the right of the deleted things is shifted left. - fn delete_chars(&mut self, _: Column) {} + fn delete_chars(&mut self, _: usize) {} /// Move backward `count` tabs. fn move_backward_tabs(&mut self, _count: u16) {} @@ -1122,11 +1122,9 @@ where }; match (action, intermediates) { - ('@', []) => handler.insert_blank(Column(next_param_or(1) as usize)), - ('A', []) => { - handler.move_up(Line(next_param_or(1) as usize)); - }, - ('B', []) | ('e', []) => handler.move_down(Line(next_param_or(1) as usize)), + ('@', []) => handler.insert_blank(next_param_or(1) as usize), + ('A', []) => handler.move_up(next_param_or(1) as usize), + ('B', []) | ('e', []) => handler.move_down(next_param_or(1) as usize), ('b', []) => { if let Some(c) = self.state.preceding_char { for _ in 0..next_param_or(1) { @@ -1141,9 +1139,9 @@ where handler.identify_terminal(writer, intermediates.get(0).map(|&i| i as char)) }, ('D', []) => handler.move_backward(Column(next_param_or(1) as usize)), - ('d', []) => handler.goto_line(Line(next_param_or(1) as usize - 1)), - ('E', []) => handler.move_down_and_cr(Line(next_param_or(1) as usize)), - ('F', []) => handler.move_up_and_cr(Line(next_param_or(1) as usize)), + ('d', []) => handler.goto_line(Line(next_param_or(1) as i32 - 1)), + ('E', []) => handler.move_down_and_cr(next_param_or(1) as usize), + ('F', []) => handler.move_up_and_cr(next_param_or(1) as usize), ('G', []) | ('`', []) => handler.goto_col(Column(next_param_or(1) as usize - 1)), ('g', []) => { let mode = match next_param_or(0) { @@ -1158,7 +1156,7 @@ where handler.clear_tabs(mode); }, ('H', []) | ('f', []) => { - let y = next_param_or(1) as usize; + let y = next_param_or(1) as i32; let x = next_param_or(1) as usize; handler.goto(Line(y - 1), Column(x - 1)); }, @@ -1198,7 +1196,7 @@ where handler.clear_line(mode); }, - ('L', []) => handler.insert_blank_lines(Line(next_param_or(1) as usize)), + ('L', []) => handler.insert_blank_lines(next_param_or(1) as usize), ('l', intermediates) => { for param in params_iter.map(|param| param[0]) { match Mode::from_primitive(intermediates.get(0), param) { @@ -1207,7 +1205,7 @@ where } } }, - ('M', []) => handler.delete_lines(Line(next_param_or(1) as usize)), + ('M', []) => handler.delete_lines(next_param_or(1) as usize), ('m', []) => { if params.is_empty() { handler.terminal_attribute(Attr::Reset); @@ -1221,7 +1219,7 @@ where } }, ('n', []) => handler.device_status(writer, next_param_or(0) as usize), - ('P', []) => handler.delete_chars(Column(next_param_or(1) as usize)), + ('P', []) => handler.delete_chars(next_param_or(1) as usize), ('q', [b' ']) => { // DECSCUSR (CSI Ps SP q) -- Set Cursor Style. let cursor_style_id = next_param_or(0); @@ -1247,9 +1245,9 @@ where handler.set_scrolling_region(top, bottom); }, - ('S', []) => handler.scroll_up(Line(next_param_or(1) as usize)), + ('S', []) => handler.scroll_up(next_param_or(1) as usize), ('s', []) => handler.save_cursor_position(), - ('T', []) => handler.scroll_down(Line(next_param_or(1) as usize)), + ('T', []) => handler.scroll_down(next_param_or(1) as usize), ('t', []) => match next_param_or(1) as usize { 14 => handler.text_area_size_pixels(writer), 18 => handler.text_area_size_chars(writer), diff --git a/alacritty_terminal/src/grid/mod.rs b/alacritty_terminal/src/grid/mod.rs index 7949489a..169067af 100644 --- a/alacritty_terminal/src/grid/mod.rs +++ b/alacritty_terminal/src/grid/mod.rs @@ -1,13 +1,13 @@ //! A specialized 2D grid implementation optimized for use in a terminal. use std::cmp::{max, min}; -use std::iter::{Map, TakeWhile}; -use std::ops::{Bound, Deref, Index, IndexMut, Range, RangeBounds, RangeInclusive}; +use std::iter::TakeWhile; +use std::ops::{Bound, Deref, Index, IndexMut, Range, RangeBounds}; use serde::{Deserialize, Serialize}; use crate::ansi::{CharsetIndex, StandardCharset}; -use crate::index::{Column, IndexRange, Line, Point}; +use crate::index::{Column, Line, Point}; use crate::term::cell::{Flags, ResetDiscriminant}; pub mod resize; @@ -71,7 +71,7 @@ impl IndexMut<CharsetIndex> for Charsets { #[derive(Debug, Copy, Clone)] pub enum Scroll { - Delta(isize), + Delta(i32), PageUp, PageDown, Top, @@ -103,7 +103,7 @@ pub enum Scroll { /// │ │ /// └─────────────────────────┘ <-- zero /// ^ -/// cols +/// columns /// ``` #[derive(Serialize, Deserialize, Clone, Debug)] pub struct Grid<T> { @@ -120,10 +120,10 @@ pub struct Grid<T> { raw: Storage<T>, /// Number of columns. - cols: Column, + columns: usize, /// Number of visible lines. - lines: Line, + lines: usize, /// Offset of displayed area. /// @@ -137,15 +137,15 @@ pub struct Grid<T> { } impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { - pub fn new(lines: Line, cols: Column, max_scroll_limit: usize) -> Grid<T> { + pub fn new(lines: usize, columns: usize, max_scroll_limit: usize) -> Grid<T> { Grid { - raw: Storage::with_capacity(lines, cols), + raw: Storage::with_capacity(lines, columns), max_scroll_limit, display_offset: 0, saved_cursor: Cursor::default(), cursor: Cursor::default(), lines, - cols, + columns, } } @@ -161,12 +161,11 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { pub fn scroll_display(&mut self, scroll: Scroll) { self.display_offset = match scroll { - Scroll::Delta(count) => min( - max((self.display_offset as isize) + count, 0isize) as usize, - self.history_size(), - ), - Scroll::PageUp => min(self.display_offset + self.lines.0, self.history_size()), - Scroll::PageDown => self.display_offset.saturating_sub(self.lines.0), + Scroll::Delta(count) => { + min(max((self.display_offset as i32) + count, 0) as usize, self.history_size()) + }, + Scroll::PageUp => min(self.display_offset + self.lines, self.history_size()), + Scroll::PageDown => self.display_offset.saturating_sub(self.lines), Scroll::Top => self.history_size(), Scroll::Bottom => 0, }; @@ -175,7 +174,7 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { fn increase_scroll_limit(&mut self, count: usize) { let count = min(count, self.max_scroll_limit - self.history_size()); if count != 0 { - self.raw.initialize(count, self.cols); + self.raw.initialize(count, self.columns); } } @@ -188,18 +187,15 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { } #[inline] - pub fn scroll_down<D>(&mut self, region: &Range<Line>, positions: Line) + pub fn scroll_down<D>(&mut self, region: &Range<Line>, positions: usize) where T: ResetDiscriminant<D>, D: PartialEq, { - let screen_lines = self.screen_lines().0; - // When rotating the entire region, just reset everything. - if positions >= region.end - region.start { - for i in region.start.0..region.end.0 { - let index = screen_lines - i - 1; - self.raw[index].reset(&self.cursor.template); + if region.end - region.start <= positions { + for i in (region.start.0..region.end.0).map(Line::from) { + self.raw[i].reset(&self.cursor.template); } return; @@ -218,32 +214,32 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { // // We need to start from the top, to make sure the fixed lines aren't swapped with each // other. - let fixed_lines = screen_lines - region.end.0; - for i in (0..fixed_lines).rev() { - self.raw.swap(i, i + positions.0); + let screen_lines = self.screen_lines() as i32; + for i in (region.end.0..screen_lines).map(Line::from) { + self.raw.swap(i, i - positions as i32); } // Rotate the entire line buffer downward. - self.raw.rotate_down(*positions); + self.raw.rotate_down(positions); // Ensure all new lines are fully cleared. - for i in 0..positions.0 { - let index = screen_lines - i - 1; - self.raw[index].reset(&self.cursor.template); + for i in (0..positions).map(Line::from) { + self.raw[i].reset(&self.cursor.template); } // Swap the fixed lines at the top back into position. - for i in 0..region.start.0 { - let index = screen_lines - i - 1; - self.raw.swap(index, index - positions.0); + for i in (0..region.start.0).map(Line::from) { + self.raw.swap(i, i + positions); } } else { // Subregion rotation. - for line in IndexRange((region.start + positions)..region.end).rev() { - self.raw.swap_lines(line, line - positions); + let range = (region.start + positions).0..region.end.0; + for line in range.rev().map(Line::from) { + self.raw.swap(line, line - positions); } - for line in IndexRange(region.start..(region.start + positions)) { + let range = region.start.0..(region.start + positions).0; + for line in range.rev().map(Line::from) { self.raw[line].reset(&self.cursor.template); } } @@ -252,18 +248,15 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { /// Move lines at the bottom toward the top. /// /// This is the performance-sensitive part of scrolling. - pub fn scroll_up<D>(&mut self, region: &Range<Line>, positions: Line) + pub fn scroll_up<D>(&mut self, region: &Range<Line>, positions: usize) where T: ResetDiscriminant<D>, D: PartialEq, { - let screen_lines = self.screen_lines().0; - // When rotating the entire region with fixed lines at the top, just reset everything. - if positions >= region.end - region.start && region.start != Line(0) { - for i in region.start.0..region.end.0 { - let index = screen_lines - i - 1; - self.raw[index].reset(&self.cursor.template); + if region.end - region.start <= positions && region.start != 0 { + for i in (region.start.0..region.end.0).map(Line::from) { + self.raw[i].reset(&self.cursor.template); } return; @@ -271,11 +264,11 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { // Update display offset when not pinned to active area. if self.display_offset != 0 { - self.display_offset = min(self.display_offset + *positions, self.max_scroll_limit); + self.display_offset = min(self.display_offset + positions, self.max_scroll_limit); } // Create scrollback for the new lines. - self.increase_scroll_limit(*positions); + self.increase_scroll_limit(positions); // Swap the lines fixed at the top to their target positions after rotation. // @@ -285,23 +278,22 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { // // We need to start from the bottom, to make sure the fixed lines aren't swapped with each // other. - for i in (0..region.start.0).rev() { - let index = screen_lines - i - 1; - self.raw.swap(index, index - positions.0); + for i in (0..region.start.0).rev().map(Line::from) { + self.raw.swap(i, i + positions); } // Rotate the entire line buffer upward. - self.raw.rotate(-(positions.0 as isize)); + self.raw.rotate(-(positions as isize)); // Ensure all new lines are fully cleared. - for i in 0..positions.0 { + let screen_lines = self.screen_lines(); + for i in ((screen_lines - positions)..screen_lines).map(Line::from) { self.raw[i].reset(&self.cursor.template); } // Swap the fixed lines at the bottom back into position. - let fixed_lines = screen_lines - region.end.0; - for i in 0..fixed_lines { - self.raw.swap(i, i + positions.0); + for i in (region.end.0..(screen_lines as i32)).rev().map(Line::from) { + self.raw.swap(i, i - positions); } } @@ -311,16 +303,16 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { D: PartialEq, { // Determine how many lines to scroll up by. - let end = Point { line: 0, column: self.cols() }; + let end = Point::new(Line(self.lines as i32 - 1), Column(self.columns())); let mut iter = self.iter_from(end); while let Some(cell) = iter.prev() { - if !cell.is_empty() || cell.point.line >= *self.lines { + if !cell.is_empty() || cell.point.line < 0 { break; } } - debug_assert!(iter.point.line <= *self.lines); - let positions = self.lines - iter.point.line; - let region = Line(0)..self.screen_lines(); + debug_assert!(iter.point.line >= -1); + let positions = (iter.point.line.0 + 1) as usize; + let region = Line(0)..Line(self.lines as i32); // Reset display offset. self.display_offset = 0; @@ -329,8 +321,8 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { self.scroll_up(®ion, positions); // Reset rotated lines. - for i in positions.0..self.lines.0 { - self.raw[i].reset(&self.cursor.template); + for line in (0..(self.lines - positions)).map(Line::from) { + self.raw[line].reset(&self.cursor.template); } } @@ -347,8 +339,9 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { self.display_offset = 0; // Reset all visible lines. - for row in 0..self.raw.len() { - self.raw[row].reset(&self.cursor.template); + let range = self.topmost_line().0..(self.screen_lines() as i32); + for line in range.map(Line::from) { + self.raw[line].reset(&self.cursor.template); } } } @@ -369,59 +362,17 @@ impl<T> Grid<T> { let end = match bounds.end_bound() { Bound::Included(line) => *line + 1, Bound::Excluded(line) => *line, - Bound::Unbounded => self.screen_lines(), + Bound::Unbounded => Line(self.screen_lines() as i32), }; - debug_assert!(start < self.screen_lines()); - debug_assert!(end <= self.screen_lines()); + debug_assert!(start < self.screen_lines() as i32); + debug_assert!(end <= self.screen_lines() as i32); - for row in start.0..end.0 { - self.raw[Line(row)].reset(&self.cursor.template); + for line in (start.0..end.0).map(Line::from) { + self.raw[line].reset(&self.cursor.template); } } - /// Clamp a buffer point to the visible region. - pub fn clamp_buffer_to_visible(&self, point: Point<usize>) -> Point { - if point.line < self.display_offset { - Point::new(self.lines - 1, self.cols - 1) - } else if point.line >= self.display_offset + self.lines.0 { - Point::new(Line(0), Column(0)) - } else { - // Since edgecases are handled, conversion is identical as visible to buffer. - self.visible_to_buffer(point.into()).into() - } - } - - // Clamp a buffer point based range to the viewport. - // - // This will make sure the content within the range is visible and return `None` whenever the - // entire range is outside the visible region. - pub fn clamp_buffer_range_to_visible( - &self, - range: &RangeInclusive<Point<usize>>, - ) -> Option<RangeInclusive<Point>> { - let start = range.start(); - let end = range.end(); - - // Check if the range is completely offscreen - let viewport_end = self.display_offset; - let viewport_start = viewport_end + self.lines.0 - 1; - if end.line > viewport_start || start.line < viewport_end { - return None; - } - - let start = self.clamp_buffer_to_visible(*start); - let end = self.clamp_buffer_to_visible(*end); - - Some(start..=end) - } - - /// Convert viewport relative point to global buffer indexing. - #[inline] - pub fn visible_to_buffer(&self, point: Point) -> Point<usize> { - Point { line: self.lines.0 + self.display_offset - point.line.0 - 1, column: point.column } - } - #[inline] pub fn clear_history(&mut self) { // Explicitly purge all lines from history. @@ -438,7 +389,7 @@ impl<T> Grid<T> { self.truncate(); // Initialize everything with empty new lines. - self.raw.initialize(self.max_scroll_limit - self.history_size(), self.cols); + self.raw.initialize(self.max_scroll_limit - self.history_size(), self.columns); } /// This is used only for truncating before saving ref-tests. @@ -448,28 +399,21 @@ impl<T> Grid<T> { } #[inline] - pub fn iter_from(&self, point: Point<usize>) -> GridIterator<'_, T> { + pub fn iter_from(&self, point: Point) -> GridIterator<'_, T> { GridIterator { grid: self, point } } /// Iterator over all visible cells. #[inline] pub fn display_iter(&self) -> DisplayIter<'_, T> { - let start = Point::new(self.display_offset + self.lines.0, self.cols() - 1); - let end = Point::new(self.display_offset, self.cols()); + let start = Point::new(Line(-(self.display_offset as i32) - 1), self.last_column()); + let end = Point::new(start.line + self.lines, Column(self.columns)); let iter = GridIterator { grid: self, point: start }; - let display_offset = self.display_offset; - let lines = self.lines.0; - let take_while: DisplayIterTakeFun<'_, T> = Box::new(move |indexed: &Indexed<&T>| indexed.point <= end); - let map: DisplayIterMapFun<'_, T> = Box::new(move |indexed: Indexed<&T>| { - let line = Line(lines + display_offset - indexed.point.line - 1); - Indexed { point: Point::new(line, indexed.point.column), cell: indexed.cell } - }); - iter.take_while(take_while).map(map) + iter.take_while(take_while) } #[inline] @@ -488,7 +432,7 @@ impl<T: PartialEq> PartialEq for Grid<T> { fn eq(&self, other: &Self) -> bool { // Compare struct fields and check result of grid comparison. self.raw.eq(&other.raw) - && self.cols.eq(&other.cols) + && self.columns.eq(&other.columns) && self.lines.eq(&other.lines) && self.display_offset.eq(&other.display_offset) } @@ -510,38 +454,6 @@ impl<T> IndexMut<Line> for Grid<T> { } } -impl<T> Index<usize> for Grid<T> { - type Output = Row<T>; - - #[inline] - fn index(&self, index: usize) -> &Row<T> { - &self.raw[index] - } -} - -impl<T> IndexMut<usize> for Grid<T> { - #[inline] - fn index_mut(&mut self, index: usize) -> &mut Row<T> { - &mut self.raw[index] - } -} - -impl<T> Index<Point<usize>> for Grid<T> { - type Output = T; - - #[inline] - fn index(&self, point: Point<usize>) -> &T { - &self[point.line][point.column] - } -} - -impl<T> IndexMut<Point<usize>> for Grid<T> { - #[inline] - fn index_mut(&mut self, point: Point<usize>) -> &mut T { - &mut self[point.line][point.column] - } -} - impl<T> Index<Point> for Grid<T> { type Output = T; @@ -564,15 +476,33 @@ pub trait Dimensions { fn total_lines(&self) -> usize; /// Height of the viewport in lines. - fn screen_lines(&self) -> Line; + fn screen_lines(&self) -> usize; /// Width of the terminal in columns. - fn cols(&self) -> Column; + fn columns(&self) -> usize; + + /// Index for the last column. + #[inline] + fn last_column(&self) -> Column { + Column(self.columns() - 1) + } + + /// Line farthest up in the grid history. + #[inline] + fn topmost_line(&self) -> Line { + Line(-(self.history_size() as i32)) + } + + /// Line farthest down in the grid history. + #[inline] + fn bottommost_line(&self) -> Line { + Line(self.screen_lines() as i32 - 1) + } /// Number of invisible lines part of the scrollback history. #[inline] fn history_size(&self) -> usize { - self.total_lines() - self.screen_lines().0 + self.total_lines() - self.screen_lines() } } @@ -583,38 +513,38 @@ impl<G> Dimensions for Grid<G> { } #[inline] - fn screen_lines(&self) -> Line { + fn screen_lines(&self) -> usize { self.lines } #[inline] - fn cols(&self) -> Column { - self.cols + fn columns(&self) -> usize { + self.columns } } #[cfg(test)] -impl Dimensions for (Line, Column) { +impl Dimensions for (usize, usize) { fn total_lines(&self) -> usize { - *self.0 + self.0 } - fn screen_lines(&self) -> Line { + fn screen_lines(&self) -> usize { self.0 } - fn cols(&self) -> Column { + fn columns(&self) -> usize { self.1 } } #[derive(Debug, PartialEq)] -pub struct Indexed<T, L = usize> { - pub point: Point<L>, +pub struct Indexed<T> { + pub point: Point, pub cell: T, } -impl<T, L> Deref for Indexed<T, L> { +impl<T> Deref for Indexed<T> { type Target = T; #[inline] @@ -629,12 +559,12 @@ pub struct GridIterator<'a, T> { grid: &'a Grid<T>, /// Current position of the iterator within the grid. - point: Point<usize>, + point: Point, } impl<'a, T> GridIterator<'a, T> { /// Current iteratior position. - pub fn point(&self) -> Point<usize> { + pub fn point(&self) -> Point { self.point } @@ -648,12 +578,17 @@ impl<'a, T> Iterator for GridIterator<'a, T> { type Item = Indexed<&'a T>; fn next(&mut self) -> Option<Self::Item> { - let last_col = self.grid.cols() - 1; + let last_column = self.grid.last_column(); + + // Stop once we've reached the end of the grid. + if self.point == Point::new(self.grid.bottommost_line(), last_column) { + return None; + } + match self.point { - Point { line, column: col } if line == 0 && col == last_col => return None, - Point { column: col, .. } if (col == last_col) => { - self.point.line -= 1; + Point { column, .. } if column == last_column => { self.point.column = Column(0); + self.point.line += 1; }, _ => self.point.column += Column(1), } @@ -669,15 +604,18 @@ pub trait BidirectionalIterator: Iterator { impl<'a, T> BidirectionalIterator for GridIterator<'a, T> { fn prev(&mut self) -> Option<Self::Item> { - let last_col = self.grid.cols() - 1; + let topmost_line = self.grid.topmost_line(); + let last_column = self.grid.last_column(); + + // Stop once we've reached the end of the grid. + if self.point == Point::new(topmost_line, Column(0)) { + return None; + } match self.point { - Point { line, column: Column(0) } if line == self.grid.total_lines() - 1 => { - return None - }, Point { column: Column(0), .. } => { - self.point.line += 1; - self.point.column = last_col; + self.point.column = last_column; + self.point.line -= 1; }, _ => self.point.column -= Column(1), } @@ -686,7 +624,5 @@ impl<'a, T> BidirectionalIterator for GridIterator<'a, T> { } } -pub type DisplayIter<'a, T> = - Map<TakeWhile<GridIterator<'a, T>, DisplayIterTakeFun<'a, T>>, DisplayIterMapFun<'a, T>>; +pub type DisplayIter<'a, T> = TakeWhile<GridIterator<'a, T>, DisplayIterTakeFun<'a, T>>; type DisplayIterTakeFun<'a, T> = Box<dyn Fn(&Indexed<&'a T>) -> bool>; -type DisplayIterMapFun<'a, T> = Box<dyn FnMut(Indexed<&'a T>) -> Indexed<&'a T, Line>>; diff --git a/alacritty_terminal/src/grid/resize.rs b/alacritty_terminal/src/grid/resize.rs index 40492c3a..eb8bef0c 100644 --- a/alacritty_terminal/src/grid/resize.rs +++ b/alacritty_terminal/src/grid/resize.rs @@ -1,9 +1,9 @@ //! Grid resize and reflow. -use std::cmp::{min, Ordering}; +use std::cmp::{max, min, Ordering}; use std::mem; -use crate::index::{Column, Line}; +use crate::index::{Boundary, Column, Line}; use crate::term::cell::{Flags, ResetDiscriminant}; use crate::grid::row::Row; @@ -11,7 +11,7 @@ use crate::grid::{Dimensions, Grid, GridCell}; impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { /// Resize the grid's width and/or height. - pub fn resize<D>(&mut self, reflow: bool, lines: Line, cols: Column) + pub fn resize<D>(&mut self, reflow: bool, lines: usize, columns: usize) where T: ResetDiscriminant<D>, D: PartialEq, @@ -25,9 +25,9 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { Ordering::Equal => (), } - match self.cols.cmp(&cols) { - Ordering::Less => self.grow_cols(reflow, cols), - Ordering::Greater => self.shrink_cols(reflow, cols), + match self.columns.cmp(&columns) { + Ordering::Less => self.grow_columns(reflow, columns), + Ordering::Greater => self.shrink_columns(reflow, columns), Ordering::Equal => (), } @@ -40,32 +40,32 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { /// Alacritty keeps the cursor at the bottom of the terminal as long as there /// is scrollback available. Once scrollback is exhausted, new lines are /// simply added to the bottom of the screen. - fn grow_lines<D>(&mut self, new_line_count: Line) + fn grow_lines<D>(&mut self, target: usize) where T: ResetDiscriminant<D>, D: PartialEq, { - let lines_added = new_line_count - self.lines; + let lines_added = target - self.lines; // Need to resize before updating buffer. - self.raw.grow_visible_lines(new_line_count); - self.lines = new_line_count; + self.raw.grow_visible_lines(target); + self.lines = target; let history_size = self.history_size(); - let from_history = min(history_size, lines_added.0); + let from_history = min(history_size, lines_added); // Move existing lines up for every line that couldn't be pulled from history. - if from_history != lines_added.0 { + if from_history != lines_added { let delta = lines_added - from_history; - self.scroll_up(&(Line(0)..new_line_count), delta); + self.scroll_up(&(Line(0)..Line(target as i32)), delta); } // Move cursor down for every line pulled from history. self.saved_cursor.point.line += from_history; self.cursor.point.line += from_history; - self.display_offset = self.display_offset.saturating_sub(*lines_added); - self.decrease_scroll_limit(*lines_added); + self.display_offset = self.display_offset.saturating_sub(lines_added); + self.decrease_scroll_limit(lines_added); } /// Remove lines from the visible area. @@ -75,37 +75,37 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { /// of the terminal window. /// /// Alacritty takes the same approach. - fn shrink_lines<D>(&mut self, target: Line) + fn shrink_lines<D>(&mut self, target: usize) where T: ResetDiscriminant<D>, D: PartialEq, { // Scroll up to keep content inside the window. - let required_scrolling = (self.cursor.point.line + 1).saturating_sub(target.0); + let required_scrolling = (self.cursor.point.line.0 as usize + 1).saturating_sub(target); if required_scrolling > 0 { - self.scroll_up(&(Line(0)..self.lines), Line(required_scrolling)); + self.scroll_up(&(Line(0)..Line(self.lines as i32)), required_scrolling); // Clamp cursors to the new viewport size. - self.cursor.point.line = min(self.cursor.point.line, target - 1); + self.cursor.point.line = min(self.cursor.point.line, Line(target as i32 - 1)); } // Clamp saved cursor, since only primary cursor is scrolled into viewport. - self.saved_cursor.point.line = min(self.saved_cursor.point.line, target - 1); + self.saved_cursor.point.line = min(self.saved_cursor.point.line, Line(target as i32 - 1)); - self.raw.rotate((self.lines - target).0 as isize); + self.raw.rotate((self.lines - target) as isize); self.raw.shrink_visible_lines(target); self.lines = target; } /// Grow number of columns in each row, reflowing if necessary. - fn grow_cols(&mut self, reflow: bool, cols: Column) { + fn grow_columns(&mut self, reflow: bool, columns: usize) { // Check if a row needs to be wrapped. let should_reflow = |row: &Row<T>| -> bool { let len = Column(row.len()); - reflow && len.0 > 0 && len < cols && row[len - 1].flags().contains(Flags::WRAPLINE) + reflow && len.0 > 0 && len < columns && row[len - 1].flags().contains(Flags::WRAPLINE) }; - self.cols = cols; + self.columns = columns; let mut reversed: Vec<Row<T>> = Vec::with_capacity(self.raw.len()); let mut cursor_line_delta = 0; @@ -138,12 +138,12 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { if last_len >= 1 && last_row[Column(last_len - 1)].flags().contains(Flags::LEADING_WIDE_CHAR_SPACER) { - last_row.shrink(Column(last_len - 1)); + last_row.shrink(last_len - 1); last_len -= 1; } // Don't try to pull more cells from the next line than available. - let mut num_wrapped = cols.0 - last_len; + let mut num_wrapped = columns - last_len; let len = min(row.len(), num_wrapped); // Insert leading spacer when there's not enough room for reflowing wide char. @@ -164,30 +164,30 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { // Add removed cells to previous row and reflow content. last_row.append(&mut cells); - let cursor_buffer_line = (self.lines - self.cursor.point.line - 1).0; + let cursor_buffer_line = self.lines - self.cursor.point.line.0 as usize - 1; if i == cursor_buffer_line && reflow { // Resize cursor's line and reflow the cursor if necessary. - let mut target = self.cursor.point.sub(cols, num_wrapped); + let mut target = self.cursor.point.sub(self, Boundary::Cursor, num_wrapped); // Clamp to the last column, if no content was reflown with the cursor. if target.column.0 == 0 && row.is_clear() { self.cursor.input_needs_wrap = true; - target = target.sub(cols, 1); + target = target.sub(self, Boundary::Cursor, 1); } self.cursor.point.column = target.column; - // Get required cursor line changes. Since `num_wrapped` is smaller than `cols` + // Get required cursor line changes. Since `num_wrapped` is smaller than `columns` // this will always be either `0` or `1`. - let line_delta = (self.cursor.point.line - target.line).0; + let line_delta = self.cursor.point.line - target.line; if line_delta != 0 && row.is_clear() { continue; } - cursor_line_delta += line_delta; + cursor_line_delta += line_delta.0 as usize; } else if row.is_clear() { - if i + reversed.len() >= self.lines.0 { + if i + reversed.len() >= self.lines { // Since we removed a line, rotate down the viewport. self.display_offset = self.display_offset.saturating_sub(1); } @@ -210,27 +210,27 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { } // Make sure we have at least the viewport filled. - if reversed.len() < self.lines.0 { - let delta = self.lines.0 - reversed.len(); - self.cursor.point.line.0 = self.cursor.point.line.saturating_sub(delta); - reversed.resize_with(self.lines.0, || Row::new(cols)); + if reversed.len() < self.lines { + let delta = (self.lines - reversed.len()) as i32; + self.cursor.point.line = max(self.cursor.point.line - delta, Line(0)); + reversed.resize_with(self.lines, || Row::new(columns)); } // Pull content down to put cursor in correct position, or move cursor up if there's no // more lines to delete below the cursor. if cursor_line_delta != 0 { - let cursor_buffer_line = (self.lines - self.cursor.point.line - 1).0; - let available = min(cursor_buffer_line, reversed.len() - self.lines.0); + let cursor_buffer_line = self.lines - self.cursor.point.line.0 as usize - 1; + let available = min(cursor_buffer_line, reversed.len() - self.lines); let overflow = cursor_line_delta.saturating_sub(available); reversed.truncate(reversed.len() + overflow - cursor_line_delta); - self.cursor.point.line.0 = self.cursor.point.line.saturating_sub(overflow); + self.cursor.point.line = max(self.cursor.point.line - overflow, Line(0)); } // Reverse iterator and fill all rows that are still too short. let mut new_raw = Vec::with_capacity(reversed.len()); for mut row in reversed.drain(..).rev() { - if row.len() < cols.0 { - row.grow(cols); + if row.len() < columns { + row.grow(columns); } new_raw.push(row); } @@ -242,8 +242,8 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { } /// Shrink number of columns in each row, reflowing if necessary. - fn shrink_cols(&mut self, reflow: bool, cols: Column) { - self.cols = cols; + fn shrink_columns(&mut self, reflow: bool, columns: usize) { + self.columns = columns; // Remove the linewrap special case, by moving the cursor outside of the grid. if self.cursor.input_needs_wrap && reflow { @@ -260,7 +260,7 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { if let Some(buffered) = buffered.take() { // Add a column for every cell added before the cursor, if it goes beyond the new // width it is then later reflown. - let cursor_buffer_line = (self.lines - self.cursor.point.line - 1).0; + let cursor_buffer_line = self.lines - self.cursor.point.line.0 as usize - 1; if i == cursor_buffer_line { self.cursor.point.column += buffered.len(); } @@ -270,11 +270,11 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { loop { // Remove all cells which require reflowing. - let mut wrapped = match row.shrink(cols) { + let mut wrapped = match row.shrink(columns) { Some(wrapped) if reflow => wrapped, _ => { - let cursor_buffer_line = (self.lines - self.cursor.point.line - 1).0; - if reflow && i == cursor_buffer_line && self.cursor.point.column > cols { + let cursor_buffer_line = self.lines - self.cursor.point.line.0 as usize - 1; + if reflow && i == cursor_buffer_line && self.cursor.point.column > columns { // If there are empty cells before the cursor, we assume it is explicit // whitespace and need to wrap it like normal content. Vec::new() @@ -287,11 +287,13 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { }; // Insert spacer if a wide char would be wrapped into the last column. - if row.len() >= cols.0 && row[cols - 1].flags().contains(Flags::WIDE_CHAR) { + if row.len() >= columns + && row[Column(columns - 1)].flags().contains(Flags::WIDE_CHAR) + { let mut spacer = T::default(); spacer.flags_mut().insert(Flags::LEADING_WIDE_CHAR_SPACER); - let wide_char = mem::replace(&mut row[cols - 1], spacer); + let wide_char = mem::replace(&mut row[Column(columns - 1)], spacer); wrapped.insert(0, wide_char); } @@ -299,7 +301,7 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { let len = wrapped.len(); if len > 0 && wrapped[len - 1].flags().contains(Flags::LEADING_WIDE_CHAR_SPACER) { if len == 1 { - row[cols - 1].flags_mut().insert(Flags::WRAPLINE); + row[Column(columns - 1)].flags_mut().insert(Flags::WRAPLINE); new_raw.push(row); break; } else { @@ -320,7 +322,7 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { .last() .map(|c| c.flags().contains(Flags::WRAPLINE) && i >= 1) .unwrap_or(false) - && wrapped.len() < cols.0 + && wrapped.len() < columns { // Make sure previous wrap flag doesn't linger around. if let Some(cell) = wrapped.last_mut() { @@ -332,24 +334,24 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { break; } else { // Reflow cursor if a line below it is deleted. - let cursor_buffer_line = (self.lines - self.cursor.point.line - 1).0; - if (i == cursor_buffer_line && self.cursor.point.column < cols) + let cursor_buffer_line = self.lines - self.cursor.point.line.0 as usize - 1; + if (i == cursor_buffer_line && self.cursor.point.column < columns) || i < cursor_buffer_line { - self.cursor.point.line.0 = self.cursor.point.line.saturating_sub(1); + self.cursor.point.line = max(self.cursor.point.line - 1, Line(0)); } // Reflow the cursor if it is on this line beyond the width. - if i == cursor_buffer_line && self.cursor.point.column >= cols { - // Since only a single new line is created, we subtract only `cols` + if i == cursor_buffer_line && self.cursor.point.column >= columns { + // Since only a single new line is created, we subtract only `columns` // from the cursor instead of reflowing it completely. - self.cursor.point.column -= cols; + self.cursor.point.column -= columns; } // Make sure new row is at least as long as new width. let occ = wrapped.len(); - if occ < cols.0 { - wrapped.resize_with(cols.0, T::default); + if occ < columns { + wrapped.resize_with(columns, T::default); } row = Row::from_vec(wrapped, occ); } @@ -358,22 +360,22 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { // Reverse iterator and use it as the new grid storage. let mut reversed: Vec<Row<T>> = new_raw.drain(..).rev().collect(); - reversed.truncate(self.max_scroll_limit + self.lines.0); + reversed.truncate(self.max_scroll_limit + self.lines); self.raw.replace_inner(reversed); // Reflow the primary cursor, or clamp it if reflow is disabled. if !reflow { - self.cursor.point.column = min(self.cursor.point.column, cols - 1); - } else if self.cursor.point.column == cols - && !self[self.cursor.point.line][cols - 1].flags().contains(Flags::WRAPLINE) + self.cursor.point.column = min(self.cursor.point.column, Column(columns - 1)); + } else if self.cursor.point.column == columns + && !self[self.cursor.point.line][Column(columns - 1)].flags().contains(Flags::WRAPLINE) { self.cursor.input_needs_wrap = true; self.cursor.point.column -= 1; } else { - self.cursor.point = self.cursor.point.add(cols, 0); + self.cursor.point = self.cursor.point.grid_clamp(self, Boundary::Cursor); } // Clamp the saved cursor to the grid. - self.saved_cursor.point.column = min(self.saved_cursor.point.column, cols - 1); + self.saved_cursor.point.column = min(self.saved_cursor.point.column, Column(columns - 1)); } } diff --git a/alacritty_terminal/src/grid/row.rs b/alacritty_terminal/src/grid/row.rs index e48103a6..c932e83f 100644 --- a/alacritty_terminal/src/grid/row.rs +++ b/alacritty_terminal/src/grid/row.rs @@ -34,22 +34,22 @@ impl<T: Clone + Default> Row<T> { /// Create a new terminal row. /// /// Ideally the `template` should be `Copy` in all performance sensitive scenarios. - pub fn new(columns: Column) -> Row<T> { - debug_assert!(columns.0 >= 1); + pub fn new(columns: usize) -> Row<T> { + debug_assert!(columns >= 1); - let mut inner: Vec<T> = Vec::with_capacity(columns.0); + let mut inner: Vec<T> = Vec::with_capacity(columns); // This is a slightly optimized version of `std::vec::Vec::resize`. unsafe { let mut ptr = inner.as_mut_ptr(); - for _ in 1..columns.0 { + for _ in 1..columns { ptr::write(ptr, T::default()); ptr = ptr.offset(1); } ptr::write(ptr, T::default()); - inner.set_len(columns.0); + inner.set_len(columns); } Row { inner, occ: 0 } @@ -57,31 +57,31 @@ impl<T: Clone + Default> Row<T> { /// Increase the number of columns in the row. #[inline] - pub fn grow(&mut self, cols: Column) { - if self.inner.len() >= cols.0 { + pub fn grow(&mut self, columns: usize) { + if self.inner.len() >= columns { return; } - self.inner.resize_with(cols.0, T::default); + self.inner.resize_with(columns, T::default); } /// Reduce the number of columns in the row. /// /// This will return all non-empty cells that were removed. - pub fn shrink(&mut self, cols: Column) -> Option<Vec<T>> + pub fn shrink(&mut self, columns: usize) -> Option<Vec<T>> where T: GridCell, { - if self.inner.len() <= cols.0 { + if self.inner.len() <= columns { return None; } // Split off cells for a new row. - let mut new_row = self.inner.split_off(cols.0); + let mut new_row = self.inner.split_off(columns); let index = new_row.iter().rposition(|c| !c.is_empty()).map(|i| i + 1).unwrap_or(0); new_row.truncate(index); - self.occ = min(self.occ, cols.0); + self.occ = min(self.occ, columns); if new_row.is_empty() { None diff --git a/alacritty_terminal/src/grid/storage.rs b/alacritty_terminal/src/grid/storage.rs index f9c4b2cf..ad6358f0 100644 --- a/alacritty_terminal/src/grid/storage.rs +++ b/alacritty_terminal/src/grid/storage.rs @@ -5,7 +5,7 @@ use std::ops::{Index, IndexMut}; use serde::{Deserialize, Serialize}; use super::Row; -use crate::index::{Column, Line}; +use crate::index::Line; /// Maximum number of buffered lines outside of the grid for performance optimization. const MAX_CACHE_SIZE: usize = 1_000; @@ -38,7 +38,7 @@ pub struct Storage<T> { zero: usize, /// Number of visible lines. - visible_lines: Line, + visible_lines: usize, /// Total number of lines currently active in the terminal (scrollback + visible) /// @@ -61,28 +61,28 @@ impl<T: PartialEq> PartialEq for Storage<T> { impl<T> Storage<T> { #[inline] - pub fn with_capacity(visible_lines: Line, cols: Column) -> Storage<T> + pub fn with_capacity(visible_lines: usize, columns: usize) -> Storage<T> where T: Clone + Default, { // Initialize visible lines; the scrollback buffer is initialized dynamically. - let mut inner = Vec::with_capacity(visible_lines.0); - inner.resize_with(visible_lines.0, || Row::new(cols)); + let mut inner = Vec::with_capacity(visible_lines); + inner.resize_with(visible_lines, || Row::new(columns)); - Storage { inner, zero: 0, visible_lines, len: visible_lines.0 } + Storage { inner, zero: 0, visible_lines, len: visible_lines } } /// Increase the number of lines in the buffer. #[inline] - pub fn grow_visible_lines(&mut self, next: Line) + pub fn grow_visible_lines(&mut self, next: usize) where T: Clone + Default, { // Number of lines the buffer needs to grow. let growage = next - self.visible_lines; - let cols = self[0].len(); - self.initialize(growage.0, Column(cols)); + let columns = self[Line(0)].len(); + self.initialize(growage, columns); // Update visible lines. self.visible_lines = next; @@ -90,10 +90,10 @@ impl<T> Storage<T> { /// Decrease the number of lines in the buffer. #[inline] - pub fn shrink_visible_lines(&mut self, next: Line) { + pub fn shrink_visible_lines(&mut self, next: usize) { // Shrink the size without removing any lines. let shrinkage = self.visible_lines - next; - self.shrink_lines(shrinkage.0); + self.shrink_lines(shrinkage); // Update visible lines. self.visible_lines = next; @@ -120,7 +120,7 @@ impl<T> Storage<T> { /// Dynamically grow the storage buffer at runtime. #[inline] - pub fn initialize(&mut self, additional_rows: usize, cols: Column) + pub fn initialize(&mut self, additional_rows: usize, columns: usize) where T: Clone + Default, { @@ -128,7 +128,7 @@ impl<T> Storage<T> { self.rezero(); let realloc_size = self.inner.len() + max(additional_rows, MAX_CACHE_SIZE); - self.inner.resize_with(realloc_size, || Row::new(cols)); + self.inner.resize_with(realloc_size, || Row::new(columns)); } self.len += additional_rows; @@ -139,14 +139,6 @@ impl<T> Storage<T> { self.len } - #[inline] - pub fn swap_lines(&mut self, a: Line, b: Line) { - let offset = self.inner.len() + self.zero + *self.visible_lines - 1; - let a = (offset - *a) % self.inner.len(); - let b = (offset - *b) % self.inner.len(); - self.inner.swap(a, b); - } - /// Swap implementation for Row<T>. /// /// Exploits the known size of Row<T> to produce a slightly more efficient @@ -155,7 +147,7 @@ impl<T> Storage<T> { /// The default implementation from swap generates 8 movups and 4 movaps /// instructions. This implementation achieves the swap in only 8 movups /// instructions. - pub fn swap(&mut self, a: usize, b: usize) { + pub fn swap(&mut self, a: Line, b: Line) { debug_assert_eq!(mem::size_of::<Row<T>>(), mem::size_of::<usize>() * 4); let a = self.compute_index(a); @@ -222,10 +214,14 @@ impl<T> Storage<T> { /// Compute actual index in underlying storage given the requested index. #[inline] - fn compute_index(&self, requested: usize) -> usize { - debug_assert!(requested < self.len); + fn compute_index(&self, requested: Line) -> usize { + debug_assert!(requested.0 < self.visible_lines as i32); + + let positive = -(requested - self.visible_lines).0 as usize - 1; + + debug_assert!(positive < self.len); - let zeroed = self.zero + requested; + let zeroed = self.zero + positive; // Use if/else instead of remainder here to improve performance. // @@ -250,38 +246,21 @@ impl<T> Storage<T> { } } -impl<T> Index<usize> for Storage<T> { - type Output = Row<T>; - - #[inline] - fn index(&self, index: usize) -> &Self::Output { - &self.inner[self.compute_index(index)] - } -} - -impl<T> IndexMut<usize> for Storage<T> { - #[inline] - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - let index = self.compute_index(index); // borrowck - &mut self.inner[index] - } -} - impl<T> Index<Line> for Storage<T> { type Output = Row<T>; #[inline] fn index(&self, index: Line) -> &Self::Output { - let index = self.visible_lines - 1 - index; - &self[*index] + let index = self.compute_index(index); + &self.inner[index] } } impl<T> IndexMut<Line> for Storage<T> { #[inline] fn index_mut(&mut self, index: Line) -> &mut Self::Output { - let index = self.visible_lines - 1 - index; - &mut self[*index] + let index = self.compute_index(index); + &mut self.inner[index] } } @@ -313,43 +292,39 @@ mod tests { #[test] fn with_capacity() { - let storage = Storage::<char>::with_capacity(Line(3), Column(1)); + let storage = Storage::<char>::with_capacity(3, 1); assert_eq!(storage.inner.len(), 3); assert_eq!(storage.len, 3); assert_eq!(storage.zero, 0); - assert_eq!(storage.visible_lines, Line(3)); + assert_eq!(storage.visible_lines, 3); } #[test] fn indexing() { - let mut storage = Storage::<char>::with_capacity(Line(3), Column(1)); - - storage[0] = filled_row('0'); - storage[1] = filled_row('1'); - storage[2] = filled_row('2'); + let mut storage = Storage::<char>::with_capacity(3, 1); - assert_eq!(storage[0], filled_row('0')); - assert_eq!(storage[1], filled_row('1')); - assert_eq!(storage[2], filled_row('2')); + storage[Line(0)] = filled_row('0'); + storage[Line(1)] = filled_row('1'); + storage[Line(2)] = filled_row('2'); storage.zero += 1; - assert_eq!(storage[0], filled_row('1')); - assert_eq!(storage[1], filled_row('2')); - assert_eq!(storage[2], filled_row('0')); + assert_eq!(storage[Line(0)], filled_row('2')); + assert_eq!(storage[Line(1)], filled_row('0')); + assert_eq!(storage[Line(2)], filled_row('1')); } #[test] #[should_panic] fn indexing_above_inner_len() { - let storage = Storage::<char>::with_capacity(Line(1), Column(1)); - let _ = &storage[2]; + let storage = Storage::<char>::with_capacity(1, 1); + let _ = &storage[Line(-1)]; } #[test] fn rotate() { - let mut storage = Storage::<char>::with_capacity(Line(3), Column(1)); + let mut storage = Storage::<char>::with_capacity(3, 1); storage.rotate(2); assert_eq!(storage.zero, 2); storage.shrink_lines(2); @@ -377,18 +352,18 @@ mod tests { let mut storage: Storage<char> = Storage { inner: vec![filled_row('0'), filled_row('1'), filled_row('-')], zero: 0, - visible_lines: Line(3), + visible_lines: 3, len: 3, }; // Grow buffer. - storage.grow_visible_lines(Line(4)); + storage.grow_visible_lines(4); // Make sure the result is correct. let mut expected = Storage { inner: vec![filled_row('0'), filled_row('1'), filled_row('-')], zero: 0, - visible_lines: Line(4), + visible_lines: 4, len: 4, }; expected.inner.append(&mut vec![filled_row('\0'); MAX_CACHE_SIZE]); @@ -418,18 +393,18 @@ mod tests { let mut storage: Storage<char> = Storage { inner: vec![filled_row('-'), filled_row('0'), filled_row('1')], zero: 1, - visible_lines: Line(3), + visible_lines: 3, len: 3, }; // Grow buffer. - storage.grow_visible_lines(Line(4)); + storage.grow_visible_lines(4); // Make sure the result is correct. let mut expected = Storage { inner: vec![filled_row('0'), filled_row('1'), filled_row('-')], zero: 0, - visible_lines: Line(4), + visible_lines: 4, len: 4, }; expected.inner.append(&mut vec![filled_row('\0'); MAX_CACHE_SIZE]); @@ -456,18 +431,18 @@ mod tests { let mut storage: Storage<char> = Storage { inner: vec![filled_row('2'), filled_row('0'), filled_row('1')], zero: 1, - visible_lines: Line(3), + visible_lines: 3, len: 3, }; // Shrink buffer. - storage.shrink_visible_lines(Line(2)); + storage.shrink_visible_lines(2); // Make sure the result is correct. let expected = Storage { inner: vec![filled_row('2'), filled_row('0'), filled_row('1')], zero: 1, - visible_lines: Line(2), + visible_lines: 2, len: 2, }; assert_eq!(storage.visible_lines, expected.visible_lines); @@ -492,18 +467,18 @@ mod tests { let mut storage: Storage<char> = Storage { inner: vec![filled_row('0'), filled_row('1'), filled_row('2')], zero: 0, - visible_lines: Line(3), + visible_lines: 3, len: 3, }; // Shrink buffer. - storage.shrink_visible_lines(Line(2)); + storage.shrink_visible_lines(2); // Make sure the result is correct. let expected = Storage { inner: vec![filled_row('0'), filled_row('1'), filled_row('2')], zero: 0, - visible_lines: Line(2), + visible_lines: 2, len: 2, }; assert_eq!(storage.visible_lines, expected.visible_lines); @@ -541,12 +516,12 @@ mod tests { filled_row('3'), ], zero: 2, - visible_lines: Line(6), + visible_lines: 6, len: 6, }; // Shrink buffer. - storage.shrink_visible_lines(Line(2)); + storage.shrink_visible_lines(2); // Make sure the result is correct. let expected = Storage { @@ -559,7 +534,7 @@ mod tests { filled_row('3'), ], zero: 2, - visible_lines: Line(2), + visible_lines: 2, len: 2, }; assert_eq!(storage.visible_lines, expected.visible_lines); @@ -593,7 +568,7 @@ mod tests { filled_row('3'), ], zero: 2, - visible_lines: Line(1), + visible_lines: 1, len: 2, }; @@ -604,7 +579,7 @@ mod tests { let expected = Storage { inner: vec![filled_row('0'), filled_row('1')], zero: 0, - visible_lines: Line(1), + visible_lines: 1, len: 2, }; assert_eq!(storage.visible_lines, expected.visible_lines); @@ -628,7 +603,7 @@ mod tests { let mut storage: Storage<char> = Storage { inner: vec![filled_row('1'), filled_row('2'), filled_row('0')], zero: 2, - visible_lines: Line(1), + visible_lines: 1, len: 2, }; @@ -639,7 +614,7 @@ mod tests { let expected = Storage { inner: vec![filled_row('0'), filled_row('1')], zero: 0, - visible_lines: Line(1), + visible_lines: 1, len: 2, }; assert_eq!(storage.visible_lines, expected.visible_lines); @@ -685,7 +660,7 @@ mod tests { filled_row('3'), ], zero: 2, - visible_lines: Line(0), + visible_lines: 0, len: 6, }; @@ -703,7 +678,7 @@ mod tests { filled_row('3'), ], zero: 2, - visible_lines: Line(0), + visible_lines: 0, len: 3, }; assert_eq!(storage.inner, shrinking_expected.inner); @@ -711,7 +686,7 @@ mod tests { assert_eq!(storage.len, shrinking_expected.len); // Grow buffer. - storage.initialize(1, Column(1)); + storage.initialize(1, 1); // Make sure the previously freed elements are reused. let growing_expected = Storage { @@ -724,7 +699,7 @@ mod tests { filled_row('3'), ], zero: 2, - visible_lines: Line(0), + visible_lines: 0, len: 4, }; @@ -746,13 +721,13 @@ mod tests { filled_row('3'), ], zero: 2, - visible_lines: Line(0), + visible_lines: 0, len: 6, }; // Initialize additional lines. let init_size = 3; - storage.initialize(init_size, Column(1)); + storage.initialize(init_size, 1); // Generate expected grid. let mut expected_inner = vec![ @@ -765,8 +740,7 @@ mod tests { ]; let expected_init_size = std::cmp::max(init_size, MAX_CACHE_SIZE); expected_inner.append(&mut vec![filled_row('\0'); expected_init_size]); - let expected_storage = - Storage { inner: expected_inner, zero: 0, visible_lines: Line(0), len: 9 }; + let expected_storage = Storage { inner: expected_inner, zero: 0, visible_lines: 0, len: 9 }; assert_eq!(storage.len, expected_storage.len); assert_eq!(storage.zero, expected_storage.zero); @@ -778,7 +752,7 @@ mod tests { let mut storage: Storage<char> = Storage { inner: vec![filled_row('-'), filled_row('-'), filled_row('-')], zero: 2, - visible_lines: Line(0), + visible_lines: 0, len: 3, }; @@ -788,7 +762,7 @@ mod tests { } fn filled_row(content: char) -> Row<char> { - let mut row = Row::new(Column(1)); + let mut row = Row::new(1); row[Column(0)] = content; row } diff --git a/alacritty_terminal/src/grid/tests.rs b/alacritty_terminal/src/grid/tests.rs index 269ab636..49cf1a76 100644 --- a/alacritty_terminal/src/grid/tests.rs +++ b/alacritty_terminal/src/grid/tests.rs @@ -22,49 +22,15 @@ impl GridCell for usize { } } -#[test] -fn grid_clamp_buffer_point() { - let mut grid = Grid::<usize>::new(Line(10), Column(10), 1_000); - grid.display_offset = 5; - - let point = grid.clamp_buffer_to_visible(Point::new(10, Column(3))); - assert_eq!(point, Point::new(Line(4), Column(3))); - - let point = grid.clamp_buffer_to_visible(Point::new(15, Column(3))); - assert_eq!(point, Point::new(Line(0), Column(0))); - - let point = grid.clamp_buffer_to_visible(Point::new(4, Column(3))); - assert_eq!(point, Point::new(Line(9), Column(9))); - - grid.display_offset = 0; - - let point = grid.clamp_buffer_to_visible(Point::new(4, Column(3))); - assert_eq!(point, Point::new(Line(5), Column(3))); -} - -#[test] -fn visible_to_buffer() { - let mut grid = Grid::<usize>::new(Line(10), Column(10), 1_000); - grid.display_offset = 5; - - let point = grid.visible_to_buffer(Point::new(Line(4), Column(3))); - assert_eq!(point, Point::new(10, Column(3))); - - grid.display_offset = 0; - - let point = grid.visible_to_buffer(Point::new(Line(5), Column(3))); - assert_eq!(point, Point::new(4, Column(3))); -} - // Scroll up moves lines upward. #[test] fn scroll_up() { - let mut grid = Grid::<usize>::new(Line(10), Column(1), 0); + let mut grid = Grid::<usize>::new(10, 1, 0); for i in 0..10 { - grid[Line(i)][Column(0)] = i; + grid[Line(i as i32)][Column(0)] = i; } - grid.scroll_up::<usize>(&(Line(0)..Line(10)), Line(2)); + grid.scroll_up::<usize>(&(Line(0)..Line(10)), 2); assert_eq!(grid[Line(0)][Column(0)], 2); assert_eq!(grid[Line(0)].occ, 1); @@ -91,12 +57,44 @@ fn scroll_up() { // Scroll down moves lines downward. #[test] fn scroll_down() { - let mut grid = Grid::<usize>::new(Line(10), Column(1), 0); + let mut grid = Grid::<usize>::new(10, 1, 0); + for i in 0..10 { + grid[Line(i as i32)][Column(0)] = i; + } + + grid.scroll_down::<usize>(&(Line(0)..Line(10)), 2); + + assert_eq!(grid[Line(0)][Column(0)], 0); // was 8. + assert_eq!(grid[Line(0)].occ, 0); + assert_eq!(grid[Line(1)][Column(0)], 0); // was 9. + assert_eq!(grid[Line(1)].occ, 0); + assert_eq!(grid[Line(2)][Column(0)], 0); + assert_eq!(grid[Line(2)].occ, 1); + assert_eq!(grid[Line(3)][Column(0)], 1); + assert_eq!(grid[Line(3)].occ, 1); + assert_eq!(grid[Line(4)][Column(0)], 2); + assert_eq!(grid[Line(4)].occ, 1); + assert_eq!(grid[Line(5)][Column(0)], 3); + assert_eq!(grid[Line(5)].occ, 1); + assert_eq!(grid[Line(6)][Column(0)], 4); + assert_eq!(grid[Line(6)].occ, 1); + assert_eq!(grid[Line(7)][Column(0)], 5); + assert_eq!(grid[Line(7)].occ, 1); + assert_eq!(grid[Line(8)][Column(0)], 6); + assert_eq!(grid[Line(8)].occ, 1); + assert_eq!(grid[Line(9)][Column(0)], 7); + assert_eq!(grid[Line(9)].occ, 1); +} + +#[test] +fn scroll_down_with_history() { + let mut grid = Grid::<usize>::new(10, 1, 1); + grid.increase_scroll_limit(1); for i in 0..10 { - grid[Line(i)][Column(0)] = i; + grid[Line(i as i32)][Column(0)] = i; } - grid.scroll_down::<usize>(&(Line(0)..Line(10)), Line(2)); + grid.scroll_down::<usize>(&(Line(0)..Line(10)), 2); assert_eq!(grid[Line(0)][Column(0)], 0); // was 8. assert_eq!(grid[Line(0)].occ, 0); @@ -127,19 +125,19 @@ fn test_iter() { assert_eq!(Some(&value), indexed.map(|indexed| indexed.cell)); }; - let mut grid = Grid::<usize>::new(Line(5), Column(5), 0); + let mut grid = Grid::<usize>::new(5, 5, 0); for i in 0..5 { for j in 0..5 { - grid[Line(i)][Column(j)] = i * 5 + j; + grid[Line(i)][Column(j)] = i as usize * 5 + j; } } - let mut iter = grid.iter_from(Point::new(4, Column(0))); + let mut iter = grid.iter_from(Point::new(Line(0), Column(0))); assert_eq!(None, iter.prev()); assert_indexed(1, iter.next()); assert_eq!(Column(1), iter.point().column); - assert_eq!(4, iter.point().line); + assert_eq!(0, iter.point().line); assert_indexed(2, iter.next()); assert_indexed(3, iter.next()); @@ -148,139 +146,139 @@ fn test_iter() { // Test line-wrapping. assert_indexed(5, iter.next()); assert_eq!(Column(0), iter.point().column); - assert_eq!(3, iter.point().line); + assert_eq!(1, iter.point().line); assert_indexed(4, iter.prev()); assert_eq!(Column(4), iter.point().column); - assert_eq!(4, iter.point().line); + assert_eq!(0, iter.point().line); // Make sure iter.cell() returns the current iterator position. assert_eq!(&4, iter.cell()); // Test that iter ends at end of grid. - let mut final_iter = grid.iter_from(Point { line: 0, column: Column(4) }); + let mut final_iter = grid.iter_from(Point { line: Line(4), column: Column(4) }); assert_eq!(None, final_iter.next()); assert_indexed(23, final_iter.prev()); } #[test] fn shrink_reflow() { - let mut grid = Grid::<Cell>::new(Line(1), Column(5), 2); + let mut grid = Grid::<Cell>::new(1, 5, 2); grid[Line(0)][Column(0)] = cell('1'); grid[Line(0)][Column(1)] = cell('2'); grid[Line(0)][Column(2)] = cell('3'); grid[Line(0)][Column(3)] = cell('4'); grid[Line(0)][Column(4)] = cell('5'); - grid.resize(true, Line(1), Column(2)); + grid.resize(true, 1, 2); assert_eq!(grid.total_lines(), 3); - assert_eq!(grid[2].len(), 2); - assert_eq!(grid[2][Column(0)], cell('1')); - assert_eq!(grid[2][Column(1)], wrap_cell('2')); + assert_eq!(grid[Line(-2)].len(), 2); + assert_eq!(grid[Line(-2)][Column(0)], cell('1')); + assert_eq!(grid[Line(-2)][Column(1)], wrap_cell('2')); - assert_eq!(grid[1].len(), 2); - assert_eq!(grid[1][Column(0)], cell('3')); - assert_eq!(grid[1][Column(1)], wrap_cell('4')); + assert_eq!(grid[Line(-1)].len(), 2); + assert_eq!(grid[Line(-1)][Column(0)], cell('3')); + assert_eq!(grid[Line(-1)][Column(1)], wrap_cell('4')); - assert_eq!(grid[0].len(), 2); - assert_eq!(grid[0][Column(0)], cell('5')); - assert_eq!(grid[0][Column(1)], Cell::default()); + assert_eq!(grid[Line(0)].len(), 2); + assert_eq!(grid[Line(0)][Column(0)], cell('5')); + assert_eq!(grid[Line(0)][Column(1)], Cell::default()); } #[test] fn shrink_reflow_twice() { - let mut grid = Grid::<Cell>::new(Line(1), Column(5), 2); + let mut grid = Grid::<Cell>::new(1, 5, 2); grid[Line(0)][Column(0)] = cell('1'); grid[Line(0)][Column(1)] = cell('2'); grid[Line(0)][Column(2)] = cell('3'); grid[Line(0)][Column(3)] = cell('4'); grid[Line(0)][Column(4)] = cell('5'); - grid.resize(true, Line(1), Column(4)); - grid.resize(true, Line(1), Column(2)); + grid.resize(true, 1, 4); + grid.resize(true, 1, 2); assert_eq!(grid.total_lines(), 3); - assert_eq!(grid[2].len(), 2); - assert_eq!(grid[2][Column(0)], cell('1')); - assert_eq!(grid[2][Column(1)], wrap_cell('2')); + assert_eq!(grid[Line(-2)].len(), 2); + assert_eq!(grid[Line(-2)][Column(0)], cell('1')); + assert_eq!(grid[Line(-2)][Column(1)], wrap_cell('2')); - assert_eq!(grid[1].len(), 2); - assert_eq!(grid[1][Column(0)], cell('3')); - assert_eq!(grid[1][Column(1)], wrap_cell('4')); + assert_eq!(grid[Line(-1)].len(), 2); + assert_eq!(grid[Line(-1)][Column(0)], cell('3')); + assert_eq!(grid[Line(-1)][Column(1)], wrap_cell('4')); - assert_eq!(grid[0].len(), 2); - assert_eq!(grid[0][Column(0)], cell('5')); - assert_eq!(grid[0][Column(1)], Cell::default()); + assert_eq!(grid[Line(0)].len(), 2); + assert_eq!(grid[Line(0)][Column(0)], cell('5')); + assert_eq!(grid[Line(0)][Column(1)], Cell::default()); } #[test] fn shrink_reflow_empty_cell_inside_line() { - let mut grid = Grid::<Cell>::new(Line(1), Column(5), 3); + let mut grid = Grid::<Cell>::new(1, 5, 3); grid[Line(0)][Column(0)] = cell('1'); grid[Line(0)][Column(1)] = Cell::default(); grid[Line(0)][Column(2)] = cell('3'); grid[Line(0)][Column(3)] = cell('4'); grid[Line(0)][Column(4)] = Cell::default(); - grid.resize(true, Line(1), Column(2)); + grid.resize(true, 1, 2); assert_eq!(grid.total_lines(), 2); - assert_eq!(grid[1].len(), 2); - assert_eq!(grid[1][Column(0)], cell('1')); - assert_eq!(grid[1][Column(1)], wrap_cell(' ')); + assert_eq!(grid[Line(-1)].len(), 2); + assert_eq!(grid[Line(-1)][Column(0)], cell('1')); + assert_eq!(grid[Line(-1)][Column(1)], wrap_cell(' ')); - assert_eq!(grid[0].len(), 2); - assert_eq!(grid[0][Column(0)], cell('3')); - assert_eq!(grid[0][Column(1)], cell('4')); + assert_eq!(grid[Line(0)].len(), 2); + assert_eq!(grid[Line(0)][Column(0)], cell('3')); + assert_eq!(grid[Line(0)][Column(1)], cell('4')); - grid.resize(true, Line(1), Column(1)); + grid.resize(true, 1, 1); assert_eq!(grid.total_lines(), 4); - assert_eq!(grid[3].len(), 1); - assert_eq!(grid[3][Column(0)], wrap_cell('1')); + assert_eq!(grid[Line(-3)].len(), 1); + assert_eq!(grid[Line(-3)][Column(0)], wrap_cell('1')); - assert_eq!(grid[2].len(), 1); - assert_eq!(grid[2][Column(0)], wrap_cell(' ')); + assert_eq!(grid[Line(-2)].len(), 1); + assert_eq!(grid[Line(-2)][Column(0)], wrap_cell(' ')); - assert_eq!(grid[1].len(), 1); - assert_eq!(grid[1][Column(0)], wrap_cell('3')); + assert_eq!(grid[Line(-1)].len(), 1); + assert_eq!(grid[Line(-1)][Column(0)], wrap_cell('3')); - assert_eq!(grid[0].len(), 1); - assert_eq!(grid[0][Column(0)], cell('4')); + assert_eq!(grid[Line(0)].len(), 1); + assert_eq!(grid[Line(0)][Column(0)], cell('4')); } #[test] fn grow_reflow() { - let mut grid = Grid::<Cell>::new(Line(2), Column(2), 0); + let mut grid = Grid::<Cell>::new(2, 2, 0); grid[Line(0)][Column(0)] = cell('1'); grid[Line(0)][Column(1)] = wrap_cell('2'); grid[Line(1)][Column(0)] = cell('3'); grid[Line(1)][Column(1)] = Cell::default(); - grid.resize(true, Line(2), Column(3)); + grid.resize(true, 2, 3); assert_eq!(grid.total_lines(), 2); - assert_eq!(grid[1].len(), 3); - assert_eq!(grid[1][Column(0)], cell('1')); - assert_eq!(grid[1][Column(1)], cell('2')); - assert_eq!(grid[1][Column(2)], cell('3')); + assert_eq!(grid[Line(0)].len(), 3); + assert_eq!(grid[Line(0)][Column(0)], cell('1')); + assert_eq!(grid[Line(0)][Column(1)], cell('2')); + assert_eq!(grid[Line(0)][Column(2)], cell('3')); // Make sure rest of grid is empty. - assert_eq!(grid[0].len(), 3); - assert_eq!(grid[0][Column(0)], Cell::default()); - assert_eq!(grid[0][Column(1)], Cell::default()); - assert_eq!(grid[0][Column(2)], Cell::default()); + assert_eq!(grid[Line(1)].len(), 3); + assert_eq!(grid[Line(1)][Column(0)], Cell::default()); + assert_eq!(grid[Line(1)][Column(1)], Cell::default()); + assert_eq!(grid[Line(1)][Column(2)], Cell::default()); } #[test] fn grow_reflow_multiline() { - let mut grid = Grid::<Cell>::new(Line(3), Column(2), 0); + let mut grid = Grid::<Cell>::new(3, 2, 0); grid[Line(0)][Column(0)] = cell('1'); grid[Line(0)][Column(1)] = wrap_cell('2'); grid[Line(1)][Column(0)] = cell('3'); @@ -288,20 +286,20 @@ fn grow_reflow_multiline() { grid[Line(2)][Column(0)] = cell('5'); grid[Line(2)][Column(1)] = cell('6'); - grid.resize(true, Line(3), Column(6)); + grid.resize(true, 3, 6); assert_eq!(grid.total_lines(), 3); - assert_eq!(grid[2].len(), 6); - assert_eq!(grid[2][Column(0)], cell('1')); - assert_eq!(grid[2][Column(1)], cell('2')); - assert_eq!(grid[2][Column(2)], cell('3')); - assert_eq!(grid[2][Column(3)], cell('4')); - assert_eq!(grid[2][Column(4)], cell('5')); - assert_eq!(grid[2][Column(5)], cell('6')); + assert_eq!(grid[Line(0)].len(), 6); + assert_eq!(grid[Line(0)][Column(0)], cell('1')); + assert_eq!(grid[Line(0)][Column(1)], cell('2')); + assert_eq!(grid[Line(0)][Column(2)], cell('3')); + assert_eq!(grid[Line(0)][Column(3)], cell('4')); + assert_eq!(grid[Line(0)][Column(4)], cell('5')); + assert_eq!(grid[Line(0)][Column(5)], cell('6')); // Make sure rest of grid is empty. - for r in 0..2 { + for r in (1..3).map(Line::from) { assert_eq!(grid[r].len(), 6); for c in 0..6 { assert_eq!(grid[r][Column(c)], Cell::default()); @@ -311,43 +309,43 @@ fn grow_reflow_multiline() { #[test] fn grow_reflow_disabled() { - let mut grid = Grid::<Cell>::new(Line(2), Column(2), 0); + let mut grid = Grid::<Cell>::new(2, 2, 0); grid[Line(0)][Column(0)] = cell('1'); grid[Line(0)][Column(1)] = wrap_cell('2'); grid[Line(1)][Column(0)] = cell('3'); grid[Line(1)][Column(1)] = Cell::default(); - grid.resize(false, Line(2), Column(3)); + grid.resize(false, 2, 3); assert_eq!(grid.total_lines(), 2); - assert_eq!(grid[1].len(), 3); - assert_eq!(grid[1][Column(0)], cell('1')); - assert_eq!(grid[1][Column(1)], wrap_cell('2')); - assert_eq!(grid[1][Column(2)], Cell::default()); + assert_eq!(grid[Line(0)].len(), 3); + assert_eq!(grid[Line(0)][Column(0)], cell('1')); + assert_eq!(grid[Line(0)][Column(1)], wrap_cell('2')); + assert_eq!(grid[Line(0)][Column(2)], Cell::default()); - assert_eq!(grid[0].len(), 3); - assert_eq!(grid[0][Column(0)], cell('3')); - assert_eq!(grid[0][Column(1)], Cell::default()); - assert_eq!(grid[0][Column(2)], Cell::default()); + assert_eq!(grid[Line(1)].len(), 3); + assert_eq!(grid[Line(1)][Column(0)], cell('3')); + assert_eq!(grid[Line(1)][Column(1)], Cell::default()); + assert_eq!(grid[Line(1)][Column(2)], Cell::default()); } #[test] fn shrink_reflow_disabled() { - let mut grid = Grid::<Cell>::new(Line(1), Column(5), 2); + let mut grid = Grid::<Cell>::new(1, 5, 2); grid[Line(0)][Column(0)] = cell('1'); grid[Line(0)][Column(1)] = cell('2'); grid[Line(0)][Column(2)] = cell('3'); grid[Line(0)][Column(3)] = cell('4'); grid[Line(0)][Column(4)] = cell('5'); - grid.resize(false, Line(1), Column(2)); + grid.resize(false, 1, 2); assert_eq!(grid.total_lines(), 1); - assert_eq!(grid[0].len(), 2); - assert_eq!(grid[0][Column(0)], cell('1')); - assert_eq!(grid[0][Column(1)], cell('2')); + assert_eq!(grid[Line(0)].len(), 2); + assert_eq!(grid[Line(0)][Column(0)], cell('1')); + assert_eq!(grid[Line(0)][Column(1)], cell('2')); } // https://github.com/rust-lang/rust-clippy/pull/6375 diff --git a/alacritty_terminal/src/index.rs b/alacritty_terminal/src/index.rs index e8e52c80..29b3eb15 100644 --- a/alacritty_terminal/src/index.rs +++ b/alacritty_terminal/src/index.rs @@ -1,9 +1,9 @@ //! Line and Column newtypes for strongly typed tty/grid/terminal APIs. /// Indexing types and implementations for Grid and Line. -use std::cmp::{Ord, Ordering}; +use std::cmp::{max, min, Ord, Ordering}; use std::fmt; -use std::ops::{self, Add, AddAssign, Deref, Range, Sub, SubAssign}; +use std::ops::{Add, AddAssign, Deref, Sub, SubAssign}; use serde::{Deserialize, Serialize}; @@ -28,176 +28,192 @@ impl Direction { } } -/// Behavior for handling grid boundaries. +/// Terminal grid boundaries. pub enum Boundary { - /// Clamp to grid boundaries. + /// Cursor's range of motion in the grid. /// - /// When an operation exceeds the grid boundaries, the last point will be returned no matter - /// how far the boundaries were exceeded. - Clamp, + /// This is equal to the viewport when the user isn't scrolled into the history. + Cursor, - /// Wrap around grid bondaries. - /// - /// When an operation exceeds the grid boundaries, the point will wrap around the entire grid - /// history and continue at the other side. - Wrap, + /// Topmost line in history until the bottommost line in the terminal. + Grid, + + /// Unbounded. + None, } /// Index in the grid using row, column notation. #[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, Eq, PartialEq)] -pub struct Point<L = Line> { +pub struct Point<L = Line, C = Column> { pub line: L, - pub column: Column, + pub column: C, } -impl<L> Point<L> { - pub fn new(line: L, col: Column) -> Point<L> { - Point { line, column: col } - } - - #[inline] - #[must_use = "this returns the result of the operation, without modifying the original"] - pub fn sub(mut self, num_cols: Column, rhs: usize) -> Point<L> - where - L: Copy + Default + Into<Line> + Add<usize, Output = L> + Sub<usize, Output = L>, - { - let num_cols = num_cols.0; - let line_changes = (rhs + num_cols - 1).saturating_sub(self.column.0) / num_cols; - if self.line.into() >= Line(line_changes) { - self.line = self.line - line_changes; - self.column = Column((num_cols + self.column.0 - rhs % num_cols) % num_cols); - self - } else { - Point::new(L::default(), Column(0)) - } +impl<L, C> Point<L, C> { + pub fn new(line: L, column: C) -> Point<L, C> { + Point { line, column } } +} +impl Point { + /// Subtract a number of columns from a point. #[inline] #[must_use = "this returns the result of the operation, without modifying the original"] - pub fn add(mut self, num_cols: Column, rhs: usize) -> Point<L> + pub fn sub<D>(mut self, dimensions: &D, boundary: Boundary, rhs: usize) -> Self where - L: Copy + Default + Into<Line> + Add<usize, Output = L> + Sub<usize, Output = L>, + D: Dimensions, { - let num_cols = num_cols.0; - self.line = self.line + (rhs + self.column.0) / num_cols; - self.column = Column((self.column.0 + rhs) % num_cols); - self + let cols = dimensions.columns(); + let line_changes = (rhs + cols - 1).saturating_sub(self.column.0) / cols; + self.line -= line_changes; + self.column = Column((cols + self.column.0 - rhs % cols) % cols); + self.grid_clamp(dimensions, boundary) } -} -impl Point<usize> { + /// Add a number of columns to a point. #[inline] #[must_use = "this returns the result of the operation, without modifying the original"] - pub fn sub_absolute<D>(mut self, dimensions: &D, boundary: Boundary, rhs: usize) -> Point<usize> + pub fn add<D>(mut self, dimensions: &D, boundary: Boundary, rhs: usize) -> Self where D: Dimensions, { - let total_lines = dimensions.total_lines(); - let num_cols = dimensions.cols().0; - - self.line += (rhs + num_cols - 1).saturating_sub(self.column.0) / num_cols; - self.column = Column((num_cols + self.column.0 - rhs % num_cols) % num_cols); - - if self.line >= total_lines { - match boundary { - Boundary::Clamp => Point::new(total_lines - 1, Column(0)), - Boundary::Wrap => Point::new(self.line - total_lines, self.column), - } - } else { - self - } + let cols = dimensions.columns(); + self.line += (rhs + self.column.0) / cols; + self.column = Column((self.column.0 + rhs) % cols); + self.grid_clamp(dimensions, boundary) } + /// Clamp a point to a grid boundary. #[inline] #[must_use = "this returns the result of the operation, without modifying the original"] - pub fn add_absolute<D>(mut self, dimensions: &D, boundary: Boundary, rhs: usize) -> Point<usize> + pub fn grid_clamp<D>(mut self, dimensions: &D, boundary: Boundary) -> Self where D: Dimensions, { - let num_cols = dimensions.cols(); - - let line_delta = (rhs + self.column.0) / num_cols.0; - - if self.line >= line_delta { - self.line -= line_delta; - self.column = Column((self.column.0 + rhs) % num_cols.0); - self - } else { - match boundary { - Boundary::Clamp => Point::new(0, num_cols - 1), - Boundary::Wrap => { - let col = Column((self.column.0 + rhs) % num_cols.0); - let line = dimensions.total_lines() + self.line - line_delta; - Point::new(line, col) - }, - } + let last_column = dimensions.last_column(); + self.column = min(self.column, last_column); + + let topmost_line = dimensions.topmost_line(); + let bottommost_line = dimensions.bottommost_line(); + + match boundary { + Boundary::Cursor if self.line < 0 => Point::new(Line(0), Column(0)), + Boundary::Grid if self.line < topmost_line => Point::new(topmost_line, Column(0)), + Boundary::Cursor | Boundary::Grid if self.line > bottommost_line => { + Point::new(bottommost_line, last_column) + }, + Boundary::None => { + self.line = self.line.grid_clamp(dimensions, boundary); + self + }, + _ => self, } } } -impl PartialOrd for Point { - fn partial_cmp(&self, other: &Point) -> Option<Ordering> { +impl<L: Ord, C: Ord> PartialOrd for Point<L, C> { + fn partial_cmp(&self, other: &Point<L, C>) -> Option<Ordering> { Some(self.cmp(other)) } } -impl Ord for Point { - fn cmp(&self, other: &Point) -> Ordering { +impl<L: Ord, C: Ord> Ord for Point<L, C> { + fn cmp(&self, other: &Point<L, C>) -> Ordering { match (self.line.cmp(&other.line), self.column.cmp(&other.column)) { (Ordering::Equal, ord) | (ord, _) => ord, } } } -impl PartialOrd for Point<usize> { - fn partial_cmp(&self, other: &Point<usize>) -> Option<Ordering> { - Some(self.cmp(other)) +/// A line. +/// +/// Newtype to avoid passing values incorrectly. +#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd)] +pub struct Line(pub i32); + +impl Line { + /// Clamp a line to a grid boundary. + pub fn grid_clamp<D: Dimensions>(self, dimensions: &D, boundary: Boundary) -> Self { + match boundary { + Boundary::Cursor => max(Line(0), min(dimensions.bottommost_line(), self)), + Boundary::Grid => { + let bottommost_line = dimensions.bottommost_line(); + let topmost_line = dimensions.topmost_line(); + max(topmost_line, min(bottommost_line, self)) + }, + Boundary::None => { + let screen_lines = dimensions.screen_lines() as i32; + let total_lines = dimensions.total_lines() as i32; + + if self >= screen_lines { + let topmost_line = dimensions.topmost_line(); + let extra = (self.0 - screen_lines) % total_lines; + topmost_line + extra + } else { + let bottommost_line = dimensions.bottommost_line(); + let extra = (self.0 - screen_lines + 1) % total_lines; + bottommost_line + extra + } + }, + } } } -impl Ord for Point<usize> { - fn cmp(&self, other: &Point<usize>) -> Ordering { - match (self.line.cmp(&other.line), self.column.cmp(&other.column)) { - (Ordering::Equal, ord) => ord, - (Ordering::Less, _) => Ordering::Greater, - (Ordering::Greater, _) => Ordering::Less, - } +impl fmt::Display for Line { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) } } -impl From<Point<usize>> for Point<isize> { - fn from(point: Point<usize>) -> Self { - Point::new(point.line as isize, point.column) +impl From<usize> for Line { + fn from(source: usize) -> Self { + Self(source as i32) } } -impl From<Point<usize>> for Point<Line> { - fn from(point: Point<usize>) -> Self { - Point::new(Line(point.line), point.column) +impl Add<usize> for Line { + type Output = Line; + + #[inline] + fn add(self, rhs: usize) -> Line { + self + rhs as i32 } } -impl From<Point<isize>> for Point<usize> { - fn from(point: Point<isize>) -> Self { - Point::new(point.line as usize, point.column) +impl AddAssign<usize> for Line { + #[inline] + fn add_assign(&mut self, rhs: usize) { + *self += rhs as i32; } } -impl From<Point> for Point<usize> { - fn from(point: Point) -> Self { - Point::new(point.line.0, point.column) +impl Sub<usize> for Line { + type Output = Line; + + #[inline] + fn sub(self, rhs: usize) -> Line { + self - rhs as i32 } } -/// A line. -/// -/// Newtype to avoid passing values incorrectly. -#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd)] -pub struct Line(pub usize); +impl SubAssign<usize> for Line { + #[inline] + fn sub_assign(&mut self, rhs: usize) { + *self -= rhs as i32; + } +} -impl fmt::Display for Line { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) +impl PartialOrd<usize> for Line { + #[inline] + fn partial_cmp(&self, other: &usize) -> Option<Ordering> { + self.0.partial_cmp(&(*other as i32)) + } +} + +impl PartialEq<usize> for Line { + #[inline] + fn eq(&self, other: &usize) -> bool { + self.0.eq(&(*other as i32)) } } @@ -213,66 +229,25 @@ impl fmt::Display for Column { } } -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. -// -// implements binary operators "&T op U", "T op &U", "&T op &U" -// based on "T op U" where T and U are expected to be `Copy`able -macro_rules! forward_ref_binop { - (impl $imp:ident, $method:ident for $t:ty, $u:ty) => { - impl<'a> $imp<$u> for &'a $t { - type Output = <$t as $imp<$u>>::Output; - - #[inline] - fn $method(self, other: $u) -> <$t as $imp<$u>>::Output { - $imp::$method(*self, other) - } - } - - impl<'a> $imp<&'a $u> for $t { - type Output = <$t as $imp<$u>>::Output; +macro_rules! ops { + ($ty:ty, $construct:expr, $primitive:ty) => { + impl Deref for $ty { + type Target = $primitive; #[inline] - fn $method(self, other: &'a $u) -> <$t as $imp<$u>>::Output { - $imp::$method(self, *other) + fn deref(&self) -> &$primitive { + &self.0 } } - impl<'a, 'b> $imp<&'a $u> for &'b $t { - type Output = <$t as $imp<$u>>::Output; - + impl From<$primitive> for $ty { #[inline] - fn $method(self, other: &'a $u) -> <$t as $imp<$u>>::Output { - $imp::$method(*self, *other) - } - } - }; -} - -/// Macro for deriving deref. -macro_rules! deref { - ($ty:ty, $target:ty) => { - impl Deref for $ty { - type Target = $target; - - #[inline] - fn deref(&self) -> &$target { - &self.0 + fn from(val: $primitive) -> $ty { + $construct(val) } } - }; -} -macro_rules! add { - ($ty:ty, $construct:expr) => { - impl ops::Add<$ty> for $ty { + impl Add<$ty> for $ty { type Output = $ty; #[inline] @@ -280,182 +255,94 @@ macro_rules! add { $construct(self.0 + rhs.0) } } - }; -} - -macro_rules! sub { - ($ty:ty, $construct:expr) => { - impl ops::Sub<$ty> for $ty { - type Output = $ty; + impl AddAssign<$ty> for $ty { #[inline] - fn sub(self, rhs: $ty) -> $ty { - $construct(self.0 - rhs.0) + fn add_assign(&mut self, rhs: $ty) { + self.0 += rhs.0; } } - impl<'a> ops::Sub<$ty> for &'a $ty { + impl Add<$primitive> for $ty { type Output = $ty; #[inline] - fn sub(self, rhs: $ty) -> $ty { - $construct(self.0 - rhs.0) + fn add(self, rhs: $primitive) -> $ty { + $construct(self.0 + rhs) } } - impl<'a> ops::Sub<&'a $ty> for $ty { - type Output = $ty; - + impl AddAssign<$primitive> for $ty { #[inline] - fn sub(self, rhs: &'a $ty) -> $ty { - $construct(self.0 - rhs.0) + fn add_assign(&mut self, rhs: $primitive) { + self.0 += rhs } } - impl<'a, 'b> ops::Sub<&'a $ty> for &'b $ty { + impl Sub<$ty> for $ty { type Output = $ty; #[inline] - fn sub(self, rhs: &'a $ty) -> $ty { + fn sub(self, rhs: $ty) -> $ty { $construct(self.0 - rhs.0) } } - }; -} - -/// This exists because we can't implement Iterator on Range -/// and the existing impl needs the unstable Step trait -/// This should be removed and replaced with a Step impl -/// in the ops macro when `step_by` is stabilized. -pub struct IndexRange<T>(pub Range<T>); - -impl<T> From<Range<T>> for IndexRange<T> { - fn from(from: Range<T>) -> Self { - IndexRange(from) - } -} - -macro_rules! ops { - ($ty:ty, $construct:expr) => { - add!($ty, $construct); - sub!($ty, $construct); - deref!($ty, usize); - forward_ref_binop!(impl Add, add for $ty, $ty); - - impl $ty { - #[inline] - fn steps_between(start: $ty, end: $ty, by: $ty) -> Option<usize> { - if by == $construct(0) { return None; } - if start < end { - // Note: We assume $t <= usize here. - let diff = (end - start).0; - let by = by.0; - if diff % by > 0 { - Some(diff / by + 1) - } else { - Some(diff / by) - } - } else { - Some(0) - } - } - - #[inline] - fn steps_between_by_one(start: $ty, end: $ty) -> Option<usize> { - Self::steps_between(start, end, $construct(1)) - } - } - impl Iterator for IndexRange<$ty> { - type Item = $ty; - #[inline] - fn next(&mut self) -> Option<$ty> { - if self.0.start < self.0.end { - let old = self.0.start; - self.0.start = old + 1; - Some(old) - } else { - None - } - } + impl SubAssign<$ty> for $ty { #[inline] - fn size_hint(&self) -> (usize, Option<usize>) { - match Self::Item::steps_between_by_one(self.0.start, self.0.end) { - Some(hint) => (hint, Some(hint)), - None => (0, None) - } + fn sub_assign(&mut self, rhs: $ty) { + self.0 -= rhs.0; } } - impl DoubleEndedIterator for IndexRange<$ty> { - #[inline] - fn next_back(&mut self) -> Option<$ty> { - if self.0.start < self.0.end { - let new = self.0.end - 1; - self.0.end = new; - Some(new) - } else { - None - } - } - } - impl AddAssign<$ty> for $ty { - #[inline] - fn add_assign(&mut self, rhs: $ty) { - self.0 += rhs.0 - } - } + impl Sub<$primitive> for $ty { + type Output = $ty; - impl SubAssign<$ty> for $ty { #[inline] - fn sub_assign(&mut self, rhs: $ty) { - self.0 -= rhs.0 + fn sub(self, rhs: $primitive) -> $ty { + $construct(self.0 - rhs) } } - impl AddAssign<usize> for $ty { + impl SubAssign<$primitive> for $ty { #[inline] - fn add_assign(&mut self, rhs: usize) { - self.0 += rhs + fn sub_assign(&mut self, rhs: $primitive) { + self.0 -= rhs } } - impl SubAssign<usize> for $ty { + impl PartialEq<$ty> for $primitive { #[inline] - fn sub_assign(&mut self, rhs: usize) { - self.0 -= rhs + fn eq(&self, other: &$ty) -> bool { + self.eq(&other.0) } } - impl From<usize> for $ty { + impl PartialEq<$primitive> for $ty { #[inline] - fn from(val: usize) -> $ty { - $construct(val) + fn eq(&self, other: &$primitive) -> bool { + self.0.eq(other) } } - impl Add<usize> for $ty { - type Output = $ty; - + impl PartialOrd<$ty> for $primitive { #[inline] - fn add(self, rhs: usize) -> $ty { - $construct(self.0 + rhs) + fn partial_cmp(&self, other: &$ty) -> Option<Ordering> { + self.partial_cmp(&other.0) } } - impl Sub<usize> for $ty { - type Output = $ty; - + impl PartialOrd<$primitive> for $ty { #[inline] - fn sub(self, rhs: usize) -> $ty { - $construct(self.0 - rhs) + fn partial_cmp(&self, other: &$primitive) -> Option<Ordering> { + self.0.partial_cmp(other) } } - } + }; } -ops!(Line, Line); -ops!(Column, Column); +ops!(Column, Column, usize); +ops!(Line, Line, i32); #[cfg(test)] mod tests { @@ -469,154 +356,106 @@ mod tests { assert!(Point::new(Line(1), Column(1)) > Point::new(Line(0), Column(0))); assert!(Point::new(Line(1), Column(1)) > Point::new(Line(0), Column(1))); assert!(Point::new(Line(1), Column(1)) > Point::new(Line(1), Column(0))); + assert!(Point::new(Line(0), Column(0)) > Point::new(Line(-1), Column(0))); } #[test] fn sub() { - let num_cols = Column(42); - let point = Point::new(0, Column(13)); + let size = (10, 42); + let point = Point::new(Line(0), Column(13)); - let result = point.sub(num_cols, 1); + let result = point.sub(&size, Boundary::Cursor, 1); - assert_eq!(result, Point::new(0, point.column - 1)); + assert_eq!(result, Point::new(Line(0), point.column - 1)); } #[test] fn sub_wrap() { - let num_cols = Column(42); - let point = Point::new(1, Column(0)); + let size = (10, 42); + let point = Point::new(Line(1), Column(0)); - let result = point.sub(num_cols, 1); + let result = point.sub(&size, Boundary::Cursor, 1); - assert_eq!(result, Point::new(0, num_cols - 1)); + assert_eq!(result, Point::new(Line(0), size.last_column())); } #[test] fn sub_clamp() { - let num_cols = Column(42); - let point = Point::new(0, Column(0)); + let size = (10, 42); + let point = Point::new(Line(0), Column(0)); - let result = point.sub(num_cols, 1); + let result = point.sub(&size, Boundary::Cursor, 1); assert_eq!(result, point); } #[test] - fn add() { - let num_cols = Column(42); - let point = Point::new(0, Column(13)); - - let result = point.add(num_cols, 1); - - assert_eq!(result, Point::new(0, point.column + 1)); - } - - #[test] - fn add_wrap() { - let num_cols = Column(42); - let point = Point::new(0, num_cols - 1); + fn sub_grid_clamp() { + let size = (0, 42); + let point = Point::new(Line(0), Column(0)); - let result = point.add(num_cols, 1); - - assert_eq!(result, Point::new(1, Column(0))); - } - - #[test] - fn add_absolute() { - let point = Point::new(0, Column(13)); - - let result = point.add_absolute(&(Line(1), Column(42)), Boundary::Clamp, 1); - - assert_eq!(result, Point::new(0, point.column + 1)); - } - - #[test] - fn add_absolute_wrapline() { - let point = Point::new(1, Column(41)); - - let result = point.add_absolute(&(Line(2), Column(42)), Boundary::Clamp, 1); - - assert_eq!(result, Point::new(0, Column(0))); - } - - #[test] - fn add_absolute_multiline_wrapline() { - let point = Point::new(2, Column(9)); - - let result = point.add_absolute(&(Line(3), Column(10)), Boundary::Clamp, 11); - - assert_eq!(result, Point::new(0, Column(0))); - } - - #[test] - fn add_absolute_clamp() { - let point = Point::new(0, Column(41)); - - let result = point.add_absolute(&(Line(1), Column(42)), Boundary::Clamp, 1); + let result = point.sub(&size, Boundary::Grid, 1); assert_eq!(result, point); } #[test] - fn add_absolute_wrap() { - let point = Point::new(0, Column(41)); - - let result = point.add_absolute(&(Line(3), Column(42)), Boundary::Wrap, 1); - - assert_eq!(result, Point::new(2, Column(0))); - } - - #[test] - fn add_absolute_multiline_wrap() { - let point = Point::new(0, Column(9)); + fn sub_none_clamp() { + let size = (10, 42); + let point = Point::new(Line(0), Column(0)); - let result = point.add_absolute(&(Line(3), Column(10)), Boundary::Wrap, 11); + let result = point.sub(&size, Boundary::None, 1); - assert_eq!(result, Point::new(1, Column(0))); + assert_eq!(result, Point::new(Line(9), Column(41))); } #[test] - fn sub_absolute() { - let point = Point::new(0, Column(13)); + fn add() { + let size = (10, 42); + let point = Point::new(Line(0), Column(13)); - let result = point.sub_absolute(&(Line(1), Column(42)), Boundary::Clamp, 1); + let result = point.add(&size, Boundary::Cursor, 1); - assert_eq!(result, Point::new(0, point.column - 1)); + assert_eq!(result, Point::new(Line(0), point.column + 1)); } #[test] - fn sub_absolute_wrapline() { - let point = Point::new(0, Column(0)); + fn add_wrap() { + let size = (10, 42); + let point = Point::new(Line(0), size.last_column()); - let result = point.sub_absolute(&(Line(2), Column(42)), Boundary::Clamp, 1); + let result = point.add(&size, Boundary::Cursor, 1); - assert_eq!(result, Point::new(1, Column(41))); + assert_eq!(result, Point::new(Line(1), Column(0))); } #[test] - fn sub_absolute_multiline_wrapline() { - let point = Point::new(0, Column(0)); + fn add_clamp() { + let size = (10, 42); + let point = Point::new(Line(9), Column(41)); - let result = point.sub_absolute(&(Line(3), Column(10)), Boundary::Clamp, 11); + let result = point.add(&size, Boundary::Cursor, 1); - assert_eq!(result, Point::new(2, Column(9))); + assert_eq!(result, point); } #[test] - fn sub_absolute_wrap() { - let point = Point::new(2, Column(0)); + fn add_grid_clamp() { + let size = (10, 42); + let point = Point::new(Line(9), Column(41)); - let result = point.sub_absolute(&(Line(3), Column(42)), Boundary::Wrap, 1); + let result = point.add(&size, Boundary::Grid, 1); - assert_eq!(result, Point::new(0, Column(41))); + assert_eq!(result, point); } #[test] - fn sub_absolute_multiline_wrap() { - let point = Point::new(2, Column(0)); + fn add_none_clamp() { + let size = (10, 42); + let point = Point::new(Line(9), Column(41)); - let result = point.sub_absolute(&(Line(3), Column(10)), Boundary::Wrap, 11); + let result = point.add(&size, Boundary::None, 1); - assert_eq!(result, Point::new(1, Column(9))); + assert_eq!(result, Point::new(Line(0), Column(0))); } } diff --git a/alacritty_terminal/src/selection.rs b/alacritty_terminal/src/selection.rs index afb98c0d..95c19be4 100644 --- a/alacritty_terminal/src/selection.rs +++ b/alacritty_terminal/src/selection.rs @@ -5,7 +5,7 @@ //! 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::convert::TryFrom; +use std::cmp::min; use std::mem; use std::ops::{Bound, Range, RangeBounds}; @@ -18,34 +18,34 @@ use crate::term::{RenderableCursor, Term}; /// A Point and side within that point. #[derive(Debug, Copy, Clone, PartialEq)] struct Anchor { - point: Point<usize>, + point: Point, side: Side, } impl Anchor { - fn new(point: Point<usize>, side: Side) -> Anchor { + fn new(point: Point, side: Side) -> Anchor { Anchor { point, side } } } /// Represents a range of selected cells. #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct SelectionRange<L = usize> { +pub struct SelectionRange { /// Start point, top left of the selection. - pub start: Point<L>, + pub start: Point, /// End point, bottom right of the selection. - pub end: Point<L>, + pub end: Point, /// Whether this selection is a block selection. pub is_block: bool, } -impl<L> SelectionRange<L> { - pub fn new(start: Point<L>, end: Point<L>, is_block: bool) -> Self { +impl SelectionRange { + pub fn new(start: Point, end: Point, is_block: bool) -> Self { Self { start, end, is_block } } } -impl SelectionRange<Line> { +impl SelectionRange { /// Check if a point lies within the selection. pub fn contains(&self, point: Point) -> bool { self.start.line <= point.line @@ -56,7 +56,7 @@ impl SelectionRange<Line> { } /// Check if the cell at a point is part of the selection. - pub fn contains_cell(&self, indexed: &Indexed<&Cell, Line>, cursor: RenderableCursor) -> bool { + pub fn contains_cell(&self, indexed: &Indexed<&Cell>, cursor: RenderableCursor) -> bool { // Do not invert block cursor at selection boundaries. if cursor.shape == CursorShape::Block && cursor.point == indexed.point @@ -116,7 +116,7 @@ pub struct Selection { } impl Selection { - pub fn new(ty: SelectionType, location: Point<usize>, side: Side) -> Selection { + pub fn new(ty: SelectionType, location: Point, side: Side) -> Selection { Self { region: Range { start: Anchor::new(location, side), end: Anchor::new(location, side) }, ty, @@ -124,7 +124,7 @@ impl Selection { } /// Update the end of the selection. - pub fn update(&mut self, point: Point<usize>, side: Side) { + pub fn update(&mut self, point: Point, side: Side) { self.region.end = Anchor::new(point, side); } @@ -132,56 +132,52 @@ impl Selection { mut self, dimensions: &D, range: &Range<Line>, - delta: isize, + delta: i32, ) -> Option<Selection> { - let num_lines = dimensions.screen_lines().0; - let num_cols = dimensions.cols().0; - let range_bottom = range.start.0; - let range_top = range.end.0; + let bottommost_line = dimensions.bottommost_line(); + let range_bottom = range.end; + let range_top = range.start; let (mut start, mut end) = (&mut self.region.start, &mut self.region.end); - if Selection::points_need_swap(start.point, end.point) { + if start.point > end.point { mem::swap(&mut start, &mut end); } // Rotate start of selection. - if (start.point.line < range_top || range_top == num_lines) - && start.point.line >= range_bottom - { - start.point.line = usize::try_from(start.point.line as isize + delta).unwrap_or(0); + if (start.point.line >= range_top || range_top == 0) && start.point.line < range_bottom { + start.point.line = min(start.point.line - delta, bottommost_line); // If end is within the same region, delete selection once start rotates out. - if start.point.line < range_bottom && end.point.line >= range_bottom { + if start.point.line >= range_bottom && end.point.line < range_bottom { return None; } // Clamp selection to start of region. - if start.point.line >= range_top && range_top != num_lines { + if start.point.line < range_top && range_top != 0 { if self.ty != SelectionType::Block { start.point.column = Column(0); start.side = Side::Left; } - start.point.line = range_top - 1; + start.point.line = range_top; } } // Rotate end of selection. - if (end.point.line < range_top || range_top == num_lines) && end.point.line >= range_bottom - { - end.point.line = usize::try_from(end.point.line as isize + delta).unwrap_or(0); + if (end.point.line >= range_top || range_top == 0) && end.point.line < range_bottom { + end.point.line = min(end.point.line - delta, bottommost_line); // Delete selection if end has overtaken the start. - if end.point.line > start.point.line { + if end.point.line < start.point.line { return None; } // Clamp selection to end of region. - if end.point.line < range_bottom { + if end.point.line >= range_bottom { if self.ty != SelectionType::Block { - end.point.column = Column(num_cols - 1); + end.point.column = dimensions.last_column(); end.side = Side::Right; } - end.point.line = range_bottom; + end.point.line = range_bottom - 1; } } @@ -192,7 +188,7 @@ impl Selection { match self.ty { SelectionType::Simple => { let (mut start, mut end) = (self.region.start, self.region.end); - if Self::points_need_swap(start.point, end.point) { + if start.point > end.point { mem::swap(&mut start, &mut end); } @@ -223,27 +219,27 @@ impl Selection { } /// Check whether selection contains any point in a given range. - pub fn intersects_range<R: RangeBounds<usize>>(&self, range: R) -> bool { + pub fn intersects_range<R: RangeBounds<Line>>(&self, range: R) -> bool { let mut start = self.region.start.point.line; let mut end = self.region.end.point.line; - if Self::points_need_swap(self.region.start.point, self.region.end.point) { + if start > end { mem::swap(&mut start, &mut end); } - let range_start = match range.start_bound() { + let range_top = match range.start_bound() { Bound::Included(&range_start) => range_start, - Bound::Excluded(&range_start) => range_start.saturating_add(1), - Bound::Unbounded => 0, + Bound::Excluded(&range_start) => range_start + 1, + Bound::Unbounded => Line(i32::min_value()), }; - let range_end = match range.end_bound() { + let range_bottom = match range.end_bound() { Bound::Included(&range_end) => range_end, - Bound::Excluded(&range_end) => range_end.saturating_sub(1), - Bound::Unbounded => usize::max_value(), + Bound::Excluded(&range_end) => range_end - 1, + Bound::Unbounded => Line(i32::max_value()), }; - range_start <= start && range_end >= end + range_bottom >= start && range_top <= end } /// Expand selection sides to include all cells. @@ -257,7 +253,7 @@ impl Selection { (Side::Right, Side::Left) }, SelectionType::Block => (Side::Left, Side::Right), - _ if Self::points_need_swap(start, end) => (Side::Right, Side::Left), + _ if start > end => (Side::Right, Side::Left), _ => (Side::Left, Side::Right), }; @@ -268,63 +264,25 @@ impl Selection { /// Convert selection to grid coordinates. pub fn to_range<T>(&self, term: &Term<T>) -> Option<SelectionRange> { let grid = term.grid(); - let num_cols = grid.cols(); + let columns = grid.columns(); // Order start above the end. let mut start = self.region.start; let mut end = self.region.end; - if Self::points_need_swap(start.point, end.point) { + if start.point > end.point { mem::swap(&mut start, &mut end); } - // Clamp to inside the grid buffer. - let is_block = self.ty == SelectionType::Block; - let (start, end) = Self::grid_clamp(start, end, is_block, grid.total_lines()).ok()?; - match self.ty { - SelectionType::Simple => self.range_simple(start, end, num_cols), + SelectionType::Simple => self.range_simple(start, end, columns), SelectionType::Block => self.range_block(start, end), SelectionType::Semantic => Some(Self::range_semantic(term, start.point, end.point)), SelectionType::Lines => Some(Self::range_lines(term, start.point, end.point)), } } - /// Bring start and end points in the correct order. - fn points_need_swap(start: Point<usize>, end: Point<usize>) -> bool { - start.line < end.line || start.line == end.line && start.column > end.column - } - - /// Clamp selection inside grid to prevent OOB. - fn grid_clamp( - mut start: Anchor, - end: Anchor, - is_block: bool, - lines: usize, - ) -> Result<(Anchor, Anchor), ()> { - // Clamp selection inside of grid to prevent OOB. - if start.point.line >= lines { - // Remove selection if it is fully out of the grid. - if end.point.line >= lines { - return Err(()); - } - - // Clamp to grid if it is still partially visible. - if !is_block { - start.side = Side::Left; - start.point.column = Column(0); - } - start.point.line = lines - 1; - } - - Ok((start, end)) - } - - fn range_semantic<T>( - term: &Term<T>, - mut start: Point<usize>, - mut end: Point<usize>, - ) -> SelectionRange { + fn range_semantic<T>(term: &Term<T>, mut start: Point, mut end: Point) -> SelectionRange { if start == end { if let Some(matching) = term.bracket_search(start) { if (matching.line == start.line && matching.column < start.column) @@ -339,19 +297,15 @@ impl Selection { } } - start = term.semantic_search_left(start); - end = term.semantic_search_right(end); + let start = term.semantic_search_left(start); + let end = term.semantic_search_right(end); SelectionRange { start, end, is_block: false } } - fn range_lines<T>( - term: &Term<T>, - mut start: Point<usize>, - mut end: Point<usize>, - ) -> SelectionRange { - start = term.line_search_left(start); - end = term.line_search_right(end); + fn range_lines<T>(term: &Term<T>, start: Point, end: Point) -> SelectionRange { + let start = term.line_search_left(start); + let end = term.line_search_right(end); SelectionRange { start, end, is_block: false } } @@ -360,7 +314,7 @@ impl Selection { &self, mut start: Anchor, mut end: Anchor, - num_cols: Column, + columns: usize, ) -> Option<SelectionRange> { if self.is_empty() { return None; @@ -369,9 +323,9 @@ impl Selection { // Remove last cell if selection ends to the left of a cell. if end.side == Side::Left && start.point != end.point { // Special case when selection ends to left of first cell. - if end.point.column == Column(0) { - end.point.column = num_cols - 1; - end.point.line += 1; + if end.point.column == 0 { + end.point.column = Column(columns - 1); + end.point.line -= 1; } else { end.point.column -= 1; } @@ -382,8 +336,9 @@ impl Selection { start.point.column += 1; // Wrap to next line when selection starts to the right of last column. - if start.point.column == num_cols { - start.point = Point::new(start.point.line.saturating_sub(1), Column(0)); + if start.point.column == columns { + start.point.column = Column(0); + start.point.line += 1; } } @@ -429,7 +384,7 @@ mod tests { use super::*; use crate::config::MockConfig; - use crate::index::{Column, Line, Point, Side}; + use crate::index::{Column, Point, Side}; use crate::term::{SizeInfo, Term}; fn term(height: usize, width: usize) -> Term<()> { @@ -444,7 +399,7 @@ mod tests { /// 3. [BE] #[test] fn single_cell_left_to_right() { - let location = Point { line: 0, column: Column(0) }; + let location = Point::new(Line(0), Column(0)); let mut selection = Selection::new(SelectionType::Simple, location, Side::Left); selection.update(location, Side::Right); @@ -462,7 +417,7 @@ mod tests { /// 3. [EB] #[test] fn single_cell_right_to_left() { - let location = Point { line: 0, column: Column(0) }; + let location = Point::new(Line(0), Column(0)); let mut selection = Selection::new(SelectionType::Simple, location, Side::Right); selection.update(location, Side::Left); @@ -481,8 +436,8 @@ mod tests { #[test] fn between_adjacent_cells_left_to_right() { let mut selection = - Selection::new(SelectionType::Simple, Point::new(0, Column(0)), Side::Right); - selection.update(Point::new(0, Column(1)), Side::Left); + Selection::new(SelectionType::Simple, Point::new(Line(0), Column(0)), Side::Right); + selection.update(Point::new(Line(0), Column(1)), Side::Left); assert_eq!(selection.to_range(&term(1, 2)), None); } @@ -495,8 +450,8 @@ mod tests { #[test] fn between_adjacent_cells_right_to_left() { let mut selection = - Selection::new(SelectionType::Simple, Point::new(0, Column(1)), Side::Left); - selection.update(Point::new(0, Column(0)), Side::Right); + Selection::new(SelectionType::Simple, Point::new(Line(0), Column(1)), Side::Left); + selection.update(Point::new(Line(0), Column(0)), Side::Right); assert_eq!(selection.to_range(&term(1, 2)), None); } @@ -512,12 +467,12 @@ mod tests { #[test] fn across_adjacent_lines_upward_final_cell_exclusive() { let mut selection = - Selection::new(SelectionType::Simple, Point::new(1, Column(1)), Side::Right); - selection.update(Point::new(0, Column(1)), Side::Right); + Selection::new(SelectionType::Simple, Point::new(Line(0), Column(1)), Side::Right); + selection.update(Point::new(Line(1), Column(1)), Side::Right); assert_eq!(selection.to_range(&term(2, 5)).unwrap(), SelectionRange { - start: Point::new(1, Column(2)), - end: Point::new(0, Column(1)), + start: Point::new(Line(0), Column(2)), + end: Point::new(Line(1), Column(1)), is_block: false, }); } @@ -535,73 +490,73 @@ mod tests { #[test] fn selection_bigger_then_smaller() { let mut selection = - Selection::new(SelectionType::Simple, Point::new(0, Column(1)), Side::Right); - selection.update(Point::new(1, Column(1)), Side::Right); - selection.update(Point::new(1, Column(0)), Side::Right); + Selection::new(SelectionType::Simple, Point::new(Line(1), Column(1)), Side::Right); + selection.update(Point::new(Line(0), Column(1)), Side::Right); + selection.update(Point::new(Line(0), Column(0)), Side::Right); assert_eq!(selection.to_range(&term(2, 5)).unwrap(), SelectionRange { - start: Point::new(1, Column(1)), - end: Point::new(0, Column(1)), + start: Point::new(Line(0), Column(1)), + end: Point::new(Line(1), Column(1)), is_block: false, }); } #[test] fn line_selection() { - let size = (Line(10), Column(5)); + let size = (10, 5); let mut selection = - Selection::new(SelectionType::Lines, Point::new(0, Column(1)), Side::Left); - selection.update(Point::new(5, Column(1)), Side::Right); - selection = selection.rotate(&size, &(Line(0)..size.0), 7).unwrap(); + Selection::new(SelectionType::Lines, Point::new(Line(9), Column(1)), Side::Left); + selection.update(Point::new(Line(4), Column(1)), Side::Right); + selection = selection.rotate(&size, &(Line(0)..Line(size.0 as i32)), 7).unwrap(); - assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange { - start: Point::new(9, Column(0)), - end: Point::new(7, Column(4)), + assert_eq!(selection.to_range(&term(size.0, size.1)).unwrap(), SelectionRange { + start: Point::new(Line(-3), Column(0)), + end: Point::new(Line(2), Column(4)), is_block: false, }); } #[test] fn semantic_selection() { - let size = (Line(10), Column(5)); + let size = (10, 5); let mut selection = - Selection::new(SelectionType::Semantic, Point::new(0, Column(3)), Side::Left); - selection.update(Point::new(5, Column(1)), Side::Right); - selection = selection.rotate(&size, &(Line(0)..size.0), 7).unwrap(); + Selection::new(SelectionType::Semantic, Point::new(Line(9), Column(3)), Side::Left); + selection.update(Point::new(Line(4), Column(1)), Side::Right); + selection = selection.rotate(&size, &(Line(0)..Line(size.0 as i32)), 4).unwrap(); - assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange { - start: Point::new(9, Column(0)), - end: Point::new(7, Column(3)), + assert_eq!(selection.to_range(&term(size.0, size.1)).unwrap(), SelectionRange { + start: Point::new(Line(0), Column(1)), + end: Point::new(Line(5), Column(3)), is_block: false, }); } #[test] fn simple_selection() { - let size = (Line(10), Column(5)); + let size = (10, 5); let mut selection = - Selection::new(SelectionType::Simple, Point::new(0, Column(3)), Side::Right); - selection.update(Point::new(5, Column(1)), Side::Right); - selection = selection.rotate(&size, &(Line(0)..size.0), 7).unwrap(); + Selection::new(SelectionType::Simple, Point::new(Line(9), Column(3)), Side::Right); + selection.update(Point::new(Line(4), Column(1)), Side::Right); + selection = selection.rotate(&size, &(Line(0)..Line(size.0 as i32)), 7).unwrap(); - assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange { - start: Point::new(9, Column(0)), - end: Point::new(7, Column(3)), + assert_eq!(selection.to_range(&term(size.0, size.1)).unwrap(), SelectionRange { + start: Point::new(Line(-3), Column(2)), + end: Point::new(Line(2), Column(3)), is_block: false, }); } #[test] fn block_selection() { - let size = (Line(10), Column(5)); + let size = (10, 5); let mut selection = - Selection::new(SelectionType::Block, Point::new(0, Column(3)), Side::Right); - selection.update(Point::new(5, Column(1)), Side::Right); - selection = selection.rotate(&size, &(Line(0)..size.0), 7).unwrap(); + Selection::new(SelectionType::Block, Point::new(Line(9), Column(3)), Side::Right); + selection.update(Point::new(Line(4), Column(1)), Side::Right); + selection = selection.rotate(&size, &(Line(0)..Line(size.0 as i32)), 7).unwrap(); - assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange { - start: Point::new(9, Column(2)), - end: Point::new(7, Column(3)), + assert_eq!(selection.to_range(&term(size.0, size.1)).unwrap(), SelectionRange { + start: Point::new(Line(-3), Column(2)), + end: Point::new(Line(2), Column(3)), is_block: true }); } @@ -609,72 +564,72 @@ mod tests { #[test] fn simple_is_empty() { let mut selection = - Selection::new(SelectionType::Simple, Point::new(0, Column(0)), Side::Right); + Selection::new(SelectionType::Simple, Point::new(Line(1), Column(0)), Side::Right); assert!(selection.is_empty()); - selection.update(Point::new(0, Column(1)), Side::Left); + selection.update(Point::new(Line(1), Column(1)), Side::Left); assert!(selection.is_empty()); - selection.update(Point::new(1, Column(0)), Side::Right); + selection.update(Point::new(Line(0), Column(0)), Side::Right); assert!(!selection.is_empty()); } #[test] fn block_is_empty() { let mut selection = - Selection::new(SelectionType::Block, Point::new(0, Column(0)), Side::Right); + Selection::new(SelectionType::Block, Point::new(Line(1), Column(0)), Side::Right); assert!(selection.is_empty()); - selection.update(Point::new(0, Column(1)), Side::Left); + selection.update(Point::new(Line(1), Column(1)), Side::Left); assert!(selection.is_empty()); - selection.update(Point::new(0, Column(1)), Side::Right); + selection.update(Point::new(Line(1), Column(1)), Side::Right); assert!(!selection.is_empty()); - selection.update(Point::new(1, Column(0)), Side::Right); + selection.update(Point::new(Line(0), Column(0)), Side::Right); assert!(selection.is_empty()); - selection.update(Point::new(1, Column(1)), Side::Left); + selection.update(Point::new(Line(0), Column(1)), Side::Left); assert!(selection.is_empty()); - selection.update(Point::new(1, Column(1)), Side::Right); + selection.update(Point::new(Line(0), Column(1)), Side::Right); assert!(!selection.is_empty()); } #[test] fn rotate_in_region_up() { - let size = (Line(10), Column(5)); + let size = (10, 5); let mut selection = - Selection::new(SelectionType::Simple, Point::new(2, Column(3)), Side::Right); - selection.update(Point::new(5, Column(1)), Side::Right); - selection = selection.rotate(&size, &(Line(1)..(size.0 - 1)), 4).unwrap(); + Selection::new(SelectionType::Simple, Point::new(Line(7), Column(3)), Side::Right); + selection.update(Point::new(Line(4), Column(1)), Side::Right); + selection = selection.rotate(&size, &(Line(1)..Line(size.0 as i32 - 1)), 4).unwrap(); - assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange { - start: Point::new(8, Column(0)), - end: Point::new(6, Column(3)), + assert_eq!(selection.to_range(&term(size.0, size.1)).unwrap(), SelectionRange { + start: Point::new(Line(1), Column(0)), + end: Point::new(Line(3), Column(3)), is_block: false, }); } #[test] fn rotate_in_region_down() { - let size = (Line(10), Column(5)); + let size = (10, 5); let mut selection = - Selection::new(SelectionType::Simple, Point::new(5, Column(3)), Side::Right); - selection.update(Point::new(8, Column(1)), Side::Left); - selection = selection.rotate(&size, &(Line(1)..(size.0 - 1)), -5).unwrap(); + Selection::new(SelectionType::Simple, Point::new(Line(4), Column(3)), Side::Right); + selection.update(Point::new(Line(1), Column(1)), Side::Left); + selection = selection.rotate(&size, &(Line(1)..Line(size.0 as i32 - 1)), -5).unwrap(); - assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange { - start: Point::new(3, Column(1)), - end: Point::new(1, size.1 - 1), + assert_eq!(selection.to_range(&term(size.0, size.1)).unwrap(), SelectionRange { + start: Point::new(Line(6), Column(1)), + end: Point::new(Line(8), size.last_column()), is_block: false, }); } #[test] fn rotate_in_region_up_block() { - let size = (Line(10), Column(5)); + let size = (10, 5); let mut selection = - Selection::new(SelectionType::Block, Point::new(2, Column(3)), Side::Right); - selection.update(Point::new(5, Column(1)), Side::Right); - selection = selection.rotate(&size, &(Line(1)..(size.0 - 1)), 4).unwrap(); + Selection::new(SelectionType::Block, Point::new(Line(7), Column(3)), Side::Right); + selection.update(Point::new(Line(4), Column(1)), Side::Right); + selection = selection.rotate(&size, &(Line(1)..Line(size.0 as i32 - 1)), 4).unwrap(); - assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange { - start: Point::new(8, Column(2)), - end: Point::new(6, Column(3)), + assert_eq!(selection.to_range(&term(size.0, size.1)).unwrap(), SelectionRange { + start: Point::new(Line(1), Column(2)), + end: Point::new(Line(3), Column(3)), is_block: true, }); } @@ -682,17 +637,17 @@ mod tests { #[test] fn range_intersection() { let mut selection = - Selection::new(SelectionType::Lines, Point::new(6, Column(1)), Side::Left); - selection.update(Point::new(3, Column(1)), Side::Right); + Selection::new(SelectionType::Lines, Point::new(Line(3), Column(1)), Side::Left); + selection.update(Point::new(Line(6), Column(1)), Side::Right); assert!(selection.intersects_range(..)); - assert!(selection.intersects_range(2..)); - assert!(selection.intersects_range(2..=4)); - assert!(selection.intersects_range(2..=7)); - assert!(selection.intersects_range(4..=5)); - assert!(selection.intersects_range(5..8)); - - assert!(!selection.intersects_range(..=2)); - assert!(!selection.intersects_range(7..=8)); + assert!(selection.intersects_range(Line(2)..)); + assert!(selection.intersects_range(Line(2)..=Line(4))); + assert!(selection.intersects_range(Line(2)..=Line(7))); + assert!(selection.intersects_range(Line(4)..=Line(5))); + assert!(selection.intersects_range(Line(5)..Line(8))); + + assert!(!selection.intersects_range(..=Line(2))); + assert!(!selection.intersects_range(Line(7)..=Line(8))); } } diff --git a/alacritty_terminal/src/term/cell.rs b/alacritty_terminal/src/term/cell.rs index 91ee3391..255cbce7 100644 --- a/alacritty_terminal/src/term/cell.rs +++ b/alacritty_terminal/src/term/cell.rs @@ -178,7 +178,7 @@ mod tests { #[test] fn line_length_works() { - let mut row = Row::<Cell>::new(Column(10)); + let mut row = Row::<Cell>::new(10); row[Column(5)].c = 'a'; assert_eq!(row.line_length(), Column(6)); @@ -186,7 +186,7 @@ mod tests { #[test] fn line_length_works_with_wrapline() { - let mut row = Row::<Cell>::new(Column(10)); + let mut row = Row::<Cell>::new(10); row[Column(9)].flags.insert(super::Flags::WRAPLINE); assert_eq!(row.line_length(), Column(10)); diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index b853ceba..8d0fc0f8 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -16,7 +16,7 @@ use crate::ansi::{ use crate::config::Config; use crate::event::{Event, EventListener}; use crate::grid::{Dimensions, DisplayIter, Grid, Scroll}; -use crate::index::{self, Boundary, Column, Direction, IndexRange, Line, Point, Side}; +use crate::index::{self, Boundary, Column, Direction, Line, Point, Side}; use crate::selection::{Selection, SelectionRange}; use crate::term::cell::{Cell, Flags, LineLength}; use crate::term::color::{Colors, Rgb}; @@ -29,7 +29,7 @@ pub mod search; /// Minimum number of columns. /// /// A minimum of 2 is necessary to hold fullwidth unicode characters. -pub const MIN_COLS: usize = 2; +pub const MIN_COLUMNS: usize = 2; /// Minimum number of visible lines. pub const MIN_SCREEN_LINES: usize = 1; @@ -97,10 +97,10 @@ pub struct SizeInfo { padding_y: f32, /// Number of lines in the viewport. - screen_lines: Line, + screen_lines: usize, /// Number of columns in the viewport. - cols: Column, + columns: usize, } impl SizeInfo { @@ -120,10 +120,10 @@ impl SizeInfo { } let lines = (height - 2. * padding_y) / cell_height; - let screen_lines = Line(max(lines as usize, MIN_SCREEN_LINES)); + let screen_lines = max(lines as usize, MIN_SCREEN_LINES); - let cols = (width - 2. * padding_x) / cell_width; - let cols = Column(max(cols as usize, MIN_COLS)); + let columns = (width - 2. * padding_x) / cell_width; + let columns = max(columns as usize, MIN_COLUMNS); SizeInfo { width, @@ -133,13 +133,13 @@ impl SizeInfo { padding_x: padding_x.floor(), padding_y: padding_y.floor(), screen_lines, - cols, + columns, } } #[inline] pub fn reserve_lines(&mut self, count: usize) { - self.screen_lines = Line(max(self.screen_lines.saturating_sub(count), MIN_SCREEN_LINES)); + self.screen_lines = max(self.screen_lines.saturating_sub(count), MIN_SCREEN_LINES); } /// Check if coordinates are inside the terminal grid. @@ -147,26 +147,12 @@ impl SizeInfo { /// The padding, message bar or search are not counted as part of the grid. #[inline] pub fn contains_point(&self, x: usize, y: usize) -> bool { - x <= (self.padding_x + self.cols.0 as f32 * self.cell_width) as usize + x <= (self.padding_x + self.columns as f32 * self.cell_width) as usize && x > self.padding_x as usize - && y <= (self.padding_y + self.screen_lines.0 as f32 * self.cell_height) as usize + && y <= (self.padding_y + self.screen_lines as f32 * self.cell_height) as usize && y > self.padding_y as usize } - /// Convert window space pixels to terminal grid coordinates. - /// - /// If the coordinates are outside of the terminal grid, like positions inside the padding, the - /// coordinates will be clamped to the closest grid coordinates. - pub fn pixels_to_coords(&self, x: usize, y: usize) -> Point { - let col = Column(x.saturating_sub(self.padding_x as usize) / (self.cell_width as usize)); - let line = Line(y.saturating_sub(self.padding_y as usize) / (self.cell_height as usize)); - - Point { - line: min(line, Line(self.screen_lines.saturating_sub(1))), - column: min(col, Column(self.cols.saturating_sub(1))), - } - } - #[inline] pub fn width(&self) -> f32 { self.width @@ -197,20 +183,27 @@ impl SizeInfo { self.padding_y } + /// Calculate padding to spread it evenly around the terminal content. #[inline] - pub fn screen_lines(&self) -> Line { - self.screen_lines + fn dynamic_padding(padding: f32, dimension: f32, cell_dimension: f32) -> f32 { + padding + ((dimension - 2. * padding) % cell_dimension) / 2. } +} +impl Dimensions for SizeInfo { #[inline] - pub fn cols(&self) -> Column { - self.cols + fn columns(&self) -> usize { + self.columns } - /// Calculate padding to spread it evenly around the terminal content. #[inline] - fn dynamic_padding(padding: f32, dimension: f32, cell_dimension: f32) -> f32 { - padding + ((dimension - 2. * padding) % cell_dimension) / 2. + fn screen_lines(&self) -> usize { + self.screen_lines + } + + #[inline] + fn total_lines(&self) -> usize { + self.screen_lines() } } @@ -286,19 +279,26 @@ impl<T> Term<T> { { self.grid.scroll_display(scroll); self.event_proxy.send_event(Event::MouseCursorDirty); + + // Clamp vi mode cursor to the viewport. + let viewport_start = -(self.grid.display_offset() as i32); + let viewport_end = viewport_start + self.bottommost_line().0; + let vi_cursor_line = &mut self.vi_mode_cursor.point.line.0; + *vi_cursor_line = min(viewport_end, max(viewport_start, *vi_cursor_line)); + self.vi_mode_recompute_selection(); } pub fn new<C>(config: &Config<C>, size: SizeInfo, event_proxy: T) -> Term<T> { - let num_cols = size.cols; + let num_cols = size.columns; let num_lines = size.screen_lines; let history_size = config.scrolling.history() as usize; let grid = Grid::new(num_lines, num_cols, history_size); let alt = Grid::new(num_lines, num_cols, 0); - let tabs = TabStops::new(grid.cols()); + let tabs = TabStops::new(grid.columns()); - let scroll_region = Line(0)..grid.screen_lines(); + let scroll_region = Line(0)..Line(grid.screen_lines() as i32); Term { grid, @@ -353,11 +353,11 @@ impl<T> Term<T> { let mut res = String::new(); if is_block { - for line in (end.line + 1..=start.line).rev() { + for line in (start.line.0..end.line.0).map(Line::from) { res += &self.line_to_string(line, start.column..end.column, start.column.0 != 0); // If the last column is included, newline is appended automatically. - if end.column != self.cols() - 1 { + if end.column != self.columns() - 1 { res += "\n"; } } @@ -370,12 +370,12 @@ impl<T> Term<T> { } /// Convert range between two points to a String. - pub fn bounds_to_string(&self, start: Point<usize>, end: Point<usize>) -> String { + pub fn bounds_to_string(&self, start: Point, end: Point) -> String { let mut res = String::new(); - for line in (end.line..=start.line).rev() { + for line in (start.line.0..=end.line.0).map(Line::from) { let start_col = if line == start.line { start.column } else { Column(0) }; - let end_col = if line == end.line { end.column } else { self.cols() - 1 }; + let end_col = if line == end.line { end.column } else { self.last_column() }; res += &self.line_to_string(line, start_col..end_col, line == end.line); } @@ -386,7 +386,7 @@ impl<T> Term<T> { /// Convert a single line in the grid to a String. fn line_to_string( &self, - line: usize, + line: Line, mut cols: Range<Column>, include_wrapped_wide: bool, ) -> String { @@ -401,12 +401,12 @@ impl<T> Term<T> { } let mut tab_mode = false; - for col in IndexRange::from(cols.start..line_length) { - let cell = &grid_line[col]; + for column in (cols.start.0..line_length.0).map(Column::from) { + let cell = &grid_line[column]; // Skip over cells until next tab-stop once a tab was found. if tab_mode { - if self.tabs[col] { + if self.tabs[column] { tab_mode = false; } else { continue; @@ -428,7 +428,7 @@ impl<T> Term<T> { } } - if cols.end >= self.cols() - 1 + if cols.end >= self.columns() - 1 && (line_length.0 == 0 || !self.grid[line][line_length - 1].flags.contains(Flags::WRAPLINE)) { @@ -436,22 +436,17 @@ impl<T> Term<T> { } // If wide char is not part of the selection, but leading spacer is, include it. - if line_length == self.cols() + if line_length == self.columns() && line_length.0 >= 2 && grid_line[line_length - 1].flags.contains(Flags::LEADING_WIDE_CHAR_SPACER) && include_wrapped_wide { - text.push(self.grid[line - 1][Column(0)].c); + text.push(self.grid[line - 1i32][Column(0)].c); } text } - #[inline] - pub fn visible_to_buffer(&self, point: Point) -> Point<usize> { - self.grid.visible_to_buffer(point) - } - /// Terminal content required for rendering. #[inline] pub fn renderable_content(&self) -> RenderableContent<'_> @@ -480,10 +475,10 @@ impl<T> Term<T> { self.cell_width = size.cell_width as usize; self.cell_height = size.cell_height as usize; - let old_cols = self.cols(); + let old_cols = self.columns(); let old_lines = self.screen_lines(); - let num_cols = size.cols; + let num_cols = size.columns; let num_lines = size.screen_lines; if old_cols == num_cols && old_lines == num_lines { @@ -493,6 +488,13 @@ impl<T> Term<T> { debug!("New num_cols is {} and num_lines is {}", num_cols, num_lines); + // Move vi mode cursor with the content. + let history_size = self.history_size(); + let mut delta = num_lines as i32 - old_lines as i32; + let min_delta = min(0, num_lines as i32 - self.grid.cursor.point.line.0 - 1); + delta = min(max(delta, min_delta), history_size as i32); + self.vi_mode_cursor.point.line += delta; + // Invalidate selection and tabs only when necessary. if old_cols != num_cols { self.selection = None; @@ -500,27 +502,21 @@ impl<T> Term<T> { // Recreate tabs list. self.tabs.resize(num_cols); } else if let Some(selection) = self.selection.take() { - // Move the selection if only number of lines changed. - let delta = if num_lines > old_lines { - (num_lines - old_lines.0).saturating_sub(self.history_size()) as isize - } else { - let cursor_line = self.grid.cursor.point.line; - -(min(old_lines - cursor_line - 1, old_lines - num_lines).0 as isize) - }; - self.selection = selection.rotate(self, &(Line(0)..num_lines), delta); + let range = Line(0)..Line(num_lines as i32); + self.selection = selection.rotate(self, &range, -delta); } let is_alt = self.mode.contains(TermMode::ALT_SCREEN); - self.grid.resize(!is_alt, num_lines, num_cols); self.inactive_grid.resize(is_alt, num_lines, num_cols); // Clamp vi cursor to viewport. - self.vi_mode_cursor.point.column = min(self.vi_mode_cursor.point.column, num_cols - 1); - self.vi_mode_cursor.point.line = min(self.vi_mode_cursor.point.line, num_lines - 1); + let vi_point = self.vi_mode_cursor.point; + self.vi_mode_cursor.point.column = min(vi_point.column, Column(num_cols - 1)); + self.vi_mode_cursor.point.line = min(vi_point.line, Line(num_lines as i32 - 1)); // Reset scrolling region. - self.scroll_region = Line(0)..self.screen_lines(); + self.scroll_region = Line(0)..Line(self.screen_lines() as i32); } /// Active terminal modes. @@ -547,49 +543,22 @@ impl<T> Term<T> { self.selection = None; } - /// Get the selection within the viewport. - fn visible_selection(&self) -> Option<SelectionRange<Line>> { - let selection = self.selection.as_ref()?.to_range(self)?; - - // Set horizontal limits for block selection. - let (limit_start, limit_end) = if selection.is_block { - (selection.start.column, selection.end.column) - } else { - (Column(0), self.cols() - 1) - }; - - let range = self.grid.clamp_buffer_range_to_visible(&(selection.start..=selection.end))?; - let mut start = *range.start(); - let mut end = *range.end(); - - // Trim start/end with partially visible block selection. - start.column = max(limit_start, start.column); - end.column = min(limit_end, end.column); - - Some(SelectionRange::new(start, end, selection.is_block)) - } - /// Scroll screen down. /// /// Text moves down; clear at bottom /// Expects origin to be in scroll range. #[inline] - fn scroll_down_relative(&mut self, origin: Line, mut lines: Line) { + fn scroll_down_relative(&mut self, origin: Line, mut lines: usize) { trace!("Scrolling down relative: origin={}, lines={}", origin, lines); - let num_lines = self.screen_lines(); - - lines = min(lines, self.scroll_region.end - self.scroll_region.start); - lines = min(lines, self.scroll_region.end - origin); + lines = min(lines, (self.scroll_region.end - self.scroll_region.start).0 as usize); + lines = min(lines, (self.scroll_region.end - origin).0 as usize); let region = origin..self.scroll_region.end; - let absolute_region = (num_lines - region.end)..(num_lines - region.start); // Scroll selection. - self.selection = self - .selection - .take() - .and_then(|s| s.rotate(self, &absolute_region, -(lines.0 as isize))); + self.selection = + self.selection.take().and_then(|s| s.rotate(self, ®ion, -(lines as i32))); // Scroll between origin and bottom self.grid.scroll_down(®ion, lines); @@ -600,19 +569,15 @@ impl<T> Term<T> { /// Text moves up; clear at top /// Expects origin to be in scroll range. #[inline] - fn scroll_up_relative(&mut self, origin: Line, mut lines: Line) { + fn scroll_up_relative(&mut self, origin: Line, mut lines: usize) { trace!("Scrolling up relative: origin={}, lines={}", origin, lines); - let num_lines = self.screen_lines(); - - lines = min(lines, self.scroll_region.end - self.scroll_region.start); + lines = min(lines, (self.scroll_region.end - self.scroll_region.start).0 as usize); let region = origin..self.scroll_region.end; - let absolute_region = (num_lines - region.end)..(num_lines - region.start); // Scroll selection. - self.selection = - self.selection.take().and_then(|s| s.rotate(self, &absolute_region, lines.0 as isize)); + self.selection = self.selection.take().and_then(|s| s.rotate(self, ®ion, lines as i32)); // Scroll from origin to bottom less number of lines. self.grid.scroll_up(®ion, lines); @@ -648,9 +613,7 @@ impl<T> Term<T> { if self.mode.contains(TermMode::VI) { // Reset vi mode cursor position to match primary cursor. - let cursor = self.grid.cursor.point; - let line = min(cursor.line + self.grid.display_offset(), self.screen_lines() - 1); - self.vi_mode_cursor = ViModeCursor::new(Point::new(line, cursor.column)); + self.vi_mode_cursor = ViModeCursor::new(self.grid.cursor.point); } // Update UI about cursor blinking state changes. @@ -673,9 +636,9 @@ impl<T> Term<T> { self.vi_mode_recompute_selection(); } - /// Move vi cursor to absolute point in grid. + /// Move vi cursor to a point in the grid. #[inline] - pub fn vi_goto_point(&mut self, point: Point<usize>) + pub fn vi_goto_point(&mut self, point: Point) where T: EventListener, { @@ -683,7 +646,7 @@ impl<T> Term<T> { self.scroll_to_point(point); // Move vi cursor to the point. - self.vi_mode_cursor.point = self.grid.clamp_buffer_to_visible(point); + self.vi_mode_cursor.point = point; self.vi_mode_recompute_selection(); } @@ -696,43 +659,38 @@ impl<T> Term<T> { return; } - let viewport_point = self.visible_to_buffer(self.vi_mode_cursor.point); - // Update only if non-empty selection is present. - let selection = match &mut self.selection { - Some(selection) if !selection.is_empty() => selection, - _ => return, - }; - - selection.update(viewport_point, Side::Left); - selection.include_all(); + if let Some(selection) = self.selection.as_mut().filter(|s| !s.is_empty()) { + selection.update(self.vi_mode_cursor.point, Side::Left); + selection.include_all(); + } } /// Scroll display to point if it is outside of viewport. - pub fn scroll_to_point(&mut self, point: Point<usize>) + pub fn scroll_to_point(&mut self, point: Point) where T: EventListener, { - let display_offset = self.grid.display_offset(); - let num_lines = self.screen_lines().0; + let display_offset = self.grid.display_offset() as i32; + let screen_lines = self.grid.screen_lines() as i32; - if point.line >= display_offset + num_lines { - let lines = point.line.saturating_sub(display_offset + num_lines - 1); - self.scroll_display(Scroll::Delta(lines as isize)); - } else if point.line < display_offset { - let lines = display_offset.saturating_sub(point.line); - self.scroll_display(Scroll::Delta(-(lines as isize))); + if point.line < -display_offset { + let lines = point.line + display_offset; + self.scroll_display(Scroll::Delta(-lines.0)); + } else if point.line >= (screen_lines - display_offset) { + let lines = point.line + display_offset - screen_lines + 1i32; + self.scroll_display(Scroll::Delta(-lines.0)); } } /// Jump to the end of a wide cell. - pub fn expand_wide(&self, mut point: Point<usize>, direction: Direction) -> Point<usize> { + pub fn expand_wide(&self, mut point: Point, direction: Direction) -> Point { let flags = self.grid[point.line][point.column].flags; match direction { Direction::Right if flags.contains(Flags::LEADING_WIDE_CHAR_SPACER) => { point.column = Column(1); - point.line -= 1; + point.line += 1; }, Direction::Right if flags.contains(Flags::WIDE_CHAR) => point.column += 1, Direction::Left if flags.intersects(Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER) => { @@ -740,7 +698,7 @@ impl<T> Term<T> { point.column -= 1; } - let prev = point.sub_absolute(self, Boundary::Clamp, 1); + let prev = point.sub(self, Boundary::Grid, 1); if self.grid[prev].flags.contains(Flags::LEADING_WIDE_CHAR_SPACER) { point = prev; } @@ -784,7 +742,7 @@ impl<T> Term<T> { self.grid.cursor_cell().flags.insert(Flags::WRAPLINE); - if (self.grid.cursor.point.line + 1) >= self.scroll_region.end { + if self.grid.cursor.point.line + 1 >= self.scroll_region.end { self.linefeed(); } else { self.grid.cursor.point.line += 1; @@ -817,12 +775,12 @@ impl<T> Term<T> { impl<T> Dimensions for Term<T> { #[inline] - fn cols(&self) -> Column { - self.grid.cols() + fn columns(&self) -> usize { + self.grid.columns() } #[inline] - fn screen_lines(&self) -> Line { + fn screen_lines(&self) -> usize { self.grid.screen_lines() } @@ -865,7 +823,7 @@ impl<T: EventListener> Handler for Term<T> { self.wrapline(); } - let num_cols = self.cols(); + let num_cols = self.columns(); // If in insert mode, first shift cells to the right. if self.mode.contains(TermMode::INSERT) && self.grid.cursor.point.column + width < num_cols @@ -874,7 +832,7 @@ impl<T: EventListener> Handler for Term<T> { let col = self.grid.cursor.point.column; let row = &mut self.grid[line][..]; - for col in (col.0..(num_cols - width).0).rev() { + for col in (col.0..(num_cols - width)).rev() { row.swap(col + width, col); } } @@ -913,8 +871,8 @@ impl<T: EventListener> Handler for Term<T> { fn decaln(&mut self) { trace!("Decalnning"); - for line in 0..self.screen_lines().0 { - for column in 0..self.cols().0 { + for line in (0..self.screen_lines()).map(Line::from) { + for column in 0..self.columns() { let cell = &mut self.grid[line][Column(column)]; *cell = Cell::default(); cell.c = 'E'; @@ -928,11 +886,11 @@ impl<T: EventListener> Handler for Term<T> { let (y_offset, max_y) = if self.mode.contains(TermMode::ORIGIN) { (self.scroll_region.start, self.scroll_region.end - 1) } else { - (Line(0), self.screen_lines() - 1) + (Line(0), self.bottommost_line()) }; - self.grid.cursor.point.line = min(line + y_offset, max_y); - self.grid.cursor.point.column = min(col, self.cols() - 1); + self.grid.cursor.point.line = max(min(line + y_offset, max_y), Line(0)); + self.grid.cursor.point.column = min(col, self.last_column()); self.grid.cursor.input_needs_wrap = false; } @@ -949,50 +907,48 @@ impl<T: EventListener> Handler for Term<T> { } #[inline] - fn insert_blank(&mut self, count: Column) { + fn insert_blank(&mut self, count: usize) { let cursor = &self.grid.cursor; let bg = cursor.template.bg; // Ensure inserting within terminal bounds - let count = min(count, self.cols() - cursor.point.column); + let count = min(count, self.columns() - cursor.point.column.0); let source = cursor.point.column; - let destination = cursor.point.column + count; - let num_cells = (self.cols() - destination).0; + let destination = cursor.point.column.0 + count; + let num_cells = self.columns() - destination; let line = cursor.point.line; let row = &mut self.grid[line][..]; for offset in (0..num_cells).rev() { - row.swap(destination.0 + offset, source.0 + offset); + row.swap(destination + offset, source.0 + offset); } // Cells were just moved out toward the end of the line; // fill in between source and dest with blanks. - for cell in &mut row[source.0..destination.0] { + for cell in &mut row[source.0..destination] { *cell = bg.into(); } } #[inline] - fn move_up(&mut self, lines: Line) { + fn move_up(&mut self, lines: usize) { trace!("Moving up: {}", lines); - let move_to = Line(self.grid.cursor.point.line.0.saturating_sub(lines.0)); - self.goto(move_to, self.grid.cursor.point.column) + self.goto(self.grid.cursor.point.line - lines, self.grid.cursor.point.column) } #[inline] - fn move_down(&mut self, lines: Line) { + fn move_down(&mut self, lines: usize) { trace!("Moving down: {}", lines); - let move_to = self.grid.cursor.point.line + lines; - self.goto(move_to, self.grid.cursor.point.column) + self.goto(self.grid.cursor.point.line + lines, self.grid.cursor.point.column) } #[inline] fn move_forward(&mut self, cols: Column) { trace!("Moving forward: {}", cols); - let num_cols = self.cols(); - self.grid.cursor.point.column = min(self.grid.cursor.point.column + cols, num_cols - 1); + let last_column = self.last_column(); + self.grid.cursor.point.column = min(self.grid.cursor.point.column + cols, last_column); self.grid.cursor.input_needs_wrap = false; } @@ -1037,17 +993,15 @@ impl<T: EventListener> Handler for Term<T> { } #[inline] - fn move_down_and_cr(&mut self, lines: Line) { + fn move_down_and_cr(&mut self, lines: usize) { trace!("Moving down and cr: {}", lines); - let move_to = self.grid.cursor.point.line + lines; - self.goto(move_to, Column(0)) + self.goto(self.grid.cursor.point.line + lines, Column(0)) } #[inline] - fn move_up_and_cr(&mut self, lines: Line) { + fn move_up_and_cr(&mut self, lines: usize) { trace!("Moving up and cr: {}", lines); - let move_to = Line(self.grid.cursor.point.line.0.saturating_sub(lines.0)); - self.goto(move_to, Column(0)) + self.goto(self.grid.cursor.point.line - lines, Column(0)) } /// Insert tab at cursor position. @@ -1059,7 +1013,7 @@ impl<T: EventListener> Handler for Term<T> { return; } - while self.grid.cursor.point.column < self.cols() && count != 0 { + while self.grid.cursor.point.column < self.columns() && count != 0 { count -= 1; let c = self.grid.cursor.charsets[self.active_charset].map('\t'); @@ -1069,7 +1023,7 @@ impl<T: EventListener> Handler for Term<T> { } loop { - if (self.grid.cursor.point.column + 1) == self.cols() { + if (self.grid.cursor.point.column + 1) == self.columns() { break; } @@ -1107,7 +1061,7 @@ impl<T: EventListener> Handler for Term<T> { trace!("Linefeed"); let next = self.grid.cursor.point.line + 1; if next == self.scroll_region.end { - self.scroll_up(Line(1)); + self.scroll_up(1); } else if next < self.screen_lines() { self.grid.cursor.point.line += 1; } @@ -1163,19 +1117,19 @@ impl<T: EventListener> Handler for Term<T> { } #[inline] - fn scroll_up(&mut self, lines: Line) { + fn scroll_up(&mut self, lines: usize) { let origin = self.scroll_region.start; self.scroll_up_relative(origin, lines); } #[inline] - fn scroll_down(&mut self, lines: Line) { + fn scroll_down(&mut self, lines: usize) { let origin = self.scroll_region.start; self.scroll_down_relative(origin, lines); } #[inline] - fn insert_blank_lines(&mut self, lines: Line) { + fn insert_blank_lines(&mut self, lines: usize) { trace!("Inserting blank {} lines", lines); let origin = self.grid.cursor.point.line; @@ -1185,13 +1139,13 @@ impl<T: EventListener> Handler for Term<T> { } #[inline] - fn delete_lines(&mut self, lines: Line) { + fn delete_lines(&mut self, lines: usize) { let origin = self.grid.cursor.point.line; - let lines = min(self.screen_lines() - origin, lines); + let lines = min(self.screen_lines() - origin.0 as usize, lines); trace!("Deleting {} lines", lines); - if lines.0 > 0 && self.scroll_region.contains(&self.grid.cursor.point.line) { + if lines > 0 && self.scroll_region.contains(&origin) { self.scroll_up_relative(origin, lines); } } @@ -1203,7 +1157,7 @@ impl<T: EventListener> Handler for Term<T> { trace!("Erasing chars: count={}, col={}", count, cursor.point.column); let start = cursor.point.column; - let end = min(start + count, self.cols()); + let end = min(start + count, Column(self.columns())); // Cleared cells have current background color set. let bg = self.grid.cursor.template.bg; @@ -1215,28 +1169,28 @@ impl<T: EventListener> Handler for Term<T> { } #[inline] - fn delete_chars(&mut self, count: Column) { - let cols = self.cols(); + fn delete_chars(&mut self, count: usize) { + let columns = self.columns(); let cursor = &self.grid.cursor; let bg = cursor.template.bg; // Ensure deleting within terminal bounds. - let count = min(count, cols); + let count = min(count, columns); - let start = cursor.point.column; - let end = min(start + count, cols - 1); - let num_cells = (cols - end).0; + let start = cursor.point.column.0; + let end = min(start + count, columns - 1); + let num_cells = columns - end; let line = cursor.point.line; let row = &mut self.grid[line][..]; for offset in 0..num_cells { - row.swap(start.0 + offset, end.0 + offset); + row.swap(start + offset, end + offset); } // Clear last `count` cells in the row. If deleting 1 char, need to delete // 1 cell. - let end = (cols - count).0; + let end = columns - count; for cell in &mut row[end..] { *cell = bg.into(); } @@ -1305,11 +1259,8 @@ impl<T: EventListener> Handler for Term<T> { }, } - let cursor_buffer_line = (self.screen_lines() - self.grid.cursor.point.line - 1).0; - self.selection = self - .selection - .take() - .filter(|s| !s.intersects_range(cursor_buffer_line..=cursor_buffer_line)); + let range = self.grid.cursor.point.line..=self.grid.cursor.point.line; + self.selection = self.selection.take().filter(|s| !s.intersects_range(range)); } /// Set the indexed color value. @@ -1384,29 +1335,26 @@ impl<T: EventListener> Handler for Term<T> { trace!("Clearing screen: {:?}", mode); let bg = self.grid.cursor.template.bg; - let num_lines = self.screen_lines().0; - let cursor_buffer_line = num_lines - self.grid.cursor.point.line.0 - 1; + let screen_lines = self.screen_lines(); match mode { ansi::ClearMode::Above => { let cursor = self.grid.cursor.point; // If clearing more than one line. - if cursor.line > Line(1) { + if cursor.line > 1 { // Fully clear all lines before the current line. self.grid.reset_region(..cursor.line); } // Clear up to the current column in the current line. - let end = min(cursor.column + 1, self.cols()); + let end = min(cursor.column + 1, Column(self.columns())); for cell in &mut self.grid[cursor.line][..end] { *cell = bg.into(); } - self.selection = self - .selection - .take() - .filter(|s| !s.intersects_range(cursor_buffer_line..num_lines)); + let range = Line(0)..=cursor.line; + self.selection = self.selection.take().filter(|s| !s.intersects_range(range)); }, ansi::ClearMode::Below => { let cursor = self.grid.cursor.point; @@ -1414,12 +1362,12 @@ impl<T: EventListener> Handler for Term<T> { *cell = bg.into(); } - if cursor.line.0 < num_lines - 1 { + if (cursor.line.0 as usize) < screen_lines - 1 { self.grid.reset_region((cursor.line + 1)..); } - self.selection = - self.selection.take().filter(|s| !s.intersects_range(..=cursor_buffer_line)); + let range = cursor.line..Line(screen_lines as i32); + self.selection = self.selection.take().filter(|s| !s.intersects_range(range)); }, ansi::ClearMode::All => { if self.mode.contains(TermMode::ALT_SCREEN) { @@ -1428,12 +1376,12 @@ impl<T: EventListener> Handler for Term<T> { self.grid.clear_viewport(); } - self.selection = self.selection.take().filter(|s| !s.intersects_range(..num_lines)); + self.selection = None; }, ansi::ClearMode::Saved if self.history_size() > 0 => { self.grid.clear_history(); - self.selection = self.selection.take().filter(|s| !s.intersects_range(num_lines..)); + self.selection = self.selection.take().filter(|s| !s.intersects_range(..Line(0))); }, // We have no history to clear. ansi::ClearMode::Saved => (), @@ -1463,8 +1411,8 @@ impl<T: EventListener> Handler for Term<T> { self.cursor_style = None; self.grid.reset(); self.inactive_grid.reset(); - self.scroll_region = Line(0)..self.screen_lines(); - self.tabs = TabStops::new(self.cols()); + self.scroll_region = Line(0)..Line(self.screen_lines() as i32); + self.tabs = TabStops::new(self.columns()); self.title_stack = Vec::new(); self.title = None; self.selection = None; @@ -1482,9 +1430,9 @@ impl<T: EventListener> Handler for Term<T> { trace!("Reversing index"); // If cursor is at the top. if self.grid.cursor.point.line == self.scroll_region.start { - self.scroll_down(Line(1)); + self.scroll_down(1); } else { - self.grid.cursor.point.line = Line(self.grid.cursor.point.line.saturating_sub(1)); + self.grid.cursor.point.line = max(self.grid.cursor.point.line - 1, Line(0)); } } @@ -1628,7 +1576,7 @@ impl<T: EventListener> Handler for Term<T> { #[inline] fn set_scrolling_region(&mut self, top: usize, bottom: Option<usize>) { // Fallback to the last line as default. - let bottom = bottom.unwrap_or_else(|| self.screen_lines().0); + let bottom = bottom.unwrap_or_else(|| self.screen_lines()); if top >= bottom { debug!("Invalid scrolling region: ({};{})", top, bottom); @@ -1639,13 +1587,14 @@ impl<T: EventListener> Handler for Term<T> { // usually included. One option would be to use an inclusive // range, but instead we just let the open range end be 1 // higher. - let start = Line(top - 1); - let end = Line(bottom); + let start = Line(top as i32 - 1); + let end = Line(bottom as i32); trace!("Setting scrolling region: ({};{})", start, end); - self.scroll_region.start = min(start, self.screen_lines()); - self.scroll_region.end = min(end, self.screen_lines()); + let screen_lines = Line(self.screen_lines() as i32); + self.scroll_region.start = min(start, screen_lines); + self.scroll_region.end = min(end, screen_lines); self.goto(Line(0), Column(0)); } @@ -1732,14 +1681,14 @@ impl<T: EventListener> Handler for Term<T> { #[inline] fn text_area_size_pixels<W: io::Write>(&mut self, writer: &mut W) { - let width = self.cell_width * self.cols().0; - let height = self.cell_height * self.screen_lines().0; + let width = self.cell_width * self.columns(); + let height = self.cell_height * self.screen_lines(); let _ = write!(writer, "\x1b[4;{};{}t", height, width); } #[inline] fn text_area_size_chars<W: io::Write>(&mut self, writer: &mut W) { - let _ = write!(writer, "\x1b[8;{};{}t", self.screen_lines(), self.cols()); + let _ = write!(writer, "\x1b[8;{};{}t", self.screen_lines(), self.columns()); } } @@ -1776,12 +1725,8 @@ struct TabStops { impl TabStops { #[inline] - fn new(num_cols: Column) -> TabStops { - TabStops { - tabs: IndexRange::from(Column(0)..num_cols) - .map(|i| (*i as usize) % INITIAL_TABSTOPS == 0) - .collect::<Vec<bool>>(), - } + fn new(columns: usize) -> TabStops { + TabStops { tabs: (0..columns).map(|i| i % INITIAL_TABSTOPS == 0).collect() } } /// Remove all tabstops. @@ -1794,9 +1739,9 @@ impl TabStops { /// Increase tabstop capacity. #[inline] - fn resize(&mut self, num_cols: Column) { + fn resize(&mut self, columns: usize) { let mut index = self.tabs.len(); - self.tabs.resize_with(num_cols.0, || { + self.tabs.resize_with(columns, || { let is_tabstop = index % INITIAL_TABSTOPS == 0; index += 1; is_tabstop @@ -1829,19 +1774,10 @@ impl RenderableCursor { fn new<T>(term: &Term<T>) -> Self { // Cursor position. let vi_mode = term.mode().contains(TermMode::VI); - let mut point = if vi_mode { - term.vi_mode_cursor.point - } else { - let mut point = term.grid.cursor.point; - point.line += term.grid.display_offset(); - point - }; + let point = if vi_mode { term.vi_mode_cursor.point } else { term.grid.cursor.point }; // Cursor shape. - let shape = if !vi_mode - && (!term.mode().contains(TermMode::SHOW_CURSOR) || point.line >= term.screen_lines()) - { - point.line = Line(0); + let shape = if !vi_mode && !term.mode().contains(TermMode::SHOW_CURSOR) { CursorShape::Hidden } else { term.cursor_style().shape @@ -1856,7 +1792,7 @@ impl RenderableCursor { /// This contains all content required to render the current terminal view. pub struct RenderableContent<'a> { pub display_iter: DisplayIter<'a, Cell>, - pub selection: Option<SelectionRange<Line>>, + pub selection: Option<SelectionRange>, pub cursor: RenderableCursor, pub display_offset: usize, pub colors: &'a color::Colors, @@ -1869,7 +1805,7 @@ impl<'a> RenderableContent<'a> { display_iter: term.grid().display_iter(), display_offset: term.grid().display_offset(), cursor: RenderableCursor::new(term), - selection: term.visible_selection(), + selection: term.selection.as_ref().and_then(|s| s.to_range(term)), colors: &term.colors, mode: *term.mode(), } @@ -1917,8 +1853,9 @@ pub mod test { let mut term = Term::new(&Config::<()>::default(), size, ()); // Fill terminal with content. - for (line, text) in lines.iter().rev().enumerate() { - if !text.ends_with('\r') && line != 0 { + for (line, text) in lines.iter().enumerate() { + let line = Line(line as i32); + if !text.ends_with('\r') && line + 1 != lines.len() { term.grid[line][Column(num_cols - 1)].flags.insert(Flags::WRAPLINE); } @@ -1950,15 +1887,15 @@ mod tests { use crate::ansi::{self, CharsetIndex, Handler, StandardCharset}; use crate::config::MockConfig; use crate::grid::{Grid, Scroll}; - use crate::index::{Column, Line, Point, Side}; + use crate::index::{Column, Point, Side}; use crate::selection::{Selection, SelectionType}; use crate::term::cell::{Cell, Flags}; #[test] fn semantic_selection_works() { - let size = SizeInfo::new(21.0, 51.0, 3.0, 3.0, 0.0, 0.0, false); + let size = SizeInfo::new(5., 3., 1.0, 1.0, 0.0, 0.0, false); let mut term = Term::new(&MockConfig::default(), size, ()); - let mut grid: Grid<Cell> = Grid::new(Line(3), Column(5), 0); + let mut grid: Grid<Cell> = Grid::new(3, 5, 0); for i in 0..5 { for j in 0..2 { grid[Line(j)][Column(i)].c = 'a'; @@ -1977,7 +1914,7 @@ mod tests { { term.selection = Some(Selection::new( SelectionType::Semantic, - Point { line: 2, column: Column(1) }, + Point { line: Line(0), column: Column(1) }, Side::Left, )); assert_eq!(term.selection_to_string(), Some(String::from("aa"))); @@ -1986,7 +1923,7 @@ mod tests { { term.selection = Some(Selection::new( SelectionType::Semantic, - Point { line: 2, column: Column(4) }, + Point { line: Line(0), column: Column(4) }, Side::Left, )); assert_eq!(term.selection_to_string(), Some(String::from("aaa"))); @@ -1995,7 +1932,7 @@ mod tests { { term.selection = Some(Selection::new( SelectionType::Semantic, - Point { line: 1, column: Column(1) }, + Point { line: Line(1), column: Column(1) }, Side::Left, )); assert_eq!(term.selection_to_string(), Some(String::from("aaa"))); @@ -2004,9 +1941,9 @@ mod tests { #[test] fn line_selection_works() { - let size = SizeInfo::new(21.0, 51.0, 3.0, 3.0, 0.0, 0.0, false); + let size = SizeInfo::new(5., 1., 1.0, 1.0, 0.0, 0.0, false); let mut term = Term::new(&MockConfig::default(), size, ()); - let mut grid: Grid<Cell> = Grid::new(Line(1), Column(5), 0); + let mut grid: Grid<Cell> = Grid::new(1, 5, 0); for i in 0..5 { grid[Line(0)][Column(i)].c = 'a'; } @@ -2017,7 +1954,7 @@ mod tests { term.selection = Some(Selection::new( SelectionType::Lines, - Point { line: 0, column: Column(3) }, + Point { line: Line(0), column: Column(3) }, Side::Left, )); assert_eq!(term.selection_to_string(), Some(String::from("\"aa\"a\n"))); @@ -2025,9 +1962,9 @@ mod tests { #[test] fn selecting_empty_line() { - let size = SizeInfo::new(21.0, 51.0, 3.0, 3.0, 0.0, 0.0, false); + let size = SizeInfo::new(3.0, 3.0, 1.0, 1.0, 0.0, 0.0, false); let mut term = Term::new(&MockConfig::default(), size, ()); - let mut grid: Grid<Cell> = Grid::new(Line(3), Column(3), 0); + let mut grid: Grid<Cell> = Grid::new(3, 3, 0); for l in 0..3 { if l != 1 { for c in 0..3 { @@ -2038,9 +1975,12 @@ mod tests { mem::swap(&mut term.grid, &mut grid); - let mut selection = - Selection::new(SelectionType::Simple, Point { line: 2, column: Column(0) }, Side::Left); - selection.update(Point { line: 0, column: Column(2) }, Side::Right); + let mut selection = Selection::new( + SelectionType::Simple, + Point { line: Line(0), column: Column(0) }, + Side::Left, + ); + selection.update(Point { line: Line(2), column: Column(2) }, Side::Right); term.selection = Some(selection); assert_eq!(term.selection_to_string(), Some("aaa\n\naaa\n".into())); } @@ -2051,7 +1991,7 @@ mod tests { /// test this property with a T=Cell. #[test] fn grid_serde() { - let grid: Grid<Cell> = Grid::new(Line(24), Column(80), 0); + let grid: Grid<Cell> = Grid::new(24, 80, 0); let serialized = serde_json::to_string(&grid).expect("ser"); let deserialized = serde_json::from_str::<Grid<Cell>>(&serialized).expect("de"); @@ -2075,7 +2015,7 @@ mod tests { let mut term = Term::new(&MockConfig::default(), size, ()); // Add one line of scrollback. - term.grid.scroll_up(&(Line(0)..Line(1)), Line(1)); + term.grid.scroll_up(&(Line(0)..Line(1)), 1); // Clear the history. term.clear_screen(ansi::ClearMode::Saved); @@ -2104,7 +2044,7 @@ mod tests { assert_eq!(term.grid.cursor.point, Point::new(Line(9), Column(0))); // Increase visible lines. - size.screen_lines.0 = 30; + size.screen_lines = 30; term.resize(size); assert_eq!(term.history_size(), 0); @@ -2127,7 +2067,7 @@ mod tests { term.set_mode(ansi::Mode::SwapScreenAndSetRestoreCursor); // Increase visible lines. - size.screen_lines.0 = 30; + size.screen_lines = 30; term.resize(size); // Leave alt screen. @@ -2150,7 +2090,7 @@ mod tests { assert_eq!(term.grid.cursor.point, Point::new(Line(9), Column(0))); // Increase visible lines. - size.screen_lines.0 = 5; + size.screen_lines = 5; term.resize(size); assert_eq!(term.history_size(), 15); @@ -2173,7 +2113,7 @@ mod tests { term.set_mode(ansi::Mode::SwapScreenAndSetRestoreCursor); // Increase visible lines. - size.screen_lines.0 = 5; + size.screen_lines = 5; term.resize(size); // Leave alt screen. diff --git a/alacritty_terminal/src/term/search.rs b/alacritty_terminal/src/term/search.rs index 0eba7567..638df670 100644 --- a/alacritty_terminal/src/term/search.rs +++ b/alacritty_terminal/src/term/search.rs @@ -1,4 +1,4 @@ -use std::cmp::min; +use std::cmp::max; use std::mem; use std::ops::RangeInclusive; @@ -12,7 +12,7 @@ use crate::term::Term; /// Used to match equal brackets, when performing a bracket-pair selection. const BRACKET_PAIRS: [(char, char); 4] = [('(', ')'), ('[', ']'), ('{', '}'), ('<', '>')]; -pub type Match = RangeInclusive<Point<usize>>; +pub type Match = RangeInclusive<Point>; /// Terminal regex search state. #[derive(Clone, Debug)] @@ -53,7 +53,7 @@ impl<T> Term<T> { pub fn search_next( &self, dfas: &RegexSearch, - mut origin: Point<usize>, + mut origin: Point, direction: Direction, side: Side, mut max_lines: Option<usize>, @@ -72,7 +72,7 @@ impl<T> Term<T> { fn next_match_right( &self, dfas: &RegexSearch, - origin: Point<usize>, + origin: Point, side: Side, max_lines: Option<usize>, ) -> Option<Match> { @@ -80,13 +80,12 @@ impl<T> Term<T> { let mut end = start; // Limit maximum number of lines searched. - let total_lines = self.total_lines(); end = match max_lines { Some(max_lines) => { - let line = (start.line + total_lines - max_lines) % total_lines; - Point::new(line, self.cols() - 1) + let line = (start.line + max_lines).grid_clamp(self, Boundary::Grid); + Point::new(line, self.last_column()) }, - _ => end.sub_absolute(self, Boundary::Wrap, 1), + _ => end.sub(self, Boundary::None, 1), }; let mut regex_iter = RegexIter::new(start, end, Direction::Right, &self, dfas).peekable(); @@ -99,8 +98,8 @@ impl<T> Term<T> { let match_point = Self::match_side(®ex_match, side); // If the match's point is beyond the origin, we're done. - match_point.line > start.line - || match_point.line < origin.line + match_point.line < start.line + || match_point.line > origin.line || (match_point.line == origin.line && match_point.column >= origin.column) }) .unwrap_or(first_match); @@ -112,7 +111,7 @@ impl<T> Term<T> { fn next_match_left( &self, dfas: &RegexSearch, - origin: Point<usize>, + origin: Point, side: Side, max_lines: Option<usize>, ) -> Option<Match> { @@ -121,8 +120,11 @@ impl<T> Term<T> { // Limit maximum number of lines searched. end = match max_lines { - Some(max_lines) => Point::new((start.line + max_lines) % self.total_lines(), Column(0)), - _ => end.add_absolute(self, Boundary::Wrap, 1), + Some(max_lines) => { + let line = (start.line - max_lines).grid_clamp(self, Boundary::Grid); + Point::new(line, Column(0)) + }, + _ => end.add(self, Boundary::None, 1), }; let mut regex_iter = RegexIter::new(start, end, Direction::Left, &self, dfas).peekable(); @@ -135,8 +137,8 @@ impl<T> Term<T> { let match_point = Self::match_side(®ex_match, side); // If the match's point is beyond the origin, we're done. - match_point.line < start.line - || match_point.line > origin.line + match_point.line > start.line + || match_point.line < origin.line || (match_point.line == origin.line && match_point.column <= origin.column) }) .unwrap_or(first_match); @@ -145,7 +147,7 @@ impl<T> Term<T> { } /// Get the side of a match. - fn match_side(regex_match: &Match, side: Side) -> Point<usize> { + fn match_side(regex_match: &Match, side: Side) -> Point { match side { Side::Right => *regex_match.end(), Side::Left => *regex_match.start(), @@ -155,12 +157,7 @@ impl<T> Term<T> { /// Find the next regex match to the left of the origin point. /// /// The origin is always included in the regex. - pub fn regex_search_left( - &self, - dfas: &RegexSearch, - start: Point<usize>, - end: Point<usize>, - ) -> Option<Match> { + pub fn regex_search_left(&self, dfas: &RegexSearch, start: Point, end: Point) -> Option<Match> { // Find start and end of match. let match_start = self.regex_search(start, end, Direction::Left, &dfas.left_fdfa)?; let match_end = self.regex_search(match_start, start, Direction::Right, &dfas.left_rdfa)?; @@ -174,8 +171,8 @@ impl<T> Term<T> { pub fn regex_search_right( &self, dfas: &RegexSearch, - start: Point<usize>, - end: Point<usize>, + start: Point, + end: Point, ) -> Option<Match> { // Find start and end of match. let match_end = self.regex_search(start, end, Direction::Right, &dfas.right_fdfa)?; @@ -189,13 +186,14 @@ impl<T> Term<T> { /// This will always return the side of the first match which is farthest from the start point. fn regex_search( &self, - start: Point<usize>, - end: Point<usize>, + start: Point, + end: Point, direction: Direction, dfa: &impl DFA, - ) -> Option<Point<usize>> { - let last_line = self.total_lines() - 1; - let last_col = self.cols() - 1; + ) -> Option<Point> { + let topmost_line = self.topmost_line(); + let screen_lines = self.screen_lines() as i32; + let last_column = self.last_column(); // Advance the iterator. let next = match direction { @@ -250,7 +248,8 @@ impl<T> Term<T> { Some(Indexed { cell, .. }) => cell, None => { // Wrap around to other end of the scrollback buffer. - let start = Point::new(last_line - point.line, last_col - point.column); + let line = topmost_line - point.line + screen_lines - 1; + let start = Point::new(line, last_column - point.column); iter = self.grid.iter_from(start); iter.cell() }, @@ -262,8 +261,8 @@ impl<T> Term<T> { let last_point = mem::replace(&mut point, iter.point()); // Handle linebreaks. - if (last_point.column == last_col && point.column == Column(0) && !last_wrapped) - || (last_point.column == Column(0) && point.column == last_col && !wrapped) + if (last_point.column == last_column && point.column == Column(0) && !last_wrapped) + || (last_point.column == Column(0) && point.column == last_column && !wrapped) { match regex_match { Some(_) => break, @@ -299,7 +298,7 @@ impl<T> Term<T> { *cell = new_cell; } - let prev = iter.point().sub_absolute(self, Boundary::Clamp, 1); + let prev = iter.point().sub(self, Boundary::Grid, 1); if self.grid[prev].flags.contains(Flags::LEADING_WIDE_CHAR_SPACER) { iter.prev(); } @@ -309,8 +308,8 @@ impl<T> Term<T> { } /// Find next matching bracket. - pub fn bracket_search(&self, point: Point<usize>) -> Option<Point<usize>> { - let start_char = self.grid[point.line][point.column].c; + pub fn bracket_search(&self, point: Point) -> Option<Point> { + let start_char = self.grid[point].c; // Find the matching bracket we're looking for let (forward, end_char) = BRACKET_PAIRS.iter().find_map(|(open, close)| { @@ -353,12 +352,12 @@ impl<T> Term<T> { } /// Find left end of semantic block. - pub fn semantic_search_left(&self, mut point: Point<usize>) -> Point<usize> { + pub fn semantic_search_left(&self, mut point: Point) -> Point { // Limit the starting point to the last line in the history - point.line = min(point.line, self.total_lines() - 1); + point.line = max(point.line, self.topmost_line()); let mut iter = self.grid.iter_from(point); - let last_col = self.cols() - Column(1); + let last_column = self.columns() - 1; let wide = Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER; while let Some(cell) = iter.prev() { @@ -366,7 +365,7 @@ impl<T> Term<T> { break; } - if cell.point.column == last_col && !cell.flags.contains(Flags::WRAPLINE) { + if cell.point.column == last_column && !cell.flags.contains(Flags::WRAPLINE) { break; // cut off if on new line or hit escape char } @@ -377,12 +376,12 @@ impl<T> Term<T> { } /// Find right end of semantic block. - pub fn semantic_search_right(&self, mut point: Point<usize>) -> Point<usize> { + pub fn semantic_search_right(&self, mut point: Point) -> Point { // Limit the starting point to the last line in the history - point.line = min(point.line, self.total_lines() - 1); + point.line = max(point.line, self.topmost_line()); let wide = Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER; - let last_col = self.cols() - 1; + let last_column = self.columns() - 1; for cell in self.grid.iter_from(point) { if !cell.flags.intersects(wide) && self.semantic_escape_chars.contains(cell.c) { @@ -391,7 +390,7 @@ impl<T> Term<T> { point = cell.point; - if point.column == last_col && !cell.flags.contains(Flags::WRAPLINE) { + if point.column == last_column && !cell.flags.contains(Flags::WRAPLINE) { break; // cut off if on new line or hit escape char } } @@ -400,11 +399,11 @@ impl<T> Term<T> { } /// Find the beginning of the current line across linewraps. - pub fn line_search_left(&self, mut point: Point<usize>) -> Point<usize> { - while point.line + 1 < self.total_lines() - && self.grid[point.line + 1][self.cols() - 1].flags.contains(Flags::WRAPLINE) + pub fn line_search_left(&self, mut point: Point) -> Point { + while point.line > self.topmost_line() + && self.grid[point.line - 1i32][self.last_column()].flags.contains(Flags::WRAPLINE) { - point.line += 1; + point.line -= 1; } point.column = Column(0); @@ -413,14 +412,14 @@ impl<T> Term<T> { } /// Find the end of the current line across linewraps. - pub fn line_search_right(&self, mut point: Point<usize>) -> Point<usize> { - while point.line > 0 - && self.grid[point.line][self.cols() - 1].flags.contains(Flags::WRAPLINE) + pub fn line_search_right(&self, mut point: Point) -> Point { + while point.line + 1 < self.screen_lines() + && self.grid[point.line][self.last_column()].flags.contains(Flags::WRAPLINE) { - point.line -= 1; + point.line += 1; } - point.column = self.cols() - 1; + point.column = self.last_column(); point } @@ -428,8 +427,8 @@ impl<T> Term<T> { /// Iterator over regex matches. pub struct RegexIter<'a, T> { - point: Point<usize>, - end: Point<usize>, + point: Point, + end: Point, direction: Direction, dfas: &'a RegexSearch, term: &'a Term<T>, @@ -438,8 +437,8 @@ pub struct RegexIter<'a, T> { impl<'a, T> RegexIter<'a, T> { pub fn new( - start: Point<usize>, - end: Point<usize>, + start: Point, + end: Point, direction: Direction, term: &'a Term<T>, dfas: &'a RegexSearch, @@ -452,8 +451,8 @@ impl<'a, T> RegexIter<'a, T> { self.point = self.term.expand_wide(self.point, self.direction); self.point = match self.direction { - Direction::Right => self.point.add_absolute(self.term, Boundary::Wrap, 1), - Direction::Left => self.point.sub_absolute(self.term, Boundary::Wrap, 1), + Direction::Right => self.point.add(self.term, Boundary::None, 1), + Direction::Left => self.point.sub(self.term, Boundary::None, 1), }; } @@ -498,7 +497,7 @@ impl<'a, T> Iterator for RegexIter<'a, T> { mod tests { use super::*; - use crate::index::Column; + use crate::index::{Column, Line}; use crate::term::test::mock_term; #[test] @@ -514,10 +513,10 @@ mod tests { // Check regex across wrapped and unwrapped lines. let dfas = RegexSearch::new("Ala.*123").unwrap(); - let start = Point::new(3, Column(0)); - let end = Point::new(0, Column(2)); - let match_start = Point::new(3, Column(0)); - let match_end = Point::new(2, Column(2)); + let start = Point::new(Line(1), Column(0)); + let end = Point::new(Line(4), Column(2)); + let match_start = Point::new(Line(1), Column(0)); + let match_end = Point::new(Line(2), Column(2)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end)); } @@ -534,10 +533,10 @@ mod tests { // Check regex across wrapped and unwrapped lines. let dfas = RegexSearch::new("Ala.*123").unwrap(); - let start = Point::new(0, Column(2)); - let end = Point::new(3, Column(0)); - let match_start = Point::new(3, Column(0)); - let match_end = Point::new(2, Column(2)); + let start = Point::new(Line(4), Column(2)); + let end = Point::new(Line(1), Column(0)); + let match_start = Point::new(Line(1), Column(0)); + let match_end = Point::new(Line(2), Column(2)); assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end)); } @@ -551,14 +550,14 @@ mod tests { // Greedy stopped at linebreak. let dfas = RegexSearch::new("Ala.*critty").unwrap(); - let start = Point::new(1, Column(0)); - let end = Point::new(1, Column(25)); + let start = Point::new(Line(0), Column(0)); + let end = Point::new(Line(0), Column(25)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end)); // Greedy stopped at dead state. let dfas = RegexSearch::new("Ala[^y]*critty").unwrap(); - let start = Point::new(1, Column(0)); - let end = Point::new(1, Column(15)); + let start = Point::new(Line(0), Column(0)); + let end = Point::new(Line(0), Column(15)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end)); } @@ -572,8 +571,8 @@ mod tests { "); let dfas = RegexSearch::new("nothing").unwrap(); - let start = Point::new(2, Column(0)); - let end = Point::new(0, Column(4)); + let start = Point::new(Line(2), Column(0)); + let end = Point::new(Line(0), Column(4)); assert_eq!(term.regex_search_right(&dfas, start, end), None); } @@ -587,8 +586,8 @@ mod tests { "); let dfas = RegexSearch::new("nothing").unwrap(); - let start = Point::new(0, Column(4)); - let end = Point::new(2, Column(0)); + let start = Point::new(Line(0), Column(4)); + let end = Point::new(Line(2), Column(0)); assert_eq!(term.regex_search_left(&dfas, start, end), None); } @@ -602,10 +601,10 @@ mod tests { // Make sure the cell containing the linebreak is not skipped. let dfas = RegexSearch::new("te.*123").unwrap(); - let start = Point::new(0, Column(0)); - let end = Point::new(1, Column(0)); - let match_start = Point::new(1, Column(0)); - let match_end = Point::new(1, Column(9)); + let start = Point::new(Line(1), Column(0)); + let end = Point::new(Line(0), Column(0)); + let match_start = Point::new(Line(0), Column(0)); + let match_end = Point::new(Line(0), Column(9)); assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end)); } @@ -619,9 +618,9 @@ mod tests { // Make sure the cell containing the linebreak is not skipped. let dfas = RegexSearch::new("te.*123").unwrap(); - let start = Point::new(1, Column(2)); - let end = Point::new(0, Column(9)); - let match_start = Point::new(0, Column(0)); + let start = Point::new(Line(0), Column(2)); + let end = Point::new(Line(1), Column(9)); + let match_start = Point::new(Line(1), Column(0)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=end)); } @@ -631,8 +630,8 @@ mod tests { // Make sure dead state cell is skipped when reversing. let dfas = RegexSearch::new("alacrit").unwrap(); - let start = Point::new(0, Column(0)); - let end = Point::new(0, Column(6)); + let start = Point::new(Line(0), Column(0)); + let end = Point::new(Line(0), Column(6)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end)); } @@ -642,10 +641,10 @@ mod tests { // Make sure the reverse DFA operates the same as a forward DFA. let dfas = RegexSearch::new("zoo").unwrap(); - let start = Point::new(0, Column(9)); - let end = Point::new(0, Column(0)); - let match_start = Point::new(0, Column(0)); - let match_end = Point::new(0, Column(2)); + let start = Point::new(Line(0), Column(9)); + let end = Point::new(Line(0), Column(0)); + let match_start = Point::new(Line(0), Column(0)); + let match_end = Point::new(Line(0), Column(2)); assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end)); } @@ -654,13 +653,13 @@ mod tests { let term = mock_term("testвосибing"); let dfas = RegexSearch::new("te.*ing").unwrap(); - let start = Point::new(0, Column(0)); - let end = Point::new(0, Column(11)); + let start = Point::new(Line(0), Column(0)); + let end = Point::new(Line(0), Column(11)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end)); let dfas = RegexSearch::new("te.*ing").unwrap(); - let start = Point::new(0, Column(11)); - let end = Point::new(0, Column(0)); + let start = Point::new(Line(0), Column(11)); + let end = Point::new(Line(0), Column(0)); assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=start)); } @@ -669,13 +668,13 @@ mod tests { let term = mock_term("a🦇x🦇"); let dfas = RegexSearch::new("[^ ]*").unwrap(); - let start = Point::new(0, Column(0)); - let end = Point::new(0, Column(5)); + let start = Point::new(Line(0), Column(0)); + let end = Point::new(Line(0), Column(5)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end)); let dfas = RegexSearch::new("[^ ]*").unwrap(); - let start = Point::new(0, Column(5)); - let end = Point::new(0, Column(0)); + let start = Point::new(Line(0), Column(5)); + let end = Point::new(Line(0), Column(0)); assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=start)); } @@ -684,13 +683,13 @@ mod tests { let term = mock_term("🦇"); let dfas = RegexSearch::new("🦇").unwrap(); - let start = Point::new(0, Column(0)); - let end = Point::new(0, Column(1)); + let start = Point::new(Line(0), Column(0)); + let end = Point::new(Line(0), Column(1)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end)); let dfas = RegexSearch::new("🦇").unwrap(); - let start = Point::new(0, Column(1)); - let end = Point::new(0, Column(0)); + let start = Point::new(Line(0), Column(1)); + let end = Point::new(Line(0), Column(0)); assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=start)); } @@ -703,15 +702,15 @@ mod tests { "); let dfas = RegexSearch::new("xxx").unwrap(); - let start = Point::new(0, Column(2)); - let end = Point::new(1, Column(2)); - let match_start = Point::new(1, Column(0)); + let start = Point::new(Line(0), Column(2)); + let end = Point::new(Line(1), Column(2)); + let match_start = Point::new(Line(1), Column(0)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=end)); let dfas = RegexSearch::new("xxx").unwrap(); - let start = Point::new(1, Column(0)); - let end = Point::new(0, Column(0)); - let match_end = Point::new(0, Column(2)); + let start = Point::new(Line(1), Column(0)); + let end = Point::new(Line(0), Column(0)); + let match_end = Point::new(Line(0), Column(2)); assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=match_end)); } @@ -724,17 +723,17 @@ mod tests { "); let dfas = RegexSearch::new("🦇x").unwrap(); - let start = Point::new(0, Column(0)); - let end = Point::new(1, Column(3)); - let match_start = Point::new(1, Column(0)); - let match_end = Point::new(1, Column(2)); + let start = Point::new(Line(1), Column(0)); + let end = Point::new(Line(0), Column(3)); + let match_start = Point::new(Line(0), Column(0)); + let match_end = Point::new(Line(0), Column(2)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end)); let dfas = RegexSearch::new("x🦇").unwrap(); - let start = Point::new(1, Column(2)); - let end = Point::new(0, Column(0)); - let match_start = Point::new(0, Column(1)); - let match_end = Point::new(0, Column(3)); + let start = Point::new(Line(0), Column(2)); + let end = Point::new(Line(1), Column(0)); + let match_start = Point::new(Line(1), Column(1)); + let match_end = Point::new(Line(1), Column(3)); assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end)); } @@ -745,34 +744,34 @@ mod tests { xxx \n\ 🦇xx\ "); - term.grid[1][Column(3)].flags.insert(Flags::LEADING_WIDE_CHAR_SPACER); + term.grid[Line(0)][Column(3)].flags.insert(Flags::LEADING_WIDE_CHAR_SPACER); let dfas = RegexSearch::new("🦇x").unwrap(); - let start = Point::new(1, Column(0)); - let end = Point::new(0, Column(3)); - let match_start = Point::new(1, Column(3)); - let match_end = Point::new(0, Column(2)); + let start = Point::new(Line(0), Column(0)); + let end = Point::new(Line(1), Column(3)); + let match_start = Point::new(Line(0), Column(3)); + let match_end = Point::new(Line(1), Column(2)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end)); let dfas = RegexSearch::new("🦇x").unwrap(); - let start = Point::new(0, Column(3)); - let end = Point::new(1, Column(0)); - let match_start = Point::new(1, Column(3)); - let match_end = Point::new(0, Column(2)); + let start = Point::new(Line(1), Column(3)); + let end = Point::new(Line(0), Column(0)); + let match_start = Point::new(Line(0), Column(3)); + let match_end = Point::new(Line(1), Column(2)); assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end)); let dfas = RegexSearch::new("x🦇").unwrap(); - let start = Point::new(1, Column(0)); - let end = Point::new(0, Column(3)); - let match_start = Point::new(1, Column(2)); - let match_end = Point::new(0, Column(1)); + let start = Point::new(Line(0), Column(0)); + let end = Point::new(Line(1), Column(3)); + let match_start = Point::new(Line(0), Column(2)); + let match_end = Point::new(Line(1), Column(1)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end)); let dfas = RegexSearch::new("x🦇").unwrap(); - let start = Point::new(0, Column(3)); - let end = Point::new(1, Column(0)); - let match_start = Point::new(1, Column(2)); - let match_end = Point::new(0, Column(1)); + let start = Point::new(Line(1), Column(3)); + let end = Point::new(Line(0), Column(0)); + let match_start = Point::new(Line(0), Column(2)); + let match_end = Point::new(Line(1), Column(1)); assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end)); } } diff --git a/alacritty_terminal/src/tty/unix.rs b/alacritty_terminal/src/tty/unix.rs index 9cce1983..ba59bb66 100644 --- a/alacritty_terminal/src/tty/unix.rs +++ b/alacritty_terminal/src/tty/unix.rs @@ -25,6 +25,7 @@ use signal_hook::{self as sighook, iterator::Signals}; use crate::config::{Config, Program}; use crate::event::OnResize; +use crate::grid::Dimensions; use crate::term::SizeInfo; use crate::tty::{ChildEvent, EventedPty, EventedReadWrite}; @@ -357,8 +358,8 @@ pub trait ToWinsize { impl<'a> ToWinsize for &'a SizeInfo { fn to_winsize(&self) -> winsize { winsize { - ws_row: self.screen_lines().0 as libc::c_ushort, - ws_col: self.cols().0 as libc::c_ushort, + ws_row: self.screen_lines() as libc::c_ushort, + ws_col: self.columns() as libc::c_ushort, ws_xpixel: self.width() as libc::c_ushort, ws_ypixel: self.height() as libc::c_ushort, } diff --git a/alacritty_terminal/src/tty/windows/conpty.rs b/alacritty_terminal/src/tty/windows/conpty.rs index de3d8d4e..919bd00f 100644 --- a/alacritty_terminal/src/tty/windows/conpty.rs +++ b/alacritty_terminal/src/tty/windows/conpty.rs @@ -19,6 +19,7 @@ use winapi::um::wincontypes::{COORD, HPCON}; use crate::config::Config; use crate::event::OnResize; +use crate::grid::Dimensions; use crate::term::SizeInfo; use crate::tty::windows::child::ChildExitWatcher; use crate::tty::windows::{cmdline, win32_string, Pty}; @@ -185,11 +186,11 @@ impl OnResize for Conpty { /// Helper to build a COORD from a SizeInfo, returning None in overflow cases. fn coord_from_sizeinfo(size: &SizeInfo) -> Option<COORD> { - let cols = size.cols().0; - let lines = size.screen_lines().0; + let lines = size.screen_lines(); + let columns = size.columns(); - if cols <= i16::MAX as usize && lines <= i16::MAX as usize { - Some(COORD { X: cols as i16, Y: lines as i16 }) + if columns <= i16::MAX as usize && lines <= i16::MAX as usize { + Some(COORD { X: columns as i16, Y: lines as i16 }) } else { None } diff --git a/alacritty_terminal/src/vi_mode.rs b/alacritty_terminal/src/vi_mode.rs index 1b3390d6..54229998 100644 --- a/alacritty_terminal/src/vi_mode.rs +++ b/alacritty_terminal/src/vi_mode.rs @@ -65,135 +65,126 @@ impl ViModeCursor { /// Move vi mode cursor. #[must_use = "this returns the result of the operation, without modifying the original"] pub fn motion<T: EventListener>(mut self, term: &mut Term<T>, motion: ViMotion) -> Self { - let display_offset = term.grid().display_offset(); - let lines = term.screen_lines(); - let cols = term.cols(); - - let mut buffer_point = term.visible_to_buffer(self.point); - match motion { ViMotion::Up => { - if buffer_point.line + 1 < term.total_lines() { - buffer_point.line += 1; + if self.point.line > term.topmost_line() { + self.point.line -= 1; + } + }, + ViMotion::Down => { + if self.point.line + 1 < term.screen_lines() as i32 { + self.point.line += 1; } }, - ViMotion::Down => buffer_point.line = buffer_point.line.saturating_sub(1), ViMotion::Left => { - buffer_point = term.expand_wide(buffer_point, Direction::Left); - let wrap_point = Point::new(buffer_point.line + 1, cols - 1); - if buffer_point.column.0 == 0 - && buffer_point.line + 1 < term.total_lines() + self.point = term.expand_wide(self.point, Direction::Left); + let wrap_point = Point::new(self.point.line - 1, term.last_column()); + if self.point.column == 0 + && self.point.line > term.topmost_line() && is_wrap(term, wrap_point) { - buffer_point = wrap_point; + self.point = wrap_point; } else { - buffer_point.column = Column(buffer_point.column.saturating_sub(1)); + self.point.column = Column(self.point.column.saturating_sub(1)); } }, ViMotion::Right => { - buffer_point = term.expand_wide(buffer_point, Direction::Right); - if is_wrap(term, buffer_point) { - buffer_point = Point::new(buffer_point.line - 1, Column(0)); + self.point = term.expand_wide(self.point, Direction::Right); + if is_wrap(term, self.point) { + self.point = Point::new(self.point.line + 1, Column(0)); } else { - buffer_point.column = min(buffer_point.column + 1, cols - 1); + self.point.column = min(self.point.column + 1, term.last_column()); } }, ViMotion::First => { - buffer_point = term.expand_wide(buffer_point, Direction::Left); - while buffer_point.column.0 == 0 - && buffer_point.line + 1 < term.total_lines() - && is_wrap(term, Point::new(buffer_point.line + 1, cols - 1)) + self.point = term.expand_wide(self.point, Direction::Left); + while self.point.column == 0 + && self.point.line > term.topmost_line() + && is_wrap(term, Point::new(self.point.line - 1, term.last_column())) { - buffer_point.line += 1; + self.point.line -= 1; } - buffer_point.column = Column(0); + self.point.column = Column(0); }, - ViMotion::Last => buffer_point = last(term, buffer_point), - ViMotion::FirstOccupied => buffer_point = first_occupied(term, buffer_point), + ViMotion::Last => self.point = last(term, self.point), + ViMotion::FirstOccupied => self.point = first_occupied(term, self.point), ViMotion::High => { - let line = display_offset + lines.0 - 1; + let line = Line(-(term.grid().display_offset() as i32)); let col = first_occupied_in_line(term, line).unwrap_or_default().column; - buffer_point = Point::new(line, col); + self.point = Point::new(line, col); }, ViMotion::Middle => { - let line = display_offset + lines.0 / 2; + let display_offset = term.grid().display_offset() as i32; + let line = Line(-display_offset + term.screen_lines() as i32 / 2 - 1); let col = first_occupied_in_line(term, line).unwrap_or_default().column; - buffer_point = Point::new(line, col); + self.point = Point::new(line, col); }, ViMotion::Low => { - let line = display_offset; + let display_offset = term.grid().display_offset() as i32; + let line = Line(-display_offset + term.screen_lines() as i32 - 1); let col = first_occupied_in_line(term, line).unwrap_or_default().column; - buffer_point = Point::new(line, col); + self.point = Point::new(line, col); }, ViMotion::SemanticLeft => { - buffer_point = semantic(term, buffer_point, Direction::Left, Side::Left); + self.point = semantic(term, self.point, Direction::Left, Side::Left); }, ViMotion::SemanticRight => { - buffer_point = semantic(term, buffer_point, Direction::Right, Side::Left); + self.point = semantic(term, self.point, Direction::Right, Side::Left); }, ViMotion::SemanticLeftEnd => { - buffer_point = semantic(term, buffer_point, Direction::Left, Side::Right); + self.point = semantic(term, self.point, Direction::Left, Side::Right); }, ViMotion::SemanticRightEnd => { - buffer_point = semantic(term, buffer_point, Direction::Right, Side::Right); + self.point = semantic(term, self.point, Direction::Right, Side::Right); }, ViMotion::WordLeft => { - buffer_point = word(term, buffer_point, Direction::Left, Side::Left); + self.point = word(term, self.point, Direction::Left, Side::Left); }, ViMotion::WordRight => { - buffer_point = word(term, buffer_point, Direction::Right, Side::Left); + self.point = word(term, self.point, Direction::Right, Side::Left); }, ViMotion::WordLeftEnd => { - buffer_point = word(term, buffer_point, Direction::Left, Side::Right); + self.point = word(term, self.point, Direction::Left, Side::Right); }, ViMotion::WordRightEnd => { - buffer_point = word(term, buffer_point, Direction::Right, Side::Right); - }, - ViMotion::Bracket => { - buffer_point = term.bracket_search(buffer_point).unwrap_or(buffer_point); + self.point = word(term, self.point, Direction::Right, Side::Right); }, + ViMotion::Bracket => self.point = term.bracket_search(self.point).unwrap_or(self.point), } - term.scroll_to_point(buffer_point); - self.point = term.grid().clamp_buffer_to_visible(buffer_point); + term.scroll_to_point(self.point); self } /// Get target cursor point for vim-like page movement. #[must_use = "this returns the result of the operation, without modifying the original"] - pub fn scroll<T: EventListener>(mut self, term: &Term<T>, lines: isize) -> Self { + pub fn scroll<T: EventListener>(mut self, term: &Term<T>, lines: i32) -> Self { // Check number of lines the cursor needs to be moved. let overscroll = if lines > 0 { let max_scroll = term.history_size() - term.grid().display_offset(); - max(0, lines - max_scroll as isize) + max(0, lines - max_scroll as i32) } else { let max_scroll = term.grid().display_offset(); - min(0, lines + max_scroll as isize) + min(0, lines + max_scroll as i32) }; // Clamp movement to within visible region. - let mut line = self.point.line.0 as isize; - line -= overscroll; - line = max(0, min(term.screen_lines().0 as isize - 1, line)); + let line = (self.point.line - overscroll).grid_clamp(term, Boundary::Cursor); // Find the first occupied cell after scrolling has been performed. - let buffer_point = term.visible_to_buffer(self.point); - let mut target_line = buffer_point.line as isize + lines; - target_line = max(0, min(term.total_lines() as isize - 1, target_line)); - let col = first_occupied_in_line(term, target_line as usize).unwrap_or_default().column; + let target_line = (self.point.line - lines).grid_clamp(term, Boundary::Grid); + let column = first_occupied_in_line(term, target_line).unwrap_or_default().column; // Move cursor. - self.point = Point::new(Line(line as usize), col); + self.point = Point::new(line, column); self } } /// Find next end of line to move to. -fn last<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> { - let cols = term.cols(); - +fn last<T>(term: &Term<T>, mut point: Point) -> Point { // Expand across wide cells. point = term.expand_wide(point, Direction::Right); @@ -205,35 +196,35 @@ fn last<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> { occupied } else if is_wrap(term, point) { // Jump to last occupied cell across linewraps. - while point.line > 0 && is_wrap(term, point) { - point.line -= 1; + while is_wrap(term, point) { + point.line += 1; } last_occupied_in_line(term, point.line).unwrap_or(point) } else { // Jump to last column when beyond the last occupied cell. - Point::new(point.line, cols - 1) + Point::new(point.line, term.last_column()) } } /// Find next non-empty cell to move to. -fn first_occupied<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> { - let cols = term.cols(); +fn first_occupied<T>(term: &Term<T>, mut point: Point) -> Point { + let last_column = term.last_column(); // Expand left across wide chars, since we're searching lines left to right. point = term.expand_wide(point, Direction::Left); // Find first non-empty cell in current line. let occupied = first_occupied_in_line(term, point.line) - .unwrap_or_else(|| Point::new(point.line, cols - 1)); + .unwrap_or_else(|| Point::new(point.line, last_column)); // Jump across wrapped lines if we're already at this line's first occupied cell. if point == occupied { let mut occupied = None; // Search for non-empty cell in previous lines. - for line in (point.line + 1)..term.total_lines() { - if !is_wrap(term, Point::new(line, cols - 1)) { + for line in (term.topmost_line().0..point.line.0).rev().map(Line::from) { + if !is_wrap(term, Point::new(line, last_column)) { break; } @@ -247,12 +238,12 @@ fn first_occupied<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> { break occupied; } - let last_cell = Point::new(line, cols - 1); - if line == 0 || !is_wrap(term, last_cell) { + let last_cell = Point::new(line, last_column); + if !is_wrap(term, last_cell) { break last_cell; } - line -= 1; + line += 1; }) } else { occupied @@ -262,14 +253,14 @@ fn first_occupied<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> { /// Move by semantically separated word, like w/b/e/ge in vi. fn semantic<T: EventListener>( term: &mut Term<T>, - mut point: Point<usize>, + mut point: Point, direction: Direction, side: Side, -) -> Point<usize> { +) -> Point { // Expand semantically based on movement direction. - let expand_semantic = |point: Point<usize>| { + let expand_semantic = |point: Point| { // Do not expand when currently on a semantic escape char. - let cell = &term.grid()[point.line][point.column]; + let cell = &term.grid()[point]; if term.semantic_escape_chars().contains(cell.c) && !cell.flags.intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER) { @@ -312,10 +303,10 @@ fn semantic<T: EventListener>( /// Move by whitespace separated word, like W/B/E/gE in vi. fn word<T: EventListener>( term: &mut Term<T>, - mut point: Point<usize>, + mut point: Point, direction: Direction, side: Side, -) -> Point<usize> { +) -> Point { // Make sure we jump above wide chars. point = term.expand_wide(point, direction); @@ -351,45 +342,46 @@ fn word<T: EventListener>( } /// Find first non-empty cell in line. -fn first_occupied_in_line<T>(term: &Term<T>, line: usize) -> Option<Point<usize>> { - (0..term.cols().0) +fn first_occupied_in_line<T>(term: &Term<T>, line: Line) -> Option<Point> { + (0..term.columns()) .map(|col| Point::new(line, Column(col))) .find(|&point| !is_space(term, point)) } /// Find last non-empty cell in line. -fn last_occupied_in_line<T>(term: &Term<T>, line: usize) -> Option<Point<usize>> { - (0..term.cols().0) +fn last_occupied_in_line<T>(term: &Term<T>, line: Line) -> Option<Point> { + (0..term.columns()) .map(|col| Point::new(line, Column(col))) .rfind(|&point| !is_space(term, point)) } /// Advance point based on direction. -fn advance<T>(term: &Term<T>, point: Point<usize>, direction: Direction) -> Point<usize> { +fn advance<T>(term: &Term<T>, point: Point, direction: Direction) -> Point { if direction == Direction::Left { - point.sub_absolute(term, Boundary::Clamp, 1) + point.sub(term, Boundary::Grid, 1) } else { - point.add_absolute(term, Boundary::Clamp, 1) + point.add(term, Boundary::Grid, 1) } } /// Check if cell at point contains whitespace. -fn is_space<T>(term: &Term<T>, point: Point<usize>) -> bool { +fn is_space<T>(term: &Term<T>, point: Point) -> bool { let cell = &term.grid()[point.line][point.column]; !cell.flags().intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER) && (cell.c == ' ' || cell.c == '\t') } -fn is_wrap<T>(term: &Term<T>, point: Point<usize>) -> bool { - point.line != 0 && term.grid()[point.line][point.column].flags.contains(Flags::WRAPLINE) +/// Check if the cell at a point contains the WRAPLINE flag. +fn is_wrap<T>(term: &Term<T>, point: Point) -> bool { + term.grid()[point].flags.contains(Flags::WRAPLINE) } /// Check if point is at screen boundary. -fn is_boundary<T>(term: &Term<T>, point: Point<usize>, direction: Direction) -> bool { - let total_lines = term.total_lines(); - let num_cols = term.cols(); - (point.line + 1 >= total_lines && point.column.0 == 0 && direction == Direction::Left) - || (point.line == 0 && point.column + 1 >= num_cols && direction == Direction::Right) +fn is_boundary<T>(term: &Term<T>, point: Point, direction: Direction) -> bool { + (point.line <= term.topmost_line() && point.column == 0 && direction == Direction::Left) + || (point.line == term.bottommost_line() + && point.column + 1 >= term.columns() + && direction == Direction::Right) } #[cfg(test)] @@ -647,12 +639,12 @@ mod tests { #[test] fn scroll_semantic() { let mut term = term(); - term.grid_mut().scroll_up(&(Line(0)..Line(20)), Line(5)); + term.grid_mut().scroll_up(&(Line(0)..Line(20)), 5); let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(0))); cursor = cursor.motion(&mut term, ViMotion::SemanticLeft); - assert_eq!(cursor.point, Point::new(Line(0), Column(0))); + assert_eq!(cursor.point, Point::new(Line(-5), Column(0))); assert_eq!(term.grid().display_offset(), 5); cursor = cursor.motion(&mut term, ViMotion::SemanticRight); @@ -660,7 +652,7 @@ mod tests { assert_eq!(term.grid().display_offset(), 0); cursor = cursor.motion(&mut term, ViMotion::SemanticLeftEnd); - assert_eq!(cursor.point, Point::new(Line(0), Column(0))); + assert_eq!(cursor.point, Point::new(Line(-5), Column(0))); assert_eq!(term.grid().display_offset(), 5); cursor = cursor.motion(&mut term, ViMotion::SemanticRightEnd); @@ -723,12 +715,12 @@ mod tests { #[test] fn scroll_word() { let mut term = term(); - term.grid_mut().scroll_up(&(Line(0)..Line(20)), Line(5)); + term.grid_mut().scroll_up(&(Line(0)..Line(20)), 5); let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(0))); cursor = cursor.motion(&mut term, ViMotion::WordLeft); - assert_eq!(cursor.point, Point::new(Line(0), Column(0))); + assert_eq!(cursor.point, Point::new(Line(-5), Column(0))); assert_eq!(term.grid().display_offset(), 5); cursor = cursor.motion(&mut term, ViMotion::WordRight); @@ -736,7 +728,7 @@ mod tests { assert_eq!(term.grid().display_offset(), 0); cursor = cursor.motion(&mut term, ViMotion::WordLeftEnd); - assert_eq!(cursor.point, Point::new(Line(0), Column(0))); + assert_eq!(cursor.point, Point::new(Line(-5), Column(0))); assert_eq!(term.grid().display_offset(), 5); cursor = cursor.motion(&mut term, ViMotion::WordRightEnd); |