@@ -99,6 +99,8 @@ pub struct ActivateItem(pub usize);
#[serde(deny_unknown_fields)]
pub struct CloseActiveItem {
pub save_intent: Option<SaveIntent>,
+ #[serde(default)]
+ pub close_pinned: bool,
}
#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
@@ -1224,6 +1226,37 @@ impl Pane {
return None;
}
+ if self.is_tab_pinned(self.active_item_index) && !action.close_pinned {
+ // Activate any non-pinned tab in same pane
+ let non_pinned_tab_index = self
+ .items()
+ .enumerate()
+ .find(|(index, _item)| !self.is_tab_pinned(*index))
+ .map(|(index, _item)| index);
+ if let Some(index) = non_pinned_tab_index {
+ self.activate_item(index, false, false, window, cx);
+ return None;
+ }
+
+ // Activate any non-pinned tab in different pane
+ let current_pane = cx.entity();
+ self.workspace
+ .update(cx, |workspace, cx| {
+ let panes = workspace.center.panes();
+ let pane_with_unpinned_tab = panes.iter().find(|pane| {
+ if **pane == ¤t_pane {
+ return false;
+ }
+ pane.read(cx).has_unpinned_tabs()
+ });
+ if let Some(pane) = pane_with_unpinned_tab {
+ pane.update(cx, |pane, cx| pane.activate_unpinned_tab(window, cx));
+ }
+ })
+ .ok();
+
+ return None;
+ };
let active_item_id = self.items[self.active_item_index].item_id();
Some(self.close_item_by_id(
active_item_id,
@@ -2105,6 +2138,24 @@ impl Pane {
self.pinned_tab_count != 0
}
+ fn has_unpinned_tabs(&self) -> bool {
+ self.pinned_tab_count < self.items.len()
+ }
+
+ fn activate_unpinned_tab(&mut self, window: &mut Window, cx: &mut Context<Self>) {
+ if self.items.is_empty() {
+ return;
+ }
+ let Some(index) = self
+ .items()
+ .enumerate()
+ .find_map(|(index, _item)| (!self.is_tab_pinned(index)).then_some(index))
+ else {
+ return;
+ };
+ self.activate_item(index, true, true, window, cx);
+ }
+
fn render_tab(
&self,
ix: usize,
@@ -2280,7 +2331,10 @@ impl Pane {
pane.unpin_tab_at(ix, window, cx);
}))
} else {
- end_slot_action = &CloseActiveItem { save_intent: None };
+ end_slot_action = &CloseActiveItem {
+ save_intent: None,
+ close_pinned: false,
+ };
end_slot_tooltip_text = "Close Tab";
IconButton::new("close tab", IconName::Close)
.when(!always_show_close_button, |button| {
@@ -2350,7 +2404,10 @@ impl Pane {
menu = menu
.entry(
"Close",
- Some(Box::new(CloseActiveItem { save_intent: None })),
+ Some(Box::new(CloseActiveItem {
+ save_intent: None,
+ close_pinned: true,
+ })),
window.handler_for(&pane, move |pane, window, cx| {
pane.close_item_by_id(item_id, SaveIntent::Close, window, cx)
.detach_and_log_err(cx);
@@ -2991,14 +3048,9 @@ impl Pane {
self.items
.iter()
- .map(|item| item.item_id())
- .filter(|item_id| {
- if let Some(ix) = self.index_for_item_id(*item_id) {
- self.is_tab_pinned(ix)
- } else {
- true
- }
- })
+ .enumerate()
+ .filter(|(index, _item)| self.is_tab_pinned(*index))
+ .map(|(_, item)| item.item_id())
.collect()
}
@@ -3561,7 +3613,14 @@ mod tests {
pane.update_in(cx, |pane, window, cx| {
assert!(pane
- .close_active_item(&CloseActiveItem { save_intent: None }, window, cx)
+ .close_active_item(
+ &CloseActiveItem {
+ save_intent: None,
+ close_pinned: false
+ },
+ window,
+ cx
+ )
.is_none())
});
}
@@ -3902,7 +3961,14 @@ mod tests {
assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx);
pane.update_in(cx, |pane, window, cx| {
- pane.close_active_item(&CloseActiveItem { save_intent: None }, window, cx)
+ pane.close_active_item(
+ &CloseActiveItem {
+ save_intent: None,
+ close_pinned: false,
+ },
+ window,
+ cx,
+ )
})
.unwrap()
.await
@@ -3915,7 +3981,14 @@ mod tests {
assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
pane.update_in(cx, |pane, window, cx| {
- pane.close_active_item(&CloseActiveItem { save_intent: None }, window, cx)
+ pane.close_active_item(
+ &CloseActiveItem {
+ save_intent: None,
+ close_pinned: false,
+ },
+ window,
+ cx,
+ )
})
.unwrap()
.await
@@ -3923,7 +3996,14 @@ mod tests {
assert_item_labels(&pane, ["A", "B*", "C"], cx);
pane.update_in(cx, |pane, window, cx| {
- pane.close_active_item(&CloseActiveItem { save_intent: None }, window, cx)
+ pane.close_active_item(
+ &CloseActiveItem {
+ save_intent: None,
+ close_pinned: false,
+ },
+ window,
+ cx,
+ )
})
.unwrap()
.await
@@ -3931,7 +4011,14 @@ mod tests {
assert_item_labels(&pane, ["A", "C*"], cx);
pane.update_in(cx, |pane, window, cx| {
- pane.close_active_item(&CloseActiveItem { save_intent: None }, window, cx)
+ pane.close_active_item(
+ &CloseActiveItem {
+ save_intent: None,
+ close_pinned: false,
+ },
+ window,
+ cx,
+ )
})
.unwrap()
.await
@@ -3967,7 +4054,14 @@ mod tests {
assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx);
pane.update_in(cx, |pane, window, cx| {
- pane.close_active_item(&CloseActiveItem { save_intent: None }, window, cx)
+ pane.close_active_item(
+ &CloseActiveItem {
+ save_intent: None,
+ close_pinned: false,
+ },
+ window,
+ cx,
+ )
})
.unwrap()
.await
@@ -3980,7 +4074,14 @@ mod tests {
assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
pane.update_in(cx, |pane, window, cx| {
- pane.close_active_item(&CloseActiveItem { save_intent: None }, window, cx)
+ pane.close_active_item(
+ &CloseActiveItem {
+ save_intent: None,
+ close_pinned: false,
+ },
+ window,
+ cx,
+ )
})
.unwrap()
.await
@@ -3988,7 +4089,14 @@ mod tests {
assert_item_labels(&pane, ["A", "B", "C*"], cx);
pane.update_in(cx, |pane, window, cx| {
- pane.close_active_item(&CloseActiveItem { save_intent: None }, window, cx)
+ pane.close_active_item(
+ &CloseActiveItem {
+ save_intent: None,
+ close_pinned: false,
+ },
+ window,
+ cx,
+ )
})
.unwrap()
.await
@@ -3996,7 +4104,14 @@ mod tests {
assert_item_labels(&pane, ["A", "B*"], cx);
pane.update_in(cx, |pane, window, cx| {
- pane.close_active_item(&CloseActiveItem { save_intent: None }, window, cx)
+ pane.close_active_item(
+ &CloseActiveItem {
+ save_intent: None,
+ close_pinned: false,
+ },
+ window,
+ cx,
+ )
})
.unwrap()
.await
@@ -4032,7 +4147,14 @@ mod tests {
assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx);
pane.update_in(cx, |pane, window, cx| {
- pane.close_active_item(&CloseActiveItem { save_intent: None }, window, cx)
+ pane.close_active_item(
+ &CloseActiveItem {
+ save_intent: None,
+ close_pinned: false,
+ },
+ window,
+ cx,
+ )
})
.unwrap()
.await
@@ -4045,7 +4167,14 @@ mod tests {
assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
pane.update_in(cx, |pane, window, cx| {
- pane.close_active_item(&CloseActiveItem { save_intent: None }, window, cx)
+ pane.close_active_item(
+ &CloseActiveItem {
+ save_intent: None,
+ close_pinned: false,
+ },
+ window,
+ cx,
+ )
})
.unwrap()
.await
@@ -4058,7 +4187,14 @@ mod tests {
assert_item_labels(&pane, ["A*", "B", "C"], cx);
pane.update_in(cx, |pane, window, cx| {
- pane.close_active_item(&CloseActiveItem { save_intent: None }, window, cx)
+ pane.close_active_item(
+ &CloseActiveItem {
+ save_intent: None,
+ close_pinned: false,
+ },
+ window,
+ cx,
+ )
})
.unwrap()
.await
@@ -4066,7 +4202,14 @@ mod tests {
assert_item_labels(&pane, ["B*", "C"], cx);
pane.update_in(cx, |pane, window, cx| {
- pane.close_active_item(&CloseActiveItem { save_intent: None }, window, cx)
+ pane.close_active_item(
+ &CloseActiveItem {
+ save_intent: None,
+ close_pinned: false,
+ },
+ window,
+ cx,
+ )
})
.unwrap()
.await
@@ -4300,7 +4443,7 @@ mod tests {
let project = Project::test(fs, None, cx).await;
let (workspace, cx) =
- cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
+ cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
let item_a = add_labeled_item(&pane, "A", false, cx);
@@ -4326,6 +4469,71 @@ mod tests {
assert_item_labels(&pane, [], cx);
}
+ #[gpui::test]
+ async fn test_close_pinned_tab_with_non_pinned_in_same_pane(cx: &mut TestAppContext) {
+ init_test(cx);
+ let fs = FakeFs::new(cx.executor());
+ let project = Project::test(fs, None, cx).await;
+ let (workspace, cx) =
+ cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
+
+ // Non-pinned tabs in same pane
+ let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
+ add_labeled_item(&pane, "A", false, cx);
+ add_labeled_item(&pane, "B", false, cx);
+ add_labeled_item(&pane, "C", false, cx);
+ pane.update_in(cx, |pane, window, cx| {
+ pane.pin_tab_at(0, window, cx);
+ });
+ set_labeled_items(&pane, ["A*", "B", "C"], cx);
+ pane.update_in(cx, |pane, window, cx| {
+ pane.close_active_item(
+ &CloseActiveItem {
+ save_intent: None,
+ close_pinned: false,
+ },
+ window,
+ cx,
+ );
+ });
+ // Non-pinned tab should be active
+ assert_item_labels(&pane, ["A", "B*", "C"], cx);
+ }
+
+ #[gpui::test]
+ async fn test_close_pinned_tab_with_non_pinned_in_different_pane(cx: &mut TestAppContext) {
+ init_test(cx);
+ let fs = FakeFs::new(cx.executor());
+ let project = Project::test(fs, None, cx).await;
+ let (workspace, cx) =
+ cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
+
+ // No non-pinned tabs in same pane, non-pinned tabs in another pane
+ let pane1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
+ let pane2 = workspace.update_in(cx, |workspace, window, cx| {
+ workspace.split_pane(pane1.clone(), SplitDirection::Right, window, cx)
+ });
+ add_labeled_item(&pane1, "A", false, cx);
+ pane1.update_in(cx, |pane, window, cx| {
+ pane.pin_tab_at(0, window, cx);
+ });
+ set_labeled_items(&pane1, ["A*"], cx);
+ add_labeled_item(&pane2, "B", false, cx);
+ set_labeled_items(&pane2, ["B"], cx);
+ pane1.update_in(cx, |pane, window, cx| {
+ pane.close_active_item(
+ &CloseActiveItem {
+ save_intent: None,
+ close_pinned: false,
+ },
+ window,
+ cx,
+ );
+ });
+ // Non-pinned tab of other pane should be active
+ assert_item_labels(&pane2, ["B*"], cx);
+ }
+
fn init_test(cx: &mut TestAppContext) {
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);