Add ability to hide status bar (#39430)

Mansoor Ahmed and Conrad Irwin created

This pull request adds the ability to configure the setting to hide or
show the status bar, as described in discussion:
https://github.com/zed-industries/zed/discussions/38591

The original [PR
#38974](https://github.com/zed-industries/zed/pull/38974#issuecomment-3362020879)
was merged but reverted due to hidden conflicts. As per @ConradIrwin 's
[request](https://github.com/zed-industries/zed/pull/38974#issuecomment-3362020879),
I am recreating the PR on top of updated main branch.

Release Notes:

- Added an experimental setting `"status_bar": { "experimental.show":
false}` to hide the status bars.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>

Change summary

assets/settings/default.json                           |  2 
crates/editor/src/editor_settings.rs                   | 18 ----
crates/go_to_line/src/cursor_position.rs               |  9 -
crates/language_selector/src/active_buffer_language.rs |  9 -
crates/settings/src/settings_content.rs                |  1 
crates/settings/src/settings_content/editor.rs         | 16 ---
crates/settings/src/settings_content/workspace.rs      | 18 ++++
crates/settings_ui/src/settings_ui.rs                  |  6 
crates/workspace/src/workspace.rs                      | 52 +++++++++++
crates/workspace/src/workspace_settings.rs             | 27 ++++++
docs/src/configuring-zed.md                            |  8 +
11 files changed, 114 insertions(+), 52 deletions(-)

Detailed changes

assets/settings/default.json 🔗

@@ -1329,6 +1329,8 @@
   },
   // Status bar-related settings.
   "status_bar": {
+    // Whether to show the status bar.
+    "experimental.show": true,
     // Whether to show the active language button in the status bar.
     "active_language_button": true,
     // Whether to show the cursor position button in the status bar.

crates/editor/src/editor_settings.rs 🔗

@@ -25,7 +25,6 @@ pub struct EditorSettings {
     pub lsp_highlight_debounce: u64,
     pub hover_popover_enabled: bool,
     pub hover_popover_delay: u64,
-    pub status_bar: StatusBar,
     pub toolbar: Toolbar,
     pub scrollbar: Scrollbar,
     pub minimap: Minimap,
@@ -67,18 +66,6 @@ pub struct Jupyter {
     pub enabled: bool,
 }
 
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct StatusBar {
-    /// Whether to display the active language button in the status bar.
-    ///
-    /// Default: true
-    pub active_language_button: bool,
-    /// Whether to show the cursor position button in the status bar.
-    ///
-    /// Default: true
-    pub cursor_position_button: bool,
-}
-
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct Toolbar {
     pub breadcrumbs: bool,
@@ -195,7 +182,6 @@ impl Settings for EditorSettings {
         let minimap = editor.minimap.unwrap();
         let gutter = editor.gutter.unwrap();
         let axes = scrollbar.axes.unwrap();
-        let status_bar = editor.status_bar.unwrap();
         let toolbar = editor.toolbar.unwrap();
         let search = editor.search.unwrap();
         let drag_and_drop_selection = editor.drag_and_drop_selection.unwrap();
@@ -208,10 +194,6 @@ impl Settings for EditorSettings {
             lsp_highlight_debounce: editor.lsp_highlight_debounce.unwrap(),
             hover_popover_enabled: editor.hover_popover_enabled.unwrap(),
             hover_popover_delay: editor.hover_popover_delay.unwrap(),
-            status_bar: StatusBar {
-                active_language_button: status_bar.active_language_button.unwrap(),
-                cursor_position_button: status_bar.cursor_position_button.unwrap(),
-            },
             toolbar: Toolbar {
                 breadcrumbs: toolbar.breadcrumbs.unwrap(),
                 quick_actions: toolbar.quick_actions.unwrap(),

crates/go_to_line/src/cursor_position.rs 🔗

@@ -1,4 +1,4 @@
-use editor::{Editor, EditorSettings, MultiBufferSnapshot};
+use editor::{Editor, MultiBufferSnapshot};
 use gpui::{App, Entity, FocusHandle, Focusable, Subscription, Task, WeakEntity};
 use settings::Settings;
 use std::{fmt::Write, num::NonZeroU32, time::Duration};
@@ -8,7 +8,7 @@ use ui::{
     Render, Tooltip, Window, div,
 };
 use util::paths::FILE_ROW_COLUMN_DELIMITER;
-use workspace::{StatusItemView, Workspace, item::ItemHandle};
+use workspace::{StatusBarSettings, StatusItemView, Workspace, item::ItemHandle};
 
 #[derive(Copy, Clone, Debug, Default, PartialOrd, PartialEq)]
 pub(crate) struct SelectionStats {
@@ -205,10 +205,7 @@ impl CursorPosition {
 
 impl Render for CursorPosition {
     fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        if !EditorSettings::get_global(cx)
-            .status_bar
-            .cursor_position_button
-        {
+        if !StatusBarSettings::get_global(cx).cursor_position_button {
             return div();
         }
 

crates/language_selector/src/active_buffer_language.rs 🔗

@@ -1,11 +1,11 @@
-use editor::{Editor, EditorSettings};
+use editor::Editor;
 use gpui::{
     Context, Entity, IntoElement, ParentElement, Render, Subscription, WeakEntity, Window, div,
 };
 use language::LanguageName;
 use settings::Settings as _;
 use ui::{Button, ButtonCommon, Clickable, FluentBuilder, LabelSize, Tooltip};
-use workspace::{StatusItemView, Workspace, item::ItemHandle};
+use workspace::{StatusBarSettings, StatusItemView, Workspace, item::ItemHandle};
 
 use crate::{LanguageSelector, Toggle};
 
@@ -40,10 +40,7 @@ impl ActiveBufferLanguage {
 
 impl Render for ActiveBufferLanguage {
     fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        if !EditorSettings::get_global(cx)
-            .status_bar
-            .active_language_button
-        {
+        if !StatusBarSettings::get_global(cx).active_language_button {
             return div();
         }
 

crates/settings/src/settings_content.rs 🔗

@@ -60,6 +60,7 @@ pub struct SettingsContent {
 
     pub tabs: Option<ItemSettingsContent>,
     pub tab_bar: Option<TabBarSettingsContent>,
+    pub status_bar: Option<StatusBarSettingsContent>,
 
     pub preview_tabs: Option<PreviewTabsSettingsContent>,
 

crates/settings/src/settings_content/editor.rs 🔗

@@ -54,8 +54,6 @@ pub struct EditorSettingsContent {
     ///
     /// Default: 300
     pub hover_popover_delay: Option<u64>,
-    /// Status bar related settings
-    pub status_bar: Option<StatusBarContent>,
     /// Toolbar related settings
     pub toolbar: Option<ToolbarContent>,
     /// Scrollbar related settings
@@ -193,20 +191,6 @@ pub struct EditorSettingsContent {
     pub lsp_document_colors: Option<DocumentColorsRenderMode>,
 }
 
-// Status bar related settings
-#[skip_serializing_none]
-#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)]
-pub struct StatusBarContent {
-    /// Whether to display the active language button in the status bar.
-    ///
-    /// Default: true
-    pub active_language_button: Option<bool>,
-    /// Whether to show the cursor position button in the status bar.
-    ///
-    /// Default: true
-    pub cursor_position_button: Option<bool>,
-}
-
 // Toolbar related settings
 #[skip_serializing_none]
 #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)]

crates/settings/src/settings_content/workspace.rs 🔗

@@ -361,6 +361,24 @@ pub struct TabBarSettingsContent {
     pub show_tab_bar_buttons: Option<bool>,
 }
 
+#[skip_serializing_none]
+#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq, Eq)]
+pub struct StatusBarSettingsContent {
+    /// Whether to show the status bar.
+    ///
+    /// Default: true
+    #[serde(rename = "experimental.show", default)]
+    pub show: Option<bool>,
+    /// Whether to display the active language button in the status bar.
+    ///
+    /// Default: true
+    pub active_language_button: Option<bool>,
+    /// Whether to show the cursor position button in the status bar.
+    ///
+    /// Default: true
+    pub cursor_position_button: Option<bool>,
+}
+
 #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
 #[serde(rename_all = "snake_case")]
 pub enum AutosaveSetting {

crates/settings_ui/src/settings_ui.rs 🔗

@@ -1418,7 +1418,7 @@ fn user_settings_data() -> Vec<SettingsPage> {
                     description: "Whether to show the active language button in the status bar",
                     field: Box::new(SettingField {
                         pick: |settings_content| {
-                            if let Some(status_bar) = &settings_content.editor.status_bar {
+                            if let Some(status_bar) = &settings_content.status_bar {
                                 &status_bar.active_language_button
                             } else {
                                 &None
@@ -1426,7 +1426,6 @@ fn user_settings_data() -> Vec<SettingsPage> {
                         },
                         pick_mut: |settings_content| {
                             &mut settings_content
-                                .editor
                                 .status_bar
                                 .get_or_insert_default()
                                 .active_language_button
@@ -1439,7 +1438,7 @@ fn user_settings_data() -> Vec<SettingsPage> {
                     description: "Whether to show the cursor position button in the status bar",
                     field: Box::new(SettingField {
                         pick: |settings_content| {
-                            if let Some(status_bar) = &settings_content.editor.status_bar {
+                            if let Some(status_bar) = &settings_content.status_bar {
                                 &status_bar.cursor_position_button
                             } else {
                                 &None
@@ -1447,7 +1446,6 @@ fn user_settings_data() -> Vec<SettingsPage> {
                         },
                         pick_mut: |settings_content| {
                             &mut settings_content
-                                .editor
                                 .status_bar
                                 .get_or_insert_default()
                                 .cursor_position_button

crates/workspace/src/workspace.rs 🔗

@@ -112,7 +112,8 @@ use util::{
 };
 use uuid::Uuid;
 pub use workspace_settings::{
-    AutosaveSetting, BottomDockLayout, RestoreOnStartupBehavior, TabBarSettings, WorkspaceSettings,
+    AutosaveSetting, BottomDockLayout, RestoreOnStartupBehavior, StatusBarSettings, TabBarSettings,
+    WorkspaceSettings,
 };
 use zed_actions::{Spawn, feedback::FileBugReport};
 
@@ -506,6 +507,7 @@ pub fn init_settings(cx: &mut App) {
     ItemSettings::register(cx);
     PreviewTabsSettings::register(cx);
     TabBarSettings::register(cx);
+    StatusBarSettings::register(cx);
 }
 
 fn prompt_and_open_paths(app_state: Arc<AppState>, options: PathPromptOptions, cx: &mut App) {
@@ -1747,6 +1749,10 @@ impl Workspace {
         &self.status_bar
     }
 
+    pub fn status_bar_visible(&self, cx: &App) -> bool {
+        StatusBarSettings::get_global(cx).show
+    }
+
     pub fn app_state(&self) -> &Arc<AppState> {
         &self.app_state
     }
@@ -6729,7 +6735,9 @@ impl Render for Workspace {
                                 }))
                                 .children(self.render_notifications(window, cx)),
                         )
-                        .child(self.status_bar.clone())
+                        .when(self.status_bar_visible(cx), |parent| {
+                            parent.child(self.status_bar.clone())
+                        })
                         .child(self.modal_layer.clone())
                         .child(self.toast_layer.clone()),
                 ),
@@ -10772,6 +10780,46 @@ mod tests {
         }
     }
 
+    #[gpui::test]
+    async fn test_status_bar_visibility(cx: &mut 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.clone(), window, cx));
+
+        // Test with status bar shown (default)
+        workspace.read_with(cx, |workspace, cx| {
+            let visible = workspace.status_bar_visible(cx);
+            assert!(visible, "Status bar should be visible by default");
+        });
+
+        // Test with status bar hidden
+        cx.update_global(|store: &mut SettingsStore, cx| {
+            store.update_user_settings(cx, |settings| {
+                settings.status_bar.get_or_insert_default().show = Some(false);
+            });
+        });
+
+        workspace.read_with(cx, |workspace, cx| {
+            let visible = workspace.status_bar_visible(cx);
+            assert!(!visible, "Status bar should be hidden when show is false");
+        });
+
+        // Test with status bar shown explicitly
+        cx.update_global(|store: &mut SettingsStore, cx| {
+            store.update_user_settings(cx, |settings| {
+                settings.status_bar.get_or_insert_default().show = Some(true);
+            });
+        });
+
+        workspace.read_with(cx, |workspace, cx| {
+            let visible = workspace.status_bar_visible(cx);
+            assert!(visible, "Status bar should be visible when show is true");
+        });
+    }
+
     fn pane_items_paths(pane: &Entity<Pane>, cx: &App) -> Vec<String> {
         pane.read(cx)
             .items()

crates/workspace/src/workspace_settings.rs 🔗

@@ -222,3 +222,30 @@ impl Settings for TabBarSettings {
         }
     }
 }
+
+#[derive(Deserialize)]
+pub struct StatusBarSettings {
+    pub show: bool,
+    pub active_language_button: bool,
+    pub cursor_position_button: bool,
+}
+
+impl Settings for StatusBarSettings {
+    fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
+        let status_bar = content.status_bar.clone().unwrap();
+        StatusBarSettings {
+            show: status_bar.show.unwrap(),
+            active_language_button: status_bar.active_language_button.unwrap(),
+            cursor_position_button: status_bar.cursor_position_button.unwrap(),
+        }
+    }
+
+    fn import_from_vscode(
+        vscode: &settings::VsCodeSettings,
+        current: &mut settings::SettingsContent,
+    ) {
+        if let Some(show) = vscode.read_bool("workbench.statusBar.visible") {
+            current.status_bar.get_or_insert_default().show = Some(show);
+        }
+    }
+}

docs/src/configuring-zed.md 🔗

@@ -1502,6 +1502,14 @@ Positive `integer` value between 1 and 32. Values outside of this range will be
 },
 ```
 
+There is an experimental setting that completely hides the status bar. This causes major usability problems (you will be unable to use many of Zed's features), but is provided for those who value screen real-estate above all else.
+
+```json
+"status_bar": {
+  "experimental.show": false
+}
+```
+
 ## LSP
 
 - Description: Configuration for language servers.