summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md3
-rw-r--r--Cargo.lock2
-rw-r--r--alacritty.yml3
-rw-r--r--alacritty/Cargo.toml2
-rw-r--r--alacritty/src/config/debug.rs4
-rw-r--r--alacritty/src/display/content.rs53
-rw-r--r--alacritty/src/display/damage.rs86
-rw-r--r--alacritty/src/display/meter.rs2
-rw-r--r--alacritty/src/display/mod.rs185
-rw-r--r--alacritty/src/display/window.rs10
-rw-r--r--alacritty/src/event.rs6
-rw-r--r--alacritty/src/input.rs4
-rw-r--r--alacritty/src/renderer/builtin_font.rs2
-rw-r--r--alacritty_terminal/Cargo.toml2
-rw-r--r--alacritty_terminal/src/config/mod.rs2
-rw-r--r--alacritty_terminal/src/config/scrolling.rs2
-rw-r--r--alacritty_terminal/src/selection.rs1
-rw-r--r--alacritty_terminal/src/term/mod.rs755
-rw-r--r--alacritty_terminal/src/vi_mode.rs2
19 files changed, 991 insertions, 135 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index edcdc07e..90995ded 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,7 +13,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added
- - Option `font.builtin_box_drawing` to disable the built-in font for drawing box characters
+- Option `font.builtin_box_drawing` to disable the built-in font for drawing box characters
+- Track and report surface damage information to Wayland compositors
### Changed
diff --git a/Cargo.lock b/Cargo.lock
index 6936b448..2274c25c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -56,7 +56,7 @@ dependencies = [
[[package]]
name = "alacritty_terminal"
-version = "0.16.1-dev"
+version = "0.17.0-dev"
dependencies = [
"alacritty_config_derive",
"base64",
diff --git a/alacritty.yml b/alacritty.yml
index c9959497..85de5701 100644
--- a/alacritty.yml
+++ b/alacritty.yml
@@ -876,3 +876,6 @@
# Print all received window events.
#print_events: false
+
+ # Highlight window damage information.
+ #highlight_damage: false
diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml
index ef46eb2c..b1438c23 100644
--- a/alacritty/Cargo.toml
+++ b/alacritty/Cargo.toml
@@ -11,7 +11,7 @@ rust-version = "1.56.0"
[dependencies.alacritty_terminal]
path = "../alacritty_terminal"
-version = "0.16.1-dev"
+version = "0.17.0-dev"
default-features = false
[dependencies.alacritty_config_derive]
diff --git a/alacritty/src/config/debug.rs b/alacritty/src/config/debug.rs
index f52cdf90..3fa987a5 100644
--- a/alacritty/src/config/debug.rs
+++ b/alacritty/src/config/debug.rs
@@ -15,6 +15,9 @@ pub struct Debug {
/// Should show render timer.
pub render_timer: bool,
+ /// Highlight damage information produced by alacritty.
+ pub highlight_damage: bool,
+
/// Record ref test.
#[config(skip)]
pub ref_test: bool,
@@ -27,6 +30,7 @@ impl Default for Debug {
print_events: Default::default(),
persistent_logging: Default::default(),
render_timer: Default::default(),
+ highlight_damage: Default::default(),
ref_test: Default::default(),
}
}
diff --git a/alacritty/src/display/content.rs b/alacritty/src/display/content.rs
index 72d79f7e..3b549992 100644
--- a/alacritty/src/display/content.rs
+++ b/alacritty/src/display/content.rs
@@ -7,6 +7,7 @@ use alacritty_terminal::ansi::{Color, CursorShape, NamedColor};
use alacritty_terminal::event::EventListener;
use alacritty_terminal::grid::{Dimensions, Indexed};
use alacritty_terminal::index::{Column, Direction, Line, Point};
+use alacritty_terminal::selection::SelectionRange;
use alacritty_terminal::term::cell::{Cell, Flags};
use alacritty_terminal::term::color::{CellRgb, Rgb};
use alacritty_terminal::term::search::{Match, RegexIter, RegexSearch};
@@ -26,7 +27,7 @@ pub const MIN_CURSOR_CONTRAST: f64 = 1.5;
/// This provides the terminal cursor and an iterator over all non-empty cells.
pub struct RenderableContent<'a> {
terminal_content: TerminalContent<'a>,
- cursor: Option<RenderableCursor>,
+ cursor: RenderableCursor,
cursor_shape: CursorShape,
cursor_point: Point<usize>,
search: Option<Regex<'a>>,
@@ -73,7 +74,7 @@ impl<'a> RenderableContent<'a> {
Self {
colors: &display.colors,
- cursor: None,
+ cursor: RenderableCursor::new_hidden(),
terminal_content,
focused_match,
cursor_shape,
@@ -90,7 +91,7 @@ impl<'a> RenderableContent<'a> {
}
/// Get the terminal cursor.
- pub fn cursor(mut self) -> Option<RenderableCursor> {
+ pub fn cursor(mut self) -> RenderableCursor {
// Assure this function is only called after the iterator has been drained.
debug_assert!(self.next().is_none());
@@ -102,14 +103,14 @@ impl<'a> RenderableContent<'a> {
self.terminal_content.colors[color].unwrap_or(self.colors[color])
}
+ pub fn selection_range(&self) -> Option<SelectionRange> {
+ self.terminal_content.selection
+ }
+
/// Assemble the information required to render the terminal cursor.
///
/// This will return `None` when there is no cursor visible.
- fn renderable_cursor(&mut self, cell: &RenderableCell) -> Option<RenderableCursor> {
- if self.cursor_shape == CursorShape::Hidden {
- return None;
- }
-
+ fn renderable_cursor(&mut self, cell: &RenderableCell) -> RenderableCursor {
// Cursor colors.
let color = if self.terminal_content.mode.contains(TermMode::VI) {
self.config.colors.vi_mode_cursor
@@ -134,13 +135,13 @@ impl<'a> RenderableContent<'a> {
text_color = self.config.colors.primary.background;
}
- Some(RenderableCursor {
+ RenderableCursor {
is_wide: cell.flags.contains(Flags::WIDE_CHAR),
shape: self.cursor_shape,
point: self.cursor_point,
cursor_color,
text_color,
- })
+ }
}
}
@@ -159,18 +160,15 @@ impl<'a> Iterator for RenderableContent<'a> {
if self.cursor_point == cell.point {
// Store the cursor which should be rendered.
- self.cursor = self.renderable_cursor(&cell).map(|cursor| {
- if cursor.shape == CursorShape::Block {
- cell.fg = cursor.text_color;
- cell.bg = cursor.cursor_color;
-
- // Since we draw Block cursor by drawing cell below it with a proper color,
- // we must adjust alpha to make it visible.
- cell.bg_alpha = 1.;
- }
-
- cursor
- });
+ self.cursor = self.renderable_cursor(&cell);
+ if self.cursor.shape == CursorShape::Block {
+ cell.fg = self.cursor.text_color;
+ cell.bg = self.cursor.cursor_color;
+
+ // Since we draw Block cursor by drawing cell below it with a proper color,
+ // we must adjust alpha to make it visible.
+ cell.bg_alpha = 1.;
+ }
return Some(cell);
} else if !cell.is_empty() && !cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
@@ -372,6 +370,17 @@ pub struct RenderableCursor {
}
impl RenderableCursor {
+ fn new_hidden() -> Self {
+ let shape = CursorShape::Hidden;
+ let cursor_color = Rgb::default();
+ let text_color = Rgb::default();
+ let is_wide = false;
+ let point = Point::default();
+ Self { shape, cursor_color, text_color, is_wide, point }
+ }
+}
+
+impl RenderableCursor {
pub fn color(&self) -> Rgb {
self.cursor_color
}
diff --git a/alacritty/src/display/damage.rs b/alacritty/src/display/damage.rs
new file mode 100644
index 00000000..d6a69a2d
--- /dev/null
+++ b/alacritty/src/display/damage.rs
@@ -0,0 +1,86 @@
+use std::cmp;
+use std::iter::Peekable;
+
+use glutin::Rect;
+
+use alacritty_terminal::term::{LineDamageBounds, SizeInfo, TermDamageIterator};
+
+/// Iterator which converts `alacritty_terminal` damage information into renderer damaged rects.
+pub struct RenderDamageIterator<'a> {
+ damaged_lines: Peekable<TermDamageIterator<'a>>,
+ size_info: SizeInfo<u32>,
+}
+
+impl<'a> RenderDamageIterator<'a> {
+ pub fn new(damaged_lines: TermDamageIterator<'a>, size_info: SizeInfo<u32>) -> Self {
+ Self { damaged_lines: damaged_lines.peekable(), size_info }
+ }
+
+ #[inline]
+ fn rect_for_line(&self, line_damage: LineDamageBounds) -> Rect {
+ let size_info = &self.size_info;
+ let y_top = size_info.height() - size_info.padding_y();
+ let x = size_info.padding_x() + line_damage.left as u32 * size_info.cell_width();
+ let y = y_top - (line_damage.line + 1) as u32 * size_info.cell_height();
+ let width = (line_damage.right - line_damage.left + 1) as u32 * size_info.cell_width();
+ Rect { x, y, height: size_info.cell_height(), width }
+ }
+
+ // Make sure to damage near cells to include wide chars.
+ #[inline]
+ fn overdamage(&self, mut rect: Rect) -> Rect {
+ let size_info = &self.size_info;
+ rect.x = rect.x.saturating_sub(size_info.cell_width());
+ rect.width = cmp::min(size_info.width() - rect.x, rect.width + 2 * size_info.cell_width());
+ rect.y = rect.y.saturating_sub(size_info.cell_height() / 2);
+ rect.height = cmp::min(size_info.height() - rect.y, rect.height + size_info.cell_height());
+
+ rect
+ }
+}
+
+impl<'a> Iterator for RenderDamageIterator<'a> {
+ type Item = Rect;
+
+ fn next(&mut self) -> Option<Rect> {
+ let line = self.damaged_lines.next()?;
+ let mut total_damage_rect = self.overdamage(self.rect_for_line(line));
+
+ // Merge rectangles which overlap with each other.
+ while let Some(line) = self.damaged_lines.peek().copied() {
+ let next_rect = self.overdamage(self.rect_for_line(line));
+ if !rects_overlap(total_damage_rect, next_rect) {
+ break;
+ }
+
+ total_damage_rect = merge_rects(total_damage_rect, next_rect);
+ let _ = self.damaged_lines.next();
+ }
+
+ Some(total_damage_rect)
+ }
+}
+
+/// Check if two given [`glutin::Rect`] overlap.
+fn rects_overlap(lhs: Rect, rhs: Rect) -> bool {
+ !(
+ // `lhs` is left of `rhs`.
+ lhs.x + lhs.width < rhs.x
+ // `lhs` is right of `rhs`.
+ || rhs.x + rhs.width < lhs.x
+ // `lhs` is below `rhs`.
+ || lhs.y + lhs.height < rhs.y
+ // `lhs` is above `rhs`.
+ || rhs.y + rhs.height < lhs.y
+ )
+}
+
+/// Merge two [`glutin::Rect`] by producing the smallest rectangle that contains both.
+#[inline]
+fn merge_rects(lhs: Rect, rhs: Rect) -> Rect {
+ let left_x = cmp::min(lhs.x, rhs.x);
+ let right_x = cmp::max(lhs.x + lhs.width, rhs.x + rhs.width);
+ let y_top = cmp::max(lhs.y + lhs.height, rhs.y + rhs.height);
+ let y_bottom = cmp::min(lhs.y, rhs.y);
+ Rect { x: left_x, y: y_bottom, width: right_x - left_x, height: y_top - y_bottom }
+}
diff --git a/alacritty/src/display/meter.rs b/alacritty/src/display/meter.rs
index c07d901f..9ccfe52d 100644
--- a/alacritty/src/display/meter.rs
+++ b/alacritty/src/display/meter.rs
@@ -31,7 +31,7 @@ pub struct Meter {
/// Average sample time in microseconds.
avg: f64,
- /// Index of next time to update..
+ /// Index of next time to update.
index: usize,
}
diff --git a/alacritty/src/display/mod.rs b/alacritty/src/display/mod.rs
index d9ec8593..7d53e678 100644
--- a/alacritty/src/display/mod.rs
+++ b/alacritty/src/display/mod.rs
@@ -1,13 +1,12 @@
//! The display subsystem including window management, font rasterization, and
//! GPU drawing.
-use std::cmp::min;
use std::convert::TryFrom;
use std::fmt::{self, Formatter};
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
use std::sync::atomic::Ordering;
use std::time::Instant;
-use std::{f64, mem};
+use std::{cmp, mem};
use glutin::dpi::PhysicalSize;
use glutin::event::ModifiersState;
@@ -15,6 +14,7 @@ use glutin::event_loop::EventLoopWindowTarget;
#[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))]
use glutin::platform::unix::EventLoopWindowTargetExtUnix;
use glutin::window::CursorIcon;
+use glutin::Rect as DamageRect;
use log::{debug, info};
use parking_lot::MutexGuard;
use unicode_width::UnicodeWidthChar;
@@ -24,12 +24,16 @@ use wayland_client::EventQueue;
use crossfont::{self, Rasterize, Rasterizer};
use alacritty_terminal::ansi::NamedColor;
+use alacritty_terminal::config::MAX_SCROLLBACK_LINES;
use alacritty_terminal::event::{EventListener, OnResize};
use alacritty_terminal::grid::Dimensions as _;
use alacritty_terminal::index::{Column, Direction, Line, Point};
-use alacritty_terminal::selection::Selection;
+use alacritty_terminal::selection::{Selection, SelectionRange};
use alacritty_terminal::term::cell::Flags;
-use alacritty_terminal::term::{SizeInfo, Term, TermMode, MIN_COLUMNS, MIN_SCREEN_LINES};
+use alacritty_terminal::term::color::Rgb;
+use alacritty_terminal::term::{
+ SizeInfo, Term, TermDamage, TermMode, MIN_COLUMNS, MIN_SCREEN_LINES,
+};
use crate::config::font::Font;
#[cfg(not(windows))]
@@ -40,6 +44,7 @@ use crate::display::bell::VisualBell;
use crate::display::color::List;
use crate::display::content::RenderableContent;
use crate::display::cursor::IntoRects;
+use crate::display::damage::RenderDamageIterator;
use crate::display::hint::{HintMatch, HintState};
use crate::display::meter::Meter;
use crate::display::window::Window;
@@ -55,6 +60,7 @@ pub mod window;
mod bell;
mod color;
+mod damage;
mod meter;
/// Maximum number of linewraps followed outside of the viewport during search highlighting.
@@ -66,6 +72,9 @@ const FORWARD_SEARCH_LABEL: &str = "Search: ";
/// Label for the backward terminal search bar.
const BACKWARD_SEARCH_LABEL: &str = "Backward Search: ";
+/// Color which is used to highlight damaged rects when debugging.
+const DAMAGE_RECT_COLOR: Rgb = Rgb { r: 255, g: 0, b: 255 };
+
#[derive(Debug)]
pub enum Error {
/// Error with window management.
@@ -193,6 +202,9 @@ pub struct Display {
/// Unprocessed display updates.
pub pending_update: DisplayUpdate,
+ is_damage_supported: bool,
+ debug_damage: bool,
+ damage_rects: Vec<DamageRect>,
renderer: QuadRenderer,
glyph_cache: GlyphCache,
meter: Meter,
@@ -319,6 +331,13 @@ impl Display {
}
let hint_state = HintState::new(config.hints.alphabet());
+ let is_damage_supported = window.swap_buffers_with_damage_supported();
+ let debug_damage = config.debug.highlight_damage;
+ let damage_rects = if is_damage_supported || debug_damage {
+ Vec::with_capacity(size_info.screen_lines())
+ } else {
+ Vec::new()
+ };
Ok(Self {
window,
@@ -335,6 +354,9 @@ impl Display {
visual_bell: VisualBell::from(&config.bell),
colors: List::from(&config.colors),
pending_update: Default::default(),
+ is_damage_supported,
+ debug_damage,
+ damage_rects,
})
}
@@ -457,10 +479,58 @@ impl Display {
self.window.resize(physical);
self.renderer.resize(&self.size_info);
+ if self.collect_damage() {
+ let lines = self.size_info.screen_lines();
+ if lines > self.damage_rects.len() {
+ self.damage_rects.reserve(lines);
+ } else {
+ self.damage_rects.shrink_to(lines);
+ }
+ }
+
info!("Padding: {} x {}", self.size_info.padding_x(), self.size_info.padding_y());
info!("Width: {}, Height: {}", self.size_info.width(), self.size_info.height());
}
+ fn update_damage<T: EventListener>(
+ &mut self,
+ terminal: &mut MutexGuard<'_, Term<T>>,
+ selection_range: Option<SelectionRange>,
+ search_state: &SearchState,
+ ) {
+ let requires_full_damage = self.visual_bell.intensity() != 0.
+ || self.hint_state.active()
+ || search_state.regex().is_some();
+ if requires_full_damage {
+ terminal.mark_fully_damaged();
+ }
+
+ self.damage_highlighted_hints(terminal);
+ let size_info: SizeInfo<u32> = self.size_info.into();
+ match terminal.damage(selection_range) {
+ TermDamage::Full => {
+ let screen_rect =
+ DamageRect { x: 0, y: 0, width: size_info.width(), height: size_info.height() };
+ self.damage_rects.push(screen_rect);
+ },
+ TermDamage::Partial(damaged_lines) => {
+ let damaged_rects = RenderDamageIterator::new(damaged_lines, size_info);
+ for damaged_rect in damaged_rects {
+ self.damage_rects.push(damaged_rect);
+ }
+ },
+ }
+ terminal.reset_damage();
+
+ // Ensure that the content requiring full damage is cleaned up again on the next frame.
+ if requires_full_damage {
+ terminal.mark_fully_damaged();
+ }
+
+ // Damage highlighted hints for the next frame as well, so we'll clear them.
+ self.damage_highlighted_hints(terminal);
+ }
+
/// Draw the screen.
///
/// A reference to Term whose state is being drawn must be provided.
@@ -468,7 +538,7 @@ impl Display {
/// This call may block if vsync is enabled.
pub fn draw<T: EventListener>(
&mut self,
- terminal: MutexGuard<'_, Term<T>>,
+ mut terminal: MutexGuard<'_, Term<T>>,
message_buffer: &MessageBuffer,
config: &UiConfig,
search_state: &SearchState,
@@ -479,6 +549,7 @@ impl Display {
for cell in &mut content {
grid_cells.push(cell);
}
+ let selection_range = content.selection_range();
let background_color = content.color(NamedColor::Background as usize);
let display_offset = content.display_offset();
let cursor = content.cursor();
@@ -491,6 +562,11 @@ impl Display {
let vi_mode = terminal.mode().contains(TermMode::VI);
let vi_mode_cursor = if vi_mode { Some(terminal.vi_mode_cursor) } else { None };
+ if self.collect_damage() {
+ self.damage_rects.clear();
+ self.update_damage(&mut terminal, selection_range, search_state);
+ }
+
// Drop terminal as early as possible to free lock.
drop(terminal);
@@ -549,11 +625,9 @@ impl Display {
self.draw_line_indicator(config, &size_info, total_lines, None, display_offset);
}
- // Push the cursor rects for rendering.
- if let Some(cursor) = cursor {
- for rect in cursor.rects(&size_info, config.terminal_config.cursor.thickness()) {
- rects.push(rect);
- }
+ // Draw cursor.
+ for rect in cursor.rects(&size_info, config.terminal_config.cursor.thickness()) {
+ rects.push(rect);
}
// Push visual bell after url/underline/strikeout rects.
@@ -570,6 +644,10 @@ impl Display {
rects.push(visual_bell_rect);
}
+ if self.debug_damage {
+ self.highlight_damage(&mut rects);
+ }
+
if let Some(message) = message_buffer.message() {
let search_offset = if search_state.regex().is_some() { 1 } else { 0 };
let text = message.text(&size_info);
@@ -636,7 +714,12 @@ impl Display {
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
self.request_frame(&self.window);
- self.window.swap_buffers();
+ // Clearing debug highlights from the previous frame requires full redraw.
+ if self.is_damage_supported && !self.debug_damage {
+ self.window.swap_buffers_with_damage(&self.damage_rects);
+ } else {
+ self.window.swap_buffers();
+ }
#[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))]
if self.is_x11 {
@@ -651,6 +734,7 @@ impl Display {
/// Update to a new configuration.
pub fn update_config(&mut self, config: &UiConfig) {
+ self.debug_damage = config.debug.highlight_damage;
self.visual_bell.update_config(&config.bell);
self.colors = List::from(&config.colors);
}
@@ -722,7 +806,7 @@ impl Display {
let num_cols = size_info.columns();
let label_len = search_label.chars().count();
let regex_len = formatted_regex.chars().count();
- let truncate_len = min((regex_len + label_len).saturating_sub(num_cols), regex_len);
+ let truncate_len = cmp::min((regex_len + label_len).saturating_sub(num_cols), regex_len);
let index = formatted_regex.char_indices().nth(truncate_len).map(|(i, _c)| i).unwrap_or(0);
let truncated_regex = &formatted_regex[index..];
@@ -758,13 +842,15 @@ impl Display {
return;
}
- let glyph_cache = &mut self.glyph_cache;
-
let timing = format!("{:.3} usec", self.meter.average());
let point = Point::new(size_info.screen_lines().saturating_sub(2), Column(0));
let fg = config.colors.primary.background;
let bg = config.colors.normal.red;
+ // Damage the entire line.
+ self.damage_from_point(point, self.size_info.columns() as u32);
+
+ let glyph_cache = &mut self.glyph_cache;
self.renderer.with_api(config, size_info, |mut api| {
api.draw_string(glyph_cache, point, fg, bg, &timing);
});
@@ -779,8 +865,26 @@ impl Display {
obstructed_column: Option<Column>,
line: usize,
) {
+ const fn num_digits(mut number: u32) -> usize {
+ let mut res = 0;
+ loop {
+ number /= 10;
+ res += 1;
+ if number == 0 {
+ break res;
+ }
+ }
+ }
+
let text = format!("[{}/{}]", line, total_lines - 1);
let column = Column(size_info.columns().saturating_sub(text.len()));
+ let point = Point::new(0, column);
+
+ // Damage the maximum possible length of the format text, which could be achieved when
+ // using `MAX_SCROLLBACK_LINES` as current and total lines adding a `3` for formatting.
+ const MAX_LEN: usize = num_digits(MAX_SCROLLBACK_LINES) + 3;
+ self.damage_from_point(Point::new(0, point.column - MAX_LEN), MAX_LEN as u32 * 2);
+
let colors = &config.colors;
let fg = colors.line_indicator.foreground.unwrap_or(colors.primary.background);
let bg = colors.line_indicator.background.unwrap_or(colors.primary.foreground);
@@ -789,11 +893,60 @@ impl Display {
if obstructed_column.map_or(true, |obstructed_column| obstructed_column < column) {
let glyph_cache = &mut self.glyph_cache;
self.renderer.with_api(config, size_info, |mut api| {
- api.draw_string(glyph_cache, Point::new(0, column), fg, bg, &text);
+ api.draw_string(glyph_cache, point, fg, bg, &text);
});
}
}
+ /// Damage `len` starting from a `point`.
+ #[inline]
+ fn damage_from_point(&mut self, point: Point<usize>, len: u32) {
+ if !self.collect_damage() {
+ return;
+ }
+
+ let size_info: SizeInfo<u32> = self.size_info.into();
+ let x = size_info.padding_x() + point.column.0 as u32 * size_info.cell_width();
+ let y_top = size_info.height() - size_info.padding_y();
+ let y = y_top - (point.line as u32 + 1) * size_info.cell_height();
+ let width = len as u32 * size_info.cell_width();
+ self.damage_rects.push(DamageRect { x, y, width, height: size_info.cell_height() })
+ }
+
+ /// Damage currently highlighted `Display` hints.
+ #[inline]
+ fn damage_highlighted_hints<T: EventListener>(&self, terminal: &mut Term<T>) {
+ let display_offset = terminal.grid().display_offset();
+ for hint in self.highlighted_hint.iter().chain(&self.vi_highlighted_hint) {
+ for point in (hint.bounds.start().line.0..=hint.bounds.end().line.0).flat_map(|line| {
+ point_to_viewport(display_offset, Point::new(Line(line), Column(0)))
+ }) {
+ terminal.damage_line(point.line, 0, terminal.columns() - 1);
+ }
+ }
+ }
+
+ /// Returns `true` if damage information should be collected, `false` otherwise.
+ #[inline]
+ fn collect_damage(&self) -> bool {
+ self.is_damage_supported || self.debug_damage
+ }
+
+ /// Highlight damaged rects.
+ ///
+ /// This function is for debug purposes only.
+ fn highlight_damage(&self, render_rects: &mut Vec<RenderRect>) {
+ for damage_rect in &self.damage_rects {
+ let x = damage_rect.x as f32;
+ let height = damage_rect.height as f32;
+ let width = damage_rect.width as f32;
+ let y = self.size_info.height() - damage_rect.y as f32 - height;
+ let render_rect = RenderRect::new(x, y, width, height, DAMAGE_RECT_COLOR, 0.5);
+
+ render_rects.push(render_rect);
+ }
+ }
+
/// Requst a new frame for a window on Wayland.
#[inline]
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
@@ -824,12 +977,14 @@ impl Drop for Display {
}
/// Convert a terminal point to a viewport relative point.
+#[inline]
pub fn point_to_viewport(display_offset: usize, point: Point) -> Option<Point<usize>> {
let viewport_line = point.line.0 + display_offset as i32;
usize::try_from(viewport_line).ok().map(|line| Point::new(line, point.column))
}
/// Convert a viewport relative point to a terminal point.
+#[inline]
pub fn viewport_to_point(display_offset: usize, point: Point<usize>) -> Point {
let line = Line(point.line as i32) - display_offset;
Point::new(line, point.column)
diff --git a/alacritty/src/display/window.rs b/alacritty/src/display/window.rs
index 493e5ef9..712b4ac9 100644
--- a/alacritty/src/display/window.rs
+++ b/alacritty/src/display/window.rs
@@ -39,7 +39,7 @@ use glutin::platform::windows::IconExtWindows;
use glutin::window::{
CursorIcon, Fullscreen, UserAttentionType, Window as GlutinWindow, WindowBuilder, WindowId,
};
-use glutin::{self, ContextBuilder, PossiblyCurrent, WindowedContext};
+use glutin::{self, ContextBuilder, PossiblyCurrent, Rect, WindowedContext};
#[cfg(target_os = "macos")]
use objc::{msg_send, sel, sel_impl};
#[cfg(target_os = "macos")]
@@ -428,6 +428,14 @@ impl Window {
self.windowed_context.swap_buffers().expect("swap buffers");
}
+ pub fn swap_buffers_with_damage(&self, damage: &[Rect]) {
+ self.windowed_context.swap_buffers_with_damage(damage).expect("swap buffes with damage");
+ }
+
+ pub fn swap_buffers_with_damage_supported(&self) -> bool {
+ self.windowed_context.swap_buffers_with_damage_supported()
+ }
+
pub fn resize(&self, size: PhysicalSize<u32>) {
self.windowed_context.resize(size);
}
diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs
index aea6010d..8bd1dec7 100644
--- a/alacritty/src/event.rs
+++ b/alacritty/src/event.rs
@@ -768,7 +768,11 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
/// Toggle the vi mode status.
#[inline]
fn toggle_vi_mode(&mut self) {
- if !self.terminal.mode().contains(TermMode::VI) {
+ if self.terminal.mode().contains(TermMode::VI) {
+ // Damage line indicator and Vi cursor if we're leaving Vi mode.
+ self.terminal.damage_vi_cursor();
+ self.terminal.damage_line(0, 0, self.terminal.columns() - 1);
+ } else {
self.clear_selection();
}
diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs
index 0aa2cbba..51bd3fc5 100644
--- a/alacritty/src/input.rs
+++ b/alacritty/src/input.rs
@@ -801,7 +801,9 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
self.ctx.on_typing_start();
- self.ctx.scroll(Scroll::Bottom);
+ if self.ctx.terminal().grid().display_offset() != 0 {
+ self.ctx.scroll(Scroll::Bottom);
+ }
self.ctx.clear_selection();
let utf8_len = c.len_utf8();
diff --git a/alacritty/src/renderer/builtin_font.rs b/alacritty/src/renderer/builtin_font.rs
index f3dbe9bb..05798466 100644
--- a/alacritty/src/renderer/builtin_font.rs
+++ b/alacritty/src/renderer/builtin_font.rs
@@ -784,7 +784,7 @@ impl Canvas {
}
#[cfg(test)]
-mod test {
+mod tests {
use super::*;
use crossfont::Metrics;
diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml
index be8d17dd..269b110a 100644
--- a/alacritty_terminal/Cargo.toml
+++ b/alacritty_terminal/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "alacritty_terminal"
-version = "0.16.1-dev"
+version = "0.17.0-dev"
authors = ["Christian Duerr <contact@christianduerr.com>", "Joe Wilm <joe@jwilm.com>"]
license = "Apache-2.0"
description = "Library for writing terminal emulators"
diff --git a/alacritty_terminal/src/config/mod.rs b/alacritty_terminal/src/config/mod.rs
index 09161e03..e99c37b5 100644
--- a/alacritty_terminal/src/config/mod.rs
+++ b/alacritty_terminal/src/config/mod.rs
@@ -10,7 +10,7 @@ mod scrolling;
use crate::ansi::{CursorShape, CursorStyle};
-pub use crate::config::scrolling::Scrolling;
+pub use crate::config::scrolling::{Scrolling, MAX_SCROLLBACK_LINES};
pub const LOG_TARGET_CONFIG: &str = "alacritty_config_derive";
const MIN_BLINK_INTERVAL: u64 = 10;
diff --git a/alacritty_terminal/src/config/scrolling.rs b/alacritty_terminal/src/config/scrolling.rs
index 159b0f44..9a5a718c 100644
--- a/alacritty_terminal/src/config/scrolling.rs
+++ b/alacritty_terminal/src/config/scrolling.rs
@@ -4,7 +4,7 @@ use serde::{Deserialize, Deserializer};
use alacritty_config_derive::ConfigDeserialize;
/// Maximum scrollback amount configurable.
-const MAX_SCROLLBACK_LINES: u32 = 100_000;
+pub const MAX_SCROLLBACK_LINES: u32 = 100_000;
/// Struct for scrolling related settings.
#[derive(ConfigDeserialize, Copy, Clone, Debug, PartialEq, Eq)]
diff --git a/alacritty_terminal/src/selection.rs b/alacritty_terminal/src/selection.rs
index f00622d1..669db6a2 100644
--- a/alacritty_terminal/src/selection.rs
+++ b/alacritty_terminal/src/selection.rs
@@ -41,6 +41,7 @@ pub struct SelectionRange {
impl SelectionRange {
pub fn new(start: Point, end: Point, is_block: bool) -> Self {
+ assert!(start <= end);
Self { start, end, is_block }
}
}
diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs
index 3fa57f7f..7b0667e2 100644
--- a/alacritty_terminal/src/term/mod.rs
+++ b/alacritty_terminal/src/term/mod.rs
@@ -1,9 +1,8 @@
//! Exports the `Term` type which is a high-level API for the Grid.
-use std::cmp::{max, min};
use std::ops::{Index, IndexMut, Range};
use std::sync::Arc;
-use std::{mem, ptr, str};
+use std::{cmp, mem, ptr, slice, str};
use bitflags::bitflags;
use log::{debug, trace};
@@ -62,7 +61,7 @@ bitflags! {
const ALTERNATE_SCROLL = 0b0000_1000_0000_0000_0000;
const VI = 0b0001_0000_0000_0000_0000;
const URGENCY_HINTS = 0b0010_0000_0000_0000_0000;
- const ANY = std::u32::MAX;
+ const ANY = u32::MAX;
}
}
@@ -77,24 +76,24 @@ impl Default for TermMode {
/// Terminal size info.
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)]
-pub struct SizeInfo {
+pub struct SizeInfo<T = f32> {
/// Terminal window width.
- width: f32,
+ width: T,
/// Terminal window height.
- height: f32,
+ height: T,
/// Width of individual cell.
- cell_width: f32,
+ cell_width: T,
/// Height of individual cell.
- cell_height: f32,
+ cell_height: T,
/// Horizontal window padding.
- padding_x: f32,
+ padding_x: T,
/// Vertical window padding.
- padding_y: f32,
+ padding_y: T,
/// Number of lines in the viewport.
screen_lines: usize,
@@ -103,7 +102,54 @@ pub struct SizeInfo {
columns: usize,
}
-impl SizeInfo {
+impl From<SizeInfo<f32>> for SizeInfo<u32> {
+ fn from(size_info: SizeInfo<f32>) -> Self {
+ Self {
+ width: size_info.width as u32,
+ height: size_info.height as u32,
+ cell_width: size_info.cell_width as u32,
+ cell_height: size_info.cell_height as u32,
+ padding_x: size_info.padding_x as u32,
+ padding_y: size_info.padding_y as u32,
+ screen_lines: size_info.screen_lines,
+ columns: size_info.screen_lines,
+ }
+ }
+}
+
+impl<T: Clone + Copy> SizeInfo<T> {
+ #[inline]
+ pub fn width(&self) -> T {
+ self.width
+ }
+
+ #[inline]
+ pub fn height(&self) -> T {
+ self.height
+ }
+
+ #[inline]
+ pub fn cell_width(&self) -> T {
+ self.cell_width
+ }
+
+ #[inline]
+ pub fn cell_height(&self) -> T {
+ self.cell_height
+ }
+
+ #[inline]
+ pub fn padding_x(&self) -> T {
+ self.padding_x
+ }
+
+ #[inline]
+ pub fn padding_y(&self) -> T {
+ self.padding_y
+ }
+}
+
+impl SizeInfo<f32> {
#[allow(clippy::too_many_arguments)]
pub fn new(
width: f32,
@@ -120,10 +166,10 @@ impl SizeInfo {
}
let lines = (height - 2. * padding_y) / cell_height;
- let screen_lines = max(lines as usize, MIN_SCREEN_LINES);
+ let screen_lines = cmp::max(lines as usize, MIN_SCREEN_LINES);
let columns = (width - 2. * padding_x) / cell_width;
- let columns = max(columns as usize, MIN_COLUMNS);
+ let columns = cmp::max(columns as usize, MIN_COLUMNS);
SizeInfo {
width,
@@ -139,7 +185,7 @@ impl SizeInfo {
#[inline]
pub fn reserve_lines(&mut self, count: usize) {
- self.screen_lines = max(self.screen_lines.saturating_sub(count), MIN_SCREEN_LINES);
+ self.screen_lines = cmp::max(self.screen_lines.saturating_sub(count), MIN_SCREEN_LINES);
}
/// Check if coordinates are inside the terminal grid.
@@ -153,57 +199,176 @@ impl SizeInfo {
&& y > self.padding_y as usize
}
+ /// Calculate padding to spread it evenly around the terminal content.
#[inline]
- pub fn width(&self) -> f32 {
- self.width
+ fn dynamic_padding(padding: f32, dimension: f32, cell_dimension: f32) -> f32 {
+ padding + ((dimension - 2. * padding) % cell_dimension) / 2.
}
+}
+impl Dimensions for SizeInfo {
#[inline]
- pub fn height(&self) -> f32 {
- self.height
+ fn columns(&self) -> usize {
+ self.columns
}
#[inline]
- pub fn cell_width(&self) -> f32 {
- self.cell_width
+ fn screen_lines(&self) -> usize {
+ self.screen_lines
}
#[inline]
- pub fn cell_height(&self) -> f32 {
- self.cell_height
+ fn total_lines(&self) -> usize {
+ self.screen_lines()
}
+}
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct LineDamageBounds {
+ /// Damaged line number.
+ pub line: usize,
+
+ /// Leftmost damaged column.
+ pub left: usize,
+
+ /// Rightmost damaged column.
+ pub right: usize,
+}
+
+impl LineDamageBounds {
#[inline]
- pub fn padding_x(&self) -> f32 {
- self.padding_x
+ pub fn undamaged(line: usize, num_cols: usize) -> Self {
+ Self { line, left: num_cols, right: 0 }
}
#[inline]
- pub fn padding_y(&self) -> f32 {
- self.padding_y
+ pub fn reset(&mut self, num_cols: usize) {
+ *self = Self::undamaged(self.line, num_cols);
}
- /// Calculate padding to spread it evenly around the terminal content.
#[inline]
- fn dynamic_padding(padding: f32, dimension: f32, cell_dimension: f32) -> f32 {
- padding + ((dimension - 2. * padding) % cell_dimension) / 2.
+ pub fn expand(&mut self, left: usize, right: usize) {
+ self.left = cmp::min(self.left, left);
+ self.right = cmp::max(self.right, right);
+ }
+
+ #[inline]
+ pub fn is_damaged(&self) -> bool {
+ self.left <= self.right
}
}
-impl Dimensions for SizeInfo {
+/// Terminal damage information collected since the last [`Term::reset_damage`] call.
+#[derive(Debug)]
+pub enum TermDamage<'a> {
+ /// The entire terminal is damaged.
+ Full,
+
+ /// Iterator over damaged lines in the terminal.
+ Partial(TermDamageIterator<'a>),
+}
+
+/// Iterator over the terminal's damaged lines.
+#[derive(Clone, Debug)]
+pub struct TermDamageIterator<'a> {
+ line_damage: slice::Iter<'a, LineDamageBounds>,
+}
+
+impl<'a> TermDamageIterator<'a> {
+ fn new(line_damage: &'a [LineDamageBounds]) -> Self {
+ Self { line_damage: line_damage.iter() }
+ }
+}
+
+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()
+ }
+}
+
+/// State of the terminal damage.
+struct TermDamageState {
+ /// Hint whether terminal should be damaged entirely regardless of the actual damage changes.
+ is_fully_damaged: bool,
+
+ /// Information about damage on terminal lines.
+ lines: Vec<LineDamageBounds>,
+
+ /// Old terminal cursor point.
+ last_cursor: Point,
+
+ /// Old selection range.
+ last_selection: Option<SelectionRange>,
+}
+
+impl TermDamageState {
+ fn new(num_cols: usize, num_lines: usize) -> Self {
+ let lines =
+ (0..num_lines).map(|line| LineDamageBounds::undamaged(line, num_cols)).collect();
+
+ Self {
+ is_fully_damaged: true,
+ lines,
+ last_cursor: Default::default(),
+ last_selection: Default::default(),
+ }
+ }
+
#[inline]
- fn columns(&self) -> usize {
- self.columns
+ 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_selection = None;
+ self.is_fully_damaged = true;
+
+ self.lines.clear();
+ self.lines.reserve(num_lines);
+ for line in 0..num_lines {
+ self.lines.push(LineDamageBounds::undamaged(line, num_cols));
+ }
}
+ /// Damage point inside of the viewport.
#[inline]
- fn screen_lines(&self) -> usize {
- self.screen_lines
+ fn damage_point(&mut self, point: Point<usize>) {
+ self.damage_line(point.line, point.column.0 as usize, point.column.0 as usize);
}
+ /// Expand `line`'s damage to span at least `left` to `right` column.
#[inline]
- fn total_lines(&self) -> usize {
- self.screen_lines()
+ fn damage_line(&mut self, line: usize, left: usize, right: usize) {
+ 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 = cmp::min(cmp::max(selection.end.line.0 + display_offset, 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.lines.iter_mut().for_each(|line| line.reset(num_cols));
}
}
@@ -269,6 +434,9 @@ pub struct Term<T> {
/// Information about cell dimensions.
cell_width: usize,
cell_height: usize,
+
+ /// Information about damaged cells.
+ damage: TermDamageState,
}
impl<T> Term<T> {
@@ -277,6 +445,7 @@ impl<T> Term<T> {
where
T: EventListener,
{
+ let old_display_offset = self.grid.display_offset();
self.grid.scroll_display(scroll);
self.event_proxy.send_event(Event::MouseCursorDirty);
@@ -284,8 +453,13 @@ impl<T> Term<T> {
let viewport_start = -(self.grid.display_offset() as i32);
let viewport_end = viewport_start + self.bottommost_line().0;
let vi_cursor_line = &mut self.vi_mode_cursor.point.line.0;
- *vi_cursor_line = min(viewport_end, max(viewport_start, *vi_cursor_line));
+ *vi_cursor_line = cmp::min(viewport_end, cmp::max(viewport_start, *vi_cursor_line));
self.vi_mode_recompute_selection();
+
+ // Damage everything if display offset changed.
+ if old_display_offset != self.grid().display_offset() {
+ self.mark_fully_damaged();
+ }
}
pub fn new(config: &Config, size: SizeInfo, event_proxy: T) -> Term<T> {
@@ -300,6 +474,9 @@ impl<T> Term<T> {
let scroll_region = Line(0)..Line(grid.screen_lines() as i32);
+ // Initialize terminal damage, covering the entire terminal upon launch.
+ let damage = TermDamageState::new(num_cols, num_lines);
+
Term {
grid,
inactive_grid: alt,
@@ -320,7 +497,68 @@ impl<T> Term<T> {
selection: None,
cell_width: size.cell_width as usize,
cell_height: size.cell_height as usize,
+ damage,
+ }
+ }
+
+ #[must_use]
+ pub fn damage(&mut self, selection: Option<SelectionRange>) -> 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();
+ }
+
+ // Early return if the entire terminal is damaged.
+ if self.damage.is_fully_damaged {
+ self.damage.last_cursor = self.grid.cursor.point;
+ self.damage.last_selection = selection;
+ return TermDamage::Full;
+ }
+
+ // Add information about old cursor position and new one if they are not the same, so we
+ // cover everything that was produced by `Term::input`.
+ if self.damage.last_cursor != self.grid.cursor.point {
+ // Cursor cooridanates are always inside viewport even if you have `display_offset`.
+ let point =
+ Point::new(self.damage.last_cursor.line.0 as usize, self.damage.last_cursor.column);
+ self.damage.damage_point(point);
}
+
+ // Always damage current cursor.
+ self.damage_cursor();
+ self.damage.last_cursor = self.grid.cursor.point;
+
+ // Damage Vi cursor if it's present.
+ if self.mode.contains(TermMode::VI) {
+ self.damage_vi_cursor();
+ }
+
+ if self.damage.last_selection != selection {
+ let display_offset = self.grid().display_offset();
+ for selection in self.damage.last_selection.into_iter().chain(selection) {
+ self.damage.damage_selection(selection, display_offset, self.columns());
+ }
+ }
+ self.damage.last_selection = selection;
+
+ TermDamage::Partial(TermDamageIterator::new(&self.damage.lines))
+ }
+
+ /// Resets the terminal damage information.
+ pub fn reset_damage(&mut self) {
+ self.damage.reset(self.columns());
+ }
+
+ #[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);
}
pub fn update_config(&mut self, config: &Config)
@@ -343,6 +581,9 @@ impl<T> Term<T> {
} else {
self.grid.update_history(config.scrolling.history() as usize);
}
+
+ // Damage everything on config updates.
+ self.mark_fully_damaged();
}
/// Convert the active selection to a String.
@@ -398,7 +639,7 @@ impl<T> Term<T> {
let mut text = String::new();
let grid_line = &self.grid[line];
- let line_length = min(grid_line.line_length(), cols.end + 1);
+ let line_length = cmp::min(grid_line.line_length(), cols.end + 1);
// Include wide char when trailing spacer is selected.
if grid_line[cols.start].flags.contains(Flags::WIDE_CHAR_SPACER) {
@@ -496,8 +737,8 @@ impl<T> Term<T> {
// Move vi mode cursor with the content.
let history_size = self.history_size();
let mut delta = num_lines as i32 - old_lines as i32;
- let min_delta = min(0, num_lines as i32 - self.grid.cursor.point.line.0 - 1);
- delta = min(max(delta, min_delta), history_size as i32);
+ let min_delta = cmp::min(0, num_lines as i32 - self.grid.cursor.point.line.0 - 1);
+ delta = cmp::min(cmp::max(delta, min_delta), history_size as i32);
self.vi_mode_cursor.point.line += delta;
// Invalidate selection and tabs only when necessary.
@@ -519,11 +760,15 @@ impl<T> Term<T> {
let vi_point = self.vi_mode_cursor.point;
let viewport_top = Line(-(self.grid.display_offset() as i32));
let viewport_bottom = viewport_top + self.bottommost_line();
- self.vi_mode_cursor.point.line = max(min(vi_point.line, viewport_bottom), viewport_top);
- self.vi_mode_cursor.point.column = min(vi_point.column, self.last_column());
+ self.vi_mode_cursor.point.line =
+ cmp::max(cmp::min(vi_point.line, viewport_bottom), viewport_top);
+ self.vi_mode_cursor.point.column = cmp::min(vi_point.column, self.last_column());
// Reset scrolling region.
self.scroll_region = Line(0)..Line(self.screen_lines() as i32);
+
+ // Resize damage information.
+ self.damage.resize(num_cols, num_lines);
}
/// Active terminal modes.
@@ -548,6 +793,7 @@ impl<T> Term<T> {
mem::swap(&mut self.grid, &mut self.inactive_grid);
self.mode ^= TermMode::ALT_SCREEN;
self.selection = None;
+ self.mark_fully_damaged();
}
/// Scroll screen down.
@@ -558,8 +804,8 @@ impl<T> Term<T> {
fn scroll_down_relative(&mut self, origin: Line, mut lines: usize) {
trace!("Scrolling down relative: origin={}, lines={}", origin, lines);
- lines = min(lines, (self.scroll_region.end - self.scroll_region.start).0 as usize);
- lines = min(lines, (self.scroll_region.end - origin).0 as usize);
+ lines = cmp::min(lines, (self.scroll_region.end - self.scroll_region.start).0 as usize);
+ lines = cmp::min(lines, (self.scroll_region.end - origin).0 as usize);
let region = origin..self.scroll_region.end;
@@ -570,11 +816,12 @@ impl<T> Term<T> {
// Scroll vi mode cursor.
let line = &mut self.vi_mode_cursor.point.line;
if region.start <= *line && region.end > *line {
- *line = min(*line + lines, region.end - 1);
+ *line = cmp::min(*line + lines, region.end - 1);
}
// Scroll between origin and bottom
self.grid.scroll_down(&region, lines);
+ self.mark_fully_damaged();
}
/// Scroll screen up
@@ -585,7 +832,7 @@ impl<T> Term<T> {
fn scroll_up_relative(&mut self, origin: Line, mut lines: usize) {
trace!("Scrolling up relative: origin={}, lines={}", origin, lines);
- lines = min(lines, (self.scroll_region.end - self.scroll_region.start).0 as usize);
+ lines = cmp::min(lines, (self.scroll_region.end - self.scroll_region.start).0 as usize);
let region = origin..self.scroll_region.end;
@@ -599,8 +846,9 @@ impl<T> Term<T> {
let top = if region.start == 0 { viewport_top } else { region.start };
let line = &mut self.vi_mode_cursor.point.line;
if (top <= *line) && region.end > *line {
- *line = max(*line - lines, top);
+ *line = cmp::max(*line - lines, top);
}
+ self.mark_fully_damaged();
}
fn deccolm(&mut self)
@@ -613,6 +861,7 @@ impl<T> Term<T> {
// Clear grid.
self.grid.reset_region(..);
+ self.mark_fully_damaged();
}
#[inline]
@@ -659,7 +908,9 @@ impl<T> Term<T> {
}
// Move cursor.
+ self.damage_vi_cursor();
self.vi_mode_cursor = self.vi_mode_cursor.motion(self, motion);
+ self.damage_vi_cursor();
self.vi_mode_recompute_selection();
}
@@ -669,6 +920,7 @@ impl<T> Term<T> {
where
T: EventListener,
{
+ self.damage_vi_cursor();
// Move viewport to make point visible.
self.scroll_to_point(point);
@@ -676,6 +928,7 @@ impl<T> Term<T> {
self.vi_mode_cursor.point = point;
self.vi_mode_recompute_selection();
+ self.damage_vi_cursor();
}
/// Update the active selection to match the vi mode cursor position.
@@ -720,7 +973,7 @@ impl<T> Term<T> {
point.line += 1;
},
Direction::Right if flags.contains(Flags::WIDE_CHAR) => {
- point.column = min(point.column + 1, self.last_column());
+ point.column = cmp::min(point.column + 1, self.last_column());
},
Direction::Left if flags.intersects(Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER) => {
if flags.contains(Flags::WIDE_CHAR_SPACER) {
@@ -757,6 +1010,10 @@ impl<T> Term<T> {
}
}
+ pub fn colors(&self) -> &Colors {
+ &self.colors
+ }
+
/// Insert a linebreak at the current cursor position.
#[inline]
fn wrapline(&mut self)
@@ -774,11 +1031,13 @@ impl<T> Term<T> {
if self.grid.cursor.point.line + 1 >= self.scroll_region.end {
self.linefeed();
} else {
+ self.damage_cursor();
self.grid.cursor.point.line += 1;
}
self.grid.cursor.point.column = Column(0);
self.grid.cursor.input_needs_wrap = false;
+ self.damage_cursor();
}
/// Write `c` to the cell at the cursor position.
@@ -819,8 +1078,21 @@ impl<T> Term<T> {
cursor_cell.flags = flags;
}
- pub fn colors(&self) -> &Colors {
- &self.colors
+ #[inline]
+ fn damage_cursor(&mut self) {
+ // The normal cursor coordinates are always in viewport.
+ let point =
+ Point::new(self.grid.cursor.point.line.0 as usize, self.grid.cursor.point.column);
+ self.damage.damage_point(point);
+ }
+
+ /// Damage `Vi` mode cursor.
+ #[inline]
+ pub fn damage_vi_cursor(&mut self) {
+ let line = (self.grid.display_offset() as i32 + self.vi_mode_cursor.point.line.0)
+ .clamp(0, self.screen_lines() as i32 - 1) as usize;
+ let vi_point = Point::new(line, self.vi_mode_cursor.point.column);
+ self.damage.damage_point(vi_point);
}
}
@@ -933,6 +1205,8 @@ impl<T: EventListener> Handler for Term<T> {
cell.c = 'E';
}
}
+
+ self.mark_fully_damaged();
}
#[inline]
@@ -944,8 +1218,10 @@ impl<T: EventListener> Handler for Term<T> {
(Line(0), self.bottommost_line())
};
- self.grid.cursor.point.line = max(min(line + y_offset, max_y), Line(0));
- self.grid.cursor.point.column = min(col, self.last_column());
+ self.damage_cursor();
+ self.grid.cursor.point.line = cmp::max(cmp::min(line + y_offset, max_y), Line(0));
+ self.grid.cursor.point.column = cmp::min(col, self.last_column());
+ self.damage_cursor();
self.grid.cursor.input_needs_wrap = false;
}
@@ -967,13 +1243,15 @@ impl<T: EventListener> Handler for Term<T> {
let bg = cursor.template.bg;
// Ensure inserting within terminal bounds
- let count = min(count, self.columns() - cursor.point.column.0);
+ let count = cmp::min(count, self.columns() - cursor.point.column.0);
let source = cursor.point.column;
let destination = cursor.point.column.0 + count;
let num_cells = self.columns() - destination;
let line = cursor.point.line;
+ self.damage.damage_line(line.0 as usize, 0, self.columns() - 1);
+
let row = &mut self.grid[line][..];
for offset in (0..num_cells).rev() {
@@ -1002,16 +1280,24 @@ impl<T: EventListener> Handler for Term<T> {
#[inline]
fn move_forward(&mut self, cols: Column) {
trace!("Moving forward: {}", cols);
- let last_column = self.last_column();
- self.grid.cursor.point.column = min(self.grid.cursor.point.column + cols, last_column);
+ let last_column = cmp::min(self.grid.cursor.point.column + cols, self.last_column());
+
+ let cursor_line = self.grid.cursor.point.line.0 as usize;
+ self.damage.damage_line(cursor_line, self.grid.cursor.point.column.0, last_column.0);
+
+ self.grid.cursor.point.column = last_column;
self.grid.cursor.input_needs_wrap = false;
}
#[inline]
fn move_backward(&mut self, cols: Column) {
trace!("Moving backward: {}", cols);
- self.grid.cursor.point.column =
- Column(self.grid.cursor.point.column.saturating_sub(cols.0));
+ let column = self.grid.cursor.point.column.saturating_sub(cols.0);
+
+ let cursor_line = self.grid.cursor.point.line.0 as usize;
+ self.damage.damage_line(cursor_line, column, self.grid.cursor.point.column.0);
+
+ self.grid.cursor.point.column = Column(column);
self.grid.cursor.input_needs_wrap = false;
}
@@ -1100,8 +1386,11 @@ impl<T: EventListener> Handler for Term<T> {
trace!("Backspace");
if self.grid.cursor.point.column > Column(0) {
+ let line = self.grid.cursor.point.line.0 as usize;
+ let column = self.grid.cursor.point.column.0 as usize;
self.grid.cursor.point.column -= 1;
self.grid.cursor.input_needs_wrap = false;
+ self.damage.damage_line(line, column - 1, column);
}
}
@@ -1109,7 +1398,10 @@ impl<T: EventListener> Handler for Term<T> {
#[inline]
fn carriage_return(&mut self) {
trace!("Carriage return");
- self.grid.cursor.point.column = Column(0);
+ 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.grid.cursor.point.column = Column(new_col);
self.grid.cursor.input_needs_wrap = false;
}
@@ -1121,7 +1413,9 @@ impl<T: EventListener> Handler for Term<T> {
if next == self.scroll_region.end {
self.scroll_up(1);
} else if next < self.screen_lines() {
+ self.damage_cursor();
self.grid.cursor.point.line += 1;
+ self.damage_cursor();
}
}
@@ -1199,7 +1493,7 @@ impl<T: EventListener> Handler for Term<T> {
#[inline]
fn delete_lines(&mut self, lines: usize) {
let origin = self.grid.cursor.point.line;
- let lines = min(self.screen_lines() - origin.0 as usize, lines);
+ let lines = cmp::min(self.screen_lines() - origin.0 as usize, lines);
trace!("Deleting {} lines", lines);
@@ -1215,11 +1509,12 @@ impl<T: EventListener> Handler for Term<T> {
trace!("Erasing chars: count={}, col={}", count, cursor.point.column);
let start = cursor.point.column;
- let end = min(start + count, Column(self.columns()));
+ let end = cmp::min(start + count, Column(self.columns()));
// Cleared cells have current background color set.
let bg = self.grid.cursor.template.bg;
let line = cursor.point.line;
+ self.damage.damage_line(line.0 as usize, start.0, end.0);
let row = &mut self.grid[line];
for cell in &mut row[start..end] {
*cell = bg.into();
@@ -1233,13 +1528,14 @@ impl<T: EventListener> Handler for Term<T> {
let bg = cursor.template.bg;
// Ensure deleting within terminal bounds.
- let count = min(count, columns);
+ let count = cmp::min(count, columns);
let start = cursor.point.column.0;
- let end = min(start + count, columns - 1);
+ let end = cmp::min(start + count, columns - 1);
let num_cells = columns - end;
let line = cursor.point.line;
+ self.damage.damage_line(line.0 as usize, 0, self.columns() - 1);
let row = &mut self.grid[line][..];
for offset in 0..num_cells {
@@ -1257,7 +1553,9 @@ impl<T: EventListener> Handler for Term<T> {
#[inline]
fn move_backward_tabs(&mut self, count: u16) {
trace!("Moving backward {} tabs", count);
+ self.damage_cursor();
+ let old_col = self.grid.cursor.point.column.0;
for _ in 0..count {
let mut col = self.grid.cursor.point.column;
for i in (0..(col.0)).rev() {
@@ -1268,6 +1566,9 @@ impl<T: EventListener> Handler for Term<T> {
}
self.grid.cursor.point.column = col;
}
+
+ let line = self.grid.cursor.point.line.0 as usize;
+ self.damage_line(line, self.grid.cursor.point.column.0, old_col);
}
#[inline]
@@ -1286,7 +1587,9 @@ impl<T: EventListener> Handler for Term<T> {
fn restore_cursor_position(&mut self) {
trace!("Restoring cursor position");
+ self.damage_cursor();
self.grid.cursor = self.grid.saved_cursor.clone();
+ self.damage_cursor();
}
#[inline]
@@ -1295,26 +1598,19 @@ impl<T: EventListener> Handler for Term<T> {
let cursor = &self.grid.cursor;
let bg = cursor.template.bg;
-
let point = cursor.point;
- let row = &mut self.grid[point.line];
- match mode {
- ansi::LineClearMode::Right => {
- for cell in &mut row[point.column..] {
- *cell = bg.into();
- }
- },
- ansi::LineClearMode::Left => {
- for cell in &mut row[..=point.column] {
- *cell = bg.into();
- }
- },
- ansi::LineClearMode::All => {
- for cell in &mut row[..] {
- *cell = bg.into();
- }
- },
+ let (left, right) = match mode {
+ ansi::LineClearMode::Right => (point.column, Column(self.columns())),
+ ansi::LineClearMode::Left => (Column(0), point.column + 1),
+ ansi::LineClearMode::All => (Column(0), Column(self.columns())),
+ };
+
+ self.damage.damage_line(point.line.0 as usize, left.0, right.0 - 1);
+
+ let row = &mut self.grid[point.line];
+ for cell in &mut row[left..right] {
+ *cell = bg.into();
}
let range = self.grid.cursor.point.line..=self.grid.cursor.point.line;
@@ -1406,7 +1702,7 @@ impl<T: EventListener> Handler for Term<T> {
}
// Clear up to the current column in the current line.
- let end = min(cursor.column + 1, Column(self.columns()));
+ let end = cmp::min(cursor.column + 1, Column(self.columns()));
for cell in &mut self.grid[cursor.line][..end] {
*cell = bg.into();
}
@@ -1455,6 +1751,8 @@ impl<T: EventListener> Handler for Term<T> {
// We have no history to clear.
ansi::ClearMode::Saved => (),
}
+
+ self.mark_fully_damaged();
}
#[inline]
@@ -1491,6 +1789,7 @@ impl<T: EventListener> Handler for Term<T> {
self.mode.insert(TermMode::default());
self.event_proxy.send_event(Event::CursorBlinkingChange);
+ self.mark_fully_damaged();
}
#[inline]
@@ -1500,7 +1799,9 @@ impl<T: EventListener> Handler for Term<T> {
if self.grid.cursor.point.line == self.scroll_region.start {
self.scroll_down(1);
} else {
- self.grid.cursor.point.line = max(self.grid.cursor.point.line - 1, Line(0));
+ self.damage_cursor();
+ self.grid.cursor.point.line = cmp::max(self.grid.cursor.point.line - 1, Line(0));
+ self.damage_cursor();
}
}
@@ -1632,7 +1933,10 @@ impl<T: EventListener> Handler for Term<T> {
ansi::Mode::LineFeedNewLine => self.mode.remove(TermMode::LINE_FEED_NEW_LINE),
ansi::Mode::Origin => self.mode.remove(TermMode::ORIGIN),
ansi::Mode::ColumnMode => self.deccolm(),
- ansi::Mode::Insert => self.mode.remove(TermMode::INSERT),
+ ansi::Mode::Insert => {
+ self.mode.remove(TermMode::INSERT);
+ self.mark_fully_damaged();
+ },
ansi::Mode::BlinkingCursor => {
let style = self.cursor_style.get_or_insert(self.default_cursor_style);
style.blinking = false;
@@ -1661,8 +1965,8 @@ impl<T: EventListener> Handler for Term<T> {
trace!("Setting scrolling region: ({};{})", start, end);
let screen_lines = Line(self.screen_lines() as i32);
- self.scroll_region.start = min(start, screen_lines);
- self.scroll_region.end = min(end, screen_lines);
+ self.scroll_region.start = cmp::min(start, screen_lines);
+ self.scroll_region.end = cmp::min(end, screen_lines);
self.goto(Line(0), Column(0));
}
@@ -1833,7 +2137,7 @@ impl IndexMut<Column> for TabStops {
}
/// Terminal cursor rendering information.
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, PartialEq, Eq)]
pub struct RenderableCursor {
pub shape: CursorShape,
pub point: Point,
@@ -2430,6 +2734,285 @@ mod tests {
}
#[test]
+ fn damage_public_usage() {
+ let size = SizeInfo::new(10.0, 10.0, 1.0, 1.0, 0.0, 0.0, false);
+ let mut term = Term::new(&Config::default(), size, ());
+ // Reset terminal for partial damage tests since it's initialized as fully damaged.
+ term.reset_damage();
+
+ // Test that we damage input form [`Term::input`].
+
+ let left = term.grid.cursor.point.column.0;
+ term.input('d');
+ term.input('a');
+ term.input('m');
+ term.input('a');
+ term.input('g');
+ term.input('e');
+ let right = term.grid.cursor.point.column.0;
+
+ let mut damaged_lines = match term.damage(None) {
+ TermDamage::Full => panic!("Expected partial damage, however got Full"),
+ TermDamage::Partial(damaged_lines) => damaged_lines,
+ };
+ assert_eq!(damaged_lines.next(), Some(LineDamageBounds { line: 0, left, right }));
+ 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.
+
+ let mut damaged_lines = match term.damage(None) {
+ TermDamage::Full => panic!("Expected partial damage, however got Full"),
+ TermDamage::Partial(damaged_lines) => damaged_lines,
+ };
+ // 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.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 as usize;
+ let right = left;
+ let mut damaged_lines = match term.damage(None) {
+ 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);
+ }
+
+ #[test]
+ fn damage_cursor_movements() {
+ let size = SizeInfo::new(10.0, 10.0, 1.0, 1.0, 0.0, 0.0, false);
+ let mut term = Term::new(&Config::default(), size, ());
+ let num_cols = term.columns();
+ // Reset terminal for partial damage tests since it's initialized as fully damaged.
+ term.reset_damage();
+
+ term.goto(Line(1), Column(1));
+
+ // NOTE While we can use `[Term::damage]` to access terminal damage information, in the
+ // following tests we will be accessing `term.damage.lines` directly to avoid adding extra
+ // damage information (like cursor and Vi cursor), which we're not testing.
+
+ assert_eq!(term.damage.lines[0], LineDamageBounds { line: 0, left: 0, right: 0 });
+ assert_eq!(term.damage.lines[1], LineDamageBounds { line: 1, left: 1, right: 1 });
+ term.damage.reset(num_cols);
+
+ term.move_forward(Column(3));
+ assert_eq!(term.damage.lines[1], LineDamageBounds { line: 1, left: 1, right: 4 });
+ term.damage.reset(num_cols);
+
+ term.move_backward(Column(8));
+ assert_eq!(term.damage.lines[1], LineDamageBounds { line: 1, left: 0, right: 4 });
+ term.goto(Line(5), Column(5));
+ term.damage.reset(num_cols);
+
+ term.backspace();
+ term.backspace();
+ assert_eq!(term.damage.lines[5], LineDamageBounds { line: 5, left: 3, right: 5 });
+ term.damage.reset(num_cols);
+
+ term.move_up(1);
+ assert_eq!(term.damage.lines[5], LineDamageBounds { line: 5, left: 3, right: 3 });
+ assert_eq!(term.damage.lines[4], LineDamageBounds { line: 4, left: 3, right: 3 });
+ term.damage.reset(num_cols);
+
+ term.move_down(1);
+ term.move_down(1);
+ assert_eq!(term.damage.lines[4], LineDamageBounds { line: 4, left: 3, right: 3 });
+ assert_eq!(term.damage.lines[5], LineDamageBounds { line: 5, left: 3, right: 3 });
+ assert_eq!(term.damage.lines[6], LineDamageBounds { line: 6, left: 3, right: 3 });
+ term.damage.reset(num_cols);
+
+ term.wrapline();
+ assert_eq!(term.damage.lines[6], LineDamageBounds { line: 6, left: 3, right: 3 });
+ assert_eq!(term.damage.lines[7], LineDamageBounds { line: 7, left: 0, right: 0 });
+ term.move_forward(Column(3));
+ term.move_up(1);
+ term.damage.reset(num_cols);
+
+ term.linefeed();
+ assert_eq!(term.damage.lines[6], LineDamageBounds { line: 6, left: 3, right: 3 });
+ assert_eq!(term.damage.lines[7], LineDamageBounds { line: 7, left: 3, right: 3 });
+ term.damage.reset(num_cols);
+
+ term.carriage_return();
+ assert_eq!(term.damage.lines[7], LineDamageBounds { line: 7, left: 0, right: 3 });
+ term.damage.reset(num_cols);
+
+ term.erase_chars(Column(5));
+ assert_eq!(term.damage.lines[7], LineDamageBounds { line: 7, left: 0, right: 5 });
+ term.damage.reset(num_cols);
+
+ term.delete_chars(3);
+ let right = term.columns() - 1;
+ assert_eq!(term.damage.lines[7], LineDamageBounds { line: 7, left: 0, right });
+ term.move_forward(Column(term.columns()));
+ term.damage.reset(num_cols);
+
+ term.move_backward_tabs(1);
+ assert_eq!(term.damage.lines[7], LineDamageBounds { line: 7, left: 8, right });
+ term.save_cursor_position();
+ term.goto(Line(1), Column(1));
+ term.damage.reset(num_cols);
+
+ term.restore_cursor_position();
+ assert_eq!(term.damage.lines[1], LineDamageBounds { line: 1, left: 1, right: 1 });
+ assert_eq!(term.damage.lines[7], LineDamageBounds { line: 7, left: 8, right: 8 });
+ term.damage.reset(num_cols);
+
+ term.clear_line(ansi::LineClearMode::All);
+ assert_eq!(term.damage.lines[7], LineDamageBounds { line: 7, left: 0, right });
+ term.damage.reset(num_cols);
+
+ term.clear_line(ansi::LineClearMode::Left);
+ assert_eq!(term.damage.lines[7], LineDamageBounds { line: 7, left: 0, right: 8 });
+ term.damage.reset(num_cols);
+
+ term.clear_line(ansi::LineClearMode::Right);
+ assert_eq!(term.damage.lines[7], LineDamageBounds { line: 7, left: 8, right });
+ term.damage.reset(num_cols);
+
+ term.reverse_index();
+ assert_eq!(term.damage.lines[7], LineDamageBounds { line: 7, left: 8, right: 8 });
+ assert_eq!(term.damage.lines[6], LineDamageBounds { line: 6, left: 8, right: 8 });
+ }
+
+ #[test]
+ fn damage_vi_movements() {
+ let size = SizeInfo::new(10.0, 10.0, 1.0, 1.0, 0.0, 0.0, false);
+ let mut term = Term::new(&Config::default(), size, ());
+ let num_cols = term.columns();
+ // Reset terminal for partial damage tests since it's initialized as fully damaged.
+ term.reset_damage();
+
+ // Enable Vi mode.
+ term.toggle_vi_mode();
+
+ // NOTE While we can use `[Term::damage]` to access terminal damage information, in the
+ // following tests we will be accessing `term.damage.lines` directly to avoid adding extra
+ // damage information (like cursor and Vi cursor), which we're not testing.
+
+ term.vi_goto_point(Point::new(Line(5), Column(5)));
+ assert_eq!(term.damage.lines[0], LineDamageBounds { line: 0, left: 0, right: 0 });
+ assert_eq!(term.damage.lines[5], LineDamageBounds { line: 5, left: 5, right: 5 });
+ term.damage.reset(num_cols);
+
+ term.vi_motion(ViMotion::Up);
+ term.vi_motion(ViMotion::Right);
+ term.vi_motion(ViMotion::Up);
+ term.vi_motion(ViMotion::Left);
+ assert_eq!(term.damage.lines[3], LineDamageBounds { line: 3, left: 5, right: 6 });
+ assert_eq!(term.damage.lines[4], LineDamageBounds { line: 4, left: 5, right: 6 });
+ assert_eq!(term.damage.lines[5], LineDamageBounds { line: 5, left: 5, right: 5 });
+
+ // Ensure that we haven't damaged entire terminal during the test.
+ assert!(!term.damage.is_fully_damaged);
+ }
+
+ #[test]
+ fn full_damage() {
+ let size = SizeInfo::new(100.0, 10.0, 1.0, 1.0, 0.0, 0.0, false);
+ let mut term = Term::new(&Config::default(), size, ());
+
+ assert!(term.damage.is_fully_damaged);
+ for _ in 0..20 {
+ term.newline();
+ }
+ term.reset_damage();
+
+ term.clear_screen(ansi::ClearMode::Above);
+ assert!(term.damage.is_fully_damaged);
+ term.reset_damage();
+
+ term.scroll_display(Scroll::Top);
+ assert!(term.damage.is_fully_damaged);
+ 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);
+ term.reset_damage();
+
+ term.update_config(&Config::default());
+ assert!(term.damage.is_fully_damaged);
+ term.reset_damage();
+
+ term.scroll_down_relative(Line(5), 2);
+ assert!(term.damage.is_fully_damaged);
+ term.reset_damage();
+
+ term.scroll_up_relative(Line(3), 2);
+ assert!(term.damage.is_fully_damaged);
+ term.reset_damage();
+
+ term.deccolm();
+ assert!(term.damage.is_fully_damaged);
+ term.reset_damage();
+
+ term.decaln();
+ assert!(term.damage.is_fully_damaged);
+ term.reset_damage();
+
+ term.set_mode(ansi::Mode::Insert);
+ // Just setting `Insert` mode shouldn't mark terminal as damaged.
+ assert!(!term.damage.is_fully_damaged);
+ term.reset_damage();
+
+ // However requesting terminal damage should mark terminal as fully damaged in `Insert`
+ // mode.
+ let _ = term.damage(None);
+ assert!(term.damage.is_fully_damaged);
+ term.reset_damage();
+
+ term.unset_mode(ansi::Mode::Insert);
+ assert!(term.damage.is_fully_damaged);
+ 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);
+ term.reset_damage();
+
+ let size = SizeInfo::new(10.0, 10.0, 1.0, 1.0, 0.0, 0.0, false);
+ term.resize(size);
+ assert!(term.damage.is_fully_damaged);
+ }
+
+ #[test]
fn window_title() {
let size = SizeInfo::new(21.0, 51.0, 3.0, 3.0, 0.0, 0.0, false);
let mut term = Term::new(&Config::default(), size, ());
diff --git a/alacritty_terminal/src/vi_mode.rs b/alacritty_terminal/src/vi_mode.rs
index de5c61b5..8a77b760 100644
--- a/alacritty_terminal/src/vi_mode.rs
+++ b/alacritty_terminal/src/vi_mode.rs
@@ -52,7 +52,7 @@ pub enum ViMotion {
}
/// Cursor tracking vi mode position.
-#[derive(Default, Copy, Clone)]
+#[derive(Default, Copy, Clone, PartialEq, Eq)]
pub struct ViModeCursor {
pub point: Point,
}