#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RowDelta {
pub start: usize,
pub replaced: usize,
pub len: usize,
pub src_shift: i64,
}
impl RowDelta {
pub fn is_empty(&self) -> bool {
self.replaced == 0 && self.len == 0
}
}
pub fn row_delta<R>(
old: &[R],
new: &[R],
same: impl Fn(&R, &R, i64) -> bool,
shift_between: impl Fn(&R, &R) -> Option<i64>,
) -> RowDelta {
let shortest = old.len().min(new.len());
let mut start = 0;
while start < shortest && same(&old[start], &new[start], 0) {
start += 1;
}
let mut suffix = 0;
let mut shift: Option<i64> = None;
while suffix < shortest - start {
let a = &old[old.len() - 1 - suffix];
let b = &new[new.len() - 1 - suffix];
let candidate = shift.or_else(|| shift_between(a, b));
if !same(a, b, candidate.unwrap_or(0)) {
break;
}
shift = candidate;
suffix += 1;
}
RowDelta {
start,
replaced: old.len() - start - suffix,
len: new.len() - start - suffix,
src_shift: shift.unwrap_or(0),
}
}
pub fn apply_row_delta<R: Clone>(
rows: &mut Vec<R>,
delta: RowDelta,
span: &[R],
shift: impl Fn(&mut R, i64),
) {
let end = delta.start + delta.replaced;
rows.splice(delta.start..end, span.iter().cloned());
if delta.src_shift != 0 {
for row in &mut rows[delta.start + delta.len..] {
shift(row, delta.src_shift);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone, Debug, PartialEq)]
struct R(&'static str, Option<i64>);
fn same(a: &R, b: &R, shift: i64) -> bool {
a.0 == b.0 && a.1.map(|s| s + shift) == b.1
}
fn between(a: &R, b: &R) -> Option<i64> {
Some(b.1? - a.1?)
}
fn shift(r: &mut R, by: i64) {
if let Some(s) = &mut r.1 {
*s += by;
}
}
fn delta(old: &[R], new: &[R]) -> RowDelta {
let d = row_delta(old, new, same, between);
let mut applied = old.to_vec();
apply_row_delta(&mut applied, d, &new[d.start..d.start + d.len], shift);
assert_eq!(applied, new, "{d:?}");
d
}
#[test]
fn the_same_rows_are_an_empty_span() {
let rows = [R("a", Some(0)), R("b", Some(2))];
let d = delta(&rows, &rows);
assert!(d.is_empty());
assert_eq!(d.start, 2);
assert_eq!(d.src_shift, 0);
}
#[test]
fn a_keystroke_is_one_row_and_a_shift_for_the_rest() {
let old = [R("a", Some(0)), R("b", Some(2)), R("c", Some(4))];
let new = [R("a", Some(0)), R("bx", Some(2)), R("c", Some(5))];
let d = delta(&old, &new);
assert_eq!(
d,
RowDelta {
start: 1,
replaced: 1,
len: 1,
src_shift: 1
}
);
}
#[test]
fn a_row_that_does_not_stand_at_the_shift_is_in_the_span() {
let old = [R("a", Some(0)), R("b", Some(2)), R("c", Some(4))];
let new = [R("a", Some(0)), R("b", Some(3)), R("c", Some(6))];
let d = delta(&old, &new);
assert_eq!(d.start, 1);
assert_eq!(d.replaced, 1);
assert_eq!(d.src_shift, 2);
}
#[test]
fn a_blank_row_at_the_end_leaves_the_shift_to_the_row_that_carries_one() {
let old = [R("a", Some(0)), R("b", Some(2)), R("", None)];
let new = [R("ab", Some(0)), R("b", Some(3)), R("", None)];
let d = delta(&old, &new);
assert_eq!((d.start, d.replaced, d.len, d.src_shift), (0, 1, 1, 1));
}
#[test]
fn an_inserted_row_and_a_removed_one() {
let a = R("a", Some(0));
let b = R("b", Some(2));
let c = R("c", Some(4));
let d = delta(
&[a.clone(), c.clone()],
&[a.clone(), b.clone(), R("c", Some(6))],
);
assert_eq!((d.start, d.replaced, d.len, d.src_shift), (1, 0, 1, 2));
let d = delta(&[a.clone(), b, c], &[a, R("c", Some(2))]);
assert_eq!((d.start, d.replaced, d.len, d.src_shift), (1, 1, 0, -2));
}
#[test]
fn everything_changed_is_the_whole_of_both() {
let d = delta(&[R("a", Some(0))], &[R("x", Some(0)), R("y", Some(2))]);
assert_eq!((d.start, d.replaced, d.len), (0, 1, 2));
let d = delta(&[], &[R("x", Some(0))]);
assert_eq!((d.start, d.replaced, d.len), (0, 0, 1));
let d = delta(&[R("x", Some(0))], &[]);
assert_eq!((d.start, d.replaced, d.len), (0, 1, 0));
}
}