From c54e294965df8e05b76773f04e879e68a0cc3687 Mon Sep 17 00:00:00 2001 From: itsaphel Date: Wed, 17 Sep 2025 14:08:29 +0100 Subject: [PATCH] Autosave files on close, when setting is `afterDelay` (#36929) Closes https://github.com/zed-industries/zed/issues/12149 Closes #35524 Release Notes: - Improved autosave behavior, to prevent a confirmation dialog when quickly closing files and using the `afterDelay` setting --------- Co-authored-by: MrSubidubi --- crates/search/src/project_search.rs | 8 +---- crates/workspace/src/pane.rs | 6 ++-- crates/workspace/src/workspace.rs | 36 ++++++++++++++++++++-- crates/workspace/src/workspace_settings.rs | 11 +++++++ docs/src/configuring-zed.md | 2 ++ 5 files changed, 49 insertions(+), 14 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index eaad5dad65b75c1fffcabb400eb6a2dea1fb1811..33ccd095687c448abc5d8b685da22e89ab59cbc8 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1071,18 +1071,12 @@ impl ProjectSearchView { window: &mut Window, cx: &mut Context, ) -> Task> { - use workspace::AutosaveSetting; - let project = self.entity.read(cx).project.clone(); let can_autosave = self.results_editor.can_autosave(cx); let autosave_setting = self.results_editor.workspace_settings(cx).autosave; - let will_autosave = can_autosave - && matches!( - autosave_setting, - AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange - ); + let will_autosave = can_autosave && autosave_setting.should_save_on_close(); let is_dirty = self.is_dirty(cx); diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 4ce8a890237f97db5596f55f607a603a2695ab7b..0418be7a2fbc858c8623400e65fed0f96e2cdb61 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2082,10 +2082,8 @@ impl Pane { } else if is_dirty && (can_save || can_save_as) { if save_intent == SaveIntent::Close { let will_autosave = cx.update(|_window, cx| { - matches!( - item.workspace_settings(cx).autosave, - AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange - ) && item.can_autosave(cx) + item.can_autosave(cx) + && item.workspace_settings(cx).autosave.should_save_on_close() })?; if !will_autosave { let item_id = item.item_id(); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 23d611964c3cd1b0284521c9444b6952748a5db9..5772695310e1258bee2a62953ad4bcc3620cd4ee 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -8737,6 +8737,36 @@ mod tests { cx.executor().advance_clock(Duration::from_millis(250)); item.read_with(cx, |item, _| assert_eq!(item.save_count, 4)); + // Autosave after delay, should save earlier than delay if tab is closed + item.update(cx, |item, cx| { + item.is_dirty = true; + cx.emit(ItemEvent::Edit); + }); + cx.executor().advance_clock(Duration::from_millis(250)); + item.read_with(cx, |item, _| assert_eq!(item.save_count, 4)); + + // // Ensure auto save with delay saves the item on close, even if the timer hasn't yet run out. + pane.update_in(cx, |pane, window, cx| { + pane.close_items(window, cx, SaveIntent::Close, move |id| id == item_id) + }) + .await + .unwrap(); + assert!(!cx.has_pending_prompt()); + item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); + + // Add the item again, ensuring autosave is prevented if the underlying file has been deleted. + workspace.update_in(cx, |workspace, window, cx| { + workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx); + }); + item.update_in(cx, |item, _window, cx| { + item.is_dirty = true; + for project_item in &mut item.project_items { + project_item.update(cx, |project_item, _| project_item.is_dirty = true); + } + }); + cx.run_until_parked(); + item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); + // Autosave on focus change, ensuring closing the tab counts as such. item.update(cx, |item, cx| { SettingsStore::update_global(cx, |settings, cx| { @@ -8756,7 +8786,7 @@ mod tests { .await .unwrap(); assert!(!cx.has_pending_prompt()); - item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); + item.read_with(cx, |item, _| assert_eq!(item.save_count, 6)); // Add the item again, ensuring autosave is prevented if the underlying file has been deleted. workspace.update_in(cx, |workspace, window, cx| { @@ -8770,7 +8800,7 @@ mod tests { window.blur(); }); cx.run_until_parked(); - item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); + item.read_with(cx, |item, _| assert_eq!(item.save_count, 6)); // Ensure autosave is prevented for deleted files also when closing the buffer. let _close_items = pane.update_in(cx, |pane, window, cx| { @@ -8778,7 +8808,7 @@ mod tests { }); cx.run_until_parked(); assert!(cx.has_pending_prompt()); - item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); + item.read_with(cx, |item, _| assert_eq!(item.save_count, 6)); } #[gpui::test] diff --git a/crates/workspace/src/workspace_settings.rs b/crates/workspace/src/workspace_settings.rs index 8868f3190575ac4b861e0619732890f477d83b69..a86f81f442b0437494d5cc2915a0a0b7d15ca7f3 100644 --- a/crates/workspace/src/workspace_settings.rs +++ b/crates/workspace/src/workspace_settings.rs @@ -254,6 +254,17 @@ pub enum AutosaveSetting { OnWindowChange, } +impl AutosaveSetting { + pub fn should_save_on_close(&self) -> bool { + matches!( + &self, + AutosaveSetting::OnFocusChange + | AutosaveSetting::OnWindowChange + | AutosaveSetting::AfterDelay { .. } + ) + } +} + #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum PaneSplitDirectionHorizontal { diff --git a/docs/src/configuring-zed.md b/docs/src/configuring-zed.md index a7b89dc5207e0acea422401b0ce77946c7d484c6..6c25d62e291f91f3faf4dc77e5a6dab3b8637ca8 100644 --- a/docs/src/configuring-zed.md +++ b/docs/src/configuring-zed.md @@ -246,6 +246,8 @@ Define extensions which should be installed (`true`) or never installed (`false` } ``` +Note that a save will be triggered when an unsaved tab is closed, even if this is earlier than the configured inactivity period. + ## Autoscroll on Clicks - Description: Whether to scroll when clicking near the edge of the visible text area.