Skip to main content

leaf_core/
frame.rs

1//! What one frame's rows are as a change from the frame before's.
2//!
3//! A binding answers every gesture with a frame — the rows to paint, and
4//! where the caret is among them — and lifting a whole frame across a
5//! boundary (UniFFI records into Swift, serde into JS objects) costs the
6//! document, not the gesture: on a long document the lift is most of a
7//! keystroke and all of a click. Both renderers already keep the rows a frame
8//! did not change, so what is left to make cheap is the crossing, and the way
9//! to do that is to cross only the rows that changed.
10//!
11//! A binding does not need to know *why* rows changed to know *which* did.
12//! It keeps the rows it last handed out and takes the common prefix and
13//! suffix against the ones it is about to: comparing a few hundred rows in
14//! memory is microseconds, and what falls between is exactly the span a
15//! renderer diffing whole frames would have found for itself. The one wrinkle
16//! is that a row carries the source offset each of its runs came from, and a
17//! keystroke moves every offset after it — so read by `==`, every row below
18//! an edit is a different row, and the "span" is half the document. A row
19//! after the edit is the same row *moved*, and the suffix is matched on that
20//! footing: equal in everything, with its offsets shifted by one constant,
21//! which the frame then carries so the renderer moves its own copies the same
22//! way. The shift is read off the rows and checked on every row it is claimed
23//! for, never assumed from the edit, so a frame reproduces the rows exactly
24//! or names the row as changed.
25//!
26//! This module is the algorithm; each binding supplies what "the same row,
27//! shifted" means for its own row type, and the fields the frame carries the
28//! answer in are documented on the binding's `DocView`.
29
30/// Where two frames' rows differ: the span of the old rows a span of the new
31/// ones replaces, and the offset the rows after it moved by. See
32/// [`row_delta`].
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub struct RowDelta {
35    /// The first row that differs — the length of the common prefix, which is
36    /// where the new span begins in both the old rows and the new.
37    pub start: usize,
38    /// How many of the old rows, from `start`, the new span replaces.
39    pub replaced: usize,
40    /// How many new rows stand in their place.
41    pub len: usize,
42    /// The byte offset every source offset in a row after the span moved by:
43    /// the rows after the span are the old ones with this added to each
44    /// offset they carry. Zero when nothing moved, and when no row after the
45    /// span carries an offset to move.
46    pub src_shift: i64,
47}
48
49impl RowDelta {
50    /// Whether the two frames' rows are the same rows.
51    pub fn is_empty(&self) -> bool {
52        self.replaced == 0 && self.len == 0
53    }
54}
55
56/// The change from `old` to `new`, as the span outside their common prefix
57/// and suffix.
58///
59/// `same(a, b, shift)` says whether `b` is `a` with every source offset it
60/// carries moved by `shift` — `shift == 0` is plain equality. `shift_between(a,
61/// b)` reads the shift `b`'s offsets stand at from `a`'s off any one offset the
62/// two share, or `None` when `a` carries none (a blank row), which leaves the
63/// shift to be read off a row that does.
64///
65/// The prefix is matched at shift zero: nothing before an edit moves. The
66/// suffix is matched from the end, at whichever shift the first offset-bearing
67/// row from the end stands at, and a row that does not stand at it — or that
68/// differs in anything else — ends the suffix and is part of the span. So the
69/// rows the delta reports as kept are exactly reproducible from the old rows
70/// and `src_shift`, whatever the edit was.
71pub fn row_delta<R>(
72    old: &[R],
73    new: &[R],
74    same: impl Fn(&R, &R, i64) -> bool,
75    shift_between: impl Fn(&R, &R) -> Option<i64>,
76) -> RowDelta {
77    let shortest = old.len().min(new.len());
78    let mut start = 0;
79    while start < shortest && same(&old[start], &new[start], 0) {
80        start += 1;
81    }
82    let mut suffix = 0;
83    let mut shift: Option<i64> = None;
84    while suffix < shortest - start {
85        let a = &old[old.len() - 1 - suffix];
86        let b = &new[new.len() - 1 - suffix];
87        let candidate = shift.or_else(|| shift_between(a, b));
88        if !same(a, b, candidate.unwrap_or(0)) {
89            break;
90        }
91        shift = candidate;
92        suffix += 1;
93    }
94    RowDelta {
95        start,
96        replaced: old.len() - start - suffix,
97        len: new.len() - start - suffix,
98        src_shift: shift.unwrap_or(0),
99    }
100}
101
102/// Apply `delta` — with the rows `span` it stands for — to `rows`, the frame
103/// before's, so they become the frame's: the reference for what a renderer
104/// does with the fields, and what the bindings' tests apply a sequence of
105/// frames with. `shift(row, by)` moves every offset the row carries.
106pub fn apply_row_delta<R: Clone>(
107    rows: &mut Vec<R>,
108    delta: RowDelta,
109    span: &[R],
110    shift: impl Fn(&mut R, i64),
111) {
112    let end = delta.start + delta.replaced;
113    rows.splice(delta.start..end, span.iter().cloned());
114    if delta.src_shift != 0 {
115        for row in &mut rows[delta.start + delta.len..] {
116            shift(row, delta.src_shift);
117        }
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    /// A row for the tests: its text, and the offset it came from.
126    #[derive(Clone, Debug, PartialEq)]
127    struct R(&'static str, Option<i64>);
128
129    fn same(a: &R, b: &R, shift: i64) -> bool {
130        a.0 == b.0 && a.1.map(|s| s + shift) == b.1
131    }
132    fn between(a: &R, b: &R) -> Option<i64> {
133        Some(b.1? - a.1?)
134    }
135    fn shift(r: &mut R, by: i64) {
136        if let Some(s) = &mut r.1 {
137            *s += by;
138        }
139    }
140    fn delta(old: &[R], new: &[R]) -> RowDelta {
141        let d = row_delta(old, new, same, between);
142        // Whatever the answer, applying it reproduces the new rows.
143        let mut applied = old.to_vec();
144        apply_row_delta(&mut applied, d, &new[d.start..d.start + d.len], shift);
145        assert_eq!(applied, new, "{d:?}");
146        d
147    }
148
149    #[test]
150    fn the_same_rows_are_an_empty_span() {
151        let rows = [R("a", Some(0)), R("b", Some(2))];
152        let d = delta(&rows, &rows);
153        assert!(d.is_empty());
154        assert_eq!(d.start, 2);
155        assert_eq!(d.src_shift, 0);
156    }
157
158    #[test]
159    fn a_keystroke_is_one_row_and_a_shift_for_the_rest() {
160        let old = [R("a", Some(0)), R("b", Some(2)), R("c", Some(4))];
161        let new = [R("a", Some(0)), R("bx", Some(2)), R("c", Some(5))];
162        let d = delta(&old, &new);
163        assert_eq!(
164            d,
165            RowDelta {
166                start: 1,
167                replaced: 1,
168                len: 1,
169                src_shift: 1
170            }
171        );
172    }
173
174    #[test]
175    fn a_row_that_does_not_stand_at_the_shift_is_in_the_span() {
176        // The last row moved by two, the one before it by one: only the last
177        // is kept, whatever the middle one says.
178        let old = [R("a", Some(0)), R("b", Some(2)), R("c", Some(4))];
179        let new = [R("a", Some(0)), R("b", Some(3)), R("c", Some(6))];
180        let d = delta(&old, &new);
181        assert_eq!(d.start, 1);
182        assert_eq!(d.replaced, 1);
183        assert_eq!(d.src_shift, 2);
184    }
185
186    #[test]
187    fn a_blank_row_at_the_end_leaves_the_shift_to_the_row_that_carries_one() {
188        let old = [R("a", Some(0)), R("b", Some(2)), R("", None)];
189        let new = [R("ab", Some(0)), R("b", Some(3)), R("", None)];
190        let d = delta(&old, &new);
191        assert_eq!((d.start, d.replaced, d.len, d.src_shift), (0, 1, 1, 1));
192    }
193
194    #[test]
195    fn an_inserted_row_and_a_removed_one() {
196        let a = R("a", Some(0));
197        let b = R("b", Some(2));
198        let c = R("c", Some(4));
199        let d = delta(
200            &[a.clone(), c.clone()],
201            &[a.clone(), b.clone(), R("c", Some(6))],
202        );
203        assert_eq!((d.start, d.replaced, d.len, d.src_shift), (1, 0, 1, 2));
204        let d = delta(&[a.clone(), b, c], &[a, R("c", Some(2))]);
205        assert_eq!((d.start, d.replaced, d.len, d.src_shift), (1, 1, 0, -2));
206    }
207
208    #[test]
209    fn everything_changed_is_the_whole_of_both() {
210        let d = delta(&[R("a", Some(0))], &[R("x", Some(0)), R("y", Some(2))]);
211        assert_eq!((d.start, d.replaced, d.len), (0, 1, 2));
212        let d = delta(&[], &[R("x", Some(0))]);
213        assert_eq!((d.start, d.replaced, d.len), (0, 0, 1));
214        let d = delta(&[R("x", Some(0))], &[]);
215        assert_eq!((d.start, d.replaced, d.len), (0, 1, 0));
216    }
217}