breadcrumbs: Stylize filename in breadcrumbs when tab-bar is off and file is dirty (#30507)

Vivek Pothina created

Closes [#18870](https://github.com/zed-industries/zed/issues/18870)


- I like to use Zed with tab_bar off
- when the file is modified there is no indicator when tab_bar is off
- this PR aims to fix that

Thanks to @Qkessler for initial PR - #22418 

This is style decided in this discussion - #22418 
@iamnbutler @mikayla-maki [subtle style
decided](https://github.com/zed-industries/zed/pull/22418#issuecomment-2605253667)

Release Notes:
- When tab_bar is off, filename in the breadcrumbs will be the indicator
when file is unsaved.


#### Changes
- when tab_bar is off and file is dirty (unsaved)
<img width="834" alt="image"
src="https://github.com/user-attachments/assets/f205731b-c8e3-4d7a-9214-cbe706e372bf"
/>


- when tab_bar is off and file is not dirty (saved)
<img width="846" alt="image"
src="https://github.com/user-attachments/assets/88ea96eb-16a2-48e8-900d-64a921f0b5c3"
/>


- when tab_bar is on
<img width="741" alt="image"
src="https://github.com/user-attachments/assets/cc543544-9949-46ed-8e09-cdcbe2f47ab8"
/>

<img width="740" alt="image"
src="https://github.com/user-attachments/assets/8d347258-26f7-4bd7-82d4-8f23dbe63d61"
/>

Release Notes:

- Changed the highlighting of the current file to represent the current
saved state, when the tab bar is turned off.

Change summary

Cargo.lock                            |  1 
crates/breadcrumbs/Cargo.toml         |  1 
crates/breadcrumbs/src/breadcrumbs.rs | 65 +++++++++++++++++++++++++---
3 files changed, 60 insertions(+), 7 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -2200,6 +2200,7 @@ dependencies = [
  "editor",
  "gpui",
  "itertools 0.14.0",
+ "settings",
  "theme",
  "ui",
  "workspace",

crates/breadcrumbs/Cargo.toml 🔗

@@ -16,6 +16,7 @@ doctest = false
 editor.workspace = true
 gpui.workspace = true
 itertools.workspace = true
+settings.workspace = true
 theme.workspace = true
 ui.workspace = true
 workspace.workspace = true

crates/breadcrumbs/src/breadcrumbs.rs 🔗

@@ -1,14 +1,15 @@
 use editor::Editor;
 use gpui::{
-    Context, Element, EventEmitter, Focusable, IntoElement, ParentElement, Render, StyledText,
-    Subscription, Window,
+    Context, Element, EventEmitter, Focusable, FontWeight, IntoElement, ParentElement, Render,
+    StyledText, Subscription, Window,
 };
 use itertools::Itertools;
+use settings::Settings;
 use std::cmp;
 use theme::ActiveTheme;
 use ui::{ButtonLike, ButtonStyle, Label, Tooltip, prelude::*};
 use workspace::{
-    ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
+    TabBarSettings, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
     item::{BreadcrumbText, ItemEvent, ItemHandle},
 };
 
@@ -71,16 +72,23 @@ impl Render for Breadcrumbs {
             );
         }
 
-        let highlighted_segments = segments.into_iter().map(|segment| {
+        let highlighted_segments = segments.into_iter().enumerate().map(|(index, segment)| {
             let mut text_style = window.text_style();
-            if let Some(font) = segment.font {
-                text_style.font_family = font.family;
-                text_style.font_features = font.features;
+            if let Some(ref font) = segment.font {
+                text_style.font_family = font.family.clone();
+                text_style.font_features = font.features.clone();
                 text_style.font_style = font.style;
                 text_style.font_weight = font.weight;
             }
             text_style.color = Color::Muted.color(cx);
 
+            if index == 0 && !TabBarSettings::get_global(cx).show && active_item.is_dirty(cx) {
+                if let Some(styled_element) = apply_dirty_filename_style(&segment, &text_style, cx)
+                {
+                    return styled_element;
+                }
+            }
+
             StyledText::new(segment.text.replace('\n', "⏎"))
                 .with_default_highlights(&text_style, segment.highlights.unwrap_or_default())
                 .into_any()
@@ -184,3 +192,46 @@ impl ToolbarItemView for Breadcrumbs {
         self.pane_focused = pane_focused;
     }
 }
+
+fn apply_dirty_filename_style(
+    segment: &BreadcrumbText,
+    text_style: &gpui::TextStyle,
+    cx: &mut Context<Breadcrumbs>,
+) -> Option<gpui::AnyElement> {
+    let text = segment.text.replace('\n', "⏎");
+
+    let filename_position = std::path::Path::new(&segment.text)
+        .file_name()
+        .and_then(|f| {
+            let filename_str = f.to_string_lossy();
+            segment.text.rfind(filename_str.as_ref())
+        })?;
+
+    let bold_weight = FontWeight::BOLD;
+    let default_color = Color::Default.color(cx);
+
+    if filename_position == 0 {
+        let mut filename_style = text_style.clone();
+        filename_style.font_weight = bold_weight;
+        filename_style.color = default_color;
+
+        return Some(
+            StyledText::new(text)
+                .with_default_highlights(&filename_style, [])
+                .into_any(),
+        );
+    }
+
+    let highlight_style = gpui::HighlightStyle {
+        font_weight: Some(bold_weight),
+        color: Some(default_color),
+        ..Default::default()
+    };
+
+    let highlight = vec![(filename_position..text.len(), highlight_style)];
+    Some(
+        StyledText::new(text)
+            .with_default_highlights(&text_style, highlight)
+            .into_any(),
+    )
+}