Show warning when deleting files with unsaved changes (#20172)

Closes #9905

<img width="172" alt="Screenshot 2024-11-04 at 10 16 34 AM"
src="https://github.com/user-attachments/assets/5fa84e06-bcb9-471d-adab-e06881fbd3ca">
<img width="172" alt="Screenshot 2024-11-04 at 10 16 22 AM"
src="https://github.com/user-attachments/assets/d7def162-e910-4061-a160-6178c9d344e5">
<img width="172" alt="Screenshot 2024-11-04 at 10 17 17 AM"
src="https://github.com/user-attachments/assets/43c7e4fe-1b71-4786-bc05-44f34ed15dc5">
<img width="172" alt="Screenshot 2024-11-04 at 10 17 09 AM"
src="https://github.com/user-attachments/assets/17263782-c706-44b2-acbc-c3d2d14c20ac">


Release Notes:

- When deleting or trashing files, the confirmation prompt now warns if
files have unsaved changes.
This commit is contained in:
Richard Feldman 2024-11-07 11:40:33 -05:00 committed by GitHub
parent e85ab077be
commit 453c41205b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 41 additions and 8 deletions

View file

@ -3970,6 +3970,21 @@ impl Project {
self.worktree_store.read(cx).worktree_metadata_protos(cx)
}
/// Iterator of all open buffers that have unsaved changes
pub fn dirty_buffers<'a>(
&'a self,
cx: &'a AppContext,
) -> impl Iterator<Item = ProjectPath> + 'a {
self.buffer_store.read(cx).buffers().filter_map(|buf| {
let buf = buf.read(cx);
if buf.is_dirty() {
buf.project_path(cx)
} else {
None
}
})
}
fn set_worktrees_from_proto(
&mut self,
worktrees: Vec<proto::WorktreeMetadata>,

View file

@ -1108,13 +1108,17 @@ impl ProjectPanel {
}
let project = self.project.read(cx);
let items_to_delete = self.marked_entries();
let mut dirty_buffers = 0;
let file_paths = items_to_delete
.into_iter()
.filter_map(|selection| {
let project_path = project.path_for_entry(selection.entry_id, cx)?;
dirty_buffers +=
project.dirty_buffers(cx).any(|path| path == project_path) as usize;
Some((
selection.entry_id,
project
.path_for_entry(selection.entry_id, cx)?
project_path
.path
.file_name()?
.to_string_lossy()
@ -1127,11 +1131,17 @@ impl ProjectPanel {
}
let answer = if !skip_prompt {
let operation = if trash { "Trash" } else { "Delete" };
let prompt = match file_paths.first() {
Some((_, path)) if file_paths.len() == 1 => {
let unsaved_warning = if dirty_buffers > 0 {
"\n\nIt has unsaved changes, which will be lost."
} else {
""
};
let prompt =
if let Some((_, path)) = file_paths.first().filter(|_| file_paths.len() == 1) {
format!("{operation} {path}?")
} else {
format!("{operation} {path}?{unsaved_warning}")
}
_ => {
const CUTOFF_POINT: usize = 10;
let names = if file_paths.len() > CUTOFF_POINT {
let truncated_path_counts = file_paths.len() - CUTOFF_POINT;
@ -1150,14 +1160,22 @@ impl ProjectPanel {
} else {
file_paths.iter().map(|(_, path)| path.clone()).collect()
};
let unsaved_warning = if dirty_buffers == 0 {
String::new()
} else if dirty_buffers == 1 {
"\n\n1 of these has unsaved changes, which will be lost.".to_string()
} else {
format!("\n\n{dirty_buffers} of these have unsaved changes, which will be lost.")
};
format!(
"Do you want to {} the following {} files?\n{}",
"Do you want to {} the following {} files?\n{}{unsaved_warning}",
operation.to_lowercase(),
file_paths.len(),
names.join("\n")
)
};
}
};
Some(cx.prompt(PromptLevel::Info, &prompt, None, &[operation, "Cancel"]))
} else {
None