1use std::collections::VecDeque;
21use std::hash::Hash;
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum ListEdit<K> {
26 Keep { key: K },
28 Move { key: K },
30 Insert { key: K },
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct KeyedDiff<K> {
37 pub edits: Vec<ListEdit<K>>,
39 pub removed: Vec<K>,
41}
42
43pub fn reconcile_keys<K>(old: &[K], new: &[K]) -> KeyedDiff<K>
46where
47 K: Clone + Eq + Hash,
48{
49 let mut surviving: VecDeque<(usize, &K)> = old
52 .iter()
53 .enumerate()
54 .filter(|(_, k)| new.contains(k))
55 .collect();
56 let removed: Vec<K> = old.iter().filter(|&k| !new.contains(k)).cloned().collect();
57
58 let mut edits = Vec::with_capacity(new.len());
59 for k in new {
60 let in_old = old.contains(k);
61 if !in_old {
62 edits.push(ListEdit::Insert { key: k.clone() });
63 continue;
64 }
65 if let Some(front) = surviving.front() {
67 if front.1 == k {
68 surviving.pop_front();
69 edits.push(ListEdit::Keep { key: k.clone() });
70 } else {
71 if let Some(idx) = surviving.iter().position(|(_, sk)| *sk == k) {
72 surviving.remove(idx);
73 }
74 edits.push(ListEdit::Move { key: k.clone() });
75 }
76 } else {
77 edits.push(ListEdit::Move { key: k.clone() });
78 }
79 }
80
81 KeyedDiff { edits, removed }
82}
83
84pub fn apply_edits<K>(old: &[K], diff: &KeyedDiff<K>) -> Vec<K>
88where
89 K: Clone + Eq + Hash,
90{
91 let mut working: Vec<K> = old
92 .iter()
93 .filter(|&k| !diff.removed.contains(k))
94 .cloned()
95 .collect();
96 let mut out = Vec::with_capacity(diff.edits.len());
97 for edit in &diff.edits {
98 match edit {
99 ListEdit::Keep { key } | ListEdit::Move { key } => {
100 if let Some(idx) = working.iter().position(|w| w == key) {
101 working.remove(idx);
102 }
103 out.push(key.clone());
104 }
105 ListEdit::Insert { key } => out.push(key.clone()),
106 }
107 }
108 out
109}
110
111pub fn edit_description<K: std::fmt::Display>(edit: &ListEdit<K>) -> String {
115 match edit {
116 ListEdit::Keep { key } => format!("kept {key}"),
117 ListEdit::Move { key } => format!("moved {key}"),
118 ListEdit::Insert { key } => format!("inserted {key}"),
119 }
120}
121
122pub fn diff_summary<K: std::fmt::Display>(diff: &KeyedDiff<K>) -> String {
126 let mut keeps = 0;
127 let mut moves = 0;
128 let mut inserts = 0;
129 for e in &diff.edits {
130 match e {
131 ListEdit::Keep { .. } => keeps += 1,
132 ListEdit::Move { .. } => moves += 1,
133 ListEdit::Insert { .. } => inserts += 1,
134 }
135 }
136 format!(
137 "kept {keeps}, moved {moves}, inserted {inserts}, removed {}",
138 diff.removed.len()
139 )
140}
141
142pub struct History<T> {
152 past: Vec<T>,
153 present: T,
154 future: Vec<T>,
155 limit: Option<usize>,
156}
157
158impl<T: Clone> History<T> {
159 pub fn new(initial: T, limit: Option<usize>) -> Self {
162 History {
163 past: Vec::new(),
164 present: initial,
165 future: Vec::new(),
166 limit,
167 }
168 }
169
170 pub fn present(&self) -> &T {
172 &self.present
173 }
174
175 pub fn push(&mut self, new_value: T)
179 where
180 T: PartialEq,
181 {
182 if new_value == self.present {
183 return;
184 }
185 self.past.push(self.present.clone());
186 if let Some(limit) = self.limit {
187 while self.past.len() > limit {
188 self.past.remove(0);
189 }
190 }
191 self.present = new_value;
192 self.future.clear();
193 }
194
195 pub fn undo(&mut self) -> bool {
198 if let Some(prev) = self.past.pop() {
199 self.future.push(std::mem::replace(&mut self.present, prev));
200 true
201 } else {
202 false
203 }
204 }
205
206 pub fn redo(&mut self) -> bool {
209 if let Some(next) = self.future.pop() {
210 self.past.push(std::mem::replace(&mut self.present, next));
211 true
212 } else {
213 false
214 }
215 }
216
217 pub fn can_undo(&self) -> bool {
219 !self.past.is_empty()
220 }
221
222 pub fn can_redo(&self) -> bool {
224 !self.future.is_empty()
225 }
226
227 pub fn depth(&self) -> usize {
229 self.past.len()
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 fn diff_and_apply(old: &[&str], new: &[&str]) -> Vec<String> {
238 let owned_old: Vec<String> = old.iter().map(|s| s.to_string()).collect();
239 let owned_new: Vec<String> = new.iter().map(|s| s.to_string()).collect();
240 let diff = reconcile_keys(&owned_old, &owned_new);
241 apply_edits(&owned_old, &diff).into_iter().collect()
242 }
243
244 #[test]
245 fn identical_lists_are_all_keeps() {
246 let old = vec!["a".to_string(), "b".to_string(), "c".to_string()];
247 let diff = reconcile_keys(&old, &old);
248 assert!(diff.removed.is_empty());
249 assert_eq!(
250 diff.edits,
251 vec![
252 ListEdit::Keep {
253 key: "a".to_string()
254 },
255 ListEdit::Keep {
256 key: "b".to_string()
257 },
258 ListEdit::Keep {
259 key: "c".to_string()
260 },
261 ]
262 );
263 }
264
265 #[test]
266 fn append_produces_insert_and_no_removes() {
267 let out = diff_and_apply(&["a", "b"], &["a", "b", "c"]);
268 assert_eq!(out, vec!["a", "b", "c"]);
269 }
270
271 #[test]
272 fn truncate_removes_tail() {
273 let old = vec!["a".to_string(), "b".to_string(), "c".to_string()];
274 let new = vec!["a".to_string()];
275 let diff = reconcile_keys(&old, &new);
276 assert_eq!(diff.removed, vec!["b".to_string(), "c".to_string()]);
277 let out = apply_edits(&old, &diff);
278 assert_eq!(out, vec!["a".to_string()]);
279 }
280
281 #[test]
282 fn reorder_is_moves_not_full_rebuild() {
283 let out = diff_and_apply(&["a", "b", "c", "d"], &["d", "c", "b", "a"]);
284 assert_eq!(out, vec!["d", "c", "b", "a"]);
285
286 let old = vec![
287 "a".to_string(),
288 "b".to_string(),
289 "c".to_string(),
290 "d".to_string(),
291 ];
292 let new = vec![
293 "d".to_string(),
294 "c".to_string(),
295 "b".to_string(),
296 "a".to_string(),
297 ];
298 let diff = reconcile_keys(&old, &new);
299 assert!(diff.removed.is_empty());
300 assert!(diff
302 .edits
303 .iter()
304 .all(|e| !matches!(e, ListEdit::Insert { .. })));
305 }
306
307 #[test]
308 fn insert_in_middle_moves_following() {
309 let old = vec!["a".to_string(), "b".to_string(), "c".to_string()];
312 let new = vec![
313 "a".to_string(),
314 "x".to_string(),
315 "b".to_string(),
316 "c".to_string(),
317 ];
318 let diff = reconcile_keys(&old, &new);
319 assert_eq!(
320 diff.edits[0],
321 ListEdit::Keep {
322 key: "a".to_string()
323 }
324 );
325 assert_eq!(
326 diff.edits[1],
327 ListEdit::Insert {
328 key: "x".to_string()
329 }
330 );
331 assert_eq!(
332 diff.edits[2],
333 ListEdit::Keep {
334 key: "b".to_string()
335 }
336 );
337 let out = apply_edits(&old, &diff);
338 assert_eq!(out, new);
339 }
340
341 #[test]
342 fn remove_from_middle_shifts_others_to_keep() {
343 let old = vec!["a".to_string(), "b".to_string(), "c".to_string()];
344 let new = vec!["a".to_string(), "c".to_string()];
345 let diff = reconcile_keys(&old, &new);
346 assert_eq!(diff.removed, vec!["b".to_string()]);
347 assert_eq!(
349 diff.edits[0],
350 ListEdit::Keep {
351 key: "a".to_string()
352 }
353 );
354 assert_eq!(
355 diff.edits[1],
356 ListEdit::Keep {
357 key: "c".to_string()
358 }
359 );
360 }
361
362 #[test]
363 fn mixed_add_remove_reorder_reproduces_new() {
364 let out = diff_and_apply(&["a", "b", "c", "d", "e"], &["e", "b", "f", "d"]);
365 assert_eq!(out, vec!["e", "b", "f", "d"]);
366 }
367
368 #[test]
369 fn empty_old_is_all_inserts() {
370 let old: Vec<String> = vec![];
371 let new = vec!["a".to_string(), "b".to_string()];
372 let diff = reconcile_keys(&old, &new);
373 assert!(diff.removed.is_empty());
374 assert_eq!(
375 diff.edits,
376 vec![
377 ListEdit::Insert {
378 key: "a".to_string()
379 },
380 ListEdit::Insert {
381 key: "b".to_string()
382 },
383 ]
384 );
385 }
386
387 #[test]
388 fn edit_description_is_readable() {
389 assert_eq!(edit_description(&ListEdit::Keep { key: "a" }), "kept a");
390 assert_eq!(edit_description(&ListEdit::Move { key: "b" }), "moved b");
391 assert_eq!(edit_description(&ListEdit::Insert { key: "c" }), "inserted c");
392 }
393
394 #[test]
395 fn diff_summary_counts_ops() {
396 let old = vec!["a".to_string(), "b".to_string(), "c".to_string()];
397 let new = vec!["a".to_string(), "x".to_string(), "c".to_string()];
398 let diff = reconcile_keys(&old, &new);
399 assert_eq!(diff_summary(&diff), "kept 2, moved 0, inserted 1, removed 1");
400 }
401}
402
403#[cfg(test)]
404mod history_tests {
405 use super::*;
406
407 #[test]
408 fn push_then_undo_redo_round_trips() {
409 let mut h = History::new(0u32, None);
410 h.push(1);
411 h.push(2);
412 assert_eq!(*h.present(), 2);
413 assert!(h.can_undo());
414 assert!(h.undo());
415 assert_eq!(*h.present(), 1);
416 assert!(h.undo());
417 assert_eq!(*h.present(), 0);
418 assert!(!h.can_undo());
419
420 assert!(h.redo());
421 assert_eq!(*h.present(), 1);
422 assert!(h.redo());
423 assert_eq!(*h.present(), 2);
424 assert!(!h.can_redo());
425 }
426
427 #[test]
428 fn new_push_clears_redo() {
429 let mut h = History::new(0u32, None);
430 h.push(1);
431 h.undo();
432 assert!(h.can_redo());
433 h.push(5);
434 assert!(!h.can_redo());
435 assert_eq!(*h.present(), 5);
436 }
437
438 #[test]
439 fn equal_push_is_noop() {
440 let mut h = History::new(1u32, None);
441 let depth_before = h.depth();
442 h.push(1);
443 assert_eq!(h.depth(), depth_before);
444 }
445
446 #[test]
447 fn limit_drops_oldest() {
448 let mut h = History::new(0u32, Some(2));
449 h.push(1);
450 h.push(2);
451 h.push(3);
452 assert_eq!(h.depth(), 2);
454 assert!(h.undo());
455 assert_eq!(*h.present(), 2);
456 }
457}