Add "Toggle Agent Mode" action that enables / disables the

Max Brunsfeld , cameron , and Eric Holk created

multi-workspace sidebar

Co-authored-by: cameron <cameron.studdstreet@gmail.com>
Co-authored-by: Eric Holk <eric@zed.dev>

Change summary

crates/platform_title_bar/src/platform_title_bar.rs | 11 ++++
crates/sidebar/src/sidebar.rs                       |  3 +
crates/title_bar/src/title_bar.rs                   |  8 ++
crates/workspace/src/multi_workspace.rs             | 41 ++++++++++++++
crates/zed/src/zed/app_menus.rs                     |  2 
crates/zed_actions/src/lib.rs                       |  2 
6 files changed, 65 insertions(+), 2 deletions(-)

Detailed changes

crates/platform_title_bar/src/platform_title_bar.rs 🔗

@@ -33,6 +33,7 @@ pub struct PlatformTitleBar {
     system_window_tabs: Entity<SystemWindowTabs>,
     workspace_sidebar_open: bool,
     sidebar_has_notifications: bool,
+    is_singleton: bool,
 }
 
 impl PlatformTitleBar {
@@ -48,6 +49,7 @@ impl PlatformTitleBar {
             system_window_tabs,
             workspace_sidebar_open: false,
             sidebar_has_notifications: false,
+            is_singleton: false,
         }
     }
 
@@ -96,6 +98,15 @@ impl PlatformTitleBar {
         cx.notify();
     }
 
+    pub fn is_singleton(&self) -> bool {
+        self.is_singleton
+    }
+
+    pub fn set_singleton(&mut self, is_singleton: bool, cx: &mut Context<Self>) {
+        self.is_singleton = is_singleton;
+        cx.notify();
+    }
+
     pub fn is_multi_workspace_enabled(cx: &App) -> bool {
         cx.has_flag::<AgentV2FeatureFlag>() && !DisableAiSettings::get_global(cx).disable_ai
     }

crates/sidebar/src/sidebar.rs 🔗

@@ -218,6 +218,9 @@ impl Sidebar {
                 MultiWorkspaceEvent::WorkspaceRemoved(_) => {
                     this.update_entries(cx);
                 }
+                MultiWorkspaceEvent::SingletonModeChanged => {
+                    cx.notify();
+                }
             },
         )
         .detach();

crates/title_bar/src/title_bar.rs 🔗

