From ea42a195314fac7f0b0c03ee800a195cfd70e3e4 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Mon, 13 Apr 2026 13:15:00 -0300 Subject: [PATCH] git_ui: Improve design in branch, stash, and worktree pickers (#53798) Mainly improving label truncation as well as making date/time display consistent between the branch and stash pickers. In the list item, we display relative timestamps, while in the tooltip, we display it in absolute for more details. Release Notes: - N/A --- crates/git_ui/src/branch_picker.rs | 53 ++++++++++++++++++++-------- crates/git_ui/src/stash_picker.rs | 47 +++++++++++++++++++++++- crates/git_ui/src/worktree_picker.rs | 2 +- 3 files changed, 85 insertions(+), 17 deletions(-) diff --git a/crates/git_ui/src/branch_picker.rs b/crates/git_ui/src/branch_picker.rs index 7269a14ab3c0931e71feb83673172b301c6f1087..d7186ef018437fde8d54f8f7f7ddd19bc9b68e1a 100644 --- a/crates/git_ui/src/branch_picker.rs +++ b/crates/git_ui/src/branch_picker.rs @@ -864,7 +864,7 @@ impl PickerDelegate for BranchListDelegate { ) -> Option { let entry = &self.matches.get(ix)?; - let (commit_time, author_name, subject) = entry + let (commit_time, absolute_time, author_name, subject) = entry .as_branch() .and_then(|branch| { branch.most_recent_commit.as_ref().map(|commit| { @@ -879,11 +879,22 @@ impl PickerDelegate for BranchListDelegate { local_offset, time_format::TimestampFormat::Relative, ); + let absolute_time = time_format::format_localized_timestamp( + commit_time, + OffsetDateTime::now_utc(), + local_offset, + time_format::TimestampFormat::EnhancedAbsolute, + ); let author = commit.author_name.clone(); - (Some(formatted_time), Some(author), Some(subject)) + ( + Some(formatted_time), + Some(absolute_time), + Some(author), + Some(subject), + ) }) }) - .unwrap_or_else(|| (None, None, None)); + .unwrap_or_else(|| (None, None, None, None)); let is_head_branch = entry.as_branch().is_some_and(|branch| branch.is_head); @@ -1076,19 +1087,31 @@ impl PickerDelegate for BranchListDelegate { .when_some( entry.as_branch().map(|b| b.name().to_string()), |this, branch_name| { - this.map(|this| { - if is_head_branch { - this.tooltip(move |_, cx| { - Tooltip::with_meta( - branch_name.clone(), - None, - "Current Branch", - cx, + let absolute_time = absolute_time.clone(); + this.tooltip({ + let is_head = is_head_branch; + Tooltip::element(move |_, _| { + v_flex() + .child(Label::new(branch_name.clone())) + .when(is_head, |this| { + this.child( + Label::new("Current Branch") + .size(LabelSize::Small) + .color(Color::Muted), + ) + }) + .when_some( + absolute_time.clone(), + |this, time| { + this.child( + Label::new(time) + .size(LabelSize::Small) + .color(Color::Muted), + ) + }, ) - }) - } else { - this.tooltip(Tooltip::text(branch_name)) - } + .into_any_element() + }) }) }, ), diff --git a/crates/git_ui/src/stash_picker.rs b/crates/git_ui/src/stash_picker.rs index 2d3515e833e4d353c323f533f1f0f39bb1d76561..963fa9d22bc78a6c559642bc3e7bb2b553436824 100644 --- a/crates/git_ui/src/stash_picker.rs +++ b/crates/git_ui/src/stash_picker.rs @@ -223,6 +223,7 @@ struct StashEntryMatch { entry: StashEntry, positions: Vec, formatted_timestamp: String, + formatted_absolute_timestamp: String, } pub struct StashListDelegate { @@ -264,6 +265,17 @@ impl StashListDelegate { } fn format_timestamp(timestamp: i64, timezone: UtcOffset) -> String { + let timestamp = + OffsetDateTime::from_unix_timestamp(timestamp).unwrap_or(OffsetDateTime::now_utc()); + time_format::format_localized_timestamp( + timestamp, + OffsetDateTime::now_utc(), + timezone, + time_format::TimestampFormat::Relative, + ) + } + + fn format_absolute_timestamp(timestamp: i64, timezone: UtcOffset) -> String { let timestamp = OffsetDateTime::from_unix_timestamp(timestamp).unwrap_or(OffsetDateTime::now_utc()); time_format::format_localized_timestamp( @@ -388,11 +400,14 @@ impl PickerDelegate for StashListDelegate { .into_iter() .map(|entry| { let formatted_timestamp = Self::format_timestamp(entry.timestamp, timezone); + let formatted_absolute_timestamp = + Self::format_absolute_timestamp(entry.timestamp, timezone); StashEntryMatch { entry, positions: Vec::new(), formatted_timestamp, + formatted_absolute_timestamp, } }) .collect() @@ -421,11 +436,14 @@ impl PickerDelegate for StashListDelegate { .map(|candidate| { let entry = all_stash_entries[candidate.candidate_id].clone(); let formatted_timestamp = Self::format_timestamp(entry.timestamp, timezone); + let formatted_absolute_timestamp = + Self::format_absolute_timestamp(entry.timestamp, timezone); StashEntryMatch { entry, positions: candidate.positions, formatted_timestamp, + formatted_absolute_timestamp, } }) .collect() @@ -544,6 +562,7 @@ impl PickerDelegate for StashListDelegate { .toggle_state(selected) .child( h_flex() + .min_w_0() .w_full() .gap_2p5() .child( @@ -551,7 +570,33 @@ impl PickerDelegate for StashListDelegate { .size(IconSize::Small) .color(Color::Muted), ) - .child(div().w_full().child(stash_label).child(branch_info)), + .child( + v_flex() + .id(format!("stash-tooltip-{ix}")) + .min_w_0() + .w_full() + .child(stash_label) + .child(branch_info) + .tooltip({ + let stash_message = Self::format_message( + entry_match.entry.index, + &entry_match.entry.message, + ); + let absolute_timestamp = + entry_match.formatted_absolute_timestamp.clone(); + + Tooltip::element(move |_, _| { + v_flex() + .child(Label::new(stash_message.clone())) + .child( + Label::new(absolute_timestamp.clone()) + .size(LabelSize::Small) + .color(Color::Muted), + ) + .into_any_element() + }) + }), + ), ) .end_slot( h_flex() diff --git a/crates/git_ui/src/worktree_picker.rs b/crates/git_ui/src/worktree_picker.rs index 2503c2ec6c4f5a669b0302ea45891434b901ef20..d544243cb88fa251adf8ef93f7c53c2855fb59ba 100644 --- a/crates/git_ui/src/worktree_picker.rs +++ b/crates/git_ui/src/worktree_picker.rs @@ -889,7 +889,7 @@ impl PickerDelegate for WorktreeListDelegate { }) .size(IconSize::Small), ) - .child(v_flex().w_full().child(branch_name).map(|this| { + .child(v_flex().w_full().min_w_0().child(branch_name).map(|this| { if entry.is_new { this.child( Label::new(sublabel)