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}