From cc193878530f361705851555b2ec265b15ffda2a Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 30 Sep 2025 08:55:09 +0200 Subject: [PATCH] git_ui: Render avatars in git blame gutter (#39168) Release Notes: - Added setting to render avatar in blame gutter --- assets/settings/default.json | 3 + crates/agent_ui/src/ui/burn_mode_tooltip.rs | 2 +- crates/agent_ui/src/ui/context_pill.rs | 2 +- .../src/ui/unavailable_editing_tooltip.rs | 4 +- crates/collab_ui/src/collab_panel.rs | 4 +- crates/editor/src/editor.rs | 4 +- crates/editor/src/element.rs | 32 +- crates/editor/src/git/blame.rs | 6 +- crates/git/src/hosting_provider.rs | 4 +- crates/git_ui/src/blame_ui.rs | 314 +++++++++--------- crates/git_ui/src/commit_tooltip.rs | 28 +- crates/gpui/src/app/context.rs | 19 +- .../markdown_preview/src/markdown_renderer.rs | 4 +- crates/onboarding/src/ai_setup_page.rs | 4 +- crates/project/src/project_settings.rs | 16 + crates/recent_projects/src/recent_projects.rs | 4 +- .../settings/src/settings_content/project.rs | 12 + .../terminal_view/src/terminal_tab_tooltip.rs | 4 +- crates/ui/src/components/tooltip.rs | 31 +- 19 files changed, 267 insertions(+), 230 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 298bce824569c85416e7557231357ab016473a22..4585433a69d539dd63a2d19f3c3f1f17c5b84b5d 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -1242,6 +1242,9 @@ // The minimum column number to show the inline blame information at "min_column": 0 }, + "blame": { + "show_avatar": true + }, // Control which information is shown in the branch picker. "branch_picker": { "show_author_name": true diff --git a/crates/agent_ui/src/ui/burn_mode_tooltip.rs b/crates/agent_ui/src/ui/burn_mode_tooltip.rs index 72faaa614d0d531365fef9ba5ff0e62a6fbcf145..f95dc1250e36bba388452ce11e6ec783e44248e1 100644 --- a/crates/agent_ui/src/ui/burn_mode_tooltip.rs +++ b/crates/agent_ui/src/ui/burn_mode_tooltip.rs @@ -48,7 +48,7 @@ impl Render for BurnModeTooltip { let keybinding = KeyBinding::for_action(&ToggleBurnMode, window, cx) .map(|kb| kb.size(rems_from_px(12.))); - tooltip_container(window, cx, |this, _, _| { + tooltip_container(cx, |this, _| { this .child( h_flex() diff --git a/crates/agent_ui/src/ui/context_pill.rs b/crates/agent_ui/src/ui/context_pill.rs index c42d33c6d346cf869e7b27dc04c63202b3502068..f85a06455439d8e52a7b4272bc7f8069f36548ac 100644 --- a/crates/agent_ui/src/ui/context_pill.rs +++ b/crates/agent_ui/src/ui/context_pill.rs @@ -704,7 +704,7 @@ impl ContextPillHover { impl Render for ContextPillHover { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - tooltip_container(window, cx, move |this, window, cx| { + tooltip_container(cx, move |this, cx| { this.occlude() .on_mouse_move(|_, _, cx| cx.stop_propagation()) .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()) diff --git a/crates/agent_ui/src/ui/unavailable_editing_tooltip.rs b/crates/agent_ui/src/ui/unavailable_editing_tooltip.rs index 78d4c64e0acc7bff86516657f76007e78a54d304..2993fb89a989619ecfe3d79b06d82a2a6f71fc31 100644 --- a/crates/agent_ui/src/ui/unavailable_editing_tooltip.rs +++ b/crates/agent_ui/src/ui/unavailable_editing_tooltip.rs @@ -12,8 +12,8 @@ impl UnavailableEditingTooltip { } impl Render for UnavailableEditingTooltip { - fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - tooltip_container(window, cx, |this, _, _| { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + tooltip_container(cx, |this, _| { this.child(Label::new("Unavailable Editing")).child( div().max_w_64().child( Label::new(format!( diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 6290878483e69d8a7b56ee865672b7f19d74d4de..d3190067f303becd749cc494c973f1fa1b6db195 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -3170,8 +3170,8 @@ struct JoinChannelTooltip { } impl Render for JoinChannelTooltip { - fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - tooltip_container(window, cx, |container, _, cx| { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + tooltip_container(cx, |container, cx| { let participants = self .channel_store .read(cx) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9ab7f857dad3d33b2f4eaaa5298dbee8a307f05d..c724459c98a881bb2d32e245585d05286f8f4531 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -24448,8 +24448,8 @@ fn all_edits_insertions_or_deletions( struct MissingEditPredictionKeybindingTooltip; impl Render for MissingEditPredictionKeybindingTooltip { - fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - ui::tooltip_container(window, cx, |container, _, cx| { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + ui::tooltip_container(cx, |container, cx| { container .flex_shrink_0() .max_w_80() diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 6b99b30757318b21208ab948cb85d651e33fd78c..340b03a710e336263b8f98b989577b3c98af75bc 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2618,7 +2618,7 @@ impl EditorElement { let scroll_top = scroll_position.y * line_height; let start_x = em_width; - let mut last_used_color: Option<(PlayerColor, Oid)> = None; + let mut last_used_color: Option<(Hsla, Oid)> = None; let blame_renderer = cx.global::().0.clone(); let shaped_lines = blamed_rows @@ -2635,7 +2635,8 @@ impl EditorElement { self.editor.clone(), workspace.clone(), buffer_id, - blame_renderer.clone(), + &*blame_renderer, + window, cx, )?; @@ -7513,27 +7514,25 @@ fn render_blame_entry( blame: &Entity, blame_entry: BlameEntry, style: &EditorStyle, - last_used_color: &mut Option<(PlayerColor, Oid)>, + last_used_color: &mut Option<(Hsla, Oid)>, editor: Entity, workspace: Entity, buffer: BufferId, - renderer: Arc, + renderer: &dyn BlameRenderer, + window: &mut Window, cx: &mut App, ) -> Option { - let mut sha_color = cx - .theme() - .players() - .color_for_participant(blame_entry.sha.into()); + let index: u32 = blame_entry.sha.into(); + let mut sha_color = cx.theme().players().color_for_participant(index).cursor; // If the last color we used is the same as the one we get for this line, but // the commit SHAs are different, then we try again to get a different color. - match *last_used_color { - Some((color, sha)) if sha != blame_entry.sha && color.cursor == sha_color.cursor => { - let index: u32 = blame_entry.sha.into(); - sha_color = cx.theme().players().color_for_participant(index + 1); - } - _ => {} - }; + if let Some((color, sha)) = *last_used_color + && sha != blame_entry.sha + && color == sha_color + { + sha_color = cx.theme().players().color_for_participant(index + 1).cursor; + } last_used_color.replace((sha_color, blame_entry.sha)); let blame = blame.read(cx); @@ -7547,7 +7546,8 @@ fn render_blame_entry( workspace.downgrade(), editor, ix, - sha_color.cursor, + sha_color, + window, cx, ) } diff --git a/crates/editor/src/git/blame.rs b/crates/editor/src/git/blame.rs index 35cab36a5568023db9ff77fd692074d43df56b91..836b61d56674f070abc13dbf6c67981c78818ff6 100644 --- a/crates/editor/src/git/blame.rs +++ b/crates/editor/src/git/blame.rs @@ -95,6 +95,7 @@ pub trait BlameRenderer { _: Entity, _: usize, _: Hsla, + window: &mut Window, _: &mut App, ) -> Option; @@ -142,6 +143,7 @@ impl BlameRenderer for () { _: Entity, _: usize, _: Hsla, + _: &mut Window, _: &mut App, ) -> Option { None @@ -673,8 +675,8 @@ async fn parse_commit_messages( .as_ref() .map(|(provider, remote)| GitRemote { host: provider.clone(), - owner: remote.owner.to_string(), - repo: remote.repo.to_string(), + owner: remote.owner.clone().into(), + repo: remote.repo.clone().into(), }); let pull_request = parsed_remote_url diff --git a/crates/git/src/hosting_provider.rs b/crates/git/src/hosting_provider.rs index 5c11cb5504723432c8a041de42749138c4337915..51cdcda211a21459d057b80bc0d069f8ea5a77a9 100644 --- a/crates/git/src/hosting_provider.rs +++ b/crates/git/src/hosting_provider.rs @@ -17,8 +17,8 @@ pub struct PullRequest { #[derive(Clone)] pub struct GitRemote { pub host: Arc, - pub owner: String, - pub repo: String, + pub owner: SharedString, + pub repo: SharedString, } impl std::fmt::Debug for GitRemote { diff --git a/crates/git_ui/src/blame_ui.rs b/crates/git_ui/src/blame_ui.rs index 4e2c7377ffd795fd64b65621cbb9671e2ec98049..ec405c809518a1959fc07a0488bbfdc5cc14125f 100644 --- a/crates/git_ui/src/blame_ui.rs +++ b/crates/git_ui/src/blame_ui.rs @@ -1,5 +1,5 @@ use crate::{ - commit_tooltip::{CommitAvatar, CommitDetails, CommitTooltip}, + commit_tooltip::{CommitAvatar, CommitTooltip}, commit_view::CommitView, }; use editor::{BlameRenderer, Editor, hover_markdown_style}; @@ -17,7 +17,7 @@ use settings::Settings as _; use theme::ThemeSettings; use time::OffsetDateTime; use time_format::format_local_timestamp; -use ui::{ContextMenu, Divider, IconButtonShape, prelude::*}; +use ui::{ContextMenu, Divider, IconButtonShape, prelude::*, tooltip_container}; use workspace::Workspace; const GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED: usize = 20; @@ -39,6 +39,7 @@ impl BlameRenderer for GitBlameRenderer { editor: Entity, ix: usize, sha_color: Hsla, + window: &mut Window, cx: &mut App, ) -> Option { let relative_timestamp = blame_entry_relative_timestamp(&blame_entry); @@ -46,6 +47,19 @@ impl BlameRenderer for GitBlameRenderer { let author_name = blame_entry.author.as_deref().unwrap_or(""); let name = util::truncate_and_trailoff(author_name, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED); + let avatar = if ProjectSettings::get_global(cx) + .git + .inline_blame + .show_commit_summary + { + None + } else { + CommitAvatar::new( + &blame_entry.sha.to_string().into(), + details.as_ref().and_then(|it| it.remote.as_ref()), + ) + .render(window, cx) + }; Some( div() .mr_2() @@ -63,6 +77,7 @@ impl BlameRenderer for GitBlameRenderer { .items_center() .gap_2() .child(div().text_color(sha_color).child(short_commit_id)) + .children(avatar) .child(name), ) .child(relative_timestamp) @@ -179,31 +194,22 @@ impl BlameRenderer for GitBlameRenderer { .and_then(|t| OffsetDateTime::from_unix_timestamp(t).ok()) .unwrap_or(OffsetDateTime::now_utc()); - let commit_details = CommitDetails { - sha: blame.sha.to_string().into(), - commit_time, - author_name: blame - .author - .clone() - .unwrap_or("".to_string()) - .into(), - author_email: blame.author_mail.unwrap_or("".to_string()).into(), - message: details, - }; - - let avatar = CommitAvatar::new(&commit_details).render(window, cx); - - let author = commit_details.author_name.clone(); - let author_email = commit_details.author_email.clone(); + let sha = blame.sha.to_string().into(); + let author: SharedString = blame + .author + .clone() + .unwrap_or("".to_string()) + .into(); + let author_email = blame.author_mail.as_deref().unwrap_or_default(); + let avatar = CommitAvatar::new(&sha, details.as_ref().and_then(|it| it.remote.as_ref())) + .render(window, cx); - let short_commit_id = commit_details - .sha - .get(0..8) + let short_commit_id = sha + .get(..8) .map(|sha| sha.to_string().into()) - .unwrap_or_else(|| commit_details.sha.clone()); - let full_sha = commit_details.sha.to_string(); + .unwrap_or_else(|| sha.clone()); let absolute_timestamp = format_local_timestamp( - commit_details.commit_time, + commit_time, OffsetDateTime::now_utc(), time_format::TimestampFormat::MediumAbsolute, ); @@ -215,165 +221,143 @@ impl BlameRenderer for GitBlameRenderer { style }; - let message = commit_details - .message + let message = details .as_ref() .map(|_| MarkdownElement::new(markdown.clone(), markdown_style).into_any()) .unwrap_or("".into_any()); - let pull_request = commit_details - .message + let pull_request = details .as_ref() .and_then(|details| details.pull_request.clone()); let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx); let message_max_height = window.line_height() * 12 + (ui_font_size / 0.4); let commit_summary = CommitSummary { - sha: commit_details.sha.clone(), - subject: commit_details - .message + sha: sha.clone(), + subject: details .as_ref() - .map_or(Default::default(), |message| { - message - .message - .split('\n') - .next() - .unwrap() - .trim_end() - .to_string() - .into() - }), - commit_timestamp: commit_details.commit_time.unix_timestamp(), - author_name: commit_details.author_name.clone(), + .and_then(|details| { + Some( + details + .message + .split('\n') + .next()? + .trim_end() + .to_string() + .into(), + ) + }) + .unwrap_or_default(), + commit_timestamp: commit_time.unix_timestamp(), + author_name: author.clone(), has_parent: false, }; - let ui_font = ThemeSettings::get_global(cx).ui_font.clone(); - - // padding to avoid tooltip appearing right below the mouse cursor - // TODO: use tooltip_container here Some( - div() - .pl_2() - .pt_2p5() - .child( - v_flex() - .elevation_2(cx) - .font(ui_font) - .text_ui(cx) - .text_color(cx.theme().colors().text) - .py_1() - .px_2() - .map(|el| { - el.occlude() - .on_mouse_move(|_, _, cx| cx.stop_propagation()) - .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()) - .child( - v_flex() - .w(gpui::rems(30.)) - .gap_4() - .child( - h_flex() - .pb_1p5() - .gap_x_2() - .overflow_x_hidden() - .flex_wrap() - .children(avatar) - .child(author) - .when(!author_email.is_empty(), |this| { - this.child( - div() - .text_color( - cx.theme().colors().text_muted, - ) - .child(author_email), - ) - }) - .border_b_1() - .border_color(cx.theme().colors().border_variant), - ) - .child( + tooltip_container(cx, |d, cx| { + d.occlude() + .on_mouse_move(|_, _, cx| cx.stop_propagation()) + .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()) + .child( + v_flex() + .w(gpui::rems(30.)) + .gap_4() + .child( + h_flex() + .pb_1p5() + .gap_x_2() + .overflow_x_hidden() + .flex_wrap() + .children(avatar) + .child(author) + .when(!author_email.is_empty(), |this| { + this.child( div() - .id("inline-blame-commit-message") - .child(message) - .max_h(message_max_height) - .overflow_y_scroll() - .track_scroll(&scroll_handle), - ) - .child( - h_flex() .text_color(cx.theme().colors().text_muted) - .w_full() - .justify_between() - .pt_1p5() - .border_t_1() - .border_color(cx.theme().colors().border_variant) - .child(absolute_timestamp) - .child( - h_flex() - .gap_1p5() - .when_some(pull_request, |this, pr| { - this.child( - Button::new( - "pull-request-button", - format!("#{}", pr.number), - ) - .color(Color::Muted) - .icon(IconName::PullRequest) - .icon_color(Color::Muted) - .icon_position(IconPosition::Start) - .style(ButtonStyle::Subtle) - .on_click(move |_, _, cx| { - cx.stop_propagation(); - cx.open_url(pr.url.as_str()) - }), - ) - }) - .child(Divider::vertical()) - .child( - Button::new( - "commit-sha-button", - short_commit_id.clone(), - ) - .style(ButtonStyle::Subtle) - .color(Color::Muted) - .icon(IconName::FileGit) - .icon_color(Color::Muted) - .icon_position(IconPosition::Start) - .on_click(move |_, window, cx| { - CommitView::open( - commit_summary.clone(), - repository.downgrade(), - workspace.clone(), - window, - cx, - ); - cx.stop_propagation(); - }), + .child(author_email.to_owned()), + ) + }) + .border_b_1() + .border_color(cx.theme().colors().border_variant), + ) + .child( + div() + .id("inline-blame-commit-message") + .child(message) + .max_h(message_max_height) + .overflow_y_scroll() + .track_scroll(&scroll_handle), + ) + .child( + h_flex() + .text_color(cx.theme().colors().text_muted) + .w_full() + .justify_between() + .pt_1p5() + .border_t_1() + .border_color(cx.theme().colors().border_variant) + .child(absolute_timestamp) + .child( + h_flex() + .gap_1p5() + .when_some(pull_request, |this, pr| { + this.child( + Button::new( + "pull-request-button", + format!("#{}", pr.number), + ) + .color(Color::Muted) + .icon(IconName::PullRequest) + .icon_color(Color::Muted) + .icon_position(IconPosition::Start) + .style(ButtonStyle::Subtle) + .on_click(move |_, _, cx| { + cx.stop_propagation(); + cx.open_url(pr.url.as_str()) + }), + ) + }) + .child(Divider::vertical()) + .child( + Button::new( + "commit-sha-button", + short_commit_id.clone(), + ) + .style(ButtonStyle::Subtle) + .color(Color::Muted) + .icon(IconName::FileGit) + .icon_color(Color::Muted) + .icon_position(IconPosition::Start) + .on_click(move |_, window, cx| { + CommitView::open( + commit_summary.clone(), + repository.downgrade(), + workspace.clone(), + window, + cx, + ); + cx.stop_propagation(); + }), + ) + .child( + IconButton::new("copy-sha-button", IconName::Copy) + .shape(IconButtonShape::Square) + .icon_size(IconSize::Small) + .icon_color(Color::Muted) + .on_click(move |_, _, cx| { + cx.stop_propagation(); + cx.write_to_clipboard( + ClipboardItem::new_string( + sha.to_string(), + ), ) - .child( - IconButton::new( - "copy-sha-button", - IconName::Copy, - ) - .shape(IconButtonShape::Square) - .icon_size(IconSize::Small) - .icon_color(Color::Muted) - .on_click(move |_, _, cx| { - cx.stop_propagation(); - cx.write_to_clipboard( - ClipboardItem::new_string( - full_sha.clone(), - ), - ) - }), - ), - ), - ), - ) - }), - ) - .into_any_element(), + }), + ), + ), + ), + ) + }) + .into_any_element(), ) } diff --git a/crates/git_ui/src/commit_tooltip.rs b/crates/git_ui/src/commit_tooltip.rs index 809274beae369736ddeb4bb6e3ddc2cca2b15262..84ecc0b3a9c0c708ec81a0af1234506ec0208cd0 100644 --- a/crates/git_ui/src/commit_tooltip.rs +++ b/crates/git_ui/src/commit_tooltip.rs @@ -28,25 +28,33 @@ pub struct CommitDetails { } pub struct CommitAvatar<'a> { - commit: &'a CommitDetails, + sha: &'a SharedString, + remote: Option<&'a GitRemote>, } impl<'a> CommitAvatar<'a> { - pub fn new(details: &'a CommitDetails) -> Self { - Self { commit: details } + pub fn new(sha: &'a SharedString, remote: Option<&'a GitRemote>) -> Self { + Self { sha, remote } + } + + pub fn from_commit_details(details: &'a CommitDetails) -> Self { + Self { + sha: &details.sha, + remote: details + .message + .as_ref() + .and_then(|details| details.remote.as_ref()), + } } } impl<'a> CommitAvatar<'a> { pub fn render(&'a self, window: &mut Window, cx: &mut App) -> Option> { let remote = self - .commit - .message - .as_ref() - .and_then(|details| details.remote.clone()) + .remote .filter(|remote| remote.host_supports_avatars())?; - let avatar_url = CommitAvatarAsset::new(remote, self.commit.sha.clone()); + let avatar_url = CommitAvatarAsset::new(remote.clone(), self.sha.clone()); let element = match window.use_asset::(&avatar_url, cx) { // Loading or no avatar found @@ -169,7 +177,7 @@ impl CommitTooltip { impl Render for CommitTooltip { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - let avatar = CommitAvatar::new(&self.commit).render(window, cx); + let avatar = CommitAvatar::from_commit_details(&self.commit).render(window, cx); let author = self.commit.author_name.clone(); @@ -233,7 +241,7 @@ impl Render for CommitTooltip { has_parent: false, }; - tooltip_container(window, cx, move |this, _, cx| { + tooltip_container(cx, move |this, cx| { this.occlude() .on_mouse_move(|_, _, cx| cx.stop_propagation()) .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()) diff --git a/crates/gpui/src/app/context.rs b/crates/gpui/src/app/context.rs index 1112878a66b07c133031086c3b14aa8427617bea..a95c225bcbb95e53a1fecbcb251dc8eef7b7c7a8 100644 --- a/crates/gpui/src/app/context.rs +++ b/crates/gpui/src/app/context.rs @@ -4,12 +4,12 @@ use crate::{ Subscription, Task, WeakEntity, WeakFocusHandle, Window, WindowHandle, }; use anyhow::Result; -use derive_more::{Deref, DerefMut}; use futures::FutureExt; use std::{ any::{Any, TypeId}, borrow::{Borrow, BorrowMut}, future::Future, + ops, sync::Arc, }; use util::Deferred; @@ -17,14 +17,25 @@ use util::Deferred; use super::{App, AsyncWindowContext, Entity, KeystrokeEvent}; /// The app context, with specialized behavior for the given entity. -#[derive(Deref, DerefMut)] pub struct Context<'a, T> { - #[deref] - #[deref_mut] app: &'a mut App, entity_state: WeakEntity, } +impl<'a, T> ops::Deref for Context<'a, T> { + type Target = App; + + fn deref(&self) -> &Self::Target { + self.app + } +} + +impl<'a, T> ops::DerefMut for Context<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.app + } +} + impl<'a, T: 'static> Context<'a, T> { pub(crate) fn new_context(app: &'a mut App, entity_state: WeakEntity) -> Self { Self { app, entity_state } diff --git a/crates/markdown_preview/src/markdown_renderer.rs b/crates/markdown_preview/src/markdown_renderer.rs index 221387ee55a785fc5a3192c46bcc2672600d2e9f..de5018ff3d222ed5354e315336551f5dac6b30e7 100644 --- a/crates/markdown_preview/src/markdown_renderer.rs +++ b/crates/markdown_preview/src/markdown_renderer.rs @@ -839,8 +839,8 @@ impl InteractiveMarkdownElementTooltip { } impl Render for InteractiveMarkdownElementTooltip { - fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - tooltip_container(window, cx, |el, _, _| { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + tooltip_container(cx, |el, _| { let secondary_modifier = Keystroke { modifiers: Modifiers::secondary_key(), ..Default::default() diff --git a/crates/onboarding/src/ai_setup_page.rs b/crates/onboarding/src/ai_setup_page.rs index 64c97863c0ee720f3f9ddd1d466b553b043086e0..6acc8aab389c4563f2302ae3a71934676669c130 100644 --- a/crates/onboarding/src/ai_setup_page.rs +++ b/crates/onboarding/src/ai_setup_page.rs @@ -401,10 +401,10 @@ impl AiPrivacyTooltip { } impl Render for AiPrivacyTooltip { - fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { const DESCRIPTION: &str = "We believe in opt-in data sharing as the default for building AI products, rather than opt-out. We'll only use or store your data if you affirmatively send it to us. "; - tooltip_container(window, cx, move |this, _, _| { + tooltip_container(cx, move |this, _| { this.child( h_flex() .gap_1() diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs index a6cbc25198e224df1633f801d361116989be5ca3..5bae7b05773afb71ae9bcb541ef140cda29464ca 100644 --- a/crates/project/src/project_settings.rs +++ b/crates/project/src/project_settings.rs @@ -307,6 +307,8 @@ pub struct GitSettings { /// /// Default: on pub inline_blame: InlineBlameSettings, + /// Git blame settings. + pub blame: BlameSettings, /// Which information to show in the branch picker. /// /// Default: on @@ -344,6 +346,14 @@ pub struct InlineBlameSettings { pub show_commit_summary: bool, } +#[derive(Clone, Copy, Debug)] +pub struct BlameSettings { + /// Whether to show the avatar of the author of the commit. + /// + /// Default: true + pub show_avatar: bool, +} + impl GitSettings { pub fn inline_blame_delay(&self) -> Option { if self.inline_blame.delay_ms.as_millis() > 0 { @@ -447,6 +457,12 @@ impl Settings for ProjectSettings { show_commit_summary: inline.show_commit_summary.unwrap(), } }, + blame: { + let blame = git.blame.unwrap(); + BlameSettings { + show_avatar: blame.show_avatar.unwrap(), + } + }, branch_picker: { let branch_picker = git.branch_picker.unwrap(); BranchPickerSettings { diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index a0753d9549a292f274a84acb5e08f5ac7ab5c09f..b97966692f6377d9761b9b689df1471031e0a011 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -688,8 +688,8 @@ struct MatchTooltip { } impl Render for MatchTooltip { - fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - tooltip_container(window, cx, |div, _, _| { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + tooltip_container(cx, |div, _| { self.highlighted_location.render_paths_children(div) }) } diff --git a/crates/settings/src/settings_content/project.rs b/crates/settings/src/settings_content/project.rs index b44f77fcf171e754313542641ad0f6453a730ed8..cc258148e396bb1d4ebf879423f1900b84e082c3 100644 --- a/crates/settings/src/settings_content/project.rs +++ b/crates/settings/src/settings_content/project.rs @@ -256,6 +256,8 @@ pub struct GitSettings { /// /// Default: on pub inline_blame: Option, + /// Git blame settings. + pub blame: Option, /// Which information to show in the branch picker. /// /// Default: on @@ -305,6 +307,16 @@ pub struct InlineBlameSettings { pub show_commit_summary: Option, } +#[skip_serializing_none] +#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] +#[serde(rename_all = "snake_case")] +pub struct BlameSettings { + /// Whether to show the avatar of the author of the commit. + /// + /// Default: true + pub show_avatar: Option, +} + #[skip_serializing_none] #[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] diff --git a/crates/terminal_view/src/terminal_tab_tooltip.rs b/crates/terminal_view/src/terminal_tab_tooltip.rs index 2e25e4798ff4ad9326d76897c56edc9937375793..6324c0999a8231bb1e633ef39343944783029895 100644 --- a/crates/terminal_view/src/terminal_tab_tooltip.rs +++ b/crates/terminal_view/src/terminal_tab_tooltip.rs @@ -16,8 +16,8 @@ impl TerminalTooltip { } impl Render for TerminalTooltip { - fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - tooltip_container(window, cx, move |this, _window, _cx| { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + tooltip_container(cx, move |this, _cx| { this.occlude() .on_mouse_move(|_, _window, cx| cx.stop_propagation()) .child( diff --git a/crates/ui/src/components/tooltip.rs b/crates/ui/src/components/tooltip.rs index 65ed2f2b6848a2b0c74c6102499027badce935a9..4bfb7d2fc3e38ba5af2d1734d28de75a51096811 100644 --- a/crates/ui/src/components/tooltip.rs +++ b/crates/ui/src/components/tooltip.rs @@ -1,6 +1,7 @@ +use std::borrow::Borrow; use std::rc::Rc; -use gpui::{Action, AnyElement, AnyView, AppContext as _, FocusHandle, IntoElement, Render}; +use gpui::{Action, AnyElement, AnyView, AppContext, FocusHandle, IntoElement, Render}; use settings::Settings; use theme::ThemeSettings; @@ -195,8 +196,8 @@ impl Tooltip { } impl Render for Tooltip { - fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - tooltip_container(window, cx, |el, _, _| { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + tooltip_container(cx, |el, _| { el.child( h_flex() .gap_4() @@ -216,23 +217,23 @@ impl Render for Tooltip { } } -pub fn tooltip_container) -> Div>( - window: &mut Window, - cx: &mut Context, - f: ContentsBuilder, -) -> impl IntoElement + use { - let ui_font = ThemeSettings::get_global(cx).ui_font.clone(); +pub fn tooltip_container(cx: &mut C, f: impl FnOnce(Div, &mut C) -> Div) -> impl IntoElement +where + C: AppContext + Borrow, +{ + let app = (*cx).borrow(); + let ui_font = ThemeSettings::get_global(app).ui_font.clone(); // padding to avoid tooltip appearing right below the mouse cursor div().pl_2().pt_2p5().child( v_flex() - .elevation_2(cx) + .elevation_2(app) .font(ui_font) - .text_ui(cx) - .text_color(cx.theme().colors().text) + .text_ui(app) + .text_color(app.theme().colors().text) .py_1() .px_2() - .map(|el| f(el, window, cx)), + .map(|el| f(el, cx)), ) } @@ -261,8 +262,8 @@ impl LinkPreview { } impl Render for LinkPreview { - fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - tooltip_container(window, cx, |el, _, _| { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + tooltip_container(cx, |el, _| { el.child( Label::new(self.link.clone()) .size(LabelSize::XSmall)