diff options
author | Kirill Chibisov <contact@kchibisov.com> | 2023-11-23 16:48:09 +0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-11-23 16:48:09 +0400 |
commit | 40160c5da1fafeab3ccedc52efe2c135f1eab843 (patch) | |
tree | b763e853844e6292535300534aaf68597d708e3d /alacritty_terminal | |
parent | 0589b7189445e5ee236a7ab17b4f3a2047543481 (diff) | |
download | alacritty-40160c5da1fafeab3ccedc52efe2c135f1eab843.tar.gz alacritty-40160c5da1fafeab3ccedc52efe2c135f1eab843.zip |
Damage only terminal inside `alacritty_terminal`
The damage tracking was including selection and vi_cursor which were
rendering viewport related, however all the damage tracking inside
the `alacritty_terminal` was _terminal viewport_ related, meaning that
it should be affected by `display_offset`.
Refactor the damage tracking so `alacritty_terminal` is only tracking
actual terminal updates and properly applying display offset to them,
while `alacritty` pulls this damage into its own UI damage state.
Fixes #7111.
Diffstat (limited to 'alacritty_terminal')
-rw-r--r-- | alacritty_terminal/src/term/mod.rs | 250 |
1 files changed, 93 insertions, 157 deletions
diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index 3df1d128..f56fa605 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -110,6 +110,11 @@ pub struct LineDamageBounds { impl LineDamageBounds { #[inline] + pub fn new(line: usize, left: usize, right: usize) -> Self { + Self { line, left, right } + } + + #[inline] pub fn undamaged(line: usize, num_cols: usize) -> Self { Self { line, left: num_cols, right: 0 } } @@ -141,15 +146,19 @@ pub enum TermDamage<'a> { Partial(TermDamageIterator<'a>), } -/// Iterator over the terminal's damaged lines. +/// Iterator over the terminal's viewport damaged lines. #[derive(Clone, Debug)] pub struct TermDamageIterator<'a> { line_damage: slice::Iter<'a, LineDamageBounds>, + display_offset: usize, } impl<'a> TermDamageIterator<'a> { - fn new(line_damage: &'a [LineDamageBounds]) -> Self { - Self { line_damage: line_damage.iter() } + pub fn new(line_damage: &'a [LineDamageBounds], display_offset: usize) -> Self { + let num_lines = line_damage.len(); + // Filter out invisible damage. + let line_damage = &line_damage[..num_lines.saturating_sub(display_offset)]; + Self { display_offset, line_damage: line_damage.iter() } } } @@ -157,26 +166,26 @@ impl<'a> Iterator for TermDamageIterator<'a> { type Item = LineDamageBounds; fn next(&mut self) -> Option<Self::Item> { - self.line_damage.find(|line| line.is_damaged()).copied() + self.line_damage.find_map(|line| { + line.is_damaged().then_some(LineDamageBounds::new( + line.line + self.display_offset, + line.left, + line.right, + )) + }) } } /// State of the terminal damage. struct TermDamageState { /// Hint whether terminal should be damaged entirely regardless of the actual damage changes. - is_fully_damaged: bool, + full: bool, /// Information about damage on terminal lines. lines: Vec<LineDamageBounds>, /// Old terminal cursor point. last_cursor: Point, - - /// Last Vi cursor point. - last_vi_cursor_point: Option<Point<usize>>, - - /// Old selection range. - last_selection: Option<SelectionRange>, } impl TermDamageState { @@ -184,22 +193,14 @@ impl TermDamageState { let lines = (0..num_lines).map(|line| LineDamageBounds::undamaged(line, num_cols)).collect(); - Self { - is_fully_damaged: true, - lines, - last_cursor: Default::default(), - last_vi_cursor_point: Default::default(), - last_selection: Default::default(), - } + Self { full: true, lines, last_cursor: Default::default() } } #[inline] fn resize(&mut self, num_cols: usize, num_lines: usize) { // Reset point, so old cursor won't end up outside of the viewport. self.last_cursor = Default::default(); - self.last_vi_cursor_point = None; - self.last_selection = None; - self.is_fully_damaged = true; + self.full = true; self.lines.clear(); self.lines.reserve(num_lines); @@ -220,32 +221,9 @@ impl TermDamageState { self.lines[line].expand(left, right); } - fn damage_selection( - &mut self, - selection: SelectionRange, - display_offset: usize, - num_cols: usize, - ) { - let display_offset = display_offset as i32; - let last_visible_line = self.lines.len() as i32 - 1; - - // Don't damage invisible selection. - if selection.end.line.0 + display_offset < 0 - || selection.start.line.0.abs() < display_offset - last_visible_line - { - return; - }; - - let start = cmp::max(selection.start.line.0 + display_offset, 0); - let end = (selection.end.line.0 + display_offset).clamp(0, last_visible_line); - for line in start as usize..=end as usize { - self.damage_line(line, 0, num_cols - 1); - } - } - /// Reset information about terminal damage. fn reset(&mut self, num_cols: usize) { - self.is_fully_damaged = false; + self.full = false; self.lines.iter_mut().for_each(|line| line.reset(num_cols)); } } @@ -417,30 +395,27 @@ impl<T> Term<T> { } } + /// Collect the information about the changes in the lines, which + /// could be used to minimize the amount of drawing operations. + /// + /// The user controlled elements, like `Vi` mode cursor and `Selection` are **not** part of the + /// collected damage state. Those could easily be tracked by comparing their old and new + /// value between adjacent frames. + /// + /// After reading damage [`reset_damage`] should be called. + /// + /// [`reset_damage`]: Self::reset_damage #[must_use] - pub fn damage(&mut self, selection: Option<SelectionRange>) -> TermDamage<'_> { + pub fn damage(&mut self) -> TermDamage<'_> { // Ensure the entire terminal is damaged after entering insert mode. // Leaving is handled in the ansi handler. if self.mode.contains(TermMode::INSERT) { self.mark_fully_damaged(); } - // Update tracking of cursor, selection, and vi mode cursor. - - let display_offset = self.grid().display_offset(); - let vi_cursor_point = if self.mode.contains(TermMode::VI) { - point_to_viewport(display_offset, self.vi_mode_cursor.point) - } else { - None - }; - let previous_cursor = mem::replace(&mut self.damage.last_cursor, self.grid.cursor.point); - let previous_selection = mem::replace(&mut self.damage.last_selection, selection); - let previous_vi_cursor_point = - mem::replace(&mut self.damage.last_vi_cursor_point, vi_cursor_point); - // Early return if the entire terminal is damaged. - if self.damage.is_fully_damaged { + if self.damage.full { return TermDamage::Full; } @@ -455,24 +430,10 @@ impl<T> Term<T> { // Always damage current cursor. self.damage_cursor(); - // Vi mode doesn't update the terminal content, thus only last vi cursor position and the - // new one should be damaged. - if let Some(previous_vi_cursor_point) = previous_vi_cursor_point { - self.damage.damage_point(previous_vi_cursor_point) - } - - // Damage Vi cursor if it's present. - if let Some(vi_cursor_point) = self.damage.last_vi_cursor_point { - self.damage.damage_point(vi_cursor_point); - } - - if self.damage.last_selection != previous_selection { - for selection in self.damage.last_selection.into_iter().chain(previous_selection) { - self.damage.damage_selection(selection, display_offset, self.columns()); - } - } - - TermDamage::Partial(TermDamageIterator::new(&self.damage.lines)) + // NOTE: damage which changes all the content when the display offset is non-zero (e.g. + // scrolling) is handled via full damage. + let display_offset = self.grid().display_offset(); + TermDamage::Partial(TermDamageIterator::new(&self.damage.lines, display_offset)) } /// Resets the terminal damage information. @@ -481,14 +442,8 @@ impl<T> Term<T> { } #[inline] - pub fn mark_fully_damaged(&mut self) { - self.damage.is_fully_damaged = true; - } - - /// Damage line in a terminal viewport. - #[inline] - pub fn damage_line(&mut self, line: usize, left: usize, right: usize) { - self.damage.damage_line(line, left, right); + fn mark_fully_damaged(&mut self) { + self.damage.full = true; } /// Set new options for the [`Term`]. @@ -1323,7 +1278,7 @@ impl<T: EventListener> Handler for Term<T> { trace!("Carriage return"); let new_col = 0; let line = self.grid.cursor.point.line.0 as usize; - self.damage_line(line, new_col, self.grid.cursor.point.column.0); + self.damage.damage_line(line, new_col, self.grid.cursor.point.column.0); self.grid.cursor.point.column = Column(new_col); self.grid.cursor.input_needs_wrap = false; } @@ -1491,7 +1446,7 @@ impl<T: EventListener> Handler for Term<T> { } let line = self.grid.cursor.point.line.0 as usize; - self.damage_line(line, self.grid.cursor.point.column.0, old_col); + self.damage.damage_line(line, self.grid.cursor.point.column.0, old_col); } #[inline] @@ -2888,7 +2843,7 @@ mod tests { term.input('e'); let right = term.grid.cursor.point.column.0; - let mut damaged_lines = match term.damage(None) { + let mut damaged_lines = match term.damage() { TermDamage::Full => panic!("Expected partial damage, however got Full"), TermDamage::Partial(damaged_lines) => damaged_lines, }; @@ -2896,70 +2851,51 @@ mod tests { assert_eq!(damaged_lines.next(), None); term.reset_damage(); - // Check that selection we've passed was properly damaged. - - let line = 1; - let left = 0; - let right = term.columns() - 1; - let mut selection = - Selection::new(SelectionType::Block, Point::new(Line(line), Column(3)), Side::Left); - selection.update(Point::new(Line(line), Column(5)), Side::Left); - let selection_range = selection.to_range(&term); - - let mut damaged_lines = match term.damage(selection_range) { - TermDamage::Full => panic!("Expected partial damage, however got Full"), - TermDamage::Partial(damaged_lines) => damaged_lines, - }; - let line = line as usize; - // Skip cursor damage information, since we're just testing selection. - damaged_lines.next(); - assert_eq!(damaged_lines.next(), Some(LineDamageBounds { line, left, right })); - assert_eq!(damaged_lines.next(), None); - term.reset_damage(); - - // Check that existing selection gets damaged when it is removed. + // Create scrollback. + for _ in 0..20 { + term.newline(); + } - let mut damaged_lines = match term.damage(None) { - TermDamage::Full => panic!("Expected partial damage, however got Full"), - TermDamage::Partial(damaged_lines) => damaged_lines, + match term.damage() { + TermDamage::Full => (), + TermDamage::Partial(_) => panic!("Expected Full damage, however got Partial "), }; - // Skip cursor damage information, since we're just testing selection clearing. - damaged_lines.next(); - assert_eq!(damaged_lines.next(), Some(LineDamageBounds { line, left, right })); - assert_eq!(damaged_lines.next(), None); term.reset_damage(); - // Check that `Vi` cursor in vi mode is being always damaged. - - term.toggle_vi_mode(); - // Put Vi cursor to a different location than normal cursor. - term.vi_goto_point(Point::new(Line(5), Column(5))); - // Reset damage, so the damage information from `vi_goto_point` won't affect test. + term.scroll_display(Scroll::Delta(10)); term.reset_damage(); - let vi_cursor_point = term.vi_mode_cursor.point; - let line = vi_cursor_point.line.0 as usize; - let left = vi_cursor_point.column.0; - let right = left; - let mut damaged_lines = match term.damage(None) { + // No damage when scrolled into viewport. + for idx in 0..term.columns() { + term.goto(idx as i32, idx); + } + let mut damaged_lines = match term.damage() { TermDamage::Full => panic!("Expected partial damage, however got Full"), TermDamage::Partial(damaged_lines) => damaged_lines, }; - // Skip cursor damage information, since we're just testing Vi cursor. - damaged_lines.next(); - assert_eq!(damaged_lines.next(), Some(LineDamageBounds { line, left, right })); assert_eq!(damaged_lines.next(), None); - // Ensure that old Vi cursor got damaged as well. + // Scroll back into the viewport, so we have 2 visible lines which terminal can write + // to. + term.scroll_display(Scroll::Delta(-2)); term.reset_damage(); - term.toggle_vi_mode(); - let mut damaged_lines = match term.damage(None) { + + term.goto(0, 0); + term.goto(1, 0); + term.goto(2, 0); + let display_offset = term.grid().display_offset(); + let mut damaged_lines = match term.damage() { TermDamage::Full => panic!("Expected partial damage, however got Full"), TermDamage::Partial(damaged_lines) => damaged_lines, }; - // Skip cursor damage information, since we're just testing Vi cursor. - damaged_lines.next(); - assert_eq!(damaged_lines.next(), Some(LineDamageBounds { line, left, right })); + assert_eq!( + damaged_lines.next(), + Some(LineDamageBounds { line: display_offset, left: 0, right: 0 }) + ); + assert_eq!( + damaged_lines.next(), + Some(LineDamageBounds { line: display_offset + 1, left: 0, right: 0 }) + ); assert_eq!(damaged_lines.next(), None); } @@ -3066,85 +3002,85 @@ mod tests { let size = TermSize::new(100, 10); let mut term = Term::new(Config::default(), &size, VoidListener); - assert!(term.damage.is_fully_damaged); + assert!(term.damage.full); for _ in 0..20 { term.newline(); } term.reset_damage(); term.clear_screen(ansi::ClearMode::Above); - assert!(term.damage.is_fully_damaged); + assert!(term.damage.full); term.reset_damage(); term.scroll_display(Scroll::Top); - assert!(term.damage.is_fully_damaged); + assert!(term.damage.full); term.reset_damage(); // Sequential call to scroll display without doing anything shouldn't damage. term.scroll_display(Scroll::Top); - assert!(!term.damage.is_fully_damaged); + assert!(!term.damage.full); term.reset_damage(); term.set_options(Config::default()); - assert!(term.damage.is_fully_damaged); + assert!(term.damage.full); term.reset_damage(); term.scroll_down_relative(Line(5), 2); - assert!(term.damage.is_fully_damaged); + assert!(term.damage.full); term.reset_damage(); term.scroll_up_relative(Line(3), 2); - assert!(term.damage.is_fully_damaged); + assert!(term.damage.full); term.reset_damage(); term.deccolm(); - assert!(term.damage.is_fully_damaged); + assert!(term.damage.full); term.reset_damage(); term.decaln(); - assert!(term.damage.is_fully_damaged); + assert!(term.damage.full); term.reset_damage(); term.set_mode(NamedMode::Insert.into()); // Just setting `Insert` mode shouldn't mark terminal as damaged. - assert!(!term.damage.is_fully_damaged); + assert!(!term.damage.full); term.reset_damage(); let color_index = 257; term.set_color(color_index, Rgb::default()); - assert!(term.damage.is_fully_damaged); + assert!(term.damage.full); term.reset_damage(); // Setting the same color once again shouldn't trigger full damage. term.set_color(color_index, Rgb::default()); - assert!(!term.damage.is_fully_damaged); + assert!(!term.damage.full); term.reset_color(color_index); - assert!(term.damage.is_fully_damaged); + assert!(term.damage.full); term.reset_damage(); // We shouldn't trigger fully damage when cursor gets update. term.set_color(NamedColor::Cursor as usize, Rgb::default()); - assert!(!term.damage.is_fully_damaged); + assert!(!term.damage.full); // However requesting terminal damage should mark terminal as fully damaged in `Insert` // mode. - let _ = term.damage(None); - assert!(term.damage.is_fully_damaged); + let _ = term.damage(); + assert!(term.damage.full); term.reset_damage(); term.unset_mode(NamedMode::Insert.into()); - assert!(term.damage.is_fully_damaged); + assert!(term.damage.full); term.reset_damage(); // Keep this as a last check, so we don't have to deal with restoring from alt-screen. term.swap_alt(); - assert!(term.damage.is_fully_damaged); + assert!(term.damage.full); term.reset_damage(); let size = TermSize::new(10, 10); term.resize(size); - assert!(term.damage.is_fully_damaged); + assert!(term.damage.full); } #[test] |