From c049193fd93b2c27d3e4a8a40f5e1739db4640b1 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 8 May 2026 13:36:03 +0300 Subject: [PATCH] Make all status bar tools able to hide its button via UI (#54971) Closes https://github.com/zed-industries/zed/discussions/53471 Adds a requirement on status bar items to provide a way to hide themselves. image image Release Notes: - Added a way to hide sidebar buttons --- .../src/activity_indicator.rs | 5 + crates/agent_ui/src/agent_panel.rs | 6 ++ crates/collab_ui/src/collab_panel.rs | 6 ++ crates/debugger_ui/src/debugger_panel.rs | 6 ++ crates/diagnostics/src/items.rs | 12 ++- .../src/edit_prediction_button.rs | 9 +- .../src/active_buffer_encoding.rs | 15 ++- crates/git_ui/src/conflict_view.rs | 11 ++- crates/git_ui/src/git_panel.rs | 6 ++ crates/go_to_line/src/cursor_position.rs | 11 ++- crates/image_viewer/src/image_info.rs | 9 +- .../src/active_buffer_language.rs | 15 ++- crates/language_tools/src/lsp_button.rs | 8 +- .../src/line_ending_indicator.rs | 15 ++- crates/outline_panel/src/outline_panel.rs | 6 ++ crates/project_panel/src/project_panel.rs | 6 ++ crates/search/src/search_status_button.rs | 10 +- crates/terminal_view/src/terminal_panel.rs | 6 ++ .../src/active_toolchain.rs | 12 ++- crates/vim/src/mode_indicator.rs | 11 ++- crates/workspace/src/active_file_name.rs | 12 ++- crates/workspace/src/dock.rs | 25 +++++ crates/workspace/src/status_bar.rs | 99 +++++++++++++++++-- crates/workspace/src/workspace.rs | 6 +- 24 files changed, 292 insertions(+), 35 deletions(-) diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index 0abb0622f9e64ffa2a3e14de27e44734297b9489..dccf7cf6f3153df7fb82a1ed8e3242a07fcba2c5 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -817,4 +817,9 @@ impl StatusItemView for ActivityIndicator { _: &mut Context, ) { } + + fn hide_setting(&self, _: &App) -> Option { + // Activity indicator auto-hides when there's no work to display. + None + } } diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index b17c52818be8ada163588b7eece0ecaba1e65d84..10fe51feb732eedad67f67d24010177c722896f9 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -3039,6 +3039,12 @@ impl Panel for AgentPanel { true } + fn hide_button_setting(&self, _: &App) -> Option { + Some(workspace::HideStatusItem::new(|settings| { + settings.agent.get_or_insert_default().button = Some(false); + })) + } + fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool { self.zoomed } diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 5fe6d839569b63d56eb1b766698d0e1b48802ef7..13208fe5ae147d04ab4abfea2cad6132bc9bfa0a 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -3830,6 +3830,12 @@ impl Panel for CollabPanel { fn activation_priority(&self) -> u32 { 5 } + + fn hide_button_setting(&self, _: &App) -> Option { + Some(workspace::HideStatusItem::new(|settings| { + settings.collaboration_panel.get_or_insert_default().button = Some(false); + })) + } } impl Focusable for CollabPanel { diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 36327d7695c1312aaca8b52c1dd89fce7820df31..c034363bcd9e923692a43f002fff14b5614b470b 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -1606,6 +1606,12 @@ impl Panel for DebugPanel { 7 } + fn hide_button_setting(&self, _: &App) -> Option { + Some(workspace::HideStatusItem::new(|settings| { + settings.debugger.get_or_insert_default().button = Some(false); + })) + } + fn set_active(&mut self, _: bool, _: &mut Window, _: &mut Context) {} fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool { diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index 67a6877bbe95778815d9470c0d9c8360657328f3..7733dab8201f24ea6e6823c14e76e4e6b17d5320 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -2,15 +2,15 @@ use std::time::Duration; use editor::{Editor, MultiBufferOffset}; use gpui::{ - Context, Entity, EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, Task, - WeakEntity, Window, + App, Context, Entity, EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, + Task, WeakEntity, Window, }; use language::Diagnostic; use project::project_settings::{GoToDiagnosticSeverityFilter, ProjectSettings}; use settings::Settings; use ui::{Button, ButtonLike, Color, Icon, IconName, Label, Tooltip, h_flex, prelude::*}; use util::ResultExt; -use workspace::{StatusItemView, ToolbarItemEvent, Workspace, item::ItemHandle}; +use workspace::{HideStatusItem, StatusItemView, ToolbarItemEvent, Workspace, item::ItemHandle}; use crate::{Deploy, IncludeWarnings, ProjectDiagnosticsEditor}; @@ -224,4 +224,10 @@ impl StatusItemView for DiagnosticIndicator { } cx.notify(); } + + fn hide_setting(&self, _: &App) -> Option { + Some(HideStatusItem::new(|settings| { + settings.diagnostics.get_or_insert_default().button = Some(false); + })) + } } diff --git a/crates/edit_prediction_ui/src/edit_prediction_button.rs b/crates/edit_prediction_ui/src/edit_prediction_button.rs index 9f2b7a5f1fcf07f9b4585b8ba20b80a49c2b2435..43edcfb8910a0366f5e0af39b77771cdf2410d75 100644 --- a/crates/edit_prediction_ui/src/edit_prediction_button.rs +++ b/crates/edit_prediction_ui/src/edit_prediction_button.rs @@ -37,7 +37,7 @@ use ui::{ use util::ResultExt as _; use workspace::{ - StatusItemView, Toast, Workspace, create_and_open_local_file, item::ItemHandle, + HideStatusItem, StatusItemView, Toast, Workspace, create_and_open_local_file, item::ItemHandle, notifications::NotificationId, }; use zed_actions::{OpenBrowser, OpenSettingsAt}; @@ -1364,6 +1364,13 @@ impl StatusItemView for EditPredictionButton { } cx.notify(); } + + fn hide_setting(&self, _: &App) -> Option { + // This button is already gated on having a non-disabled edit + // prediction provider, which the user manages through provider/AI + // settings. + None + } } async fn open_disabled_globs_setting_in_editor( diff --git a/crates/encoding_selector/src/active_buffer_encoding.rs b/crates/encoding_selector/src/active_buffer_encoding.rs index 42fd5f662f66c8e9f1eaa18953c6765c51244e77..6d78234314254783fe69f8238c75ad072686611b 100644 --- a/crates/encoding_selector/src/active_buffer_encoding.rs +++ b/crates/encoding_selector/src/active_buffer_encoding.rs @@ -3,13 +3,13 @@ use crate::{EncodingSelector, Toggle}; use editor::Editor; use encoding_rs::{Encoding, UTF_8}; use gpui::{ - Context, Entity, IntoElement, ParentElement, Render, Styled, Subscription, WeakEntity, Window, - div, + App, Context, Entity, IntoElement, ParentElement, Render, Styled, Subscription, WeakEntity, + Window, div, }; use project::Project; use ui::{Button, ButtonCommon, Clickable, LabelSize, Tooltip}; use workspace::{ - StatusBarSettings, StatusItemView, Workspace, + EncodingDisplayOptions, HideStatusItem, StatusBarSettings, StatusItemView, Workspace, item::{ItemHandle, Settings}, }; @@ -131,4 +131,13 @@ impl StatusItemView for ActiveBufferEncoding { cx.notify(); } + + fn hide_setting(&self, _: &App) -> Option { + Some(HideStatusItem::new(|settings| { + settings + .status_bar + .get_or_insert_default() + .active_encoding_button = Some(EncodingDisplayOptions::Disabled); + })) + } } diff --git a/crates/git_ui/src/conflict_view.rs b/crates/git_ui/src/conflict_view.rs index d5c5fe02bfe5b56a7dcc4daa4d63d8ab8de4b14b..70e10168adf4de5fba368188686e900295b6b29c 100644 --- a/crates/git_ui/src/conflict_view.rs +++ b/crates/git_ui/src/conflict_view.rs @@ -18,7 +18,7 @@ use settings::Settings; use std::{ops::Range, sync::Arc}; use ui::{ButtonLike, Divider, Tooltip, prelude::*}; use util::{ResultExt as _, debug_panic, maybe}; -use workspace::{StatusItemView, Workspace, item::ItemHandle}; +use workspace::{HideStatusItem, StatusItemView, Workspace, item::ItemHandle}; use zed_actions::agent::{ ConflictContent, ResolveConflictedFilesWithAgent, ResolveConflictsWithAgent, }; @@ -678,4 +678,13 @@ impl StatusItemView for MergeConflictIndicator { _: &mut Context, ) { } + + fn hide_setting(&self, _: &App) -> Option { + Some(HideStatusItem::new(|settings| { + settings + .agent + .get_or_insert_default() + .show_merge_conflict_indicator = Some(false); + })) + } } diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 4262d8bf3982e18dcd7132554be500fdf29b99f4..7b8898b9ab8dc81aebd677ccfe8587d1f142f1e9 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -6214,6 +6214,12 @@ impl Panel for GitPanel { fn activation_priority(&self) -> u32 { 3 } + + fn hide_button_setting(&self, _: &App) -> Option { + Some(workspace::HideStatusItem::new(|settings| { + settings.git_panel.get_or_insert_default().button = Some(false); + })) + } } impl PanelHeader for GitPanel {} diff --git a/crates/go_to_line/src/cursor_position.rs b/crates/go_to_line/src/cursor_position.rs index 03bec51ac209fd6e3c254689b3b7caa2695fa450..f1f3a61c977f56349e8900a34f2761348818914d 100644 --- a/crates/go_to_line/src/cursor_position.rs +++ b/crates/go_to_line/src/cursor_position.rs @@ -8,7 +8,7 @@ use ui::{ Render, Tooltip, Window, div, }; use util::paths::FILE_ROW_COLUMN_DELIMITER; -use workspace::{StatusBarSettings, StatusItemView, Workspace, item::ItemHandle}; +use workspace::{HideStatusItem, StatusBarSettings, StatusItemView, Workspace, item::ItemHandle}; #[derive(Copy, Clone, Debug, Default, PartialOrd, PartialEq)] pub(crate) struct SelectionStats { @@ -290,6 +290,15 @@ impl StatusItemView for CursorPosition { cx.notify(); } + + fn hide_setting(&self, _: &App) -> Option { + Some(HideStatusItem::new(|settings| { + settings + .status_bar + .get_or_insert_default() + .cursor_position_button = Some(false); + })) + } } #[derive(Clone, Copy, PartialEq, Eq, RegisterSetting)] diff --git a/crates/image_viewer/src/image_info.rs b/crates/image_viewer/src/image_info.rs index 6eedb13ed1a150094ae4882718f2384b06cfe6a7..c819970051b3d4b42bb45c92f78d43083798536f 100644 --- a/crates/image_viewer/src/image_info.rs +++ b/crates/image_viewer/src/image_info.rs @@ -1,9 +1,9 @@ -use gpui::{Context, Entity, IntoElement, ParentElement, Render, Subscription, div}; +use gpui::{App, Context, Entity, IntoElement, ParentElement, Render, Subscription, div}; use project::image_store::{ImageFormat, ImageMetadata}; use settings::Settings; use ui::prelude::*; use util::size::format_file_size; -use workspace::{ItemHandle, StatusItemView, Workspace}; +use workspace::{HideStatusItem, ItemHandle, StatusItemView, Workspace}; use crate::{ImageFileSizeUnit, ImageView, ImageViewerSettings}; @@ -102,4 +102,9 @@ impl StatusItemView for ImageInfo { } cx.notify(); } + + fn hide_setting(&self, _: &App) -> Option { + // The image info is only visible when an image viewer item is active. + None + } } diff --git a/crates/language_selector/src/active_buffer_language.rs b/crates/language_selector/src/active_buffer_language.rs index 1f280282af933094cf46cd9e7ab790efd07b8a12..e9e6dc82ffc096e5965ef14fd41cbf009d283b5c 100644 --- a/crates/language_selector/src/active_buffer_language.rs +++ b/crates/language_selector/src/active_buffer_language.rs @@ -1,12 +1,12 @@ use editor::Editor; use gpui::{ - Context, Entity, IntoElement, ParentElement, Render, Styled, Subscription, WeakEntity, Window, - div, + App, Context, Entity, IntoElement, ParentElement, Render, Styled, Subscription, WeakEntity, + Window, div, }; use language::LanguageName; use settings::Settings as _; use ui::{Button, ButtonCommon, Clickable, FluentBuilder, LabelSize, Tooltip}; -use workspace::{StatusBarSettings, StatusItemView, Workspace, item::ItemHandle}; +use workspace::{HideStatusItem, StatusBarSettings, StatusItemView, Workspace, item::ItemHandle}; use crate::{LanguageSelector, Toggle}; @@ -86,4 +86,13 @@ impl StatusItemView for ActiveBufferLanguage { cx.notify(); } + + fn hide_setting(&self, _: &App) -> Option { + Some(HideStatusItem::new(|settings| { + settings + .status_bar + .get_or_insert_default() + .active_language_button = Some(false); + })) + } } diff --git a/crates/language_tools/src/lsp_button.rs b/crates/language_tools/src/lsp_button.rs index 63529ea0cf300d9b8ce66d7dab4a78e9039c9e06..8b7088dc2285471d736937b75954b84eed30442e 100644 --- a/crates/language_tools/src/lsp_button.rs +++ b/crates/language_tools/src/lsp_button.rs @@ -13,7 +13,7 @@ use language::language_settings::{EditPredictionProvider, all_language_settings} use client::proto; use collections::HashSet; use editor::{Editor, EditorEvent}; -use gpui::{Anchor, Entity, Subscription, Task, TaskExt, WeakEntity, actions}; +use gpui::{Anchor, App, Entity, Subscription, Task, TaskExt, WeakEntity, actions}; use language::{BinaryStatus, BufferId, ServerHealth}; use lsp::{LanguageServerId, LanguageServerName, LanguageServerSelector}; use project::{ @@ -1248,6 +1248,12 @@ impl StatusItemView for LspButton { self.refresh_lsp_menu(false, window, cx); } } + + fn hide_setting(&self, _: &App) -> Option { + Some(workspace::HideStatusItem::new(|settings| { + settings.global_lsp_settings.get_or_insert_default().button = Some(false); + })) + } } impl Render for LspButton { diff --git a/crates/line_ending_selector/src/line_ending_indicator.rs b/crates/line_ending_selector/src/line_ending_indicator.rs index 9c493344e757174035a30e42126389ced9ea1624..419c63d0ac8fdf6ec9e6c95edd81d9dfed63ff7e 100644 --- a/crates/line_ending_selector/src/line_ending_indicator.rs +++ b/crates/line_ending_selector/src/line_ending_indicator.rs @@ -1,8 +1,10 @@ use editor::Editor; -use gpui::{Entity, Subscription, WeakEntity}; +use gpui::{App, Entity, Subscription, WeakEntity}; use language::LineEnding; use ui::{Tooltip, prelude::*}; -use workspace::{StatusBarSettings, StatusItemView, item::ItemHandle, item::Settings}; +use workspace::{ + HideStatusItem, StatusBarSettings, StatusItemView, item::ItemHandle, item::Settings, +}; use crate::{LineEndingSelector, Toggle}; @@ -65,4 +67,13 @@ impl StatusItemView for LineEndingIndicator { } cx.notify(); } + + fn hide_setting(&self, _: &App) -> Option { + Some(HideStatusItem::new(|settings| { + settings + .status_bar + .get_or_insert_default() + .line_endings_button = Some(false); + })) + } } diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 9e179c97a7d60bb780e91d470a5b128e49681c45..7c5bb7bcf62eb26d9e83dbcc4d377cd764c48b00 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -4963,6 +4963,12 @@ impl Panel for OutlinePanel { fn activation_priority(&self) -> u32 { 6 } + + fn hide_button_setting(&self, _: &App) -> Option { + Some(workspace::HideStatusItem::new(|settings| { + settings.outline_panel.get_or_insert_default().button = Some(false); + })) + } } impl Focusable for OutlinePanel { diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 4f9bc801d63ec565bac2e50f2ee2eaf7fefbfaa3..780d8c9274e75436c24ae4e0e1adf61218aa4fe9 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -7291,6 +7291,12 @@ impl Panel for ProjectPanel { fn activation_priority(&self) -> u32 { 1 } + + fn hide_button_setting(&self, _: &App) -> Option { + Some(workspace::HideStatusItem::new(|settings| { + settings.project_panel.get_or_insert_default().button = Some(false); + })) + } } impl ProjectPanel { diff --git a/crates/search/src/search_status_button.rs b/crates/search/src/search_status_button.rs index 5faab32d424df832f55d18059b4485c77eaccdfb..a31121e2f05ad8ab8b0078769f8bde2906dbb92a 100644 --- a/crates/search/src/search_status_button.rs +++ b/crates/search/src/search_status_button.rs @@ -1,8 +1,8 @@ use editor::EditorSettings; -use gpui::FocusHandle; +use gpui::{App, FocusHandle}; use settings::Settings as _; use ui::{ButtonCommon, Clickable, Context, Render, Tooltip, Window, prelude::*}; -use workspace::{ItemHandle, StatusItemView}; +use workspace::{HideStatusItem, ItemHandle, StatusItemView}; pub const SEARCH_ICON: IconName = IconName::MagnifyingGlass; @@ -62,4 +62,10 @@ impl StatusItemView for SearchButton { ) { self.pane_item_focus_handle = active_pane_item.map(|item| item.item_focus_handle(cx)); } + + fn hide_setting(&self, _: &App) -> Option { + Some(HideStatusItem::new(|settings| { + settings.editor.search.get_or_insert_default().button = Some(false); + })) + } } diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 34ec1eddcc8c4b1ac5d876a0140256ca1c71e081..25e4ad9d99979079f2fc76ab24b8035a83ff2497 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -1666,6 +1666,12 @@ impl Panel for TerminalPanel { fn activation_priority(&self) -> u32 { 2 } + + fn hide_button_setting(&self, _: &App) -> Option { + Some(workspace::HideStatusItem::new(|settings| { + settings.terminal.get_or_insert_default().button = Some(false); + })) + } } struct TerminalProvider(Entity); diff --git a/crates/toolchain_selector/src/active_toolchain.rs b/crates/toolchain_selector/src/active_toolchain.rs index a9218564b5567d86f097781b224ac0658a0d5221..72c24df92e04586b083bcc315cf75bb3f3b34ae1 100644 --- a/crates/toolchain_selector/src/active_toolchain.rs +++ b/crates/toolchain_selector/src/active_toolchain.rs @@ -2,14 +2,14 @@ use std::sync::Arc; use editor::Editor; use gpui::{ - AsyncWindowContext, Context, Entity, IntoElement, ParentElement, Render, Styled, Subscription, - Task, WeakEntity, Window, div, + App, AsyncWindowContext, Context, Entity, IntoElement, ParentElement, Render, Styled, + Subscription, Task, WeakEntity, Window, div, }; use language::{Buffer, BufferEvent, LanguageName, Toolchain, ToolchainScope}; use project::{Project, ProjectPath, Toolchains, WorktreeId, toolchain_store::ToolchainStoreEvent}; use ui::{Button, ButtonCommon, Clickable, LabelSize, SharedString, Tooltip}; use util::{maybe, rel_path::RelPath}; -use workspace::{StatusItemView, Workspace, item::ItemHandle}; +use workspace::{HideStatusItem, StatusItemView, Workspace, item::ItemHandle}; use crate::ToolchainSelector; @@ -264,4 +264,10 @@ impl StatusItemView for ActiveToolchain { } cx.notify(); } + + fn hide_setting(&self, _: &App) -> Option { + // The toolchain selector only appears when the active buffer has a + // language with toolchain support. + None + } } diff --git a/crates/vim/src/mode_indicator.rs b/crates/vim/src/mode_indicator.rs index 4660bbfb8290008fb5d65621768af50cde0a6e12..daab005183d153b197251a1b975afbd3f94804ad 100644 --- a/crates/vim/src/mode_indicator.rs +++ b/crates/vim/src/mode_indicator.rs @@ -1,6 +1,8 @@ -use gpui::{Context, Element, Entity, FontWeight, Render, Subscription, WeakEntity, Window, div}; +use gpui::{ + App, Context, Element, Entity, FontWeight, Render, Subscription, WeakEntity, Window, div, +}; use ui::text_for_keystrokes; -use workspace::{StatusItemView, item::ItemHandle, ui::prelude::*}; +use workspace::{HideStatusItem, StatusItemView, item::ItemHandle, ui::prelude::*}; use crate::{Vim, VimEvent, VimGlobals}; @@ -186,4 +188,9 @@ impl StatusItemView for ModeIndicator { _cx: &mut Context, ) { } + + fn hide_setting(&self, _: &App) -> Option { + // The Vim mode indicator is only visible while Vim mode is on. + None + } } diff --git a/crates/workspace/src/active_file_name.rs b/crates/workspace/src/active_file_name.rs index f35312d529423c4dc81bb71dc585c99169afdd39..68d57c2983be17688de955c125f98030a8b85886 100644 --- a/crates/workspace/src/active_file_name.rs +++ b/crates/workspace/src/active_file_name.rs @@ -1,11 +1,13 @@ use gpui::{ - Context, Empty, EventEmitter, IntoElement, ParentElement, Render, SharedString, Window, + App, Context, Empty, EventEmitter, IntoElement, ParentElement, Render, SharedString, Window, }; use settings::Settings; use ui::{Button, Tooltip, prelude::*}; use util::paths::PathStyle; -use crate::{StatusItemView, item::ItemHandle, workspace_settings::StatusBarSettings}; +use crate::{ + HideStatusItem, StatusItemView, item::ItemHandle, workspace_settings::StatusBarSettings, +}; pub struct ActiveFileName { project_path: Option, @@ -66,4 +68,10 @@ impl StatusItemView for ActiveFileName { } cx.notify(); } + + fn hide_setting(&self, _: &App) -> Option { + Some(HideStatusItem::new(|settings| { + settings.status_bar.get_or_insert_default().show_active_file = Some(false); + })) + } } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 461726757d73f8ab37eb2e469a91d0ee4f3c6a4b..62655a90639629eb25c163ab17b4532ac2ed98b0 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,5 +1,6 @@ use crate::focus_follows_mouse::FocusFollowsMouse as _; use crate::persistence::model::DockData; +use crate::status_bar::HideStatusItem; use crate::{DraggedDock, Event, FocusFollowsMouse, ModalLayer, Pane, WorkspaceSettings}; use crate::{Workspace, status_bar::StatusItemView}; use anyhow::Context as _; @@ -86,6 +87,12 @@ pub trait Panel: Focusable + EventEmitter + Render + Sized { fn is_agent_panel(&self) -> bool { false } + /// Returns metadata describing how to hide this panel's button from the + /// status bar by writing to user settings. Implementors should return + /// `None` if the panel button cannot be hidden through settings. + fn hide_button_setting(&self, _: &App) -> Option { + None + } } pub trait PanelHandle: Send + Sync { @@ -116,6 +123,7 @@ pub trait PanelHandle: Send + Sync { fn activation_priority(&self, cx: &App) -> u32; fn enabled(&self, cx: &App) -> bool; fn is_agent_panel(&self, cx: &App) -> bool; + fn hide_button_setting(&self, cx: &App) -> Option; fn move_to_next_position(&self, window: &mut Window, cx: &mut App) { let current_position = self.position(window, cx); let next_position = [ @@ -244,6 +252,10 @@ where fn is_agent_panel(&self, cx: &App) -> bool { self.read(cx).is_agent_panel() } + + fn hide_button_setting(&self, cx: &App) -> Option { + self.read(cx).hide_button_setting(cx) + } } impl From<&dyn PanelHandle> for AnyView { @@ -1251,6 +1263,7 @@ impl Render for PanelButtons { DockPosition::Bottom, ]; + let panel_hide = panel.hide_button_setting(cx); ContextMenu::build(window, cx, |mut menu, _, cx| { let mut has_position_entries = false; for position in POSITIONS { @@ -1322,6 +1335,12 @@ impl Render for PanelButtons { }, ); } + if let Some(hide) = panel_hide { + menu = crate::status_bar::add_hide_button_entry( + menu.separator(), + hide, + ); + } menu }) }) @@ -1388,6 +1407,12 @@ impl StatusItemView for PanelButtons { ) { // Nothing to do, panel buttons don't depend on the active center item } + + fn hide_setting(&self, _: &App) -> Option { + // Panel buttons are hidden on a per-panel basis through each panel + // button's own context menu. + None + } } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/workspace/src/status_bar.rs b/crates/workspace/src/status_bar.rs index fbaa1b50a0a512843ac263a53a47e54d0a985725..ae34936047d06aa3182cac68a05e2738092e9c39 100644 --- a/crates/workspace/src/status_bar.rs +++ b/crates/workspace/src/status_bar.rs @@ -3,12 +3,41 @@ use crate::{ sidebar_side_context_menu, }; use gpui::{ - Anchor, AnyView, App, Context, Decorations, Entity, IntoElement, ParentElement, Render, Styled, - Subscription, WeakEntity, Window, + Anchor, AnyView, App, Context, Decorations, Entity, IntoElement, ParentElement, Render, + SharedString, Styled, Subscription, WeakEntity, Window, }; -use std::any::TypeId; +use settings::{SettingsContent, update_settings_file}; +use std::{any::TypeId, sync::Arc}; use theme::CLIENT_SIDE_DECORATION_ROUNDING; -use ui::{Divider, Indicator, Tooltip, prelude::*}; +use ui::{ContextMenu, Divider, IconPosition, Indicator, Tooltip, prelude::*, right_click_menu}; + +/// Describes how a status-bar item can be hidden by the user. +/// +/// Every [`StatusItemView`] must either provide this (so that the user gets a +/// "Hide Button" entry in the right-click menu) or explicitly return `None` +/// to opt out. Returning `None` should be reserved for items that are +/// already conditional on some other setting exposed elsewhere (e.g., the +/// activity indicator, which disappears on its own once there's no work to +/// display). +#[derive(Clone)] +pub struct HideStatusItem { + hide: Arc, +} + +impl HideStatusItem { + pub fn new(hide: impl Fn(&mut SettingsContent) + Send + Sync + 'static) -> Self { + Self { + hide: Arc::new(hide), + } + } + + /// Persists the hide by updating the user settings file. + pub fn apply(&self, cx: &App) { + let hide = self.hide.clone(); + let fs = ::global(cx); + update_settings_file(fs, cx, move |settings, _cx| (hide)(settings)); + } +} pub trait StatusItemView: Render { /// Event callback that is triggered when the active pane item changes. @@ -18,6 +47,15 @@ pub trait StatusItemView: Render { window: &mut Window, cx: &mut Context, ); + + /// Returns metadata describing how this item can be hidden from the + /// status bar by writing to the user settings file. + /// + /// Implementors that return `None` must be inherently conditional on + /// another user-exposed setting; otherwise, they should return `Some` so + /// that the status bar can show a "Hide Button" entry in its + /// right-click menu. + fn hide_setting(&self, cx: &App) -> Option; } trait StatusItemViewHandle: Send { @@ -29,6 +67,7 @@ trait StatusItemViewHandle: Send { cx: &mut App, ); fn item_type(&self) -> TypeId; + fn hide_setting(&self, cx: &App) -> Option; } #[derive(Default)] @@ -124,7 +163,9 @@ impl StatusBar { sidebar.show_toggle && !sidebar.open && sidebar.side == SidebarSide::Left, |this| this.child(self.render_sidebar_toggle(sidebar, cx)), ) - .children(self.left_items.iter().map(|item| item.to_any())) + .children(self.left_items.iter().enumerate().map(|(index, item)| { + render_hideable_item("status-bar-left", index, item.as_ref(), cx) + })) } fn render_right_tools( @@ -136,7 +177,15 @@ impl StatusBar { .flex_shrink_0() .gap_1() .overflow_x_hidden() - .children(self.right_items.iter().rev().map(|item| item.to_any())) + .children( + self.right_items + .iter() + .enumerate() + .rev() + .map(|(index, item)| { + render_hideable_item("status-bar-right", index, item.as_ref(), cx) + }), + ) .when( sidebar.show_toggle && !sidebar.open && sidebar.side == SidebarSide::Right, |this| this.child(self.render_sidebar_toggle(sidebar, cx)), @@ -201,6 +250,40 @@ impl StatusBar { } } +fn render_hideable_item( + side: &'static str, + index: usize, + item: &dyn StatusItemViewHandle, + cx: &App, +) -> impl IntoElement { + let view = item.to_any(); + let Some(hide) = item.hide_setting(cx) else { + return view.into_any_element(); + }; + + let menu_id: SharedString = format!("{side}-item-menu-{index}").into(); + right_click_menu(menu_id) + .trigger(move |_is_active, _window, _cx| view) + .menu(move |window, cx| { + let hide = hide.clone(); + ContextMenu::build(window, cx, move |menu, _window, _cx| { + add_hide_button_entry(menu, hide) + }) + }) + .into_any_element() +} + +/// Appends a "Hide Button" entry aligned with surrounding toggleable entries. +pub fn add_hide_button_entry(menu: ContextMenu, hide: HideStatusItem) -> ContextMenu { + menu.toggleable_entry( + "Hide Button", + false, + IconPosition::Start, + None, + move |_window, cx| hide.apply(cx), + ) +} + impl StatusBar { pub fn new( active_pane: &Entity, @@ -350,6 +433,10 @@ impl StatusItemViewHandle for Entity { fn item_type(&self) -> TypeId { TypeId::of::() } + + fn hide_setting(&self, cx: &App) -> Option { + self.read(cx).hide_setting(cx) + } } impl From<&dyn StatusItemViewHandle> for AnyView { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 79b7e28ffb34ff8279b207b160c8664986c076e6..267420cc15f98021e7e9f962f2ec920a45d7c19c 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -118,7 +118,7 @@ use sqlez::{ statement::Statement, }; use status_bar::StatusBar; -pub use status_bar::StatusItemView; +pub use status_bar::{HideStatusItem, StatusItemView, add_hide_button_entry}; use std::{ any::TypeId, borrow::Cow, @@ -152,8 +152,8 @@ use util::{ }; use uuid::Uuid; pub use workspace_settings::{ - AutosaveSetting, BottomDockLayout, FocusFollowsMouse, RestoreOnStartupBehavior, - StatusBarSettings, TabBarSettings, WorkspaceSettings, + AutosaveSetting, BottomDockLayout, EncodingDisplayOptions, FocusFollowsMouse, + RestoreOnStartupBehavior, StatusBarSettings, TabBarSettings, WorkspaceSettings, }; use zed_actions::{Spawn, feedback::FileBugReport, theme::ToggleMode};