]> git.lizzy.rs Git - rust.git/blobdiff - crates/text_edit/src/lib.rs
test: add unit test for TextEdit::apply()
[rust.git] / crates / text_edit / src / lib.rs
index e9700c560ad3b64771d44833b02cdb954bf747df..f478e4dcf576c4a82d3c54adc657b87ffd4a0f0a 100644 (file)
@@ -3,12 +3,13 @@
 //! `rust-analyzer` never mutates text itself and only sends diffs to clients,
 //! so `TextEdit` is the ultimate representation of the work done by
 //! rust-analyzer.
+
 pub use text_size::{TextRange, TextSize};
 
 /// `InsertDelete` -- a single "atomic" change to text
 ///
 /// Must not overlap with other `InDel`s
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct Indel {
     pub insert: String,
     /// Refers to offsets in the original text
@@ -116,11 +117,14 @@ pub fn apply(&self, text: &mut String) {
     pub fn union(&mut self, other: TextEdit) -> Result<(), TextEdit> {
         // FIXME: can be done without allocating intermediate vector
         let mut all = self.iter().chain(other.iter()).collect::<Vec<_>>();
-        if !check_disjoint(&mut all) {
+        if !check_disjoint_and_sort(&mut all) {
             return Err(other);
         }
+
         self.indels.extend(other.indels);
-        assert_disjoint(&mut self.indels);
+        check_disjoint_and_sort(&mut self.indels);
+        // Only dedup deletions and replacements, keep all insertions
+        self.indels.dedup_by(|a, b| a == b && !a.delete.is_empty());
         Ok(())
     }
 
@@ -173,7 +177,7 @@ pub fn insert(&mut self, offset: TextSize, text: String) {
     }
     pub fn finish(self) -> TextEdit {
         let mut indels = self.indels;
-        assert_disjoint(&mut indels);
+        assert_disjoint_or_equal(&mut indels);
         TextEdit { indels }
     }
     pub fn invalidates_offset(&self, offset: TextSize) -> bool {
@@ -182,18 +186,34 @@ pub fn invalidates_offset(&self, offset: TextSize) -> bool {
     fn indel(&mut self, indel: Indel) {
         self.indels.push(indel);
         if self.indels.len() <= 16 {
-            assert_disjoint(&mut self.indels);
+            assert_disjoint_or_equal(&mut self.indels);
         }
     }
 }
 
-fn assert_disjoint(indels: &mut [impl std::borrow::Borrow<Indel>]) {
-    assert!(check_disjoint(indels));
+fn assert_disjoint_or_equal(indels: &mut [Indel]) {
+    assert!(check_disjoint_and_sort(indels));
 }
-fn check_disjoint(indels: &mut [impl std::borrow::Borrow<Indel>]) -> bool {
+// FIXME: Remove the impl Bound here, it shouldn't be needed
+fn check_disjoint_and_sort(indels: &mut [impl std::borrow::Borrow<Indel>]) -> bool {
     indels.sort_by_key(|indel| (indel.borrow().delete.start(), indel.borrow().delete.end()));
-    indels
-        .iter()
-        .zip(indels.iter().skip(1))
-        .all(|(l, r)| l.borrow().delete.end() <= r.borrow().delete.start())
+    indels.iter().zip(indels.iter().skip(1)).all(|(l, r)| {
+        let l = l.borrow();
+        let r = r.borrow();
+        l.delete.end() <= r.delete.start() || l == r
+    })
 }
+
+#[test]
+fn test_apply() {
+    let mut text = "_11h1_2222_xx3333_4444_6666".to_string();
+    let mut builder = TextEditBuilder::default();
+    builder.replace(TextRange::new(3.into(), 4.into()), "1".to_string());
+    builder.delete(TextRange::new(11.into(), 13.into()));
+    builder.insert(22.into(), "_5555".to_string());
+
+    let text_edit = builder.finish();
+    text_edit.apply(&mut text);
+
+    assert_eq!(text, "_1111_2222_3333_4444_5555_6666")
+}
\ No newline at end of file