mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-18 13:55:23 -05:00
Fix deletion of lines when clearing the screen
Previously Alacritty would delete lines when clearing the screen, leading to a loss of data in the scrollback buffer. Instead of deleting these lines, they are now rotated outside of the visible region. This also fixes some issues with Alacritty only resetting lines partially when the background color of the template cell changed. Fixes #2199.
This commit is contained in:
parent
bcdc605436
commit
182a9d5c2e
5 changed files with 170 additions and 39 deletions
|
@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- On Windows, query DirectWrite for recommended anti-aliasing settings
|
- On Windows, query DirectWrite for recommended anti-aliasing settings
|
||||||
|
- Scroll lines out of the visible region instead of deleting them when clearing the screen
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,32 @@ pub trait GridCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the terminal display contents
|
/// Represents the terminal display contents
|
||||||
|
///
|
||||||
|
/// ```notrust
|
||||||
|
/// ┌─────────────────────────┐ <-- max_scroll_limit + lines
|
||||||
|
/// │ │
|
||||||
|
/// │ UNINITIALIZED │
|
||||||
|
/// │ │
|
||||||
|
/// ├─────────────────────────┤ <-- raw.len()
|
||||||
|
/// │ │
|
||||||
|
/// │ RESIZE BUFFER │
|
||||||
|
/// │ │
|
||||||
|
/// ├─────────────────────────┤ <-- scroll_limit + lines
|
||||||
|
/// │ │
|
||||||
|
/// │ SCROLLUP REGION │
|
||||||
|
/// │ │
|
||||||
|
/// ├─────────────────────────┤v lines
|
||||||
|
/// │ │|
|
||||||
|
/// │ VISIBLE REGION │|
|
||||||
|
/// │ │|
|
||||||
|
/// ├─────────────────────────┤^ <-- display_offset
|
||||||
|
/// │ │
|
||||||
|
/// │ SCROLLDOWN REGION │
|
||||||
|
/// │ │
|
||||||
|
/// └─────────────────────────┘ <-- zero
|
||||||
|
/// ^
|
||||||
|
/// cols
|
||||||
|
/// ```
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct Grid<T> {
|
pub struct Grid<T> {
|
||||||
/// Lines in the grid. Each row holds a list of cells corresponding to the
|
/// Lines in the grid. Each row holds a list of cells corresponding to the
|
||||||
|
@ -82,9 +108,7 @@ pub struct Grid<T> {
|
||||||
/// Number of columns
|
/// Number of columns
|
||||||
cols: index::Column,
|
cols: index::Column,
|
||||||
|
|
||||||
/// Number of lines.
|
/// Number of visible lines.
|
||||||
///
|
|
||||||
/// Invariant: lines is equivalent to raw.len()
|
|
||||||
lines: index::Line,
|
lines: index::Line,
|
||||||
|
|
||||||
/// Offset of displayed area
|
/// Offset of displayed area
|
||||||
|
@ -472,10 +496,10 @@ impl<T: GridCell + Copy + Clone> Grid<T> {
|
||||||
selection.rotate(*positions as isize);
|
selection.rotate(*positions as isize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// // This next loop swaps "fixed" lines outside of a scroll region
|
// This next loop swaps "fixed" lines outside of a scroll region
|
||||||
// // back into place after the rotation. The work is done in buffer-
|
// back into place after the rotation. The work is done in buffer-
|
||||||
// // space rather than terminal-space to avoid redundant
|
// space rather than terminal-space to avoid redundant
|
||||||
// // transformations.
|
// transformations.
|
||||||
let fixed_lines = *self.num_lines() - *region.end;
|
let fixed_lines = *self.num_lines() - *region.end;
|
||||||
|
|
||||||
for i in 0..fixed_lines {
|
for i in 0..fixed_lines {
|
||||||
|
@ -501,11 +525,30 @@ impl<T: GridCell + Copy + Clone> Grid<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear_viewport(&mut self, template: &T) {
|
||||||
|
// Determine how many lines to scroll up by.
|
||||||
|
let end = Point { line: 0, col: self.num_cols() };
|
||||||
|
let mut iter = self.iter_from(end);
|
||||||
|
while let Some(cell) = iter.prev() {
|
||||||
|
if !cell.is_empty() || iter.cur.line >= *self.lines {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug_assert!(iter.cur.line <= *self.lines);
|
||||||
|
let positions = self.lines - iter.cur.line;
|
||||||
|
let region = Line(0)..self.num_lines();
|
||||||
|
|
||||||
|
// Clear the viewport
|
||||||
|
self.scroll_up(®ion, positions, template);
|
||||||
|
|
||||||
|
// Reset rotated lines
|
||||||
|
for i in positions.0..self.lines.0 {
|
||||||
|
self.raw[i].reset(&template);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Completely reset the grid state
|
// Completely reset the grid state
|
||||||
pub fn reset(&mut self, template: &T) {
|
pub fn reset(&mut self, template: &T) {
|
||||||
// Explicitly purge all lines from history
|
|
||||||
let shrinkage = self.raw.len() - self.lines.0;
|
|
||||||
self.raw.shrink_lines(shrinkage);
|
|
||||||
self.clear_history();
|
self.clear_history();
|
||||||
|
|
||||||
// Reset all visible lines
|
// Reset all visible lines
|
||||||
|
@ -535,6 +578,9 @@ impl<T> Grid<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_history(&mut self) {
|
pub fn clear_history(&mut self) {
|
||||||
|
// Explicitly purge all lines from history
|
||||||
|
let shrinkage = self.raw.len() - self.lines.0;
|
||||||
|
self.raw.shrink_lines(shrinkage);
|
||||||
self.scroll_limit = 0;
|
self.scroll_limit = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,12 +90,15 @@ impl<T: Copy> Row<T> {
|
||||||
where
|
where
|
||||||
T: GridCell,
|
T: GridCell,
|
||||||
{
|
{
|
||||||
for item in &mut self.inner[..self.occ] {
|
|
||||||
*item = *template;
|
|
||||||
}
|
|
||||||
|
|
||||||
if template.is_empty() {
|
if template.is_empty() {
|
||||||
|
for item in &mut self.inner[..self.occ] {
|
||||||
|
*item = *template;
|
||||||
|
}
|
||||||
self.occ = 0;
|
self.occ = 0;
|
||||||
|
} else {
|
||||||
|
let len = self.inner.len();
|
||||||
|
self.inner = vec![*template; len];
|
||||||
|
self.occ = len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,4 @@
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
/// Wrapper around Vec which supports fast indexing and rotation
|
|
||||||
///
|
|
||||||
/// The rotation implemented by grid::Storage is a simple integer addition.
|
|
||||||
/// Compare with standard library rotation which requires rearranging items in
|
|
||||||
/// memory.
|
|
||||||
///
|
|
||||||
/// As a consequence, the indexing operators need to be reimplemented for this
|
|
||||||
/// type to account for the 0th element not always being at the start of the
|
|
||||||
/// allocation.
|
|
||||||
///
|
|
||||||
/// Because certain Vec operations are no longer valid on this type, no Deref
|
|
||||||
/// implementation is provided. Anything from Vec that should be exposed must be
|
|
||||||
/// done so manually.
|
|
||||||
use std::ops::{Index, IndexMut};
|
use std::ops::{Index, IndexMut};
|
||||||
use std::vec::Drain;
|
use std::vec::Drain;
|
||||||
|
|
||||||
|
@ -23,10 +10,34 @@ use crate::index::Line;
|
||||||
/// Maximum number of invisible lines before buffer is resized
|
/// Maximum number of invisible lines before buffer is resized
|
||||||
const TRUNCATE_STEP: usize = 100;
|
const TRUNCATE_STEP: usize = 100;
|
||||||
|
|
||||||
|
/// A ring buffer for optimizing indexing and rotation.
|
||||||
|
///
|
||||||
|
/// The [`Storage::rotate`] and [`Storage::rotate_up`] functions are fast modular additions on the
|
||||||
|
/// internal [`zero`] field. As compared with [`slice::rotate_left`] which must rearrange items in
|
||||||
|
/// memory.
|
||||||
|
///
|
||||||
|
/// As a consequence, both [`Index`] and [`IndexMut`] are reimplemented for this type to account
|
||||||
|
/// for the zeroth element not always being at the start of the allocation.
|
||||||
|
///
|
||||||
|
/// Because certain [`Vec`] operations are no longer valid on this type, no [`Deref`]
|
||||||
|
/// implementation is provided. Anything from [`Vec`] that should be exposed must be done so
|
||||||
|
/// manually.
|
||||||
|
///
|
||||||
|
/// [`slice::rotate_left`]: https://doc.rust-lang.org/std/primitive.slice.html#method.rotate_left
|
||||||
|
/// [`Deref`]: std::ops::Deref
|
||||||
|
/// [`zero`]: #structfield.zero
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct Storage<T> {
|
pub struct Storage<T> {
|
||||||
inner: Vec<Row<T>>,
|
inner: Vec<Row<T>>,
|
||||||
|
|
||||||
|
/// Starting point for the storage of rows.
|
||||||
|
///
|
||||||
|
/// This value represents the starting line offset within the ring buffer. The value of this
|
||||||
|
/// offset may be larger than the `len` itself, and will wrap around to the start to form the
|
||||||
|
/// ring buffer. It represents the bottommost line of the terminal.
|
||||||
zero: usize,
|
zero: usize,
|
||||||
|
|
||||||
|
/// An index separating the visible and scrollback regions.
|
||||||
visible_lines: Line,
|
visible_lines: Line,
|
||||||
|
|
||||||
/// Total number of lines currently active in the terminal (scrollback + visible)
|
/// Total number of lines currently active in the terminal (scrollback + visible)
|
||||||
|
@ -326,6 +337,68 @@ mod test {
|
||||||
fn set_wrap(&mut self, _wrap: bool) {}
|
fn set_wrap(&mut self, _wrap: bool) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn with_capacity() {
|
||||||
|
let storage = Storage::with_capacity(Line(3), Row::new(Column(0), &' '));
|
||||||
|
|
||||||
|
assert_eq!(storage.inner.len(), 3);
|
||||||
|
assert_eq!(storage.len, 3);
|
||||||
|
assert_eq!(storage.zero, 0);
|
||||||
|
assert_eq!(storage.visible_lines, Line(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn indexing() {
|
||||||
|
let mut storage = Storage::with_capacity(Line(3), Row::new(Column(0), &' '));
|
||||||
|
|
||||||
|
storage[0] = Row::new(Column(1), &'0');
|
||||||
|
storage[1] = Row::new(Column(1), &'1');
|
||||||
|
storage[2] = Row::new(Column(1), &'2');
|
||||||
|
|
||||||
|
assert_eq!(storage[0], Row::new(Column(1), &'0'));
|
||||||
|
assert_eq!(storage[1], Row::new(Column(1), &'1'));
|
||||||
|
assert_eq!(storage[2], Row::new(Column(1), &'2'));
|
||||||
|
|
||||||
|
storage.zero += 1;
|
||||||
|
|
||||||
|
assert_eq!(storage[0], Row::new(Column(1), &'1'));
|
||||||
|
assert_eq!(storage[1], Row::new(Column(1), &'2'));
|
||||||
|
assert_eq!(storage[2], Row::new(Column(1), &'0'));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn indexing_above_len() {
|
||||||
|
let mut storage = Storage::with_capacity(Line(3), Row::new(Column(0), &' '));
|
||||||
|
storage.shrink_lines(2);
|
||||||
|
let _ = &storage[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn indexing_above_inner_len() {
|
||||||
|
let storage = Storage::with_capacity(Line(1), Row::new(Column(0), &' '));
|
||||||
|
let _ = &storage[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rotate() {
|
||||||
|
let mut storage = Storage::with_capacity(Line(3), Row::new(Column(0), &' '));
|
||||||
|
storage.rotate(2);
|
||||||
|
assert_eq!(storage.zero, 2);
|
||||||
|
storage.shrink_lines(2);
|
||||||
|
assert_eq!(storage.len, 1);
|
||||||
|
assert_eq!(storage.inner.len(), 3);
|
||||||
|
assert_eq!(storage.zero, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn rotate_overflow() {
|
||||||
|
let mut storage = Storage::with_capacity(Line(3), Row::new(Column(0), &' '));
|
||||||
|
storage.rotate(4);
|
||||||
|
}
|
||||||
|
|
||||||
/// Grow the buffer one line at the end of the buffer
|
/// Grow the buffer one line at the end of the buffer
|
||||||
///
|
///
|
||||||
/// Before:
|
/// Before:
|
||||||
|
|
|
@ -701,7 +701,9 @@ pub struct Term<T> {
|
||||||
/// Mode flags
|
/// Mode flags
|
||||||
mode: TermMode,
|
mode: TermMode,
|
||||||
|
|
||||||
/// Scroll region
|
/// Scroll region.
|
||||||
|
///
|
||||||
|
/// Range going from top to bottom of the terminal, indexed from the top of the viewport.
|
||||||
scroll_region: Range<Line>,
|
scroll_region: Range<Line>,
|
||||||
|
|
||||||
pub dirty: bool,
|
pub dirty: bool,
|
||||||
|
@ -1749,17 +1751,6 @@ impl<T: EventListener> ansi::Handler for Term<T> {
|
||||||
self.grid.selection = None;
|
self.grid.selection = None;
|
||||||
|
|
||||||
match mode {
|
match mode {
|
||||||
ansi::ClearMode::Below => {
|
|
||||||
for cell in &mut self.grid[self.cursor.point.line][self.cursor.point.col..] {
|
|
||||||
cell.reset(&template);
|
|
||||||
}
|
|
||||||
if self.cursor.point.line < self.grid.num_lines() - 1 {
|
|
||||||
self.grid
|
|
||||||
.region_mut((self.cursor.point.line + 1)..)
|
|
||||||
.each(|cell| cell.reset(&template));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ansi::ClearMode::All => self.grid.region_mut(..).each(|c| c.reset(&template)),
|
|
||||||
ansi::ClearMode::Above => {
|
ansi::ClearMode::Above => {
|
||||||
// If clearing more than one line
|
// If clearing more than one line
|
||||||
if self.cursor.point.line > Line(1) {
|
if self.cursor.point.line > Line(1) {
|
||||||
|
@ -1774,6 +1765,23 @@ impl<T: EventListener> ansi::Handler for Term<T> {
|
||||||
cell.reset(&template);
|
cell.reset(&template);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
ansi::ClearMode::Below => {
|
||||||
|
for cell in &mut self.grid[self.cursor.point.line][self.cursor.point.col..] {
|
||||||
|
cell.reset(&template);
|
||||||
|
}
|
||||||
|
if self.cursor.point.line < self.grid.num_lines() - 1 {
|
||||||
|
self.grid
|
||||||
|
.region_mut((self.cursor.point.line + 1)..)
|
||||||
|
.each(|cell| cell.reset(&template));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ansi::ClearMode::All => {
|
||||||
|
if self.mode.contains(TermMode::ALT_SCREEN) {
|
||||||
|
self.grid.region_mut(..).each(|c| c.reset(&template));
|
||||||
|
} else {
|
||||||
|
self.grid.clear_viewport(&template);
|
||||||
|
}
|
||||||
|
},
|
||||||
ansi::ClearMode::Saved => self.grid.clear_history(),
|
ansi::ClearMode::Saved => self.grid.clear_history(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue