patch.rs

  1use std::{cmp, mem};
  2
  3type Edit = buffer::Edit<u32>;
  4
  5#[derive(Default, Debug, PartialEq, Eq)]
  6pub struct Patch(Vec<Edit>);
  7
  8impl Patch {
  9    pub unsafe fn new_unchecked(edits: Vec<Edit>) -> Self {
 10        Self(edits)
 11    }
 12
 13    pub fn into_inner(self) -> Vec<Edit> {
 14        self.0
 15    }
 16
 17    pub fn compose(&self, other: &Self) -> Self {
 18        let mut old_edits_iter = self.0.iter().cloned().peekable();
 19        let mut new_edits_iter = other.0.iter().cloned().peekable();
 20        let mut composed = Patch(Vec::new());
 21
 22        let mut old_start = 0;
 23        let mut new_start = 0;
 24        loop {
 25            let old_edit = old_edits_iter.peek_mut();
 26            let new_edit = new_edits_iter.peek_mut();
 27
 28            // Push the old edit if its new end is before the new edit's old start.
 29            if let Some(old_edit) = old_edit.as_ref() {
 30                let new_edit = new_edit.as_ref();
 31                if new_edit.map_or(true, |new_edit| old_edit.new.end < new_edit.old.start) {
 32                    let catchup = old_edit.old.start - old_start;
 33                    old_start += catchup;
 34                    new_start += catchup;
 35
 36                    let old_end = old_start + old_edit.old.len() as u32;
 37                    let new_end = new_start + old_edit.new.len() as u32;
 38                    composed.push(Edit {
 39                        old: old_start..old_end,
 40                        new: new_start..new_end,
 41                    });
 42                    old_start = old_end;
 43                    new_start = new_end;
 44                    old_edits_iter.next();
 45                    continue;
 46                }
 47            }
 48
 49            // Push the new edit if its old end is before the old edit's new start.
 50            if let Some(new_edit) = new_edit.as_ref() {
 51                let old_edit = old_edit.as_ref();
 52                if old_edit.map_or(true, |old_edit| new_edit.old.end < old_edit.new.start) {
 53                    let catchup = new_edit.new.start - new_start;
 54                    old_start += catchup;
 55                    new_start += catchup;
 56
 57                    let old_end = old_start + new_edit.old.len() as u32;
 58                    let new_end = new_start + new_edit.new.len() as u32;
 59                    composed.push(Edit {
 60                        old: old_start..old_end,
 61                        new: new_start..new_end,
 62                    });
 63                    old_start = old_end;
 64                    new_start = new_end;
 65                    new_edits_iter.next();
 66                    continue;
 67                }
 68            }
 69
 70            // If we still have edits by this point then they must intersect, so we compose them.
 71            if let Some((old_edit, new_edit)) = old_edit.zip(new_edit) {
 72                if old_edit.new.start < new_edit.old.start {
 73                    let catchup = old_edit.old.start - old_start;
 74                    old_start += catchup;
 75                    new_start += catchup;
 76
 77                    let overshoot = new_edit.old.start - old_edit.new.start;
 78                    let old_end = cmp::min(old_start + overshoot, old_edit.old.end);
 79                    let new_end = new_start + overshoot;
 80                    composed.push(Edit {
 81                        old: old_start..old_end,
 82                        new: new_start..new_end,
 83                    });
 84
 85                    old_edit.old.start += overshoot;
 86                    old_edit.new.start += overshoot;
 87                    old_start = old_end;
 88                    new_start = new_end;
 89                } else {
 90                    let catchup = new_edit.new.start - new_start;
 91                    old_start += catchup;
 92                    new_start += catchup;
 93
 94                    let overshoot = old_edit.new.start - new_edit.old.start;
 95                    let old_end = old_start + overshoot;
 96                    let new_end = cmp::min(new_start + overshoot, new_edit.new.end);
 97                    composed.push(Edit {
 98                        old: old_start..old_end,
 99                        new: new_start..new_end,
100                    });
101
102                    new_edit.old.start += overshoot;
103                    new_edit.new.start += overshoot;
104                    old_start = old_end;
105                    new_start = new_end;
106                }
107
108                if old_edit.new.end > new_edit.old.end {
109                    let old_end =
110                        old_start + cmp::min(old_edit.old.len() as u32, new_edit.old.len() as u32);
111                    let new_end = new_start + new_edit.new.len() as u32;
112                    composed.push(Edit {
113                        old: old_start..old_end,
114                        new: new_start..new_end,
115                    });
116
117                    old_edit.old.start = old_end;
118                    old_edit.new.start = new_edit.old.end;
119                    old_start = old_end;
120                    new_start = new_end;
121                    new_edits_iter.next();
122                } else {
123                    let old_end = old_start + old_edit.old.len() as u32;
124                    let new_end =
125                        new_start + cmp::min(old_edit.new.len() as u32, new_edit.new.len() as u32);
126                    composed.push(Edit {
127                        old: old_start..old_end,
128                        new: new_start..new_end,
129                    });
130
131                    new_edit.old.start = old_edit.new.end;
132                    new_edit.new.start = new_end;
133                    old_start = old_end;
134                    new_start = new_end;
135                    old_edits_iter.next();
136                }
137            } else {
138                break;
139            }
140        }
141
142        composed
143    }
144
145    pub fn invert(&mut self) -> &mut Self {
146        for edit in &mut self.0 {
147            mem::swap(&mut edit.old, &mut edit.new);
148        }
149        self
150    }
151
152    pub fn clear(&mut self) {
153        self.0.clear();
154    }
155
156    fn push(&mut self, edit: Edit) {
157        if edit.old.len() == 0 && edit.new.len() == 0 {
158            return;
159        }
160
161        if let Some(last) = self.0.last_mut() {
162            if last.old.end >= edit.old.start {
163                last.old.end = edit.old.end;
164                last.new.end = edit.new.end;
165            } else {
166                self.0.push(edit);
167            }
168        } else {
169            self.0.push(edit);
170        }
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177    use rand::prelude::*;
178    use std::env;
179
180    #[gpui::test]
181    fn test_one_disjoint_edit() {
182        assert_patch_composition(
183            Patch(vec![Edit {
184                old: 1..3,
185                new: 1..4,
186            }]),
187            Patch(vec![Edit {
188                old: 0..0,
189                new: 0..4,
190            }]),
191            Patch(vec![
192                Edit {
193                    old: 0..0,
194                    new: 0..4,
195                },
196                Edit {
197                    old: 1..3,
198                    new: 5..8,
199                },
200            ]),
201        );
202
203        assert_patch_composition(
204            Patch(vec![Edit {
205                old: 1..3,
206                new: 1..4,
207            }]),
208            Patch(vec![Edit {
209                old: 5..9,
210                new: 5..7,
211            }]),
212            Patch(vec![
213                Edit {
214                    old: 1..3,
215                    new: 1..4,
216                },
217                Edit {
218                    old: 4..8,
219                    new: 5..7,
220                },
221            ]),
222        );
223    }
224
225    #[gpui::test]
226    fn test_one_overlapping_edit() {
227        assert_patch_composition(
228            Patch(vec![Edit {
229                old: 1..3,
230                new: 1..4,
231            }]),
232            Patch(vec![Edit {
233                old: 3..5,
234                new: 3..6,
235            }]),
236            Patch(vec![Edit {
237                old: 1..4,
238                new: 1..6,
239            }]),
240        );
241    }
242
243    #[gpui::test]
244    fn test_two_disjoint_and_overlapping() {
245        assert_patch_composition(
246            Patch(vec![
247                Edit {
248                    old: 1..3,
249                    new: 1..4,
250                },
251                Edit {
252                    old: 8..12,
253                    new: 9..11,
254                },
255            ]),
256            Patch(vec![
257                Edit {
258                    old: 0..0,
259                    new: 0..4,
260                },
261                Edit {
262                    old: 3..10,
263                    new: 7..9,
264                },
265            ]),
266            Patch(vec![
267                Edit {
268                    old: 0..0,
269                    new: 0..4,
270                },
271                Edit {
272                    old: 1..12,
273                    new: 5..10,
274                },
275            ]),
276        );
277    }
278
279    #[gpui::test]
280    fn test_two_new_edits_overlapping_one_old_edit() {
281        assert_patch_composition(
282            Patch(vec![Edit {
283                old: 0..0,
284                new: 0..3,
285            }]),
286            Patch(vec![
287                Edit {
288                    old: 0..0,
289                    new: 0..1,
290                },
291                Edit {
292                    old: 1..2,
293                    new: 2..2,
294                },
295            ]),
296            Patch(vec![Edit {
297                old: 0..0,
298                new: 0..3,
299            }]),
300        );
301
302        assert_patch_composition(
303            Patch(vec![Edit {
304                old: 2..3,
305                new: 2..4,
306            }]),
307            Patch(vec![
308                Edit {
309                    old: 0..2,
310                    new: 0..1,
311                },
312                Edit {
313                    old: 3..3,
314                    new: 2..5,
315                },
316            ]),
317            Patch(vec![Edit {
318                old: 0..3,
319                new: 0..6,
320            }]),
321        );
322
323        assert_patch_composition(
324            Patch(vec![Edit {
325                old: 0..0,
326                new: 0..2,
327            }]),
328            Patch(vec![
329                Edit {
330                    old: 0..0,
331                    new: 0..2,
332                },
333                Edit {
334                    old: 2..5,
335                    new: 4..4,
336                },
337            ]),
338            Patch(vec![Edit {
339                old: 0..3,
340                new: 0..4,
341            }]),
342        );
343    }
344
345    // #[test]
346    // fn test_compose_edits() {
347    //     assert_eq!(
348    //         compose_edits(
349    //             &Edit {
350    //                 old: 3..3,
351    //                 new: 3..6,
352    //             },
353    //             &Edit {
354    //                 old: 2..7,
355    //                 new: 2..4,
356    //             },
357    //         ),
358    //         Edit {
359    //             old: 2..4,
360    //             new: 2..4
361    //         }
362    //     );
363    // }
364
365    #[gpui::test]
366    fn test_two_new_edits_touching_one_old_edit() {
367        assert_patch_composition(
368            Patch(vec![
369                Edit {
370                    old: 2..3,
371                    new: 2..4,
372                },
373                Edit {
374                    old: 7..7,
375                    new: 8..11,
376                },
377            ]),
378            Patch(vec![
379                Edit {
380                    old: 2..3,
381                    new: 2..2,
382                },
383                Edit {
384                    old: 4..4,
385                    new: 3..4,
386                },
387            ]),
388            Patch(vec![
389                Edit {
390                    old: 2..3,
391                    new: 2..4,
392                },
393                Edit {
394                    old: 7..7,
395                    new: 8..11,
396                },
397            ]),
398        );
399    }
400
401    #[gpui::test(iterations = 100)]
402    fn test_random_patch_compositions(mut rng: StdRng) {
403        let operations = env::var("OPERATIONS")
404            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
405            .unwrap_or(20);
406
407        let initial_chars = (0..rng.gen_range(0..=100))
408            .map(|_| rng.gen_range(b'a'..=b'z') as char)
409            .collect::<Vec<_>>();
410        log::info!("initial chars: {:?}", initial_chars);
411
412        // Generate two sequential patches
413        let mut patches = Vec::new();
414        let mut expected_chars = initial_chars.clone();
415        for i in 0..2 {
416            log::info!("patch {}:", i);
417
418            let mut delta = 0i32;
419            let mut last_edit_end = 0;
420            let mut edits = Vec::new();
421
422            for _ in 0..operations {
423                if last_edit_end >= expected_chars.len() {
424                    break;
425                }
426
427                let end = rng.gen_range(last_edit_end..=expected_chars.len());
428                let start = rng.gen_range(last_edit_end..=end);
429                let old_len = end - start;
430
431                let mut new_len = rng.gen_range(0..=3);
432                if start == end && new_len == 0 {
433                    new_len += 1;
434                }
435
436                last_edit_end = start + new_len + 1;
437
438                let new_chars = (0..new_len)
439                    .map(|_| rng.gen_range(b'A'..=b'Z') as char)
440                    .collect::<Vec<_>>();
441                log::info!(
442                    "  editing {:?}: {:?}",
443                    start..end,
444                    new_chars.iter().collect::<String>()
445                );
446                edits.push(Edit {
447                    old: (start as i32 - delta) as u32..(end as i32 - delta) as u32,
448                    new: start as u32..(start + new_len) as u32,
449                });
450                expected_chars.splice(start..end, new_chars);
451
452                delta += new_len as i32 - old_len as i32;
453            }
454
455            patches.push(Patch(edits));
456        }
457
458        log::info!("old patch: {:?}", &patches[0]);
459        log::info!("new patch: {:?}", &patches[1]);
460        log::info!("initial chars: {:?}", initial_chars);
461        log::info!("final chars: {:?}", expected_chars);
462
463        // Compose the patches, and verify that it has the same effect as applying the
464        // two patches separately.
465        let composed = patches[0].compose(&patches[1]);
466        log::info!("composed patch: {:?}", &composed);
467
468        let mut actual_chars = initial_chars.clone();
469        for edit in composed.0 {
470            actual_chars.splice(
471                edit.new.start as usize..edit.new.start as usize + edit.old.len(),
472                expected_chars[edit.new.start as usize..edit.new.end as usize]
473                    .iter()
474                    .copied(),
475            );
476        }
477
478        assert_eq!(actual_chars, expected_chars);
479    }
480
481    #[track_caller]
482    fn assert_patch_composition(old: Patch, new: Patch, composed: Patch) {
483        let original = ('a'..'z').collect::<Vec<_>>();
484        let inserted = ('A'..'Z').collect::<Vec<_>>();
485
486        let mut expected = original.clone();
487        apply_patch(&mut expected, &old, &inserted);
488        apply_patch(&mut expected, &new, &inserted);
489
490        let mut actual = original.clone();
491        apply_patch(&mut actual, &composed, &expected);
492        assert_eq!(
493            actual.into_iter().collect::<String>(),
494            expected.into_iter().collect::<String>(),
495            "expected patch is incorrect"
496        );
497
498        assert_eq!(old.compose(&new), composed);
499    }
500
501    fn apply_patch(text: &mut Vec<char>, patch: &Patch, new_text: &[char]) {
502        for edit in patch.0.iter().rev() {
503            text.splice(
504                edit.old.start as usize..edit.old.end as usize,
505                new_text[edit.new.start as usize..edit.new.end as usize]
506                    .iter()
507                    .copied(),
508            );
509        }
510    }
511}