diff options
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | alacritty_terminal/src/grid/mod.rs | 125 | ||||
-rw-r--r-- | alacritty_terminal/src/grid/storage.rs | 12 |
3 files changed, 84 insertions, 54 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 34dde984..32844199 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Preserve vi mode across terminal `reset` - Escapes `CSI Ps b` and `CSI Ps Z` with large parameters locking up Alacritty - Dimming colors which use the indexed `CSI 38 : 5 : Ps m` notation +- Performance of scrolling regions with offset from the bottom ### Removed diff --git a/alacritty_terminal/src/grid/mod.rs b/alacritty_terminal/src/grid/mod.rs index 21e7e2f9..1c23bfc8 100644 --- a/alacritty_terminal/src/grid/mod.rs +++ b/alacritty_terminal/src/grid/mod.rs @@ -223,25 +223,49 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { 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. + let screen_lines = self.screen_lines().0; + + // When rotating the entire region, just reset everything. + if positions >= region.end - region.start { + for i in region.start.0..region.end.0 { + let index = screen_lines - i - 1; + self.raw[index].reset(&self.cursor.template); + } + + return; + } + + // Which implementation we can use depends on the existence of a scrollback history. // - // To accommodate scroll regions, rows are reordered at the end. - if region.start == Line(0) && self.max_scroll_limit == 0 { - // Rotate the entire line buffer. If there's a scrolling region - // active, the bottom lines are restored in the next step. - self.raw.rotate_up(*positions); - - // Now, restore any scroll region lines. - let lines = self.lines; - for i in IndexRange(region.end..lines) { - self.raw.swap_lines(i, i + positions); + // Since a scrollback history prevents us from rotating the entire buffer downwards, we + // instead have to rely on a slower, swap-based implementation. + if self.max_scroll_limit == 0 { + // Swap the lines fixed at the bottom to their target positions after rotation. + // + // Since we've made sure that the rotation will never rotate away the entire region, we + // know that the position of the fixed lines before the rotation must already be + // visible. + // + // We need to start from the top, to make sure the fixed lines aren't swapped with each + // other. + let fixed_lines = screen_lines - region.end.0; + for i in (0..fixed_lines).rev() { + self.raw.swap(i, i + positions.0); } - // Finally, reset recycled lines. - for i in IndexRange(Line(0)..positions) { - self.raw[i].reset(&self.cursor.template); + // Rotate the entire line buffer downward. + self.raw.rotate_down(*positions); + + // Ensure all new lines are fully cleared. + for i in 0..positions.0 { + let index = screen_lines - i - 1; + self.raw[index].reset(&self.cursor.template); + } + + // Swap the fixed lines at the top back into position. + for i in 0..region.start.0 { + let index = screen_lines - i - 1; + self.raw.swap(index, index - positions.0); } } else { // Subregion rotation. @@ -263,46 +287,51 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { T: ResetDiscriminant<D>, D: PartialEq, { - let num_lines = self.screen_lines().0; + let screen_lines = self.screen_lines().0; - if region.start == Line(0) { - // Update display offset when not pinned to active area. - if self.display_offset != 0 { - self.display_offset = min(self.display_offset + *positions, self.max_scroll_limit); + // When rotating the entire region with fixed lines at the top, just reset everything. + if positions >= region.end - region.start && region.start != Line(0) { + for i in region.start.0..region.end.0 { + let index = screen_lines - i - 1; + self.raw[index].reset(&self.cursor.template); } - self.increase_scroll_limit(*positions); + return; + } - // Rotate the entire line buffer. If there's a scrolling region - // active, the bottom lines are restored in the next step. - self.raw.rotate(-(*positions as isize)); + // Update display offset when not pinned to active area. + if self.display_offset != 0 { + self.display_offset = min(self.display_offset + *positions, self.max_scroll_limit); + } - // This next loop swaps "fixed" lines outside of a scroll region - // back into place after the rotation. The work is done in buffer- - // space rather than terminal-space to avoid redundant - // transformations. - let fixed_lines = num_lines - *region.end; + // Create scrollback for the new lines. + self.increase_scroll_limit(*positions); - for i in 0..fixed_lines { - self.raw.swap(i, i + *positions); - } + // Swap the lines fixed at the top to their target positions after rotation. + // + // Since we've made sure that the rotation will never rotate away the entire region, we + // know that the position of the fixed lines before the rotation must already be + // visible. + // + // We need to start from the bottom, to make sure the fixed lines aren't swapped with each + // other. + for i in (0..region.start.0).rev() { + let index = screen_lines - i - 1; + self.raw.swap(index, index - positions.0); + } - // Finally, reset recycled lines. - // - // Recycled lines are just above the end of the scrolling region. - for i in 0..*positions { - self.raw[i + fixed_lines].reset(&self.cursor.template); - } - } else { - // Subregion rotation. - for line in IndexRange(region.start..(region.end - positions)) { - self.raw.swap_lines(line, line + positions); - } + // Rotate the entire line buffer upward. + self.raw.rotate(-(positions.0 as isize)); - // Clear reused lines. - for line in IndexRange((region.end - positions)..region.end) { - self.raw[line].reset(&self.cursor.template); - } + // Ensure all new lines are fully cleared. + for i in 0..positions.0 { + self.raw[i].reset(&self.cursor.template); + } + + // Swap the fixed lines at the bottom back into position. + let fixed_lines = screen_lines - region.end.0; + for i in 0..fixed_lines { + self.raw.swap(i, i + positions.0); } } diff --git a/alacritty_terminal/src/grid/storage.rs b/alacritty_terminal/src/grid/storage.rs index dd8dbb22..96c578b3 100644 --- a/alacritty_terminal/src/grid/storage.rs +++ b/alacritty_terminal/src/grid/storage.rs @@ -12,9 +12,9 @@ const MAX_CACHE_SIZE: usize = 1_000; /// 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. +/// The [`Storage::rotate`] and [`Storage::rotate_down`] 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. @@ -186,16 +186,16 @@ impl<T> Storage<T> { debug_assert!(count.abs() as usize <= self.inner.len()); let len = self.inner.len(); - self.zero = (self.zero as isize + count + len as isize) as usize % self.inner.len(); + self.zero = (self.zero as isize + count + len as isize) as usize % len; } - /// Rotate the grid up, moving all existing lines down in history. + /// Rotate all existing lines down in history. /// /// This is a faster, specialized version of [`rotate_left`]. /// /// [`rotate_left`]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.rotate_left #[inline] - pub fn rotate_up(&mut self, count: usize) { + pub fn rotate_down(&mut self, count: usize) { self.zero = (self.zero + count) % self.inner.len(); } |