Remove Panel impl from AgentPanel

Eric Holk and Max Brunsfeld created

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>

Change summary

crates/agent_ui/src/agent_configuration/manage_profiles_modal.rs |   2 
crates/agent_ui/src/agent_panel.rs                               | 221 -
crates/agent_ui/src/completion_provider.rs                       |   2 
crates/agent_ui/src/connection_view.rs                           |   4 
crates/agent_ui/src/connection_view/thread_view.rs               |   6 
crates/agent_ui/src/inline_assistant.rs                          |   6 
crates/agent_ui/src/sidebar.rs                                   |  18 
crates/agent_ui/src/thread_history_view.rs                       |   2 
crates/agent_ui/src/ui/acp_onboarding_modal.rs                   |   4 
crates/agent_ui/src/ui/claude_agent_onboarding_modal.rs          |   4 
crates/agent_ui/src/ui/mention_crease.rs                         |   2 
crates/workspace/src/dock.rs                                     |   7 
crates/workspace/src/workspace.rs                                | 104 
crates/zed/src/main.rs                                           |   6 
crates/zed/src/zed.rs                                            |  59 
15 files changed, 232 insertions(+), 215 deletions(-)

Detailed changes

crates/agent_ui/src/agent_configuration/manage_profiles_modal.rs πŸ”—

