pane: Ignore max tabs on terminal pane (#40740)

vipex and Joseph T. Lyons created

Closes #39901

I'm unsure as to which direction the team wants to go with this, but
this is the behavior of VSCode which is what this feature is based off
so i'm going with this.

Changes: 

1. Introduced a new argument to the `new` method on the Pane called
`ignore_max_tabs` that forces the `max_tabs` to None if it's true.
2. Added a new test `test_bypass_max_tabs_limit`.

Release Notes:

- Fixed: `max_tabs` Setting affecting the terminal pane.

---------

Co-authored-by: Joseph T. Lyons <JosephTLyons@gmail.com>

Change summary

crates/debugger_ui/src/session/running.rs  |  1 
crates/terminal_view/src/terminal_panel.rs | 51 ++++++++++++++++++++++++
crates/workspace/src/pane.rs               | 12 ++++
crates/workspace/src/workspace.rs          |  2 
4 files changed, 64 insertions(+), 2 deletions(-)

Detailed changes

crates/terminal_view/src/terminal_panel.rs 🔗

@@ -1103,6 +1103,7 @@ pub fn new_terminal_pane(
             Default::default(),
             None,
             NewTerminal.boxed_clone(),
+            false,
             window,
             cx,
         );
@@ -1752,6 +1753,8 @@ impl Render for InlineAssistTabBarButton {
 
 #[cfg(test)]
 mod tests {
+    use std::num::NonZero;
+
     use super::*;
     use gpui::{TestAppContext, UpdateGlobal as _};
     use pretty_assertions::assert_eq;
@@ -1808,6 +1811,46 @@ mod tests {
             .unwrap();
     }
 
+    #[gpui::test]
+    async fn test_bypass_max_tabs_limit(cx: &mut TestAppContext) {
+        init_test(cx);
+
+        let fs = FakeFs::new(cx.executor());
+        let project = Project::test(fs, [], cx).await;
+        let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
+
+        let (window_handle, terminal_panel) = workspace
+            .update(cx, |workspace, window, cx| {
+                let window_handle = window.window_handle();
+                let terminal_panel = cx.new(|cx| TerminalPanel::new(workspace, window, cx));
+                (window_handle, terminal_panel)
+            })
+            .unwrap();
+
+        set_max_tabs(cx, Some(3));
+
+        for _ in 0..5 {
+            let task = window_handle
+                .update(cx, |_, window, cx| {
+                    terminal_panel.update(cx, |panel, cx| {
+                        panel.add_terminal_shell(None, RevealStrategy::Always, window, cx)
+                    })
+                })
+                .unwrap();
+            task.await.unwrap();
+        }
+
+        cx.run_until_parked();
+
+        let item_count =
+            terminal_panel.read_with(cx, |panel, cx| panel.active_pane.read(cx).items_len());
+
+        assert_eq!(
+            item_count, 5,
+            "Terminal panel should bypass max_tabs limit and have all 5 terminals"
+        );
+    }
+
     // A complex Unix command won't be properly parsed by the Windows terminal hence omit the test there.
     #[cfg(unix)]
     #[gpui::test]
@@ -1922,6 +1965,14 @@ mod tests {
             .unwrap();
     }
 
+    fn set_max_tabs(cx: &mut TestAppContext, value: Option<usize>) {
+        cx.update_global(|store: &mut SettingsStore, cx| {
+            store.update_user_settings(cx, |settings| {
+                settings.workspace.max_tabs = value.map(|v| NonZero::new(v).unwrap())
+            });
+        });
+    }
+
     pub fn init_test(cx: &mut TestAppContext) {
         cx.update(|cx| {
             let store = SettingsStore::test(cx);

crates/workspace/src/pane.rs 🔗

@@ -376,6 +376,7 @@ pub struct Pane {
     render_tab_bar: Rc<dyn Fn(&mut Pane, &mut Window, &mut Context<Pane>) -> AnyElement>,
     show_tab_bar_buttons: bool,
     max_tabs: Option<NonZeroUsize>,
+    use_max_tabs: bool,
     _subscriptions: Vec<Subscription>,
     tab_bar_scroll_handle: ScrollHandle,
     /// This is set to true if a user scroll has occurred more recently than a system scroll
@@ -473,10 +474,16 @@ impl Pane {
         next_timestamp: Arc<AtomicUsize>,
         can_drop_predicate: Option<Arc<dyn Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static>>,
         double_click_dispatch_action: Box<dyn Action>,
+        use_max_tabs: bool,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> Self {
         let focus_handle = cx.focus_handle();
+        let max_tabs = if use_max_tabs {
+            WorkspaceSettings::get_global(cx).max_tabs
+        } else {
+            None
+        };
 
         let subscriptions = vec![
             cx.on_focus(&focus_handle, window, Pane::focus_in),
@@ -498,7 +505,8 @@ impl Pane {
             zoomed: false,
             active_item_index: 0,
             preview_item_id: None,
-            max_tabs: WorkspaceSettings::get_global(cx).max_tabs,
+            max_tabs,
+            use_max_tabs,
             last_focus_handle_by_item: Default::default(),
             nav_history: NavHistory(Arc::new(Mutex::new(NavHistoryState {
                 mode: NavigationMode::Normal,
@@ -706,7 +714,7 @@ impl Pane {
             self.preview_item_id = None;
         }
 
-        if new_max_tabs != self.max_tabs {
+        if self.use_max_tabs && new_max_tabs != self.max_tabs {
             self.max_tabs = new_max_tabs;
             self.close_items_on_settings_change(window, cx);
         }

crates/workspace/src/workspace.rs 🔗

@@ -1331,6 +1331,7 @@ impl Workspace {
                 pane_history_timestamp.clone(),
                 None,
                 NewFile.boxed_clone(),
+                true,
                 window,
                 cx,
             );
@@ -3235,6 +3236,7 @@ impl Workspace {
                 self.pane_history_timestamp.clone(),
                 None,
                 NewFile.boxed_clone(),
+                true,
                 window,
                 cx,
             );