Skip to main content

polyspec_crudui_generator/
history.rs

1use crate::error::{FormError, FormResult};
2
3/// The number of records an undo history keeps in each direction (CURT-062).
4pub const HISTORY_LIMIT: usize = 100;
5
6/// The undo history of a form: the records before earlier changes and the records after undone changes, oldest first.
7/// The functions of the module take it by reference and return new values.
8#[derive(Debug, Clone, PartialEq)]
9pub struct History<T> {
10    /// Earlier records, at most [`HISTORY_LIMIT`].
11    pub entries: Vec<T>,
12    /// Records that redo restores, at most [`HISTORY_LIMIT`].
13    pub redo_entries: Vec<T>,
14    /// Path of the last change when it was a value edit.
15    pub last_path: Option<String>,
16}
17
18/// The history after an undo or a redo and the record that it restored.
19#[derive(Debug, Clone, PartialEq)]
20pub struct HistoryResult<T> {
21    /// The history after the restore.
22    pub history: History<T>,
23    /// The restored record.
24    pub value: T,
25}
26
27/// The last [`HISTORY_LIMIT`] records of a list.
28fn last_records<T: Clone>(records: &[T]) -> Vec<T> {
29    records[records.len().saturating_sub(HISTORY_LIMIT)..].to_vec()
30}
31
32/// The history with no record (CURT-076).
33pub fn empty_history<T>() -> History<T> {
34    History {
35        entries: Vec::new(),
36        redo_entries: Vec::new(),
37        last_path: None,
38    }
39}
40
41/// Appends the record before a change to the entries unless `path` equals the path of the last value edit, keeps the
42/// last [`HISTORY_LIMIT`] entries, clears the redo entries and sets the path of the history to `path` (CURT-077,
43/// CURT-063).
44pub fn record_change<T: Clone>(
45    history: &History<T>,
46    previous: T,
47    path: Option<&str>,
48) -> History<T> {
49    let mut entries = history.entries.clone();
50    if path.is_none() || path != history.last_path.as_deref() {
51        entries.push(previous);
52    }
53    History {
54        entries: last_records(&entries),
55        redo_entries: Vec::new(),
56        last_path: path.map(str::to_string),
57    }
58}
59
60/// Whether the history has a record to restore (CURT-078).
61pub fn can_undo<T>(history: &History<T>) -> bool {
62    !history.entries.is_empty()
63}
64
65/// Whether the history has a record to restore in the forward direction (CURT-078).
66pub fn can_redo<T>(history: &History<T>) -> bool {
67    !history.redo_entries.is_empty()
68}
69
70/// The history without its last entry, with `current` appended to the redo entries, and the last entry as the record
71/// (CURT-079); it fails when the history has no entry.
72pub fn undo_change<T: Clone>(history: &History<T>, current: T) -> FormResult<HistoryResult<T>> {
73    let Some((value, rest)) = history.entries.split_last() else {
74        return Err(FormError::input("Nothing to undo"));
75    };
76    let mut redo_entries = history.redo_entries.clone();
77    redo_entries.push(current);
78    Ok(HistoryResult {
79        history: History {
80            entries: rest.to_vec(),
81            redo_entries: last_records(&redo_entries),
82            last_path: None,
83        },
84        value: value.clone(),
85    })
86}
87
88/// The history with `current` appended to the entries and without its last redo entry, and that redo entry as the
89/// record (CURT-080); it fails when the history has no redo entry.
90pub fn redo_change<T: Clone>(history: &History<T>, current: T) -> FormResult<HistoryResult<T>> {
91    let Some((value, rest)) = history.redo_entries.split_last() else {
92        return Err(FormError::input("Nothing to redo"));
93    };
94    let mut entries = history.entries.clone();
95    entries.push(current);
96    Ok(HistoryResult {
97        history: History {
98            entries: last_records(&entries),
99            redo_entries: rest.to_vec(),
100            last_path: None,
101        },
102        value: value.clone(),
103    })
104}