1use nifty_runtime::{DownloadField, Step};
9
10pub trait StatePlace: Clone + PartialEq {
12 fn steps(&self) -> &[Step];
14 #[must_use]
16 fn with_steps(&self, steps: Vec<Step>) -> Self;
17 fn same_root(&self, other: &Self) -> bool;
19}
20
21pub trait StateReader {
23 type Place: StatePlace;
24 fn list_len(&self, place: &Self::Place) -> Option<usize>;
26 fn holds_text(&self, place: &Self::Place, text: &str) -> bool;
28}
29
30pub fn follow_id<R: StateReader>(
33 reader: &R,
34 bindings: &mut [(DownloadField, R::Place)],
35 id: &str,
36) -> bool {
37 let Some(at) = bindings
38 .iter()
39 .find(|(field, _)| *field == DownloadField::Id)
40 .map(|(_, place)| place.clone())
41 else {
42 return true;
43 };
44 let Some(now) = find(reader, &at, id) else {
45 return false;
46 };
47 if now != at {
48 for (_, place) in bindings.iter_mut() {
49 *place = moved(place, &at, &now);
50 }
51 }
52 true
53}
54
55fn find<R: StateReader>(reader: &R, at: &R::Place, id: &str) -> Option<R::Place> {
58 if reader.holds_text(at, id) {
59 return Some(at.clone());
60 }
61 search(reader, at, &mut Vec::new(), at.steps(), id)
62}
63
64fn search<R: StateReader>(
65 reader: &R,
66 at: &R::Place,
67 walked: &mut Vec<Step>,
68 rest: &[Step],
69 id: &str,
70) -> Option<R::Place> {
71 let Some((step, rest)) = rest.split_first() else {
72 let place = at.with_steps(walked.clone());
73 return reader.holds_text(&place, id).then_some(place);
74 };
75 let choices = match *step {
76 Step::Field(_) => vec![*step],
77 Step::Index(_) => {
78 let len = reader.list_len(&at.with_steps(walked.clone()))?;
79 (0..len).map(Step::Index).collect()
80 }
81 };
82 choices.into_iter().find_map(|choice| {
83 walked.push(choice);
84 let found = search(reader, at, walked, rest, id);
85 walked.pop();
86 found
87 })
88}
89
90fn moved<P: StatePlace>(place: &P, from: &P, to: &P) -> P {
94 if !place.same_root(from) {
95 return place.clone();
96 }
97 let shared = place
98 .steps()
99 .iter()
100 .zip(from.steps())
101 .take_while(|(a, b)| a == b)
102 .count();
103 let mut steps = to.steps()[..shared].to_vec();
104 steps.extend_from_slice(&place.steps()[shared..]);
105 place.with_steps(steps)
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[derive(Clone, Debug, PartialEq)]
114 struct Place(usize, Vec<Step>);
115
116 impl StatePlace for Place {
117 fn steps(&self) -> &[Step] {
118 &self.1
119 }
120
121 fn with_steps(&self, steps: Vec<Step>) -> Self {
122 Self(self.0, steps)
123 }
124
125 fn same_root(&self, other: &Self) -> bool {
126 self.0 == other.0
127 }
128 }
129
130 struct Rows(Vec<&'static str>);
133
134 impl StateReader for Rows {
135 type Place = Place;
136
137 fn list_len(&self, place: &Place) -> Option<usize> {
138 (place.0 == 0 && place.1.is_empty()).then_some(self.0.len())
139 }
140
141 fn holds_text(&self, place: &Place, text: &str) -> bool {
142 match place.1[..] {
143 [Step::Index(row), Step::Field(0)] if place.0 == 0 => {
144 self.0.get(row) == Some(&text)
145 }
146 _ => false,
147 }
148 }
149 }
150
151 fn row(index: usize, field: usize) -> Place {
152 Place(0, vec![Step::Index(index), Step::Field(field)])
153 }
154
155 fn bindings_at(index: usize) -> Vec<(DownloadField, Place)> {
156 vec![
157 (DownloadField::Id, row(index, 0)),
158 (DownloadField::Progress, row(index, 1)),
159 (DownloadField::Status, Place(1, Vec::new())),
160 ]
161 }
162
163 #[test]
164 fn bindings_stay_while_the_item_stays() {
165 let mut bindings = bindings_at(1);
166 assert!(follow_id(&Rows(vec!["a", "b"]), &mut bindings, "b"));
167 assert_eq!(bindings, bindings_at(1));
168 }
169
170 #[test]
171 fn bindings_follow_their_item_to_its_new_position() {
172 let mut bindings = bindings_at(2);
173 assert!(follow_id(&Rows(vec!["c", "x"]), &mut bindings, "c"));
174 assert_eq!(
175 bindings,
176 [
177 (DownloadField::Id, row(0, 0)),
178 (DownloadField::Progress, row(0, 1)),
179 (DownloadField::Status, Place(1, Vec::new())),
180 ],
181 "the binding outside the list stays"
182 );
183 }
184
185 #[test]
186 fn a_removed_item_lets_go_of_the_download() {
187 let mut bindings = bindings_at(0);
188 assert!(!follow_id(&Rows(vec!["x"]), &mut bindings, "a"));
189 assert!(!follow_id(&Rows(Vec::new()), &mut bindings, "a"));
190 }
191
192 #[test]
193 fn bindings_without_an_id_stay_put() {
194 let mut bindings = vec![(DownloadField::Progress, row(3, 1))];
195 assert!(follow_id(&Rows(Vec::new()), &mut bindings, "a"));
196 assert_eq!(bindings, [(DownloadField::Progress, row(3, 1))]);
197 }
198}