1use crate::change::Change;
4use crate::error::UndoRedoError;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct HistoryEntry {
10 pub change: Change,
12 pub index: usize,
14 pub is_undone: bool,
16}
17
18impl HistoryEntry {
19 pub fn new(change: Change, index: usize) -> Self {
21 HistoryEntry {
22 change,
23 index,
24 is_undone: false,
25 }
26 }
27}
28
29pub struct HistoryManager {
31 undo_stack: Vec<Change>,
32 redo_stack: Vec<Change>,
33 all_changes: Vec<HistoryEntry>,
34}
35
36impl HistoryManager {
37 pub fn new() -> Self {
39 HistoryManager {
40 undo_stack: Vec::new(),
41 redo_stack: Vec::new(),
42 all_changes: Vec::new(),
43 }
44 }
45
46 pub fn record_change(&mut self, change: Change) -> Result<(), UndoRedoError> {
48 change.validate()?;
49
50 self.undo_stack.push(change.clone());
52
53 self.redo_stack.clear();
55
56 let index = self.all_changes.len();
58 self.all_changes.push(HistoryEntry::new(change, index));
59
60 Ok(())
61 }
62
63 pub fn undo(&mut self) -> Result<Change, UndoRedoError> {
65 let change = self
66 .undo_stack
67 .pop()
68 .ok_or(UndoRedoError::NoMoreUndos)?;
69
70 if let Some(entry) = self.all_changes.iter_mut().rev().find(|e| e.change.id == change.id)
72 {
73 entry.is_undone = true;
74 }
75
76 self.redo_stack.push(change.clone());
78
79 Ok(change)
80 }
81
82 pub fn redo(&mut self) -> Result<Change, UndoRedoError> {
84 let change = self
85 .redo_stack
86 .pop()
87 .ok_or(UndoRedoError::NoMoreRedos)?;
88
89 if let Some(entry) = self.all_changes.iter_mut().rev().find(|e| e.change.id == change.id)
91 {
92 entry.is_undone = false;
93 }
94
95 self.undo_stack.push(change.clone());
97
98 Ok(change)
99 }
100
101 pub fn can_undo(&self) -> bool {
103 !self.undo_stack.is_empty()
104 }
105
106 pub fn can_redo(&self) -> bool {
108 !self.redo_stack.is_empty()
109 }
110
111 pub fn get_history(&self, limit: usize, offset: usize) -> Vec<HistoryEntry> {
113 self.all_changes
114 .iter()
115 .skip(offset)
116 .take(limit)
117 .cloned()
118 .collect()
119 }
120
121 pub fn get_change_details(&self, change_id: &str) -> Result<HistoryEntry, UndoRedoError> {
123 self.all_changes
124 .iter()
125 .find(|e| e.change.id == change_id)
126 .cloned()
127 .ok_or_else(|| UndoRedoError::change_not_found(change_id))
128 }
129
130 pub fn get_changes_by_file(&self, file_path: &str) -> Vec<HistoryEntry> {
132 self.all_changes
133 .iter()
134 .filter(|e| e.change.file_path == file_path)
135 .cloned()
136 .collect()
137 }
138
139 pub fn total_changes(&self) -> usize {
141 self.all_changes.len()
142 }
143
144 pub fn undoable_count(&self) -> usize {
146 self.undo_stack.len()
147 }
148
149 pub fn redoable_count(&self) -> usize {
151 self.redo_stack.len()
152 }
153}
154
155impl Default for HistoryManager {
156 fn default() -> Self {
157 Self::new()
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164 use crate::change::ChangeType;
165
166 #[test]
167 fn test_history_manager_record_change() {
168 let mut manager = HistoryManager::new();
169 let change = Change::new(
170 "test.txt",
171 "before",
172 "after",
173 "Modify",
174 ChangeType::Modify,
175 )
176 .unwrap();
177 let result = manager.record_change(change);
178 assert!(result.is_ok());
179 assert_eq!(manager.total_changes(), 1);
180 assert!(manager.can_undo());
181 }
182
183 #[test]
184 fn test_history_manager_undo() {
185 let mut manager = HistoryManager::new();
186 let change = Change::new(
187 "test.txt",
188 "before",
189 "after",
190 "Modify",
191 ChangeType::Modify,
192 )
193 .unwrap();
194 manager.record_change(change.clone()).unwrap();
195
196 let undone = manager.undo();
197 assert!(undone.is_ok());
198 assert!(!manager.can_undo());
199 assert!(manager.can_redo());
200 }
201
202 #[test]
203 fn test_history_manager_redo() {
204 let mut manager = HistoryManager::new();
205 let change = Change::new(
206 "test.txt",
207 "before",
208 "after",
209 "Modify",
210 ChangeType::Modify,
211 )
212 .unwrap();
213 manager.record_change(change).unwrap();
214 manager.undo().unwrap();
215
216 let redone = manager.redo();
217 assert!(redone.is_ok());
218 assert!(manager.can_undo());
219 assert!(!manager.can_redo());
220 }
221
222 #[test]
223 fn test_history_manager_no_more_undos() {
224 let mut manager = HistoryManager::new();
225 let result = manager.undo();
226 assert!(result.is_err());
227 assert!(matches!(result, Err(UndoRedoError::NoMoreUndos)));
228 }
229
230 #[test]
231 fn test_history_manager_no_more_redos() {
232 let mut manager = HistoryManager::new();
233 let result = manager.redo();
234 assert!(result.is_err());
235 assert!(matches!(result, Err(UndoRedoError::NoMoreRedos)));
236 }
237
238 #[test]
239 fn test_history_manager_redo_stack_clearing() {
240 let mut manager = HistoryManager::new();
241 let change1 = Change::new(
242 "test.txt",
243 "before1",
244 "after1",
245 "Modify 1",
246 ChangeType::Modify,
247 )
248 .unwrap();
249 let change2 = Change::new(
250 "test.txt",
251 "after1",
252 "after2",
253 "Modify 2",
254 ChangeType::Modify,
255 )
256 .unwrap();
257
258 manager.record_change(change1).unwrap();
259 manager.undo().unwrap();
260 assert!(manager.can_redo());
261
262 manager.record_change(change2).unwrap();
264 assert!(!manager.can_redo());
265 }
266
267 #[test]
268 fn test_history_manager_get_history() {
269 let mut manager = HistoryManager::new();
270 for i in 0..5 {
271 let change = Change::new(
272 "test.txt",
273 &format!("before{}", i),
274 &format!("after{}", i),
275 &format!("Modify {}", i),
276 ChangeType::Modify,
277 )
278 .unwrap();
279 manager.record_change(change).unwrap();
280 }
281
282 let history = manager.get_history(2, 1);
283 assert_eq!(history.len(), 2);
284 assert_eq!(history[0].index, 1);
285 }
286
287 #[test]
288 fn test_history_manager_get_change_details() {
289 let mut manager = HistoryManager::new();
290 let change = Change::new(
291 "test.txt",
292 "before",
293 "after",
294 "Modify",
295 ChangeType::Modify,
296 )
297 .unwrap();
298 let change_id = change.id.clone();
299 manager.record_change(change).unwrap();
300
301 let details = manager.get_change_details(&change_id);
302 assert!(details.is_ok());
303 assert_eq!(details.unwrap().change.id, change_id);
304 }
305
306 #[test]
307 fn test_history_manager_get_changes_by_file() {
308 let mut manager = HistoryManager::new();
309 let change1 = Change::new(
310 "file1.txt",
311 "before",
312 "after",
313 "Modify",
314 ChangeType::Modify,
315 )
316 .unwrap();
317 let change2 = Change::new(
318 "file2.txt",
319 "before",
320 "after",
321 "Modify",
322 ChangeType::Modify,
323 )
324 .unwrap();
325 let change3 = Change::new(
326 "file1.txt",
327 "before",
328 "after",
329 "Modify",
330 ChangeType::Modify,
331 )
332 .unwrap();
333
334 manager.record_change(change1).unwrap();
335 manager.record_change(change2).unwrap();
336 manager.record_change(change3).unwrap();
337
338 let file1_changes = manager.get_changes_by_file("file1.txt");
339 assert_eq!(file1_changes.len(), 2);
340 }
341}
342
343#[cfg(test)]
344mod property_tests {
345 use super::*;
346 use crate::change::ChangeType;
347 use proptest::prelude::*;
348
349 fn file_path_strategy() -> impl Strategy<Value = String> {
351 r"[a-zA-Z0-9_\-./]{1,50}\.rs"
352 .prop_map(|s| s.to_string())
353 }
354
355 fn content_strategy() -> impl Strategy<Value = String> {
357 r"[a-zA-Z0-9]{1,100}"
358 .prop_map(|s| s.to_string())
359 }
360
361 proptest! {
362 #[test]
367 fn prop_undo_redo_consistency(
368 changes_data in prop::collection::vec(
369 (file_path_strategy(), content_strategy(), content_strategy()),
370 1..10
371 ),
372 ) {
373 let mut manager = HistoryManager::new();
374 let mut recorded_changes = Vec::new();
375
376 for (idx, (file_path, before, after)) in changes_data.iter().enumerate() {
378 prop_assume!(before != after);
380
381 let change = Change::new(
382 file_path.clone(),
383 before.clone(),
384 after.clone(),
385 format!("Change {}", idx),
386 ChangeType::Modify,
387 )
388 .unwrap();
389
390 recorded_changes.push(change.clone());
391 manager.record_change(change).ok();
392 }
393
394 prop_assert_eq!(
396 manager.total_changes(),
397 recorded_changes.len(),
398 "All changes should be recorded"
399 );
400
401 let mut undone_changes = Vec::new();
403 while manager.can_undo() {
404 if let Ok(change) = manager.undo() {
405 undone_changes.push(change);
406 }
407 }
408
409 prop_assert_eq!(
411 undone_changes.len(),
412 recorded_changes.len(),
413 "All changes should be undoable"
414 );
415
416 let mut redone_changes = Vec::new();
418 while manager.can_redo() {
419 if let Ok(change) = manager.redo() {
420 redone_changes.push(change);
421 }
422 }
423
424 prop_assert_eq!(
426 redone_changes.len(),
427 recorded_changes.len(),
428 "All changes should be redoable"
429 );
430
431 for (undone, redone) in undone_changes.iter().zip(redone_changes.iter().rev()) {
433 prop_assert_eq!(
434 &undone.id, &redone.id,
435 "Undone and redone changes should have same ID"
436 );
437 prop_assert_eq!(
438 &undone.file_path, &redone.file_path,
439 "File paths should match"
440 );
441 prop_assert_eq!(
442 &undone.before, &redone.before,
443 "Before states should match"
444 );
445 prop_assert_eq!(
446 &undone.after, &redone.after,
447 "After states should match"
448 );
449 }
450
451 prop_assert!(manager.can_undo(), "Should be able to undo after redo");
454 prop_assert!(!manager.can_redo(), "No more redos should be available after redo");
455 }
456
457 #[test]
462 fn prop_redo_stack_clearing(
463 initial_changes in prop::collection::vec(
464 (file_path_strategy(), content_strategy(), content_strategy()),
465 1..5
466 ),
467 new_change_data in (file_path_strategy(), content_strategy(), content_strategy()),
468 ) {
469 let mut manager = HistoryManager::new();
470
471 for (idx, (file_path, before, after)) in initial_changes.iter().enumerate() {
473 prop_assume!(before != after);
474
475 let change = Change::new(
476 file_path.clone(),
477 before.clone(),
478 after.clone(),
479 format!("Initial {}", idx),
480 ChangeType::Modify,
481 )
482 .unwrap();
483
484 manager.record_change(change).ok();
485 }
486
487 let initial_count = manager.total_changes();
488 prop_assume!(initial_count > 0);
489
490 let undo_result = manager.undo();
492 prop_assert!(undo_result.is_ok(), "Undo should succeed");
493 prop_assert!(manager.can_redo(), "Redo should be available after undo");
494
495 let (file_path, before, after) = new_change_data;
497 prop_assume!(before != after);
498
499 let new_change = Change::new(
500 file_path,
501 before,
502 after,
503 "New change after undo",
504 ChangeType::Modify,
505 )
506 .unwrap();
507
508 manager.record_change(new_change).ok();
509
510 prop_assert!(
512 !manager.can_redo(),
513 "Redo stack should be cleared after new change"
514 );
515
516 prop_assert!(
518 manager.can_undo(),
519 "Should be able to undo the new change"
520 );
521
522 prop_assert_eq!(
524 manager.total_changes(),
525 initial_count + 1,
526 "Total changes should increase by 1"
527 );
528 }
529
530 #[test]
534 fn prop_single_change_undo_redo(
535 file_path in file_path_strategy(),
536 before in content_strategy(),
537 after in content_strategy(),
538 ) {
539 prop_assume!(before != after);
540
541 let mut manager = HistoryManager::new();
542 let change = Change::new(
543 &file_path,
544 &before,
545 &after,
546 "Test change",
547 ChangeType::Modify,
548 )
549 .unwrap();
550
551 let original_id = change.id.clone();
552 manager.record_change(change).unwrap();
553
554 prop_assert!(manager.can_undo(), "Should be able to undo");
556 prop_assert!(!manager.can_redo(), "Should not be able to redo initially");
557
558 let undone = manager.undo().unwrap();
560 prop_assert_eq!(&undone.id, &original_id, "Undone change should have same ID");
561 prop_assert_eq!(&undone.file_path, &file_path, "File path should match");
562 prop_assert_eq!(&undone.before, &before, "Before state should match");
563 prop_assert_eq!(&undone.after, &after, "After state should match");
564
565 prop_assert!(!manager.can_undo(), "Should not be able to undo after undo");
567 prop_assert!(manager.can_redo(), "Should be able to redo after undo");
568
569 let redone = manager.redo().unwrap();
571 prop_assert_eq!(&redone.id, &original_id, "Redone change should have same ID");
572 prop_assert_eq!(&redone.file_path, &file_path, "File path should match");
573 prop_assert_eq!(&redone.before, &before, "Before state should match");
574 prop_assert_eq!(&redone.after, &after, "After state should match");
575
576 prop_assert!(manager.can_undo(), "Should be able to undo after redo");
578 prop_assert!(!manager.can_redo(), "Should not be able to redo after redo");
579 }
580 }
581}