Set window's `edited = true` when there are unsaved changes

Antonio Scandurra created

Change summary

crates/gpui/src/app.rs                 |  7 +++++++
crates/gpui/src/platform.rs            |  1 +
crates/gpui/src/platform/mac/window.rs | 11 +++++++++++
crates/gpui/src/platform/test.rs       |  6 ++++++
crates/workspace/src/workspace.rs      | 13 +++++++++++++
5 files changed, 38 insertions(+)

Detailed changes

crates/gpui/src/app.rs 🔗

@@ -3339,6 +3339,13 @@ impl<'a, T: View> ViewContext<'a, T> {
         }
     }
 
+    pub fn set_window_edited(&mut self, edited: bool) {
+        let window_id = self.window_id();
+        if let Some((_, window)) = self.presenters_and_platform_windows.get_mut(&window_id) {
+            window.set_edited(edited);
+        }
+    }
+
     pub fn add_model<S, F>(&mut self, build_model: F) -> ModelHandle<S>
     where
         S: Entity,

crates/gpui/src/platform.rs 🔗

@@ -97,6 +97,7 @@ pub trait Window: WindowContext {
     fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
     fn activate(&self);
     fn set_title(&mut self, title: &str);
+    fn set_edited(&mut self, edited: bool);
 }
 
 pub trait WindowContext {

crates/gpui/src/platform/mac/window.rs 🔗

@@ -397,6 +397,17 @@ impl platform::Window for Window {
             msg_send![app, changeWindowsItem:window title:title filename:false]
         }
     }
+
+    fn set_edited(&mut self, edited: bool) {
+        unsafe {
+            let window = self.0.borrow().native_window;
+            msg_send![window, setDocumentEdited: edited as BOOL]
+        }
+
+        // Changing the document edited state resets the traffic light position,
+        // so we have to move it again.
+        self.0.borrow().move_traffic_light();
+    }
 }
 
 impl platform::WindowContext for Window {

crates/gpui/src/platform/test.rs 🔗

@@ -38,6 +38,7 @@ pub struct Window {
     resize_handlers: Vec<Box<dyn FnMut()>>,
     close_handlers: Vec<Box<dyn FnOnce()>>,
     pub(crate) title: Option<String>,
+    pub(crate) edited: bool,
     pub(crate) pending_prompts: RefCell<VecDeque<oneshot::Sender<usize>>>,
 }
 
@@ -191,6 +192,7 @@ impl Window {
             scale_factor: 1.0,
             current_scene: None,
             title: None,
+            edited: false,
             pending_prompts: Default::default(),
         }
     }
@@ -258,6 +260,10 @@ impl super::Window for Window {
     fn set_title(&mut self, title: &str) {
         self.title = Some(title.to_string())
     }
+
+    fn set_edited(&mut self, edited: bool) {
+        self.edited = edited;
+    }
 }
 
 pub fn platform() -> Platform {

crates/workspace/src/workspace.rs 🔗

@@ -731,6 +731,7 @@ pub struct Workspace {
     leader_state: LeaderState,
     follower_states_by_leader: FollowerStatesByLeader,
     last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
+    window_edited: bool,
     _observe_current_user: Task<()>,
 }
 
@@ -846,6 +847,7 @@ impl Workspace {
             leader_state: Default::default(),
             follower_states_by_leader: Default::default(),
             last_leaders_by_pane: Default::default(),
+            window_edited: false,
             _observe_current_user,
         };
         this.project_remote_id_changed(this.project.read(cx).remote_id(), cx);
@@ -1474,6 +1476,7 @@ impl Workspace {
                     if pane == self.active_pane {
                         self.active_item_path_changed(cx);
                     }
+                    self.update_window_edited(cx);
                 }
             }
         } else {
@@ -1778,6 +1781,16 @@ impl Workspace {
         cx.set_window_title(&title);
     }
 
+    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
+        let is_edited = self
+            .items(cx)
+            .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
+        if is_edited != self.window_edited {
+            self.window_edited = is_edited;
+            cx.set_window_edited(self.window_edited)
+        }
+    }
+
     fn render_collaborators(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> Vec<ElementBox> {
         let mut collaborators = self
             .project