1#[derive(Debug, Clone, PartialEq, Eq)]
4pub struct EditStep {
5 pub offset: usize,
7 pub deleted: String,
9 pub inserted: String,
11}
12
13impl EditStep {
14 pub fn insert(offset: usize, text: impl Into<String>) -> Self {
16 Self {
17 offset,
18 deleted: String::new(),
19 inserted: text.into(),
20 }
21 }
22
23 pub fn delete(offset: usize, deleted: impl Into<String>) -> Self {
25 Self {
26 offset,
27 deleted: deleted.into(),
28 inserted: String::new(),
29 }
30 }
31
32 pub fn replace(offset: usize, deleted: impl Into<String>, inserted: impl Into<String>) -> Self {
34 Self {
35 offset,
36 deleted: deleted.into(),
37 inserted: inserted.into(),
38 }
39 }
40
41 pub fn inverse(&self) -> Self {
43 Self {
44 offset: self.offset,
45 deleted: self.inserted.clone(),
46 inserted: self.deleted.clone(),
47 }
48 }
49
50 pub fn inserted_len(&self) -> usize {
52 self.inserted.chars().count()
53 }
54
55 pub fn deleted_len(&self) -> usize {
57 self.deleted.chars().count()
58 }
59
60 pub fn is_single_char_insert(&self) -> bool {
62 self.deleted.is_empty() && self.inserted.chars().count() == 1
63 }
64
65 pub fn is_single_char_delete(&self) -> bool {
67 self.inserted.is_empty() && self.deleted.chars().count() == 1
68 }
69}
70
71#[derive(Debug, Clone)]
74pub struct Transaction {
75 pub steps: Vec<EditStep>,
76 pub cursor_before: Option<crate::Position>,
78 pub cursor_after: Option<crate::Position>,
80}
81
82impl Transaction {
83 pub fn single(step: EditStep) -> Self {
85 Self {
86 steps: vec![step],
87 cursor_before: None,
88 cursor_after: None,
89 }
90 }
91
92 pub fn new(steps: Vec<EditStep>) -> Self {
94 Self {
95 steps,
96 cursor_before: None,
97 cursor_after: None,
98 }
99 }
100
101 pub fn with_cursors(mut self, before: crate::Position, after: crate::Position) -> Self {
103 self.cursor_before = Some(before);
104 self.cursor_after = Some(after);
105 self
106 }
107
108 pub fn inverse(&self) -> Self {
111 Self {
112 steps: self.steps.iter().rev().map(|s| s.inverse()).collect(),
113 cursor_before: self.cursor_after,
114 cursor_after: self.cursor_before,
115 }
116 }
117
118 pub fn can_coalesce(&self, other: &Transaction) -> bool {
122 if self.steps.len() != 1 || other.steps.len() != 1 {
123 return false;
124 }
125 let a = &self.steps[0];
126 let b = &other.steps[0];
127
128 if a.deleted.is_empty() && !a.inserted.is_empty()
130 && b.is_single_char_insert()
131 {
132 let a_last = a.inserted.chars().last().unwrap();
133 let b_char = b.inserted.chars().next().unwrap();
134 if a_last == '\n' || b_char == '\n' || b_char == ' ' || a_last == ' ' {
138 return false;
139 }
140 return b.offset == a.offset + a.inserted_len();
141 }
142
143 if a.inserted.is_empty() && !a.deleted.is_empty()
145 && b.is_single_char_delete()
146 {
147 let b_char = b.deleted.chars().next().unwrap();
148 if b_char == '\n' {
149 return false;
150 }
151 if b.offset + 1 == a.offset {
153 return true;
154 }
155 if b.offset == a.offset {
157 return true;
158 }
159 }
160
161 false
162 }
163
164 pub fn merge(&mut self, other: &Transaction) {
166 if self.steps.len() != 1 || other.steps.len() != 1 {
167 return;
168 }
169 if other.cursor_after.is_some() {
171 self.cursor_after = other.cursor_after;
172 }
173
174 let a = &mut self.steps[0];
175 let b = &other.steps[0];
176
177 if a.deleted.is_empty() && b.is_single_char_insert() {
178 a.inserted.push_str(&b.inserted);
180 } else if a.inserted.is_empty() && b.is_single_char_delete() {
181 if b.offset + 1 == a.offset {
182 a.deleted.insert_str(0, &b.deleted);
184 a.offset = b.offset;
185 } else if b.offset == a.offset {
186 a.deleted.push_str(&b.deleted);
188 }
189 }
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn insert_inverse() {
199 let step = EditStep::insert(5, "hello");
200 let inv = step.inverse();
201 assert_eq!(inv.offset, 5);
202 assert_eq!(inv.deleted, "hello");
203 assert!(inv.inserted.is_empty());
204 }
205
206 #[test]
207 fn delete_inverse() {
208 let step = EditStep::delete(3, "abc");
209 let inv = step.inverse();
210 assert_eq!(inv.offset, 3);
211 assert!(inv.deleted.is_empty());
212 assert_eq!(inv.inserted, "abc");
213 }
214
215 #[test]
216 fn replace_inverse() {
217 let step = EditStep::replace(0, "old", "new");
218 let inv = step.inverse();
219 assert_eq!(inv.deleted, "new");
220 assert_eq!(inv.inserted, "old");
221 }
222
223 #[test]
224 fn transaction_inverse() {
225 let tx = Transaction::new(vec![
226 EditStep::insert(0, "a"),
227 EditStep::insert(1, "b"),
228 ]);
229 let inv = tx.inverse();
230 assert_eq!(inv.steps.len(), 2);
231 assert_eq!(inv.steps[0].offset, 1);
233 assert_eq!(inv.steps[0].deleted, "b");
234 assert_eq!(inv.steps[1].offset, 0);
235 assert_eq!(inv.steps[1].deleted, "a");
236 }
237
238 #[test]
239 fn coalesce_inserts() {
240 let a = Transaction::single(EditStep::insert(0, "a"));
241 let b = Transaction::single(EditStep::insert(1, "b"));
242 let c = Transaction::single(EditStep::insert(2, "\n"));
243 assert!(a.can_coalesce(&b));
244 assert!(!b.can_coalesce(&c)); }
246
247 #[test]
248 fn coalesce_backspaces() {
249 let a = Transaction::single(EditStep::delete(3, "d"));
250 let b = Transaction::single(EditStep::delete(2, "c"));
251 assert!(a.can_coalesce(&b));
252 }
253
254 #[test]
255 fn merge_inserts() {
256 let mut a = Transaction::single(EditStep::insert(0, "a"));
257 let b = Transaction::single(EditStep::insert(1, "b"));
258 a.merge(&b);
259 assert_eq!(a.steps[0].inserted, "ab");
260 }
261
262 #[test]
263 fn merge_backspaces() {
264 let mut a = Transaction::single(EditStep::delete(3, "d"));
265 let b = Transaction::single(EditStep::delete(2, "c"));
266 a.merge(&b);
267 assert_eq!(a.steps[0].deleted, "cd");
268 assert_eq!(a.steps[0].offset, 2);
269 }
270
271 #[test]
281 fn space_typed_after_word_breaks_coalescing() {
282 let a = Transaction::single(EditStep::insert(0, "hello"));
283 let b = Transaction::single(EditStep::insert(5, " "));
284 assert!(
287 !a.can_coalesce(&b),
288 "space after a word should break coalescing, not join the word's group"
289 );
290 }
291}