Skip to content

Commit ab6125d

Browse files
authored
Fix bugs around pinned tabs (#31871)
Closes #31870 Release Notes: - Allowed opening 1 more item if `n` tabs are pinned, where `n` equals `max_tabs` count. - Fixed a bug where pinned tabs would eventually be closed out when exceeding the `max_tabs` count. - Fixed a bug where a tab could be lost when pinning a tab while at the `max_tabs` count. - Fixed a bug where pinning a tab when already at the `max_tabs` limit could cause other tabs to be incorrectly closed.
1 parent d3bc561 commit ab6125d

File tree

1 file changed

+302
-8
lines changed

1 file changed

+302
-8
lines changed

crates/workspace/src/pane.rs

Lines changed: 302 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,14 @@ impl Pane {
899899
window: &mut Window,
900900
cx: &mut Context<Self>,
901901
) {
902-
self.close_items_over_max_tabs(window, cx);
902+
let item_already_exists = self
903+
.items
904+
.iter()
905+
.any(|existing_item| existing_item.item_id() == item.item_id());
906+
907+
if !item_already_exists {
908+
self.close_items_over_max_tabs(window, cx);
909+
}
903910

904911
if item.is_singleton(cx) {
905912
if let Some(&entry_id) = item.project_entry_ids(cx).first() {
@@ -1404,6 +1411,9 @@ impl Pane {
14041411
if let Some(true) = self.items.get(index).map(|item| item.is_dirty(cx)) {
14051412
continue;
14061413
}
1414+
if self.is_tab_pinned(index) {
1415+
continue;
1416+
}
14071417

14081418
index_list.push(index);
14091419
items_len -= 1;
@@ -2053,13 +2063,15 @@ impl Pane {
20532063
self.set_preview_item_id(None, cx);
20542064
}
20552065

2056-
self.workspace
2057-
.update(cx, |_, cx| {
2058-
cx.defer_in(window, move |_, window, cx| {
2059-
move_item(&pane, &pane, id, destination_index, window, cx)
2060-
});
2061-
})
2062-
.ok()?;
2066+
if ix != destination_index {
2067+
self.workspace
2068+
.update(cx, |_, cx| {
2069+
cx.defer_in(window, move |_, window, cx| {
2070+
move_item(&pane, &pane, id, destination_index, window, cx)
2071+
});
2072+
})
2073+
.ok()?;
2074+
}
20632075

20642076
Some(())
20652077
});
@@ -3789,6 +3801,288 @@ mod tests {
37893801
);
37903802
}
37913803

3804+
#[gpui::test]
3805+
async fn test_allow_pinning_dirty_item_at_max_tabs(cx: &mut TestAppContext) {
3806+
init_test(cx);
3807+
let fs = FakeFs::new(cx.executor());
3808+
3809+
let project = Project::test(fs, None, cx).await;
3810+
let (workspace, cx) =
3811+
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
3812+
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3813+
3814+
set_max_tabs(cx, Some(1));
3815+
let item_a = add_labeled_item(&pane, "A", true, cx);
3816+
3817+
pane.update_in(cx, |pane, window, cx| {
3818+
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
3819+
pane.pin_tab_at(ix, window, cx);
3820+
});
3821+
assert_item_labels(&pane, ["A*^!"], cx);
3822+
}
3823+
3824+
#[gpui::test]
3825+
async fn test_allow_pinning_non_dirty_item_at_max_tabs(cx: &mut TestAppContext) {
3826+
init_test(cx);
3827+
let fs = FakeFs::new(cx.executor());
3828+
3829+
let project = Project::test(fs, None, cx).await;
3830+
let (workspace, cx) =
3831+
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
3832+
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3833+
3834+
set_max_tabs(cx, Some(1));
3835+
let item_a = add_labeled_item(&pane, "A", false, cx);
3836+
3837+
pane.update_in(cx, |pane, window, cx| {
3838+
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
3839+
pane.pin_tab_at(ix, window, cx);
3840+
});
3841+
assert_item_labels(&pane, ["A*!"], cx);
3842+
}
3843+
3844+
#[gpui::test]
3845+
async fn test_pin_tabs_incrementally_at_max_capacity(cx: &mut TestAppContext) {
3846+
init_test(cx);
3847+
let fs = FakeFs::new(cx.executor());
3848+
3849+
let project = Project::test(fs, None, cx).await;
3850+
let (workspace, cx) =
3851+
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
3852+
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3853+
3854+
set_max_tabs(cx, Some(3));
3855+
3856+
let item_a = add_labeled_item(&pane, "A", false, cx);
3857+
assert_item_labels(&pane, ["A*"], cx);
3858+
3859+
pane.update_in(cx, |pane, window, cx| {
3860+
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
3861+
pane.pin_tab_at(ix, window, cx);
3862+
});
3863+
assert_item_labels(&pane, ["A*!"], cx);
3864+
3865+
let item_b = add_labeled_item(&pane, "B", false, cx);
3866+
assert_item_labels(&pane, ["A!", "B*"], cx);
3867+
3868+
pane.update_in(cx, |pane, window, cx| {
3869+
let ix = pane.index_for_item_id(item_b.item_id()).unwrap();
3870+
pane.pin_tab_at(ix, window, cx);
3871+
});
3872+
assert_item_labels(&pane, ["A!", "B*!"], cx);
3873+
3874+
let item_c = add_labeled_item(&pane, "C", false, cx);
3875+
assert_item_labels(&pane, ["A!", "B!", "C*"], cx);
3876+
3877+
pane.update_in(cx, |pane, window, cx| {
3878+
let ix = pane.index_for_item_id(item_c.item_id()).unwrap();
3879+
pane.pin_tab_at(ix, window, cx);
3880+
});
3881+
assert_item_labels(&pane, ["A!", "B!", "C*!"], cx);
3882+
}
3883+
3884+
#[gpui::test]
3885+
async fn test_pin_tabs_left_to_right_after_opening_at_max_capacity(cx: &mut TestAppContext) {
3886+
init_test(cx);
3887+
let fs = FakeFs::new(cx.executor());
3888+
3889+
let project = Project::test(fs, None, cx).await;
3890+
let (workspace, cx) =
3891+
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
3892+
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3893+
3894+
set_max_tabs(cx, Some(3));
3895+
3896+
let item_a = add_labeled_item(&pane, "A", false, cx);
3897+
assert_item_labels(&pane, ["A*"], cx);
3898+
3899+
let item_b = add_labeled_item(&pane, "B", false, cx);
3900+
assert_item_labels(&pane, ["A", "B*"], cx);
3901+
3902+
let item_c = add_labeled_item(&pane, "C", false, cx);
3903+
assert_item_labels(&pane, ["A", "B", "C*"], cx);
3904+
3905+
pane.update_in(cx, |pane, window, cx| {
3906+
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
3907+
pane.pin_tab_at(ix, window, cx);
3908+
});
3909+
assert_item_labels(&pane, ["A!", "B", "C*"], cx);
3910+
3911+
pane.update_in(cx, |pane, window, cx| {
3912+
let ix = pane.index_for_item_id(item_b.item_id()).unwrap();
3913+
pane.pin_tab_at(ix, window, cx);
3914+
});
3915+
assert_item_labels(&pane, ["A!", "B!", "C*"], cx);
3916+
3917+
pane.update_in(cx, |pane, window, cx| {
3918+
let ix = pane.index_for_item_id(item_c.item_id()).unwrap();
3919+
pane.pin_tab_at(ix, window, cx);
3920+
});
3921+
assert_item_labels(&pane, ["A!", "B!", "C*!"], cx);
3922+
}
3923+
3924+
#[gpui::test]
3925+
async fn test_pin_tabs_right_to_left_after_opening_at_max_capacity(cx: &mut TestAppContext) {
3926+
init_test(cx);
3927+
let fs = FakeFs::new(cx.executor());
3928+
3929+
let project = Project::test(fs, None, cx).await;
3930+
let (workspace, cx) =
3931+
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
3932+
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3933+
3934+
set_max_tabs(cx, Some(3));
3935+
3936+
let item_a = add_labeled_item(&pane, "A", false, cx);
3937+
assert_item_labels(&pane, ["A*"], cx);
3938+
3939+
let item_b = add_labeled_item(&pane, "B", false, cx);
3940+
assert_item_labels(&pane, ["A", "B*"], cx);
3941+
3942+
let item_c = add_labeled_item(&pane, "C", false, cx);
3943+
assert_item_labels(&pane, ["A", "B", "C*"], cx);
3944+
3945+
pane.update_in(cx, |pane, window, cx| {
3946+
let ix = pane.index_for_item_id(item_c.item_id()).unwrap();
3947+
pane.pin_tab_at(ix, window, cx);
3948+
});
3949+
assert_item_labels(&pane, ["C*!", "A", "B"], cx);
3950+
3951+
pane.update_in(cx, |pane, window, cx| {
3952+
let ix = pane.index_for_item_id(item_b.item_id()).unwrap();
3953+
pane.pin_tab_at(ix, window, cx);
3954+
});
3955+
assert_item_labels(&pane, ["C!", "B*!", "A"], cx);
3956+
3957+
pane.update_in(cx, |pane, window, cx| {
3958+
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
3959+
pane.pin_tab_at(ix, window, cx);
3960+
});
3961+
assert_item_labels(&pane, ["C!", "B*!", "A!"], cx);
3962+
}
3963+
3964+
#[gpui::test]
3965+
async fn test_pinned_tabs_never_closed_at_max_tabs(cx: &mut TestAppContext) {
3966+
init_test(cx);
3967+
let fs = FakeFs::new(cx.executor());
3968+
3969+
let project = Project::test(fs, None, cx).await;
3970+
let (workspace, cx) =
3971+
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
3972+
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3973+
3974+
let item_a = add_labeled_item(&pane, "A", false, cx);
3975+
pane.update_in(cx, |pane, window, cx| {
3976+
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
3977+
pane.pin_tab_at(ix, window, cx);
3978+
});
3979+
3980+
let item_b = add_labeled_item(&pane, "B", false, cx);
3981+
pane.update_in(cx, |pane, window, cx| {
3982+
let ix = pane.index_for_item_id(item_b.item_id()).unwrap();
3983+
pane.pin_tab_at(ix, window, cx);
3984+
});
3985+
3986+
add_labeled_item(&pane, "C", false, cx);
3987+
add_labeled_item(&pane, "D", false, cx);
3988+
add_labeled_item(&pane, "E", false, cx);
3989+
assert_item_labels(&pane, ["A!", "B!", "C", "D", "E*"], cx);
3990+
3991+
set_max_tabs(cx, Some(3));
3992+
add_labeled_item(&pane, "F", false, cx);
3993+
assert_item_labels(&pane, ["A!", "B!", "F*"], cx);
3994+
3995+
add_labeled_item(&pane, "G", false, cx);
3996+
assert_item_labels(&pane, ["A!", "B!", "G*"], cx);
3997+
3998+
add_labeled_item(&pane, "H", false, cx);
3999+
assert_item_labels(&pane, ["A!", "B!", "H*"], cx);
4000+
}
4001+
4002+
#[gpui::test]
4003+
async fn test_always_allows_one_unpinned_item_over_max_tabs_regardless_of_pinned_count(
4004+
cx: &mut TestAppContext,
4005+
) {
4006+
init_test(cx);
4007+
let fs = FakeFs::new(cx.executor());
4008+
4009+
let project = Project::test(fs, None, cx).await;
4010+
let (workspace, cx) =
4011+
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
4012+
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4013+
4014+
set_max_tabs(cx, Some(3));
4015+
4016+
let item_a = add_labeled_item(&pane, "A", false, cx);
4017+
pane.update_in(cx, |pane, window, cx| {
4018+
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
4019+
pane.pin_tab_at(ix, window, cx);
4020+
});
4021+
4022+
let item_b = add_labeled_item(&pane, "B", false, cx);
4023+
pane.update_in(cx, |pane, window, cx| {
4024+
let ix = pane.index_for_item_id(item_b.item_id()).unwrap();
4025+
pane.pin_tab_at(ix, window, cx);
4026+
});
4027+
4028+
let item_c = add_labeled_item(&pane, "C", false, cx);
4029+
pane.update_in(cx, |pane, window, cx| {
4030+
let ix = pane.index_for_item_id(item_c.item_id()).unwrap();
4031+
pane.pin_tab_at(ix, window, cx);
4032+
});
4033+
4034+
assert_item_labels(&pane, ["A!", "B!", "C*!"], cx);
4035+
4036+
let item_d = add_labeled_item(&pane, "D", false, cx);
4037+
assert_item_labels(&pane, ["A!", "B!", "C!", "D*"], cx);
4038+
4039+
pane.update_in(cx, |pane, window, cx| {
4040+
let ix = pane.index_for_item_id(item_d.item_id()).unwrap();
4041+
pane.pin_tab_at(ix, window, cx);
4042+
});
4043+
assert_item_labels(&pane, ["A!", "B!", "C!", "D*!"], cx);
4044+
4045+
add_labeled_item(&pane, "E", false, cx);
4046+
assert_item_labels(&pane, ["A!", "B!", "C!", "D!", "E*"], cx);
4047+
4048+
add_labeled_item(&pane, "F", false, cx);
4049+
assert_item_labels(&pane, ["A!", "B!", "C!", "D!", "F*"], cx);
4050+
}
4051+
4052+
#[gpui::test]
4053+
async fn test_can_open_one_item_when_all_tabs_are_dirty_at_max(cx: &mut TestAppContext) {
4054+
init_test(cx);
4055+
let fs = FakeFs::new(cx.executor());
4056+
4057+
let project = Project::test(fs, None, cx).await;
4058+
let (workspace, cx) =
4059+
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
4060+
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4061+
4062+
set_max_tabs(cx, Some(3));
4063+
4064+
add_labeled_item(&pane, "A", true, cx);
4065+
assert_item_labels(&pane, ["A*^"], cx);
4066+
4067+
add_labeled_item(&pane, "B", true, cx);
4068+
assert_item_labels(&pane, ["A^", "B*^"], cx);
4069+
4070+
add_labeled_item(&pane, "C", true, cx);
4071+
assert_item_labels(&pane, ["A^", "B^", "C*^"], cx);
4072+
4073+
add_labeled_item(&pane, "D", false, cx);
4074+
assert_item_labels(&pane, ["A^", "B^", "C^", "D*"], cx);
4075+
4076+
add_labeled_item(&pane, "E", false, cx);
4077+
assert_item_labels(&pane, ["A^", "B^", "C^", "E*"], cx);
4078+
4079+
add_labeled_item(&pane, "F", false, cx);
4080+
assert_item_labels(&pane, ["A^", "B^", "C^", "F*"], cx);
4081+
4082+
add_labeled_item(&pane, "G", true, cx);
4083+
assert_item_labels(&pane, ["A^", "B^", "C^", "G*^"], cx);
4084+
}
4085+
37924086
#[gpui::test]
37934087
async fn test_add_item_with_new_item(cx: &mut TestAppContext) {
37944088
init_test(cx);

0 commit comments

Comments
 (0)