Files
zed/crates/git_ui/src/git_panel_settings.rs
T
ISHIMWE VainqueurandDanilo Leal ae445634e0 git_ui: Show uncommitted change count badge on git panel icon (#49624)
## Summary

- Implements `icon_label` on `GitPanel` to return the total count of
uncommitted changes (`new_count + changes_count`) when non-zero, capped
at `"99+"` for large repos.
- Updates `PanelButtons::render()` to render that label as a small green
badge overlaid on the panel's sidebar icon, using absolute positioning
within a `div().relative()` wrapper.
- The badge uses `version_control_added` theme color and
`LabelSize::XSmall` text with `LineHeightStyle::UiLabel` for accurate
vertical centering, positioned at the top-right corner of the icon
button.

The `icon_label` method already existed on the `Panel`/`PanelHandle`
traits with a default `None` impl, and was already implemented by
`NotificationPanel` (unread notification count) and `TerminalPanel`
(open terminal count) — but was never rendered. This wires it up for all
three panels at once.

## Notes

- Badge is positioned with non-negative offsets (`top(0)`, `right(0)`)
to stay within the parent container's bounds. The status bar's
`render_left_tools()` uses `.overflow_x_hidden()`, which in GPUI clips
both axes (the `overflow_mask` returns a full content mask whenever any
axis is non-`Visible`), so negative offsets would be clipped.
- `LineHeightStyle::UiLabel` collapses line height to `relative(1.)` so
flex centering aligns the visual glyph rather than a
taller-than-necessary line box.
- No new data tracking logic — `GitPanel` already maintains `new_count`
and `changes_count` reactively.
- No feature flag or settings added per YAGNI.

## Suggested .rules additions

The following pattern came up repeatedly and would prevent future
sessions from hitting the same issue:

```
## GPUI overflow clipping

`overflow_x_hidden()` (and any single-axis overflow setter) clips **both** axes in GPUI.
The `overflow_mask()` implementation in `style.rs` returns a full `ContentMask` (bounding box)
whenever any axis is non-`Visible`. Absolute-positioned children that extend outside the element
bounds will be clipped even if only the X axis is set to Hidden.
Avoid negative `top`/`right`/`bottom`/`left` offsets on absolute children of containers
that have any overflow hidden — keep badge/overlay elements within the parent's bounds instead.
```

Release Notes:

- Added a numeric badge to the git panel sidebar icon showing the count
of uncommitted changes.

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2026-03-16 12:57:20 -03:00

72 lines
2.6 KiB
Rust

use editor::EditorSettings;
use gpui::Pixels;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{RegisterSetting, Settings, StatusStyle};
use ui::{
px,
scrollbars::{ScrollbarVisibility, ShowScrollbar},
};
use workspace::dock::DockPosition;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct ScrollbarSettings {
pub show: Option<ShowScrollbar>,
}
#[derive(Debug, Clone, PartialEq, RegisterSetting)]
pub struct GitPanelSettings {
pub button: bool,
pub dock: DockPosition,
pub default_width: Pixels,
pub status_style: StatusStyle,
pub file_icons: bool,
pub folder_icons: bool,
pub scrollbar: ScrollbarSettings,
pub fallback_branch_name: String,
pub sort_by_path: bool,
pub collapse_untracked_diff: bool,
pub tree_view: bool,
pub diff_stats: bool,
pub show_count_badge: bool,
}
impl ScrollbarVisibility for GitPanelSettings {
fn visibility(&self, cx: &ui::App) -> ShowScrollbar {
// TODO: This PR should have defined Editor's `scrollbar.axis`
// as an Option<ScrollbarAxis>, not a ScrollbarAxes as it would allow you to
// `.unwrap_or(EditorSettings::get_global(cx).scrollbar.show)`.
//
// Once this is fixed we can extend the GitPanelSettings with a `scrollbar.axis`
// so we can show each axis based on the settings.
//
// We should fix this. PR: https://github.com/zed-industries/zed/pull/19495
self.scrollbar
.show
.unwrap_or_else(|| EditorSettings::get_global(cx).scrollbar.show)
}
}
impl Settings for GitPanelSettings {
fn from_settings(content: &settings::SettingsContent) -> Self {
let git_panel = content.git_panel.clone().unwrap();
Self {
button: git_panel.button.unwrap(),
dock: git_panel.dock.unwrap().into(),
default_width: px(git_panel.default_width.unwrap()),
status_style: git_panel.status_style.unwrap(),
file_icons: git_panel.file_icons.unwrap(),
folder_icons: git_panel.folder_icons.unwrap(),
scrollbar: ScrollbarSettings {
show: git_panel.scrollbar.unwrap().show.map(Into::into),
},
fallback_branch_name: git_panel.fallback_branch_name.unwrap(),
sort_by_path: git_panel.sort_by_path.unwrap(),
collapse_untracked_diff: git_panel.collapse_untracked_diff.unwrap(),
tree_view: git_panel.tree_view.unwrap(),
diff_stats: git_panel.diff_stats.unwrap(),
show_count_badge: git_panel.show_count_badge.unwrap(),
}
}
}