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.
This commit is contained in:
Christian Duerr 2019-03-13 18:55:18 +00:00 committed by GitHub
parent 0b9ae4ce93
commit b1032bcc6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 609 additions and 71 deletions

View File

@ -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

View File

@ -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 => {},

View File

@ -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<'_>> {

View File

@ -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) {

View File

@ -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, 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;

View File

@ -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;
}
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<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);
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()
}
}
impl<'a, T> IntoIterator for &'a Row<T> {
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<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]
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
}
}

View File

@ -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);
}

View File

@ -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
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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

View File

@ -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();