editor: Save base buffers when applying changes from their branches (#19562)

Marshall Bowers and Max created

This PR makes it so that when we apply changes within a branch
buffer—currently just the edits buffer—we save the underlying buffer.

This also fixes an issue where new files created via edits were not
properly flushed to disk.

Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>

Change summary

crates/editor/src/editor.rs                  | 22 --------
crates/editor/src/hunk_diff.rs               | 55 +++++++++++++++++++--
crates/editor/src/items.rs                   |  4 +
crates/editor/src/proposed_changes_editor.rs | 16 ++++++
4 files changed, 67 insertions(+), 30 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -6260,28 +6260,6 @@ 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) {
-                    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);
-                });
-            }
-        });
-    }
-
     pub fn open_active_item_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext<Self>) {
         if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
             let project_path = buffer.read(cx).project_path(cx)?;

crates/editor/src/hunk_diff.rs 🔗

@@ -7,11 +7,13 @@ use multi_buffer::{
     MultiBufferSnapshot, ToPoint,
 };
 use std::{ops::Range, sync::Arc};
+use text::OffsetRangeExt;
 use ui::{
     prelude::*, ActiveTheme, ContextMenu, IconButtonShape, InteractiveElement, IntoElement,
     ParentElement, PopoverMenu, Styled, Tooltip, ViewContext, VisualContext,
 };
 use util::RangeExt;
+use workspace::Item;
 
 use crate::{
     editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, ApplyDiffHunk,
@@ -327,7 +329,7 @@ impl Editor {
         Some(())
     }
 
-    fn apply_changes_in_range(
+    fn apply_diff_hunks_in_range(
         &mut self,
         range: Range<Anchor>,
         cx: &mut ViewContext<'_, Editor>,
@@ -343,16 +345,54 @@ impl Editor {
             branch_buffer.merge_into_base(vec![range], cx);
         });
 
+        if let Some(project) = self.project.clone() {
+            self.save(true, project, cx).detach_and_log_err(cx);
+        }
+
         None
     }
 
-    pub(crate) fn apply_all_changes(&self, cx: &mut ViewContext<Self>) {
+    pub(crate) fn apply_all_diff_hunks(&mut self, cx: &mut ViewContext<Self>) {
         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(Vec::new(), cx);
             });
         }
+
+        if let Some(project) = self.project.clone() {
+            self.save(true, project, cx).detach_and_log_err(cx);
+        }
+    }
+
+    pub(crate) 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) {
+                    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);
+                });
+            }
+        });
+
+        if let Some(project) = self.project.clone() {
+            self.save(true, project, cx).detach_and_log_err(cx);
+        }
     }
 
     fn hunk_header_block(
@@ -548,11 +588,12 @@ impl Editor {
                                                             let hunk = hunk.clone();
                                                             move |_event, cx| {
                                                                 editor.update(cx, |editor, cx| {
-                                                                    editor.apply_changes_in_range(
-                                                                        hunk.multi_buffer_range
-                                                                            .clone(),
-                                                                        cx,
-                                                                    );
+                                                                    editor
+                                                                        .apply_diff_hunks_in_range(
+                                                                            hunk.multi_buffer_range
+                                                                                .clone(),
+                                                                            cx,
+                                                                        );
                                                                 });
                                                             }
                                                         }),

crates/editor/src/items.rs 🔗

@@ -720,6 +720,10 @@ impl Item for Editor {
     ) -> Task<Result<()>> {
         self.report_editor_event("save", None, cx);
         let buffers = self.buffer().clone().read(cx).all_buffers();
+        let buffers = buffers
+            .into_iter()
+            .map(|handle| handle.read(cx).diff_base_buffer().unwrap_or(handle.clone()))
+            .collect::<HashSet<_>>();
         cx.spawn(|this, mut cx| async move {
             if format {
                 this.update(&mut cx, |editor, cx| {

crates/editor/src/proposed_changes_editor.rs 🔗

@@ -298,6 +298,20 @@ impl Item for ProposedChangesEditor {
             Item::set_nav_history(editor, nav_history, cx)
         });
     }
+
+    fn can_save(&self, cx: &AppContext) -> bool {
+        self.editor.read(cx).can_save(cx)
+    }
+
+    fn save(
+        &mut self,
+        format: bool,
+        project: Model<Project>,
+        cx: &mut ViewContext<Self>,
+    ) -> Task<gpui::Result<()>> {
+        self.editor
+            .update(cx, |editor, cx| Item::save(editor, format, project, cx))
+    }
 }
 
 impl ProposedChangesEditorToolbar {
@@ -323,7 +337,7 @@ impl Render for ProposedChangesEditorToolbar {
             if let Some(editor) = &editor {
                 editor.update(cx, |editor, cx| {
                     editor.editor.update(cx, |editor, cx| {
-                        editor.apply_all_changes(cx);
+                        editor.apply_all_diff_hunks(cx);
                     })
                 });
             }