Allow saving of all buffers contained in project diagnostics editor

Antonio Scandurra and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/diagnostics/src/diagnostics.rs | 24 ++++++---
crates/editor/src/items.rs            |  8 +++
crates/workspace/src/workspace.rs     | 68 +++++++++++++++++-----------
3 files changed, 64 insertions(+), 36 deletions(-)

Detailed changes

crates/diagnostics/src/diagnostics.rs 🔗

@@ -1,3 +1,4 @@
+use anyhow::Result;
 use editor::{
     context_header_renderer, diagnostic_block_renderer, diagnostic_header_renderer,
     display_map::{BlockDisposition, BlockProperties},
@@ -5,7 +6,7 @@ use editor::{
 };
 use gpui::{
     action, elements::*, keymap::Binding, AppContext, Entity, ModelHandle, MutableAppContext,
-    RenderContext, View, ViewContext, ViewHandle,
+    RenderContext, Task, View, ViewContext, ViewHandle,
 };
 use language::{Bias, Buffer, Point};
 use postage::watch;
@@ -207,7 +208,7 @@ impl workspace::Item for ProjectDiagnostics {
                         .await?;
                     view.update(&mut cx, |view, cx| view.populate_excerpts(buffer, cx))
                 }
-                Result::Ok::<_, anyhow::Error>(())
+                Result::<_, anyhow::Error>::Ok(())
             }
         })
         .detach();
@@ -229,11 +230,8 @@ impl workspace::ItemView for ProjectDiagnosticsEditor {
         None
     }
 
-    fn save(
-        &mut self,
-        _: &mut ViewContext<Self>,
-    ) -> anyhow::Result<gpui::Task<anyhow::Result<()>>> {
-        todo!()
+    fn save(&mut self, cx: &mut ViewContext<Self>) -> Result<Task<Result<()>>> {
+        self.excerpts.update(cx, |excerpts, cx| excerpts.save(cx))
     }
 
     fn save_as(
@@ -241,8 +239,8 @@ impl workspace::ItemView for ProjectDiagnosticsEditor {
         _: ModelHandle<project::Worktree>,
         _: &std::path::Path,
         _: &mut ViewContext<Self>,
-    ) -> gpui::Task<anyhow::Result<()>> {
-        todo!()
+    ) -> Task<Result<()>> {
+        unreachable!()
     }
 
     fn is_dirty(&self, cx: &AppContext) -> bool {
@@ -259,6 +257,14 @@ impl workspace::ItemView for ProjectDiagnosticsEditor {
             Event::Saved | Event::Dirtied | Event::FileHandleChanged
         )
     }
+
+    fn can_save(&self, _: &AppContext) -> bool {
+        true
+    }
+
+    fn can_save_as(&self, _: &AppContext) -> bool {
+        false
+    }
 }
 
 #[cfg(test)]

crates/editor/src/items.rs 🔗

@@ -187,6 +187,14 @@ impl ItemView for Editor {
     fn has_conflict(&self, cx: &AppContext) -> bool {
         self.buffer().read(cx).read(cx).has_conflict()
     }
+
+    fn can_save(&self, cx: &AppContext) -> bool {
+        self.project_path(cx).is_some()
+    }
+
+    fn can_save_as(&self, _: &AppContext) -> bool {
+        true
+    }
 }
 
 pub struct CursorPosition {

crates/workspace/src/workspace.rs 🔗

@@ -102,7 +102,9 @@ pub trait ItemView: View {
     fn has_conflict(&self, _: &AppContext) -> bool {
         false
     }
+    fn can_save(&self, cx: &AppContext) -> bool;
     fn save(&mut self, cx: &mut ViewContext<Self>) -> Result<Task<Result<()>>>;
+    fn can_save_as(&self, cx: &AppContext) -> bool;
     fn save_as(
         &mut self,
         worktree: ModelHandle<Worktree>,
@@ -146,6 +148,8 @@ pub trait ItemViewHandle {
     fn to_any(&self) -> AnyViewHandle;
     fn is_dirty(&self, cx: &AppContext) -> bool;
     fn has_conflict(&self, cx: &AppContext) -> bool;
+    fn can_save(&self, cx: &AppContext) -> bool;
+    fn can_save_as(&self, cx: &AppContext) -> bool;
     fn save(&self, cx: &mut MutableAppContext) -> Result<Task<Result<()>>>;
     fn save_as(
         &self,
@@ -276,6 +280,14 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
     fn to_any(&self) -> AnyViewHandle {
         self.into()
     }
+
+    fn can_save(&self, cx: &AppContext) -> bool {
+        self.read(cx).can_save(cx)
+    }
+
+    fn can_save_as(&self, cx: &AppContext) -> bool {
+        self.read(cx).can_save_as(cx)
+    }
 }
 
 impl Clone for Box<dyn ItemViewHandle> {
@@ -685,7 +697,35 @@ impl Workspace {
     pub fn save_active_item(&mut self, _: &Save, cx: &mut ViewContext<Self>) {
         if let Some(item) = self.active_item(cx) {
             let handle = cx.handle();
-            if item.project_path(cx.as_ref()).is_none() {
+            if item.can_save(cx) {
+                if item.has_conflict(cx.as_ref()) {
+                    const CONFLICT_MESSAGE: &'static str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
+
+                    cx.prompt(
+                        PromptLevel::Warning,
+                        CONFLICT_MESSAGE,
+                        &["Overwrite", "Cancel"],
+                        move |answer, cx| {
+                            if answer == 0 {
+                                cx.spawn(|mut cx| async move {
+                                    if let Err(error) = cx.update(|cx| item.save(cx)).unwrap().await
+                                    {
+                                        error!("failed to save item: {:?}, ", error);
+                                    }
+                                })
+                                .detach();
+                            }
+                        },
+                    );
+                } else {
+                    cx.spawn(|_, mut cx| async move {
+                        if let Err(error) = cx.update(|cx| item.save(cx)).unwrap().await {
+                            error!("failed to save item: {:?}, ", error);
+                        }
+                    })
+                    .detach();
+                }
+            } else if item.can_save_as(cx) {
                 let worktree = self.worktrees(cx).first();
                 let start_abs_path = worktree
                     .and_then(|w| w.read(cx).as_local())
@@ -717,32 +757,6 @@ impl Workspace {
                         .detach()
                     }
                 });
-                return;
-            } else if item.has_conflict(cx.as_ref()) {
-                const CONFLICT_MESSAGE: &'static str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
-
-                cx.prompt(
-                    PromptLevel::Warning,
-                    CONFLICT_MESSAGE,
-                    &["Overwrite", "Cancel"],
-                    move |answer, cx| {
-                        if answer == 0 {
-                            cx.spawn(|mut cx| async move {
-                                if let Err(error) = cx.update(|cx| item.save(cx)).unwrap().await {
-                                    error!("failed to save item: {:?}, ", error);
-                                }
-                            })
-                            .detach();
-                        }
-                    },
-                );
-            } else {
-                cx.spawn(|_, mut cx| async move {
-                    if let Err(error) = cx.update(|cx| item.save(cx)).unwrap().await {
-                        error!("failed to save item: {:?}, ", error);
-                    }
-                })
-                .detach();
             }
         }
     }