git_ui: Clean up file history view design (#43941)

Mostly cleaning up the UI code. The UI looks fairly the same just a bit
more polished, with proper colors and spacing. Also added a scrollbar in
there. Next step would be to make it keyboard navigable.

<img width="500" height="1948" alt="Screenshot 2025-12-01 at 5  38@2x"
src="https://github.com/user-attachments/assets/c266b97c-4a79-4a0e-8e78-2f1ed1ba495f"
/>

Release Notes:

- N/A
This commit is contained in:
Danilo Leal
2025-12-01 20:56:34 +00:00
committed by GitHub
parent 7881551dda
commit 58c9cbae40
2 changed files with 92 additions and 133 deletions
+83 -130
View File
@@ -4,8 +4,7 @@ use git::repository::{FileHistory, FileHistoryEntry, RepoPath};
use git::{GitHostingProviderRegistry, GitRemote, parse_git_remote_url};
use gpui::{
AnyElement, AnyEntity, App, Asset, Context, Entity, EventEmitter, FocusHandle, Focusable,
IntoElement, ListSizingBehavior, Render, Task, UniformListScrollHandle, WeakEntity, Window,
actions, rems, uniform_list,
IntoElement, Render, Task, UniformListScrollHandle, WeakEntity, Window, actions, uniform_list,
};
use project::{
Project, ProjectPath,
@@ -14,11 +13,8 @@ use project::{
use std::any::{Any, TypeId};
use time::OffsetDateTime;
use ui::{
Avatar, Button, ButtonStyle, Color, Icon, IconName, IconSize, Label, LabelCommon as _,
LabelSize, SharedString, prelude::*,
};
use util::{ResultExt, truncate_and_trailoff};
use ui::{Avatar, Chip, Divider, ListItem, WithScrollbar, prelude::*};
use util::ResultExt;
use workspace::{
Item, Workspace,
item::{ItemEvent, SaveOptions},
@@ -195,38 +191,24 @@ impl FileHistoryView {
task.detach();
}
fn list_item_height(&self) -> Rems {
rems(2.0)
}
fn fallback_commit_avatar() -> AnyElement {
Icon::new(IconName::Person)
.color(Color::Muted)
.size(IconSize::Small)
.into_element()
.into_any()
}
fn render_commit_avatar(
&self,
sha: &SharedString,
window: &mut Window,
cx: &mut App,
) -> AnyElement {
) -> impl IntoElement {
let remote = self.remote.as_ref().filter(|r| r.host_supports_avatars());
let size = rems_from_px(20.);
if let Some(remote) = remote {
let avatar_asset = CommitAvatarAsset::new(remote.clone(), sha.clone());
if let Some(Some(url)) = window.use_asset::<CommitAvatarAsset>(&avatar_asset, cx) {
Avatar::new(url.to_string())
.size(rems(1.25))
.into_element()
.into_any()
Avatar::new(url.to_string()).size(size)
} else {
Self::fallback_commit_avatar()
Avatar::new("").size(size)
}
} else {
Self::fallback_commit_avatar()
Avatar::new("").size(size)
}
}
@@ -263,34 +245,52 @@ impl FileHistoryView {
time_format::TimestampFormat::Relative,
);
let selected = self.selected_entry == Some(ix);
let sha = entry.sha.clone();
let repo = self.repository.clone();
let workspace = self.workspace.clone();
let file_path = self.history.path.clone();
let base_bg = if selected {
cx.theme().status().info.alpha(0.1)
} else {
cx.theme().colors().editor_background
};
let hover_bg = if selected {
cx.theme().status().info.alpha(0.15)
} else {
cx.theme().colors().element_hover
};
h_flex()
.id(("commit", ix))
.h(self.list_item_height())
.w_full()
.items_center()
.px(rems(0.75))
.gap_2()
.bg(base_bg)
.hover(|style| style.bg(hover_bg))
.cursor_pointer()
ListItem::new(("commit", ix))
.child(
h_flex()
.h_8()
.w_full()
.pl_0p5()
.pr_2p5()
.gap_2()
.child(
div()
.w(rems_from_px(52.))
.flex_none()
.child(Chip::new(pr_number)),
)
.child(self.render_commit_avatar(&entry.sha, window, cx))
.child(
h_flex()
.w_full()
.justify_between()
.child(
h_flex()
.gap_1()
.child(
Label::new(entry.author_name.clone())
.size(LabelSize::Small)
.color(Color::Default),
)
.child(
Label::new(&entry.subject)
.size(LabelSize::Small)
.color(Color::Muted)
.truncate(),
),
)
.child(
Label::new(relative_timestamp)
.size(LabelSize::Small)
.color(Color::Muted),
),
),
)
.on_click(cx.listener(move |this, _, window, cx| {
this.selected_entry = Some(ix);
cx.notify();
@@ -308,56 +308,6 @@ impl FileHistoryView {
);
}
}))
.child(
div().flex_none().min_w(rems(4.0)).child(
div()
.px(rems(0.5))
.py(rems(0.25))
.rounded_md()
.bg(cx.theme().colors().element_background)
.border_1()
.border_color(cx.theme().colors().border)
.child(
Label::new(pr_number)
.size(LabelSize::Small)
.color(Color::Muted)
.single_line(),
),
),
)
.child(
div()
.flex_none()
.w(rems(1.75))
.child(self.render_commit_avatar(&entry.sha, window, cx)),
)
.child(
div().flex_1().overflow_hidden().child(
h_flex()
.gap_3()
.items_center()
.child(
Label::new(entry.author_name.clone())
.size(LabelSize::Small)
.color(Color::Default)
.single_line(),
)
.child(
Label::new(truncate_and_trailoff(&entry.subject, 100))
.size(LabelSize::Small)
.color(Color::Muted)
.single_line(),
),
),
)
.child(
div().flex_none().child(
Label::new(relative_timestamp)
.size(LabelSize::Small)
.color(Color::Muted)
.single_line(),
),
)
.into_any_element()
}
}
@@ -419,31 +369,49 @@ impl Focusable for FileHistoryView {
}
impl Render for FileHistoryView {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let _file_name = self.history.path.file_name().unwrap_or("File");
let entry_count = self.history.entries.len();
v_flex()
.size_full()
.bg(cx.theme().colors().editor_background)
.child(
h_flex()
.px(rems(0.75))
.py(rems(0.5))
.border_b_1()
.border_color(cx.theme().colors().border)
.bg(cx.theme().colors().title_bar_background)
.items_center()
.h(rems_from_px(41.))
.pl_3()
.pr_2()
.justify_between()
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.child(
h_flex().gap_2().items_center().child(
Label::new(format!("History: {}", self.history.path.as_unix_str()))
.size(LabelSize::Default),
),
Label::new(self.history.path.as_unix_str().to_string())
.color(Color::Muted)
.buffer_font(cx),
)
.child(
Label::new(format!("{} commits", entry_count))
.size(LabelSize::Small)
.color(Color::Muted),
h_flex()
.gap_1p5()
.child(
Label::new(format!("{} commits", entry_count))
.size(LabelSize::Small)
.color(Color::Muted)
.when(self.has_more, |this| this.mr_1()),
)
.when(self.has_more, |this| {
this.child(Divider::vertical()).child(
Button::new("load-more", "Load More")
.disabled(self.loading_more)
.label_size(LabelSize::Small)
.icon(IconName::ArrowCircle)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.on_click(cx.listener(|this, _, window, cx| {
this.load_more(window, cx);
})),
)
}),
),
)
.child(
@@ -474,24 +442,9 @@ impl Render for FileHistoryView {
)
.flex_1()
.size_full()
.with_sizing_behavior(ListSizingBehavior::Auto)
.track_scroll(&self.scroll_handle)
})
.when(self.has_more, |this| {
this.child(
div().p(rems(0.75)).flex().justify_start().child(
Button::new("load-more", "Load more")
.style(ButtonStyle::Subtle)
.disabled(self.loading_more)
.label_size(LabelSize::Small)
.icon(IconName::ArrowCircle)
.icon_position(IconPosition::Start)
.on_click(cx.listener(|this, _, window, cx| {
this.load_more(window, cx);
})),
),
)
}),
.vertical_scrollbar_for(&self.scroll_handle, window, cx),
)
}
}
@@ -518,7 +471,7 @@ impl Item for FileHistoryView {
}
fn tab_icon(&self, _window: &Window, _cx: &App) -> Option<Icon> {
Some(Icon::new(IconName::FileGit))
Some(Icon::new(IconName::GitBranch))
}
fn telemetry_event_text(&self) -> Option<&'static str> {
+9 -3
View File
@@ -91,10 +91,16 @@ impl RenderOnce for Avatar {
self.image
.size(image_size)
.rounded_full()
.bg(cx.theme().colors().ghost_element_background)
.bg(cx.theme().colors().element_disabled)
.with_fallback(|| {
Icon::new(IconName::Person)
.color(Color::Muted)
h_flex()
.size_full()
.justify_center()
.child(
Icon::new(IconName::Person)
.color(Color::Muted)
.size(IconSize::Small),
)
.into_any_element()
}),
)