Fix item closing overly triggering save dialogues (#21374)
Closes https://github.com/zed-industries/zed/issues/12029 Allows to introspect project items inside items more deeply, checking them for being dirty. For that: * renames `project::Item` into `project::ProjectItem` * adds an `is_dirty(&self) -> bool` method to the renamed trait * changes the closing logic to only care about dirty project items when checking for save prompts conditions * save prompts are raised only if the item is singleton without a project path; or if the item has dirty project items that are not open elsewhere Release Notes: - Fixed item closing overly triggering save dialogues
This commit is contained in:
parent
c2cd84a749
commit
28849dd2a8
19 changed files with 600 additions and 85 deletions
|
@ -1295,10 +1295,12 @@ impl Pane {
|
|||
) -> Task<Result<()>> {
|
||||
// Find the items to close.
|
||||
let mut items_to_close = Vec::new();
|
||||
let mut item_ids_to_close = HashSet::default();
|
||||
let mut dirty_items = Vec::new();
|
||||
for item in &self.items {
|
||||
if should_close(item.item_id()) {
|
||||
items_to_close.push(item.boxed_clone());
|
||||
item_ids_to_close.insert(item.item_id());
|
||||
if item.is_dirty(cx) {
|
||||
dirty_items.push(item.boxed_clone());
|
||||
}
|
||||
|
@ -1339,16 +1341,23 @@ impl Pane {
|
|||
}
|
||||
}
|
||||
let mut saved_project_items_ids = HashSet::default();
|
||||
for item in items_to_close.clone() {
|
||||
// Find the item's current index and its set of project item models. Avoid
|
||||
for item_to_close in items_to_close {
|
||||
// Find the item's current index and its set of dirty project item models. Avoid
|
||||
// storing these in advance, in case they have changed since this task
|
||||
// was started.
|
||||
let (item_ix, mut project_item_ids) = pane.update(&mut cx, |pane, cx| {
|
||||
(pane.index_for_item(&*item), item.project_item_model_ids(cx))
|
||||
})?;
|
||||
let item_ix = if let Some(ix) = item_ix {
|
||||
ix
|
||||
} else {
|
||||
let mut dirty_project_item_ids = Vec::new();
|
||||
let Some(item_ix) = pane.update(&mut cx, |pane, cx| {
|
||||
item_to_close.for_each_project_item(
|
||||
cx,
|
||||
&mut |project_item_id, project_item| {
|
||||
if project_item.is_dirty() {
|
||||
dirty_project_item_ids.push(project_item_id);
|
||||
}
|
||||
},
|
||||
);
|
||||
pane.index_for_item(&*item_to_close)
|
||||
})?
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
@ -1356,27 +1365,34 @@ impl Pane {
|
|||
// in the workspace, AND that the user has not already been prompted to save.
|
||||
// If there are any such project entries, prompt the user to save this item.
|
||||
let project = workspace.update(&mut cx, |workspace, cx| {
|
||||
for item in workspace.items(cx) {
|
||||
if !items_to_close
|
||||
.iter()
|
||||
.any(|item_to_close| item_to_close.item_id() == item.item_id())
|
||||
{
|
||||
let other_project_item_ids = item.project_item_model_ids(cx);
|
||||
project_item_ids.retain(|id| !other_project_item_ids.contains(id));
|
||||
for open_item in workspace.items(cx) {
|
||||
let open_item_id = open_item.item_id();
|
||||
if !item_ids_to_close.contains(&open_item_id) {
|
||||
let other_project_item_ids = open_item.project_item_model_ids(cx);
|
||||
dirty_project_item_ids
|
||||
.retain(|id| !other_project_item_ids.contains(id));
|
||||
}
|
||||
}
|
||||
workspace.project().clone()
|
||||
})?;
|
||||
let should_save = project_item_ids
|
||||
let should_save = dirty_project_item_ids
|
||||
.iter()
|
||||
.any(|id| saved_project_items_ids.insert(*id));
|
||||
.any(|id| saved_project_items_ids.insert(*id))
|
||||
// Always propose to save singleton files without any project paths: those cannot be saved via multibuffer, as require a file path selection modal.
|
||||
|| cx
|
||||
.update(|cx| {
|
||||
item_to_close.is_dirty(cx)
|
||||
&& item_to_close.is_singleton(cx)
|
||||
&& item_to_close.project_path(cx).is_none()
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
if should_save
|
||||
&& !Self::save_item(
|
||||
project.clone(),
|
||||
&pane,
|
||||
item_ix,
|
||||
&*item,
|
||||
&*item_to_close,
|
||||
save_intent,
|
||||
&mut cx,
|
||||
)
|
||||
|
@ -1390,7 +1406,7 @@ impl Pane {
|
|||
if let Some(item_ix) = pane
|
||||
.items
|
||||
.iter()
|
||||
.position(|i| i.item_id() == item.item_id())
|
||||
.position(|i| i.item_id() == item_to_close.item_id())
|
||||
{
|
||||
pane.remove_item(item_ix, false, true, cx);
|
||||
}
|
||||
|
@ -3725,9 +3741,18 @@ mod tests {
|
|||
|
||||
assert_item_labels(&pane, [], cx);
|
||||
|
||||
add_labeled_item(&pane, "A", true, cx);
|
||||
add_labeled_item(&pane, "B", true, cx);
|
||||
add_labeled_item(&pane, "C", true, cx);
|
||||
add_labeled_item(&pane, "A", true, cx).update(cx, |item, cx| {
|
||||
item.project_items
|
||||
.push(TestProjectItem::new(1, "A.txt", cx))
|
||||
});
|
||||
add_labeled_item(&pane, "B", true, cx).update(cx, |item, cx| {
|
||||
item.project_items
|
||||
.push(TestProjectItem::new(2, "B.txt", cx))
|
||||
});
|
||||
add_labeled_item(&pane, "C", true, cx).update(cx, |item, cx| {
|
||||
item.project_items
|
||||
.push(TestProjectItem::new(3, "C.txt", cx))
|
||||
});
|
||||
assert_item_labels(&pane, ["A^", "B^", "C*^"], cx);
|
||||
|
||||
let save = pane
|
||||
|
@ -3746,6 +3771,30 @@ mod tests {
|
|||
cx.simulate_prompt_answer(2);
|
||||
save.await.unwrap();
|
||||
assert_item_labels(&pane, [], cx);
|
||||
|
||||
add_labeled_item(&pane, "A", true, cx);
|
||||
add_labeled_item(&pane, "B", true, cx);
|
||||
add_labeled_item(&pane, "C", true, cx);
|
||||
assert_item_labels(&pane, ["A^", "B^", "C*^"], cx);
|
||||
let save = pane
|
||||
.update(cx, |pane, cx| {
|
||||
pane.close_all_items(
|
||||
&CloseAllItems {
|
||||
save_intent: None,
|
||||
close_pinned: false,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
cx.simulate_prompt_answer(2);
|
||||
cx.executor().run_until_parked();
|
||||
cx.simulate_prompt_answer(2);
|
||||
cx.executor().run_until_parked();
|
||||
save.await.unwrap();
|
||||
assert_item_labels(&pane, ["A*^", "B^", "C^"], cx);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -3833,14 +3882,14 @@ mod tests {
|
|||
}
|
||||
|
||||
// Assert the item label, with the active item label suffixed with a '*'
|
||||
#[track_caller]
|
||||
fn assert_item_labels<const COUNT: usize>(
|
||||
pane: &View<Pane>,
|
||||
expected_states: [&str; COUNT],
|
||||
cx: &mut VisualTestContext,
|
||||
) {
|
||||
pane.update(cx, |pane, cx| {
|
||||
let actual_states = pane
|
||||
.items
|
||||
let actual_states = pane.update(cx, |pane, cx| {
|
||||
pane.items
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(ix, item)| {
|
||||
|
@ -3859,12 +3908,11 @@ mod tests {
|
|||
}
|
||||
state
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(
|
||||
actual_states, expected_states,
|
||||
"pane items do not match expectation"
|
||||
);
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
assert_eq!(
|
||||
actual_states, expected_states,
|
||||
"pane items do not match expectation"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue