summaryrefslogtreecommitdiffstats
path: root/alacritty_terminal
diff options
context:
space:
mode:
authorChristian Duerr <contact@christianduerr.com>2020-11-05 04:45:14 +0000
committerGitHub <noreply@github.com>2020-11-05 04:45:14 +0000
commitec42b42ce601808070462111c0c28edb0e89babb (patch)
tree48006abca4497f66307f3b4d485bb1f162f4bf32 /alacritty_terminal
parent9028fb451a967d69a9e258a083ba64b052a9a5dd (diff)
Use dynamic storage for zerowidth characters
The zerowidth characters were conventionally stored in a [char; 5]. This creates problems both by limiting the maximum number of zerowidth characters and by increasing the cell size beyond what is necessary even when no zerowidth characters are used. Instead of storing zerowidth characters as a slice, a new CellExtra struct is introduced which can store arbitrary optional cell data that is rarely required. Since this is stored behind an optional pointer (Option<Box<CellExtra>>), the initialization and dropping in the case of no extra data are extremely cheap and the size penalty to cells without this extra data is limited to 8 instead of 20 bytes. The most noticible difference with this PR should be a reduction in memory size of up to at least 30% (1.06G -> 733M, 100k scrollback, 72 lines, 280 columns). Since the zerowidth characters are now stored dynamically, the limit of 5 per cell is also no longer present.
Diffstat (limited to 'alacritty_terminal')
-rw-r--r--alacritty_terminal/src/grid/mod.rs95
-rw-r--r--alacritty_terminal/src/grid/resize.rs47
-rw-r--r--alacritty_terminal/src/grid/row.rs54
-rw-r--r--alacritty_terminal/src/grid/storage.rs415
-rw-r--r--alacritty_terminal/src/grid/tests.rs42
-rw-r--r--alacritty_terminal/src/index.rs4
-rw-r--r--alacritty_terminal/src/term/cell.rs151
-rw-r--r--alacritty_terminal/src/term/mod.rs169
-rw-r--r--alacritty_terminal/src/term/search.rs39
-rw-r--r--alacritty_terminal/src/vi_mode.rs8
-rw-r--r--alacritty_terminal/tests/ref.rs6
-rw-r--r--alacritty_terminal/tests/ref/alt_reset/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/clear_underline/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/colored_reset/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/csi_rep/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/decaln_reset/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/deccolm_reset/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/delete_chars_reset/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/delete_lines/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/erase_chars_reset/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/fish_cc/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/grid_reset/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/history/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/indexed_256_colors/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/insert_blank_reset/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/issue_855/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/ll/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/newline_with_cursor_beyond_scroll_region/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/region_scroll_down/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/row_reset/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/saved_cursor/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/saved_cursor_alt/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/scroll_up_reset/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/selective_erasure/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/sgr/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/tab_rendering/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/tmux_git_log/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/tmux_htop/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/underline/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/vim_24bitcolors_bce/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/vim_large_window_scroll/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/vim_simple_edit/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/vttest_cursor_movement_1/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/vttest_insert/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/vttest_origin_mode_1/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/vttest_origin_mode_2/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/vttest_scroll/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/vttest_tab_clear_set/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/wrapline_alt_toggle/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/zerowidth/alacritty.recording4
-rw-r--r--alacritty_terminal/tests/ref/zerowidth/config.json2
-rw-r--r--alacritty_terminal/tests/ref/zerowidth/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/zerowidth/size.json2
-rw-r--r--alacritty_terminal/tests/ref/zsh_tab_completion/grid.json2
54 files changed, 579 insertions, 539 deletions
diff --git a/alacritty_terminal/src/grid/mod.rs b/alacritty_terminal/src/grid/mod.rs
index 5ab25c78..70dbc936 100644
--- a/alacritty_terminal/src/grid/mod.rs
+++ b/alacritty_terminal/src/grid/mod.rs
@@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
use crate::ansi::{CharsetIndex, StandardCharset};
use crate::index::{Column, IndexRange, Line, Point};
-use crate::term::cell::{Cell, Flags};
+use crate::term::cell::{Flags, ResetDiscriminant};
pub mod resize;
mod row;
@@ -49,25 +49,24 @@ impl<T: PartialEq> ::std::cmp::PartialEq for Grid<T> {
}
}
-pub trait GridCell {
+pub trait GridCell: Sized {
+ /// Check if the cell contains any content.
fn is_empty(&self) -> bool;
+
+ /// Perform an opinionated cell reset based on a template cell.
+ fn reset(&mut self, template: &Self);
+
fn flags(&self) -> &Flags;
fn flags_mut(&mut self) -> &mut Flags;
-
- /// Fast equality approximation.
- ///
- /// This is a faster alternative to [`PartialEq`],
- /// but might report unequal cells as equal.
- fn fast_eq(&self, other: Self) -> bool;
}
-#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
-pub struct Cursor {
+#[derive(Debug, Default, Clone, PartialEq, Eq)]
+pub struct Cursor<T> {
/// The location of this cursor.
pub point: Point,
/// Template cell when using this cursor.
- pub template: Cell,
+ pub template: T,
/// Currently configured graphic character sets.
pub charsets: Charsets,
@@ -131,11 +130,11 @@ impl IndexMut<CharsetIndex> for Charsets {
pub struct Grid<T> {
/// Current cursor for writing data.
#[serde(skip)]
- pub cursor: Cursor,
+ pub cursor: Cursor<T>,
/// Last saved cursor.
#[serde(skip)]
- pub saved_cursor: Cursor,
+ pub saved_cursor: Cursor<T>,
/// Lines in the grid. Each row holds a list of cells corresponding to the
/// columns in that row.
@@ -167,10 +166,10 @@ pub enum Scroll {
Bottom,
}
-impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
- pub fn new(lines: Line, cols: Column, max_scroll_limit: usize, template: T) -> Grid<T> {
+impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
+ pub fn new(lines: Line, cols: Column, max_scroll_limit: usize) -> Grid<T> {
Grid {
- raw: Storage::with_capacity(lines, Row::new(cols, template)),
+ raw: Storage::with_capacity(lines, cols),
max_scroll_limit,
display_offset: 0,
saved_cursor: Cursor::default(),
@@ -203,10 +202,10 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
};
}
- fn increase_scroll_limit(&mut self, count: usize, template: T) {
+ fn increase_scroll_limit(&mut self, count: usize) {
let count = min(count, self.max_scroll_limit - self.history_size());
if count != 0 {
- self.raw.initialize(count, template, self.cols);
+ self.raw.initialize(count, self.cols);
}
}
@@ -219,7 +218,11 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
}
#[inline]
- pub fn scroll_down(&mut self, region: &Range<Line>, positions: Line, template: T) {
+ pub fn scroll_down<D>(&mut self, region: &Range<Line>, positions: Line)
+ where
+ T: ResetDiscriminant<D>,
+ D: PartialEq,
+ {
// 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.
@@ -238,7 +241,7 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
// Finally, reset recycled lines.
for i in IndexRange(Line(0)..positions) {
- self.raw[i].reset(template);
+ self.raw[i].reset(&self.cursor.template);
}
} else {
// Subregion rotation.
@@ -247,7 +250,7 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
}
for line in IndexRange(region.start..(region.start + positions)) {
- self.raw[line].reset(template);
+ self.raw[line].reset(&self.cursor.template);
}
}
}
@@ -255,7 +258,11 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
/// Move lines at the bottom toward the top.
///
/// This is the performance-sensitive part of scrolling.
- pub fn scroll_up(&mut self, region: &Range<Line>, positions: Line, template: T) {
+ pub fn scroll_up<D>(&mut self, region: &Range<Line>, positions: Line)
+ where
+ T: ResetDiscriminant<D>,
+ D: PartialEq,
+ {
let num_lines = self.screen_lines().0;
if region.start == Line(0) {
@@ -264,7 +271,7 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
self.display_offset = min(self.display_offset + *positions, self.max_scroll_limit);
}
- self.increase_scroll_limit(*positions, template);
+ 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.
@@ -284,7 +291,7 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
//
// Recycled lines are just above the end of the scrolling region.
for i in 0..*positions {
- self.raw[i + fixed_lines].reset(template);
+ self.raw[i + fixed_lines].reset(&self.cursor.template);
}
} else {
// Subregion rotation.
@@ -294,12 +301,16 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
// Clear reused lines.
for line in IndexRange((region.end - positions)..region.end) {
- self.raw[line].reset(template);
+ self.raw[line].reset(&self.cursor.template);
}
}
}
- pub fn clear_viewport(&mut self, template: T) {
+ pub fn clear_viewport<D>(&mut self)
+ where
+ T: ResetDiscriminant<D>,
+ D: PartialEq,
+ {
// Determine how many lines to scroll up by.
let end = Point { line: 0, col: self.cols() };
let mut iter = self.iter_from(end);
@@ -316,26 +327,30 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
self.display_offset = 0;
// Clear the viewport.
- self.scroll_up(&region, positions, template);
+ self.scroll_up(&region, positions);
// Reset rotated lines.
for i in positions.0..self.lines.0 {
- self.raw[i].reset(template);
+ self.raw[i].reset(&self.cursor.template);
}
}
/// Completely reset the grid state.
- pub fn reset(&mut self, template: T) {
+ pub fn reset<D>(&mut self)
+ where
+ T: ResetDiscriminant<D>,
+ D: PartialEq,
+ {
self.clear_history();
- // Reset all visible lines.
- for row in 0..self.raw.len() {
- self.raw[row].reset(template);
- }
-
self.saved_cursor = Cursor::default();
self.cursor = Cursor::default();
self.display_offset = 0;
+
+ // Reset all visible lines.
+ for row in 0..self.raw.len() {
+ self.raw[row].reset(&self.cursor.template);
+ }
}
}
@@ -372,15 +387,15 @@ impl<T> Grid<T> {
/// This is used only for initializing after loading ref-tests.
#[inline]
- pub fn initialize_all(&mut self, template: T)
+ pub fn initialize_all(&mut self)
where
- T: Copy + GridCell,
+ T: GridCell + Clone + Default,
{
// Remove all cached lines to clear them of any content.
self.truncate();
// Initialize everything with empty new lines.
- self.raw.initialize(self.max_scroll_limit - self.history_size(), template, self.cols);
+ self.raw.initialize(self.max_scroll_limit - self.history_size(), self.cols);
}
/// This is used only for truncating before saving ref-tests.
@@ -753,8 +768,8 @@ impl<'a, T: 'a> DisplayIter<'a, T> {
}
}
-impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> {
- type Item = Indexed<T>;
+impl<'a, T: 'a> Iterator for DisplayIter<'a, T> {
+ type Item = Indexed<&'a T>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
@@ -765,7 +780,7 @@ impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> {
// Get the next item.
let item = Some(Indexed {
- inner: self.grid.raw[self.offset][self.col],
+ inner: &self.grid.raw[self.offset][self.col],
line: self.line,
column: self.col,
});
diff --git a/alacritty_terminal/src/grid/resize.rs b/alacritty_terminal/src/grid/resize.rs
index acdc040e..1a16e09e 100644
--- a/alacritty_terminal/src/grid/resize.rs
+++ b/alacritty_terminal/src/grid/resize.rs
@@ -1,16 +1,24 @@
//! Grid resize and reflow.
use std::cmp::{min, Ordering};
+use std::mem;
use crate::index::{Column, Line};
-use crate::term::cell::Flags;
+use crate::term::cell::{Flags, ResetDiscriminant};
use crate::grid::row::Row;
use crate::grid::{Dimensions, Grid, GridCell};
-impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
+impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
/// Resize the grid's width and/or height.
- pub fn resize(&mut self, reflow: bool, lines: Line, cols: Column) {
+ pub fn resize<D>(&mut self, reflow: bool, lines: Line, cols: Column)
+ where
+ T: ResetDiscriminant<D>,
+ D: PartialEq,
+ {
+ // Use empty template cell for resetting cells due to resize.
+ let template = mem::take(&mut self.cursor.template);
+
match self.lines.cmp(&lines) {
Ordering::Less => self.grow_lines(lines),
Ordering::Greater => self.shrink_lines(lines),
@@ -22,6 +30,9 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
Ordering::Greater => self.shrink_cols(reflow, cols),
Ordering::Equal => (),
}
+
+ // Restore template cell.
+ self.cursor.template = template;
}
/// Add lines to the visible area.
@@ -29,11 +40,15 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
/// 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: Line) {
+ fn grow_lines<D>(&mut self, new_line_count: Line)
+ where
+ T: ResetDiscriminant<D>,
+ D: PartialEq,
+ {
let lines_added = new_line_count - self.lines;
// Need to resize before updating buffer.
- self.raw.grow_visible_lines(new_line_count, Row::new(self.cols, T::default()));
+ self.raw.grow_visible_lines(new_line_count);
self.lines = new_line_count;
let history_size = self.history_size();
@@ -42,7 +57,7 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
// Move existing lines up for every line that couldn't be pulled from history.
if from_history != lines_added.0 {
let delta = lines_added - from_history;
- self.scroll_up(&(Line(0)..new_line_count), delta, T::default());
+ self.scroll_up(&(Line(0)..new_line_count), delta);
}
// Move cursor down for every line pulled from history.
@@ -60,11 +75,15 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
/// of the terminal window.
///
/// Alacritty takes the same approach.
- fn shrink_lines(&mut self, target: Line) {
+ fn shrink_lines<D>(&mut self, target: Line)
+ where
+ T: ResetDiscriminant<D>,
+ D: PartialEq,
+ {
// Scroll up to keep content inside the window.
let required_scrolling = (self.cursor.point.line + 1).saturating_sub(target.0);
if required_scrolling > 0 {
- self.scroll_up(&(Line(0)..self.lines), Line(required_scrolling), T::default());
+ self.scroll_up(&(Line(0)..self.lines), Line(required_scrolling));
// Clamp cursors to the new viewport size.
self.cursor.point.line = min(self.cursor.point.line, target - 1);
@@ -194,7 +213,7 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
if reversed.len() < self.lines.0 {
let delta = self.lines.0 - reversed.len();
self.cursor.point.line.0 = self.cursor.point.line.saturating_sub(delta);
- reversed.append(&mut vec![Row::new(cols, T::default()); delta]);
+ reversed.resize_with(self.lines.0, || Row::new(cols));
}
// Pull content down to put cursor in correct position, or move cursor up if there's no
@@ -211,7 +230,7 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
let mut new_raw = Vec::with_capacity(reversed.len());
for mut row in reversed.drain(..).rev() {
if row.len() < cols.0 {
- row.grow(cols, T::default());
+ row.grow(cols);
}
new_raw.push(row);
}
@@ -269,11 +288,11 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
// Insert spacer if a wide char would be wrapped into the last column.
if row.len() >= cols.0 && row[cols - 1].flags().contains(Flags::WIDE_CHAR) {
- wrapped.insert(0, row[cols - 1]);
-
let mut spacer = T::default();
spacer.flags_mut().insert(Flags::LEADING_WIDE_CHAR_SPACER);
- row[cols - 1] = spacer;
+
+ let wide_char = mem::replace(&mut row[cols - 1], spacer);
+ wrapped.insert(0, wide_char);
}
// Remove wide char spacer before shrinking.
@@ -330,7 +349,7 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
// Make sure new row is at least as long as new width.
let occ = wrapped.len();
if occ < cols.0 {
- wrapped.append(&mut vec![T::default(); cols.0 - occ]);
+ wrapped.resize_with(cols.0, T::default);
}
row = Row::from_vec(wrapped, occ);
}
diff --git a/alacritty_terminal/src/grid/row.rs b/alacritty_terminal/src/grid/row.rs
index 7846a7ae..5b1be31c 100644
--- a/alacritty_terminal/src/grid/row.rs
+++ b/alacritty_terminal/src/grid/row.rs
@@ -3,12 +3,14 @@
use std::cmp::{max, min};
use std::ops::{Index, IndexMut};
use std::ops::{Range, RangeFrom, RangeFull, RangeTo, RangeToInclusive};
+use std::ptr;
use std::slice;
use serde::{Deserialize, Serialize};
use crate::grid::GridCell;
use crate::index::Column;
+use crate::term::cell::ResetDiscriminant;
/// A row in the grid.
#[derive(Default, Clone, Debug, Serialize, Deserialize)]
@@ -28,23 +30,44 @@ impl<T: PartialEq> PartialEq for Row<T> {
}
}
-impl<T: Copy> Row<T> {
- pub fn new(columns: Column, template: T) -> Row<T>
- where
- T: GridCell,
- {
- let occ = if template.is_empty() { 0 } else { columns.0 };
- Row { inner: vec![template; columns.0], occ }
+impl<T: Clone + Default> Row<T> {
+ /// Create a new terminal row.
+ ///
+ /// Ideally the `template` should be `Copy` in all performance sensitive scenarios.
+ pub fn new(columns: Column) -> Row<T> {
+ debug_assert!(columns.0 >= 1);
+
+ let mut inner: Vec<T> = Vec::with_capacity(columns.0);
+
+ // This is a slightly optimized version of `std::vec::Vec::resize`.
+ unsafe {
+ let mut ptr = inner.as_mut_ptr();
+
+ for _ in 1..columns.0 {
+ ptr::write(ptr, T::default());
+ ptr = ptr.offset(1);
+ }
+ ptr::write(ptr, T::default());
+
+ inner.set_len(columns.0);
+ }
+
+ Row { inner, occ: 0 }
}
- pub fn grow(&mut self, cols: Column, template: T) {
+ /// Increase the number of columns in the row.
+ #[inline]
+ pub fn grow(&mut self, cols: Column) {
if self.inner.len() >= cols.0 {
return;
}
- self.inner.append(&mut vec![template; cols.0 - self.len()]);
+ self.inner.resize_with(cols.0, T::default);
}
+ /// Reduce the number of columns in the row.
+ ///
+ /// This will return all non-empty cells that were removed.
pub fn shrink(&mut self, cols: Column) -> Option<Vec<T>>
where
T: GridCell,
@@ -69,21 +92,22 @@ impl<T: Copy> Row<T> {
/// Reset all cells in the row to the `template` cell.
#[inline]
- pub fn reset(&mut self, template: T)
+ pub fn reset<D>(&mut se