Rename Sidebar -> ThreadsPanel, get some agent_ui tests passing

Max Brunsfeld created

Change summary

crates/agent_ui/src/agent_panel.rs     |  38 +-----
crates/agent_ui/src/agent_ui.rs        |   3 
crates/agent_ui/src/connection_view.rs |   2 
crates/agent_ui/src/threads_panel.rs   |  45 ++++----
crates/workspace/src/workspace.rs      | 144 ++++++++++++---------------
crates/zed/src/zed.rs                  |  15 +-
6 files changed, 105 insertions(+), 142 deletions(-)

Detailed changes

crates/agent_ui/src/agent_panel.rs 🔗

@@ -430,14 +430,14 @@ pub fn init(cx: &mut App) {
                             if workspace.right_drawer_view().is_none() {
                                 workspace.set_right_drawer(panel_view, cx);
                             } else {
-                                workspace.toggle_right_drawer(cx);
+                                workspace.toggle_drawer::<AgentPanel>(cx);
                             }
                         }
                         DockPosition::Left | DockPosition::Bottom => {
                             if workspace.left_drawer_view().is_none() {
                                 workspace.set_left_drawer(panel_view, cx);
                             } else {
-                                workspace.toggle_left_drawer(cx);
+                                workspace.toggle_drawer::<AgentPanel>(cx);
                             }
                         }
                     }
