In proposed change editors, apply diff hunks in batches (#18841)

Max Brunsfeld created

Release Notes:

- N/A

Change summary

crates/editor/src/editor.rs         | 14 ++++++++--
crates/editor/src/hunk_diff.rs      |  4 +-
crates/language/src/buffer.rs       | 40 +++++++++++++++++++++++-------
crates/language/src/buffer_tests.rs | 14 +++++-----
4 files changed, 50 insertions(+), 22 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -6213,14 +6213,22 @@ impl Editor {
     fn apply_selected_diff_hunks(&mut self, _: &ApplyDiffHunk, cx: &mut ViewContext<Self>) {
         let snapshot = self.buffer.read(cx).snapshot(cx);
         let hunks = hunks_for_selections(&snapshot, &self.selections.disjoint_anchors());
+        let mut ranges_by_buffer = HashMap::default();
         self.transact(cx, |editor, cx| {
             for hunk in hunks {
                 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
-                    buffer.update(cx, |buffer, cx| {
-                        buffer.merge_into_base(Some(hunk.buffer_range.to_offset(buffer)), cx);
-                    });
+                    ranges_by_buffer
+                        .entry(buffer.clone())
+                        .or_insert_with(Vec::new)
+                        .push(hunk.buffer_range.to_offset(buffer.read(cx)));
                 }
             }
+
+            for (buffer, ranges) in ranges_by_buffer {
+                buffer.update(cx, |buffer, cx| {
+                    buffer.merge_into_base(ranges, cx);
+                });
+            }
         });
     }
 

crates/editor/src/hunk_diff.rs 🔗

@@ -350,7 +350,7 @@ impl Editor {
             .next()?;
 
         buffer.update(cx, |branch_buffer, cx| {
-            branch_buffer.merge_into_base(Some(range), cx);
+            branch_buffer.merge_into_base(vec![range], cx);
         });
 
         None
@@ -360,7 +360,7 @@ impl Editor {
         let buffers = self.buffer.read(cx).all_buffers();
         for branch_buffer in buffers {
             branch_buffer.update(cx, |branch_buffer, cx| {
-                branch_buffer.merge_into_base(None, cx);
+                branch_buffer.merge_into_base(Vec::new(), cx);
             });
         }
     }

crates/language/src/buffer.rs 🔗

@@ -827,25 +827,45 @@ impl Buffer {
         })
     }
 
-    /// Applies all of the changes in this buffer that intersect the given `range`
-    /// to its base buffer. This buffer must be a branch buffer to call this method.
-    pub fn merge_into_base(&mut self, range: Option<Range<usize>>, cx: &mut ModelContext<Self>) {
+    /// Applies all of the changes in this buffer that intersect any of the
+    /// given `ranges` to its base buffer.
+    ///
+    /// If `ranges` is empty, then all changes will be applied. This buffer must
+    /// be a branch buffer to call this method.
+    pub fn merge_into_base(&mut self, ranges: Vec<Range<usize>>, cx: &mut ModelContext<Self>) {
         let Some(base_buffer) = self.diff_base_buffer() else {
             debug_panic!("not a branch buffer");
             return;
         };
 
+        let mut ranges = if ranges.is_empty() {
+            &[0..usize::MAX]
+        } else {
+            ranges.as_slice()
+        }
+        .into_iter()
+        .peekable();
+
         let mut edits = Vec::new();
         for edit in self.edits_since::<usize>(&base_buffer.read(cx).version()) {
-            if let Some(range) = &range {
-                if range.start > edit.new.end || edit.new.start > range.end {
-                    continue;
+            let mut is_included = false;
+            while let Some(range) = ranges.peek() {
+                if range.end < edit.new.start {
+                    ranges.next().unwrap();
+                } else {
+                    if range.start <= edit.new.end {
+                        is_included = true;
+                    }
+                    break;
                 }
             }
-            edits.push((
-                edit.old.clone(),
-                self.text_for_range(edit.new.clone()).collect::<String>(),
-            ));
+
+            if is_included {
+                edits.push((
+                    edit.old.clone(),
+                    self.text_for_range(edit.new.clone()).collect::<String>(),
+                ));
+            }
         }
 
         let operation = base_buffer.update(cx, |base_buffer, cx| {

crates/language/src/buffer_tests.rs 🔗

@@ -2472,7 +2472,7 @@ fn test_branch_and_merge(cx: &mut TestAppContext) {
 
     // Merging the branch applies all of its changes to the base.
     branch_buffer.update(cx, |branch_buffer, cx| {
-        branch_buffer.merge_into_base(None, cx);
+        branch_buffer.merge_into_base(Vec::new(), cx);
     });
 
     branch_buffer.update(cx, |branch_buffer, cx| {
@@ -2494,7 +2494,7 @@ fn test_merge_into_base(cx: &mut TestAppContext) {
     // Make 3 edits, merge one into the base.
     branch.update(cx, |branch, cx| {
         branch.edit([(0..3, "ABC"), (7..9, "HI"), (11..11, "LMN")], None, cx);
-        branch.merge_into_base(Some(5..8), cx);
+        branch.merge_into_base(vec![5..8], cx);
     });
 
     branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjkLMN"));
@@ -2503,13 +2503,13 @@ fn test_merge_into_base(cx: &mut TestAppContext) {
     // Undo the one already-merged edit. Merge that into the base.
     branch.update(cx, |branch, cx| {
         branch.edit([(7..9, "hi")], None, cx);
-        branch.merge_into_base(Some(5..8), cx);
+        branch.merge_into_base(vec![5..8], cx);
     });
     base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
 
     // Merge an insertion into the base.
     branch.update(cx, |branch, cx| {
-        branch.merge_into_base(Some(11..11), cx);
+        branch.merge_into_base(vec![11..11], cx);
     });
 
     branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefghijkLMN"));
@@ -2518,7 +2518,7 @@ fn test_merge_into_base(cx: &mut TestAppContext) {
     // Deleted the inserted text and merge that into the base.
     branch.update(cx, |branch, cx| {
         branch.edit([(11..14, "")], None, cx);
-        branch.merge_into_base(Some(10..11), cx);
+        branch.merge_into_base(vec![10..11], cx);
     });
 
     base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
@@ -2534,7 +2534,7 @@ fn test_undo_after_merge_into_base(cx: &mut TestAppContext) {
     // Make 2 edits, merge one into the base.
     branch.update(cx, |branch, cx| {
         branch.edit([(0..3, "ABC"), (7..9, "HI")], None, cx);
-        branch.merge_into_base(Some(7..7), cx);
+        branch.merge_into_base(vec![7..7], cx);
     });
     base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
     branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
@@ -2548,7 +2548,7 @@ fn test_undo_after_merge_into_base(cx: &mut TestAppContext) {
 
     // Merge that operation into the base again.
     branch.update(cx, |branch, cx| {
-        branch.merge_into_base(Some(7..7), cx);
+        branch.merge_into_base(vec![7..7], cx);
     });
     base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
     branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));