From d3bc561f26e8077a1de8ffb98b7d9e012133980c Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Sun, 1 Jun 2025 11:15:33 -0400 Subject: [PATCH] Disable close clean menu item when all are dirty (#31859) This PR disables the "Close Clean" tab context menu action if all items are dirty. SCR-20250601-kaev SCR-20250601-kahl Also did a bit more general refactoring. Release Notes: - N/A --- crates/workspace/src/pane.rs | 148 +++++++++++++++-------------------- 1 file changed, 63 insertions(+), 85 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 4851326960..082f99cc6d 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -392,6 +392,11 @@ pub struct DraggedTab { impl EventEmitter for Pane {} +pub enum Side { + Left, + Right, +} + impl Pane { pub fn new( workspace: WeakEntity, @@ -1314,63 +1319,31 @@ impl Pane { }) } - pub fn close_items_to_the_left( - &mut self, - action: &CloseItemsToTheLeft, - window: &mut Window, - cx: &mut Context, - ) -> Task> { - if self.items.is_empty() { - return Task::ready(Ok(())); - } - - let active_item_id = self.active_item_id(); - let pinned_item_ids = self.pinned_item_ids(); - - self.close_items_to_the_left_by_id(active_item_id, action, pinned_item_ids, window, cx) - } - pub fn close_items_to_the_left_by_id( &mut self, - item_id: EntityId, + item_id: Option, action: &CloseItemsToTheLeft, - pinned_item_ids: HashSet, window: &mut Window, cx: &mut Context, ) -> Task> { - if self.items.is_empty() { - return Task::ready(Ok(())); - } - - let to_the_left_item_ids = self.to_the_left_item_ids(item_id); - - self.close_items(window, cx, SaveIntent::Close, move |item_id| { - to_the_left_item_ids.contains(&item_id) - && (action.close_pinned || !pinned_item_ids.contains(&item_id)) - }) - } - - pub fn close_items_to_the_right( - &mut self, - action: &CloseItemsToTheRight, - window: &mut Window, - cx: &mut Context, - ) -> Task> { - if self.items.is_empty() { - return Task::ready(Ok(())); - } - - let active_item_id = self.active_item_id(); - let pinned_item_ids = self.pinned_item_ids(); - - self.close_items_to_the_right_by_id(active_item_id, action, pinned_item_ids, window, cx) + self.close_items_to_the_side_by_id(item_id, Side::Left, action.close_pinned, window, cx) } pub fn close_items_to_the_right_by_id( &mut self, - item_id: EntityId, + item_id: Option, action: &CloseItemsToTheRight, - pinned_item_ids: HashSet, + window: &mut Window, + cx: &mut Context, + ) -> Task> { + self.close_items_to_the_side_by_id(item_id, Side::Right, action.close_pinned, window, cx) + } + + pub fn close_items_to_the_side_by_id( + &mut self, + item_id: Option, + side: Side, + close_pinned: bool, window: &mut Window, cx: &mut Context, ) -> Task> { @@ -1378,11 +1351,13 @@ impl Pane { return Task::ready(Ok(())); } - let to_the_right_item_ids = self.to_the_right_item_ids(item_id); + let item_id = item_id.unwrap_or_else(|| self.active_item_id()); + let to_the_side_item_ids = self.to_the_side_item_ids(item_id, side); + let pinned_item_ids = self.pinned_item_ids(); self.close_items(window, cx, SaveIntent::Close, move |item_id| { - to_the_right_item_ids.contains(&item_id) - && (action.close_pinned || !pinned_item_ids.contains(&item_id)) + to_the_side_item_ids.contains(&item_id) + && (close_pinned || !pinned_item_ids.contains(&item_id)) }) } @@ -2376,6 +2351,7 @@ impl Pane { let total_items = self.items.len(); let has_items_to_left = ix > 0; let has_items_to_right = ix < total_items - 1; + let has_clean_items = self.items.iter().any(|item| !item.is_dirty(cx)); let is_pinned = self.is_tab_pinned(ix); let pane = cx.entity().downgrade(); let menu_context = item.item_focus_handle(cx); @@ -2436,9 +2412,8 @@ impl Pane { .disabled(!has_items_to_left) .handler(window.handler_for(&pane, move |pane, window, cx| { pane.close_items_to_the_left_by_id( - item_id, + Some(item_id), &close_items_to_the_left_action, - pane.pinned_item_ids(), window, cx, ) @@ -2451,9 +2426,8 @@ impl Pane { .disabled(!has_items_to_right) .handler(window.handler_for(&pane, move |pane, window, cx| { pane.close_items_to_the_right_by_id( - item_id, + Some(item_id), &close_items_to_the_right_action, - pane.pinned_item_ids(), window, cx, ) @@ -2461,14 +2435,19 @@ impl Pane { })), )) .separator() - .entry( - "Close Clean", - Some(Box::new(close_clean_items_action.clone())), - window.handler_for(&pane, move |pane, window, cx| { - pane.close_clean_items(&close_clean_items_action, window, cx) + .item(ContextMenuItem::Entry( + ContextMenuEntry::new("Close Clean") + .action(Box::new(close_clean_items_action.clone())) + .disabled(!has_clean_items) + .handler(window.handler_for(&pane, move |pane, window, cx| { + pane.close_clean_items( + &close_clean_items_action, + window, + cx, + ) .detach_and_log_err(cx) - }), - ) + })), + )) .entry( "Close All", Some(Box::new(close_all_items_action.clone())), @@ -3102,19 +3081,20 @@ impl Pane { .collect() } - fn to_the_left_item_ids(&self, item_id: EntityId) -> HashSet { - self.items() - .take_while(|item| item.item_id() != item_id) - .map(|item| item.item_id()) - .collect() - } - - fn to_the_right_item_ids(&self, item_id: EntityId) -> HashSet { - self.items() - .rev() - .take_while(|item| item.item_id() != item_id) - .map(|item| item.item_id()) - .collect() + fn to_the_side_item_ids(&self, item_id: EntityId, side: Side) -> HashSet { + match side { + Side::Left => self + .items() + .take_while(|item| item.item_id() != item_id) + .map(|item| item.item_id()) + .collect(), + Side::Right => self + .items() + .rev() + .take_while(|item| item.item_id() != item_id) + .map(|item| item.item_id()) + .collect(), + } } pub fn drag_split_direction(&self) -> Option { @@ -3333,13 +3313,13 @@ impl Render for Pane { ) .on_action(cx.listener( |pane: &mut Self, action: &CloseItemsToTheLeft, window, cx| { - pane.close_items_to_the_left(action, window, cx) + pane.close_items_to_the_left_by_id(None, action, window, cx) .detach_and_log_err(cx) }, )) .on_action(cx.listener( |pane: &mut Self, action: &CloseItemsToTheRight, window, cx| { - pane.close_items_to_the_right(action, window, cx) + pane.close_items_to_the_right_by_id(None, action, window, cx) .detach_and_log_err(cx) }, )) @@ -3349,12 +3329,6 @@ impl Render for Pane { .detach_and_log_err(cx) }), ) - .on_action( - cx.listener(|pane: &mut Self, action: &CloseActiveItem, window, cx| { - pane.close_active_item(action, window, cx) - .detach_and_log_err(cx) - }), - ) .on_action( cx.listener(|pane: &mut Self, action: &RevealInProjectPanel, _, cx| { let entry_id = action @@ -4436,7 +4410,8 @@ mod tests { set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); pane.update_in(cx, |pane, window, cx| { - pane.close_items_to_the_left( + pane.close_items_to_the_left_by_id( + None, &CloseItemsToTheLeft { close_pinned: false, }, @@ -4462,7 +4437,8 @@ mod tests { set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); pane.update_in(cx, |pane, window, cx| { - pane.close_items_to_the_right( + pane.close_items_to_the_right_by_id( + None, &CloseItemsToTheRight { close_pinned: false, }, @@ -4779,7 +4755,8 @@ mod tests { .unwrap(); pane.update_in(cx, |pane, window, cx| { - pane.close_items_to_the_right( + pane.close_items_to_the_right_by_id( + None, &CloseItemsToTheRight { close_pinned: false, }, @@ -4791,7 +4768,8 @@ mod tests { .unwrap(); pane.update_in(cx, |pane, window, cx| { - pane.close_items_to_the_left( + pane.close_items_to_the_left_by_id( + None, &CloseItemsToTheLeft { close_pinned: false, },