@@ -822,7 +822,6 @@ pub struct AgentPanel {
     agent_navigation_menu: Option<Entity<ContextMenu>>,
     _extension_subscription: Option<Subscription>,
     width: Option<Pixels>,
-    height: Option<Pixels>,
     zoomed: bool,
     pending_serialization: Option<Task<Result<()>>>,
     onboarding: Entity<AgentPanelOnboarding>,
@@ -1159,7 +1158,6 @@ impl AgentPanel {
             agent_navigation_menu: None,
             _extension_subscription: extension_subscription,
             width: None,
-            height: None,
             zoomed: false,
             pending_serialization: None,
             onboarding,
@@ -1250,20 +1248,7 @@ impl AgentPanel {
     }
 
     pub fn is_visible(workspace: &Entity<Workspace>, cx: &App) -> bool {
-        let workspace_read = workspace.read(cx);
-
-        workspace_read
-            .drawer::<AgentPanel>()
-            .map(|panel| {
-                let panel_id = Entity::entity_id(&panel);
-
-                workspace_read.all_docks().iter().any(|dock| {
-                    dock.read(cx)
-                        .visible_panel()
-                        .is_some_and(|visible_panel| visible_panel.panel_id() == panel_id)
-                })
-            })
-            .unwrap_or(false)
+        workspace.read(cx).drawer_is_open::<Self>()
     }
 
     pub fn active_connection_view(&self) -> Option<&Entity<ConnectionView>> {
@@ -3070,11 +3055,6 @@ impl AgentPanel {
     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 {
@@ -5159,7 +5139,7 @@ mod tests {
             let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
             let panel =
                 cx.new(|cx| AgentPanel::new(workspace, text_thread_store, None, window, cx));
-            workspace.add_panel(panel, window, cx);
+            workspace.set_left_drawer(panel.clone().into(), cx);
         });
 
         cx.run_until_parked();
@@ -5671,7 +5651,7 @@ mod tests {
             let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
             let panel =
                 cx.new(|cx| AgentPanel::new(workspace, text_thread_store, None, window, cx));
-            workspace.add_panel(panel.clone(), window, cx);
+            workspace.set_left_drawer(panel.clone().into(), cx);
             panel
         });
 
@@ -5781,7 +5761,7 @@ mod tests {
             let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
             let panel =
                 cx.new(|cx| AgentPanel::new(workspace, text_thread_store, None, window, cx));
-            workspace.add_panel(panel.clone(), window, cx);
+            workspace.set_left_drawer(panel.clone().into(), cx);
             panel
         });
 
@@ -5866,17 +5846,17 @@ mod tests {
             let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
             let panel =
                 cx.new(|cx| AgentPanel::new(workspace, text_thread_store, None, window, cx));
-            workspace.add_panel(panel.clone(), window, cx);
+            workspace.set_left_drawer(panel.clone().into(), cx);
+            workspace.focus_drawer::<AgentPanel>(window, cx);
             panel
         });
 
         cx.run_until_parked();
 
         // Simulate worktree creation in progress and reset to Uninitialized
-        panel.update_in(cx, |panel, window, cx| {
+        panel.update(cx, |panel, _cx| {
             panel.worktree_creation_status = Some(WorktreeCreationStatus::Creating);
             panel.active_view = ActiveView::Uninitialized;
-            Panel::set_active(panel, true, window, cx);
             assert!(
                 matches!(panel.active_view, ActiveView::Uninitialized),
                 "set_active should not create a thread while worktree is being created"

crates/agent_ui/src/agent_ui.rs 🔗

@@ -23,7 +23,6 @@ mod mode_selector;
 mod model_selector;
 mod model_selector_popover;
 mod profile_selector;
-pub mod sidebar;
 mod slash_command;
 mod slash_command_picker;
 mod terminal_codegen;
@@ -35,6 +34,7 @@ mod text_thread_history;
 mod thread_history;
 mod thread_history_view;
 mod threads_archive_view;
+mod threads_panel;
 mod ui;
 
 use std::rc::Rc;
@@ -78,6 +78,7 @@ pub(crate) use model_selector_popover::ModelSelectorPopover;
 pub use text_thread_editor::{AgentPanelDelegate, TextThreadEditor};
 pub(crate) use thread_history::ThreadHistory;
 pub(crate) use thread_history_view::*;
+pub use threads_panel::ThreadsPanel;
 use zed_actions;
 
 actions!(

crates/agent_ui/src/connection_view.rs 🔗

@@ -3490,7 +3490,7 @@ pub(crate) mod tests {
                 cx.new(|cx| TextThreadStore::fake(workspace.project().clone(), cx));
             let panel =
                 cx.new(|cx| crate::AgentPanel::new(workspace, text_thread_store, None, window, cx));
-            workspace.add_panel(panel, window, cx);
+            workspace.set_left_drawer(panel.clone().into(), cx);
 
             // Open the dock and activate the agent panel so it's visible
             workspace.focus_drawer::<crate::AgentPanel>(window, cx);

crates/agent_ui/src/sidebar.rs → crates/agent_ui/src/threads_panel.rs 🔗

@@ -10,9 +10,9 @@ use db::kvp::KEY_VALUE_STORE;
 use editor::Editor;
 use feature_flags::{AgentV2FeatureFlag, FeatureFlagViewExt as _};
 use gpui::{
-    Action, AnyElement, App, AsyncWindowContext, Context, Entity, EventEmitter, FocusHandle,
-    Focusable, ListState, Pixels, Render, SharedString, Task, WeakEntity, Window, actions, list,
-    prelude::*, px,
+    AnyElement, App, AsyncWindowContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
+    ListState, Pixels, Render, SharedString, Task, WeakEntity, Window, actions, list, prelude::*,
+    px,
 };
 use menu::{Cancel, Confirm, SelectFirst, SelectLast, SelectNext, SelectPrevious};
 use project::Event as ProjectEvent;
@@ -232,7 +232,7 @@ fn workspace_label_from_path_list(path_list: &PathList) -> SharedString {
     }
 }
 
-pub struct Sidebar {
+pub struct ThreadsPanel {
     multi_workspace: WeakEntity<MultiWorkspace>,
     persistence_key: Option<u64>,
     is_open: bool,
@@ -254,7 +254,7 @@ pub struct Sidebar {
     _subscriptions: Vec<gpui::Subscription>,
 }
 
-impl Sidebar {
+impl ThreadsPanel {
     pub fn load(
         _workspace: WeakEntity<Workspace>,
         mut cx: AsyncWindowContext,
@@ -1856,7 +1856,7 @@ impl Sidebar {
     }
 }
 
-impl Sidebar {
+impl ThreadsPanel {
     pub fn is_open(&self) -> bool {
         self.is_open
     }
@@ -1984,15 +1984,15 @@ impl Sidebar {
     }
 }
 
-impl Focusable for Sidebar {
+impl Focusable for ThreadsPanel {
     fn focus_handle(&self, cx: &App) -> FocusHandle {
         self.filter_editor.focus_handle(cx)
     }
 }
 
-impl EventEmitter<PanelEvent> for Sidebar {}
+impl EventEmitter<PanelEvent> for ThreadsPanel {}
 
-impl Panel for Sidebar {
+impl Panel for ThreadsPanel {
     fn persistent_name() -> &'static str {
         "ThreadsSidebar"
     }
@@ -2056,7 +2056,7 @@ impl Panel for Sidebar {
     }
 }
 
-impl Render for Sidebar {
+impl Render for ThreadsPanel {
     fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         let ui_font = theme::setup_ui_font(window, cx);
         let docked_right = AgentSettings::get_global(cx).dock == settings::DockPosition::Right;
@@ -2170,7 +2170,7 @@ mod tests {
     fn setup_sidebar(
         multi_workspace: &Entity<MultiWorkspace>,
         cx: &mut gpui::VisualTestContext,
-    ) -> Entity<Sidebar> {
+    ) -> Entity<ThreadsPanel> {
         let (sidebar, _panel) = setup_sidebar_with_agent_panel(multi_workspace, cx);
         sidebar
     }
@@ -2178,22 +2178,17 @@ mod tests {
     fn setup_sidebar_with_agent_panel(
         multi_workspace: &Entity<MultiWorkspace>,
         cx: &mut gpui::VisualTestContext,
-    ) -> (Entity<Sidebar>, Entity<AgentPanel>) {
+    ) -> (Entity<ThreadsPanel>, Entity<AgentPanel>) {
         let workspace = multi_workspace.read_with(cx, |mw, _cx| mw.workspace().clone());
         let project = workspace.read_with(cx, |ws, _cx| ws.project().clone());
         let panel = add_agent_panel(&workspace, &project, cx);
         workspace.update_in(cx, |workspace, window, cx| {
-            workspace.right_dock().update(cx, |dock, cx| {
-                if let Some(panel_ix) = dock.panel_index_for_type::<AgentPanel>() {
-                    dock.activate_panel(panel_ix, window, cx);
-                }
-                dock.set_open(true, window, cx);
-            });
+            workspace.focus_drawer::<AgentPanel>(window, cx);
         });
         cx.run_until_parked();
         let multi_workspace_entity = multi_workspace.clone();
         let sidebar = workspace.update_in(cx, |_, window, cx| {
-            cx.new(|cx| Sidebar::new(multi_workspace_entity, window, cx))
+            cx.new(|cx| ThreadsPanel::new(multi_workspace_entity, window, cx))
         });
         (sidebar, panel)
     }
@@ -2242,7 +2237,7 @@ mod tests {
         cx.run_until_parked();
     }
 
-    fn open_and_focus_sidebar(sidebar: &Entity<Sidebar>, cx: &mut gpui::VisualTestContext) {
+    fn open_and_focus_sidebar(sidebar: &Entity<ThreadsPanel>, cx: &mut gpui::VisualTestContext) {
         cx.run_until_parked();
         sidebar.update_in(cx, |sidebar, window, cx| {
             sidebar.set_open(true, cx);
@@ -2252,7 +2247,7 @@ mod tests {
     }
 
     fn visible_entries_as_strings(
-        sidebar: &Entity<Sidebar>,
+        sidebar: &Entity<ThreadsPanel>,
         cx: &mut gpui::VisualTestContext,
     ) -> Vec<String> {
         sidebar.read_with(cx, |sidebar, _cx| {
@@ -3154,7 +3149,7 @@ mod tests {
         workspace.update_in(cx, |workspace, window, cx| {
             let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
             let panel = cx.new(|cx| AgentPanel::test_new(workspace, text_thread_store, window, cx));
-            workspace.add_panel(panel.clone(), window, cx);
+            workspace.set_left_drawer(panel.clone().into(), cx);
             panel
         })
     }
@@ -3266,7 +3261,11 @@ mod tests {
         );
     }
 
-    fn type_in_search(sidebar: &Entity<Sidebar>, query: &str, cx: &mut gpui::VisualTestContext) {
+    fn type_in_search(
+        sidebar: &Entity<ThreadsPanel>,
+        query: &str,
+        cx: &mut gpui::VisualTestContext,
+    ) {
         sidebar.update_in(cx, |sidebar, window, cx| {
             window.focus(&sidebar.filter_editor.focus_handle(cx), cx);
             sidebar.filter_editor.update(cx, |editor, cx| {

crates/workspace/src/workspace.rs 🔗

@@ -7017,6 +7017,34 @@ impl Workspace {
         cx.notify();
     }
 
+    fn drawer_mut<T: 'static>(&mut self) -> Option<(Entity<T>, &mut Drawer)> {
+        if let Some(left) = self.left_drawer.as_mut() {
+            if let Some(drawer) = left.view.clone().downcast().ok() {
+                return Some((drawer, left));
+            }
+        }
+        if let Some(right) = self.right_drawer.as_mut() {
+            if let Some(drawer) = right.view.clone().downcast().ok() {
+                return Some((drawer, right));
+            }
+        }
+        None
+    }
+
+    fn drawer_ref<T: 'static>(&self) -> Option<(Entity<T>, &Drawer)> {
+        if let Some(left) = self.left_drawer.as_ref() {
+            if let Some(drawer) = left.view.clone().downcast().ok() {
+                return Some((drawer, left));
+            }
+        }
+        if let Some(right) = self.right_drawer.as_ref() {
+            if let Some(drawer) = right.view.clone().downcast().ok() {
+                return Some((drawer, right));
+            }
+        }
+        None
+    }
+
     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() {
@@ -7036,18 +7064,10 @@ impl Workspace {
         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);
-            }
+        if let Some((view, drawer)) = self.drawer_mut::<T>() {
+            drawer.open = true;
+            view.focus_handle(cx).focus(window, cx);
+            return Some(view);
         }
         None
     }
@@ -7057,12 +7077,14 @@ impl Workspace {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> bool {
-        if let Some(drawer) = self.drawer::<T>() {
-            if drawer.focus_handle(cx).contains_focused(window, cx) {
+        if let Some((view, drawer)) = self.drawer_mut::<T>() {
+            if view.focus_handle(cx).contains_focused(window, cx) {
                 // todo! focus the center?
                 false
             } else {
-                drawer.focus_handle(cx).focus(window, cx);
+                drawer.open = true;
+                view.focus_handle(cx).focus(window, cx);
+                cx.notify();
                 true
             }
         } else {
@@ -7070,37 +7092,41 @@ impl Workspace {
         }
     }
 
-    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;
+    pub fn toggle_drawer<T: Focusable>(&mut self, cx: &mut Context<Self>) -> bool {
+        if let Some((_, drawer)) = self.drawer_mut::<T>() {
+            if drawer.open {
+                drawer.open = false;
                 cx.notify();
-                return;
-            }
-        }
-        if let Some(right) = self.right_drawer.as_mut() {
-            if right.view.clone().downcast::<T>().is_ok() {
-                right.open = true;
+                false
+            } else {
+                drawer.open = true;
                 cx.notify();
-                return;
+                true
             }
+        } else {
+            false
+        }
+    }
+
+    pub fn open_drawer<T: Focusable>(&mut self, cx: &mut Context<Self>) {
+        if let Some((_, drawer)) = self.drawer_mut::<T>() {
+            drawer.open = true;
+            cx.notify();
         }
     }
 
     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((_, drawer)) = self.drawer_mut::<T>() {
+            drawer.open = false;
+            cx.notify();
         }
-        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 drawer_is_open<T: 'static>(&self) -> bool {
+        if let Some((_, drawer)) = self.drawer_ref::<T>() {
+            drawer.open
+        } else {
+            false
         }
     }
 
@@ -7137,48 +7163,6 @@ impl Workspace {
         self.right_drawer.as_ref().is_some_and(|d| d.open)
     }
 
-    pub fn open_left_drawer(&mut self, cx: &mut Context<Self>) {
-        if let Some(drawer) = &mut self.left_drawer {
-            drawer.open = true;
-            cx.notify();
-        }
-    }
-
-    pub fn open_right_drawer(&mut self, cx: &mut Context<Self>) {
-        if let Some(drawer) = &mut self.right_drawer {
-            drawer.open = true;
-            cx.notify();
-        }
-    }
-
-    pub fn close_left_drawer(&mut self, cx: &mut Context<Self>) {
-        if let Some(drawer) = &mut self.left_drawer {
-            drawer.open = false;
-            cx.notify();
-        }
-    }
-
-    pub fn close_right_drawer(&mut self, cx: &mut Context<Self>) {
-        if let Some(drawer) = &mut self.right_drawer {
-            drawer.open = false;
-            cx.notify();
-        }
-    }
-
-    pub fn toggle_left_drawer(&mut self, cx: &mut Context<Self>) {
-        if let Some(drawer) = &mut self.left_drawer {
-            drawer.open = !drawer.open;
-            cx.notify();
-        }
-    }
-
-    pub fn toggle_right_drawer(&mut self, cx: &mut Context<Self>) {
-        if let Some(drawer) = &mut self.right_drawer {
-            drawer.open = !drawer.open;
-            cx.notify();
-        }
-    }
-
     pub fn remove_left_drawer(&mut self, cx: &mut Context<Self>) {
         self.left_drawer = None;
         cx.notify();

crates/zed/src/zed.rs 🔗

@@ -88,9 +88,9 @@ use workspace::notifications::{
 };
 
 use workspace::{
-    AppState, MultiWorkspace, NewFile, NewWindow, OpenLog, Panel, Toast, Workspace,
-    WorkspaceSettings, create_and_open_local_file,
-    notifications::simple_message_notification::MessageNotification, open_new,
+    AppState, MultiWorkspace, NewFile, NewWindow, OpenLog, Toast, Workspace, WorkspaceSettings,
+    create_and_open_local_file, notifications::simple_message_notification::MessageNotification,
+    open_new,
 };
 use workspace::{
     CloseIntent, CloseProject, CloseWindow, NotificationFrame, RestoreBanner,
@@ -667,19 +667,18 @@ fn setup_or_teardown_ai_panels(
         .get::<DisableAiSettings>(None)
         .disable_ai
         || cfg!(test);
-    let existing_panel = workspace.panel::<agent_ui::sidebar::Sidebar>(cx);
+    let existing_panel = workspace.panel::<agent_ui::ThreadsPanel>(cx);
     match (disable_ai, existing_panel) {
         (false, None) => cx.spawn_in(window, async move |workspace, cx| {
             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?;
+            let sidebar_panel = agent_ui::ThreadsPanel::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::<agent_ui::sidebar::Sidebar>(cx).is_some();
+                let have_panel = workspace.panel::<agent_ui::ThreadsPanel>(cx).is_some();
                 if !disable_ai && !have_panel {
                     let position =
                         workspace::dock::PanelHandle::position(&sidebar_panel, window, cx);
@@ -1048,7 +1047,7 @@ fn register_actions(
              window: &mut Window,
              cx: &mut Context<Workspace>| {
                 workspace
-                    .toggle_panel_focus::<agent_ui::sidebar::Sidebar>(window, cx);
+                    .toggle_panel_focus::<agent_ui::ThreadsPanel>(window, cx);
             },
         )
         .register_action({