Don't autosave unmodified buffers (#32626)

Closes https://github.com/zed-industries/zed/issues/12091

Proper redo of https://github.com/zed-industries/zed/pull/32603

Release Notes:

- Fixed formatting effects not triggered when saving unmodified
singleton buffers

---------

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-authored-by: Cole Miller <m@cole-miller.net>
This commit is contained in:
Kirill Bulatov 2025-06-13 01:12:14 +03:00 committed by GitHub
parent cd018da1ad
commit cef0c415f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 453 additions and 171 deletions

View file

@ -40,7 +40,7 @@ use ui::{IconDecorationKind, prelude::*};
use util::{ResultExt, TryFutureExt, paths::PathExt};
use workspace::{
CollaboratorId, ItemId, ItemNavHistory, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
item::{FollowableItem, Item, ItemEvent, ProjectItem},
item::{FollowableItem, Item, ItemEvent, ProjectItem, SaveOptions},
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
};
use workspace::{
@ -805,7 +805,7 @@ impl Item for Editor {
fn save(
&mut self,
format: bool,
options: SaveOptions,
project: Entity<Project>,
window: &mut Window,
cx: &mut Context<Self>,
@ -816,48 +816,54 @@ impl Item for Editor {
.into_iter()
.map(|handle| handle.read(cx).base_buffer().unwrap_or(handle.clone()))
.collect::<HashSet<_>>();
let save_non_dirty_buffers = self.save_non_dirty_buffers(cx);
cx.spawn_in(window, async move |editor, cx| {
if format {
editor
.update_in(cx, |editor, window, cx| {
editor.perform_format(
project.clone(),
FormatTrigger::Save,
FormatTarget::Buffers,
window,
cx,
)
// let mut buffers_to_save =
let buffers_to_save = if self.buffer.read(cx).is_singleton() && !options.autosave {
buffers.clone()
} else {
buffers
.iter()
.filter(|buffer| buffer.read(cx).is_dirty())
.cloned()
.collect()
};
cx.spawn_in(window, async move |this, cx| {
if options.format {
this.update_in(cx, |editor, window, cx| {
editor.perform_format(
project.clone(),
FormatTrigger::Save,
FormatTarget::Buffers(buffers_to_save.clone()),
window,
cx,
)
})?
.await?;
}
if !buffers_to_save.is_empty() {
project
.update(cx, |project, cx| {
project.save_buffers(buffers_to_save.clone(), cx)
})?
.await?;
}
if save_non_dirty_buffers {
project
.update(cx, |project, cx| project.save_buffers(buffers, cx))?
.await?;
} else {
// For multi-buffers, only format and save the buffers with changes.
// For clean buffers, we simulate saving by calling `Buffer::did_save`,
// so that language servers or other downstream listeners of save events get notified.
let (dirty_buffers, clean_buffers) = buffers.into_iter().partition(|buffer| {
buffer
.read_with(cx, |buffer, _| buffer.is_dirty() || buffer.has_conflict())
.unwrap_or(false)
});
// Notify about clean buffers for language server events
let buffers_that_were_not_saved: Vec<_> = buffers
.into_iter()
.filter(|b| !buffers_to_save.contains(b))
.collect();
project
.update(cx, |project, cx| project.save_buffers(dirty_buffers, cx))?
.await?;
for buffer in clean_buffers {
buffer
.update(cx, |buffer, cx| {
let version = buffer.saved_version().clone();
let mtime = buffer.saved_mtime();
buffer.did_save(version, mtime, cx);
})
.ok();
}
for buffer in buffers_that_were_not_saved {
buffer
.update(cx, |buffer, cx| {
let version = buffer.saved_version().clone();
let mtime = buffer.saved_mtime();
buffer.did_save(version, mtime, cx);
})
.ok();
}
Ok(())