@@ -121,7 +121,7 @@ impl ManageProfilesModal {
         _cx: &mut Context<Workspace>,
     ) {
         workspace.register_action(|workspace, action: &ManageProfiles, window, cx| {
-            if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
+            if let Some(panel) = workspace.drawer::<AgentPanel>() {
                 let fs = workspace.app_state().fs.clone();
                 let active_model = panel
                     .read(cx)

crates/agent_ui/src/agent_panel.rs πŸ”—

@@ -86,7 +86,7 @@ use util::{ResultExt as _, debug_panic};
 use workspace::{
     CollaboratorId, DraggedSelection, DraggedTab, OpenResult, ToggleZoom, ToolbarItemView,
     Workspace, WorkspaceId,
-    dock::{DockPosition, Panel, PanelEvent},
+    dock::{DockPosition, PanelEvent},
 };
 use zed_actions::{
     DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize,
@@ -151,50 +151,50 @@ pub fn init(cx: &mut App) {
         |workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {
             workspace
                 .register_action(|workspace, action: &NewThread, window, cx| {
-                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
+                    if let Some(panel) = workspace.drawer::<AgentPanel>() {
                         panel.update(cx, |panel, cx| panel.new_thread(action, window, cx));
-                        workspace.focus_panel::<AgentPanel>(window, cx);
+                        workspace.focus_drawer::<AgentPanel>(window, cx);
                     }
                 })
                 .register_action(
                     |workspace, action: &NewNativeAgentThreadFromSummary, window, cx| {
-                        if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
+                        if let Some(panel) = workspace.drawer::<AgentPanel>() {
                             panel.update(cx, |panel, cx| {
                                 panel.new_native_agent_thread_from_summary(action, window, cx)
                             });
-                            workspace.focus_panel::<AgentPanel>(window, cx);
+                            workspace.focus_drawer::<AgentPanel>(window, cx);
                         }
                     },
                 )
                 .register_action(|workspace, _: &ExpandMessageEditor, window, cx| {
-                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
-                        workspace.focus_panel::<AgentPanel>(window, cx);
+                    if let Some(panel) = workspace.drawer::<AgentPanel>() {
+                        workspace.focus_drawer::<AgentPanel>(window, cx);
                         panel.update(cx, |panel, cx| panel.expand_message_editor(window, cx));
                     }
                 })
                 .register_action(|workspace, _: &OpenHistory, window, cx| {
-                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
-                        workspace.focus_panel::<AgentPanel>(window, cx);
+                    if let Some(panel) = workspace.drawer::<AgentPanel>() {
+                        workspace.focus_drawer::<AgentPanel>(window, cx);
                         panel.update(cx, |panel, cx| panel.open_history(window, cx));
                     }
                 })
                 .register_action(|workspace, _: &OpenSettings, window, cx| {
-                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
-                        workspace.focus_panel::<AgentPanel>(window, cx);
+                    if let Some(panel) = workspace.drawer::<AgentPanel>() {
+                        workspace.focus_drawer::<AgentPanel>(window, cx);
                         panel.update(cx, |panel, cx| panel.open_configuration(window, cx));
                     }
                 })
                 .register_action(|workspace, _: &NewTextThread, window, cx| {
-                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
-                        workspace.focus_panel::<AgentPanel>(window, cx);
+                    if let Some(panel) = workspace.drawer::<AgentPanel>() {
+                        workspace.focus_drawer::<AgentPanel>(window, cx);
                         panel.update(cx, |panel, cx| {
                             panel.new_text_thread(window, cx);
                         });
                     }
                 })
                 .register_action(|workspace, action: &NewExternalAgentThread, window, cx| {
-                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
-                        workspace.focus_panel::<AgentPanel>(window, cx);
+                    if let Some(panel) = workspace.drawer::<AgentPanel>() {
+                        workspace.focus_drawer::<AgentPanel>(window, cx);
                         panel.update(cx, |panel, cx| {
                             panel.external_thread(
                                 action.agent.clone(),
@@ -210,8 +210,8 @@ pub fn init(cx: &mut App) {
                     }
                 })
                 .register_action(|workspace, action: &OpenRulesLibrary, window, cx| {
-                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
-                        workspace.focus_panel::<AgentPanel>(window, cx);
+                    if let Some(panel) = workspace.drawer::<AgentPanel>() {
+                        workspace.focus_drawer::<AgentPanel>(window, cx);
                         panel.update(cx, |panel, cx| {
                             panel.deploy_rules_library(action, window, cx)
                         });
@@ -222,7 +222,7 @@ pub fn init(cx: &mut App) {
                 })
                 .register_action(|workspace, _: &OpenAgentDiff, window, cx| {
                     let thread = workspace
-                        .panel::<AgentPanel>(cx)
+                        .drawer::<AgentPanel>()
                         .and_then(|panel| panel.read(cx).active_connection_view().cloned())
                         .and_then(|thread_view| {
                             thread_view
@@ -236,24 +236,24 @@ pub fn init(cx: &mut App) {
                     }
                 })
                 .register_action(|workspace, _: &ToggleNavigationMenu, window, cx| {
-                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
-                        workspace.focus_panel::<AgentPanel>(window, cx);
+                    if let Some(panel) = workspace.drawer::<AgentPanel>() {
+                        workspace.focus_drawer::<AgentPanel>(window, cx);
                         panel.update(cx, |panel, cx| {
                             panel.toggle_navigation_menu(&ToggleNavigationMenu, window, cx);
                         });
                     }
                 })
                 .register_action(|workspace, _: &ToggleOptionsMenu, window, cx| {
-                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
-                        workspace.focus_panel::<AgentPanel>(window, cx);
+                    if let Some(panel) = workspace.drawer::<AgentPanel>() {
+                        workspace.focus_drawer::<AgentPanel>(window, cx);
                         panel.update(cx, |panel, cx| {
                             panel.toggle_options_menu(&ToggleOptionsMenu, window, cx);
                         });
                     }
                 })
                 .register_action(|workspace, _: &ToggleNewThreadMenu, window, cx| {
-                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
-                        workspace.focus_panel::<AgentPanel>(window, cx);
+                    if let Some(panel) = workspace.drawer::<AgentPanel>() {
+                        workspace.focus_drawer::<AgentPanel>(window, cx);
                         panel.update(cx, |panel, cx| {
                             panel.toggle_new_thread_menu(&ToggleNewThreadMenu, window, cx);
                         });
@@ -272,7 +272,7 @@ pub fn init(cx: &mut App) {
                     window.refresh();
                 })
                 .register_action(|workspace, _: &ResetTrialUpsell, _window, cx| {
-                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
+                    if let Some(panel) = workspace.drawer::<AgentPanel>() {
                         panel.update(cx, |panel, _| {
                             panel
                                 .on_boarding_upsell_dismissed
@@ -285,29 +285,29 @@ pub fn init(cx: &mut App) {
                     TrialEndUpsell::set_dismissed(false, cx);
                 })
                 .register_action(|workspace, _: &ResetAgentZoom, window, cx| {
-                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
+                    if let Some(panel) = workspace.drawer::<AgentPanel>() {
                         panel.update(cx, |panel, cx| {
                             panel.reset_agent_zoom(window, cx);
                         });
                     }
                 })
                 .register_action(|workspace, _: &CopyThreadToClipboard, window, cx| {
-                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
+                    if let Some(panel) = workspace.drawer::<AgentPanel>() {
                         panel.update(cx, |panel, cx| {
                             panel.copy_thread_to_clipboard(window, cx);
                         });
                     }
                 })
                 .register_action(|workspace, _: &LoadThreadFromClipboard, window, cx| {
-                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
-                        workspace.focus_panel::<AgentPanel>(window, cx);
+                    if let Some(panel) = workspace.drawer::<AgentPanel>() {
+                        workspace.focus_drawer::<AgentPanel>(window, cx);
                         panel.update(cx, |panel, cx| {
                             panel.load_thread_from_clipboard(window, cx);
                         });
                     }
                 })
                 .register_action(|workspace, action: &ReviewBranchDiff, window, cx| {
-                    let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
+                    let Some(panel) = workspace.drawer::<AgentPanel>() else {
                         return;
                     };
 
@@ -332,7 +332,7 @@ pub fn init(cx: &mut App) {
                         )),
                     ];
 
-                    workspace.focus_panel::<AgentPanel>(window, cx);
+                    workspace.focus_drawer::<AgentPanel>(window, cx);
 
                     panel.update(cx, |panel, cx| {
                         panel.external_thread(
@@ -352,13 +352,13 @@ pub fn init(cx: &mut App) {
                 })
                 .register_action(
                     |workspace, action: &ResolveConflictsWithAgent, window, cx| {
-                        let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
+                        let Some(panel) = workspace.drawer::<AgentPanel>() else {
                             return;
                         };
 
                         let content_blocks = build_conflict_resolution_prompt(&action.conflicts);
 
-                        workspace.focus_panel::<AgentPanel>(window, cx);
+                        workspace.focus_drawer::<AgentPanel>(window, cx);
 
                         panel.update(cx, |panel, cx| {
                             panel.external_thread(
@@ -379,14 +379,14 @@ pub fn init(cx: &mut App) {
                 )
                 .register_action(
                     |workspace, action: &ResolveConflictedFilesWithAgent, window, cx| {
-                        let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
+                        let Some(panel) = workspace.drawer::<AgentPanel>() else {
                             return;
                         };
 
                         let content_blocks =
                             build_conflicted_files_resolution_prompt(&action.conflicted_file_paths);
 
-                        workspace.focus_panel::<AgentPanel>(window, cx);
+                        workspace.focus_drawer::<AgentPanel>(window, cx);
 
                         panel.update(cx, |panel, cx| {
                             panel.external_thread(
@@ -406,21 +406,21 @@ pub fn init(cx: &mut App) {
                     },
                 )
                 .register_action(|workspace, action: &StartThreadIn, _window, cx| {
-                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
+                    if let Some(panel) = workspace.drawer::<AgentPanel>() {
                         panel.update(cx, |panel, cx| {
                             panel.set_start_thread_in(action, cx);
                         });
                     }
                 })
                 .register_action(|workspace, _: &CycleStartThreadIn, _window, cx| {
-                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
+                    if let Some(panel) = workspace.drawer::<AgentPanel>() {
                         panel.update(cx, |panel, cx| {
                             panel.cycle_start_thread_in(cx);
                         });
                     }
                 })
                 .register_action(|workspace, _: &ToggleAgentDrawer, _window, cx| {
-                    let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
+                    let Some(panel) = workspace.drawer::<AgentPanel>() else {
                         return;
                     };
                     let panel_view: AnyView = panel.into();
@@ -839,6 +839,10 @@ pub struct AgentPanel {
 }
 
 impl AgentPanel {
+    fn enabled(&self, cx: &App) -> bool {
+        AgentSettings::get_global(cx).enabled(cx)
+    }
+
     fn serialize(&mut self, cx: &mut App) {
         let Some(workspace_id) = self.workspace_id else {
             return;
@@ -1186,10 +1190,10 @@ impl AgentPanel {
         cx: &mut Context<Workspace>,
     ) {
         if workspace
-            .panel::<Self>(cx)
+            .drawer::<Self>()
             .is_some_and(|panel| panel.read(cx).enabled(cx))
         {
-            workspace.toggle_panel_focus::<Self>(window, cx);
+            workspace.toggle_drawer_focus::<Self>(window, cx);
         }
     }
 
@@ -1200,11 +1204,11 @@ impl AgentPanel {
         cx: &mut Context<Workspace>,
     ) {
         if workspace
-            .panel::<Self>(cx)
+            .drawer::<Self>()
             .is_some_and(|panel| panel.read(cx).enabled(cx))
         {
-            if !workspace.toggle_panel_focus::<Self>(window, cx) {
-                workspace.close_panel::<Self>(window, cx);
+            if !workspace.toggle_drawer_focus::<Self>(window, cx) {
+                workspace.close_drawer::<Self>(cx);
             }
         }
     }
@@ -1249,7 +1253,7 @@ impl AgentPanel {
         let workspace_read = workspace.read(cx);
 
         workspace_read
-            .panel::<AgentPanel>(cx)
+            .drawer::<AgentPanel>()
             .map(|panel| {
                 let panel_id = Entity::entity_id(&panel);
 
@@ -3033,8 +3037,8 @@ impl AgentPanel {
                         .detach();
                 }
 
-                workspace.focus_panel::<AgentPanel>(window, cx);
-                if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
+                workspace.focus_drawer::<AgentPanel>(window, cx);
+                if let Some(panel) = workspace.drawer::<AgentPanel>() {
                     panel.update(cx, |panel, cx| {
                         panel.external_thread(
                             None,
@@ -3062,6 +3066,15 @@ impl AgentPanel {
 
         anyhow::Ok(())
     }
+
+    fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool {
+        self.zoomed
+    }
+
+    fn set_zoomed(&mut self, zoomed: bool, _window: &mut Window, cx: &mut Context<Self>) {
+        self.zoomed = zoomed;
+        cx.notify();
+    }
 }
 
 impl Focusable for AgentPanel {
@@ -3100,98 +3113,6 @@ pub enum AgentPanelEvent {
 impl EventEmitter<PanelEvent> for AgentPanel {}
 impl EventEmitter<AgentPanelEvent> for AgentPanel {}
 
-impl Panel for AgentPanel {
-    fn persistent_name() -> &'static str {
-        "AgentPanel"
-    }
-
-    fn panel_key() -> &'static str {
-        AGENT_PANEL_KEY
-    }
-
-    fn position(&self, _window: &Window, cx: &App) -> DockPosition {
-        agent_panel_dock_position(cx)
-    }
-
-    fn position_is_valid(&self, position: DockPosition) -> bool {
-        position != DockPosition::Bottom
-    }
-
-    fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
-        settings::update_settings_file(self.fs.clone(), cx, move |settings, _| {
-            settings
-                .agent
-                .get_or_insert_default()
-                .set_dock(position.into());
-        });
-    }
-
-    fn size(&self, window: &Window, cx: &App) -> Pixels {
-        let settings = AgentSettings::get_global(cx);
-        match self.position(window, cx) {
-            DockPosition::Left | DockPosition::Right => {
-                self.width.unwrap_or(settings.default_width)
-            }
-            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
-        }
-    }
-
-    fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
-        match self.position(window, cx) {
-            DockPosition::Left | DockPosition::Right => self.width = size,
-            DockPosition::Bottom => self.height = size,
-        }
-        self.serialize(cx);
-        cx.notify();
-    }
-
-    fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut Context<Self>) {
-        if active
-            && matches!(self.active_view, ActiveView::Uninitialized)
-            && !matches!(
-                self.worktree_creation_status,
-                Some(WorktreeCreationStatus::Creating)
-            )
-        {
-            let selected_agent_type = self.selected_agent_type.clone();
-            self.new_agent_thread_inner(selected_agent_type, false, window, cx);
-        }
-    }
-
-    fn remote_id() -> Option<proto::PanelId> {
-        Some(proto::PanelId::AssistantPanel)
-    }
-
-    fn icon(&self, _window: &Window, cx: &App) -> Option<IconName> {
-        (self.enabled(cx) && AgentSettings::get_global(cx).button).then_some(IconName::ZedAssistant)
-    }
-
-    fn icon_tooltip(&self, _window: &Window, _cx: &App) -> &'static str {
-        "Agent Panel"
-    }
-
-    fn toggle_action(&self) -> Box<dyn Action> {
-        Box::new(ToggleAgentDrawer)
-    }
-
-    fn activation_priority(&self) -> u32 {
-        3
-    }
-
-    fn enabled(&self, cx: &App) -> bool {
-        AgentSettings::get_global(cx).enabled(cx)
-    }
-
-    fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool {
-        self.zoomed
-    }
-
-    fn set_zoomed(&mut self, zoomed: bool, _window: &mut Window, cx: &mut Context<Self>) {
-        self.zoomed = zoomed;
-        cx.notify();
-    }
-}
-
 impl AgentPanel {
     fn render_title_view(&self, _window: &mut Window, cx: &Context<Self>) -> AnyElement {
         const LOADING_SUMMARY_PLACEHOLDER: &str = "Loading Summary…";
@@ -3764,7 +3685,7 @@ impl AgentPanel {
                                         if let Some(workspace) = workspace.upgrade() {
                                             workspace.update(cx, |workspace, cx| {
                                                 if let Some(panel) =
-                                                    workspace.panel::<AgentPanel>(cx)
+                                                    workspace.drawer::<AgentPanel>()
                                                 {
                                                     panel.update(cx, |panel, cx| {
                                                         panel.new_agent_thread(
@@ -3790,7 +3711,7 @@ impl AgentPanel {
                                         if let Some(workspace) = workspace.upgrade() {
                                             workspace.update(cx, |workspace, cx| {
                                                 if let Some(panel) =
-                                                    workspace.panel::<AgentPanel>(cx)
+                                                    workspace.drawer::<AgentPanel>()
                                                 {
                                                     panel.update(cx, |panel, cx| {
                                                         panel.new_agent_thread(
@@ -3878,7 +3799,7 @@ impl AgentPanel {
                                             if let Some(workspace) = workspace.upgrade() {
                                                 workspace.update(cx, |workspace, cx| {
                                                     if let Some(panel) =
-                                                        workspace.panel::<AgentPanel>(cx)
+                                                        workspace.drawer::<AgentPanel>()
                                                     {
                                                         panel.update(cx, |panel, cx| {
                                                             panel.new_agent_thread(
@@ -3974,7 +3895,7 @@ impl AgentPanel {
                                             if let Some(workspace) = workspace.upgrade() {
                                                 workspace.update(cx, |workspace, cx| {
                                                     if let Some(panel) =
-                                                        workspace.panel::<AgentPanel>(cx)
+                                                        workspace.drawer::<AgentPanel>()
                                                     {
                                                         panel.update(cx, |panel, cx| {
                                                             panel.new_agent_thread(
@@ -4789,7 +4710,7 @@ impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
             let Some(workspace) = self.workspace.upgrade() else {
                 return;
             };
-            let Some(panel) = workspace.read(cx).panel::<AgentPanel>(cx) else {
+            let Some(panel) = workspace.read(cx).drawer::<AgentPanel>() else {
                 return;
             };
             let Some(history) = panel
@@ -4825,7 +4746,7 @@ impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
         window: &mut Window,
         cx: &mut Context<Workspace>,
     ) -> bool {
-        workspace.focus_panel::<AgentPanel>(window, cx).is_some()
+        workspace.focus_drawer::<AgentPanel>(window, cx).is_some()
     }
 }
 
@@ -4838,7 +4759,7 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
         _window: &mut Window,
         cx: &mut Context<Workspace>,
     ) -> Option<Entity<TextThreadEditor>> {
-        let panel = workspace.panel::<AgentPanel>(cx)?;
+        let panel = workspace.drawer::<AgentPanel>()?;
         panel.read(cx).active_text_thread_editor()
     }
 
@@ -4849,7 +4770,7 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
         window: &mut Window,
         cx: &mut Context<Workspace>,
     ) -> Task<Result<()>> {
-        let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
+        let Some(panel) = workspace.drawer::<AgentPanel>() else {
             return Task::ready(Err(anyhow!("Agent panel not found")));
         };
 
@@ -4876,12 +4797,12 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
         window: &mut Window,
         cx: &mut Context<Workspace>,
     ) {
-        let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
+        let Some(panel) = workspace.drawer::<AgentPanel>() else {
             return;
         };
 
         if !panel.focus_handle(cx).contains_focused(window, cx) {
-            workspace.toggle_panel_focus::<AgentPanel>(window, cx);
+            workspace.toggle_drawer_focus::<AgentPanel>(window, cx);
         }
 
         panel.update(cx, |_, cx| {
@@ -4914,12 +4835,12 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
         window: &mut Window,
         cx: &mut Context<Workspace>,
     ) {
-        let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
+        let Some(panel) = workspace.drawer::<AgentPanel>() else {
             return;
         };
 
         if !panel.focus_handle(cx).contains_focused(window, cx) {
-            workspace.toggle_panel_focus::<AgentPanel>(window, cx);
+            workspace.toggle_drawer_focus::<AgentPanel>(window, cx);
         }
 
         panel.update(cx, |_, cx| {

crates/agent_ui/src/completion_provider.rs πŸ”—

@@ -1096,7 +1096,7 @@ impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {
         let project = workspace.project().read(cx);
         let include_root_name = workspace.visible_worktrees(cx).count() > 1;
 
-        if let Some(agent_panel) = workspace.panel::<AgentPanel>(cx)
+        if let Some(agent_panel) = workspace.drawer::<AgentPanel>()
             && let Some(thread) = agent_panel.read(cx).active_agent_thread(cx)
         {
             let thread = thread.read(cx);

crates/agent_ui/src/connection_view.rs πŸ”—

@@ -2465,7 +2465,7 @@ impl ConnectionView {
                                         if let Some(workspace) = workspace_handle.upgrade() {
                                             multi_workspace.activate(workspace.clone(), cx);
                                             workspace.update(cx, |workspace, cx| {
-                                                workspace.focus_panel::<AgentPanel>(window, cx);
+                                                workspace.focus_drawer::<AgentPanel>(window, cx);
                                             });
                                         }
                                     })
@@ -3493,7 +3493,7 @@ pub(crate) mod tests {
             workspace.add_panel(panel, window, cx);
 
             // Open the dock and activate the agent panel so it's visible
-            workspace.focus_panel::<crate::AgentPanel>(window, cx);
+            workspace.focus_drawer::<crate::AgentPanel>(window, cx);
         });
 
         cx.run_until_parked();

crates/agent_ui/src/connection_view/thread_view.rs πŸ”—

@@ -783,7 +783,7 @@ impl ThreadView {
             && self
                 .workspace
                 .upgrade()
-                .and_then(|workspace| workspace.read(cx).panel::<AgentPanel>(cx))
+                .and_then(|workspace| workspace.read(cx).drawer::<AgentPanel>())
                 .is_some_and(|panel| {
                     panel.read(cx).start_thread_in() == &StartThreadIn::NewWorktree
                 });
@@ -8000,14 +8000,14 @@ pub(crate) fn open_link(
             }
             MentionUri::Selection { abs_path: None, .. } => {}
             MentionUri::Thread { id, name } => {
-                if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
+                if let Some(panel) = workspace.drawer::<AgentPanel>() {
                     panel.update(cx, |panel, cx| {
                         panel.open_thread(id, None, Some(name.into()), window, cx)
                     });
                 }
             }
             MentionUri::TextThread { path, .. } => {
-                if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
+                if let Some(panel) = workspace.drawer::<AgentPanel>() {
                     panel.update(cx, |panel, cx| {
                         panel
                             .open_saved_text_thread(path.as_path().into(), window, cx)

crates/agent_ui/src/inline_assistant.rs πŸ”—

@@ -259,7 +259,7 @@ impl InlineAssistant {
 
         let Some(inline_assist_target) = Self::resolve_inline_assist_target(
             workspace,
-            workspace.panel::<AgentPanel>(cx),
+            workspace.drawer::<AgentPanel>(),
             window,
             cx,
         ) else {
@@ -271,7 +271,7 @@ impl InlineAssistant {
             model_registry.configuration_error(model_registry.inline_assistant_model(), cx)
         };
 
-        let Some(agent_panel) = workspace.panel::<AgentPanel>(cx) else {
+        let Some(agent_panel) = workspace.drawer::<AgentPanel>() else {
             return;
         };
         let agent_panel = agent_panel.read(cx);
@@ -1974,7 +1974,7 @@ impl CodeActionProvider for AssistantCodeActionProvider {
             let (thread_store, history) = cx.update(|_window, cx| {
                 let panel = workspace
                     .read(cx)
-                    .panel::<AgentPanel>(cx)
+                    .drawer::<AgentPanel>()
                     .context("missing agent panel")?
                     .read(cx);
 

crates/agent_ui/src/sidebar.rs πŸ”—

@@ -428,7 +428,7 @@ impl Sidebar {
         )
         .detach();
 
-        if let Some(agent_panel) = workspace.read(cx).panel::<AgentPanel>(cx) {
+        if let Some(agent_panel) = workspace.read(cx).drawer::<AgentPanel>() {
             self.subscribe_to_agent_panel(&agent_panel, window, cx);
         }
     }
@@ -457,7 +457,7 @@ impl Sidebar {
         workspace: &Entity<Workspace>,
         cx: &App,
     ) -> Vec<ActiveThreadInfo> {
-        let Some(agent_panel) = workspace.read(cx).panel::<AgentPanel>(cx) else {
+        let Some(agent_panel) = workspace.read(cx).drawer::<AgentPanel>() else {
             return Vec::new();
         };
         let agent_panel_ref = agent_panel.read(cx);
@@ -511,7 +511,7 @@ impl Sidebar {
 
         self.focused_thread = active_workspace
             .as_ref()
-            .and_then(|ws| ws.read(cx).panel::<AgentPanel>(cx))
+            .and_then(|ws| ws.read(cx).drawer::<AgentPanel>())
             .and_then(|panel| panel.read(cx).active_connection_view().cloned())
             .and_then(|cv| cv.read(cx).parent_id(cx));
 
@@ -1441,10 +1441,10 @@ impl Sidebar {
         });
 
         workspace.update(cx, |workspace, cx| {
-            workspace.open_panel::<AgentPanel>(window, cx);
+            workspace.open_drawer::<AgentPanel>(cx);
         });
 
-        if let Some(agent_panel) = workspace.read(cx).panel::<AgentPanel>(cx) {
+        if let Some(agent_panel) = workspace.read(cx).drawer::<AgentPanel>() {
             agent_panel.update(cx, |panel, cx| {
                 panel.load_agent_thread(
                     agent,
@@ -1766,12 +1766,12 @@ impl Sidebar {
         });
 
         workspace.update(cx, |workspace, cx| {
-            if let Some(agent_panel) = workspace.panel::<AgentPanel>(cx) {
+            if let Some(agent_panel) = workspace.drawer::<AgentPanel>() {
                 agent_panel.update(cx, |panel, cx| {
                     panel.new_thread(&NewThread, window, cx);
                 });
             }
-            workspace.focus_panel::<AgentPanel>(window, cx);
+            workspace.focus_drawer::<AgentPanel>(window, cx);
         });
     }
 
@@ -1871,7 +1871,7 @@ impl Sidebar {
             return;
         };
 
-        let Some(agent_panel) = active_workspace.read(cx).panel::<AgentPanel>(cx) else {
+        let Some(agent_panel) = active_workspace.read(cx).drawer::<AgentPanel>() else {
             return;
         };
 
@@ -4091,7 +4091,7 @@ mod tests {
 
         workspace_a.read_with(cx, |workspace, cx| {
             assert!(
-                workspace.panel::<AgentPanel>(cx).is_some(),
+                workspace.drawer::<AgentPanel>().is_some(),
                 "Agent panel should exist"
             );
             let dock = workspace.right_dock().read(cx);

crates/agent_ui/src/thread_history_view.rs πŸ”—

@@ -749,7 +749,7 @@ impl RenderOnce for HistoryEntryElement {
                         .upgrade()
                         .and_then(|view| view.read(cx).workspace().upgrade())
                     {
-                        if let Some(panel) = workspace.read(cx).panel::<AgentPanel>(cx) {
+                        if let Some(panel) = workspace.read(cx).drawer::<AgentPanel>() {
                             panel.update(cx, |panel, cx| {
                                 if let Some(agent) = panel.selected_agent() {
                                     panel.load_agent_thread(

crates/agent_ui/src/ui/acp_onboarding_modal.rs πŸ”—

@@ -33,9 +33,9 @@ impl AcpOnboardingModal {
 
     fn open_panel(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
         self.workspace.update(cx, |workspace, cx| {
-            workspace.focus_panel::<AgentPanel>(window, cx);
+            workspace.focus_drawer::<AgentPanel>(window, cx);
 
-            if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
+            if let Some(panel) = workspace.drawer::<AgentPanel>() {
                 panel.update(cx, |panel, cx| {
                     panel.new_agent_thread(
                         AgentType::Custom {

crates/agent_ui/src/ui/claude_agent_onboarding_modal.rs πŸ”—

@@ -33,9 +33,9 @@ impl ClaudeCodeOnboardingModal {
 
     fn open_panel(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
         self.workspace.update(cx, |workspace, cx| {
-            workspace.focus_panel::<AgentPanel>(window, cx);
+            workspace.focus_drawer::<AgentPanel>(window, cx);
 
-            if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
+            if let Some(panel) = workspace.drawer::<AgentPanel>() {
                 panel.update(cx, |panel, cx| {
                     panel.new_agent_thread(
                         AgentType::Custom {

crates/agent_ui/src/ui/mention_crease.rs πŸ”—

@@ -273,7 +273,7 @@ fn open_thread(
 ) {
     use crate::AgentPanel;
 
-    let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
+    let Some(panel) = workspace.drawer::<AgentPanel>() else {
         return;
     };
 

crates/workspace/src/dock.rs πŸ”—

@@ -14,7 +14,6 @@ use settings::SettingsStore;
 use std::sync::Arc;
 use ui::{ContextMenu, Divider, DividerColor, IconButton, Tooltip, h_flex};
 use ui::{prelude::*, right_click_menu};
-use util::ResultExt as _;
 
 pub(crate) const RESIZE_HANDLE_SIZE: Pixels = px(6.);
 
@@ -41,6 +40,7 @@ pub trait Panel: Focusable + EventEmitter<PanelEvent> + Render + Sized {
     fn icon_label(&self, _window: &Window, _: &App) -> Option<String> {
         None
     }
+
     fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool {
         false
     }
@@ -78,7 +78,6 @@ pub trait PanelHandle: Send + Sync {
     fn icon(&self, window: &Window, cx: &App) -> Option<ui::IconName>;
     fn icon_tooltip(&self, window: &Window, cx: &App) -> &'static str;
     fn toggle_action(&self, window: &Window, cx: &App) -> Box<dyn Action>;
-    fn icon_label(&self, window: &Window, cx: &App) -> Option<String>;
     fn panel_focus_handle(&self, cx: &App) -> FocusHandle;
     fn to_any(&self) -> AnyView;
     fn activation_priority(&self, cx: &App) -> u32;
@@ -168,10 +167,6 @@ where
         self.read(cx).toggle_action()
     }
 
-    fn icon_label(&self, window: &Window, cx: &App) -> Option<String> {
-        self.read(cx).icon_label(window, cx)
-    }
-
     fn to_any(&self) -> AnyView {
         self.clone().into()
     }

crates/workspace/src/workspace.rs πŸ”—

@@ -7017,6 +7017,110 @@ impl Workspace {
         cx.notify();
     }
 
+    pub fn drawer<T: 'static>(&self) -> Option<Entity<T>> {
+        if let Some(left) = self.left_drawer.as_ref() {
+            if let Some(drawer) = left.view.clone().downcast().ok() {
+                return Some(drawer);
+            }
+        }
+        if let Some(right) = self.right_drawer.as_ref() {
+            if let Some(drawer) = right.view.clone().downcast().ok() {
+                return Some(drawer);
+            }
+        }
+        None
+    }
+
+    pub fn focus_drawer<T: Focusable>(
+        &mut self,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> Option<Entity<T>> {
+        if let Some(drawer) = self.left_drawer.as_mut() {
+            if let Some(view) = drawer.view.clone().downcast::<T>().ok() {
+                drawer.open = true;
+                view.focus_handle(cx).focus(window, cx);
+                return Some(view);
+            }
+        }
+        if let Some(drawer) = self.right_drawer.as_mut() {
+            if let Some(view) = drawer.view.clone().downcast::<T>().ok() {
+                drawer.open = true;
+                return Some(view);
+            }
+        }
+        None
+    }
+
+    pub fn toggle_drawer_focus<T: Focusable>(
+        &mut self,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> bool {
+        if let Some(drawer) = self.drawer::<T>() {
+            if drawer.focus_handle(cx).contains_focused(window, cx) {
+                // todo! focus the center?
+                false
+            } else {
+                drawer.focus_handle(cx).focus(window, cx);
+                true
+            }
+        } else {
+            false
+        }
+    }
+
+    pub fn open_drawer<T: Focusable>(&mut self, cx: &mut Context<Self>) {
+        if let Some(left) = self.left_drawer.as_mut() {
+            if left.view.clone().downcast::<T>().is_ok() {
+                left.open = true;
+                cx.notify();
+                return;
+            }
+        }
+        if let Some(right) = self.right_drawer.as_mut() {
+            if right.view.clone().downcast::<T>().is_ok() {
+                right.open = true;
+                cx.notify();
+                return;
+            }
+        }
+    }
+
+    pub fn close_drawer<T: Focusable>(&mut self, cx: &mut Context<Self>) {
+        if let Some(left) = self.left_drawer.as_mut() {
+            if left.view.clone().downcast::<T>().is_ok() {
+                left.open = false;
+                cx.notify();
+                return;
+            }
+        }
+        if let Some(right) = self.right_drawer.as_mut() {
+            if right.view.clone().downcast::<T>().is_ok() {
+                right.open = false;
+                cx.notify();
+                return;
+            }
+        }
+    }
+
+    pub fn remove_drawer<T: Focusable>(&mut self, cx: &mut Context<Self>) {
+        if let Some(left) = self.left_drawer.as_mut() {
+            if left.view.clone().downcast::<T>().is_ok() {
+                self.left_drawer = None;
+                cx.notify();
+                return;
+            }
+        }
+        if let Some(right) = self.right_drawer.as_mut() {
+            if right.view.clone().downcast::<T>().is_ok() {
+                self.right_drawer = None;
+                cx.notify();
+                return;
+            }
+        }
+    }
+
     pub fn left_drawer_view(&self) -> Option<&AnyView> {
         self.left_drawer.as_ref().map(|d| &d.view)
     }

crates/zed/src/main.rs πŸ”—

@@ -923,7 +923,7 @@ fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut
 
                     multi_workspace.update(cx, |multi_workspace, window, cx| {
                         multi_workspace.workspace().update(cx, |workspace, cx| {
-                            if let Some(panel) = workspace.focus_panel::<AgentPanel>(window, cx) {
+                            if let Some(panel) = workspace.focus_drawer::<AgentPanel>(window, cx) {
                                 panel.update(cx, |panel, cx| {
                                     panel.new_agent_thread_with_external_source_prompt(
                                         external_source_prompt,
@@ -951,7 +951,7 @@ fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut
                             workspace.update(cx, |workspace, cx| {
                                 let client = workspace.project().read(cx).client();
                                 let thread_store: Option<gpui::Entity<ThreadStore>> = workspace
-                                    .panel::<AgentPanel>(cx)
+                                    .drawer::<AgentPanel>()
                                     .map(|panel| panel.read(cx).thread_store().clone());
                                 anyhow::Ok((client, thread_store))
                             })
@@ -989,7 +989,7 @@ fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut
 
                     multi_workspace.update(cx, |_, window, cx| {
                         workspace.update(cx, |workspace, cx| {
-                            if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
+                            if let Some(panel) = workspace.drawer::<AgentPanel>() {
                                 panel.update(cx, |panel, cx| {
                                     panel.open_thread(
                                         session_id,

crates/zed/src/zed.rs πŸ”—

@@ -657,36 +657,50 @@ fn initialize_panels(
     })
 }
 
-fn setup_or_teardown_ai_panel<P: Panel>(
+fn setup_or_teardown_ai_panels(
     workspace: &mut Workspace,
+    prompt_builder: Arc<PromptBuilder>,
     window: &mut Window,
     cx: &mut Context<Workspace>,
-    load_panel: impl FnOnce(
-        WeakEntity<Workspace>,
-        AsyncWindowContext,
-    ) -> Task<anyhow::Result<Entity<P>>>
-    + 'static,
 ) -> Task<anyhow::Result<()>> {
     let disable_ai = SettingsStore::global(cx)
         .get::<DisableAiSettings>(None)
         .disable_ai
         || cfg!(test);
-    let existing_panel = workspace.panel::<P>(cx);
+    let existing_panel = workspace.panel::<agent_ui::sidebar::Sidebar>(cx);
     match (disable_ai, existing_panel) {
         (false, None) => cx.spawn_in(window, async move |workspace, cx| {
-            let panel = load_panel(workspace.clone(), cx.clone()).await?;
+            let agent_drawer =
+                agent_ui::AgentPanel::load(workspace.clone(), prompt_builder.clone(), cx.clone())
+                    .await?;
+            let sidebar_panel =
+                agent_ui::sidebar::Sidebar::load(workspace.clone(), cx.clone()).await?;
             workspace.update_in(cx, |workspace, window, cx| {
                 let disable_ai = SettingsStore::global(cx)
                     .get::<DisableAiSettings>(None)
                     .disable_ai;
-                let have_panel = workspace.panel::<P>(cx).is_some();
+                let have_panel = workspace.panel::<agent_ui::sidebar::Sidebar>(cx).is_some();
                 if !disable_ai && !have_panel {
-                    workspace.add_panel(panel, window, cx);
+                    let position =
+                        workspace::dock::PanelHandle::position(&sidebar_panel, window, cx);
+                    workspace.add_panel(sidebar_panel, window, cx);
+                    match position {
+                        workspace::dock::DockPosition::Left => {
+                            workspace.set_left_drawer(agent_drawer.into(), cx)
+                        }
+                        workspace::dock::DockPosition::Right => {
+                            workspace.set_right_drawer(agent_drawer.into(), cx)
+                        }
+                        workspace::dock::DockPosition::Bottom => {
+                            unreachable!("drawers cannot go on the bottom")
+                        }
+                    }
                 }
             })
         }),
         (true, Some(existing_panel)) => {
-            workspace.remove_panel::<P>(&existing_panel, window, cx);
+            workspace.remove_panel(&existing_panel, window, cx);
+            workspace.remove_drawer::<agent_ui::AgentPanel>(cx);
             Task::ready(Ok(()))
         }
         _ => Task::ready(Ok(())),
@@ -700,18 +714,7 @@ async fn initialize_agent_panel(
 ) -> anyhow::Result<()> {
     workspace_handle
         .update_in(&mut cx, |workspace, window, cx| {
-            let prompt_builder = prompt_builder.clone();
-            setup_or_teardown_ai_panel(workspace, window, cx, move |workspace, cx| {
-                agent_ui::AgentPanel::load(workspace, prompt_builder, cx)
-            })
-        })?
-        .await?;
-
-    workspace_handle
-        .update_in(&mut cx, |workspace, window, cx| {
-            setup_or_teardown_ai_panel(workspace, window, cx, |workspace, cx| {
-                agent_ui::sidebar::Sidebar::load(workspace, cx)
-            })
+            setup_or_teardown_ai_panels(workspace, prompt_builder.clone(), window, cx)
         })?
         .await?;
 
@@ -719,14 +722,8 @@ async fn initialize_agent_panel(
         let prompt_builder = prompt_builder.clone();
         cx.observe_global_in::<SettingsStore>(window, move |workspace, window, cx| {
             let prompt_builder = prompt_builder.clone();
-            setup_or_teardown_ai_panel(workspace, window, cx, move |workspace, cx| {
-                agent_ui::AgentPanel::load(workspace, prompt_builder, cx)
-            })
-            .detach_and_log_err(cx);
-            setup_or_teardown_ai_panel(workspace, window, cx, |workspace, cx| {
-                agent_ui::sidebar::Sidebar::load(workspace, cx)
-            })
-            .detach_and_log_err(cx);
+            setup_or_teardown_ai_panels(workspace, prompt_builder.clone(), window, cx)
+                .detach_and_log_err(cx);
         })
         .detach();