workspace: Show file path in bottom bar (#52381)

Cameron Mcloughlin created

Context: if the toolbar and tab bar are both disabled, the current
filename is not visible. This adds it to the bottom bar, similar to vim.
Behind a setting, disabled by default

Release Notes:

- N/A or Added/Fixed/Improved ...

Change summary

assets/settings/default.json               |  2 
crates/settings/src/vscode_import.rs       |  1 
crates/settings_content/src/workspace.rs   |  4 +
crates/settings_ui/src/page_data.rs        | 24 ++++++++
crates/workspace/src/active_file_name.rs   | 69 ++++++++++++++++++++++++
crates/workspace/src/workspace.rs          |  1 
crates/workspace/src/workspace_settings.rs |  2 
crates/zed/src/zed.rs                      |  2 
8 files changed, 104 insertions(+), 1 deletion(-)

Detailed changes

assets/settings/default.json 🔗

@@ -1617,6 +1617,8 @@
   "status_bar": {
     // Whether to show the status bar.
     "experimental.show": true,
+    // Whether to show the name of the active file in the status bar.
+    "show_active_file": false,
     // 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/settings/src/vscode_import.rs 🔗

@@ -769,6 +769,7 @@ impl VsCodeSettings {
     fn status_bar_settings_content(&self) -> Option<StatusBarSettingsContent> {
         skip_default(StatusBarSettingsContent {
             show: self.read_bool("workbench.statusBar.visible"),
+            show_active_file: None,
             active_language_button: None,
             cursor_position_button: None,
             line_endings_button: None,

crates/settings_content/src/workspace.rs 🔗

@@ -434,6 +434,10 @@ pub struct StatusBarSettingsContent {
     /// Default: true
     #[serde(rename = "experimental.show")]
     pub show: Option<bool>,
+    /// Whether to show the name of the active file in the status bar.
+    ///
+    /// Default: false
+    pub show_active_file: Option<bool>,
     /// Whether to display the active language button in the status bar.
     ///
     /// Default: true

crates/settings_ui/src/page_data.rs 🔗

@@ -3327,7 +3327,7 @@ fn search_and_files_page() -> SettingsPage {
 }
 
 fn window_and_layout_page() -> SettingsPage {
-    fn status_bar_section() -> [SettingsPageItem; 9] {
+    fn status_bar_section() -> [SettingsPageItem; 10] {
         [
             SettingsPageItem::SectionHeader("Status Bar"),
             SettingsPageItem::SettingItem(SettingItem {
@@ -3472,6 +3472,28 @@ fn window_and_layout_page() -> SettingsPage {
                 metadata: None,
                 files: USER,
             }),
+            SettingsPageItem::SettingItem(SettingItem {
+                title: "Active File Name",
+                description: "Show the name of the active file in the status bar.",
+                field: Box::new(SettingField {
+                    json_path: Some("status_bar.show_active_file"),
+                    pick: |settings_content| {
+                        settings_content
+                            .status_bar
+                            .as_ref()?
+                            .show_active_file
+                            .as_ref()
+                    },
+                    write: |settings_content, value| {
+                        settings_content
+                            .status_bar
+                            .get_or_insert_default()
+                            .show_active_file = value;
+                    },
+                }),
+                metadata: None,
+                files: USER,
+            }),
         ]
     }
 

crates/workspace/src/active_file_name.rs 🔗

@@ -0,0 +1,69 @@
+use gpui::{
+    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};
+
+pub struct ActiveFileName {
+    project_path: Option<SharedString>,
+    full_path: Option<SharedString>,
+}
+
+impl ActiveFileName {
+    pub fn new() -> Self {
+        Self {
+            project_path: None,
+            full_path: None,
+        }
+    }
+}
+
+impl Render for ActiveFileName {
+    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        if !StatusBarSettings::get_global(cx).show_active_file {
+            return Empty.into_any_element();
+        }
+
+        let Some(project_path) = self.project_path.clone() else {
+            return Empty.into_any_element();
+        };
+
+        let tooltip_text = self
+            .full_path
+            .clone()
+            .unwrap_or_else(|| project_path.clone());
+
+        div()
+            .child(
+                Button::new("active-file-name-button", project_path)
+                    .label_size(LabelSize::Small)
+                    .tooltip(Tooltip::text(tooltip_text)),
+            )
+            .into_any_element()
+    }
+}
+
+impl EventEmitter<crate::ToolbarItemEvent> for ActiveFileName {}
+
+impl StatusItemView for ActiveFileName {
+    fn set_active_pane_item(
+        &mut self,
+        active_pane_item: Option<&dyn ItemHandle>,
+        _window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if let Some(item) = active_pane_item {
+            self.project_path = item
+                .project_path(cx)
+                .map(|path| path.path.display(PathStyle::local()).into_owned().into());
+            self.full_path = item.tab_tooltip_text(cx);
+        } else {
+            self.project_path = None;
+            self.full_path = None;
+        }
+        cx.notify();
+    }
+}

crates/workspace/src/workspace_settings.rs 🔗

@@ -132,6 +132,7 @@ impl Settings for TabBarSettings {
 #[derive(Deserialize, RegisterSetting)]
 pub struct StatusBarSettings {
     pub show: bool,
+    pub show_active_file: bool,
     pub active_language_button: bool,
     pub cursor_position_button: bool,
     pub line_endings_button: bool,
@@ -143,6 +144,7 @@ impl Settings for StatusBarSettings {
         let status_bar = content.status_bar.clone().unwrap();
         StatusBarSettings {
             show: status_bar.show.unwrap(),
+            show_active_file: status_bar.show_active_file.unwrap(),
             active_language_button: status_bar.active_language_button.unwrap(),
             cursor_position_button: status_bar.cursor_position_button.unwrap(),
             line_endings_button: status_bar.line_endings_button.unwrap(),

crates/zed/src/zed.rs 🔗

@@ -478,6 +478,7 @@ pub fn initialize_workspace(
         let search_button = cx.new(|_| search::search_status_button::SearchButton::new());
         let diagnostic_summary =
             cx.new(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
+        let active_file_name = cx.new(|_| workspace::active_file_name::ActiveFileName::new());
         let activity_indicator = activity_indicator::ActivityIndicator::new(
             workspace,
             workspace.project().read(cx).languages().clone(),
@@ -510,6 +511,7 @@ pub fn initialize_workspace(
             status_bar.add_left_item(search_button, window, cx);
             status_bar.add_left_item(lsp_button, window, cx);
             status_bar.add_left_item(diagnostic_summary, window, cx);
+            status_bar.add_left_item(active_file_name, window, cx);
             status_bar.add_left_item(activity_indicator, window, cx);
             status_bar.add_right_item(edit_prediction_ui, window, cx);
             status_bar.add_right_item(active_buffer_encoding, window, cx);