use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default, Serialize, Deserialize, JsonSchema)]
pub struct LineDelta {
pub start: u32,
pub old_lines: u32,
pub new_lines: u32,
}
impl LineDelta {
#[must_use]
pub fn shifted_below(self) -> bool {
self.old_lines != self.new_lines
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default, Serialize, Deserialize, JsonSchema)]
pub enum Damage {
#[default]
None,
Lines { from: u32, to: u32 },
Viewport,
Full,
}
impl Damage {
#[must_use]
pub fn line(n: u32) -> Self {
Damage::Lines { from: n, to: n }
}
#[must_use]
pub fn span(a: u32, b: u32) -> Self {
Damage::Lines {
from: a.min(b),
to: a.max(b),
}
}
#[must_use]
pub fn from_delta(d: LineDelta) -> Self {
if d.shifted_below() {
Damage::Lines {
from: d.start,
to: u32::MAX,
}
} else {
Damage::line(d.start)
}
}
#[must_use]
pub fn join(self, other: Damage) -> Damage {
use Damage::{Full, Lines, None, Viewport};
match (self, other) {
(Full, _) | (_, Full) => Full,
(Viewport, _) | (_, Viewport) => Viewport,
(None, x) | (x, None) => x,
(
Lines {
from: f0,
to: t0,
},
Lines {
from: f1,
to: t1,
},
) => Lines {
from: f0.min(f1),
to: t0.max(t1),
},
}
}
#[must_use]
pub fn is_none(self) -> bool {
matches!(self, Damage::None)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn join_is_widening_and_full_is_top() {
assert_eq!(Damage::None.join(Damage::line(3)), Damage::line(3));
assert_eq!(Damage::Full.join(Damage::Viewport), Damage::Full);
assert_eq!(Damage::Viewport.join(Damage::line(3)), Damage::Viewport);
assert_eq!(
Damage::span(2, 4).join(Damage::span(3, 9)),
Damage::Lines { from: 2, to: 9 },
"two line spans union",
);
}
#[test]
fn join_is_idempotent_and_commutative() {
let d = Damage::span(1, 5);
assert_eq!(d.join(d), d, "idempotent");
assert_eq!(
Damage::Viewport.join(d),
d.join(Damage::Viewport),
"commutative",
);
}
#[test]
fn line_count_change_runs_to_end() {
let d = LineDelta {
start: 10,
old_lines: 40,
new_lines: 41,
};
assert!(d.shifted_below());
assert_eq!(
Damage::from_delta(d),
Damage::Lines {
from: 10,
to: u32::MAX,
},
);
}
#[test]
fn in_place_edit_is_local() {
let d = LineDelta {
start: 10,
old_lines: 40,
new_lines: 40,
};
assert!(!d.shifted_below());
assert_eq!(Damage::from_delta(d), Damage::line(10));
}
}