From ee9191ecd2613c9eb9fac4d3a10f6a1ab228ea9b Mon Sep 17 00:00:00 2001 From: Cameron Mcloughlin Date: Tue, 17 Feb 2026 23:06:27 +0000 Subject: [PATCH] workspace: Add `Toggle` actions to all the side panels (#49395) Release Notes: - Add `toggle` actions to all panels to toggle visibility --------- Co-authored-by: Anthony Eid Co-authored-by: Zed Zippy <234243425+zed-zippy[bot]@users.noreply.github.com> --- assets/settings/default.json | 4 + crates/agent_ui/src/agent_panel.rs | 18 +++- crates/collab_ui/src/collab_panel.rs | 7 ++ crates/collab_ui/src/notification_panel.rs | 7 ++ crates/debugger_ui/src/debugger_ui.rs | 7 +- crates/git_ui/src/git_panel.rs | 7 ++ crates/outline_panel/src/outline_panel.rs | 7 ++ crates/project_panel/src/project_panel.rs | 10 +- crates/settings/src/vscode_import.rs | 1 + crates/settings_content/src/workspace.rs | 6 ++ crates/workspace/src/workspace.rs | 118 +++++++++++++++++++++ crates/workspace/src/workspace_settings.rs | 2 + crates/zed/src/zed.rs | 1 + crates/zed_actions/src/lib.rs | 6 ++ 14 files changed, 198 insertions(+), 3 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index d80eaf3faf662be113957a7bac932758b76cdf17..f45e9f541746363c08f4d41ff9df53c292d3ee30 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -145,6 +145,10 @@ "restore_on_file_reopen": true, // Whether to automatically close files that have been deleted on disk. "close_on_file_delete": false, + // Whether toggling a panel (e.g. with its keyboard shortcut) also closes + // the panel when it is already focused, instead of just moving focus back + // to the editor. + "close_panel_on_toggle": false, // Relative size of the drop target in the editor that will open dropped file as a split pane (0-0.5) // E.g. 0.25 == If you drop onto the top/bottom quarter of the pane a new vertical split will be used // If you drop onto the left/right quarter of the pane a new horizontal split will be used diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index dd23ece5ba94a0d1aab9053d4427659b18413ae2..89dee2b1bd669541a169fb4f042e0e44c5e15c3c 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -75,7 +75,7 @@ use zed_actions::{ agent::{ OpenAcpOnboardingModal, OpenOnboardingModal, OpenSettings, ResetAgentZoom, ResetOnboarding, }, - assistant::{OpenRulesLibrary, ToggleFocus}, + assistant::{OpenRulesLibrary, Toggle, ToggleFocus}, }; const AGENT_PANEL_KEY: &str = "agent_panel"; @@ -789,6 +789,22 @@ impl AgentPanel { } } + pub fn toggle( + workspace: &mut Workspace, + _: &Toggle, + window: &mut Window, + cx: &mut Context, + ) { + if workspace + .panel::(cx) + .is_some_and(|panel| panel.read(cx).enabled(cx)) + { + if !workspace.toggle_panel_focus::(window, cx) { + workspace.close_panel::(window, cx); + } + } + } + pub(crate) fn prompt_store(&self) -> &Option> { &self.prompt_store } diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 54bf5f3d22cf756db085b9ef81f30bc7465c1db5..6fa940a0f52a5bf72089147357db5fba2bf52530 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -45,6 +45,8 @@ use workspace::{ actions!( collab_panel, [ + /// Toggles the collab panel. + Toggle, /// Toggles focus on the collaboration panel. ToggleFocus, /// Removes the selected channel or contact. @@ -93,6 +95,11 @@ pub fn init(cx: &mut App) { }) } }); + workspace.register_action(|workspace, _: &Toggle, window, cx| { + if !workspace.toggle_panel_focus::(window, cx) { + workspace.close_panel::(window, cx); + } + }); workspace.register_action(|_, _: &OpenChannelNotes, window, cx| { let channel_id = ActiveCall::global(cx) .read(cx) diff --git a/crates/collab_ui/src/notification_panel.rs b/crates/collab_ui/src/notification_panel.rs index 99203bc867ff7da9e140bc4a886e291252a5153d..f9ce68a6afe8497c50096b153847070b3eca35a2 100644 --- a/crates/collab_ui/src/notification_panel.rs +++ b/crates/collab_ui/src/notification_panel.rs @@ -76,6 +76,8 @@ pub struct NotificationPresenter { actions!( notification_panel, [ + /// Toggles the notification panel. + Toggle, /// Toggles focus on the notification panel. ToggleFocus ] @@ -86,6 +88,11 @@ pub fn init(cx: &mut App) { workspace.register_action(|workspace, _: &ToggleFocus, window, cx| { workspace.toggle_panel_focus::(window, cx); }); + workspace.register_action(|workspace, _: &Toggle, window, cx| { + if !workspace.toggle_panel_focus::(window, cx) { + workspace.close_panel::(window, cx); + } + }); }) .detach(); } diff --git a/crates/debugger_ui/src/debugger_ui.rs b/crates/debugger_ui/src/debugger_ui.rs index 36651b99c0ed095c35d309a2e89c2d6ae9f12519..16094f89a9dc4c9ea6a7330a00967cd88fae0284 100644 --- a/crates/debugger_ui/src/debugger_ui.rs +++ b/crates/debugger_ui/src/debugger_ui.rs @@ -14,8 +14,8 @@ use tasks_ui::{Spawn, TaskOverrides}; use ui::{FluentBuilder, InteractiveElement}; use util::maybe; use workspace::{ItemHandle, ShutdownDebugAdapters, Workspace}; -use zed_actions::ToggleFocus; use zed_actions::debugger::OpenOnboardingModal; +use zed_actions::{Toggle, ToggleFocus}; pub mod attach_modal; pub mod debugger_panel; @@ -121,6 +121,11 @@ pub fn init(cx: &mut App) { .register_action(|workspace, _: &ToggleFocus, window, cx| { workspace.toggle_panel_focus::(window, cx); }) + .register_action(|workspace, _: &Toggle, window, cx| { + if !workspace.toggle_panel_focus::(window, cx) { + workspace.close_panel::(window, cx); + } + }) .register_action(|workspace: &mut Workspace, _: &Start, window, cx| { NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx); }) diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 4856af8594f110cb060917db1e1e38c1b367da8e..830870db63a3377bd3fff07eee57f53b6ae87d44 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -84,6 +84,8 @@ actions!( [ /// Closes the git panel. Close, + /// Toggles the git panel. + Toggle, /// Toggles focus on the git panel. ToggleFocus, /// Opens the git panel menu. @@ -225,6 +227,11 @@ pub fn register(workspace: &mut Workspace) { workspace.register_action(|workspace, _: &ToggleFocus, window, cx| { workspace.toggle_panel_focus::(window, cx); }); + workspace.register_action(|workspace, _: &Toggle, window, cx| { + if !workspace.toggle_panel_focus::(window, cx) { + workspace.close_panel::(window, cx); + } + }); workspace.register_action(|workspace, _: &ExpandCommitEditor, window, cx| { CommitModal::toggle(workspace, None, window, cx) }); diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index a6cce4e6845548c2615a755ad3c8e6e226be1110..445f63fa1cdc38cb358cf033cc49f404aa6e6d94 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -93,6 +93,8 @@ actions!( ToggleActiveEditorPin, /// Unfolds the selected directory. UnfoldDirectory, + /// Toggles the outline panel. + Toggle, /// Toggles focus on the outline panel. ToggleFocus, ] @@ -670,6 +672,11 @@ pub fn init(cx: &mut App) { workspace.register_action(|workspace, _: &ToggleFocus, window, cx| { workspace.toggle_panel_focus::(window, cx); }); + workspace.register_action(|workspace, _: &Toggle, window, cx| { + if !workspace.toggle_panel_focus::(window, cx) { + workspace.close_panel::(window, cx); + } + }); }) .detach(); } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index f92edad80c014aadca0f028285149992b9173a06..0f0fe6340c8ca9a2627598e0bff0974dcd008bfb 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -75,7 +75,10 @@ use workspace::{ notifications::{DetachAndPromptErr, NotifyResultExt, NotifyTaskExt}, }; use worktree::CreatedEntry; -use zed_actions::{project_panel::ToggleFocus, workspace::OpenWithSystem}; +use zed_actions::{ + project_panel::{Toggle, ToggleFocus}, + workspace::OpenWithSystem, +}; const PROJECT_PANEL_KEY: &str = "ProjectPanel"; const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX; @@ -418,6 +421,11 @@ pub fn init(cx: &mut App) { workspace.register_action(|workspace, _: &ToggleFocus, window, cx| { workspace.toggle_panel_focus::(window, cx); }); + workspace.register_action(|workspace, _: &Toggle, window, cx| { + if !workspace.toggle_panel_focus::(window, cx) { + workspace.close_panel::(window, cx); + } + }); workspace.register_action(|workspace, _: &ToggleHideGitIgnore, _, cx| { let fs = workspace.app_state().fs.clone(); diff --git a/crates/settings/src/vscode_import.rs b/crates/settings/src/vscode_import.rs index c32178ffc25accbb0c93d122b5e54980c35b953f..d0643be3bbee82be02c9c461a5f18ba62893a3cd 100644 --- a/crates/settings/src/vscode_import.rs +++ b/crates/settings/src/vscode_import.rs @@ -952,6 +952,7 @@ impl VsCodeSettings { bottom_dock_layout: None, centered_layout: None, close_on_file_delete: None, + close_panel_on_toggle: None, command_aliases: Default::default(), confirm_quit: self.read_enum("window.confirmBeforeClose", |s| match s { "always" | "keyboardOnly" => Some(true), diff --git a/crates/settings_content/src/workspace.rs b/crates/settings_content/src/workspace.rs index 8f628455a8dd90aa16cdc86f48984c84af5ebe10..3778ccc0373f4b937a08e3a435de40ad6a6d2cff 100644 --- a/crates/settings_content/src/workspace.rs +++ b/crates/settings_content/src/workspace.rs @@ -113,6 +113,12 @@ pub struct WorkspaceSettingsContent { /// /// Default: true pub zoomed_padding: Option, + /// Whether toggling a panel (e.g. with its keyboard shortcut) also closes + /// the panel when it is already focused, instead of just moving focus back + /// to the editor. + /// + /// Default: false + pub close_panel_on_toggle: Option, /// What draws window decorations/titlebar, the client application (Zed) or display server /// Default: client pub window_decorations: Option, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index aad36367f933cb54697bf637b9a58e32cc9d5039..4f44d282daf92ac3c952a9e447c9772c0912d4d1 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3630,6 +3630,8 @@ impl Workspace { /// Focus the panel of the given type if it isn't already focused. If it is /// already focused, then transfer focus back to the workspace center. + /// When the `close_panel_on_toggle` setting is enabled, also closes the + /// panel when transferring focus back to the center. pub fn toggle_panel_focus( &mut self, window: &mut Window, @@ -3641,6 +3643,10 @@ impl Workspace { did_focus_panel }); + if !did_focus_panel && WorkspaceSettings::get_global(cx).close_panel_on_toggle { + self.close_panel::(window, cx); + } + telemetry::event!( "Panel Button Clicked", name = T::persistent_name(), @@ -10546,6 +10552,118 @@ mod tests { }); } + #[gpui::test] + async fn test_close_panel_on_toggle(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.executor()); + + let project = Project::test(fs, [], cx).await; + let (workspace, cx) = + cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx)); + + let panel = workspace.update_in(cx, |workspace, window, cx| { + let panel = cx.new(|cx| TestPanel::new(DockPosition::Right, 100, cx)); + workspace.add_panel(panel.clone(), window, cx); + panel + }); + + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + pane.update_in(cx, |pane, window, cx| { + let item = cx.new(TestItem::new); + pane.add_item(Box::new(item), true, true, None, window, cx); + }); + + // Enable close_panel_on_toggle + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings(cx, |settings| { + settings.workspace.close_panel_on_toggle = Some(true); + }); + }); + + // Panel starts closed. Toggling should open and focus it. + workspace.update_in(cx, |workspace, window, cx| { + assert!(!workspace.right_dock().read(cx).is_open()); + workspace.toggle_panel_focus::(window, cx); + }); + + workspace.update_in(cx, |workspace, window, cx| { + assert!( + workspace.right_dock().read(cx).is_open(), + "Dock should be open after toggling from center" + ); + assert!( + panel.read(cx).focus_handle(cx).contains_focused(window, cx), + "Panel should be focused after toggling from center" + ); + }); + + // Panel is open and focused. Toggling should close the panel and + // return focus to the center. + workspace.update_in(cx, |workspace, window, cx| { + workspace.toggle_panel_focus::(window, cx); + }); + + workspace.update_in(cx, |workspace, window, cx| { + assert!( + !workspace.right_dock().read(cx).is_open(), + "Dock should be closed after toggling from focused panel" + ); + assert!( + !panel.read(cx).focus_handle(cx).contains_focused(window, cx), + "Panel should not be focused after toggling from focused panel" + ); + }); + + // Open the dock and focus something else so the panel is open but not + // focused. Toggling should focus the panel (not close it). + workspace.update_in(cx, |workspace, window, cx| { + workspace + .right_dock() + .update(cx, |dock, cx| dock.set_open(true, window, cx)); + window.focus(&pane.read(cx).focus_handle(cx), cx); + }); + + workspace.update_in(cx, |workspace, window, cx| { + assert!(workspace.right_dock().read(cx).is_open()); + assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx)); + workspace.toggle_panel_focus::(window, cx); + }); + + workspace.update_in(cx, |workspace, window, cx| { + assert!( + workspace.right_dock().read(cx).is_open(), + "Dock should remain open when toggling focuses an open-but-unfocused panel" + ); + assert!( + panel.read(cx).focus_handle(cx).contains_focused(window, cx), + "Panel should be focused after toggling an open-but-unfocused panel" + ); + }); + + // Now disable the setting and verify the original behavior: toggling + // from a focused panel moves focus to center but leaves the dock open. + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings(cx, |settings| { + settings.workspace.close_panel_on_toggle = Some(false); + }); + }); + + workspace.update_in(cx, |workspace, window, cx| { + workspace.toggle_panel_focus::(window, cx); + }); + + workspace.update_in(cx, |workspace, window, cx| { + assert!( + workspace.right_dock().read(cx).is_open(), + "Dock should remain open when setting is disabled" + ); + assert!( + !panel.read(cx).focus_handle(cx).contains_focused(window, cx), + "Panel should not be focused after toggling with setting disabled" + ); + }); + } + #[gpui::test] async fn test_pane_zoom_in_out(cx: &mut TestAppContext) { init_test(cx); diff --git a/crates/workspace/src/workspace_settings.rs b/crates/workspace/src/workspace_settings.rs index 1ef0cd29947782a3ddb68ce74ad8c0906c8adb1f..5575af3d7cf07fd7afd22ddbb78a620bab775714 100644 --- a/crates/workspace/src/workspace_settings.rs +++ b/crates/workspace/src/workspace_settings.rs @@ -31,6 +31,7 @@ pub struct WorkspaceSettings { pub text_rendering_mode: settings::TextRenderingMode, pub resize_all_panels_in_dock: Vec, pub close_on_file_delete: bool, + pub close_panel_on_toggle: bool, pub use_system_window_tabs: bool, pub zoomed_padding: bool, pub window_decorations: settings::WindowDecorations, @@ -108,6 +109,7 @@ impl Settings for WorkspaceSettings { .map(Into::into) .collect(), close_on_file_delete: workspace.close_on_file_delete.unwrap(), + close_panel_on_toggle: workspace.close_panel_on_toggle.unwrap(), use_system_window_tabs: workspace.use_system_window_tabs.unwrap(), zoomed_padding: workspace.zoomed_padding.unwrap(), window_decorations: workspace.window_decorations.unwrap(), diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 24f91e1d953d99c7c1e53c3ea76a9551ad0def7e..ed94725511b7fb5eaf31af0829979f2f9a05807a 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -740,6 +740,7 @@ async fn initialize_agent_panel( workspace .register_action(agent_ui::AgentPanel::toggle_focus) + .register_action(agent_ui::AgentPanel::toggle) .register_action(agent_ui::InlineAssistant::inline_assist); } })?; diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index c617ddd1da6211d5c3343a2820716751817aeb6a..5b0295b903242e1c8405ae91016fe2bfea815404 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -302,6 +302,8 @@ pub mod project_panel { actions!( project_panel, [ + /// Toggles the project panel. + Toggle, /// Toggles focus on the project panel. ToggleFocus ] @@ -465,6 +467,8 @@ pub mod assistant { actions!( agent, [ + /// Toggles the agent panel. + Toggle, #[action(deprecated_aliases = ["assistant::ToggleFocus"])] ToggleFocus ] @@ -639,6 +643,8 @@ actions!( actions!( debug_panel, [ + /// Toggles the debug panel. + Toggle, /// Toggles focus on the debug panel. ToggleFocus ]