From fa1c08f5e2866563c95d6ad3e0ac0ed9946c961e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Mar 2026 13:15:41 -0700 Subject: [PATCH] Move agent drawer together with threads panel --- crates/agent_ui/src/threads_panel.rs | 2 +- crates/git_ui/src/git_panel.rs | 2 +- crates/project_panel/src/project_panel.rs | 2 +- crates/terminal_view/src/terminal_panel.rs | 2 +- crates/workspace/src/dock.rs | 95 +++++++++------------- crates/workspace/src/persistence.rs | 1 - crates/workspace/src/workspace.rs | 49 ++++++++++- crates/zed/src/zed.rs | 79 +++++++++++------- 8 files changed, 140 insertions(+), 92 deletions(-) diff --git a/crates/agent_ui/src/threads_panel.rs b/crates/agent_ui/src/threads_panel.rs index 7aaebb10cd0bf6d4237ca77f22a21bcafe47f374..66c7caadb121ae080af29cdab634a608ddc8da61 100644 --- a/crates/agent_ui/src/threads_panel.rs +++ b/crates/agent_ui/src/threads_panel.rs @@ -2209,7 +2209,7 @@ impl Panel for ThreadsPanel { } fn activation_priority(&self) -> u32 { - 4 + 0 } fn enabled(&self, cx: &App) -> bool { diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index cfbe8a42d8f39c98dbe550a6ce83c8f91bd3ef42..a9350300dc5b4af1beca3a47914af8f568c9567e 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -5810,7 +5810,7 @@ impl Panel for GitPanel { } fn activation_priority(&self) -> u32 { - 2 + 3 } } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 1d91c4bf33a2afc368118fe75cd31e7fa987f8e6..ad48fe2bdbf772938d21e1431193d31d47b4bcb1 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -7091,7 +7091,7 @@ impl Panel for ProjectPanel { } fn activation_priority(&self) -> u32 { - 0 + 1 } } diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 50cd9ea45018136249641be61f430df0eb4a2ac7..f04dcce3f9eea1d687776f2c2425e5a937cd45cd 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -1643,7 +1643,7 @@ impl Panel for TerminalPanel { } fn activation_priority(&self) -> u32 { - 1 + 2 } fn enabled(&self, cx: &App) -> bool { diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 93b61efd0761c7794d974876f1cfbcbd860fd602..4410f877ed766836fe8861cf77a4bf24567638c6 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -90,6 +90,14 @@ pub trait PanelHandle: Send + Sync { fn to_any(&self) -> AnyView; fn activation_priority(&self, cx: &App) -> u32; fn enabled(&self, cx: &App) -> bool; + fn add_to_dock( + &self, + dock: &Entity, + workspace: WeakEntity, + window: &mut Window, + cx: &mut App, + ) -> usize; + fn remove_from_dock(&self, dock: &Entity, window: &mut Window, cx: &mut App); fn move_to_next_position(&self, window: &mut Window, cx: &mut App) { let current_position = self.position(window, cx); let next_position = [ @@ -190,6 +198,22 @@ where fn enabled(&self, cx: &App) -> bool { self.read(cx).enabled(cx) } + + fn add_to_dock( + &self, + dock: &Entity, + workspace: WeakEntity, + window: &mut Window, + cx: &mut App, + ) -> usize { + dock.update(cx, |dock, cx| { + dock.add_panel(self.clone(), workspace, window, cx) + }) + } + + fn remove_from_dock(&self, dock: &Entity, window: &mut Window, cx: &mut App) { + dock.update(cx, |dock, cx| dock.remove_panel(self, window, cx)); + } } impl From<&dyn PanelHandle> for AnyView { @@ -265,7 +289,7 @@ impl DockPosition { struct PanelEntry { panel: Arc, - _subscriptions: [Subscription; 3], + _subscriptions: [Subscription; 2], } impl Dock { @@ -465,57 +489,6 @@ impl Dock { ) -> usize { let subscriptions = [ cx.observe(&panel, |_, _, cx| cx.notify()), - cx.observe_global_in::(window, { - let workspace = workspace.clone(); - let panel = panel.clone(); - - move |this, window, cx| { - let new_position = panel.read(cx).position(window, cx); - if new_position == this.position { - return; - } - - let Ok(new_dock) = workspace.update(cx, |workspace, cx| { - if panel.is_zoomed(window, cx) { - workspace.zoomed_position = Some(new_position); - } - match new_position { - DockPosition::Left => &workspace.left_dock, - DockPosition::Bottom => &workspace.bottom_dock, - DockPosition::Right => &workspace.right_dock, - } - .clone() - }) else { - return; - }; - - let was_visible = this.is_open() - && this.visible_panel().is_some_and(|active_panel| { - active_panel.panel_id() == Entity::entity_id(&panel) - }); - - this.remove_panel(&panel, window, cx); - - new_dock.update(cx, |new_dock, cx| { - new_dock.remove_panel(&panel, window, cx); - }); - - new_dock.update(cx, |new_dock, cx| { - let index = - new_dock.add_panel(panel.clone(), workspace.clone(), window, cx); - if was_visible { - new_dock.set_open(true, window, cx); - new_dock.activate_panel(index, window, cx); - } - }); - - workspace - .update(cx, |workspace, cx| { - workspace.serialize_workspace(window, cx); - }) - .ok(); - } - }), cx.subscribe_in( &panel, window, @@ -571,11 +544,15 @@ impl Dock { ), ]; - let index = match self - .panel_entries - .binary_search_by_key(&panel.read(cx).activation_priority(), |entry| { - entry.panel.activation_priority(cx) - }) { + let position = self.position; + let priority = panel.activation_priority(cx); + let index = match self.panel_entries.binary_search_by(|probe| { + let other_priority = probe.panel.activation_priority(cx); + match position { + DockPosition::Left | DockPosition::Bottom => other_priority.cmp(&priority), + DockPosition::Right => priority.cmp(&other_priority), + } + }) { Ok(ix) => { if cfg!(debug_assertions) { panic!( @@ -668,6 +645,10 @@ impl Dock { self.panel_entries.len() } + pub fn panels(&self) -> impl Iterator> { + self.panel_entries.iter().map(|entry| &entry.panel) + } + pub fn activate_panel(&mut self, panel_ix: usize, window: &mut Window, cx: &mut Context) { if Some(panel_ix) != self.active_panel_index { if let Some(active_panel) = self.active_panel_entry() { diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 89ce7dade6e17d5b422dceb46cd9b0a6107eaa46..cdb50bcf62f5adbce5b45d4ea9ad9f2a85464c3d 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -4002,7 +4002,6 @@ mod tests { use crate::multi_workspace::MultiWorkspace; use crate::persistence::read_multi_workspace_state; use feature_flags::FeatureFlagAppExt; - use project::Project; crate::tests::init_test(cx); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 9fa00a7574c2e7c4485675d8e7f7d45af954c8af..10492a3062a3f6e4bf01105cd75414bdd181c313 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1394,7 +1394,7 @@ impl Workspace { }) .detach(); - cx.observe_global::(|_, cx| { + cx.observe_global_in::(window, |this, window, cx| { if ProjectSettings::get_global(cx).session.trust_all_worktrees { if let Some(trusted_worktrees) = TrustedWorktrees::try_get_global(cx) { trusted_worktrees.update(cx, |trusted_worktrees, cx| { @@ -1402,6 +1402,7 @@ impl Workspace { }) } } + this.reposition_panels(window, cx); }) .detach(); } @@ -2153,6 +2154,40 @@ impl Workspace { } } + fn reposition_panels(&mut self, window: &mut Window, cx: &mut Context) { + let mut panels_to_move = Vec::new(); + for dock_entity in [&self.left_dock, &self.bottom_dock, &self.right_dock] { + let dock = dock_entity.read(cx); + let old_position = dock.position(); + let visible_panel_id = dock.visible_panel().map(|panel| panel.panel_id()); + for panel in dock.panels() { + let new_position = panel.position(window, cx); + if new_position != old_position { + let was_visible = Some(panel.panel_id()) == visible_panel_id; + panels_to_move.push((dock_entity, panel.clone(), new_position, was_visible)); + } + } + } + + for (old_dock, panel, new_position, was_visible) in panels_to_move { + if panel.is_zoomed(window, cx) { + self.zoomed_position = Some(new_position); + } + + panel.remove_from_dock(old_dock, window, cx); + let new_dock = self.dock_at_position(new_position).clone(); + let index = panel.add_to_dock(&new_dock, self.weak_self.clone(), window, cx); + if was_visible { + new_dock.update(cx, |new_dock, cx| { + new_dock.set_open(true, window, cx); + new_dock.activate_panel(index, window, cx); + }); + } + } + + self.serialize_workspace(window, cx); + } + pub fn status_bar(&self) -> &Entity { &self.status_bar } @@ -7012,6 +7047,11 @@ impl Workspace { view: Entity, cx: &mut Context, ) { + if let Some(drawer) = self.right_drawer.as_mut() { + if drawer.view.entity_id() == view.entity_id() { + self.right_drawer.take(); + } + } self.left_drawer = Some(Drawer::new(view)); cx.notify(); } @@ -7021,6 +7061,11 @@ impl Workspace { view: Entity, cx: &mut Context, ) { + if let Some(drawer) = self.left_drawer.as_mut() { + if drawer.view.entity_id() == view.entity_id() { + self.left_drawer.take(); + } + } self.right_drawer = Some(Drawer::new(view)); cx.notify(); } @@ -7938,7 +7983,7 @@ impl Drawer { Self { view: view.into(), focus_handle_fn: Box::new(move |cx| entity.focus_handle(cx)), - open: true, + open: false, custom_width: None, } } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 2cd2102690cda526bc3a17adec9e27f1c16d98cb..20378904f3ee996af04e9b5c88e9da5cf70f2040 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -83,6 +83,7 @@ use util::rel_path::RelPath; use util::{ResultExt, asset_str, maybe}; use uuid::Uuid; use vim_mode_setting::VimModeSetting; +use workspace::dock::PanelHandle; use workspace::notifications::{ NotificationId, SuppressEvent, dismiss_app_notification, show_app_notification, }; @@ -669,41 +670,63 @@ fn setup_or_teardown_ai_panels( || cfg!(test); let existing_panel = workspace.panel::(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::ThreadsPanel::load(workspace.clone(), cx.clone()).await?; - workspace.update_in(cx, |workspace, window, cx| { - let disable_ai = SettingsStore::global(cx) - .get::(None) - .disable_ai; - let have_panel = workspace.panel::(cx).is_some(); - if !disable_ai && !have_panel { - 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, cx) - } - workspace::dock::DockPosition::Right => { - workspace.set_right_drawer(agent_drawer, cx) - } - workspace::dock::DockPosition::Bottom => { - unreachable!("drawers cannot go on the bottom") + (false, None) => { + return cx.spawn_in(window, async move |workspace, cx| { + let agent_drawer = agent_ui::AgentPanel::load( + workspace.clone(), + prompt_builder.clone(), + cx.clone(), + ) + .await?; + let threads_panel = + agent_ui::ThreadsPanel::load(workspace.clone(), cx.clone()).await?; + workspace.update_in(cx, |workspace, window, cx| { + let disable_ai = SettingsStore::global(cx) + .get::(None) + .disable_ai; + let have_panel = workspace.panel::(cx).is_some(); + let position = PanelHandle::position(&threads_panel, window, cx); + if !disable_ai && !have_panel { + workspace.add_panel(threads_panel, window, cx); + match position { + workspace::dock::DockPosition::Left => { + workspace.set_left_drawer(agent_drawer, cx) + } + workspace::dock::DockPosition::Right => { + workspace.set_right_drawer(agent_drawer, cx) + } + workspace::dock::DockPosition::Bottom => { + unreachable!("drawers cannot go on the bottom") + } } } - } - }) - }), + }) + }); + } (true, Some(existing_panel)) => { workspace.remove_panel(&existing_panel, window, cx); workspace.remove_drawer::(cx); - Task::ready(Ok(())) } - _ => Task::ready(Ok(())), + (false, Some(existing_panel)) => { + let position = PanelHandle::position(&existing_panel, window, cx); + if let Some(agent_drawer) = workspace.drawer::() { + match position { + workspace::dock::DockPosition::Left => { + workspace.set_left_drawer(agent_drawer, cx) + } + workspace::dock::DockPosition::Right => { + workspace.set_right_drawer(agent_drawer, cx) + } + workspace::dock::DockPosition::Bottom => { + unreachable!("drawers cannot go on the bottom") + } + } + } + } + _ => {} } + + Task::ready(Ok(())) } async fn initialize_agent_panel(