diff --git a/Cargo.lock b/Cargo.lock
index 4e3473b639..db5620acba 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4002,15 +4002,12 @@ dependencies = [
"gpui",
"lazy_static",
"libc",
- "log",
"notify",
"parking_lot",
"rope",
"serde",
- "serde_derive",
"serde_json",
"smol",
- "sum_tree",
"tempfile",
"text",
"time",
diff --git a/assets/icons/person.svg b/assets/icons/person.svg
new file mode 100644
index 0000000000..f6133478d1
--- /dev/null
+++ b/assets/icons/person.svg
@@ -0,0 +1,3 @@
+
diff --git a/crates/editor/src/blame_entry_tooltip.rs b/crates/editor/src/blame_entry_tooltip.rs
new file mode 100644
index 0000000000..a07d399149
--- /dev/null
+++ b/crates/editor/src/blame_entry_tooltip.rs
@@ -0,0 +1,250 @@
+use futures::Future;
+use git::blame::BlameEntry;
+use git::Oid;
+use gpui::{
+ Asset, Element, ParentElement, Render, ScrollHandle, StatefulInteractiveElement, WeakView,
+ WindowContext,
+};
+use settings::Settings;
+use std::hash::Hash;
+use theme::{ActiveTheme, ThemeSettings};
+use ui::{
+ div, h_flex, tooltip_container, v_flex, Avatar, Button, ButtonStyle, Clickable as _, Color,
+ FluentBuilder, Icon, IconName, IconPosition, InteractiveElement as _, IntoElement,
+ SharedString, Styled as _, ViewContext,
+};
+use ui::{ButtonCommon, Disableable as _};
+use workspace::Workspace;
+
+use crate::git::blame::{CommitDetails, GitRemote};
+use crate::EditorStyle;
+
+struct CommitAvatar<'a> {
+ details: Option<&'a CommitDetails>,
+ sha: Oid,
+}
+
+impl<'a> CommitAvatar<'a> {
+ fn new(details: Option<&'a CommitDetails>, sha: Oid) -> Self {
+ Self { details, sha }
+ }
+}
+
+impl<'a> CommitAvatar<'a> {
+ fn render(&'a self, cx: &mut ViewContext) -> Option {
+ let remote = self
+ .details
+ .and_then(|details| details.remote.as_ref())
+ .filter(|remote| remote.host_supports_avatars())?;
+
+ let avatar_url = CommitAvatarAsset::new(remote.clone(), self.sha);
+
+ let element = cx.with_element_context(|cx| {
+ match cx.use_cached_asset::(&avatar_url) {
+ // Loading or no avatar found
+ None | Some(None) => Icon::new(IconName::Person)
+ .color(Color::Muted)
+ .into_element()
+ .into_any(),
+ // Found
+ Some(Some(url)) => Avatar::new(url.to_string()).into_element().into_any(),
+ }
+ });
+ Some(element)
+ }
+}
+
+#[derive(Clone, Debug)]
+struct CommitAvatarAsset {
+ sha: Oid,
+ remote: GitRemote,
+}
+
+impl Hash for CommitAvatarAsset {
+ fn hash(&self, state: &mut H) {
+ self.sha.hash(state);
+ self.remote.host.hash(state);
+ }
+}
+
+impl CommitAvatarAsset {
+ fn new(remote: GitRemote, sha: Oid) -> Self {
+ Self { remote, sha }
+ }
+}
+
+impl Asset for CommitAvatarAsset {
+ type Source = Self;
+ type Output = Option;
+
+ fn load(
+ source: Self::Source,
+ cx: &mut WindowContext,
+ ) -> impl Future