autosave: Respect project autosave settings per file (#13369)

Thorsten Ball and Bennet created

This fixes #13316 by checking whether there are any local workspace
settings for a given file.

Release Notes:

- Fixed `autosave` settings in project-specific settings file being
ignored. ([#13316](https://github.com/zed-industries/zed/issues/13316)).

Co-authored-by: Bennet <bennet@zed.dev>

Change summary

crates/workspace/src/item.rs      | 24 ++++++++++++++++++++----
crates/workspace/src/pane.rs      | 15 +++++++--------
crates/workspace/src/workspace.rs | 10 +++++-----
crates/worktree/src/worktree.rs   |  6 ++++++
4 files changed, 38 insertions(+), 17 deletions(-)

Detailed changes

crates/workspace/src/item.rs 🔗

@@ -20,7 +20,7 @@ use gpui::{
 use project::{Project, ProjectEntryId, ProjectPath};
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsSources};
+use settings::{Settings, SettingsLocation, SettingsSources};
 use smallvec::SmallVec;
 use std::{
     any::{Any, TypeId},
@@ -331,6 +331,7 @@ pub trait ItemHandle: 'static + Send {
     fn show_toolbar(&self, cx: &AppContext) -> bool;
     fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>>;
     fn downgrade_item(&self) -> Box<dyn WeakItemHandle>;
+    fn workspace_settings<'a>(&self, cx: &'a AppContext) -> &'a WorkspaceSettings;
 }
 
 pub trait WeakItemHandle: Send + Sync {
@@ -401,6 +402,20 @@ impl<T: Item> ItemHandle for View<T> {
         result
     }
 
+    fn workspace_settings<'a>(&self, cx: &'a AppContext) -> &'a WorkspaceSettings {
+        if let Some(project_path) = self.project_path(cx) {
+            WorkspaceSettings::get(
+                Some(SettingsLocation {
+                    worktree_id: project_path.worktree_id.into(),
+                    path: &project_path.path,
+                }),
+                cx,
+            )
+        } else {
+            WorkspaceSettings::get_global(cx)
+        }
+    }
+
     fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
         let mut result = SmallVec::new();
         self.read(cx).for_each_project_item(cx, &mut |_, item| {
@@ -567,7 +582,8 @@ impl<T: Item> ItemHandle for View<T> {
                         }
 
                         ItemEvent::Edit => {
-                            let autosave = WorkspaceSettings::get_global(cx).autosave;
+                            let autosave = item.workspace_settings(cx).autosave;
+
                             if let AutosaveSetting::AfterDelay { milliseconds } = autosave {
                                 let delay = Duration::from_millis(milliseconds);
                                 let item = item.clone();
@@ -584,8 +600,8 @@ impl<T: Item> ItemHandle for View<T> {
             ));
 
             cx.on_blur(&self.focus_handle(cx), move |workspace, cx| {
-                if WorkspaceSettings::get_global(cx).autosave == AutosaveSetting::OnFocusChange {
-                    if let Some(item) = weak_item.upgrade() {
+                if let Some(item) = weak_item.upgrade() {
+                    if item.workspace_settings(cx).autosave == AutosaveSetting::OnFocusChange {
                         Pane::autosave_item(&item, workspace.project.clone(), cx)
                             .detach_and_log_err(cx);
                     }

crates/workspace/src/pane.rs 🔗

@@ -1421,7 +1421,7 @@ impl Pane {
             if save_intent == SaveIntent::Close {
                 let will_autosave = cx.update(|cx| {
                     matches!(
-                        WorkspaceSettings::get_global(cx).autosave,
+                        item.workspace_settings(cx).autosave,
                         AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
                     ) && Self::can_autosave_item(item, cx)
                 })?;
@@ -1490,13 +1490,12 @@ impl Pane {
         project: Model<Project>,
         cx: &mut WindowContext,
     ) -> Task<Result<()>> {
-        let format = if let AutosaveSetting::AfterDelay { .. } =
-            WorkspaceSettings::get_global(cx).autosave
-        {
-            false
-        } else {
-            true
-        };
+        let format =
+            if let AutosaveSetting::AfterDelay { .. } = item.workspace_settings(cx).autosave {
+                false
+            } else {
+                true
+            };
         if Self::can_autosave_item(item, cx) {
             item.save(format, project, cx)
         } else {

crates/workspace/src/workspace.rs 🔗

@@ -3502,11 +3502,11 @@ impl Workspace {
                     if let Some(item) = pane.active_item() {
                         item.workspace_deactivated(cx);
                     }
-                    if matches!(
-                        WorkspaceSettings::get_global(cx).autosave,
-                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
-                    ) {
-                        for item in pane.items() {
+                    for item in pane.items() {
+                        if matches!(
+                            item.workspace_settings(cx).autosave,
+                            AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
+                        ) {
                             Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
                                 .detach_and_log_err(cx);
                         }

crates/worktree/src/worktree.rs 🔗

@@ -70,6 +70,12 @@ pub const FS_WATCH_LATENCY: Duration = Duration::from_millis(100);
 #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
 pub struct WorktreeId(usize);
 
+impl From<WorktreeId> for usize {
+    fn from(value: WorktreeId) -> Self {
+        value.0
+    }
+}
+
 /// A set of local or remote files that are being opened as part of a project.
 /// Responsible for tracking related FS (for local)/collab (for remote) events and corresponding updates.
 /// Stores git repositories data and the diagnostics for the file(s).