1use crate::transaction::Transaction;
2
3#[derive(Debug)]
8pub struct History {
9 undo_stack: Vec<Transaction>,
10 redo_stack: Vec<Transaction>,
11 clean_depth: Option<usize>,
14 force_new_group: bool,
16}
17
18impl History {
19 pub fn new() -> Self {
20 Self {
21 undo_stack: Vec::new(),
22 redo_stack: Vec::new(),
23 clean_depth: Some(0),
24 force_new_group: false,
25 }
26 }
27
28 pub fn push(&mut self, tx: Transaction) {
31 if let Some(depth) = self.clean_depth {
33 if depth > self.undo_stack.len() {
34 self.clean_depth = None;
35 }
36 }
37 self.redo_stack.clear();
38
39 if !self.force_new_group {
40 if let Some(last) = self.undo_stack.last_mut() {
41 if last.can_coalesce(&tx) {
42 last.merge(&tx);
43 return;
44 }
45 }
46 }
47 self.force_new_group = false;
48
49 self.undo_stack.push(tx);
50 }
51
52 pub fn undo(&mut self) -> Option<Transaction> {
55 let tx = self.undo_stack.pop()?;
56 let inverse = tx.inverse();
57 self.redo_stack.push(tx);
58 Some(inverse)
59 }
60
61 pub fn redo(&mut self) -> Option<Transaction> {
64 let tx = self.redo_stack.pop()?;
65 let re_apply = tx.clone();
66 self.undo_stack.push(tx);
67 Some(re_apply)
68 }
69
70 pub fn mark_clean(&mut self) {
74 self.clean_depth = Some(self.undo_stack.len());
75 self.force_new_group = true;
76 }
77
78 pub fn is_dirty(&self) -> bool {
80 self.clean_depth != Some(self.undo_stack.len())
81 }
82
83 pub fn can_undo(&self) -> bool {
85 !self.undo_stack.is_empty()
86 }
87
88 pub fn can_redo(&self) -> bool {
90 !self.redo_stack.is_empty()
91 }
92
93 pub fn clear(&mut self) {
95 self.undo_stack.clear();
96 self.redo_stack.clear();
97 self.clean_depth = Some(0);
98 self.force_new_group = false;
99 }
100}
101
102impl Default for History {
103 fn default() -> Self {
104 Self::new()
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate::transaction::EditStep;
112
113 #[test]
114 fn undo_redo_basic() {
115 let mut history = History::new();
116 let tx = Transaction::single(EditStep::insert(0, "hello"));
117 history.push(tx);
118
119 assert!(history.can_undo());
120 assert!(!history.can_redo());
121
122 let undo_tx = history.undo().unwrap();
123 assert_eq!(undo_tx.steps[0].offset, 0);
124 assert_eq!(undo_tx.steps[0].deleted, "hello");
125 assert!(undo_tx.steps[0].inserted.is_empty());
126
127 assert!(!history.can_undo());
128 assert!(history.can_redo());
129
130 let redo_tx = history.redo().unwrap();
131 assert_eq!(redo_tx.steps[0].inserted, "hello");
132 }
133
134 #[test]
135 fn new_edit_clears_redo() {
136 let mut history = History::new();
137 history.push(Transaction::single(EditStep::insert(0, "a")));
138 history.push(Transaction::single(EditStep::insert(1, "b")));
139 history.undo(); assert!(history.can_redo());
143
144 history.push(Transaction::single(EditStep::insert(0, "x")));
146 assert!(!history.can_redo());
147 }
148
149 #[test]
150 fn coalescing_inserts() {
151 let mut history = History::new();
152 history.push(Transaction::single(EditStep::insert(0, "h")));
153 history.push(Transaction::single(EditStep::insert(1, "i")));
154
155 assert_eq!(history.undo_stack.len(), 1);
157 assert_eq!(history.undo_stack[0].steps[0].inserted, "hi");
158 }
159
160 #[test]
161 fn newline_breaks_coalescing() {
162 let mut history = History::new();
163 history.push(Transaction::single(EditStep::insert(0, "a")));
164 history.push(Transaction::single(EditStep::insert(1, "\n")));
165
166 assert_eq!(history.undo_stack.len(), 2);
168 }
169
170 #[test]
171 fn dirty_tracking() {
172 let mut history = History::new();
173 assert!(!history.is_dirty());
174
175 history.push(Transaction::single(EditStep::insert(0, "x")));
176 assert!(history.is_dirty());
177
178 history.mark_clean();
179 assert!(!history.is_dirty());
180
181 history.push(Transaction::single(EditStep::insert(1, "y")));
183 assert!(history.is_dirty());
184
185 history.undo();
187 assert!(!history.is_dirty());
188 }
189}