tab_switcher: Add support for tab switcher in assistant panel (#15475)

Piotr Osiewicz created

Additionally, I've generalized the implementation of tab switcher so
that - instead of explicitly listing panels it supports (at the time of
writing it was just the terminal panel and nothing else), it now relies
on Panel::pane trait method. As long as that's implemented, you get a
tab switcher support for free.

Release Notes:

- Added support for tab switcher in Assistant panel.

Change summary

Cargo.lock                                         |  1 
crates/assistant/src/assistant_panel.rs            | 12 ++---
crates/assistant/src/slash_command/term_command.rs | 14 +++----
crates/tab_switcher/Cargo.toml                     |  1 
crates/tab_switcher/src/tab_switcher.rs            | 29 ++++++++++-----
crates/terminal_view/src/terminal_panel.rs         | 10 ++--
6 files changed, 35 insertions(+), 32 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -10760,7 +10760,6 @@ dependencies = [
  "project",
  "serde",
  "serde_json",
- "terminal_view",
  "theme",
  "ui",
  "util",

crates/assistant/src/assistant_panel.rs 🔗

@@ -686,13 +686,11 @@ impl AssistantPanel {
                 .focus_handle(cx)
                 .contains_focused(cx)
             {
-                if let Some(terminal_view) = terminal_panel
-                    .read(cx)
-                    .pane()
-                    .read(cx)
-                    .active_item()
-                    .and_then(|t| t.downcast::<TerminalView>())
-                {
+                if let Some(terminal_view) = terminal_panel.read(cx).pane().and_then(|pane| {
+                    pane.read(cx)
+                        .active_item()
+                        .and_then(|t| t.downcast::<TerminalView>())
+                }) {
                     return Some(InlineAssistTarget::Terminal(terminal_view));
                 }
             }

crates/assistant/src/slash_command/term_command.rs 🔗

@@ -9,7 +9,7 @@ use gpui::{AppContext, Task, WeakView};
 use language::{CodeLabel, LspAdapterDelegate};
 use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
 use ui::prelude::*;
-use workspace::Workspace;
+use workspace::{dock::Panel, Workspace};
 
 use super::create_label_for_command;
 
@@ -65,13 +65,11 @@ impl SlashCommand for TermSlashCommand {
         let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
             return Task::ready(Err(anyhow::anyhow!("no terminal panel open")));
         };
-        let Some(active_terminal) = terminal_panel
-            .read(cx)
-            .pane()
-            .read(cx)
-            .active_item()
-            .and_then(|t| t.downcast::<TerminalView>())
-        else {
+        let Some(active_terminal) = terminal_panel.read(cx).pane().and_then(|pane| {
+            pane.read(cx)
+                .active_item()
+                .and_then(|t| t.downcast::<TerminalView>())
+        }) else {
             return Task::ready(Err(anyhow::anyhow!("no active terminal")));
         };
 

crates/tab_switcher/Cargo.toml 🔗

@@ -18,7 +18,6 @@ gpui.workspace = true
 menu.workspace = true
 picker.workspace = true
 serde.workspace = true
-terminal_view.workspace = true
 ui.workspace = true
 util.workspace = true
 workspace.workspace = true

crates/tab_switcher/src/tab_switcher.rs 🔗

@@ -57,16 +57,25 @@ impl TabSwitcher {
     }
 
     fn open(action: &Toggle, workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
-        let terminal = workspace.panel::<terminal_view::terminal_panel::TerminalPanel>(cx);
-        let terminal_pane = terminal.and_then(|terminal| {
-            terminal
-                .focus_handle(cx)
-                .contains_focused(cx)
-                .then(|| terminal.read(cx).pane())
-        });
-        let weak_pane = terminal_pane
-            .unwrap_or_else(|| workspace.active_pane())
-            .downgrade();
+        let mut weak_pane = workspace.active_pane().downgrade();
+        for dock in [
+            workspace.left_dock(),
+            workspace.bottom_dock(),
+            workspace.right_dock(),
+        ] {
+            dock.update(cx, |this, cx| {
+                let Some(panel) = this
+                    .active_panel()
+                    .filter(|panel| panel.focus_handle(cx).contains_focused(cx))
+                else {
+                    return;
+                };
+                if let Some(pane) = panel.pane(cx) {
+                    weak_pane = pane.downgrade();
+                }
+            })
+        }
+
         workspace.toggle_modal(cx, |cx| {
             let delegate = TabSwitcherDelegate::new(action, cx.view().downgrade(), weak_pane, cx);
             TabSwitcher::new(delegate, cx)

crates/terminal_view/src/terminal_panel.rs 🔗

@@ -171,7 +171,7 @@ impl TerminalPanel {
 
     fn apply_tab_bar_buttons(&self, cx: &mut ViewContext<Self>) {
         let additional_buttons = self.additional_tab_bar_buttons.clone();
-        self.pane().update(cx, |pane, cx| {
+        self.pane.update(cx, |pane, cx| {
             pane.set_render_tab_bar_buttons(cx, move |pane, cx| {
                 h_flex()
                     .gap_2()
@@ -683,10 +683,6 @@ impl TerminalPanel {
         Some(())
     }
 
-    pub fn pane(&self) -> &View<Pane> {
-        &self.pane
-    }
-
     fn has_no_terminals(&self, cx: &WindowContext) -> bool {
         self.pane.read(cx).items_len() == 0 && self.pending_terminals_to_add == 0
     }
@@ -849,6 +845,10 @@ impl Panel for TerminalPanel {
     fn toggle_action(&self) -> Box<dyn gpui::Action> {
         Box::new(ToggleFocus)
     }
+
+    fn pane(&self) -> Option<View<Pane>> {
+        Some(self.pane.clone())
+    }
 }
 
 #[derive(Serialize, Deserialize)]