mq_edit/document/
history.rs1use super::Cursor;
2
3#[derive(Debug, Clone)]
5pub enum EditAction {
6 InsertChar { line: usize, column: usize, c: char },
8 InsertStr {
10 line: usize,
11 column: usize,
12 text: String,
13 },
14 InsertNewline { line: usize, column: usize },
16 DeleteChar {
18 line: usize,
19 column: usize,
20 deleted: char,
21 },
22 JoinLines {
24 line: usize,
26 column: usize,
28 },
29 DeleteRange {
31 line: usize,
32 start_col: usize,
33 deleted: String,
34 },
35 ReplaceAt {
37 line: usize,
38 column: usize,
39 old_text: String,
40 new_text: String,
41 },
42}
43
44#[derive(Debug, Clone)]
46pub struct HistoryEntry {
47 pub action: EditAction,
48 pub cursor_before: Cursor,
49}
50
51#[derive(Debug, Clone)]
53pub struct EditHistory {
54 undo_stack: Vec<HistoryEntry>,
55 redo_stack: Vec<HistoryEntry>,
56 max_history: usize,
57}
58
59impl EditHistory {
60 pub fn new() -> Self {
61 Self {
62 undo_stack: Vec::new(),
63 redo_stack: Vec::new(),
64 max_history: 1000,
65 }
66 }
67
68 pub fn push(&mut self, action: EditAction, cursor_before: Cursor) {
70 self.redo_stack.clear();
71 self.undo_stack.push(HistoryEntry {
72 action,
73 cursor_before,
74 });
75 if self.undo_stack.len() > self.max_history {
76 self.undo_stack.remove(0);
77 }
78 }
79
80 pub fn undo(&mut self) -> Option<HistoryEntry> {
82 let entry = self.undo_stack.pop()?;
83 self.redo_stack.push(entry.clone());
84 Some(entry)
85 }
86
87 pub fn redo(&mut self) -> Option<HistoryEntry> {
89 let entry = self.redo_stack.pop()?;
90 self.undo_stack.push(entry.clone());
91 Some(entry)
92 }
93}
94
95impl Default for EditHistory {
96 fn default() -> Self {
97 Self::new()
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn test_push_and_undo() {
107 let mut history = EditHistory::new();
108 let cursor = Cursor::new();
109
110 history.push(
111 EditAction::InsertChar {
112 line: 0,
113 column: 0,
114 c: 'a',
115 },
116 cursor,
117 );
118
119 let entry = history.undo().unwrap();
120 match entry.action {
121 EditAction::InsertChar { c, .. } => assert_eq!(c, 'a'),
122 _ => panic!("Expected InsertChar"),
123 }
124 }
125
126 #[test]
127 fn test_undo_empty() {
128 let mut history = EditHistory::new();
129 assert!(history.undo().is_none());
130 }
131
132 #[test]
133 fn test_redo_after_undo() {
134 let mut history = EditHistory::new();
135 let cursor = Cursor::new();
136
137 history.push(
138 EditAction::InsertChar {
139 line: 0,
140 column: 0,
141 c: 'x',
142 },
143 cursor,
144 );
145
146 history.undo();
147 let entry = history.redo().unwrap();
148 match entry.action {
149 EditAction::InsertChar { c, .. } => assert_eq!(c, 'x'),
150 _ => panic!("Expected InsertChar"),
151 }
152 }
153
154 #[test]
155 fn test_new_action_clears_redo() {
156 let mut history = EditHistory::new();
157 let cursor = Cursor::new();
158
159 history.push(
160 EditAction::InsertChar {
161 line: 0,
162 column: 0,
163 c: 'a',
164 },
165 cursor,
166 );
167 history.undo();
168
169 history.push(
171 EditAction::InsertChar {
172 line: 0,
173 column: 0,
174 c: 'b',
175 },
176 cursor,
177 );
178
179 assert!(history.redo().is_none());
180 }
181
182 #[test]
183 fn test_max_history() {
184 let mut history = EditHistory::new();
185 history.max_history = 3;
186 let cursor = Cursor::new();
187
188 for i in 0..5 {
189 history.push(
190 EditAction::InsertChar {
191 line: 0,
192 column: i,
193 c: 'a',
194 },
195 cursor,
196 );
197 }
198
199 assert_eq!(history.undo_stack.len(), 3);
200 }
201}