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