settings: Add max tabs option (#18933)
Add a `max_tabs` option to the settings that ensure no more than this amount of tabs are open in a pane. If set to `null`, there is no limit. Closes #4784 Release Notes: - Added a `max_tabs` option to cap the maximum number of open tabs.
This commit is contained in:
parent
0be7cf8ea8
commit
cd5d8b4173
3 changed files with 107 additions and 0 deletions
|
@ -896,6 +896,8 @@ impl Pane {
|
|||
destination_index: Option<usize>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.close_items_over_max_tabs(cx);
|
||||
|
||||
if item.is_singleton(cx) {
|
||||
if let Some(&entry_id) = item.project_entry_ids(cx).first() {
|
||||
let project = self.project.read(cx);
|
||||
|
@ -1298,6 +1300,43 @@ impl Pane {
|
|||
))
|
||||
}
|
||||
|
||||
pub fn close_items_over_max_tabs(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let Some(max_tabs) = WorkspaceSettings::get_global(cx).max_tabs.map(|i| i.get()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Reduce over the activation history to get every dirty items up to max_tabs
|
||||
// count.
|
||||
let mut index_list = Vec::new();
|
||||
let mut items_len = self.items_len();
|
||||
let mut indexes: HashMap<EntityId, usize> = HashMap::default();
|
||||
for (index, item) in self.items.iter().enumerate() {
|
||||
indexes.insert(item.item_id(), index);
|
||||
}
|
||||
for entry in self.activation_history.iter() {
|
||||
if items_len < max_tabs {
|
||||
break;
|
||||
}
|
||||
let Some(&index) = indexes.get(&entry.entity_id) else {
|
||||
continue;
|
||||
};
|
||||
if let Some(true) = self.items.get(index).map(|item| item.is_dirty(cx)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
index_list.push(index);
|
||||
items_len -= 1;
|
||||
}
|
||||
// The sort and reverse is necessary since we remove items
|
||||
// using their index position, hence removing from the end
|
||||
// of the list first to avoid changing indexes.
|
||||
index_list.sort_unstable();
|
||||
index_list
|
||||
.iter()
|
||||
.rev()
|
||||
.for_each(|&index| self._remove_item(index, false, false, None, cx));
|
||||
}
|
||||
|
||||
pub(super) fn file_names_for_prompt(
|
||||
items: &mut dyn Iterator<Item = &Box<dyn ItemHandle>>,
|
||||
all_dirty_items: usize,
|
||||
|
@ -3282,6 +3321,8 @@ impl Render for DraggedTab {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::num::NonZero;
|
||||
|
||||
use super::*;
|
||||
use crate::item::test::{TestItem, TestProjectItem};
|
||||
use gpui::{TestAppContext, VisualTestContext};
|
||||
|
@ -3305,6 +3346,54 @@ mod tests {
|
|||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_add_item_capped_to_max_tabs(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(|cx| Workspace::test_new(project.clone(), cx));
|
||||
let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
|
||||
|
||||
for i in 0..7 {
|
||||
add_labeled_item(&pane, format!("{}", i).as_str(), false, cx);
|
||||
}
|
||||
set_max_tabs(cx, Some(5));
|
||||
add_labeled_item(&pane, "7", false, cx);
|
||||
// Remove items to respect the max tab cap.
|
||||
assert_item_labels(&pane, ["3", "4", "5", "6", "7*"], cx);
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.activate_item(0, false, false, cx);
|
||||
});
|
||||
add_labeled_item(&pane, "X", false, cx);
|
||||
// Respect activation order.
|
||||
assert_item_labels(&pane, ["3", "X*", "5", "6", "7"], cx);
|
||||
|
||||
for i in 0..7 {
|
||||
add_labeled_item(&pane, format!("D{}", i).as_str(), true, cx);
|
||||
}
|
||||
// Keeps dirty items, even over max tab cap.
|
||||
assert_item_labels(
|
||||
&pane,
|
||||
["D0^", "D1^", "D2^", "D3^", "D4^", "D5^", "D6*^"],
|
||||
cx,
|
||||
);
|
||||
|
||||
set_max_tabs(cx, None);
|
||||
for i in 0..7 {
|
||||
add_labeled_item(&pane, format!("N{}", i).as_str(), false, cx);
|
||||
}
|
||||
// No cap when max tabs is None.
|
||||
assert_item_labels(
|
||||
&pane,
|
||||
[
|
||||
"D0^", "D1^", "D2^", "D3^", "D4^", "D5^", "D6^", "N0", "N1", "N2", "N3", "N4",
|
||||
"N5", "N6*",
|
||||
],
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_add_item_with_new_item(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
@ -3984,6 +4073,14 @@ mod tests {
|
|||
});
|
||||
}
|
||||
|
||||
fn set_max_tabs(cx: &mut TestAppContext, value: Option<usize>) {
|
||||
cx.update_global(|store: &mut SettingsStore, cx| {
|
||||
store.update_user_settings::<WorkspaceSettings>(cx, |settings| {
|
||||
settings.max_tabs = value.map(|v| NonZero::new(v).unwrap())
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn add_labeled_item(
|
||||
pane: &View<Pane>,
|
||||
label: &str,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue