From 277425956f361677deb1de92b25aeca9cbcd1cd1 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Thu, 12 Oct 2017 20:01:28 -0700 Subject: Move grid Row and tests into submodules This is part of some cleanup for the grid module as a whole. --- src/grid/mod.rs | 495 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 495 insertions(+) create mode 100644 src/grid/mod.rs (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs new file mode 100644 index 00000000..123e13fa --- /dev/null +++ b/src/grid/mod.rs @@ -0,0 +1,495 @@ +// Copyright 2016 Joe Wilm, The Alacritty Project Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// 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::collections::{VecDeque, vec_deque}; +use std::iter::IntoIterator; +use std::ops::{Deref, Range, RangeTo, RangeFrom, Index, IndexMut}; + +use index::{self, Point, Line, Column, IndexRange, RangeInclusive}; + +mod row; +pub use self::row::Row; + +#[cfg(test)] +mod tests; + +/// Convert a type to a linear index range. +pub trait ToRange { + fn to_range(&self) -> RangeInclusive; +} + +/// Bidirection iterator +pub trait BidirectionalIterator: Iterator { + fn prev(&mut self) -> Option; +} + +/// An item in the grid along with its Line and Column. +pub struct Indexed { + pub inner: T, + pub line: Line, + pub column: Column, +} + +impl Deref for Indexed { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + &self.inner + } +} + +/// Represents the terminal display contents +#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] +pub struct Grid { + /// Lines in the grid. Each row holds a list of cells corresponding to the + /// columns in that row. + raw: VecDeque>, + + /// Number of columns + cols: index::Column, + + /// Number of lines. + /// + /// Invariant: lines is equivalent to raw.len() + lines: index::Line, +} + +pub struct GridIterator<'a, T: 'a> { + grid: &'a Grid, + pub cur: Point, +} + +impl Grid { + pub fn new(lines: index::Line, cols: index::Column, template: &T) -> Grid { + let mut raw = VecDeque::with_capacity(*lines); + for _ in IndexRange(index::Line(0)..lines) { + raw.push_back(Row::new(cols, template)); + } + + Grid { + raw, + cols, + lines, + } + } + + pub fn resize(&mut self, lines: index::Line, cols: index::Column, template: &T) { + // Check that there's actually work to do and return early if not + if lines == self.lines && cols == self.cols { + return; + } + + match self.lines.cmp(&lines) { + Ordering::Less => self.grow_lines(lines, template), + Ordering::Greater => self.shrink_lines(lines), + Ordering::Equal => (), + } + + match self.cols.cmp(&cols) { + Ordering::Less => self.grow_cols(cols, template), + Ordering::Greater => self.shrink_cols(cols), + Ordering::Equal => (), + } + } + + fn grow_lines(&mut self, lines: index::Line, template: &T) { + for _ in IndexRange(self.num_lines()..lines) { + self.raw.push_back(Row::new(self.cols, template)); + } + + self.lines = lines; + } + + fn grow_cols(&mut self, cols: index::Column, template: &T) { + for row in self.lines_mut() { + row.grow(cols, template); + } + + self.cols = cols; + } + +} + + + +impl Grid { + #[inline] + pub fn lines(&self) -> vec_deque::Iter> { + self.raw.iter() + } + + #[inline] + pub fn lines_mut(&mut self) -> vec_deque::IterMut> { + self.raw.iter_mut() + } + + #[inline] + pub fn num_lines(&self) -> index::Line { + self.lines + } + + #[inline] + pub fn num_cols(&self) -> index::Column { + self.cols + } + + #[inline] + pub fn scroll_down(&mut self, region: &Range, positions: index::Line) { + if region.start == Line(0) && region.end == self.num_lines() { + // Full rotation + for _ in 0..positions.0 { + let item = self.raw.pop_back().unwrap(); + self.raw.push_front(item); + } + } else { + // Subregion rotation + for line in IndexRange((region.start + positions)..region.end).rev() { + self.swap_lines(line, line - positions); + } + } + } + + #[inline] + pub fn scroll_up(&mut self, region: &Range, positions: index::Line) { + if region.start == Line(0) && region.end == self.num_lines() { + // Full rotation + for _ in 0..positions.0 { + let item = self.raw.pop_front().unwrap(); + self.raw.push_back(item); + } + } else { + // Subregion rotation + for line in IndexRange(region.start..(region.end - positions)) { + self.swap_lines(line, line + positions); + } + } + } + + pub fn iter_from(&self, point: Point) -> GridIterator { + GridIterator { + grid: self, + cur: point, + } + } + + #[inline] + pub fn contains(&self, point: &Point) -> bool { + 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); + } + + #[inline] + pub fn clear(&mut self, func: F) { + let region = index::Line(0)..self.num_lines(); + self.clear_region(region, func); + } + + fn shrink_lines(&mut self, lines: index::Line) { + while index::Line(self.raw.len()) != lines { + self.raw.pop_back(); + } + + self.lines = lines; + } + + fn shrink_cols(&mut self, cols: index::Column) { + for row in self.lines_mut() { + row.shrink(cols); + } + + self.cols = cols; + } +} + +impl<'a, T> Iterator for GridIterator<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + let last_line = self.grid.num_lines() - Line(1); + let last_col = self.grid.num_cols() - Column(1); + match self.cur { + Point { line, col } if + (line == last_line) && + (col == last_col) => None, + Point { col, .. } if + (col == last_col) => { + self.cur.line += Line(1); + self.cur.col = Column(0); + Some(&self.grid[self.cur.line][self.cur.col]) + }, + _ => { + self.cur.col += Column(1); + Some(&self.grid[self.cur.line][self.cur.col]) + } + } + } +} + +impl<'a, T> BidirectionalIterator for GridIterator<'a, T> { + fn prev(&mut self) -> Option { + let num_cols = self.grid.num_cols(); + + match self.cur { + Point { line: Line(0), col: Column(0) } => None, + Point { col: Column(0), .. } => { + self.cur.line -= Line(1); + self.cur.col = num_cols - Column(1); + Some(&self.grid[self.cur.line][self.cur.col]) + }, + _ => { + self.cur.col -= Column(1); + Some(&self.grid[self.cur.line][self.cur.col]) + } + } + } +} + +impl Index for Grid { + type Output = Row; + + #[inline] + fn index(&self, index: index::Line) -> &Row { + &self.raw[index.0] + } +} + +impl IndexMut for Grid { + #[inline] + fn index_mut(&mut self, index: index::Line) -> &mut Row { + &mut self.raw[index.0] + } +} + +impl<'point, T> Index<&'point Point> for Grid { + type Output = T; + + #[inline] + fn index<'a>(&'a self, point: &Point) -> &'a T { + &self.raw[point.line.0][point.col] + } +} + +impl<'point, T> IndexMut<&'point Point> for Grid { + #[inline] + fn index_mut<'a, 'b>(&'a mut self, point: &'b Point) -> &'a mut T { + &mut self.raw[point.line.0][point.col] + } +} + +impl<'a, T> IntoIterator for &'a Grid { + type Item = &'a Row; + type IntoIter = vec_deque::Iter<'a, Row>; + + #[inline] + fn into_iter(self) -> vec_deque::Iter<'a, Row> { + self.raw.iter() + } +} + +pub trait ClearRegion { + fn clear_region(&mut self, region: R, func: F); +} + +macro_rules! clear_region_impl { + ($range:ty) => { + impl ClearRegion<$range, T> for Grid { + fn clear_region(&mut self, region: $range, func: F) { + for row in self.region_mut(region) { + for cell in row { + func(cell); + } + } + } + } + } +} + +clear_region_impl!(Range); +clear_region_impl!(RangeTo); +clear_region_impl!(RangeFrom); + +// ================================================================================================= +// Regions ========================================================================================= +// ================================================================================================= + +/// A subset of lines in the grid +/// +/// May be constructed using Grid::region(..) +pub struct Region<'a, T: 'a> { + start: Line, + end: Line, + raw: &'a VecDeque>, +} + +/// A mutable subset of lines in the grid +/// +/// May be constructed using Grid::region_mut(..) +pub struct RegionMut<'a, T: 'a> { + start: Line, + end: Line, + raw: &'a mut VecDeque>, +} + +pub trait IndexRegion { + /// Get an immutable region of Self + fn region<'a>(&'a self, _: I) -> Region<'a, T>; + + /// Get a mutable region of Self + fn region_mut<'a>(&'a mut self, _: I) -> RegionMut<'a, T>; +} + +impl IndexRegion, T> for Grid { + fn region(&self, index: Range) -> Region { + assert!(index.start < self.num_lines()); + assert!(index.end <= self.num_lines()); + assert!(index.start <= index.end); + Region { + start: index.start, + end: index.end, + raw: &self.raw + } + } + fn region_mut(&mut self, index: Range) -> RegionMut { + assert!(index.start < self.num_lines()); + assert!(index.end <= self.num_lines()); + assert!(index.start <= index.end); + RegionMut { + start: index.start, + end: index.end, + raw: &mut self.raw + } + } +} + +impl IndexRegion, T> for Grid { + fn region(&self, index: RangeTo) -> Region { + assert!(index.end <= self.num_lines()); + Region { + start: Line(0), + end: index.end, + raw: &self.raw + } + } + fn region_mut(&mut self, index: RangeTo) -> RegionMut { + assert!(index.end <= self.num_lines()); + RegionMut { + start: Line(0), + end: index.end, + raw: &mut self.raw + } + } +} + +impl IndexRegion, T> for Grid { + fn region(&self, index: RangeFrom) -> Region { + assert!(index.start < self.num_lines()); + Region { + start: index.start, + end: self.num_lines(), + raw: &self.raw + } + } + fn region_mut(&mut self, index: RangeFrom) -> RegionMut { + assert!(index.start < self.num_lines()); + RegionMut { + start: index.start, + end: self.num_lines(), + raw: &mut self.raw + } + } +} + +pub struct RegionIter<'a, T: 'a> { + end: Line, + cur: Line, + raw: &'a VecDeque>, +} + +pub struct RegionIterMut<'a, T: 'a> { + end: Line, + cur: Line, + raw: &'a mut VecDeque>, +} + +impl<'a, T> IntoIterator for Region<'a, T> { + type Item = &'a Row; + type IntoIter = RegionIter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + RegionIter { + end: self.end, + cur: self.start, + raw: self.raw + } + } +} + +impl<'a, T> IntoIterator for RegionMut<'a, T> { + type Item = &'a mut Row; + type IntoIter = RegionIterMut<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + RegionIterMut { + end: self.end, + cur: self.start, + raw: self.raw + } + } +} + +impl<'a, T> Iterator for RegionIter<'a, T> { + type Item = &'a Row; + fn next(&mut self) -> Option { + if self.cur < self.end { + let index = self.cur; + self.cur += 1; + Some(&self.raw[*index]) + } else { + None + } + } +} + +impl<'a, T> Iterator for RegionIterMut<'a, T> { + type Item = &'a mut Row; + fn next(&mut self) -> Option { + if self.cur < self.end { + let index = self.cur; + self.cur += 1; + unsafe { + Some(&mut *(&mut self.raw[index.0] as *mut _)) + } + } else { + None + } + } +} -- cgit From 6fc0e1ec49561fd9783332b30632471336004aed Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Thu, 12 Oct 2017 20:12:29 -0700 Subject: Eliminate ClearRegion trait --- src/grid/mod.rs | 59 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 29 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 123e13fa..bd994033 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -23,7 +23,7 @@ use std::cmp::Ordering; use std::collections::{VecDeque, vec_deque}; use std::iter::IntoIterator; -use std::ops::{Deref, Range, RangeTo, RangeFrom, Index, IndexMut}; +use std::ops::{Deref, Range, RangeTo, RangeFrom, RangeFull, Index, IndexMut}; use index::{self, Point, Line, Column, IndexRange, RangeInclusive}; @@ -207,12 +207,6 @@ impl Grid { self.raw.swap(*src, *dst); } - #[inline] - pub fn clear(&mut self, func: F) { - let region = index::Line(0)..self.num_lines(); - self.clear_region(region, func); - } - fn shrink_lines(&mut self, lines: index::Line) { while index::Line(self.raw.len()) != lines { self.raw.pop_back(); @@ -315,28 +309,6 @@ impl<'a, T> IntoIterator for &'a Grid { } } -pub trait ClearRegion { - fn clear_region(&mut self, region: R, func: F); -} - -macro_rules! clear_region_impl { - ($range:ty) => { - impl ClearRegion<$range, T> for Grid { - fn clear_region(&mut self, region: $range, func: F) { - for row in self.region_mut(region) { - for cell in row { - func(cell); - } - } - } - } - } -} - -clear_region_impl!(Range); -clear_region_impl!(RangeTo); -clear_region_impl!(RangeFrom); - // ================================================================================================= // Regions ========================================================================================= // ================================================================================================= @@ -359,6 +331,17 @@ pub struct RegionMut<'a, T: 'a> { raw: &'a mut VecDeque>, } +impl<'a, T> RegionMut<'a, T> { + /// Call the provided function for every item in this region + pub fn each(self, func: F) { + for row in self { + for item in row { + func(item) + } + } + } +} + pub trait IndexRegion { /// Get an immutable region of Self fn region<'a>(&'a self, _: I) -> Region<'a, T>; @@ -428,6 +411,24 @@ impl IndexRegion, T> for Grid { } } +impl IndexRegion for Grid { + fn region(&self, _: RangeFull) -> Region { + Region { + start: Line(0), + end: self.num_lines(), + raw: &self.raw + } + } + + fn region_mut(&mut self, _: RangeFull) -> RegionMut { + RegionMut { + start: Line(0), + end: self.num_lines(), + raw: &mut self.raw + } + } +} + pub struct RegionIter<'a, T: 'a> { end: Line, cur: Line, -- cgit From 4ed25009c49b5963d91f4e3c7ead0f4a5b678980 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Thu, 12 Oct 2017 20:29:35 -0700 Subject: Remove some unused methods and impls --- src/grid/mod.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index bd994033..9ca0011a 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -122,7 +122,7 @@ impl Grid { } fn grow_cols(&mut self, cols: index::Column, template: &T) { - for row in self.lines_mut() { + for row in self.raw.iter_mut() { row.grow(cols, template); } @@ -131,19 +131,7 @@ impl Grid { } - - impl Grid { - #[inline] - pub fn lines(&self) -> vec_deque::Iter> { - self.raw.iter() - } - - #[inline] - pub fn lines_mut(&mut self) -> vec_deque::IterMut> { - self.raw.iter_mut() - } - #[inline] pub fn num_lines(&self) -> index::Line { self.lines @@ -216,7 +204,7 @@ impl Grid { } fn shrink_cols(&mut self, cols: index::Column) { - for row in self.lines_mut() { + for row in self.raw.iter_mut() { row.shrink(cols); } -- cgit From 350bb8c800232ce0b27512420e99645ec8b95ef1 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Sun, 14 Jan 2018 10:17:08 -0800 Subject: Use memcpy for resetting row contents In addition to a marginal performance improvement, this simplifies some logic in the Term implementation since now the Grid fully handles row recycling. --- src/grid/mod.rs | 81 ++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 24 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 9ca0011a..e790dd41 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -73,6 +73,15 @@ pub struct Grid { /// /// Invariant: lines is equivalent to raw.len() lines: index::Line, + + /// Template row. + /// + /// This is used to quickly populate new lines and clear recycled lines + /// during scroll wrapping. + template_row: Row, + + /// Template cell for populating template_row + template: T, } pub struct GridIterator<'a, T: 'a> { @@ -80,66 +89,60 @@ pub struct GridIterator<'a, T: 'a> { pub cur: Point, } -impl Grid { - pub fn new(lines: index::Line, cols: index::Column, template: &T) -> Grid { +impl Grid { + pub fn new(lines: index::Line, cols: index::Column, template: T) -> Grid { let mut raw = VecDeque::with_capacity(*lines); + let template_row = Row::new(cols, &template); for _ in IndexRange(index::Line(0)..lines) { - raw.push_back(Row::new(cols, template)); + raw.push_back(template_row.clone()); } Grid { raw, cols, lines, + template_row, + template, } } - pub fn resize(&mut self, lines: index::Line, cols: index::Column, template: &T) { + pub fn resize(&mut self, lines: index::Line, cols: index::Column) { // Check that there's actually work to do and return early if not if lines == self.lines && cols == self.cols { return; } match self.lines.cmp(&lines) { - Ordering::Less => self.grow_lines(lines, template), + Ordering::Less => self.grow_lines(lines), Ordering::Greater => self.shrink_lines(lines), Ordering::Equal => (), } match self.cols.cmp(&cols) { - Ordering::Less => self.grow_cols(cols, template), + Ordering::Less => self.grow_cols(cols), Ordering::Greater => self.shrink_cols(cols), Ordering::Equal => (), } } - fn grow_lines(&mut self, lines: index::Line, template: &T) { + fn grow_lines(&mut self, lines: index::Line) { for _ in IndexRange(self.num_lines()..lines) { - self.raw.push_back(Row::new(self.cols, template)); + self.raw.push_back(self.template_row.clone()); } self.lines = lines; } - fn grow_cols(&mut self, cols: index::Column, template: &T) { + fn grow_cols(&mut self, cols: index::Column) { for row in self.raw.iter_mut() { - row.grow(cols, template); + row.grow(cols, &self.template); } + // Update self cols self.cols = cols; - } - -} - -impl Grid { - #[inline] - pub fn num_lines(&self) -> index::Line { - self.lines - } - #[inline] - pub fn num_cols(&self) -> index::Column { - self.cols + // Also update template_row to be the correct length + self.template_row.grow(cols, &self.template); } #[inline] @@ -147,7 +150,8 @@ impl Grid { if region.start == Line(0) && region.end == self.num_lines() { // Full rotation for _ in 0..positions.0 { - let item = self.raw.pop_back().unwrap(); + let mut item = self.raw.pop_back().unwrap(); + item.reset(&self.template_row); self.raw.push_front(item); } } else { @@ -155,6 +159,13 @@ impl Grid { for line in IndexRange((region.start + positions)..region.end).rev() { self.swap_lines(line, line - positions); } + + let template = &self.template_row; + for i in IndexRange(Line(0)..positions) { + self.raw + .get_mut(*(region.start + i)) + .map(|row| row.reset(template)); + } } } @@ -163,7 +174,8 @@ impl Grid { if region.start == Line(0) && region.end == self.num_lines() { // Full rotation for _ in 0..positions.0 { - let item = self.raw.pop_front().unwrap(); + let mut item = self.raw.pop_front().unwrap(); + item.reset(&self.template_row); self.raw.push_back(item); } } else { @@ -171,8 +183,28 @@ impl Grid { for line in IndexRange(region.start..(region.end - positions)) { self.swap_lines(line, line + positions); } + + // Clear reused lines + let template = &self.template_row; + for i in IndexRange(Line(0)..positions) { + self.raw + .get_mut(*(region.end - i - 1)) + .map(|row| row.reset(template)); + } } } +} + +impl Grid { + #[inline] + pub fn num_lines(&self) -> index::Line { + self.lines + } + + #[inline] + pub fn num_cols(&self) -> index::Column { + self.cols + } pub fn iter_from(&self, point: Point) -> GridIterator { GridIterator { @@ -209,6 +241,7 @@ impl Grid { } self.cols = cols; + self.template_row.shrink(cols); } } -- cgit From f13685918ff42c3a919f8a84fea64a144979d13a Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Sun, 14 Jan 2018 21:51:56 -0800 Subject: WIP optimize scroll in region This intends to optimize the case where the top of the scrolling region is the top of the screen. In theory, scrolling in this case can be optimized to shifting the start/end of the visible region, and then rearranging any lines that were not supposed to be scrolled (at the bottom of the region). However, this didn't produce quite the speedup I expected. --- src/grid/mod.rs | 90 +++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 32 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index e790dd41..8e30915c 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -21,7 +21,6 @@ //! ranges is currently supported. use std::cmp::Ordering; -use std::collections::{VecDeque, vec_deque}; use std::iter::IntoIterator; use std::ops::{Deref, Range, RangeTo, RangeFrom, RangeFull, Index, IndexMut}; @@ -33,6 +32,9 @@ pub use self::row::Row; #[cfg(test)] mod tests; +mod storage; +use self::storage::Storage; + /// Convert a type to a linear index range. pub trait ToRange { fn to_range(&self) -> RangeInclusive; @@ -64,7 +66,7 @@ impl Deref for Indexed { pub struct Grid { /// Lines in the grid. Each row holds a list of cells corresponding to the /// columns in that row. - raw: VecDeque>, + raw: Storage>, /// Number of columns cols: index::Column, @@ -82,6 +84,9 @@ pub struct Grid { /// Template cell for populating template_row template: T, + + /// Temporary row storage for scrolling with a region + temp: Vec>, } pub struct GridIterator<'a, T: 'a> { @@ -91,10 +96,10 @@ pub struct GridIterator<'a, T: 'a> { impl Grid { pub fn new(lines: index::Line, cols: index::Column, template: T) -> Grid { - let mut raw = VecDeque::with_capacity(*lines); + let mut raw = Storage::with_capacity(*lines); let template_row = Row::new(cols, &template); for _ in IndexRange(index::Line(0)..lines) { - raw.push_back(template_row.clone()); + raw.push(template_row.clone()); } Grid { @@ -103,6 +108,7 @@ impl Grid { lines, template_row, template, + temp: Vec::new(), } } @@ -127,7 +133,7 @@ impl Grid { fn grow_lines(&mut self, lines: index::Line) { for _ in IndexRange(self.num_lines()..lines) { - self.raw.push_back(self.template_row.clone()); + self.raw.push(self.template_row.clone()); } self.lines = lines; @@ -147,12 +153,25 @@ impl Grid { #[inline] pub fn scroll_down(&mut self, region: &Range, positions: index::Line) { - if region.start == Line(0) && region.end == self.num_lines() { - // Full rotation - for _ in 0..positions.0 { - let mut item = self.raw.pop_back().unwrap(); - item.reset(&self.template_row); - self.raw.push_front(item); + // Whether or not there is a scrolling region active, as long as it + // starts at the top, we can do a full rotation which just involves + // changing the start index. + // + // To accomodate scroll regions, rows are reordered at the end. + 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)); + + // 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); + } + + // Finally, reset recycled lines + for i in 0..*positions { + self.raw[i].reset(&self.template_row); } } else { // Subregion rotation @@ -160,23 +179,33 @@ impl Grid { self.swap_lines(line, line - positions); } - let template = &self.template_row; for i in IndexRange(Line(0)..positions) { - self.raw - .get_mut(*(region.start + i)) - .map(|row| row.reset(template)); + self.raw[*(region.start - i - 1)].reset(&self.template_row); } } } #[inline] pub fn scroll_up(&mut self, region: &Range, positions: index::Line) { - if region.start == Line(0) && region.end == self.num_lines() { - // Full rotation - for _ in 0..positions.0 { - let mut item = self.raw.pop_front().unwrap(); - item.reset(&self.template_row); - self.raw.push_back(item); + 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); + + // Now, restore any lines outside the scroll region + let mut i = 0; + for _ in IndexRange(region.end .. self.num_lines()) { + let idx = *self.num_lines() - i - 1; + // First do the swap + self.raw.swap(idx, idx - *positions); + i += 1; + } + + // 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); } } else { // Subregion rotation @@ -185,11 +214,8 @@ impl Grid { } // Clear reused lines - let template = &self.template_row; for i in IndexRange(Line(0)..positions) { - self.raw - .get_mut(*(region.end - i - 1)) - .map(|row| row.reset(template)); + self.raw[*(region.start - i - 1)].reset(&self.template_row); } } } @@ -229,7 +255,7 @@ impl Grid { fn shrink_lines(&mut self, lines: index::Line) { while index::Line(self.raw.len()) != lines { - self.raw.pop_back(); + self.raw.pop(); } self.lines = lines; @@ -322,10 +348,10 @@ impl<'point, T> IndexMut<&'point Point> for Grid { impl<'a, T> IntoIterator for &'a Grid { type Item = &'a Row; - type IntoIter = vec_deque::Iter<'a, Row>; + type IntoIter = self::storage::Iter<'a, Row>; #[inline] - fn into_iter(self) -> vec_deque::Iter<'a, Row> { + fn into_iter(self) -> self::storage::Iter<'a, Row> { self.raw.iter() } } @@ -340,7 +366,7 @@ impl<'a, T> IntoIterator for &'a Grid { pub struct Region<'a, T: 'a> { start: Line, end: Line, - raw: &'a VecDeque>, + raw: &'a Storage>, } /// A mutable subset of lines in the grid @@ -349,7 +375,7 @@ pub struct Region<'a, T: 'a> { pub struct RegionMut<'a, T: 'a> { start: Line, end: Line, - raw: &'a mut VecDeque>, + raw: &'a mut Storage>, } impl<'a, T> RegionMut<'a, T> { @@ -453,13 +479,13 @@ impl IndexRegion for Grid { pub struct RegionIter<'a, T: 'a> { end: Line, cur: Line, - raw: &'a VecDeque>, + raw: &'a Storage>, } pub struct RegionIterMut<'a, T: 'a> { end: Line, cur: Line, - raw: &'a mut VecDeque>, + raw: &'a mut Storage>, } impl<'a, T> IntoIterator for Region<'a, T> { -- cgit From 92b11cfa43c3a5d32b461e43975146b27eb71ccd Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Sun, 11 Feb 2018 10:07:33 -0800 Subject: Minor improvements --- src/grid/mod.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 8e30915c..0c84530a 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -179,26 +179,26 @@ impl Grid { self.swap_lines(line, line - positions); } - for i in IndexRange(Line(0)..positions) { - self.raw[*(region.start - i - 1)].reset(&self.template_row); + for line in IndexRange(region.start .. (region.start + positions)) { + self.raw[*line].reset(&self.template_row); } } } + /// scroll_up moves lines at the bottom towards the top + /// + /// This is the performance-sensitive part of scrolling. #[inline] pub fn scroll_up(&mut self, region: &Range, positions: index::Line) { 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 lines outside the scroll region - let mut i = 0; - for _ in IndexRange(region.end .. self.num_lines()) { - let idx = *self.num_lines() - i - 1; + for idx in (*region.end .. *self.num_lines()).rev() { // First do the swap self.raw.swap(idx, idx - *positions); - i += 1; } // Finally, reset recycled lines @@ -214,8 +214,8 @@ impl Grid { } // Clear reused lines - for i in IndexRange(Line(0)..positions) { - self.raw[*(region.start - i - 1)].reset(&self.template_row); + for line in IndexRange((region.end - positions) .. region.end) { + self.raw[*line].reset(&self.template_row); } } } -- cgit From 84769ce170bd529989698bbbb4ee902e72569cbe Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Sun, 11 Feb 2018 12:27:23 -0800 Subject: Remove some unused impls --- src/grid/mod.rs | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 0c84530a..12702ddf 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -346,16 +346,6 @@ impl<'point, T> IndexMut<&'point Point> for Grid { } } -impl<'a, T> IntoIterator for &'a Grid { - type Item = &'a Row; - type IntoIter = self::storage::Iter<'a, Row>; - - #[inline] - fn into_iter(self) -> self::storage::Iter<'a, Row> { - self.raw.iter() - } -} - // ================================================================================================= // Regions ========================================================================================= // ================================================================================================= -- cgit From 94796a70fcbc11df2dc642057fef242466822067 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Sun, 11 Feb 2018 21:25:33 -0800 Subject: wip scrollback --- src/grid/mod.rs | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 12702ddf..f0bd2f6f 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -35,6 +35,9 @@ mod tests; mod storage; use self::storage::Storage; +/// Lines to keep in scrollback buffer +const SCROLLBACK_LINES: usize = 100_000; + /// Convert a type to a linear index range. pub trait ToRange { fn to_range(&self) -> RangeInclusive; @@ -87,6 +90,13 @@ pub struct Grid { /// Temporary row storage for scrolling with a region temp: Vec>, + + /// Offset of displayed area + /// + /// If the displayed region isn't at the bottom of the screen, it stays + /// stationary while more text is emitted. The scrolling implementation + /// updates this offset accordingly. + display_offset: usize, } pub struct GridIterator<'a, T: 'a> { @@ -96,9 +106,15 @@ pub struct GridIterator<'a, T: 'a> { impl Grid { pub fn new(lines: index::Line, cols: index::Column, template: T) -> Grid { - let mut raw = Storage::with_capacity(*lines); + let mut raw = Storage::with_capacity(*lines + SCROLLBACK_LINES); let template_row = Row::new(cols, &template); - for _ in IndexRange(index::Line(0)..lines) { + + // Allocate all lines in the buffer, including scrollback history + // + // TODO (jwilm) Allocating each line at this point is expensive and + // delays startup. A nice solution might be having `Row` delay + // allocation until it's actually used. + for _ in 0..raw.capacity() { raw.push(template_row.clone()); } @@ -191,6 +207,11 @@ impl Grid { #[inline] pub fn scroll_up(&mut self, region: &Range, positions: index::Line) { if region.start == Line(0) { + // Update display offset when not pinned to active area + if self.display_offset != 0 { + self.display_offset += *positions; + } + // 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); @@ -319,14 +340,16 @@ impl Index for Grid { #[inline] fn index(&self, index: index::Line) -> &Row { - &self.raw[index.0] + let index = self.lines.0 - index.0; + &self.raw[index] } } impl IndexMut for Grid { #[inline] fn index_mut(&mut self, index: index::Line) -> &mut Row { - &mut self.raw[index.0] + let index = self.lines.0 - index.0; + &mut self.raw[index] } } @@ -335,14 +358,14 @@ impl<'point, T> Index<&'point Point> for Grid { #[inline] fn index<'a>(&'a self, point: &Point) -> &'a T { - &self.raw[point.line.0][point.col] + &self[point.line][point.col] } } impl<'point, T> IndexMut<&'point Point> for Grid { #[inline] fn index_mut<'a, 'b>(&'a mut self, point: &'b Point) -> &'a mut T { - &mut self.raw[point.line.0][point.col] + &mut self[point.line][point.col] } } -- cgit From 45c2b3fbf72fa6dfd36bee590e64314c6da7c6c2 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Thu, 15 Feb 2018 18:35:49 -0800 Subject: 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) --- src/grid/mod.rs | 200 ++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 151 insertions(+), 49 deletions(-) (limited to 'src/grid/mod.rs') 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 Grid { + 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 { - 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 Grid { template_row, template, temp: Vec::new(), + display_offset: 0, } } @@ -147,12 +145,23 @@ impl Grid { } } - 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 Grid { 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, positions: index::Line) { // Whether or not there is a scrolling region active, as long as it @@ -177,12 +217,12 @@ impl Grid { 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 Grid { } 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 Grid { // 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 Grid { self.lines } + pub fn display_iter(&self) -> DisplayIter { + DisplayIter::new(self) + } + #[inline] pub fn num_cols(&self) -> index::Column { self.cols @@ -265,22 +309,14 @@ impl Grid { 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 Index for Grid { type Output = Row; #[inline] fn index(&self, index: index::Line) -> &Row { - let index = self.lines.0 - index.0; + &self.raw[index] + } +} + +/// Index with buffer offset +impl Index for Grid { + type Output = Row; + + #[inline] + fn index(&self, index: usize) -> &Row { &self.raw[index] } } @@ -348,7 +394,6 @@ impl Index for Grid { impl IndexMut for Grid { #[inline] fn index_mut(&mut self, index: index::Line) -> &mut Row { - let index = self.lines.0 - index.0; &mut self.raw[index] } } @@ -369,9 +414,9 @@ impl<'point, T> IndexMut<&'point Point> for Grid { } } -// ================================================================================================= -// 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, + offset: usize, + limit: usize, + col: Column, +} + +impl<'a, T: 'a> DisplayIter<'a, T> { + pub fn new(grid: &'a Grid) -> 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; + + #[inline] + fn next(&mut self) -> Option { + // 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 + } +} -- cgit From 5748066b8ac18140187de91fef0bdbddc9fdb338 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Thu, 15 Feb 2018 19:06:39 -0800 Subject: Add scrolling limit and update on grow lines --- src/grid/mod.rs | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 820cd9e2..749012fa 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -14,7 +14,7 @@ //! A specialized 2d grid implementation optimized for use in a terminal. -use std::cmp::{max, Ordering}; +use std::cmp::{min, max, Ordering}; use std::ops::{Deref, Range, Index, IndexMut, RangeTo, RangeFrom, RangeFull}; use index::{self, Point, Line, Column, IndexRange, RangeInclusive}; @@ -90,6 +90,9 @@ pub struct Grid { /// stationary while more text is emitted. The scrolling implementation /// updates this offset accordingly. display_offset: usize, + + /// An limit on how far back it's possible to scroll + scroll_limit: usize, } pub struct GridIterator<'a, T: 'a> { @@ -99,7 +102,10 @@ pub struct GridIterator<'a, T: 'a> { impl Grid { pub fn scroll_display(&mut self, count: isize) { - self.display_offset = max((self.display_offset as isize) + count, 0isize) as usize; + self.display_offset = min( + max((self.display_offset as isize) + count, 0isize) as usize, + self.scroll_limit + ); } pub fn new(lines: index::Line, cols: index::Column, template: T) -> Grid { @@ -123,6 +129,7 @@ impl Grid { template, temp: Vec::new(), display_offset: 0, + scroll_limit: 0, } } @@ -145,6 +152,10 @@ impl Grid { } } + fn increase_scroll_limit(&mut self, count: usize) { + self.scroll_limit = min(self.scroll_limit + count, self.raw.len() - *self.lines); + } + /// Add lines to the visible area /// /// The behavior in Terminal.app and iTerm.app is to keep the cursor at the @@ -155,13 +166,18 @@ impl Grid { /// 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; + fn grow_lines(&mut self, new_line_count: index::Line) { + let previous_scroll_limit = self.scroll_limit; + let lines_added = new_line_count - self.lines; - self.raw.set_visible_lines(target); - self.lines = target; + // Need to "resize" before updating buffer + self.raw.set_visible_lines(new_line_count); + self.lines = new_line_count; + + // Add new lines to bottom + self.scroll_up(&(Line(0)..new_line_count), lines_added); - self.scroll_up(&(Line(0)..target), delta); + self.scroll_limit = self.scroll_limit.saturating_sub(*lines_added); } fn grow_cols(&mut self, cols: index::Column) { @@ -252,6 +268,8 @@ impl Grid { self.display_offset += *positions; } + self.increase_scroll_limit(*positions); + // 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)); -- cgit From 9b9b138bac9353d2d95ce71ec155c3a9b2963491 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Thu, 15 Feb 2018 19:34:09 -0800 Subject: Fir cursor not scrolling --- src/grid/mod.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 749012fa..1889d59f 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -628,6 +628,7 @@ pub struct DisplayIter<'a, T: 'a> { offset: usize, limit: usize, col: Column, + line: Line, } impl<'a, T: 'a> DisplayIter<'a, T> { @@ -635,8 +636,9 @@ impl<'a, T: 'a> DisplayIter<'a, T> { let offset = grid.display_offset + *grid.num_lines() - 1; let limit = grid.display_offset; let col = Column(0); + let line = Line(0); - DisplayIter { grid, offset, col, limit } + DisplayIter { grid, offset, col, limit, line } } pub fn offset(&self) -> usize { @@ -646,6 +648,10 @@ impl<'a, T: 'a> DisplayIter<'a, T> { pub fn column(&self) -> Column { self.col } + + pub fn line(&self) -> Line { + self.line + } } impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { @@ -660,13 +666,15 @@ impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { } self.col = Column(0); + self.offset -= 1; + self.line = Line(*self.grid.lines - 1 - (self.offset - self.limit)); } // 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)), + line: self.line, column: self.col }); -- cgit From f67b17ca7b2e0e47ad5eee24d8b86800c61139b4 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Thu, 15 Feb 2018 19:34:23 -0800 Subject: wip fix scroll_down --- src/grid/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 1889d59f..23ee9cf4 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -236,8 +236,11 @@ impl Grid { self.raw.rotate_up(*positions); // Now, restore any scroll region lines - for i in IndexRange(region.end .. self.num_lines()) { + let lines = self.lines; + for i in IndexRange(region.end .. lines) { // First do the swap + // TODO there is a bug here causing a panic. + // TODO math self.raw.swap_lines(i, i + positions); } -- cgit From 7fe67743ebffd047532f6271bf28474f9d947f64 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Fri, 16 Feb 2018 17:33:32 -0800 Subject: Scroll to bottom on character received --- src/grid/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 23ee9cf4..c6543270 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -108,6 +108,10 @@ impl Grid { ); } + pub fn reset_scroll(&mut self) { + self.display_offset = 0; + } + pub fn new(lines: index::Line, cols: index::Column, template: T) -> Grid { let mut raw = Storage::with_capacity(*lines + SCROLLBACK_LINES, lines); let template_row = Row::new(cols, &template); -- cgit From c49a7e88f64d1421474d492cc6f51bfd30e1e4d1 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Fri, 16 Feb 2018 17:54:32 -0800 Subject: Make number of scrollback lines configurable --- src/grid/mod.rs | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index c6543270..1cb0876e 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -28,9 +28,6 @@ mod tests; mod storage; use self::storage::Storage; -/// Lines to keep in scrollback buffer -const SCROLLBACK_LINES: usize = 10_000; - /// Convert a type to a linear index range. pub trait ToRange { fn to_range(&self) -> RangeInclusive; @@ -101,19 +98,8 @@ pub struct GridIterator<'a, T: 'a> { } impl Grid { - pub fn scroll_display(&mut self, count: isize) { - self.display_offset = min( - max((self.display_offset as isize) + count, 0isize) as usize, - self.scroll_limit - ); - } - - pub fn reset_scroll(&mut self) { - self.display_offset = 0; - } - - pub fn new(lines: index::Line, cols: index::Column, template: T) -> Grid { - let mut raw = Storage::with_capacity(*lines + SCROLLBACK_LINES, lines); + pub fn new(lines: index::Line, cols: index::Column, scrollback: usize, template: T) -> Grid { + let mut raw = Storage::with_capacity(*lines + scrollback, lines); let template_row = Row::new(cols, &template); // Allocate all lines in the buffer, including scrollback history @@ -137,6 +123,17 @@ impl Grid { } } + pub fn scroll_display(&mut self, count: isize) { + self.display_offset = min( + max((self.display_offset as isize) + count, 0isize) as usize, + self.scroll_limit + ); + } + + pub fn reset_scroll_display(&mut self) { + self.display_offset = 0; + } + pub fn resize(&mut self, lines: index::Line, cols: index::Column) { // Check that there's actually work to do and return early if not if lines == self.lines && cols == self.cols { -- cgit From 54d50ed3be810861d3c2fa500c3fcc8e802198d9 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Fri, 16 Feb 2018 18:35:54 -0800 Subject: Fix scrolling backwards in tmux --- src/grid/mod.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 1cb0876e..ca43471d 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -157,6 +157,10 @@ impl Grid { self.scroll_limit = min(self.scroll_limit + count, self.raw.len() - *self.lines); } + fn decrease_scroll_limit(&mut self, count: usize) { + self.scroll_limit = self.scroll_limit.saturating_sub(count); + } + /// Add lines to the visible area /// /// The behavior in Terminal.app and iTerm.app is to keep the cursor at the @@ -236,17 +240,16 @@ impl Grid { // active, the bottom lines are restored in the next step. self.raw.rotate_up(*positions); + self.decrease_scroll_limit(*positions); + // Now, restore any scroll region lines let lines = self.lines; for i in IndexRange(region.end .. lines) { - // First do the swap - // TODO there is a bug here causing a panic. - // TODO math self.raw.swap_lines(i, i + positions); } // Finally, reset recycled lines - for i in 0..*positions { + for i in IndexRange(Line(0)..positions) { self.raw[i].reset(&self.template_row); } } else { -- cgit From 8ef062efd94975885776e61b6df936088b018da0 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Mon, 5 Mar 2018 09:57:34 -0800 Subject: Move selection into Grid Supporting selections with scrollback has two major components: 1. Grid needs access to Selection so that it may update the scroll position as the terminal text changes. 2. Selection needs to be implemented in terms of buffer offsets -- NOT lines -- and be updated when Storage is rotated. This commit implements the first part. --- src/grid/mod.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index ca43471d..cf66a420 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -18,6 +18,7 @@ use std::cmp::{min, max, Ordering}; use std::ops::{Deref, Range, Index, IndexMut, RangeTo, RangeFrom, RangeFull}; use index::{self, Point, Line, Column, IndexRange, RangeInclusive}; +use selection::Selection; mod row; pub use self::row::Row; @@ -54,8 +55,16 @@ impl Deref for Indexed { } } +impl ::std::cmp::PartialEq for Grid { + fn eq(&self, other: &Self) -> bool { + self.cols.eq(&other.cols) && + self.lines.eq(&other.lines) && + self.raw.eq(&other.raw) + } +} + /// Represents the terminal display contents -#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct Grid { /// Lines in the grid. Each row holds a list of cells corresponding to the /// columns in that row. @@ -73,14 +82,13 @@ pub struct Grid { /// /// This is used to quickly populate new lines and clear recycled lines /// during scroll wrapping. + #[serde(skip)] template_row: Row, /// Template cell for populating template_row + #[serde(skip)] template: T, - /// Temporary row storage for scrolling with a region - temp: Vec>, - /// Offset of displayed area /// /// If the displayed region isn't at the bottom of the screen, it stays @@ -90,6 +98,10 @@ pub struct Grid { /// An limit on how far back it's possible to scroll scroll_limit: usize, + + /// Selected region + #[serde(skip)] + pub selection: Option, } pub struct GridIterator<'a, T: 'a> { @@ -117,9 +129,9 @@ impl Grid { lines, template_row, template, - temp: Vec::new(), display_offset: 0, scroll_limit: 0, + selection: None, } } -- cgit From 8018dee1812ab88793c0f18e13335fa77c068000 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Tue, 6 Mar 2018 20:57:40 -0800 Subject: Support selections with scrolling buffer Selections now *mostly* work. They move as the buffer scrolls, copying works as it should, and it looks like the different selection modes behave properly as well. The new Selection implementation uses buffer coordinates instead of screen coordinates. This leads to doing a transform from mouse input to update the selection, and back to screen coordinates when displaying the selection. Scrolling the selection is fast because the grid is already operating in buffer coordinates. There are several bugs to address: * A _partially_ visible selection will lead to a crash since the drawing routine converts selection coordinates to screen coordinates. The solution will be to clip the coordinates at draw time. * A selection scrolling off the buffer in either direction leads to indexing out-of-bounds. The solution again is to clip, but this needs to be done within Selection::rotate by passing a max limit. It may also need a return type to indicate that the selection is no longer visible and should be discarded. * A selection scrolling out of a logical scrolling region is not clipped. A temporary and robust workaround is to simply discard the selection in the case of scrolling in a region. wip selections fix issue with line selection selection mostly working need to support selection not being on the screen at draw time Fix selection_to_string Uncomment tests --- src/grid/mod.rs | 68 ++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 15 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index cf66a420..7f648f46 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -17,7 +17,7 @@ use std::cmp::{min, max, Ordering}; use std::ops::{Deref, Range, Index, IndexMut, RangeTo, RangeFrom, RangeFull}; -use index::{self, Point, Line, Column, IndexRange, RangeInclusive}; +use index::{self, Point, Line, Column, IndexRange}; use selection::Selection; mod row; @@ -29,11 +29,6 @@ mod tests; mod storage; use self::storage::Storage; -/// Convert a type to a linear index range. -pub trait ToRange { - fn to_range(&self) -> RangeInclusive; -} - /// Bidirection iterator pub trait BidirectionalIterator: Iterator { fn prev(&mut self) -> Option; @@ -105,8 +100,17 @@ pub struct Grid { } pub struct GridIterator<'a, T: 'a> { + /// Immutable grid reference grid: &'a Grid, - pub cur: Point, + + /// Current position of the iterator within the grid. + pub cur: Point, + + /// Bottom of screen (buffer) + bot: usize, + + /// Top of screen (buffer) + top: usize, } impl Grid { @@ -135,6 +139,28 @@ impl Grid { } } + pub fn visible_to_buffer(&self, point: Point) -> Point { + Point { + line: self.visible_line_to_buffer(point.line), + col: point.col + } + } + + pub fn buffer_to_visible(&self, point: Point) -> Point { + Point { + line: self.buffer_line_to_visible(point.line), + col: point.col + } + } + + pub fn buffer_line_to_visible(&self, line: usize) -> Line { + self.offset_to_line(line - self.display_offset) + } + + pub fn visible_line_to_buffer(&self, line: Line) -> usize { + self.line_to_offset(line) + self.display_offset + } + pub fn scroll_display(&mut self, count: isize) { self.display_offset = min( max((self.display_offset as isize) + count, 0isize) as usize, @@ -224,6 +250,7 @@ impl Grid { let prev = self.lines; + self.selection = None; self.raw.rotate(*prev as isize - *target as isize); self.raw.set_visible_lines(target); self.lines = target; @@ -240,6 +267,12 @@ impl Grid { *(self.num_lines() - line - 1) } + pub fn offset_to_line(&self, offset: usize) -> Line { + assert!(offset < *self.num_lines()); + + self.lines - offset - 1 + } + #[inline] pub fn scroll_down(&mut self, region: &Range, positions: index::Line) { // Whether or not there is a scrolling region active, as long as it @@ -251,6 +284,9 @@ impl Grid { // Rotate the entire line buffer. If there's a scrolling region // active, the bottom lines are restored in the next step. self.raw.rotate_up(*positions); + if let Some(ref mut selection) = self.selection { + selection.rotate(-(*positions as isize)); + } self.decrease_scroll_limit(*positions); @@ -292,6 +328,9 @@ impl Grid { // Rotate the entire line buffer. If there's a scrolling region // active, the bottom lines are restored in the next step. self.raw.rotate(-(*positions as isize)); + if let Some(ref mut selection) = self.selection { + selection.rotate(*positions as isize); + } // Now, restore any lines outside the scroll region for idx in (*region.end .. *self.num_lines()).rev() { @@ -334,10 +373,12 @@ impl Grid { self.cols } - pub fn iter_from(&self, point: Point) -> GridIterator { + pub fn iter_from(&self, point: Point) -> GridIterator { GridIterator { grid: self, cur: point, + bot: self.display_offset, + top: self.display_offset + *self.num_lines() - 1, } } @@ -369,15 +410,12 @@ impl<'a, T> Iterator for GridIterator<'a, T> { type Item = &'a T; fn next(&mut self) -> Option { - let last_line = self.grid.num_lines() - Line(1); let last_col = self.grid.num_cols() - Column(1); match self.cur { - Point { line, col } if - (line == last_line) && - (col == last_col) => None, + Point { line, col } if (line == self.bot) && (col == last_col) => None, Point { col, .. } if (col == last_col) => { - self.cur.line += Line(1); + self.cur.line -= 1; self.cur.col = Column(0); Some(&self.grid[self.cur.line][self.cur.col]) }, @@ -394,9 +432,9 @@ impl<'a, T> BidirectionalIterator for GridIterator<'a, T> { let num_cols = self.grid.num_cols(); match self.cur { - Point { line: Line(0), col: Column(0) } => None, + Point { line, col: Column(0) } if line == self.top => None, Point { col: Column(0), .. } => { - self.cur.line -= Line(1); + self.cur.line += 1; self.cur.col = num_cols - Column(1); Some(&self.grid[self.cur.line][self.cur.col]) }, -- cgit From 231ef51365e3cb0de760d04a47a7d2b74809c41d Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 10 Mar 2018 14:53:54 +0100 Subject: Fix crash when selection leaves viewport There was an issue where alacritty tries to convert the lines in a selection to the on-screen lines even when the selection is not on the screen. This results in a crash. To prevent this from happening the selection now is not shown if it is off the screen. There currently still is a bug that when the selection is at the top of the screen but still half visible, it will not show the top line as selected but start in the second line. This bug should be resolved with https://github.com/jwilm/alacritty/pull/1171. This fixes #1148. --- src/grid/mod.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 7f648f46..a52c27c5 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -148,13 +148,17 @@ impl Grid { pub fn buffer_to_visible(&self, point: Point) -> Point { Point { - line: self.buffer_line_to_visible(point.line), + line: self.buffer_line_to_visible(point.line).expect("Line not visible"), col: point.col } } - pub fn buffer_line_to_visible(&self, line: usize) -> Line { - self.offset_to_line(line - self.display_offset) + pub fn buffer_line_to_visible(&self, line: usize) -> Option { + if line >= self.display_offset { + self.offset_to_line(line - self.display_offset) + } else { + None + } } pub fn visible_line_to_buffer(&self, line: Line) -> usize { @@ -267,10 +271,12 @@ impl Grid { *(self.num_lines() - line - 1) } - pub fn offset_to_line(&self, offset: usize) -> Line { - assert!(offset < *self.num_lines()); - - self.lines - offset - 1 + pub fn offset_to_line(&self, offset: usize) -> Option { + if offset < *self.num_lines() { + Some(self.lines - offset - 1) + } else { + None + } } #[inline] -- cgit From 2c7bb9a4d3ce3ead6de4ca6485ca67c44c0bd1c1 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 10 Mar 2018 20:24:10 +0100 Subject: Add scrollback hotkeys This offers a few additional hotkeys that can be used in combination with scrollback. None of these are used by default yet. This implements the following bindings: - ScrollPageUp: Scroll exactly one screen height up - ScrollPageDown: Scroll exactly one screen height down - ScrollToTop: Scroll as far up as possible - ScrollToBottom: Scroll as far down as possible This fixes #1151. --- src/grid/mod.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index a52c27c5..ac761adc 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -176,6 +176,26 @@ impl Grid { self.display_offset = 0; } + pub fn scroll_to_top(&mut self) { + self.display_offset = self.scroll_limit; + } + + pub fn scroll_page_up(&mut self) { + if self.display_offset + self.lines.0 >= self.scroll_limit { + self.display_offset = self.scroll_limit; + } else { + self.display_offset += self.lines.0; + } + } + + pub fn scroll_page_down(&mut self) { + if self.display_offset <= self.lines.0 { + self.display_offset = 0; + } else { + self.display_offset -= self.lines.0; + } + } + pub fn resize(&mut self, lines: index::Line, cols: index::Column) { // Check that there's actually work to do and return early if not if lines == self.lines && cols == self.cols { -- cgit From 0dcb9ca6a871fd6d28787142a134c9190001ac43 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sun, 11 Mar 2018 13:01:06 +0100 Subject: Replace scrolling methods with enum The different scrolling methods added a bunch of boilerplate where the call was just forwarded to the next struct, this has been removed by making the scroll amount into a struct. Now everything is called through one method and the parameter decides how far the viewport should be scrolled. --- src/grid/mod.rs | 58 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 28 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index ac761adc..65bc8382 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -113,6 +113,14 @@ pub struct GridIterator<'a, T: 'a> { top: usize, } +pub enum Scroll { + Lines(isize), + PageUp, + PageDown, + Top, + Bottom, +} + impl Grid { pub fn new(lines: index::Line, cols: index::Column, scrollback: usize, template: T) -> Grid { let mut raw = Storage::with_capacity(*lines + scrollback, lines); @@ -165,34 +173,28 @@ impl Grid { self.line_to_offset(line) + self.display_offset } - pub fn scroll_display(&mut self, count: isize) { - self.display_offset = min( - max((self.display_offset as isize) + count, 0isize) as usize, - self.scroll_limit - ); - } - - pub fn reset_scroll_display(&mut self) { - self.display_offset = 0; - } - - pub fn scroll_to_top(&mut self) { - self.display_offset = self.scroll_limit; - } - - pub fn scroll_page_up(&mut self) { - if self.display_offset + self.lines.0 >= self.scroll_limit { - self.display_offset = self.scroll_limit; - } else { - self.display_offset += self.lines.0; - } - } - - pub fn scroll_page_down(&mut self) { - if self.display_offset <= self.lines.0 { - self.display_offset = 0; - } else { - self.display_offset -= self.lines.0; + pub fn scroll_display(&mut self, scroll: Scroll) { + match scroll { + Scroll::Lines(count) => { + self.display_offset = min( + max((self.display_offset as isize) + count, 0isize) as usize, + self.scroll_limit + ); + }, + Scroll::PageUp => { + self.display_offset = min( + self.display_offset + self.lines.0, + self.scroll_limit + ); + }, + Scroll::PageDown => { + self.display_offset -= min( + self.display_offset, + self.lines.0 + ); + }, + Scroll::Top => self.display_offset = self.scroll_limit, + Scroll::Bottom => self.display_offset = 0, } } -- cgit From 4333b2fb1992b8185dc7041d9d3df8eee86717bc Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Tue, 20 Mar 2018 10:04:22 -0700 Subject: Fix regression with scrolling regions Resolves #1154 --- src/grid/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 65bc8382..f1cec329 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -335,7 +335,7 @@ impl Grid { } for line in IndexRange(region.start .. (region.start + positions)) { - self.raw[*line].reset(&self.template_row); + self.raw[line].reset(&self.template_row); } } } -- cgit From b0f655ac85ab6d86e9e482cbb9035200c6f08d40 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Fri, 9 Mar 2018 13:49:47 -0800 Subject: Make tests compile again Some tests are still not passing, though. A migration script was added to migrate serialized grids from pre-scrollback to the current format. The script is included with this commit for completeness, posterity, and as an example to be used in the future. A few tests in grid/tests.rs were removed due to becoming irrelevant. --- src/grid/mod.rs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index f1cec329..3495be1d 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -89,9 +89,11 @@ pub struct Grid { /// If the displayed region isn't at the bottom of the screen, it stays /// stationary while more text is emitted. The scrolling implementation /// updates this offset accordingly. + #[serde(default)] display_offset: usize, /// An limit on how far back it's possible to scroll + #[serde(default)] scroll_limit: usize, /// Selected region -- cgit From b19045da66899999856c6b2cc6707b60c607660a Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Mon, 2 Apr 2018 08:44:54 -0700 Subject: Fix BCE ref tests BCE was broken in attempt to optimize row clearing. The fix is to revert to passing in the current cursor state when clearing. --- src/grid/mod.rs | 65 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 32 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 3495be1d..faf67772 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -73,17 +73,6 @@ pub struct Grid { /// Invariant: lines is equivalent to raw.len() lines: index::Line, - /// Template row. - /// - /// This is used to quickly populate new lines and clear recycled lines - /// during scroll wrapping. - #[serde(skip)] - template_row: Row, - - /// Template cell for populating template_row - #[serde(skip)] - template: T, - /// Offset of displayed area /// /// If the displayed region isn't at the bottom of the screen, it stays @@ -126,7 +115,6 @@ pub enum Scroll { impl Grid { pub fn new(lines: index::Line, cols: index::Column, scrollback: usize, template: T) -> Grid { let mut raw = Storage::with_capacity(*lines + scrollback, lines); - let template_row = Row::new(cols, &template); // Allocate all lines in the buffer, including scrollback history // @@ -134,15 +122,13 @@ impl Grid { // delays startup. A nice solution might be having `Row` delay // allocation until it's actually used. for _ in 0..raw.capacity() { - raw.push(template_row.clone()); + raw.push(Row::new(cols, &template)); } Grid { raw, cols, lines, - template_row, - template, display_offset: 0, scroll_limit: 0, selection: None, @@ -200,20 +186,25 @@ impl Grid { } } - pub fn resize(&mut self, lines: index::Line, cols: index::Column) { + pub fn resize( + &mut self, + lines: index::Line, + cols: index::Column, + template: &T, + ) { // Check that there's actually work to do and return early if not if lines == self.lines && cols == self.cols { return; } match self.lines.cmp(&lines) { - Ordering::Less => self.grow_lines(lines), + Ordering::Less => self.grow_lines(lines, template), Ordering::Greater => self.shrink_lines(lines), Ordering::Equal => (), } match self.cols.cmp(&cols) { - Ordering::Less => self.grow_cols(cols), + Ordering::Less => self.grow_cols(cols, template), Ordering::Greater => self.shrink_cols(cols), Ordering::Equal => (), } @@ -237,7 +228,11 @@ impl Grid { /// 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, new_line_count: index::Line) { + fn grow_lines( + &mut self, + new_line_count: index::Line, + template: &T, + ) { let previous_scroll_limit = self.scroll_limit; let lines_added = new_line_count - self.lines; @@ -246,21 +241,18 @@ impl Grid { self.lines = new_line_count; // Add new lines to bottom - self.scroll_up(&(Line(0)..new_line_count), lines_added); + self.scroll_up(&(Line(0)..new_line_count), lines_added, template); self.scroll_limit = self.scroll_limit.saturating_sub(*lines_added); } - fn grow_cols(&mut self, cols: index::Column) { + fn grow_cols(&mut self, cols: index::Column, template: &T) { for row in self.raw.iter_mut() { - row.grow(cols, &self.template); + row.grow(cols, template); } // Update self cols self.cols = cols; - - // Also update template_row to be the correct length - self.template_row.grow(cols, &self.template); } /// Remove lines from the visible area @@ -304,7 +296,12 @@ impl Grid { } #[inline] - pub fn scroll_down(&mut self, region: &Range, positions: index::Line) { + pub fn scroll_down( + &mut self, + region: &Range, + positions: index::Line, + template: &T, + ) { // Whether or not there is a scrolling region active, as long as it // starts at the top, we can do a full rotation which just involves // changing the start index. @@ -328,7 +325,7 @@ impl Grid { // Finally, reset recycled lines for i in IndexRange(Line(0)..positions) { - self.raw[i].reset(&self.template_row); + self.raw[i].reset(&template); } } else { // Subregion rotation @@ -337,7 +334,7 @@ impl Grid { } for line in IndexRange(region.start .. (region.start + positions)) { - self.raw[line].reset(&self.template_row); + self.raw[line].reset(&template); } } } @@ -346,7 +343,12 @@ impl Grid { /// /// This is the performance-sensitive part of scrolling. #[inline] - pub fn scroll_up(&mut self, region: &Range, positions: index::Line) { + pub fn scroll_up( + &mut self, + region: &Range, + positions: index::Line, + template: &T + ) { if region.start == Line(0) { // Update display offset when not pinned to active area if self.display_offset != 0 { @@ -372,7 +374,7 @@ impl Grid { // // 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(&template); } } else { // Subregion rotation @@ -382,7 +384,7 @@ impl Grid { // Clear reused lines for line in IndexRange((region.end - positions) .. region.end) { - self.raw[line].reset(&self.template_row); + self.raw[line].reset(&template); } } } @@ -432,7 +434,6 @@ impl Grid { } self.cols = cols; - self.template_row.shrink(cols); } } -- cgit From 334d50e98c5f579e6f5b240ef2be971472ee27fb Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 24 Mar 2018 14:42:05 +0100 Subject: Fix cursor not showing in first column There was a bug in the display iterator where the first column was never reached after the top line because it was instantly incremented to 1 after it was reset when iterator column reached the end of the terminal width. This has been fixed by making sure that the column is never incremented when the column is reset due to a change in terminal line. This fixes #1198. --- src/grid/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index faf67772..0564cdf5 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -748,13 +748,13 @@ impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { #[inline] fn next(&mut self) -> Option { // Make sure indices are valid. Return None if we've reached the end. - if self.col == self.grid.num_cols() { + let next_line = self.col == self.grid.num_cols(); + if next_line { if self.offset == self.limit { return None; } self.col = Column(0); - self.offset -= 1; self.line = Line(*self.grid.lines - 1 - (self.offset - self.limit)); } @@ -766,7 +766,9 @@ impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { column: self.col }); - self.col += 1; + if !next_line { + self.col += 1; + } item } } -- cgit From 7f2fe5acb288d3533af5b95ac50162ed60c14042 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Mon, 2 Apr 2018 21:12:04 +0200 Subject: Add documentation to explain the process --- src/grid/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 0564cdf5..15ddf30e 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -747,25 +747,28 @@ impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { #[inline] fn next(&mut self) -> Option { - // Make sure indices are valid. Return None if we've reached the end. + // Check if the end of the line was reached let next_line = self.col == self.grid.num_cols(); if next_line { + // Return `None` if we've reached the end of the last line if self.offset == self.limit { return None; } + // Switch to the next line self.col = Column(0); self.offset -= 1; self.line = Line(*self.grid.lines - 1 - (self.offset - self.limit)); } - // Return the next item. + // Return the next item let item = Some(Indexed { inner: self.grid.raw[self.offset][self.col], line: self.line, column: self.col }); + // Only increment column if the line hasn't changed if !next_line { self.col += 1; } -- cgit From e615d112fb9fffe46121bd9068498b07c4733fa8 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 7 Apr 2018 01:50:14 +0200 Subject: Fix scrollback history size 0 bug There was an issue where alacritty would panic whenever the scrollback history size is set to 0, this fixes that issue. The panic was caused by a substraction with unsigned integers which was underflowing, this has been fixed to use `saturating_sub`. After that was fixed there was still a bug where scrollback would not behave correctly because the number of lines in the grid was decided at startup. This has been adapted so whenever the size of the terminal changes, the scrollback history and grid adapts to make sure the number of lines in the terminal is always the number of visible lines plus the amount of scrollback lines configured in the config file. This fixes #1150. --- src/grid/mod.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 15ddf30e..e42e5cfa 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -211,7 +211,10 @@ impl Grid { } fn increase_scroll_limit(&mut self, count: usize) { - self.scroll_limit = min(self.scroll_limit + count, self.raw.len() - *self.lines); + self.scroll_limit = min( + self.scroll_limit + count, + self.raw.len().saturating_sub(*self.lines), + ); } fn decrease_scroll_limit(&mut self, count: usize) { @@ -240,6 +243,13 @@ impl Grid { self.raw.set_visible_lines(new_line_count); self.lines = new_line_count; + // Fill up the history with empty lines + if self.raw.len() < self.raw.capacity() { + for _ in self.raw.len()..self.raw.capacity() { + self.raw.push(Row::new(self.cols, &template)); + } + } + // Add new lines to bottom self.scroll_up(&(Line(0)..new_line_count), lines_added, template); @@ -263,11 +273,6 @@ impl Grid { /// /// 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.selection = None; -- cgit From fe749cf0adee1be93fd3473f6d3533fbe892e3dc Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sun, 22 Apr 2018 14:39:55 +0200 Subject: Fix order of lines after resize There was an issue where the lines would be messed up when the terminal was resized, this was because lines were just added/removed at the end of the buffer instead of the actual end of the terminal (since the end of the terminal might be in the middle of the buffer). This has been fixed by relying on `self.zero` to determine the position of the start of the terminal and then calculating where lines have to be inserted/removed. Some tests have also been added with documentation that should make it a little easier to understand how the process works and how the raw buffer is layed out. This should all work no matter how big the scrollback history and even when the currenty viewport is not at the bottom of the terminal output. --- src/grid/mod.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index e42e5cfa..bc288e3b 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -240,16 +240,9 @@ impl Grid { let lines_added = new_line_count - self.lines; // Need to "resize" before updating buffer - self.raw.set_visible_lines(new_line_count); + self.raw.grow_visible_lines(new_line_count, Row::new(self.cols, template)); self.lines = new_line_count; - // Fill up the history with empty lines - if self.raw.len() < self.raw.capacity() { - for _ in self.raw.len()..self.raw.capacity() { - self.raw.push(Row::new(self.cols, &template)); - } - } - // Add new lines to bottom self.scroll_up(&(Line(0)..new_line_count), lines_added, template); @@ -277,7 +270,7 @@ impl Grid { self.selection = None; self.raw.rotate(*prev as isize - *target as isize); - self.raw.set_visible_lines(target); + self.raw.shrink_visible_lines(target); self.lines = target; } -- cgit From 4df09128ced0db31e26f3d26c67ffc658475bffb Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 21 Apr 2018 13:37:16 +0200 Subject: Revert "Fix cursor not showing in first column" This reverts commit 54b21b66ecc6f8f149d1425567e0e3d766a3ac54. --- src/grid/mod.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index bc288e3b..c05584c4 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -745,16 +745,15 @@ impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { #[inline] fn next(&mut self) -> Option { - // Check if the end of the line was reached - let next_line = self.col == self.grid.num_cols(); - if next_line { - // Return `None` if we've reached the end of the last line + // 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; } // Switch to the next line self.col = Column(0); + self.offset -= 1; self.line = Line(*self.grid.lines - 1 - (self.offset - self.limit)); } @@ -766,10 +765,7 @@ impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { column: self.col }); - // Only increment column if the line hasn't changed - if !next_line { - self.col += 1; - } + self.col += 1; item } } -- cgit From bfa926555145f9f578430b87bc1c1b72ef80589f Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 21 Apr 2018 13:45:43 +0200 Subject: Fix bright characters in first column This bug was introduced by the commit which fixed the invisible cursor in the first column (54b21b66ecc6f8f149d1425567e0e3d766a3ac54). To resolve this the alternative implementation by @jwilm has been applied which seems to work out. This fixes #1259. --- src/grid/mod.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index c05584c4..313973a3 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -745,27 +745,29 @@ impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { #[inline] fn next(&mut self) -> Option { - // 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; - } - - // Switch to the next line - self.col = Column(0); - - self.offset -= 1; - self.line = Line(*self.grid.lines - 1 - (self.offset - self.limit)); + // Return None if we've reached the end. + if self.offset == self.limit && self.grid.num_cols() == self.col { + return None; } - // Return the next item + // Get the next item. let item = Some(Indexed { inner: self.grid.raw[self.offset][self.col], line: self.line, column: self.col }); + // Update line/col to point to next item self.col += 1; + if self.col == self.grid.num_cols() { + if self.offset != self.limit { + self.offset -= 1; + + self.col = Column(0); + self.line = Line(*self.grid.lines - 1 - (self.offset - self.limit)); + } + } + item } } -- cgit From d8bda60c3d8f906f1018012f4a55c7b894afb4b7 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 28 Apr 2018 14:14:45 +0000 Subject: Reset grid when running `reset` In the current scrollback PR the `reset` command does not affect the scrollback history. To make sure the terminal is properly reset, it should clear the scrollback history. To make resetting efficient, instead of resetting the history, the scrollback history is hidden by setting `grid.scroll_limit` to `0`. This will not clear the history but instead just make it inaccessible, which should have the same effect. The visible area is reset by the shell itself, so in combination this clears the complete terminal grid from a user perspective. This fixes #1242. --- src/grid/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 313973a3..ac54f580 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -403,6 +403,10 @@ impl Grid { self.cols } + pub fn reset(&mut self) { + self.scroll_limit = 0; + } + pub fn iter_from(&self, point: Point) -> GridIterator { GridIterator { grid: self, -- cgit From 2234234ca9a2ab0d7eccd46893cbe6799b051aba Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 14 Apr 2018 17:21:48 +0200 Subject: Enable history comparison in ref-tests Previously ref-tests just ignored the scrollback history to keep the old tests working, this would lead to new tests which rely on scrollback history to succeeed even though they should not. This has been fixed and it is now possible to create ref-tests with and without scrollback history. When available the scrollback history is compared, but the old tests still work without having to adjust them. This fixes #1244. --- src/grid/mod.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index ac54f580..b6cff604 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -52,9 +52,23 @@ impl Deref for Indexed { impl ::std::cmp::PartialEq for Grid { fn eq(&self, other: &Self) -> bool { + // Compare raw grid content + // TODO: This is pretty inefficient but only used in tests + let mut raw_match = true; + for i in 0..(self.lines.0 + self.history_size) { + for j in 0..self.cols.0 { + if self[i][Column(j)] != other[i][Column(j)] { + raw_match = false; + break; + } + } + } + + // Compare struct fields and check result of grid comparison self.cols.eq(&other.cols) && self.lines.eq(&other.lines) && - self.raw.eq(&other.raw) + self.history_size.eq(&other.history_size) && + raw_match } } @@ -88,6 +102,10 @@ pub struct Grid { /// Selected region #[serde(skip)] pub selection: Option, + + /// Maximum number of lines in the scrollback history + #[serde(default)] + history_size: usize, } pub struct GridIterator<'a, T: 'a> { @@ -132,6 +150,7 @@ impl Grid { display_offset: 0, scroll_limit: 0, selection: None, + history_size: scrollback, } } -- cgit From 8168d85a21b1a67b9cf25808c4e3e01f7437b50d Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 28 Apr 2018 16:15:21 +0200 Subject: Improve storage comparison algorithm Instead of iterating over the raw storage vector because the offsets don't allow direct comparison, the comparison is now done in chunks. Based on benchmarking this is a lot more efficient than using split_off + append or iterating over the elements of the buffer. The `history_size` field has also been removed from the storage structure because it can be easily calculated by substracting the number of visible lines from the length of the raw storage vector. --- src/grid/mod.rs | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index b6cff604..f3c8ea79 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -52,23 +52,13 @@ impl Deref for Indexed { impl ::std::cmp::PartialEq for Grid { fn eq(&self, other: &Self) -> bool { - // Compare raw grid content - // TODO: This is pretty inefficient but only used in tests - let mut raw_match = true; - for i in 0..(self.lines.0 + self.history_size) { - for j in 0..self.cols.0 { - if self[i][Column(j)] != other[i][Column(j)] { - raw_match = false; - break; - } - } - } - // Compare struct fields and check result of grid comparison - self.cols.eq(&other.cols) && + self.raw.eq(&other.raw) && + self.cols.eq(&other.cols) && self.lines.eq(&other.lines) && - self.history_size.eq(&other.history_size) && - raw_match + self.display_offset.eq(&other.display_offset) && + self.scroll_limit.eq(&other.scroll_limit) && + self.selection.eq(&other.selection) } } @@ -102,10 +92,6 @@ pub struct Grid { /// Selected region #[serde(skip)] pub selection: Option, - - /// Maximum number of lines in the scrollback history - #[serde(default)] - history_size: usize, } pub struct GridIterator<'a, T: 'a> { @@ -150,7 +136,6 @@ impl Grid { display_offset: 0, scroll_limit: 0, selection: None, - history_size: scrollback, } } @@ -426,6 +411,12 @@ impl Grid { self.scroll_limit = 0; } + /// Total number of lines in the buffer, this includes scrollback + visible lines + #[inline] + pub fn len(&self) -> usize { + self.raw.len() + } + pub fn iter_from(&self, point: Point) -> GridIterator { GridIterator { grid: self, -- cgit From a3c6c1db63af8dea62146739dc183c05b26b56d6 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Mon, 30 Apr 2018 00:22:15 +0200 Subject: Improve the resizing implementation Until now the resizing implementation with scrollback has been really inefficient because it made use of APIs like `Vec::insert`. This has been rewored with this commit. A `len` property has been added to the `Storage` struct which keeps track of the actual length of the raw buffer. This has changed both shrinking and growing implementations. With shrinking, no more lines are removed, only the `len` property is updated to set all lines shrunk to an "invisible" state which cannot be accessed from the outside, this effectively changes it to a O(1) operation. The only issue with this would be memory consumption, but since the maximum shrinkage is the number of lines on one screen, it should be a rather small impacte (probabl <100 lines usually). If desired it would be possible to change this to shrink the raw inner buffer whenever there are more than X lines hidden. Growing now works in a similar way to shrinking, if the "invisible" lines are enough, no new lines are inserted but rather the invisible buffer is made visible again. Otherwise the amount of lines that still needs to be inserted is added to the raw buffer, but instead of the inefficient `Vec::insert`, the `Vec::push` API is used now. This fixes #1271. --- src/grid/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index f3c8ea79..87ac3d05 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -125,7 +125,7 @@ impl Grid { // TODO (jwilm) Allocating each line at this point is expensive and // delays startup. A nice solution might be having `Row` delay // allocation until it's actually used. - for _ in 0..raw.capacity() { + for _ in 0..raw.len() { raw.push(Row::new(cols, &template)); } -- cgit From eabd6bb95b1ab883bdec16f8c307432c1e7c73d5 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Tue, 1 May 2018 20:07:59 +0200 Subject: Remove `push` from `Storage` Since every line is allocated at startup anyways, the `push` method on `Storage` has been removed and instead of pushing to the vector the initialization has been moved to the `with_capacity` method. This has the advantage that we don't need to keep track of the `len` in push (like adding one), but we just need to worry about growing/shrinking the visible area. --- src/grid/mod.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 87ac3d05..803f1e6e 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -118,17 +118,7 @@ pub enum Scroll { impl Grid { pub fn new(lines: index::Line, cols: index::Column, scrollback: usize, template: T) -> Grid { - let mut raw = Storage::with_capacity(*lines + scrollback, lines); - - // Allocate all lines in the buffer, including scrollback history - // - // TODO (jwilm) Allocating each line at this point is expensive and - // delays startup. A nice solution might be having `Row` delay - // allocation until it's actually used. - for _ in 0..raw.len() { - raw.push(Row::new(cols, &template)); - } - + let raw = Storage::with_capacity(*lines + scrollback, lines, Row::new(cols, &template)); Grid { raw, cols, -- cgit From ac93f6d03198c1826dbed60fa8283aeb8d1a577d Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Tue, 15 May 2018 22:36:14 +0200 Subject: Truncate invisible lines before storing ref-tests Because there is no good way to store invisible lines in a backwards- and forwards-compatible way, they buffer now gets truncated before dumping the state of a grid when creating a ref-test. This involved a few workaround of which a few required adding additional methods which are only used in ref-tests, these should be minimal though. Since this required the creation of a truncation method anyways, some logic has been added which automatically truncates the invisible buffer when there are more than X (set to 100) invisible lines. This should not impact performance because it rarely occurs, but it could save a bit of memory when the history size is shrunk during runtime (see #1293). This also adds an optional `config.json` file to the ref-test output where it is possible to manually specify variables which should override config defaults, this has been used only for history_size so far. Creating a new ref-test does also still work, so there was no regression here, if history size is altered, the config.json just has to be created manually with the content `{"history_size":HIST_SIZE}`, where `HIST_SIZE` is the desired history size. --- src/grid/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 803f1e6e..0eea4201 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -407,6 +407,11 @@ impl Grid { self.raw.len() } + /// This is used only for truncating before saving ref-tests + pub fn truncate(&mut self) { + self.raw.truncate(); + } + pub fn iter_from(&self, point: Point) -> GridIterator { GridIterator { grid: self, -- cgit From 5a11f8584317b6a949a1ff3a5b3a446766db0665 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Sat, 19 May 2018 12:06:21 -0700 Subject: Fix OOB index in grid::DisplayIter When resizing prior to this patch, hidden rows in Storage were not having columns added along with everything else. This feels like a bit of tech debt, but the patch is simple enough that it won't be much extra to back out later when the underlying cause is addressed (see comments in code). --- src/grid/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 0eea4201..9b36fcdf 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -244,7 +244,7 @@ impl Grid { } fn grow_cols(&mut self, cols: index::Column, template: &T) { - for row in self.raw.iter_mut() { + for row in self.raw.iter_mut_raw() { row.grow(cols, template); } -- cgit From 169b87e4ce19f4fd22e5d06ff27f9d3d0ee19b44 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Sat, 19 May 2018 14:47:16 -0700 Subject: Shave a few cycles off Grid::scroll_up This implementation avoids a few extra transformations by operating in buffer-space. However, there is still a transformation from the logical buffer-space to the underlying vector. --- src/grid/mod.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 9b36fcdf..0998d640 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -334,7 +334,6 @@ impl Grid { /// scroll_up moves lines at the bottom towards the top /// /// This is the performance-sensitive part of scrolling. - #[inline] pub fn scroll_up( &mut self, region: &Range, @@ -356,17 +355,21 @@ impl Grid { selection.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_lines(Line(idx), Line(idx) - positions); + // // This next loop swaps "fixed" lines outside of a scroll region + // // back into place after the rotation. The work is done in buffer- + // // space rather than terminal-space to avoid redundant + // // transformations. + let fixed_lines = *self.num_lines() - *region.end; + + for i in 0..fixed_lines { + self.raw.swap(i, i + *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(&template); + self.raw[i + fixed_lines].reset(&template); } } else { // Subregion rotation -- cgit From efc568be4e7da9481f5b6d35ff2854c1ff4cd2be Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Tue, 29 May 2018 11:15:03 -0700 Subject: Fix issue with endless allocation loop Shrinking columns wasn't updating hidden rows in the storage buffer on resizing. When growing the columns again, this resulted in an endless allocation loop. Only one real change was made here, and that was `raw.iter_mut()` to `raw.iter_mut_raw()`. The method `shrink_cols` was relocated to be adjacent to `grow_cols` in the code. --- src/grid/mod.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 0998d640..c9829c2e 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -252,6 +252,14 @@ impl Grid { self.cols = cols; } + fn shrink_cols(&mut self, cols: index::Column) { + for row in self.raw.iter_mut_raw() { + row.shrink(cols); + } + + self.cols = cols; + } + /// Remove lines from the visible area /// /// The behavior in Terminal.app and iTerm.app is to keep the cursor at the @@ -437,14 +445,6 @@ impl Grid { // 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() { - row.shrink(cols); - } - - self.cols = cols; - } } impl<'a, T> Iterator for GridIterator<'a, T> { -- cgit From 9a98d5e0ee9139d5a2988d125352c5d70a39ad20 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Tue, 29 May 2018 21:33:13 -0700 Subject: Refactor Storage to be a Vec> internally This will allow certain optimizations around swap which are currently only possible in a generalized way with specialization. --- src/grid/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index c9829c2e..b03ae54f 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -67,7 +67,7 @@ impl ::std::cmp::PartialEq for Grid { pub struct Grid { /// Lines in the grid. Each row holds a list of cells corresponding to the /// columns in that row. - raw: Storage>, + raw: Storage, /// Number of columns cols: index::Column, @@ -540,7 +540,7 @@ impl<'point, T> IndexMut<&'point Point> for Grid { pub struct Region<'a, T: 'a> { start: Line, end: Line, - raw: &'a Storage>, + raw: &'a Storage, } /// A mutable subset of lines in the grid @@ -549,7 +549,7 @@ pub struct Region<'a, T: 'a> { pub struct RegionMut<'a, T: 'a> { start: Line, end: Line, - raw: &'a mut Storage>, + raw: &'a mut Storage, } impl<'a, T> RegionMut<'a, T> { @@ -653,13 +653,13 @@ impl IndexRegion for Grid { pub struct RegionIter<'a, T: 'a> { end: Line, cur: Line, - raw: &'a Storage>, + raw: &'a Storage, } pub struct RegionIterMut<'a, T: 'a> { end: Line, cur: Line, - raw: &'a mut Storage>, + raw: &'a mut Storage, } impl<'a, T> IntoIterator for Region<'a, T> { -- cgit From 24c50fa0a793cd8c6127b5cf8ba3ea59f47cc1ca Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Tue, 29 May 2018 21:45:28 -0700 Subject: Remove dead code --- src/grid/mod.rs | 1 - 1 file changed, 1 deletion(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index b03ae54f..97614d71 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -230,7 +230,6 @@ impl Grid { new_line_count: index::Line, template: &T, ) { - let previous_scroll_limit = self.scroll_limit; let lines_added = new_line_count - self.lines; // Need to "resize" before updating buffer -- cgit From 4631ca4db5faf10eb0276e3968814a68c86c81ee Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Wed, 30 May 2018 10:20:47 +0200 Subject: Allow changing scrollback history size at runtime Making use of the changes that have been introduced in #1234 and #1284, this allows changing the size of the scrollback buffer at runtime. This simply changes the size of the raw inner buffer making use of the optimized mutation algorithms introduced in #1284. As a result, shrinking the scrollback history size at runtime should be basically free and growing will only introduce a performance cost when there are no more buffered lines. However, as a result there will not be any memory freed when shrinking the scrollback history size at runtime. As discussed in #1234 a potential solution for this could be to truncate the raw buffer whenever more than X lines are deleted, however this issue should not be very significant PR and if a solution is desired a separate issue/PR should be opened. This fixes #1235. --- src/grid/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 97614d71..535f6cc6 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -155,6 +155,13 @@ impl Grid { self.line_to_offset(line) + self.display_offset } + /// Update the size of the scrollback history + pub fn update_history(&mut self, history_size: usize, template: &T) + { + self.raw.update_history(history_size, Row::new(self.cols, &template)); + self.scroll_limit = min(self.scroll_limit, history_size); + } + pub fn scroll_display(&mut self, scroll: Scroll) { match scroll { Scroll::Lines(count) => { -- cgit From 07aaf05f7463a971e56de87e3b6b24e4153e5a98 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Mon, 2 Jul 2018 22:03:04 +0000 Subject: Fix scrollback accessing indices out of bounds There have been two instances of the scrollback trying to access indices which were moved out of bounds due to new lines (`yes` command). These have both been fixed. The first instance was during semantic selection, since the logic of limiting the selection start point was moved outside of `compute_index`, it was necessary to add this to semantic selection too. Now semantic selection, line selection and normal selection should all work without crashing when new lines are shoving the selection out of bounds. The other error was with the viewport being outside of the scrollback history. Since the default is to keep the scrollback buffer at its current position when new lines are added, it is possible that the position the scrollback buffer is at is suddenly shoved out of the visible area. To fix this the `display_offset` is now limited to always be an allowed value. If a single line of the viewport is moved out of the history now, the viewport should move down a single line now, so only valid content is displayed, with multiple lines this process is repeated. This fixes #1400. There was another error where the iterator would attempt to iterate before the first line in the history buffer, this was because the bounds of the `prev` iterator weren't setup correctly. The iterator should now properly iterate from the first cell in the terminal until the last one. This also fixes #1406, since these semantic selection errors were partiall related to indexing. --- src/grid/mod.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 535f6cc6..949a5ed5 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -100,12 +100,6 @@ pub struct GridIterator<'a, T: 'a> { /// Current position of the iterator within the grid. pub cur: Point, - - /// Bottom of screen (buffer) - bot: usize, - - /// Top of screen (buffer) - top: usize, } pub enum Scroll { @@ -357,7 +351,10 @@ impl Grid { if region.start == Line(0) { // Update display offset when not pinned to active area if self.display_offset != 0 { - self.display_offset += *positions; + self.display_offset = min( + self.display_offset + *positions, + self.len() - self.num_lines().0, + ); } self.increase_scroll_limit(*positions); @@ -433,8 +430,6 @@ impl Grid { GridIterator { grid: self, cur: point, - bot: self.display_offset, - top: self.display_offset + *self.num_lines() - 1, } } @@ -459,7 +454,7 @@ impl<'a, T> Iterator for GridIterator<'a, T> { fn next(&mut self) -> Option { let last_col = self.grid.num_cols() - Column(1); match self.cur { - Point { line, col } if (line == self.bot) && (col == last_col) => None, + Point { line, col } if line == 0 && col == last_col => None, Point { col, .. } if (col == last_col) => { self.cur.line -= 1; @@ -479,7 +474,7 @@ impl<'a, T> BidirectionalIterator for GridIterator<'a, T> { let num_cols = self.grid.num_cols(); match self.cur { - Point { line, col: Column(0) } if line == self.top => None, + Point { line, col: Column(0) } if line == self.grid.len() - 1 => None, Point { col: Column(0), .. } => { self.cur.line += 1; self.cur.col = num_cols - Column(1); -- cgit From f50ca1a54c94fe324d22d985c1acae1ff7c16a80 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 21 Jul 2018 17:17:41 +0000 Subject: Scrollback cleanup There were some unneeded codeblocks and TODO/XXX comments in the code that have been removed. All issues marked with TODO/XXX have either been already resolved or tracking issues exist. --- src/grid/mod.rs | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 949a5ed5..ef587ffd 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -102,6 +102,7 @@ pub struct GridIterator<'a, T: 'a> { pub cur: Point, } +#[derive(Copy, Clone)] pub enum Scroll { Lines(isize), PageUp, @@ -396,6 +397,7 @@ impl Grid { } } +#[cfg_attr(feature = "cargo-clippy", allow(len_without_is_empty))] impl Grid { #[inline] pub fn num_lines(&self) -> index::Line { @@ -437,15 +439,6 @@ impl Grid { pub fn contains(&self, point: &Point) -> bool { 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); - // } } impl<'a, T> Iterator for GridIterator<'a, T> { @@ -566,10 +559,10 @@ impl<'a, T> RegionMut<'a, T> { pub trait IndexRegion { /// Get an immutable region of Self - fn region<'a>(&'a self, _: I) -> Region<'a, T>; + fn region(&self, _: I) -> Region; /// Get a mutable region of Self - fn region_mut<'a>(&'a mut self, _: I) -> RegionMut<'a, T>; + fn region_mut(&mut self, _: I) -> RegionMut; } impl IndexRegion, T> for Grid { @@ -772,13 +765,11 @@ impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { // Update line/col to point to next item self.col += 1; - if self.col == self.grid.num_cols() { - if self.offset != self.limit { - self.offset -= 1; + if self.col == self.grid.num_cols() && self.offset != self.limit { + self.offset -= 1; - self.col = Column(0); - self.line = Line(*self.grid.lines - 1 - (self.offset - self.limit)); - } + self.col = Column(0); + self.line = Line(*self.grid.lines - 1 - (self.offset - self.limit)); } item -- cgit From facab5beef95c4b1808f7bcba92aa74bf792daf0 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Fri, 3 Aug 2018 22:12:23 +0000 Subject: Reset visible area when RIS is received When running bash and executing `echo -ne '\033c'`, the terminal should be cleared. However there was an issue with the visible area not being cleared, so all the text previously printed would still remain visible. To fix this, whenever a `reset` call is received now, the complete visible area is reset to `Cell::default()` (the default Cell) and the length of the available scrollback history is reset to `0`, which results in the scrollback history being cleared from the perspective of the user. This fixes #1483. --- src/grid/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index ef587ffd..05fae373 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -413,7 +413,7 @@ impl Grid { self.cols } - pub fn reset(&mut self) { + pub fn clear_history(&mut self) { self.scroll_limit = 0; } -- cgit From 8e8ecdd0f98dd8005cd940d19dc0a922661d64fc Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Wed, 15 Aug 2018 20:09:59 +0000 Subject: Scroll visible area when growing window Since Alacritty never had any scrollback history, the behavior when the window height was increased was to just keep the prompt on the same line it has been before the resize. However the usual behavior of terminal emulators is to keep the distance from the prompt to the bottom of the screen consistent whenever possible. This fixes this behavior by loading lines from the scrollback buffer when the window height is increased. This is only done when scrollback is available, so there are only N lines available, the maximum amount of lines which will be loaded when growing the height is N. Since the number of lines available in the alternate screen buffer is 0, it still behaves the same way it did before this patch. Different terminal emulators have different behaviors when this is done in the alt screen buffer, XTerm for example loads history from the normal screen buffer when growing the height of the window from the alternate screen buffer. Since this seems wrong (the alt screen is not supposed to have any scrollback), the behavior of Termite (VTE) has been chosen instead. In Termite the alt screen buffer never loads any scrollback history itself, however when the terminal height is grown while the alternate screen is active, the normal screen's scrollback history lines are loaded. This fixes #1502. --- src/grid/mod.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 05fae373..6359a4ee 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -238,8 +238,11 @@ impl Grid { self.raw.grow_visible_lines(new_line_count, Row::new(self.cols, template)); self.lines = new_line_count; - // Add new lines to bottom - self.scroll_up(&(Line(0)..new_line_count), lines_added, template); + // Move existing lines up if there is no scrollback to fill new lines + if lines_added.0 > self.scroll_limit { + let scroll_lines = lines_added - self.scroll_limit; + self.scroll_up(&(Line(0)..new_line_count), scroll_lines, template); + } self.scroll_limit = self.scroll_limit.saturating_sub(*lines_added); } @@ -417,6 +420,11 @@ impl Grid { self.scroll_limit = 0; } + #[inline] + pub fn scroll_limit(&self) -> usize { + self.scroll_limit + } + /// Total number of lines in the buffer, this includes scrollback + visible lines #[inline] pub fn len(&self) -> usize { -- cgit From 72495172c25e00799f29eb2e79fe40ddfa189866 Mon Sep 17 00:00:00 2001 From: Nathan Lilienthal Date: Sat, 1 Sep 2018 20:30:03 -0400 Subject: Implement `ansi::ClearMode::Saved` The clearing the screen for the `ansi::ClearMode::Saved` enum value has been implemented. This is used to clear all lines which are currently outside of the visible region but still inside the scrollback buffer. The specifications of XTerm indicate that the clearing of saved lines should only clear the saved lines and not the saved lines plus the currently visible part of the grid. Applications like `clear` send both the escape for clearing history plus the escape for clearing history when requested, so all sources seem to agree here. To allow both clearing the screen and the saved lines when a key is pressed the `process_key_bindings` method has been altered so multiple bindings can be specified. So it is now possible to execute both `^L` and `ClearHistory` with just a single binding. The `process_mouse_bindings` method has also been changed for consistency. To make sure everything works properly a test has been added which clears the history and then attempts to scroll. Since scrolling is the only way for a user to check if scrollback is available, this seems like a nice abstraction to check if there is a scrollback. --- src/grid/mod.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'src/grid/mod.rs') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 6359a4ee..680aa7bd 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -219,14 +219,9 @@ impl Grid { /// 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. + /// 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: index::Line, -- cgit