1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-27 15:37:29 +03:00

cap typical blank line length to a reasonable value

This has the effect of reducing the memory and scroll cost
for lines that are shorter than the physical width of the
terminal matrix.

refs: https://github.com/wez/wezterm/issues/740
This commit is contained in:
Wez Furlong 2021-04-29 09:03:58 -07:00
parent 20a599a372
commit b01193e1a5
3 changed files with 185 additions and 37 deletions

View File

@ -4,6 +4,22 @@ use log::debug;
use std::collections::VecDeque;
use std::sync::Arc;
/// Caps the number of blank columns that we allocate in an empty line.
/// From a simplistic, idealistic perspective, we'd allocate the physical
/// column width so that each line's length matches the screen dimensions.
/// That's fine for a reasonably sized terminal (eg: 80x24), but when
/// maximized on modern large monitors with ~500 columns, that represents
/// a fairly large overhead for lines that contain mostly blank cells.
/// When working with blank/cleared lines, we cap the length to this value
/// to try to strike a balance between pre-allocating something useful
/// and not allocating any cells at all.
/// I did try settting this to 0 but it had the effective of changing
/// how semantic zone line boundaries were computing (detected via
/// our unit tests).
/// If we want to be more aggressive at reducing this in the future,
/// that is one place that would need to be adjusted accordingly.
pub(crate) const TYPICAL_NUM_COLS: usize = 64;
/// Holds the model of a screen. This can either be the primary screen
/// which includes lines of scrollback text, or the alternate screen
/// which holds no scrollback. The intent is to have one instance of
@ -62,7 +78,7 @@ impl Screen {
let mut lines =
VecDeque::with_capacity(physical_rows + scrollback_size(config, allow_scrollback));
for _ in 0..physical_rows {
lines.push_back(Line::with_width(physical_cols));
lines.push_back(Line::with_width(physical_cols.min(TYPICAL_NUM_COLS)));
}
Screen {
@ -212,7 +228,8 @@ impl Screen {
// lines than the viewport size, or we resized taller,
// pad us back out to the viewport size
while self.lines.len() < physical_rows {
self.lines.push_back(Line::with_width(physical_cols));
self.lines
.push_back(Line::with_width(physical_cols.min(TYPICAL_NUM_COLS)));
}
let new_cursor_y;
@ -233,7 +250,8 @@ impl Screen {
physical_rows.saturating_sub(new_cursor_y as usize);
let actual_num_rows_after_cursor = self.lines.len().saturating_sub(cursor_y);
for _ in actual_num_rows_after_cursor..required_num_rows_after_cursor {
self.lines.push_back(Line::with_width(physical_cols));
self.lines
.push_back(Line::with_width(physical_cols.min(TYPICAL_NUM_COLS)));
}
} else {
// Compute the new cursor location; this is logically the inverse
@ -296,6 +314,8 @@ impl Screen {
let line = self.line_mut(line_idx);
line.insert_cell(x, Cell::default(), right_margin);
if line.cells().len() > phys_cols {
// Don't allow the line width to grow beyond
// the physical width
line.resize(phys_cols);
}
}
@ -325,7 +345,8 @@ impl Screen {
let physical_cols = self.physical_cols;
let line_idx = self.phys_row(y);
let line = self.line_mut(line_idx);
line.resize(physical_cols);
line.resize(physical_cols.min(TYPICAL_NUM_COLS));
line.fill_range(cols, &Cell::new(' ', attr.clone()));
}
@ -419,6 +440,13 @@ impl Screen {
left_and_right_margins: &Range<usize>,
num_rows: usize,
) {
log::debug!(
"scroll_up_within_margins region:{:?} margins:{:?} rows={}",
scroll_region,
left_and_right_margins,
num_rows
);
if left_and_right_margins.start == 0 && left_and_right_margins.end == self.physical_cols {
return self.scroll_up(scroll_region, num_rows);
}
@ -437,13 +465,11 @@ impl Screen {
// Copy the source cells first
let cells = {
let src_row = self.line_mut(src_row);
if src_row.cells().len() < left_and_right_margins.end {
src_row.resize(left_and_right_margins.end);
}
src_row.cells_mut()[left_and_right_margins.clone()]
self.lines[src_row]
.cells()
.iter()
.skip(left_and_right_margins.start)
.take(left_and_right_margins.end - left_and_right_margins.start)
.cloned()
.collect::<Vec<_>>()
};
@ -452,16 +478,21 @@ impl Screen {
let dest_row = self.line_mut(dest_row);
dest_row.set_dirty();
dest_row.invalidate_implicit_hyperlinks();
if dest_row.cells().len() < left_and_right_margins.end {
dest_row.resize(left_and_right_margins.end);
let dest_range =
left_and_right_margins.start..left_and_right_margins.start + cells.len();
if dest_row.cells().len() < dest_range.end {
dest_row.resize(dest_range.end);
}
for (src_cell, dest_cell) in cells
.into_iter()
.zip(&mut dest_row.cells_mut()[left_and_right_margins.clone()])
let tail_range = dest_range.end..left_and_right_margins.end;
for (src_cell, dest_cell) in
cells.into_iter().zip(&mut dest_row.cells_mut()[dest_range])
{
*dest_cell = src_cell.clone();
}
dest_row.fill_range(tail_range, &Cell::default());
}
}
@ -470,7 +501,12 @@ impl Screen {
let dest_row = self.line_mut(n);
dest_row.set_dirty();
dest_row.invalidate_implicit_hyperlinks();
for cell in &mut dest_row.cells_mut()[left_and_right_margins.clone()] {
for cell in dest_row
.cells_mut()
.iter_mut()
.skip(left_and_right_margins.start)
.take(left_and_right_margins.end - left_and_right_margins.start)
{
*cell = Cell::default();
}
}
@ -538,7 +574,7 @@ impl Screen {
for _ in 0..to_move {
let mut line = self.lines.remove(remove_idx).unwrap();
// Make the line like a new one of the appropriate width
line.resize_and_clear(self.physical_cols);
line.resize_and_clear(self.physical_cols.min(TYPICAL_NUM_COLS));
line.set_dirty();
if scroll_region.end as usize == self.physical_rows {
self.lines.push_back(line);
@ -563,12 +599,15 @@ impl Screen {
if scroll_region.end as usize == self.physical_rows {
// It's cheaper to push() than it is insert() at the end
for _ in 0..to_add {
self.lines.push_back(Line::with_width(self.physical_cols));
self.lines
.push_back(Line::with_width(self.physical_cols.min(TYPICAL_NUM_COLS)));
}
} else {
for _ in 0..to_add {
self.lines
.insert(phys_scroll.end, Line::with_width(self.physical_cols));
self.lines.insert(
phys_scroll.end,
Line::with_width(self.physical_cols.min(TYPICAL_NUM_COLS)),
);
}
}
}
@ -611,8 +650,10 @@ impl Screen {
}
for _ in 0..num_rows {
self.lines
.insert(phys_scroll.start, Line::with_width(self.physical_cols));
self.lines.insert(
phys_scroll.start,
Line::with_width(self.physical_cols.min(TYPICAL_NUM_COLS)),
);
}
}
@ -640,13 +681,11 @@ impl Screen {
// Copy the source cells first
let cells = {
let src_row = self.line_mut(src_row);
if src_row.cells().len() < left_and_right_margins.end {
src_row.resize(left_and_right_margins.end);
}
src_row.cells_mut()[left_and_right_margins.clone()]
self.lines[src_row]
.cells()
.iter()
.skip(left_and_right_margins.start)
.take(left_and_right_margins.end - left_and_right_margins.start)
.cloned()
.collect::<Vec<_>>()
};
@ -655,16 +694,20 @@ impl Screen {
let dest_row = self.line_mut(dest_row);
dest_row.set_dirty();
dest_row.invalidate_implicit_hyperlinks();
if dest_row.cells().len() < left_and_right_margins.end {
dest_row.resize(left_and_right_margins.end);
let dest_range =
left_and_right_margins.start..left_and_right_margins.start + cells.len();
if dest_row.cells().len() < dest_range.end {
dest_row.resize(dest_range.end);
}
let tail_range = dest_range.end..left_and_right_margins.end;
for (src_cell, dest_cell) in cells
.into_iter()
.zip(&mut dest_row.cells_mut()[left_and_right_margins.clone()])
for (src_cell, dest_cell) in
cells.into_iter().zip(&mut dest_row.cells_mut()[dest_range])
{
*dest_cell = src_cell.clone();
}
dest_row.fill_range(tail_range, &Cell::default());
}
}
@ -673,7 +716,12 @@ impl Screen {
let dest_row = self.line_mut(n);
dest_row.set_dirty();
dest_row.invalidate_implicit_hyperlinks();
for cell in &mut dest_row.cells_mut()[left_and_right_margins.clone()] {
for cell in dest_row
.cells_mut()
.iter_mut()
.skip(left_and_right_margins.start)
.take(left_and_right_margins.end - left_and_right_margins.start)
{
*cell = Cell::default();
}
}

View File

@ -2,7 +2,8 @@ use super::*;
use std::sync::Arc;
use termwiz::escape::parser::Parser;
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
pub enum ClipboardSelection {
Clipboard,
PrimarySelection,

View File

@ -8,6 +8,7 @@ mod c1;
mod csi;
// mod selection; FIXME: port to render layer
use crate::color::ColorPalette;
use crate::screen::TYPICAL_NUM_COLS;
use pretty_assertions::assert_eq;
use std::cell::RefCell;
use std::sync::Arc;
@ -98,7 +99,6 @@ impl TestTerm {
self.term.advance_bytes(bytes);
}
#[allow(dead_code)]
fn set_mode(&mut self, mode: &str, enable: bool) {
self.print(CSI);
self.print(mode);
@ -109,7 +109,11 @@ impl TestTerm {
self.set_mode("?7", enable);
}
#[allow(dead_code)]
fn set_left_and_right_margins(&mut self, left: usize, right: usize) {
self.print(CSI);
self.print(format!("{};{}s", left + 1, right + 1));
}
fn set_scroll_region(&mut self, top: usize, bottom: usize) {
self.print(CSI);
self.print(format!("{};{}r", top + 1, bottom + 1));
@ -577,6 +581,101 @@ fn cursor_movement_damage() {
term.assert_dirty_lines(&[0, 1], Some("cursor movement dirties old and new lines"));
}
#[test]
fn scroll_up_within_left_and_right_margins() {
let ones = "1".repeat(TYPICAL_NUM_COLS);
let twos = "2".repeat(TYPICAL_NUM_COLS);
let threes = "3".repeat(TYPICAL_NUM_COLS);
let fours = "4".repeat(TYPICAL_NUM_COLS + 2);
let fives = "5".repeat(TYPICAL_NUM_COLS);
let mut term = TestTerm::new(5, TYPICAL_NUM_COLS + 2, 0);
term.print(&ones);
term.print("\r\n");
term.print(&twos);
term.print("\r\n");
term.print(&threes);
term.print("\r\n");
term.print(&fours);
term.print("\r\n");
term.print(&fives);
assert_visible_contents(
&term,
file!(),
line!(),
&[&ones, &twos, &threes, &fours, &fives],
);
term.set_mode("?69", true); // allow left/right margins to be set
term.set_left_and_right_margins(1, TYPICAL_NUM_COLS + 1);
term.set_scroll_region(2, 4);
term.cup(1, 4);
term.print("\n");
assert_visible_contents(
&term,
file!(),
line!(),
&[
&ones,
&twos,
&format!("3{}", "4".repeat(TYPICAL_NUM_COLS + 1)),
&format!("4{} ", "5".repeat(TYPICAL_NUM_COLS - 1)),
&format!("5{}", " ".repeat(TYPICAL_NUM_COLS - 1)),
],
);
}
#[test]
fn scroll_down_within_left_and_right_margins() {
let ones = "1".repeat(TYPICAL_NUM_COLS);
let twos = "2".repeat(TYPICAL_NUM_COLS);
let threes = "3".repeat(TYPICAL_NUM_COLS);
let fours = "4".repeat(TYPICAL_NUM_COLS + 2);
let fives = "5".repeat(TYPICAL_NUM_COLS);
let mut term = TestTerm::new(5, TYPICAL_NUM_COLS + 2, 0);
term.print(&ones);
term.print("\r\n");
term.print(&twos);
term.print("\r\n");
term.print(&threes);
term.print("\r\n");
term.print(&fours);
term.print("\r\n");
term.print(&fives);
assert_visible_contents(
&term,
file!(),
line!(),
&[&ones, &twos, &threes, &fours, &fives],
);
term.set_mode("?69", true); // allow left/right margins to be set
term.set_left_and_right_margins(1, TYPICAL_NUM_COLS + 1);
term.set_scroll_region(2, 5);
term.cup(1, 2);
// IL: Insert Line
term.print(CSI);
term.print("L");
assert_visible_contents(
&term,
file!(),
line!(),
&[
&ones,
&twos,
&format!("3{}", " ".repeat(TYPICAL_NUM_COLS - 1)),
&format!("4{} ", "3".repeat(TYPICAL_NUM_COLS - 1)),
&format!("5{}", "4".repeat(TYPICAL_NUM_COLS + 1)),
],
);
}
/// Replicates a bug I initially found via:
/// $ vim
/// :help