justerm_core/damage.rs
1//! Damage tracking — what changed since the last reset, as line + column spans.
2//! See ADR-0003 for the model (incremental bounds, ack-gated reset).
3
4/// The damaged column span of a single line.
5#[derive(Clone, Copy, PartialEq, Eq, Debug)]
6pub struct LineDamage {
7 pub line: usize,
8 pub left: usize,
9 pub right: usize,
10}
11
12/// A first-class scroll: rows `[top..=bottom]` shifted by `count` lines
13/// (positive = up, negative = down). The renderer moves the rows instead of
14/// redrawing them. Recorded by the engine — which executes the scroll — rather
15/// than diff-detected (ADR-0003).
16///
17/// **A reported `count` never exceeds `bottom - top + 1`** — see
18/// [`crate::Engine::scroll_delta`], which caps it. `count` is `isize` here and
19/// `i16` on the wire, so an uncapped accumulation overflowed the field and
20/// reversed the shift's direction (#661); the bound is also the point past which
21/// the value stops meaning anything, since every source row is then outside the
22/// region. The cap is applied when the op is *read*, not while it accumulates, so
23/// a region that scrolls far and returns still reports its true small net.
24#[derive(Clone, Copy, PartialEq, Eq, Debug)]
25pub struct ScrollOp {
26 pub top: usize,
27 pub bottom: usize,
28 pub count: isize,
29}
30
31/// What changed since the last `reset_damage()`.
32///
33/// **Deliberately exhaustive (#843).** A consumer that ignored a new damage kind
34/// would render stale content with no error anywhere, so a new member is one the
35/// compiler must make it look at. Left exhaustive on purpose, not by omission.
36#[derive(Clone, PartialEq, Eq, Debug)]
37pub enum TermDamage {
38 /// The whole screen must be redrawn (flood / resize / alt-screen clear).
39 Full,
40 /// Only these lines changed, each carrying its damaged column span.
41 Partial(Vec<LineDamage>),
42}
43
44/// Per-line damage bounds. "Undamaged" is encoded as `left > right`, so an
45/// untouched line never reports as damaged and the first `expand` sets a real
46/// span. (Mirrors Alacritty's `LineDamageBounds`.)
47#[derive(Clone, Copy)]
48pub(crate) struct LineBounds {
49 left: usize,
50 right: usize,
51 cols: usize,
52}
53
54impl LineBounds {
55 pub(crate) fn undamaged(cols: usize) -> Self {
56 LineBounds {
57 left: cols,
58 right: 0,
59 cols,
60 }
61 }
62
63 /// A line damaged across its full width (a newly exposed scroll line).
64 pub(crate) fn fully_damaged(cols: usize) -> Self {
65 LineBounds {
66 left: 0,
67 right: cols.saturating_sub(1),
68 cols,
69 }
70 }
71
72 /// Widen the span to include columns `[left, right]`.
73 pub(crate) fn expand(&mut self, left: usize, right: usize) {
74 self.left = self.left.min(left);
75 self.right = self.right.max(right);
76 }
77
78 pub(crate) fn is_damaged(&self) -> bool {
79 self.left <= self.right
80 }
81
82 pub(crate) fn reset(&mut self) {
83 self.left = self.cols;
84 self.right = 0;
85 }
86
87 pub(crate) fn span(&self) -> (usize, usize) {
88 (self.left, self.right)
89 }
90}