diff options
Diffstat (limited to 'src/grid')
-rw-r--r-- | src/grid/mod.rs | 134 | ||||
-rw-r--r-- | src/grid/row.rs | 93 | ||||
-rw-r--r-- | src/grid/storage.rs | 207 | ||||
-rw-r--r-- | src/grid/tests.rs | 174 |
4 files changed, 559 insertions, 49 deletions
diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 80289cd6..837d9b0e 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -64,6 +64,12 @@ impl<T: PartialEq> ::std::cmp::PartialEq for Grid<T> { } } +pub trait GridCell { + fn is_empty(&self) -> bool; + fn is_wrap(&self) -> bool; + fn set_wrap(&mut self, wrap: bool); +} + /// Represents the terminal display contents #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Grid<T> { @@ -123,7 +129,7 @@ pub enum ViewportPosition { Below, } -impl<T: Copy + Clone> Grid<T> { +impl<T: GridCell + Copy + Clone> Grid<T> { pub fn new(lines: index::Line, cols: index::Column, scrollback: usize, template: T) -> Grid<T> { let raw = Storage::with_capacity(lines, Row::new(cols, &template)); Grid { @@ -197,6 +203,7 @@ impl<T: Copy + Clone> Grid<T> { &mut self, lines: index::Line, cols: index::Column, + cursor_pos: &mut Point, template: &T, ) { // Check that there's actually work to do and return early if not @@ -211,8 +218,8 @@ impl<T: Copy + Clone> Grid<T> { } match self.cols.cmp(&cols) { - Ordering::Less => self.grow_cols(cols, template), - Ordering::Greater => self.shrink_cols(cols), + Ordering::Less => self.grow_cols(cols, cursor_pos, template), + Ordering::Greater => self.shrink_cols(cols, cursor_pos, template), Ordering::Equal => (), } } @@ -259,20 +266,125 @@ impl<T: Copy + Clone> Grid<T> { } self.scroll_limit = self.scroll_limit.saturating_sub(*lines_added); - } + self.display_offset = self.display_offset.saturating_sub(*lines_added); + } + + fn grow_cols(&mut self, cols: index::Column, cursor_pos: &mut Point, template: &T) { + // Truncate all buffered lines + self.raw.grow_hidden(cols, template); + + let max_lines = self.lines.0 + self.max_scroll_limit; + + // Iterate backwards with indices for mutation during iteration + let mut i = self.raw.len(); + while i > 0 { + i -= 1; + + // Grow the current line if there's wrapped content available + while i >= 1 + && self.raw[i].len() < cols.0 + && self.raw[i].last().map(GridCell::is_wrap) == Some(true) + { + // Remove wrap flag before appending additional cells + if let Some(cell) = self.raw[i].last_mut() { + cell.set_wrap(false); + } + + // Append as many cells from the next line as possible + let len = min(self.raw[i - 1].len(), cols.0 - self.raw[i].len()); + let mut cells = self.raw[i - 1].front_split_off(len); + self.raw[i].append(&mut cells); + + if self.raw[i - 1].is_empty() { + // Remove following line if all cells have been drained + self.raw.remove(i - 1); + + if self.raw.len() < self.lines.0 || self.scroll_limit == 0 { + // Add new line and move lines up if we can't pull from history + self.raw.insert(0, Row::new(cols, template), max_lines); + cursor_pos.line = Line(cursor_pos.line.saturating_sub(1)); + } else { + // Make sure viewport doesn't move if line is outside of the visible area + if i < self.display_offset { + self.display_offset = self.display_offset.saturating_sub(1); + } + + // Remove one line from scrollback, since we just moved it to the viewport + self.scroll_limit = self.scroll_limit.saturating_sub(1); + self.display_offset = min(self.display_offset, self.scroll_limit); + i -= 1; + } + } else if let Some(cell) = self.raw[i].last_mut() { + // Set wrap flag if next line still has cells + cell.set_wrap(true); + } + } - fn grow_cols(&mut self, cols: index::Column, template: &T) { - for row in self.raw.iter_mut_raw() { - row.grow(cols, template); + // Fill remaining cells + if self.raw[i].len() < cols.0 { + self.raw[i].grow(cols, template); + } } - // Update self cols self.cols = cols; } - fn shrink_cols(&mut self, cols: index::Column) { - for row in self.raw.iter_mut_raw() { - row.shrink(cols); + fn shrink_cols(&mut self, cols: index::Column, cursor_pos: &mut Point, template: &T) { + // Truncate all buffered lines + self.raw.shrink_hidden(cols); + + let max_lines = self.lines.0 + self.max_scroll_limit; + + // Iterate backwards with indices for mutation during iteration + let mut i = self.raw.len(); + while i > 0 { + i -= 1; + + if let Some(mut new_row) = self.raw[i].shrink(cols) { + // Set line as wrapped if cells got removed + if let Some(cell) = self.raw[i].last_mut() { + cell.set_wrap(true); + } + + if Some(true) == new_row.last().map(|c| c.is_wrap() && i >= 1) + && new_row.len() < cols.0 + { + // Make sure previous wrap flag doesn't linger around + if let Some(cell) = new_row.last_mut() { + cell.set_wrap(false); + } + + // Add removed cells to start of next row + self.raw[i - 1].append_front(new_row); + } else { + // Make sure viewport doesn't move if line is outside of the visible area + if i < self.display_offset { + self.display_offset = min(self.display_offset + 1, self.max_scroll_limit); + } + + // Make sure new row is at least as long as new width + let occ = new_row.len(); + if occ < cols.0 { + new_row.append(&mut vec![*template; cols.0 - occ]); + } + let row = Row::from_vec(new_row, occ); + + // Add new row with all removed cells + self.raw.insert(i, row, max_lines); + + if cursor_pos.line >= self.lines - 1 { + // Increase scrollback history + self.scroll_limit = min(self.scroll_limit + 1, self.max_scroll_limit); + + // Since inserted might exceed cols, we need to check the same line again + i += 1; + } else { + // Pull content down if cursor is not at the bottom + self.raw.rotate(1); + cursor_pos.line += 1; + } + } + } } self.cols = cols; diff --git a/src/grid/row.rs b/src/grid/row.rs index 72c79b02..ef27f040 100644 --- a/src/grid/row.rs +++ b/src/grid/row.rs @@ -16,9 +16,10 @@ use std::ops::{Index, IndexMut}; use std::ops::{Range, RangeTo, RangeFrom, RangeFull, RangeToInclusive}; -use std::cmp::{max, min}; +use std::cmp::{min, max}; use std::slice; +use crate::grid::GridCell; use crate::index::Column; /// A row in the grid @@ -43,7 +44,7 @@ impl<T: PartialEq> PartialEq for Row<T> { } } -impl<T: Copy + Clone> Row<T> { +impl<T: Copy> Row<T> { pub fn new(columns: Column, template: &T) -> Row<T> { Row { inner: vec![*template; *columns], @@ -52,52 +53,102 @@ impl<T: Copy + Clone> Row<T> { } pub fn grow(&mut self, cols: Column, template: &T) { - assert!(self.len() < * cols); + if self.inner.len() >= cols.0 { + return; + } + + self.inner.append(&mut vec![*template; cols.0 - self.len()]); + } + + pub fn shrink(&mut self, cols: Column) -> Option<Vec<T>> + where + T: GridCell + { + if self.inner.len() <= cols.0 { + return None; + } + + // Split off cells for a new row + let mut new_row = self.inner.split_off(cols.0); + 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); - while self.len() != *cols { - self.inner.push(*template); + if new_row.is_empty() { + None + } else { + Some(new_row) } } /// Resets contents to the contents of `other` #[inline(never)] pub fn reset(&mut self, other: &T) { - let occ = self.occ; - for item in &mut self.inner[..occ] { + for item in &mut self.inner[..self.occ] { *item = *other; } - self.occ = 0; } } #[allow(clippy::len_without_is_empty)] impl<T> Row<T> { - pub fn shrink(&mut self, cols: Column) { - while self.len() != *cols { - self.inner.pop(); + #[inline] + pub fn from_vec(vec: Vec<T>, occ: usize) -> Row<T> { + Row { + inner: vec, + occ, } - - self.occ = min(self.occ, *cols); } + #[inline] pub fn len(&self) -> usize { self.inner.len() } - pub fn iter(&self) -> slice::Iter<'_, T> { - self.inner.iter() + #[inline] + pub fn last(&self) -> Option<&T> { + self.inner.last() } -} + #[inline] + pub fn last_mut(&mut self) -> Option<&mut T> { + self.occ = self.inner.len(); + self.inner.last_mut() + } -impl<'a, T> IntoIterator for &'a Row<T> { - type Item = &'a T; - type IntoIter = slice::Iter<'a, T>; + #[inline] + pub fn append(&mut self, vec: &mut Vec<T>) + where + T: GridCell + { + self.inner.append(vec); + self.occ = self.inner.iter().rposition(|c| !c.is_empty()).map(|i| i + 1).unwrap_or(0); + } #[inline] - fn into_iter(self) -> slice::Iter<'a, T> { - self.iter() + pub fn append_front(&mut self, mut vec: Vec<T>) { + self.occ += vec.len(); + vec.append(&mut self.inner); + self.inner = vec; + } + + #[inline] + pub fn is_empty(&self) -> bool + where + T: GridCell + { + self.inner.iter().all(|c| c.is_empty()) + } + + #[inline] + pub fn front_split_off(&mut self, at: usize) -> Vec<T> { + self.occ = self.occ.saturating_sub(at); + + let mut split = self.inner.split_off(at); + std::mem::swap(&mut split, &mut self.inner); + split } } diff --git a/src/grid/storage.rs b/src/grid/storage.rs index 87a129d0..6a119ead 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -12,11 +12,11 @@ /// implementation is provided. Anything from Vec that should be exposed must be /// done so manually. use std::ops::{Index, IndexMut}; -use std::slice; use static_assertions::assert_eq_size; -use crate::index::Line; +use crate::index::{Column, Line}; +use crate::grid::GridCell; use super::Row; /// Maximum number of invisible lines before buffer is resized @@ -196,6 +196,7 @@ impl<T> Storage<T> { self.len } + #[inline] /// Compute actual index in underlying storage given the requested index. fn compute_index(&self, requested: usize) -> usize { debug_assert!(requested < self.len); @@ -250,18 +251,7 @@ impl<T> Storage<T> { } } - /// Iterate over *all* entries in the underlying buffer - /// - /// This includes hidden entries. - /// - /// XXX This suggests that Storage is a leaky abstraction. Ultimately, this - /// is needed because of the grow lines functionality implemented on - /// this type, and maybe that's where the leak is necessitating this - /// accessor. - pub fn iter_mut_raw<'a>(&'a mut self) -> slice::IterMut<'a, Row<T>> { - self.inner.iter_mut() - } - + #[inline] pub fn rotate(&mut self, count: isize) { debug_assert!(count.abs() as usize <= self.inner.len()); @@ -270,9 +260,75 @@ impl<T> Storage<T> { } // Fast path + #[inline] pub fn rotate_up(&mut self, count: usize) { self.zero = (self.zero + count) % self.inner.len(); } + + #[inline] + pub fn insert(&mut self, index: usize, row: Row<T>, max_lines: usize) { + let index = self.compute_index(index); + self.inner.insert(index, row); + + if index < self.zero { + self.zero += 1; + } + + if self.len < max_lines { + self.len += 1; + } + } + + #[inline] + pub fn remove(&mut self, index: usize) -> Row<T> { + let index = self.compute_index(index); + if index < self.zero { + self.zero -= 1; + } + self.len -= 1; + + self.inner.remove(index) + } + + /// Shrink columns of hidden buffered lines. + /// + /// XXX This suggests that Storage is a leaky abstraction. Ultimately, this + /// is needed because of the grow/shrink lines functionality. + #[inline] + pub fn shrink_hidden(&mut self, cols: Column) + where + T: GridCell + Copy + { + let start = self.zero + self.len; + let end = self.zero + self.inner.len(); + for mut i in start..end { + if i >= self.inner.len() { + i -= self.inner.len(); + } + + self.inner[i].shrink(cols); + } + } + + /// Grow columns of hidden buffered lines. + /// + /// XXX This suggests that Storage is a leaky abstraction. Ultimately, this + /// is needed because of the grow/shrink lines functionality. + #[inline] + pub fn grow_hidden(&mut self, cols: Column, template: &T) + where + T: Copy + Clone + { + let start = self.zero + self.len; + let end = self.zero + self.inner.len(); + for mut i in start..end { + if i >= self.inner.len() { + i -= self.inner.len(); + } + + self.inner[i].grow(cols, template); + } + } } impl<T> Index<usize> for Storage<T> { @@ -308,9 +364,6 @@ impl<T> IndexMut<Line> for Storage<T> { } } -#[cfg(test)] -use crate::index::Column; - /// Grow the buffer one line at the end of the buffer /// /// Before: @@ -693,3 +746,123 @@ fn initialize() { assert_eq!(storage.zero, shrinking_expected.zero); assert_eq!(storage.len, shrinking_expected.len); } + +#[test] +fn insert() { + // Setup storage area + let mut storage = Storage { + inner: vec![ + Row::new(Column(1), &'4'), + Row::new(Column(1), &'5'), + Row::new(Column(1), &'0'), + Row::new(Column(1), &'1'), + Row::new(Column(1), &'2'), + Row::new(Column(1), &'3'), + ], + zero: 2, + visible_lines: Line(0), + len: 6, + }; + + // Initialize additional lines + storage.insert(2, Row::new(Column(1), &'-'), 100); + + // Make sure the lines are present and at the right location + let shrinking_expected = Storage { + inner: vec![ + Row::new(Column(1), &'4'), + Row::new(Column(1), &'5'), + Row::new(Column(1), &'0'), + Row::new(Column(1), &'1'), + Row::new(Column(1), &'-'), + Row::new(Column(1), &'2'), + Row::new(Column(1), &'3'), + ], + zero: 2, + visible_lines: Line(0), + len: 7, + }; + assert_eq!(storage.inner, shrinking_expected.inner); + assert_eq!(storage.zero, shrinking_expected.zero); + assert_eq!(storage.len, shrinking_expected.len); +} + +#[test] +fn insert_truncate_max() { + // Setup storage area + let mut storage = Storage { + inner: vec![ + Row::new(Column(1), &'4'), + Row::new(Column(1), &'5'), + Row::new(Column(1), &'0'), + Row::new(Column(1), &'1'), + Row::new(Column(1), &'2'), + Row::new(Column(1), &'3'), + ], + zero: 2, + visible_lines: Line(0), + len: 6, + }; + + // Initialize additional lines + storage.insert(2, Row::new(Column(1), &'-'), 6); + + // Make sure the lines are present and at the right location + let shrinking_expected = Storage { + inner: vec![ + Row::new(Column(1), &'4'), + Row::new(Column(1), &'5'), + Row::new(Column(1), &'0'), + Row::new(Column(1), &'1'), + Row::new(Column(1), &'-'), + Row::new(Column(1), &'2'), + Row::new(Column(1), &'3'), + ], + zero: 2, + visible_lines: Line(0), + len: 6, + }; + assert_eq!(storage.inner, shrinking_expected.inner); + assert_eq!(storage.zero, shrinking_expected.zero); + assert_eq!(storage.len, shrinking_expected.len); +} + +#[test] +fn insert_at_zero() { + // Setup storage area + let mut storage = Storage { + inner: vec![ + Row::new(Column(1), &'4'), + Row::new(Column(1), &'5'), + Row::new(Column(1), &'0'), + Row::new(Column(1), &'1'), + Row::new(Column(1), &'2'), + Row::new(Column(1), &'3'), + ], + zero: 2, + visible_lines: Line(0), + len: 6, + }; + + // Initialize additional lines + storage.insert(0, Row::new(Column(1), &'-'), 6); + + // Make sure the lines are present and at the right location + let shrinking_expected = Storage { + inner: vec![ + Row::new(Column(1), &'4'), + Row::new(Column(1), &'5'), + Row::new(Column(1), &'-'), + Row::new(Column(1), &'0'), + Row::new(Column(1), &'1'), + Row::new(Column(1), &'2'), + Row::new(Column(1), &'3'), + ], + zero: 2, + visible_lines: Line(0), + len: 6, + }; + assert_eq!(storage.inner, shrinking_expected.inner); + assert_eq!(storage.zero, shrinking_expected.zero); + assert_eq!(storage.len, shrinking_expected.len); +} diff --git a/src/grid/tests.rs b/src/grid/tests.rs index 560bb0fe..82edda69 100644 --- a/src/grid/tests.rs +++ b/src/grid/tests.rs @@ -16,6 +16,20 @@ use super::{Grid, BidirectionalIterator}; use crate::index::{Point, Line, Column}; +use crate::term::cell::{Cell, Flags}; +use crate::grid::GridCell; + +impl GridCell for usize { + fn is_empty(&self) -> bool { + false + } + + fn is_wrap(&self) -> bool { + false + } + + fn set_wrap(&mut self, _wrap: bool) {} +} // Scroll up moves lines upwards #[test] @@ -123,3 +137,163 @@ fn test_iter() { assert_eq!(None, final_iter.next()); assert_eq!(Some(&23), final_iter.prev()); } + +#[test] +fn shrink_reflow() { + let mut grid = Grid::new(Line(1), Column(5), 2, cell('x')); + 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(Line(1), Column(2), &mut Point::new(Line(0), Column(0)), &Cell::default()); + + assert_eq!(grid.len(), 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[1].len(), 2); + assert_eq!(grid[1][Column(0)], cell('3')); + assert_eq!(grid[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()); +} + +#[test] +fn shrink_reflow_twice() { + let mut grid = Grid::new(Line(1), Column(5), 2, cell('x')); + 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(Line(1), Column(4), &mut Point::new(Line(0), Column(0)), &Cell::default()); + grid.resize(Line(1), Column(2), &mut Point::new(Line(0), Column(0)), &Cell::default()); + + assert_eq!(grid.len(), 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[1].len(), 2); + assert_eq!(grid[1][Column(0)], cell('3')); + assert_eq!(grid[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()); +} + +#[test] +fn shrink_reflow_empty_cell_inside_line() { + let mut grid = Grid::new(Line(1), Column(5), 3, cell('x')); + 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(Line(1), Column(2), &mut Point::new(Line(0), Column(0)), &Cell::default()); + + assert_eq!(grid.len(), 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[0].len(), 2); + assert_eq!(grid[0][Column(0)], cell('3')); + assert_eq!(grid[0][Column(1)], cell('4')); + + grid.resize(Line(1), Column(1), &mut Point::new(Line(0), Column(0)), &Cell::default()); + + assert_eq!(grid.len(), 4); + + assert_eq!(grid[3].len(), 1); + assert_eq!(grid[3][Column(0)], wrap_cell('1')); + + assert_eq!(grid[2].len(), 1); + assert_eq!(grid[2][Column(0)], wrap_cell(' ')); + + assert_eq!(grid[1].len(), 1); + assert_eq!(grid[1][Column(0)], wrap_cell('3')); + + assert_eq!(grid[0].len(), 1); + assert_eq!(grid[0][Column(0)], cell('4')); +} + +#[test] +fn grow_reflow() { + let mut grid = Grid::new(Line(2), Column(2), 0, cell('x')); + 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(Line(2), Column(3), &mut Point::new(Line(0), Column(0)), &Cell::default()); + + assert_eq!(grid.len(), 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')); + + // 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()); +} + +#[test] +fn grow_reflow_multiline() { + let mut grid = Grid::new(Line(3), Column(2), 0, cell('x')); + 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)] = wrap_cell('4'); + grid[Line(2)][Column(0)] = cell('5'); + grid[Line(2)][Column(1)] = cell('6'); + + grid.resize(Line(3), Column(6), &mut Point::new(Line(0), Column(0)), &Cell::default()); + + assert_eq!(grid.len(), 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')); + + // Make sure rest of grid is empty + // https://github.com/rust-lang/rust-clippy/issues/3788 + #[allow(clippy::needless_range_loop)] + for r in 0..2 { + assert_eq!(grid[r].len(), 6); + for c in 0..6 { + assert_eq!(grid[r][Column(c)], Cell::default()); + } + } +} + +fn cell(c: char) -> Cell { + let mut cell = Cell::default(); + cell.c = c; + cell +} + +fn wrap_cell(c: char) -> Cell { + let mut cell = cell(c); + cell.flags.insert(Flags::WRAPLINE); + cell +} |