@@ -371,18 +371,22 @@ impl TitleBar {
 
                     let is_open = multi_workspace.read(cx).is_sidebar_open();
                     let has_notifications = multi_workspace.read(cx).sidebar_has_notifications(cx);
+                    let is_singleton = multi_workspace.read(cx).is_singleton();
                     platform_titlebar.update(cx, |titlebar, cx| {
                         titlebar.set_workspace_sidebar_open(is_open, cx);
                         titlebar.set_sidebar_has_notifications(has_notifications, cx);
+                        titlebar.set_singleton(is_singleton, cx);
                     });
 
                     let platform_titlebar = platform_titlebar.clone();
                     let subscription = cx.observe(&multi_workspace, move |mw, cx| {
                         let is_open = mw.read(cx).is_sidebar_open();
                         let has_notifications = mw.read(cx).sidebar_has_notifications(cx);
+                        let is_singleton = mw.read(cx).is_singleton();
                         platform_titlebar.update(cx, |titlebar, cx| {
                             titlebar.set_workspace_sidebar_open(is_open, cx);
                             titlebar.set_sidebar_has_notifications(has_notifications, cx);
+                            titlebar.set_singleton(is_singleton, cx);
                         });
                     });
 
@@ -692,6 +696,10 @@ impl TitleBar {
             return None;
         }
 
+        if self.platform_titlebar.read(cx).is_singleton() {
+            return None;
+        }
+
         let is_sidebar_open = self.platform_titlebar.read(cx).is_workspace_sidebar_open();
 
         if is_sidebar_open {

crates/workspace/src/multi_workspace.rs 🔗

@@ -39,6 +39,7 @@ pub enum MultiWorkspaceEvent {
     ActiveWorkspaceChanged,
     WorkspaceAdded(Entity<Workspace>),
     WorkspaceRemoved(EntityId),
+    SingletonModeChanged,
 }
 
 pub enum SidebarEvent {
@@ -108,6 +109,7 @@ pub struct MultiWorkspace {
     active_workspace_index: usize,
     sidebar: Option<Box<dyn SidebarHandle>>,
     sidebar_open: bool,
+    is_singleton: bool,
     _sidebar_subscription: Option<Subscription>,
     pending_removal_tasks: Vec<Task<()>>,
     _serialize_task: Option<Task<()>>,
@@ -144,6 +146,7 @@ impl MultiWorkspace {
             active_workspace_index: 0,
             sidebar: None,
             sidebar_open: false,
+            is_singleton: false,
             _sidebar_subscription: None,
             pending_removal_tasks: Vec::new(),
             _serialize_task: None,
@@ -191,8 +194,29 @@ impl MultiWorkspace {
         cx.has_flag::<AgentV2FeatureFlag>() && !DisableAiSettings::get_global(cx).disable_ai
     }
 
+    pub fn is_singleton(&self) -> bool {
+        self.is_singleton
+    }
+
+    pub fn set_singleton(
+        &mut self,
+        is_singleton: bool,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if self.is_singleton == is_singleton {
+            return;
+        }
+        self.is_singleton = is_singleton;
+        if is_singleton && self.sidebar_open {
+            self.close_sidebar(window, cx);
+        }
+        cx.emit(MultiWorkspaceEvent::SingletonModeChanged);
+        cx.notify();
+    }
+
     pub fn toggle_sidebar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        if !self.multi_workspace_enabled(cx) {
+        if !self.multi_workspace_enabled(cx) || self.is_singleton {
             return;
         }
 
@@ -207,7 +231,7 @@ impl MultiWorkspace {
     }
 
     pub fn focus_sidebar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        if !self.multi_workspace_enabled(cx) {
+        if !self.multi_workspace_enabled(cx) || self.is_singleton {
             return;
         }
 
@@ -233,6 +257,9 @@ impl MultiWorkspace {
     }
 
     pub fn open_sidebar(&mut self, cx: &mut Context<Self>) {
+        if self.is_singleton {
+            return;
+        }
         self.sidebar_open = true;
         for workspace in &self.workspaces {
             workspace.update(cx, |workspace, cx| {
@@ -773,6 +800,16 @@ impl Render for MultiWorkspace {
                         this.activate_previous_workspace(window, cx);
                     },
                 ))
+                .on_action(cx.listener(
+                    |this: &mut Self, _: &zed_actions::agent::ToggleAgentMode, window, cx| {
+                        if this.is_singleton {
+                            this.set_singleton(false, window, cx);
+                            this.open_sidebar(cx);
+                        } else {
+                            this.set_singleton(true, window, cx);
+                        }
+                    },
+                ))
                 .when(self.multi_workspace_enabled(cx), |this| {
                     this.on_action(cx.listener(
                         |this: &mut Self, _: &ToggleWorkspaceSidebar, window, cx| {

crates/zed/src/zed/app_menus.rs 🔗

@@ -47,6 +47,8 @@ pub fn app_menus(cx: &mut App) -> Vec<Menu> {
         MenuItem::separator(),
         MenuItem::action("Diagnostics", diagnostics::Deploy),
         MenuItem::separator(),
+        MenuItem::action("Toggle Agent Mode", zed_actions::agent::ToggleAgentMode),
+        MenuItem::separator(),
     ];
 
     if ReleaseChannel::try_global(cx) == Some(ReleaseChannel::Dev) {

crates/zed_actions/src/lib.rs 🔗

@@ -456,6 +456,8 @@ pub mod agent {
             ResetAgentZoom,
             /// Pastes clipboard content without any formatting.
             PasteRaw,
+            /// Toggles the agent singleton mode for the current window.
+            ToggleAgentMode,
         ]
     );