From 3bd5ac221ab3b122962063edd1f4c10f9f2d117f Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Tue, 30 Mar 2021 23:25:38 +0000 Subject: 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. --- alacritty_terminal/src/grid/mod.rs | 302 +++++++++++++-------------------- alacritty_terminal/src/grid/resize.rs | 134 ++++++++------- alacritty_terminal/src/grid/row.rs | 24 +-- alacritty_terminal/src/grid/storage.rs | 156 +++++++---------- alacritty_terminal/src/grid/tests.rs | 242 +++++++++++++------------- 5 files changed, 384 insertions(+), 474 deletions(-) (limited to 'alacritty_terminal/src/grid') 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 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 { @@ -120,10 +120,10 @@ pub struct Grid { raw: Storage, /// 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 { } impl Grid { - pub fn new(lines: Line, cols: Column, max_scroll_limit: usize) -> Grid { + pub fn new(lines: usize, columns: usize, max_scroll_limit: usize) -> Grid { 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 Grid { 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 Grid { 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 Grid { } #[inline] - pub fn scroll_down(&mut self, region: &Range, positions: Line) + pub fn scroll_down(&mut self, region: &Range, positions: usize) where T: ResetDiscriminant, 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 Grid { // // 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 Grid { /// Move lines at the bottom toward the top. /// /// This is the performance-sensitive part of scrolling. - pub fn scroll_up(&mut self, region: &Range, positions: Line) + pub fn scroll_up(&mut self, region: &Range, positions: usize) where T: ResetDiscriminant, 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 Grid { // 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 Grid { // // 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 Grid { 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 Grid { 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 Grid { 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 Grid { 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) -> 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>, - ) -> Option> { - 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 { - 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 Grid { 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 Grid { } #[inline] - pub fn iter_from(&self, point: Point) -> 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 PartialEq for Grid { 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 IndexMut for Grid { } } -impl Index for Grid { - type Output = Row; - - #[inline] - fn index(&self, index: usize) -> &Row { - &self.raw[index] - } -} - -impl IndexMut for Grid { - #[inline] - fn index_mut(&mut self, index: usize) -> &mut Row { - &mut self.raw[index] - } -} - -impl Index> for Grid { - type Output = T; - - #[inline] - fn index(&self, point: Point) -> &T { - &self[point.line][point.column] - } -} - -impl IndexMut> for Grid { - #[inline] - fn index_mut(&mut self, point: Point) -> &mut T { - &mut self[point.line][point.column] - } -} - impl Index for Grid { 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 Dimensions for Grid { } #[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 { - pub point: Point, +pub struct Indexed { + pub point: Point, pub cell: T, } -impl Deref for Indexed { +impl Deref for Indexed { type Target = T; #[inline] @@ -629,12 +559,12 @@ pub struct GridIterator<'a, T> { grid: &'a Grid, /// Current position of the iterator within the grid. - point: Point, + point: Point, } impl<'a, T> GridIterator<'a, T> { /// Current iteratior position. - pub fn point(&self) -> Point { + 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 { - 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 { - 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, DisplayIterTakeFun<'a, T>>, DisplayIterMapFun<'a, T>>; +pub type DisplayIter<'a, T> = TakeWhile, DisplayIterTakeFun<'a, T>>; type DisplayIterTakeFun<'a, T> = Box) -> bool>; -type DisplayIterMapFun<'a, T> = Box) -> 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 Grid { /// Resize the grid's width and/or height. - pub fn resize(&mut self, reflow: bool, lines: Line, cols: Column) + pub fn resize(&mut self, reflow: bool, lines: usize, columns: usize) where T: ResetDiscriminant, D: PartialEq, @@ -25,9 +25,9 @@ impl Grid { 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 Grid { /// 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(&mut self, new_line_count: Line) + fn grow_lines(&mut self, target: usize) where T: ResetDiscriminant, 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 Grid { /// of the terminal window. /// /// Alacritty takes the same approach. - fn shrink_lines(&mut self, target: Line) + fn shrink_lines(&mut self, target: usize) where T: ResetDiscriminant, 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| -> 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> = Vec::with_capacity(self.raw.len()); let mut cursor_line_delta = 0; @@ -138,12 +138,12 @@ impl Grid { 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 Grid { // 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 Grid { } // 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 Grid { } /// 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 Grid { 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 Grid { 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 Grid { }; // 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 Grid { 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 Grid { .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 Grid { 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 Grid { // Reverse iterator and use it as the new grid storage. let mut reversed: Vec> = 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 Row { /// Create a new terminal row. /// /// Ideally the `template` should be `Copy` in all performance sensitive scenarios. - pub fn new(columns: Column) -> Row { - debug_assert!(columns.0 >= 1); + pub fn new(columns: usize) -> Row { + debug_assert!(columns >= 1); - let mut inner: Vec = Vec::with_capacity(columns.0); + let mut inner: Vec = 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 Row { /// 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> + pub fn shrink(&mut self, columns: usize) -> Option> 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 { 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 PartialEq for Storage { impl Storage { #[inline] - pub fn with_capacity(visible_lines: Line, cols: Column) -> Storage + pub fn with_capacity(visible_lines: usize, columns: usize) -> Storage 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 Storage { /// 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 Storage { /// 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 Storage { 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 Storage { 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. /// /// Exploits the known size of Row to produce a slightly more efficient @@ -155,7 +147,7 @@ impl Storage { /// 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::>(), mem::size_of::() * 4); let a = self.compute_index(a); @@ -222,10 +214,14 @@ impl Storage { /// 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 Storage { } } -impl Index for Storage { - type Output = Row; - - #[inline] - fn index(&self, index: usize) -> &Self::Output { - &self.inner[self.compute_index(index)] - } -} - -impl IndexMut for Storage { - #[inline] - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - let index = self.compute_index(index); // borrowck - &mut self.inner[index] - } -} - impl Index for Storage { type Output = Row; #[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 IndexMut for Storage { #[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::::with_capacity(Line(3), Column(1)); + let storage = Storage::::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::::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::::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::::with_capacity(Line(1), Column(1)); - let _ = &storage[2]; + let storage = Storage::::with_capacity(1, 1); + let _ = &storage[Line(-1)]; } #[test] fn rotate() { - let mut storage = Storage::::with_capacity(Line(3), Column(1)); + let mut storage = Storage::::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 = 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 = 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 = 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 = 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 = 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 = 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 { - 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::::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::::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::::new(Line(10), Column(1), 0); + let mut grid = Grid::::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::(&(Line(0)..Line(10)), Line(2)); + grid.scroll_up::(&(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::::new(Line(10), Column(1), 0); + let mut grid = Grid::::new(10, 1, 0); + for i in 0..10 { + grid[Line(i as i32)][Column(0)] = i; + } + + grid.scroll_down::(&(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::::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::(&(Line(0)..Line(10)), Line(2)); + grid.scroll_down::(&(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::::new(Line(5), Column(5), 0); + let mut grid = Grid::::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::::new(Line(1), Column(5), 2); + let mut grid = Grid::::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::::new(Line(1), Column(5), 2); + let mut grid = Grid::::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::::new(Line(1), Column(5), 3); + let mut grid = Grid::::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::::new(Line(2), Column(2), 0); + let mut grid = Grid::::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::::new(Line(3), Column(2), 0); + let mut grid = Grid::::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::::new(Line(2), Column(2), 0); + let mut grid = Grid::::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::::new(Line(1), Column(5), 2); + let mut grid = Grid::::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 -- cgit v1.2.3-54-g00ecf