diff --git a/CHANGELOG.md b/CHANGELOG.md index 5aa786bb..8c8cb0a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ability to specify starting position with the `--position` flag - New configuration field `window.position` allows specifying the starting position - Added the ability to change the selection color +- Text will reflow instead of truncating when resizing Alacritty ### Fixed @@ -34,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - FreeBSD: SpawnNewInstance will now open new instances in the shell's current working directory as long as linprocfs(5) is mounted on `/compat/linux/proc` - Fix lingering Alacritty window after child process has exited +- Growing the terminal while scrolled up will no longer move the content down ## Version 0.2.9 diff --git a/src/cli.rs b/src/cli.rs index 12d3ade0..1997a6bb 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -164,8 +164,8 @@ impl Options { } } - options.class = matches.value_of("class").map(|c| c.to_owned()); - options.title = matches.value_of("title").map(|t| t.to_owned()); + options.class = matches.value_of("class").map(ToOwned::to_owned); + options.title = matches.value_of("title").map(ToOwned::to_owned); match matches.occurrences_of("q") { 0 => {}, diff --git a/src/config/mod.rs b/src/config/mod.rs index b8dd9f82..2fa60dad 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -170,7 +170,7 @@ impl Default for Url { fn deserialize_modifiers<'a, D>(deserializer: D) -> ::std::result::Result where D: de::Deserializer<'a> { - ModsWrapper::deserialize(deserializer).map(|wrapper| wrapper.into_inner()) + ModsWrapper::deserialize(deserializer).map(ModsWrapper::into_inner) } /// `VisualBellAnimations` are modeled after a subset of CSS transitions and Robert @@ -1665,7 +1665,7 @@ impl Config { } None }) - .map(|path| path.into()) + .map(Into::into) } // TODO: Remove old configuration location warning (Deprecated 03/12/2018) @@ -1810,7 +1810,7 @@ impl Config { pub fn path(&self) -> Option<&Path> { self.config_path .as_ref() - .map(|p| p.as_path()) + .map(PathBuf::as_path) } pub fn shell(&self) -> Option<&Shell<'_>> { diff --git a/src/event.rs b/src/event.rs index f8044609..121c0c42 100644 --- a/src/event.rs +++ b/src/event.rs @@ -86,7 +86,7 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { } fn selection_is_empty(&self) -> bool { - self.terminal.selection().as_ref().map(|s| s.is_empty()).unwrap_or(true) + self.terminal.selection().as_ref().map(Selection::is_empty).unwrap_or(true) } fn clear_selection(&mut self) { diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 80289cd6..837d9b0e 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -64,6 +64,12 @@ impl ::std::cmp::PartialEq for Grid { } } +pub trait GridCell { + fn is_empty(&self) -> bool; + fn is_wrap(&self) -> bool; + fn set_wrap(&mut self, wrap: bool); +} + /// Represents the terminal display contents #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Grid { @@ -123,7 +129,7 @@ pub enum ViewportPosition { Below, } -impl Grid { +impl Grid { pub fn new(lines: index::Line, cols: index::Column, scrollback: usize, template: T) -> Grid { let raw = Storage::with_capacity(lines, Row::new(cols, &template)); Grid { @@ -197,6 +203,7 @@ impl Grid { &mut self, lines: index::Line, cols: index::Column, + cursor_pos: &mut Point, template: &T, ) { // Check that there's actually work to do and return early if not @@ -211,8 +218,8 @@ impl Grid { } match self.cols.cmp(&cols) { - Ordering::Less => self.grow_cols(cols, template), - Ordering::Greater => self.shrink_cols(cols), + Ordering::Less => self.grow_cols(cols, cursor_pos, template), + Ordering::Greater => self.shrink_cols(cols, cursor_pos, template), Ordering::Equal => (), } } @@ -259,20 +266,125 @@ impl Grid { } self.scroll_limit = self.scroll_limit.saturating_sub(*lines_added); + self.display_offset = self.display_offset.saturating_sub(*lines_added); } - fn grow_cols(&mut self, cols: index::Column, template: &T) { - for row in self.raw.iter_mut_raw() { - row.grow(cols, template); + fn grow_cols(&mut self, cols: index::Column, cursor_pos: &mut Point, template: &T) { + // Truncate all buffered lines + self.raw.grow_hidden(cols, template); + + let max_lines = self.lines.0 + self.max_scroll_limit; + + // Iterate backwards with indices for mutation during iteration + let mut i = self.raw.len(); + while i > 0 { + i -= 1; + + // Grow the current line if there's wrapped content available + while i >= 1 + && self.raw[i].len() < cols.0 + && self.raw[i].last().map(GridCell::is_wrap) == Some(true) + { + // Remove wrap flag before appending additional cells + if let Some(cell) = self.raw[i].last_mut() { + cell.set_wrap(false); + } + + // Append as many cells from the next line as possible + let len = min(self.raw[i - 1].len(), cols.0 - self.raw[i].len()); + let mut cells = self.raw[i - 1].front_split_off(len); + self.raw[i].append(&mut cells); + + if self.raw[i - 1].is_empty() { + // Remove following line if all cells have been drained + self.raw.remove(i - 1); + + if self.raw.len() < self.lines.0 || self.scroll_limit == 0 { + // Add new line and move lines up if we can't pull from history + self.raw.insert(0, Row::new(cols, template), max_lines); + cursor_pos.line = Line(cursor_pos.line.saturating_sub(1)); + } else { + // Make sure viewport doesn't move if line is outside of the visible area + if i < self.display_offset { + self.display_offset = self.display_offset.saturating_sub(1); + } + + // Remove one line from scrollback, since we just moved it to the viewport + self.scroll_limit = self.scroll_limit.saturating_sub(1); + self.display_offset = min(self.display_offset, self.scroll_limit); + i -= 1; + } + } else if let Some(cell) = self.raw[i].last_mut() { + // Set wrap flag if next line still has cells + cell.set_wrap(true); + } + } + + // Fill remaining cells + if self.raw[i].len() < cols.0 { + self.raw[i].grow(cols, template); + } } - // Update self cols self.cols = cols; } - fn shrink_cols(&mut self, cols: index::Column) { - for row in self.raw.iter_mut_raw() { - row.shrink(cols); + fn shrink_cols(&mut self, cols: index::Column, cursor_pos: &mut Point, template: &T) { + // Truncate all buffered lines + self.raw.shrink_hidden(cols); + + let max_lines = self.lines.0 + self.max_scroll_limit; + + // Iterate backwards with indices for mutation during iteration + let mut i = self.raw.len(); + while i > 0 { + i -= 1; + + if let Some(mut new_row) = self.raw[i].shrink(cols) { + // Set line as wrapped if cells got removed + if let Some(cell) = self.raw[i].last_mut() { + cell.set_wrap(true); + } + + if Some(true) == new_row.last().map(|c| c.is_wrap() && i >= 1) + && new_row.len() < cols.0 + { + // Make sure previous wrap flag doesn't linger around + if let Some(cell) = new_row.last_mut() { + cell.set_wrap(false); + } + + // Add removed cells to start of next row + self.raw[i - 1].append_front(new_row); + } else { + // Make sure viewport doesn't move if line is outside of the visible area + if i < self.display_offset { + self.display_offset = min(self.display_offset + 1, self.max_scroll_limit); + } + + // Make sure new row is at least as long as new width + let occ = new_row.len(); + if occ < cols.0 { + new_row.append(&mut vec![*template; cols.0 - occ]); + } + let row = Row::from_vec(new_row, occ); + + // Add new row with all removed cells + self.raw.insert(i, row, max_lines); + + if cursor_pos.line >= self.lines - 1 { + // Increase scrollback history + self.scroll_limit = min(self.scroll_limit + 1, self.max_scroll_limit); + + // Since inserted might exceed cols, we need to check the same line again + i += 1; + } else { + // Pull content down if cursor is not at the bottom + self.raw.rotate(1); + cursor_pos.line += 1; + } + } + } } self.cols = cols; diff --git a/src/grid/row.rs b/src/grid/row.rs index 72c79b02..ef27f040 100644 --- a/src/grid/row.rs +++ b/src/grid/row.rs @@ -16,9 +16,10 @@ use std::ops::{Index, IndexMut}; use std::ops::{Range, RangeTo, RangeFrom, RangeFull, RangeToInclusive}; -use std::cmp::{max, min}; +use std::cmp::{min, max}; use std::slice; +use crate::grid::GridCell; use crate::index::Column; /// A row in the grid @@ -43,7 +44,7 @@ impl PartialEq for Row { } } -impl Row { +impl Row { pub fn new(columns: Column, template: &T) -> Row { Row { inner: vec![*template; *columns], @@ -52,52 +53,102 @@ impl Row { } pub fn grow(&mut self, cols: Column, template: &T) { - assert!(self.len() < * cols); + if self.inner.len() >= cols.0 { + return; + } - while self.len() != *cols { - self.inner.push(*template); + self.inner.append(&mut vec![*template; cols.0 - self.len()]); + } + + pub fn shrink(&mut self, cols: Column) -> Option> + where + T: GridCell + { + if self.inner.len() <= cols.0 { + return None; + } + + // Split off cells for a new row + let mut new_row = self.inner.split_off(cols.0); + let index = new_row.iter().rposition(|c| !c.is_empty()).map(|i| i + 1).unwrap_or(0); + new_row.truncate(index); + + self.occ = min(self.occ, *cols); + + if new_row.is_empty() { + None + } else { + Some(new_row) } } /// Resets contents to the contents of `other` #[inline(never)] pub fn reset(&mut self, other: &T) { - let occ = self.occ; - for item in &mut self.inner[..occ] { + for item in &mut self.inner[..self.occ] { *item = *other; } - self.occ = 0; } } #[allow(clippy::len_without_is_empty)] impl Row { - pub fn shrink(&mut self, cols: Column) { - while self.len() != *cols { - self.inner.pop(); + #[inline] + pub fn from_vec(vec: Vec, occ: usize) -> Row { + Row { + inner: vec, + occ, } - - self.occ = min(self.occ, *cols); } + #[inline] pub fn len(&self) -> usize { self.inner.len() } - pub fn iter(&self) -> slice::Iter<'_, T> { - self.inner.iter() + #[inline] + pub fn last(&self) -> Option<&T> { + self.inner.last() } -} - - -impl<'a, T> IntoIterator for &'a Row { - type Item = &'a T; - type IntoIter = slice::Iter<'a, T>; #[inline] - fn into_iter(self) -> slice::Iter<'a, T> { - self.iter() + pub fn last_mut(&mut self) -> Option<&mut T> { + self.occ = self.inner.len(); + self.inner.last_mut() + } + + #[inline] + pub fn append(&mut self, vec: &mut Vec) + where + T: GridCell + { + self.inner.append(vec); + self.occ = self.inner.iter().rposition(|c| !c.is_empty()).map(|i| i + 1).unwrap_or(0); + } + + #[inline] + pub fn append_front(&mut self, mut vec: Vec) { + self.occ += vec.len(); + vec.append(&mut self.inner); + self.inner = vec; + } + + #[inline] + pub fn is_empty(&self) -> bool + where + T: GridCell + { + self.inner.iter().all(|c| c.is_empty()) + } + + #[inline] + pub fn front_split_off(&mut self, at: usize) -> Vec { + self.occ = self.occ.saturating_sub(at); + + let mut split = self.inner.split_off(at); + std::mem::swap(&mut split, &mut self.inner); + split } } diff --git a/src/grid/storage.rs b/src/grid/storage.rs index 87a129d0..6a119ead 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -12,11 +12,11 @@ /// implementation is provided. Anything from Vec that should be exposed must be /// done so manually. use std::ops::{Index, IndexMut}; -use std::slice; use static_assertions::assert_eq_size; -use crate::index::Line; +use crate::index::{Column, Line}; +use crate::grid::GridCell; use super::Row; /// Maximum number of invisible lines before buffer is resized @@ -196,6 +196,7 @@ impl Storage { self.len } + #[inline] /// Compute actual index in underlying storage given the requested index. fn compute_index(&self, requested: usize) -> usize { debug_assert!(requested < self.len); @@ -250,18 +251,7 @@ impl Storage { } } - /// Iterate over *all* entries in the underlying buffer - /// - /// This includes hidden entries. - /// - /// XXX This suggests that Storage is a leaky abstraction. Ultimately, this - /// is needed because of the grow lines functionality implemented on - /// this type, and maybe that's where the leak is necessitating this - /// accessor. - pub fn iter_mut_raw<'a>(&'a mut self) -> slice::IterMut<'a, Row> { - self.inner.iter_mut() - } - + #[inline] pub fn rotate(&mut self, count: isize) { debug_assert!(count.abs() as usize <= self.inner.len()); @@ -270,9 +260,75 @@ impl Storage { } // Fast path + #[inline] pub fn rotate_up(&mut self, count: usize) { self.zero = (self.zero + count) % self.inner.len(); } + + #[inline] + pub fn insert(&mut self, index: usize, row: Row, max_lines: usize) { + let index = self.compute_index(index); + self.inner.insert(index, row); + + if index < self.zero { + self.zero += 1; + } + + if self.len < max_lines { + self.len += 1; + } + } + + #[inline] + pub fn remove(&mut self, index: usize) -> Row { + let index = self.compute_index(index); + if index < self.zero { + self.zero -= 1; + } + self.len -= 1; + + self.inner.remove(index) + } + + /// Shrink columns of hidden buffered lines. + /// + /// XXX This suggests that Storage is a leaky abstraction. Ultimately, this + /// is needed because of the grow/shrink lines functionality. + #[inline] + pub fn shrink_hidden(&mut self, cols: Column) + where + T: GridCell + Copy + { + let start = self.zero + self.len; + let end = self.zero + self.inner.len(); + for mut i in start..end { + if i >= self.inner.len() { + i -= self.inner.len(); + } + + self.inner[i].shrink(cols); + } + } + + /// Grow columns of hidden buffered lines. + /// + /// XXX This suggests that Storage is a leaky abstraction. Ultimately, this + /// is needed because of the grow/shrink lines functionality. + #[inline] + pub fn grow_hidden(&mut self, cols: Column, template: &T) + where + T: Copy + Clone + { + let start = self.zero + self.len; + let end = self.zero + self.inner.len(); + for mut i in start..end { + if i >= self.inner.len() { + i -= self.inner.len(); + } + + self.inner[i].grow(cols, template); + } + } } impl Index for Storage { @@ -308,9 +364,6 @@ impl IndexMut for Storage { } } -#[cfg(test)] -use crate::index::Column; - /// Grow the buffer one line at the end of the buffer /// /// Before: @@ -693,3 +746,123 @@ fn initialize() { assert_eq!(storage.zero, shrinking_expected.zero); assert_eq!(storage.len, shrinking_expected.len); } + +#[test] +fn insert() { + // Setup storage area + let mut storage = Storage { + inner: vec![ + Row::new(Column(1), &'4'), + Row::new(Column(1), &'5'), + Row::new(Column(1), &'0'), + Row::new(Column(1), &'1'), + Row::new(Column(1), &'2'), + Row::new(Column(1), &'3'), + ], + zero: 2, + visible_lines: Line(0), + len: 6, + }; + + // Initialize additional lines + storage.insert(2, Row::new(Column(1), &'-'), 100); + + // Make sure the lines are present and at the right location + let shrinking_expected = Storage { + inner: vec![ + Row::new(Column(1), &'4'), + Row::new(Column(1), &'5'), + Row::new(Column(1), &'0'), + Row::new(Column(1), &'1'), + Row::new(Column(1), &'-'), + Row::new(Column(1), &'2'), + Row::new(Column(1), &'3'), + ], + zero: 2, + visible_lines: Line(0), + len: 7, + }; + assert_eq!(storage.inner, shrinking_expected.inner); + assert_eq!(storage.zero, shrinking_expected.zero); + assert_eq!(storage.len, shrinking_expected.len); +} + +#[test] +fn insert_truncate_max() { + // Setup storage area + let mut storage = Storage { + inner: vec![ + Row::new(Column(1), &'4'), + Row::new(Column(1), &'5'), + Row::new(Column(1), &'0'), + Row::new(Column(1), &'1'), + Row::new(Column(1), &'2'), + Row::new(Column(1), &'3'), + ], + zero: 2, + visible_lines: Line(0), + len: 6, + }; + + // Initialize additional lines + storage.insert(2, Row::new(Column(1), &'-'), 6); + + // Make sure the lines are present and at the right location + let shrinking_expected = Storage { + inner: vec![ + Row::new(Column(1), &'4'), + Row::new(Column(1), &'5'), + Row::new(Column(1), &'0'), + Row::new(Column(1), &'1'), + Row::new(Column(1), &'-'), + Row::new(Column(1), &'2'), + Row::new(Column(1), &'3'), + ], + zero: 2, + visible_lines: Line(0), + len: 6, + }; + assert_eq!(storage.inner, shrinking_expected.inner); + assert_eq!(storage.zero, shrinking_expected.zero); + assert_eq!(storage.len, shrinking_expected.len); +} + +#[test] +fn insert_at_zero() { + // Setup storage area + let mut storage = Storage { + inner: vec![ + Row::new(Column(1), &'4'), + Row::new(Column(1), &'5'), + Row::new(Column(1), &'0'), + Row::new(Column(1), &'1'), + Row::new(Column(1), &'2'), + Row::new(Column(1), &'3'), + ], + zero: 2, + visible_lines: Line(0), + len: 6, + }; + + // Initialize additional lines + storage.insert(0, Row::new(Column(1), &'-'), 6); + + // Make sure the lines are present and at the right location + let shrinking_expected = Storage { + inner: vec![ + Row::new(Column(1), &'4'), + Row::new(Column(1), &'5'), + Row::new(Column(1), &'-'), + Row::new(Column(1), &'0'), + Row::new(Column(1), &'1'), + Row::new(Column(1), &'2'), + Row::new(Column(1), &'3'), + ], + zero: 2, + visible_lines: Line(0), + len: 6, + }; + assert_eq!(storage.inner, shrinking_expected.inner); + assert_eq!(storage.zero, shrinking_expected.zero); + assert_eq!(storage.len, shrinking_expected.len); +} diff --git a/src/grid/tests.rs b/src/grid/tests.rs index 560bb0fe..82edda69 100644 --- a/src/grid/tests.rs +++ b/src/grid/tests.rs @@ -16,6 +16,20 @@ use super::{Grid, BidirectionalIterator}; use crate::index::{Point, Line, Column}; +use crate::term::cell::{Cell, Flags}; +use crate::grid::GridCell; + +impl GridCell for usize { + fn is_empty(&self) -> bool { + false + } + + fn is_wrap(&self) -> bool { + false + } + + fn set_wrap(&mut self, _wrap: bool) {} +} // Scroll up moves lines upwards #[test] @@ -123,3 +137,163 @@ fn test_iter() { assert_eq!(None, final_iter.next()); assert_eq!(Some(&23), final_iter.prev()); } + +#[test] +fn shrink_reflow() { + let mut grid = Grid::new(Line(1), Column(5), 2, cell('x')); + grid[Line(0)][Column(0)] = cell('1'); + grid[Line(0)][Column(1)] = cell('2'); + grid[Line(0)][Column(2)] = cell('3'); + grid[Line(0)][Column(3)] = cell('4'); + grid[Line(0)][Column(4)] = cell('5'); + + grid.resize(Line(1), Column(2), &mut Point::new(Line(0), Column(0)), &Cell::default()); + + assert_eq!(grid.len(), 3); + + assert_eq!(grid[2].len(), 2); + assert_eq!(grid[2][Column(0)], cell('1')); + assert_eq!(grid[2][Column(1)], wrap_cell('2')); + + assert_eq!(grid[1].len(), 2); + assert_eq!(grid[1][Column(0)], cell('3')); + assert_eq!(grid[1][Column(1)], wrap_cell('4')); + + assert_eq!(grid[0].len(), 2); + assert_eq!(grid[0][Column(0)], cell('5')); + assert_eq!(grid[0][Column(1)], Cell::default()); +} + +#[test] +fn shrink_reflow_twice() { + let mut grid = Grid::new(Line(1), Column(5), 2, cell('x')); + grid[Line(0)][Column(0)] = cell('1'); + grid[Line(0)][Column(1)] = cell('2'); + grid[Line(0)][Column(2)] = cell('3'); + grid[Line(0)][Column(3)] = cell('4'); + grid[Line(0)][Column(4)] = cell('5'); + + grid.resize(Line(1), Column(4), &mut Point::new(Line(0), Column(0)), &Cell::default()); + grid.resize(Line(1), Column(2), &mut Point::new(Line(0), Column(0)), &Cell::default()); + + assert_eq!(grid.len(), 3); + + assert_eq!(grid[2].len(), 2); + assert_eq!(grid[2][Column(0)], cell('1')); + assert_eq!(grid[2][Column(1)], wrap_cell('2')); + + assert_eq!(grid[1].len(), 2); + assert_eq!(grid[1][Column(0)], cell('3')); + assert_eq!(grid[1][Column(1)], wrap_cell('4')); + + assert_eq!(grid[0].len(), 2); + assert_eq!(grid[0][Column(0)], cell('5')); + assert_eq!(grid[0][Column(1)], Cell::default()); +} + +#[test] +fn shrink_reflow_empty_cell_inside_line() { + let mut grid = Grid::new(Line(1), Column(5), 3, cell('x')); + grid[Line(0)][Column(0)] = cell('1'); + grid[Line(0)][Column(1)] = Cell::default(); + grid[Line(0)][Column(2)] = cell('3'); + grid[Line(0)][Column(3)] = cell('4'); + grid[Line(0)][Column(4)] = Cell::default(); + + grid.resize(Line(1), Column(2), &mut Point::new(Line(0), Column(0)), &Cell::default()); + + assert_eq!(grid.len(), 2); + + assert_eq!(grid[1].len(), 2); + assert_eq!(grid[1][Column(0)], cell('1')); + assert_eq!(grid[1][Column(1)], wrap_cell(' ')); + + assert_eq!(grid[0].len(), 2); + assert_eq!(grid[0][Column(0)], cell('3')); + assert_eq!(grid[0][Column(1)], cell('4')); + + grid.resize(Line(1), Column(1), &mut Point::new(Line(0), Column(0)), &Cell::default()); + + assert_eq!(grid.len(), 4); + + assert_eq!(grid[3].len(), 1); + assert_eq!(grid[3][Column(0)], wrap_cell('1')); + + assert_eq!(grid[2].len(), 1); + assert_eq!(grid[2][Column(0)], wrap_cell(' ')); + + assert_eq!(grid[1].len(), 1); + assert_eq!(grid[1][Column(0)], wrap_cell('3')); + + assert_eq!(grid[0].len(), 1); + assert_eq!(grid[0][Column(0)], cell('4')); +} + +#[test] +fn grow_reflow() { + let mut grid = Grid::new(Line(2), Column(2), 0, cell('x')); + grid[Line(0)][Column(0)] = cell('1'); + grid[Line(0)][Column(1)] = wrap_cell('2'); + grid[Line(1)][Column(0)] = cell('3'); + grid[Line(1)][Column(1)] = Cell::default(); + + grid.resize(Line(2), Column(3), &mut Point::new(Line(0), Column(0)), &Cell::default()); + + assert_eq!(grid.len(), 2); + + assert_eq!(grid[1].len(), 3); + assert_eq!(grid[1][Column(0)], cell('1')); + assert_eq!(grid[1][Column(1)], cell('2')); + assert_eq!(grid[1][Column(2)], cell('3')); + + // Make sure rest of grid is empty + assert_eq!(grid[0].len(), 3); + assert_eq!(grid[0][Column(0)], Cell::default()); + assert_eq!(grid[0][Column(1)], Cell::default()); + assert_eq!(grid[0][Column(2)], Cell::default()); +} + +#[test] +fn grow_reflow_multiline() { + let mut grid = Grid::new(Line(3), Column(2), 0, cell('x')); + grid[Line(0)][Column(0)] = cell('1'); + grid[Line(0)][Column(1)] = wrap_cell('2'); + grid[Line(1)][Column(0)] = cell('3'); + grid[Line(1)][Column(1)] = wrap_cell('4'); + grid[Line(2)][Column(0)] = cell('5'); + grid[Line(2)][Column(1)] = cell('6'); + + grid.resize(Line(3), Column(6), &mut Point::new(Line(0), Column(0)), &Cell::default()); + + assert_eq!(grid.len(), 3); + + assert_eq!(grid[2].len(), 6); + assert_eq!(grid[2][Column(0)], cell('1')); + assert_eq!(grid[2][Column(1)], cell('2')); + assert_eq!(grid[2][Column(2)], cell('3')); + assert_eq!(grid[2][Column(3)], cell('4')); + assert_eq!(grid[2][Column(4)], cell('5')); + assert_eq!(grid[2][Column(5)], cell('6')); + + // Make sure rest of grid is empty + // https://github.com/rust-lang/rust-clippy/issues/3788 + #[allow(clippy::needless_range_loop)] + for r in 0..2 { + assert_eq!(grid[r].len(), 6); + for c in 0..6 { + assert_eq!(grid[r][Column(c)], Cell::default()); + } + } +} + +fn cell(c: char) -> Cell { + let mut cell = Cell::default(); + cell.c = c; + cell +} + +fn wrap_cell(c: char) -> Cell { + let mut cell = cell(c); + cell.flags.insert(Flags::WRAPLINE); + cell +} diff --git a/src/main.rs b/src/main.rs index 4f569cf5..d5142dbe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,7 +45,7 @@ use std::os::unix::io::AsRawFd; #[cfg(target_os = "macos")] use alacritty::locale; use alacritty::{cli, event, die}; -use alacritty::config::{self, Config}; +use alacritty::config::{self, Config, Monitor}; use alacritty::display::Display; use alacritty::event_loop::{self, EventLoop, Msg}; use alacritty::logging; @@ -221,7 +221,7 @@ fn run( let mut terminal_lock = processor.process_events(&terminal, display.window()); // Handle config reloads - if let Some(ref path) = config_monitor.as_ref().and_then(|monitor| monitor.pending()) { + if let Some(ref path) = config_monitor.as_ref().and_then(Monitor::pending) { // Clear old config messages from bar terminal_lock.message_buffer_mut().remove_topic(config::SOURCE_FILE_PATH); diff --git a/src/message_bar.rs b/src/message_bar.rs index bbd705aa..d7f27e7e 100644 --- a/src/message_bar.rs +++ b/src/message_bar.rs @@ -190,7 +190,7 @@ impl MessageBuffer { .messages .try_iter() .take(self.messages.len()) - .filter(|m| m.topic().map(|s| s.as_str()) != Some(topic)) + .filter(|m| m.topic().map(String::as_str) != Some(topic)) { let _ = self.tx.send(msg); } diff --git a/src/term/cell.rs b/src/term/cell.rs index 5d3b7036..88f9d7a1 100644 --- a/src/term/cell.rs +++ b/src/term/cell.rs @@ -14,7 +14,7 @@ use bitflags::bitflags; use crate::ansi::{NamedColor, Color}; -use crate::grid; +use crate::grid::{self, GridCell}; use crate::index::Column; // Maximum number of zerowidth characters which will be stored per cell. @@ -62,6 +62,32 @@ impl Default for Cell { } +impl GridCell for Cell { + #[inline] + fn is_empty(&self) -> bool { + (self.c == ' ' || self.c == '\t') + && self.extra[0] == ' ' + && self.bg == Color::Named(NamedColor::Background) + && !self + .flags + .intersects(Flags::INVERSE | Flags::UNDERLINE | Flags::STRIKEOUT | Flags::WRAPLINE) + } + + #[inline] + fn is_wrap(&self) -> bool { + self.flags.contains(Flags::WRAPLINE) + } + + #[inline] + fn set_wrap(&mut self, wrap: bool) { + if wrap { + self.flags.insert(Flags::WRAPLINE); + } else { + self.flags.remove(Flags::WRAPLINE); + } + } +} + /// Get the length of occupied cells in a line pub trait LineLength { /// Calculate the occupied line length @@ -113,14 +139,6 @@ impl Cell { } } - #[inline] - pub fn is_empty(&self) -> bool { - (self.c == ' ' || self.c == '\t') - && self.extra[0] == ' ' - && self.bg == Color::Named(NamedColor::Background) - && !self.flags.intersects(Flags::INVERSE | Flags::UNDERLINE | Flags::STRIKEOUT) - } - #[inline] pub fn reset(&mut self, template: &Cell) { // memcpy template to self diff --git a/src/term/mod.rs b/src/term/mod.rs index f48ad699..f2c0b18b 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -23,7 +23,10 @@ use unicode_width::UnicodeWidthChar; use font::{self, Size}; use crate::ansi::{self, Color, NamedColor, Attr, Handler, CharsetIndex, StandardCharset, CursorStyle}; -use crate::grid::{BidirectionalIterator, Grid, Indexed, IndexRegion, DisplayIter, Scroll, ViewportPosition}; +use crate::grid::{ + BidirectionalIterator, DisplayIter, Grid, GridCell, IndexRegion, Indexed, Scroll, + ViewportPosition, +}; use crate::index::{self, Point, Column, Line, IndexRange, Contains, RangeInclusive, Linear}; use crate::selection::{self, Selection, Locations}; use crate::config::{Config, VisualBellAnimation}; @@ -1246,8 +1249,13 @@ impl Term { debug!("New num_cols is {} and num_lines is {}", num_cols, num_lines); // Resize grids to new size - self.grid.resize(num_lines, num_cols, &Cell::default()); - self.alt_grid.resize(num_lines, num_cols, &Cell::default()); + let alt_cursor_point = if self.mode.contains(TermMode::ALT_SCREEN) { + &mut self.cursor_save.point + } else { + &mut self.cursor_save_alt.point + }; + self.grid.resize(num_lines, num_cols, &mut self.cursor.point, &Cell::default()); + self.alt_grid.resize(num_lines, num_cols, alt_cursor_point, &Cell::default()); // Reset scrolling region to new size self.scroll_region = Line(0)..self.grid.num_lines();