Rust · 7754 bytes Raw Blame History
1 use super::Position;
2
3 /// An atomic edit operation that can be undone/redone
4 #[derive(Debug, Clone)]
5 pub enum Operation {
6 /// Insert text at position
7 Insert {
8 pos: usize, // char index
9 text: String,
10 cursor_before: Position,
11 cursor_after: Position,
12 },
13 /// Delete text at position
14 Delete {
15 pos: usize, // char index
16 text: String, // the deleted text (for undo)
17 cursor_before: Position,
18 cursor_after: Position,
19 },
20 }
21
22 impl Operation {
23 pub fn cursor_before(&self) -> Position {
24 match self {
25 Operation::Insert { cursor_before, .. } => *cursor_before,
26 Operation::Delete { cursor_before, .. } => *cursor_before,
27 }
28 }
29
30 pub fn cursor_after(&self) -> Position {
31 match self {
32 Operation::Insert { cursor_after, .. } => *cursor_after,
33 Operation::Delete { cursor_after, .. } => *cursor_after,
34 }
35 }
36 }
37
38 /// A group of operations that should be undone/redone together
39 #[derive(Debug, Clone, Default)]
40 pub struct OperationGroup {
41 pub ops: Vec<Operation>,
42 /// Cursor positions before this group (for multi-cursor undo)
43 pub cursors_before: Vec<Position>,
44 /// Cursor positions after this group (for multi-cursor redo)
45 pub cursors_after: Vec<Position>,
46 }
47
48 impl OperationGroup {
49 pub fn new() -> Self {
50 Self {
51 ops: Vec::new(),
52 cursors_before: Vec::new(),
53 cursors_after: Vec::new(),
54 }
55 }
56
57 pub fn push(&mut self, op: Operation) {
58 self.ops.push(op);
59 }
60
61 pub fn is_empty(&self) -> bool {
62 self.ops.is_empty()
63 }
64
65 pub fn set_cursors_before(&mut self, positions: Vec<Position>) {
66 if self.cursors_before.is_empty() {
67 self.cursors_before = positions;
68 }
69 }
70
71 pub fn set_cursors_after(&mut self, positions: Vec<Position>) {
72 self.cursors_after = positions;
73 }
74 }
75
76 /// Undo/redo history using operation-based approach
77 #[derive(Debug, Default)]
78 pub struct History {
79 undo_stack: Vec<OperationGroup>,
80 redo_stack: Vec<OperationGroup>,
81 current_group: OperationGroup,
82 /// Whether we're in the middle of a group (e.g., typing a word)
83 grouping: bool,
84 }
85
86 impl History {
87 pub fn new() -> Self {
88 Self::default()
89 }
90
91 /// Start a new operation group
92 pub fn begin_group(&mut self) {
93 if !self.current_group.is_empty() {
94 self.commit_group();
95 }
96 self.grouping = true;
97 }
98
99 /// End current operation group
100 pub fn end_group(&mut self) {
101 if !self.current_group.is_empty() {
102 self.commit_group();
103 }
104 self.grouping = false;
105 }
106
107 /// Add an operation to the current group
108 pub fn push(&mut self, op: Operation) {
109 self.current_group.push(op);
110 self.redo_stack.clear();
111 }
112
113 /// Set cursor positions before current operation group (for multi-cursor undo)
114 pub fn set_cursors_before(&mut self, positions: Vec<Position>) {
115 self.current_group.set_cursors_before(positions);
116 }
117
118 /// Set cursor positions after current operation group (for multi-cursor redo)
119 pub fn set_cursors_after(&mut self, positions: Vec<Position>) {
120 self.current_group.set_cursors_after(positions);
121 }
122
123 /// Record an insert operation
124 pub fn record_insert(
125 &mut self,
126 pos: usize,
127 text: String,
128 cursor_before: Position,
129 cursor_after: Position,
130 ) {
131 self.push(Operation::Insert {
132 pos,
133 text,
134 cursor_before,
135 cursor_after,
136 });
137 }
138
139 /// Record a delete operation
140 pub fn record_delete(
141 &mut self,
142 pos: usize,
143 text: String,
144 cursor_before: Position,
145 cursor_after: Position,
146 ) {
147 self.push(Operation::Delete {
148 pos,
149 text,
150 cursor_before,
151 cursor_after,
152 });
153 }
154
155 /// Commit current group to undo stack
156 fn commit_group(&mut self) {
157 if !self.current_group.is_empty() {
158 let group = std::mem::take(&mut self.current_group);
159 self.undo_stack.push(group);
160 }
161 }
162
163 /// Check if we should break the current group (e.g., on non-typing command)
164 pub fn maybe_break_group(&mut self) {
165 if !self.grouping && !self.current_group.is_empty() {
166 self.commit_group();
167 }
168 }
169
170 /// Get operations to undo, returns (operations, cursor_positions_after_undo)
171 pub fn undo(&mut self) -> Option<(Vec<Operation>, Vec<Position>)> {
172 self.commit_group();
173
174 if let Some(group) = self.undo_stack.pop() {
175 // Use stored cursors_before if available, otherwise fall back to first op's cursor_before
176 let cursor_positions = if !group.cursors_before.is_empty() {
177 group.cursors_before.clone()
178 } else {
179 vec![group.ops.first().map(|op| op.cursor_before()).unwrap_or_default()]
180 };
181 self.redo_stack.push(group.clone());
182 Some((group.ops, cursor_positions))
183 } else {
184 None
185 }
186 }
187
188 /// Get operations to redo, returns (operations, cursor_positions_after_redo)
189 pub fn redo(&mut self) -> Option<(Vec<Operation>, Vec<Position>)> {
190 if let Some(group) = self.redo_stack.pop() {
191 // Use stored cursors_after if available, otherwise fall back to last op's cursor_after
192 let cursor_positions = if !group.cursors_after.is_empty() {
193 group.cursors_after.clone()
194 } else {
195 vec![group.ops.last().map(|op| op.cursor_after()).unwrap_or_default()]
196 };
197 self.undo_stack.push(group.clone());
198 Some((group.ops, cursor_positions))
199 } else {
200 None
201 }
202 }
203
204 #[allow(dead_code)]
205 pub fn can_undo(&self) -> bool {
206 !self.undo_stack.is_empty() || !self.current_group.is_empty()
207 }
208
209 #[allow(dead_code)]
210 pub fn can_redo(&self) -> bool {
211 !self.redo_stack.is_empty()
212 }
213
214 /// Clear all history
215 #[allow(dead_code)]
216 pub fn clear(&mut self) {
217 self.undo_stack.clear();
218 self.redo_stack.clear();
219 self.current_group = OperationGroup::new();
220 }
221
222 /// Get mutable reference to last operation in current group or undo stack
223 pub fn undo_stack_last_mut(&mut self) -> Option<&mut Operation> {
224 if !self.current_group.is_empty() {
225 self.current_group.ops.last_mut()
226 } else {
227 self.undo_stack.last_mut().and_then(|g| g.ops.last_mut())
228 }
229 }
230 }
231
232 #[cfg(test)]
233 mod tests {
234 use super::*;
235
236 #[test]
237 fn test_record_and_undo() {
238 let mut history = History::new();
239 let before = Position::new(0, 0);
240 let after = Position::new(0, 5);
241
242 history.record_insert(0, "hello".to_string(), before, after);
243 history.end_group();
244
245 assert!(history.can_undo());
246 let (ops, positions) = history.undo().unwrap();
247 assert_eq!(ops.len(), 1);
248 assert_eq!(positions.len(), 1);
249 assert_eq!(positions[0], before);
250 }
251
252 #[test]
253 fn test_redo() {
254 let mut history = History::new();
255 let before = Position::new(0, 0);
256 let after = Position::new(0, 5);
257
258 history.record_insert(0, "hello".to_string(), before, after);
259 history.end_group();
260
261 history.undo();
262 assert!(history.can_redo());
263
264 let (ops, positions) = history.redo().unwrap();
265 assert_eq!(ops.len(), 1);
266 assert_eq!(positions.len(), 1);
267 assert_eq!(positions[0], after);
268 }
269 }
270