Skip to main content

rpi_tui/
undo_stack.rs

1//! Undo stack for text editing.
2//!
3//! Provides a simple undo mechanism that stores snapshots of state.
4
5use std::collections::VecDeque;
6
7/// Maximum number of undo states to keep.
8const MAX_UNDO_SIZE: usize = 256;
9
10/// Undo stack for storing state snapshots.
11#[derive(Debug, Clone)]
12pub struct UndoStack<T> {
13    /// Stack of states (most recent at the back).
14    states: VecDeque<T>,
15    /// Maximum size of the stack.
16    max_size: usize,
17}
18
19impl<T: Clone> UndoStack<T> {
20    /// Create a new undo stack.
21    pub fn new() -> Self {
22        Self {
23            states: VecDeque::with_capacity(MAX_UNDO_SIZE),
24            max_size: MAX_UNDO_SIZE,
25        }
26    }
27
28    /// Create an undo stack with a custom maximum size.
29    pub fn with_max_size(max_size: usize) -> Self {
30        Self {
31            states: VecDeque::with_capacity(max_size),
32            max_size,
33        }
34    }
35
36    /// Push a state onto the undo stack.
37    pub fn push(&mut self, state: T) {
38        self.states.push_back(state);
39
40        // Trim if over capacity
41        while self.states.len() > self.max_size {
42            self.states.pop_front();
43        }
44    }
45
46    /// Pop the most recent state from the undo stack.
47    pub fn pop(&mut self) -> Option<T> {
48        self.states.pop_back()
49    }
50
51    /// Peek at the most recent state without removing it.
52    pub fn peek(&self) -> Option<&T> {
53        self.states.back()
54    }
55
56    /// Get the number of states in the undo stack.
57    pub fn len(&self) -> usize {
58        self.states.len()
59    }
60
61    /// Check if the undo stack is empty.
62    pub fn is_empty(&self) -> bool {
63        self.states.is_empty()
64    }
65
66    /// Clear all states from the undo stack.
67    pub fn clear(&mut self) {
68        self.states.clear();
69    }
70
71    /// Get all states (for debugging/testing).
72    pub fn states(&self) -> impl Iterator<Item = &T> {
73        self.states.iter()
74    }
75
76    /// Get state at index (0 is oldest, len-1 is most recent).
77    pub fn get(&self, index: usize) -> Option<&T> {
78        self.states.get(index)
79    }
80
81    /// Check if we can undo.
82    pub fn can_undo(&self) -> bool {
83        !self.states.is_empty()
84    }
85
86    /// Undo to a specific index, returning all popped states.
87    pub fn undo_to(&mut self, target_len: usize) -> Vec<T> {
88        let mut popped = Vec::new();
89        while self.states.len() > target_len {
90            if let Some(state) = self.states.pop_back() {
91                popped.push(state);
92            }
93        }
94        popped
95    }
96}
97
98impl<T: Clone> Default for UndoStack<T> {
99    fn default() -> Self {
100        Self::new()
101    }
102}
103
104/// Redo stack for storing undone states.
105#[derive(Debug, Clone)]
106pub struct RedoStack<T> {
107    /// Stack of undone states.
108    states: VecDeque<T>,
109    max_size: usize,
110}
111
112impl<T: Clone> RedoStack<T> {
113    /// Create a new redo stack.
114    pub fn new() -> Self {
115        Self {
116            states: VecDeque::with_capacity(MAX_UNDO_SIZE),
117            max_size: MAX_UNDO_SIZE,
118        }
119    }
120
121    /// Push a state onto the redo stack.
122    pub fn push(&mut self, state: T) {
123        self.states.push_back(state);
124
125        while self.states.len() > self.max_size {
126            self.states.pop_front();
127        }
128    }
129
130    /// Pop the most recent state from the redo stack.
131    pub fn pop(&mut self) -> Option<T> {
132        self.states.pop_back()
133    }
134
135    /// Get the number of states in the redo stack.
136    pub fn len(&self) -> usize {
137        self.states.len()
138    }
139
140    /// Check if the redo stack is empty.
141    pub fn is_empty(&self) -> bool {
142        self.states.is_empty()
143    }
144
145    /// Clear all states from the redo stack.
146    pub fn clear(&mut self) {
147        self.states.clear();
148    }
149
150    /// Check if we can redo.
151    pub fn can_redo(&self) -> bool {
152        !self.states.is_empty()
153    }
154}
155
156impl<T: Clone> Default for RedoStack<T> {
157    fn default() -> Self {
158        Self::new()
159    }
160}
161
162/// Combined undo/redo manager.
163#[derive(Debug, Clone)]
164pub struct UndoRedoManager<T> {
165    undo_stack: UndoStack<T>,
166    redo_stack: RedoStack<T>,
167}
168
169impl<T: Clone> UndoRedoManager<T> {
170    /// Create a new undo/redo manager.
171    pub fn new() -> Self {
172        Self {
173            undo_stack: UndoStack::new(),
174            redo_stack: RedoStack::new(),
175        }
176    }
177
178    /// Push a new state (clears redo stack).
179    pub fn push(&mut self, state: T) {
180        self.undo_stack.push(state);
181        self.redo_stack.clear();
182    }
183
184    /// Undo: move current state to redo stack and return previous state.
185    pub fn undo(&mut self, current: T) -> Option<T> {
186        if let Some(prev) = self.undo_stack.pop() {
187            self.redo_stack.push(current);
188            Some(prev)
189        } else {
190            None
191        }
192    }
193
194    /// Redo: move current state to undo stack and return next state.
195    pub fn redo(&mut self, current: T) -> Option<T> {
196        if let Some(next) = self.redo_stack.pop() {
197            self.undo_stack.push(current);
198            Some(next)
199        } else {
200            None
201        }
202    }
203
204    /// Check if undo is available.
205    pub fn can_undo(&self) -> bool {
206        self.undo_stack.can_undo()
207    }
208
209    /// Check if redo is available.
210    pub fn can_redo(&self) -> bool {
211        self.redo_stack.can_redo()
212    }
213
214    /// Clear all undo/redo history.
215    pub fn clear(&mut self) {
216        self.undo_stack.clear();
217        self.redo_stack.clear();
218    }
219
220    /// Get the number of undo states.
221    pub fn undo_count(&self) -> usize {
222        self.undo_stack.len()
223    }
224
225    /// Get the number of redo states.
226    pub fn redo_count(&self) -> usize {
227        self.redo_stack.len()
228    }
229}
230
231impl<T: Clone> Default for UndoRedoManager<T> {
232    fn default() -> Self {
233        Self::new()
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240
241    #[test]
242    fn test_push_and_pop() {
243        let mut stack: UndoStack<i32> = UndoStack::new();
244        stack.push(1);
245        stack.push(2);
246        stack.push(3);
247
248        assert_eq!(stack.len(), 3);
249        assert_eq!(stack.pop(), Some(3));
250        assert_eq!(stack.pop(), Some(2));
251        assert_eq!(stack.pop(), Some(1));
252        assert_eq!(stack.pop(), None);
253    }
254
255    #[test]
256    fn test_max_size() {
257        let mut stack: UndoStack<i32> = UndoStack::with_max_size(2);
258        stack.push(1);
259        stack.push(2);
260        stack.push(3);
261
262        assert_eq!(stack.len(), 2);
263        assert_eq!(stack.pop(), Some(3));
264        assert_eq!(stack.pop(), Some(2));
265        assert_eq!(stack.pop(), None);
266    }
267
268    #[test]
269    fn test_undo_redo_manager() {
270        let mut manager = UndoRedoManager::new();
271
272        manager.push("state1".to_string());
273        manager.push("state2".to_string());
274        manager.push("state3".to_string());
275
276        assert!(manager.can_undo());
277        assert!(!manager.can_redo());
278
279        // Undo
280        let prev = manager.undo("current".to_string());
281        assert_eq!(prev, Some("state3".to_string()));
282        assert!(manager.can_redo());
283
284        // Redo
285        let next = manager.redo("state3".to_string());
286        assert_eq!(next, Some("current".to_string()));
287    }
288
289    #[test]
290    fn test_push_clears_redo() {
291        let mut manager = UndoRedoManager::new();
292
293        manager.push("state1".to_string());
294        manager.push("state2".to_string());
295
296        let _ = manager.undo("current".to_string());
297        assert!(manager.can_redo());
298
299        // New push should clear redo
300        manager.push("new_state".to_string());
301        assert!(!manager.can_redo());
302    }
303}