diff options
author | Joe Wilm <joe@jwilm.com> | 2018-02-15 18:35:49 -0800 |
---|---|---|
committer | Joe Wilm <joe@jwilm.com> | 2018-03-07 09:47:21 -0800 |
commit | cfc4ebfd6a2fb39ae98ba37c215a4a9141865e01 (patch) | |
tree | 604b757b2d0f2754359a73b2bf682e6e5d0650ed /src/grid | |
parent | 6d1d41f8c77968547b43603db51a5efeac21a400 (diff) | |
download | alacritty-cfc4ebfd6a2fb39ae98ba37c215a4a9141865e01.tar.gz alacritty-cfc4ebfd6a2fb39ae98ba37c215a4a9141865e01.zip |
checkpoint: very basic scrolling works
Things that do not work
- Limiting how far back in the buffer it's possible to scroll
- Selections (need to transform to buffer offsets)
Diffstat (limited to 'src/grid')
-rw-r--r-- | src/grid/mod.rs | 200 | ||||
-rw-r--r-- | src/grid/row.rs | 18 | ||||
-rw-r--r-- | src/grid/storage.rs | 40 |
3 files changed, 191 insertions, 67 deletions
diff --git a/src/grid/mod.rs b/src/grid/mod.rs index f0bd2f6f..820cd9e2 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -12,17 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! A generic 2d grid implementation optimized for use in a terminal. -//! -//! The current implementation uses a vector of vectors to store cell data. -//! Reimplementing the store as a single contiguous vector may be desirable in -//! the future. Rotation and indexing would need to be reconsidered at that -//! time; rotation currently reorganize Vecs in the lines Vec, and indexing with -//! ranges is currently supported. - -use std::cmp::Ordering; -use std::iter::IntoIterator; -use std::ops::{Deref, Range, RangeTo, RangeFrom, RangeFull, Index, IndexMut}; +//! A specialized 2d grid implementation optimized for use in a terminal. + +use std::cmp::{max, Ordering}; +use std::ops::{Deref, Range, Index, IndexMut, RangeTo, RangeFrom, RangeFull}; use index::{self, Point, Line, Column, IndexRange, RangeInclusive}; @@ -36,7 +29,7 @@ mod storage; use self::storage::Storage; /// Lines to keep in scrollback buffer -const SCROLLBACK_LINES: usize = 100_000; +const SCROLLBACK_LINES: usize = 10_000; /// Convert a type to a linear index range. pub trait ToRange { @@ -105,8 +98,12 @@ pub struct GridIterator<'a, T: 'a> { } impl<T: Copy + Clone> Grid<T> { + pub fn scroll_display(&mut self, count: isize) { + self.display_offset = max((self.display_offset as isize) + count, 0isize) as usize; + } + pub fn new(lines: index::Line, cols: index::Column, template: T) -> Grid<T> { - let mut raw = Storage::with_capacity(*lines + SCROLLBACK_LINES); + let mut raw = Storage::with_capacity(*lines + SCROLLBACK_LINES, lines); let template_row = Row::new(cols, &template); // Allocate all lines in the buffer, including scrollback history @@ -125,6 +122,7 @@ impl<T: Copy + Clone> Grid<T> { template_row, template, temp: Vec::new(), + display_offset: 0, } } @@ -147,12 +145,23 @@ impl<T: Copy + Clone> Grid<T> { } } - fn grow_lines(&mut self, lines: index::Line) { - for _ in IndexRange(self.num_lines()..lines) { - self.raw.push(self.template_row.clone()); - } + /// Add lines to the visible area + /// + /// The behavior in Terminal.app and iTerm.app is to keep the cursor at the + /// bottom of the screen as long as there is scrollback available. Once + /// scrollback is exhausted, new lines are simply added to the bottom of the + /// screen. + /// + /// Alacritty takes a different approach. Rather than trying to move with + /// the scrollback, we simply pull additional lines from the back of the + /// buffer in order to populate the new area. + fn grow_lines(&mut self, target: index::Line) { + let delta = target - self.lines; - self.lines = lines; + self.raw.set_visible_lines(target); + self.lines = target; + + self.scroll_up(&(Line(0)..target), delta); } fn grow_cols(&mut self, cols: index::Column) { @@ -167,6 +176,37 @@ impl<T: Copy + Clone> Grid<T> { self.template_row.grow(cols, &self.template); } + /// Remove lines from the visible area + /// + /// The behavior in Terminal.app and iTerm.app is to keep the cursor at the + /// bottom of the screen. This is achieved by pushing history "out the top" + /// of the terminal window. + /// + /// Alacritty takes the same approach. + fn shrink_lines(&mut self, target: index::Line) { + // TODO handle disabled scrollback + // while index::Line(self.raw.len()) != lines { + // self.raw.pop(); + // } + + let prev = self.lines; + + self.raw.rotate(*prev as isize - *target as isize); + self.raw.set_visible_lines(target); + self.lines = target; + } + + /// Convert a Line index (active region) to a buffer offset + /// + /// # Panics + /// + /// This method will panic if `Line` is larger than the grid dimensions + pub fn line_to_offset(&self, line: index::Line) -> usize { + assert!(line < self.num_lines()); + + *(self.num_lines() - line - 1) + } + #[inline] pub fn scroll_down(&mut self, region: &Range<index::Line>, positions: index::Line) { // Whether or not there is a scrolling region active, as long as it @@ -177,12 +217,12 @@ impl<T: Copy + Clone> Grid<T> { if region.start == Line(0) { // Rotate the entire line buffer. If there's a scrolling region // active, the bottom lines are restored in the next step. - self.raw.rotate(-(*positions as isize)); + self.raw.rotate_up(*positions); // Now, restore any scroll region lines for i in IndexRange(region.end .. self.num_lines()) { // First do the swap - self.raw.swap(*i, *i + *positions); + self.raw.swap_lines(i, i + positions); } // Finally, reset recycled lines @@ -192,7 +232,7 @@ impl<T: Copy + Clone> Grid<T> { } else { // Subregion rotation for line in IndexRange((region.start + positions)..region.end).rev() { - self.swap_lines(line, line - positions); + self.raw.swap_lines(line, line - positions); } for line in IndexRange(region.start .. (region.start + positions)) { @@ -214,29 +254,29 @@ impl<T: Copy + Clone> Grid<T> { // Rotate the entire line buffer. If there's a scrolling region // active, the bottom lines are restored in the next step. - self.raw.rotate_up(*positions); + self.raw.rotate(-(*positions as isize)); // Now, restore any lines outside the scroll region for idx in (*region.end .. *self.num_lines()).rev() { // First do the swap - self.raw.swap(idx, idx - *positions); + self.raw.swap_lines(Line(idx), Line(idx) - positions); } // Finally, reset recycled lines // // Recycled lines are just above the end of the scrolling region. for i in 0..*positions { - self.raw[*region.end - i - 1].reset(&self.template_row); + self.raw[region.end - i - 1].reset(&self.template_row); } } else { // Subregion rotation for line in IndexRange(region.start..(region.end - positions)) { - self.swap_lines(line, line + positions); + self.raw.swap_lines(line, line + positions); } // Clear reused lines for line in IndexRange((region.end - positions) .. region.end) { - self.raw[*line].reset(&self.template_row); + self.raw[line].reset(&self.template_row); } } } @@ -248,6 +288,10 @@ impl<T> Grid<T> { self.lines } + pub fn display_iter(&self) -> DisplayIter<T> { + DisplayIter::new(self) + } + #[inline] pub fn num_cols(&self) -> index::Column { self.cols @@ -265,22 +309,14 @@ impl<T> Grid<T> { self.lines > point.line && self.cols > point.col } - /// Swap two lines in the grid - /// - /// This could have used slice::swap internally, but we are able to have - /// better error messages by doing the bounds checking ourselves. - #[inline] - pub fn swap_lines(&mut self, src: index::Line, dst: index::Line) { - self.raw.swap(*src, *dst); - } - - fn shrink_lines(&mut self, lines: index::Line) { - while index::Line(self.raw.len()) != lines { - self.raw.pop(); - } - - self.lines = lines; - } + // /// Swap two lines in the grid + // /// + // /// This could have used slice::swap internally, but we are able to have + // /// better error messages by doing the bounds checking ourselves. + // #[inline] + // pub fn swap_lines(&mut self, src: index::Line, dst: index::Line) { + // self.raw.swap(*src, *dst); + // } fn shrink_cols(&mut self, cols: index::Column) { for row in self.raw.iter_mut() { @@ -335,12 +371,22 @@ impl<'a, T> BidirectionalIterator for GridIterator<'a, T> { } } +/// Index active region by line impl<T> Index<index::Line> for Grid<T> { type Output = Row<T>; #[inline] fn index(&self, index: index::Line) -> &Row<T> { - let index = self.lines.0 - index.0; + &self.raw[index] + } +} + +/// Index with buffer offset +impl<T> Index<usize> for Grid<T> { + type Output = Row<T>; + + #[inline] + fn index(&self, index: usize) -> &Row<T> { &self.raw[index] } } @@ -348,7 +394,6 @@ impl<T> Index<index::Line> for Grid<T> { impl<T> IndexMut<index::Line> for Grid<T> { #[inline] fn index_mut(&mut self, index: index::Line) -> &mut Row<T> { - let index = self.lines.0 - index.0; &mut self.raw[index] } } @@ -369,9 +414,9 @@ impl<'point, T> IndexMut<&'point Point> for Grid<T> { } } -// ================================================================================================= -// Regions ========================================================================================= -// ================================================================================================= +// ------------------------------------------------------------------------------------------------- +// REGIONS +// ------------------------------------------------------------------------------------------------- /// A subset of lines in the grid /// @@ -533,7 +578,7 @@ impl<'a, T> Iterator for RegionIter<'a, T> { if self.cur < self.end { let index = self.cur; self.cur += 1; - Some(&self.raw[*index]) + Some(&self.raw[index]) } else { None } @@ -547,10 +592,67 @@ impl<'a, T> Iterator for RegionIterMut<'a, T> { let index = self.cur; self.cur += 1; unsafe { - Some(&mut *(&mut self.raw[index.0] as *mut _)) + Some(&mut *(&mut self.raw[index] as *mut _)) } } else { None } } } + +// ------------------------------------------------------------------------------------------------- +// DISPLAY ITERATOR +// ------------------------------------------------------------------------------------------------- + +/// Iterates over the visible area accounting for buffer transform +pub struct DisplayIter<'a, T: 'a> { + grid: &'a Grid<T>, + offset: usize, + limit: usize, + col: Column, +} + +impl<'a, T: 'a> DisplayIter<'a, T> { + pub fn new(grid: &'a Grid<T>) -> DisplayIter<'a, T> { + let offset = grid.display_offset + *grid.num_lines() - 1; + let limit = grid.display_offset; + let col = Column(0); + + DisplayIter { grid, offset, col, limit } + } + + pub fn offset(&self) -> usize { + self.offset + } + + pub fn column(&self) -> Column { + self.col + } +} + +impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { + type Item = Indexed<T>; + + #[inline] + fn next(&mut self) -> Option<Self::Item> { + // Make sure indices are valid. Return None if we've reached the end. + if self.col == self.grid.num_cols() { + if self.offset == self.limit { + return None; + } + + self.col = Column(0); + self.offset -= 1; + } + + // Return the next item. + let item = Some(Indexed { + inner: self.grid.raw[self.offset][self.col], + line: Line( *self.grid.lines - 1 - (self.offset - self.limit)), + column: self.col + }); + + self.col += 1; + item + } +} diff --git a/src/grid/row.rs b/src/grid/row.rs index 1c83d45d..6b6af7c8 100644 --- a/src/grid/row.rs +++ b/src/grid/row.rs @@ -22,10 +22,7 @@ use index::Column; /// A row in the grid #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] -pub struct Row<T> { - data: Vec<T>, - id: u64 -} +pub struct Row<T>(Vec<T>); impl<T: Copy + Clone> Row<T> { pub fn new(columns: Column, template: &T) -> Row<T> { @@ -40,9 +37,8 @@ impl<T: Copy + Clone> Row<T> { /// Resets contents to the contents of `other` #[inline] - pub fn reset(&mut self, other: &Row<T>, id: u64) { + pub fn reset(&mut self, other: &Row<T>) { self.copy_from_slice(&**other); - self.id = id; } } @@ -52,16 +48,6 @@ impl<T> Row<T> { self.pop(); } } - - #[inline] - pub fn cells(&self) -> slice::Iter<T> { - self.0.iter() - } - - #[inline] - pub fn cells_mut(&mut self) -> slice::IterMut<T> { - self.0.iter_mut() - } } diff --git a/src/grid/storage.rs b/src/grid/storage.rs index 3c64f700..b4228687 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -13,22 +13,35 @@ /// done so manually. use std::ops::{Index, IndexMut}; +use index::Line; + #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] pub struct Storage<T> { inner: Vec<T>, zero: usize, + visible_lines: Line, } impl<T> Storage<T> { #[inline] - pub fn with_capacity(cap: usize) -> Storage<T> { + pub fn with_capacity(cap: usize, lines: Line) -> Storage<T> { Storage { inner: Vec::with_capacity(cap), - zero: 0 + zero: 0, + visible_lines: lines - 1, } } #[inline] + pub fn capacity(&self) -> usize { + self.inner.capacity() + } + + pub fn set_visible_lines(&mut self, next: Line) { + self.visible_lines = next - 1; + } + + #[inline] pub fn push(&mut self, item: T) { self.inner.push(item) } @@ -55,6 +68,12 @@ impl<T> Storage<T> { self.inner.swap(a, b); } + pub fn swap_lines(&mut self, a: Line, b: Line) { + let a = self.visible_lines - a; + let b = self.visible_lines - b; + self.swap(*a, *b); + } + pub fn iter_mut(&mut self) -> IterMut<T> { IterMut { storage: self, index: 0 } } @@ -88,6 +107,23 @@ impl<T> IndexMut<usize> for Storage<T> { } } +impl<T> Index<Line> for Storage<T> { + type Output = T; + #[inline] + fn index(&self, index: Line) -> &T { + let index = self.visible_lines - index; + &self[*index] + } +} + +impl<T> IndexMut<Line> for Storage<T> { + #[inline] + fn index_mut(&mut self, index: Line) -> &mut T { + let index = self.visible_lines - index; + &mut self[*index] + } +} + pub struct IterMut<'a, T: 'a> { storage: &'a mut Storage<T>, index: usize, |