This commit is contained in:
MrSubidubi 2025-08-12 19:53:57 +02:00
parent d705585a2e
commit c28d873a2f
34 changed files with 1295 additions and 2348 deletions

View file

@ -13,10 +13,7 @@ use agent_settings::AgentSettings;
use anyhow::Context as _;
use askpass::AskPassDelegate;
use db::kvp::KEY_VALUE_STORE;
use editor::{
Editor, EditorElement, EditorMode, EditorSettings, MultiBuffer, ShowScrollbar,
scroll::ScrollbarAutoHide,
};
use editor::{Editor, EditorElement, EditorMode, MultiBuffer};
use futures::StreamExt as _;
use git::blame::ParsedCommitMessage;
use git::repository::{
@ -31,7 +28,7 @@ use git::{
UnstageAll,
};
use gpui::{
Action, Animation, AnimationExt as _, AsyncApp, AsyncWindowContext, Axis, ClickEvent, Corner,
Action, Animation, AnimationExt as _, AsyncApp, AsyncWindowContext, ClickEvent, Corner,
DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, KeyContext,
ListHorizontalSizingBehavior, ListSizingBehavior, MouseButton, MouseDownEvent, Point,
PromptLevel, ScrollStrategy, Subscription, Task, Transformation, UniformListScrollHandle,
@ -63,9 +60,10 @@ use std::{collections::HashSet, sync::Arc, time::Duration, usize};
use strum::{IntoEnumIterator, VariantNames};
use time::OffsetDateTime;
use ui::{
Checkbox, ContextMenu, ElevationIndex, IconPosition, Label, LabelSize, PopoverMenu, Scrollbar,
ScrollbarState, SplitButton, Tooltip, prelude::*,
Checkbox, ContextMenu, ElevationIndex, IconPosition, Label, LabelSize, PopoverMenu,
SplitButton, Tooltip, prelude::*,
};
use ui::{ScrollAxes, Scrollbars, WithScrollbar};
use util::{ResultExt, TryFutureExt, maybe};
use workspace::SERIALIZATION_THROTTLE_TIME;
@ -276,61 +274,6 @@ struct PendingOperation {
op_id: usize,
}
// computed state related to how to render scrollbars
// one per axis
// on render we just read this off the panel
// we update it when
// - settings change
// - on focus in, on focus out, on hover, etc.
#[derive(Debug)]
struct ScrollbarProperties {
axis: Axis,
show_scrollbar: bool,
show_track: bool,
auto_hide: bool,
hide_task: Option<Task<()>>,
state: ScrollbarState,
}
impl ScrollbarProperties {
// Shows the scrollbar and cancels any pending hide task
fn show(&mut self, cx: &mut Context<GitPanel>) {
if !self.auto_hide {
return;
}
self.show_scrollbar = true;
self.hide_task.take();
cx.notify();
}
fn hide(&mut self, window: &mut Window, cx: &mut Context<GitPanel>) {
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
if !self.auto_hide {
return;
}
let axis = self.axis;
self.hide_task = Some(cx.spawn_in(window, async move |panel, cx| {
cx.background_executor()
.timer(SCROLLBAR_SHOW_INTERVAL)
.await;
if let Some(panel) = panel.upgrade() {
panel
.update(cx, |panel, cx| {
match axis {
Axis::Vertical => panel.vertical_scrollbar.show_scrollbar = false,
Axis::Horizontal => panel.horizontal_scrollbar.show_scrollbar = false,
}
cx.notify();
})
.log_err();
}
}));
}
}
pub struct GitPanel {
pub(crate) active_repository: Option<Entity<Repository>>,
pub(crate) commit_editor: Entity<Editor>,
@ -343,8 +286,6 @@ pub struct GitPanel {
single_tracked_entry: Option<GitStatusEntry>,
focus_handle: FocusHandle,
fs: Arc<dyn Fs>,
horizontal_scrollbar: ScrollbarProperties,
vertical_scrollbar: ScrollbarProperties,
new_count: usize,
entry_count: usize,
new_staged_count: usize,
@ -429,10 +370,6 @@ impl GitPanel {
let git_panel = cx.new(|cx| {
let focus_handle = cx.focus_handle();
cx.on_focus(&focus_handle, window, Self::focus_in).detach();
cx.on_focus_out(&focus_handle, window, |this, _, window, cx| {
this.hide_scrollbars(window, cx);
})
.detach();
let mut was_sort_by_path = GitPanelSettings::get_global(cx).sort_by_path;
cx.observe_global::<SettingsStore>(move |this, cx| {
@ -457,24 +394,6 @@ impl GitPanel {
let scroll_handle = UniformListScrollHandle::new();
let vertical_scrollbar = ScrollbarProperties {
axis: Axis::Vertical,
state: ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity()),
show_scrollbar: false,
show_track: false,
auto_hide: false,
hide_task: None,
};
let horizontal_scrollbar = ScrollbarProperties {
axis: Axis::Horizontal,
state: ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity()),
show_scrollbar: false,
show_track: false,
auto_hide: false,
hide_task: None,
};
let mut assistant_enabled = AgentSettings::get_global(cx).enabled;
let mut was_ai_disabled = DisableAiSettings::get_global(cx).disable_ai;
let _settings_subscription = cx.observe_global::<SettingsStore>(move |_, cx| {
@ -555,8 +474,6 @@ impl GitPanel {
workspace: workspace.weak_handle(),
modal_open: false,
entry_count: 0,
horizontal_scrollbar,
vertical_scrollbar,
bulk_staging: None,
_settings_subscription,
};
@ -568,86 +485,6 @@ impl GitPanel {
git_panel
}
fn hide_scrollbars(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.horizontal_scrollbar.hide(window, cx);
self.vertical_scrollbar.hide(window, cx);
}
fn update_scrollbar_properties(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
// 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
let show_setting = GitPanelSettings::get_global(cx)
.scrollbar
.show
.unwrap_or(EditorSettings::get_global(cx).scrollbar.show);
let scroll_handle = self.scroll_handle.0.borrow();
let autohide = |show: ShowScrollbar, cx: &mut Context<Self>| match show {
ShowScrollbar::Auto => true,
ShowScrollbar::System => cx
.try_global::<ScrollbarAutoHide>()
.map_or_else(|| cx.should_auto_hide_scrollbars(), |autohide| autohide.0),
ShowScrollbar::Always => false,
ShowScrollbar::Never => false,
};
let longest_item_width = scroll_handle.last_item_size.and_then(|size| {
(size.contents.width > size.item.width).then_some(size.contents.width)
});
// is there an item long enough that we should show a horizontal scrollbar?
let item_wider_than_container = if let Some(longest_item_width) = longest_item_width {
longest_item_width > px(scroll_handle.base_handle.bounds().size.width.0)
} else {
true
};
let show_horizontal = match (show_setting, item_wider_than_container) {
(_, false) => false,
(ShowScrollbar::Auto | ShowScrollbar::System | ShowScrollbar::Always, true) => true,
(ShowScrollbar::Never, true) => false,
};
let show_vertical = match show_setting {
ShowScrollbar::Auto | ShowScrollbar::System | ShowScrollbar::Always => true,
ShowScrollbar::Never => false,
};
let show_horizontal_track =
show_horizontal && matches!(show_setting, ShowScrollbar::Always);
// TODO: we probably should hide the scroll track when the list doesn't need to scroll
let show_vertical_track = show_vertical && matches!(show_setting, ShowScrollbar::Always);
self.vertical_scrollbar = ScrollbarProperties {
axis: self.vertical_scrollbar.axis,
state: self.vertical_scrollbar.state.clone(),
show_scrollbar: show_vertical,
show_track: show_vertical_track,
auto_hide: autohide(show_setting, cx),
hide_task: None,
};
self.horizontal_scrollbar = ScrollbarProperties {
axis: self.horizontal_scrollbar.axis,
state: self.horizontal_scrollbar.state.clone(),
show_scrollbar: show_horizontal,
show_track: show_horizontal_track,
auto_hide: autohide(show_setting, cx),
hide_task: None,
};
cx.notify();
}
pub fn entry_by_path(&self, path: &RepoPath, cx: &App) -> Option<usize> {
if GitPanelSettings::get_global(cx).sort_by_path {
return self
@ -2508,12 +2345,11 @@ impl GitPanel {
cx.background_executor().timer(UPDATE_DEBOUNCE).await;
if let Some(git_panel) = handle.upgrade() {
git_panel
.update_in(cx, |git_panel, window, cx| {
.update(cx, |git_panel, cx| {
if clear_pending {
git_panel.clear_pending();
}
git_panel.update_visible_entries(cx);
git_panel.update_scrollbar_properties(window, cx);
})
.ok();
}
@ -3630,110 +3466,6 @@ impl GitPanel {
)
}
fn render_vertical_scrollbar(
&self,
show_horizontal_scrollbar_container: bool,
cx: &mut Context<Self>,
) -> impl IntoElement {
div()
.id("git-panel-vertical-scroll")
.occlude()
.flex_none()
.h_full()
.cursor_default()
.absolute()
.right_0()
.top_0()
.bottom_0()
.w(px(12.))
.when(show_horizontal_scrollbar_container, |this| {
this.pb_neg_3p5()
})
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|this, _, window, cx| {
if !this.vertical_scrollbar.state.is_dragging()
&& !this.focus_handle.contains_focused(window, cx)
{
this.vertical_scrollbar.hide(window, cx);
cx.notify();
}
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.children(Scrollbar::vertical(
// percentage as f32..end_offset as f32,
self.vertical_scrollbar.state.clone(),
))
}
/// Renders the horizontal scrollbar.
///
/// The right offset is used to determine how far to the right the
/// scrollbar should extend to, useful for ensuring it doesn't collide
/// with the vertical scrollbar when visible.
fn render_horizontal_scrollbar(
&self,
right_offset: Pixels,
cx: &mut Context<Self>,
) -> impl IntoElement {
div()
.id("git-panel-horizontal-scroll")
.occlude()
.flex_none()
.w_full()
.cursor_default()
.absolute()
.bottom_neg_px()
.left_0()
.right_0()
.pr(right_offset)
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|this, _, window, cx| {
if !this.horizontal_scrollbar.state.is_dragging()
&& !this.focus_handle.contains_focused(window, cx)
{
this.horizontal_scrollbar.hide(window, cx);
cx.notify();
}
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.children(Scrollbar::horizontal(
// percentage as f32..end_offset as f32,
self.horizontal_scrollbar.state.clone(),
))
}
fn render_buffer_header_controls(
&self,
entity: &Entity<Self>,
@ -3781,35 +3513,19 @@ impl GitPanel {
fn render_entries(
&self,
has_write_access: bool,
_: &Window,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let entry_count = self.entries.len();
let scroll_track_size = px(16.);
let h_scroll_offset = if self.vertical_scrollbar.show_scrollbar {
// magic number
px(3.)
} else {
px(0.)
};
v_flex()
.flex_1()
.size_full()
.overflow_hidden()
.relative()
// Show a border on the top and bottom of the container when
// the vertical scrollbar container is visible so we don't have a
// floating left border in the panel.
.when(self.vertical_scrollbar.show_track, |this| {
this.border_t_1()
.border_b_1()
.border_color(cx.theme().colors().border)
})
.child(
h_flex()
.id("git-panel-entries-container")
.flex_1()
.size_full()
.relative()
@ -3848,15 +3564,6 @@ impl GitPanel {
items
}),
)
.when(
!self.horizontal_scrollbar.show_track
&& self.horizontal_scrollbar.show_scrollbar,
|this| {
// when not showing the horizontal scrollbar track, make sure we don't
// obscure the last entry
this.pb(scroll_track_size)
},
)
.size_full()
.flex_grow()
.with_sizing_behavior(ListSizingBehavior::Auto)
@ -3872,72 +3579,14 @@ impl GitPanel {
this.deploy_panel_context_menu(event.position, window, cx)
}),
)
.when(self.vertical_scrollbar.show_track, |this| {
this.child(
v_flex()
.h_full()
.flex_none()
.w(scroll_track_size)
.bg(cx.theme().colors().panel_background)
.child(
div()
.size_full()
.flex_1()
.border_l_1()
.border_color(cx.theme().colors().border),
),
)
})
.when(self.vertical_scrollbar.show_scrollbar, |this| {
this.child(
self.render_vertical_scrollbar(
self.horizontal_scrollbar.show_track,
cx,
),
)
}),
.custom_scrollbars(
Scrollbars::for_settings::<GitPanelSettings>()
.tracked_scroll_handle(self.scroll_handle.clone())
.with_track_along(ScrollAxes::Horizontal),
window,
cx,
),
)
.when(self.horizontal_scrollbar.show_track, |this| {
this.child(
h_flex()
.w_full()
.h(scroll_track_size)
.flex_none()
.relative()
.child(
div()
.w_full()
.flex_1()
// for some reason the horizontal scrollbar is 1px
// taller than the vertical scrollbar??
.h(scroll_track_size - px(1.))
.bg(cx.theme().colors().panel_background)
.border_t_1()
.border_color(cx.theme().colors().border),
)
.when(self.vertical_scrollbar.show_track, |this| {
this.child(
div()
.flex_none()
// -1px prevents a missing pixel between the two container borders
.w(scroll_track_size - px(1.))
.h_full(),
)
.child(
// HACK: Fill the missing 1px 🥲
div()
.absolute()
.right(scroll_track_size - px(1.))
.bottom(scroll_track_size - px(1.))
.size_px()
.bg(cx.theme().colors().border),
)
}),
)
})
.when(self.horizontal_scrollbar.show_scrollbar, |this| {
this.child(self.render_horizontal_scrollbar(h_scroll_offset, cx))
})
}
fn entry_label(&self, label: impl Into<SharedString>, color: Color) -> Label {
@ -4446,15 +4095,6 @@ impl Render for GitPanel {
.when(has_write_access && has_co_authors, |git_panel| {
git_panel.on_action(cx.listener(Self::toggle_fill_co_authors))
})
.on_hover(cx.listener(move |this, hovered, window, cx| {
if *hovered {
this.horizontal_scrollbar.show(cx);
this.vertical_scrollbar.show(cx);
cx.notify();
} else if !this.focus_handle.contains_focused(window, cx) {
this.hide_scrollbars(window, cx);
}
}))
.size_full()
.overflow_hidden()
.bg(cx.theme().colors().panel_background)

View file

@ -1,8 +1,9 @@
use editor::ShowScrollbar;
use editor::EditorSettings;
use gpui::Pixels;
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
use ui::scrollbars::{ScrollbarVisibilitySetting, ShowScrollbar};
use workspace::dock::DockPosition;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
@ -89,6 +90,22 @@ pub struct GitPanelSettings {
pub collapse_untracked_diff: bool,
}
impl ScrollbarVisibilitySetting for GitPanelSettings {
fn scrollbar_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 {
const KEY: Option<&'static str> = Some("git_panel");