Use correct provider icons for extension repository links (#58108)

Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments (N/A)
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

I was unable to find an issue or PR which mentions this.

Extensions with non-GitHub repository links always use the GitHub icon,
which is inconsistent with the changes merged in #44738 and #57500. This
change makes `extension_ui.rs` use the Git hosting provider registry to
determine these icons. Below is a preview of the change:

<div align="center">
    <img
        width="450"
        alt="Preview"

src="https://github.com/user-attachments/assets/e35f0eed-de54-48f9-824d-8a5b9bfc596f"
    />
</div>

Release Notes:

- Improved extension repository links to show provider-specific icons.
This commit is contained in:
Albert Bogusz authored and GitHub committed 2026-08-04 15:14:33 +00:00
1 parent 20c1f82d13
commit 8f0161b3c2
9 files changed
+51 -20

No files matched your search

Generated
+1
View File
@@ -6439,6 +6439,7 @@ dependencies = [
"extension_host",
"fs",
"fuzzy",
"git",
"gpui",
"language",
"log",
+1
View File
@@ -23,6 +23,7 @@ extension.workspace = true
extension_host.workspace = true
fs.workspace = true
fuzzy.workspace = true
git.workspace = true
gpui.workspace = true
language.workspace = true
log.workspace = true
@@ -68,6 +68,7 @@ struct ExtensionCardDetails {
description: Option<SharedString>,
authors: SharedString,
repository_url: Option<SharedString>,
repository_icon: IconName,
provided_features: Vec<&'static str>,
source: ExtensionCardSource,
}
@@ -145,6 +146,7 @@ impl ExtensionCard {
description: extension.description.clone().map(Into::into),
authors: extension.authors.join(", ").into(),
repository_url: extension.repository.clone().map(Into::into),
repository_icon: IconName::Link,
provided_features: provided_feature_labels(extension.provides()),
source: ExtensionCardSource::Dev,
};
@@ -169,6 +171,7 @@ impl ExtensionCard {
description: extension.manifest.description.clone().map(Into::into),
authors: extension.manifest.authors.join(", ").into(),
repository_url: Some(extension.manifest.repository.clone().into()),
repository_icon: IconName::Link,
provided_features: provided_feature_labels(extension.manifest.provides.iter().copied()),
source: ExtensionCardSource::Remote {
status,
@@ -393,6 +396,11 @@ impl ExtensionCard {
}
}
pub fn repository_icon(mut self, icon: IconName) -> Self {
self.details.repository_icon = icon;
self
}
pub fn context_menu(
mut self,
builder: impl Fn(Arc<str>, SharedString, &mut Window, &mut App) -> Option<Entity<ContextMenu>>
@@ -601,6 +609,7 @@ impl RenderOnce for ExtensionCard {
description,
authors,
repository_url,
repository_icon,
provided_features,
source,
} = details;
@@ -701,7 +710,7 @@ impl RenderOnce for ExtensionCard {
.when_some(repository_url, |this, repository_url| {
let repository_url_for_tooltip = repository_url.clone();
this.child(
IconButton::new(repository_button_id, IconName::Github)
IconButton::new(repository_button_id, repository_icon)
.icon_size(IconSize::Small)
.tooltip(move |_, cx| {
Tooltip::with_meta(
+22 -2
View File
@@ -13,6 +13,7 @@ use command_palette_hooks::CommandPaletteFilter;
use editor::{Editor, EditorElement, EditorStyle};
use extension_host::{ExtensionManifest, ExtensionStore};
use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
use git::{GitHostingProviderRegistry, parse_git_remote_url};
use gpui::{
Action, App, ClipboardItem, Context, DismissEvent, Entity, EventEmitter, Focusable,
InteractiveElement, KeyContext, ParentElement, Render, Styled, Task, TaskExt, TextStyle,
@@ -378,6 +379,7 @@ fn keywords_by_feature() -> &'static BTreeMap<Feature, Vec<&'static str>> {
pub struct ExtensionsPage {
workspace: WeakEntity<Workspace>,
provider_registry: Arc<GitHostingProviderRegistry>,
list: UniformListScrollHandle,
is_fetching_extensions: bool,
fetch_failed: bool,
@@ -437,9 +439,11 @@ impl ExtensionsPage {
cx.subscribe(&query_editor, Self::on_query_change).detach();
let scroll_handle = UniformListScrollHandle::new();
let provider_registry = GitHostingProviderRegistry::default_global(cx);
let mut this = Self {
workspace: workspace.weak_handle(),
provider_registry,
list: scroll_handle,
is_fetching_extensions: false,
fetch_failed: false,
@@ -465,6 +469,12 @@ impl ExtensionsPage {
})
}
fn get_repository_icon(&self, repository_url: &str) -> IconName {
parse_git_remote_url(Arc::clone(&self.provider_registry), repository_url)
.map(|(provider, _)| ui::git_hosting_provider_icon(provider.name().as_str()))
.unwrap_or(IconName::Link)
}
fn on_extension_installed(
&mut self,
workspace: WeakEntity<Workspace>,
@@ -665,7 +675,16 @@ impl ExtensionsPage {
if ix < dev_extension_entries_len {
let dev_ix = self.filtered_dev_extension_indices[ix];
let extension = &self.dev_extension_entries[dev_ix];
ExtensionCard::for_dev(extension.clone(), cx)
let repository_icon = extension
.repository
.as_deref()
.map(|url| self.get_repository_icon(url));
let card = ExtensionCard::for_dev(extension.clone(), cx);
if let Some(icon) = repository_icon {
card.repository_icon(icon)
} else {
card
}
} else {
let extension_ix =
self.filtered_remote_extension_indices[ix - dev_extension_entries_len];
@@ -681,7 +700,8 @@ impl ExtensionsPage {
extension: &ExtensionMetadata,
cx: &mut Context<Self>,
) -> ExtensionCard {
let card = ExtensionCard::for_remote(extension, cx);
let repository_icon = self.get_repository_icon(&extension.manifest.repository);
let card = ExtensionCard::for_remote(extension, cx).repository_icon(repository_icon);
let this = cx.weak_entity();
card.context_menu(move |extension_id, authors, window, cx| {
+1 -1
View File
@@ -1226,7 +1226,7 @@ fn remote_provider_icons(
let (provider, _) = parse_git_remote_url(provider_registry.clone(), remote_url)?;
Some((
remote_name.clone(),
crate::get_provider_icon(&provider.name()),
ui::git_hosting_provider_icon(provider.name().as_str()),
))
})
.collect()
+1 -1
View File
@@ -1344,7 +1344,7 @@ impl Render for CommitViewToolbar {
}),
)
.children(remote_info.map(|(provider_name, url)| {
let icon = crate::get_provider_icon(provider_name.as_str());
let icon = ui::git_hosting_provider_icon(provider_name.as_str());
IconButton::new("view_on_provider", icon)
.icon_size(IconSize::Small)
+1 -1
View File
@@ -2982,7 +2982,7 @@ impl GitGraph {
})
.when_some(remote.clone(), |this, remote| {
let provider_name = remote.host.name();
let icon = crate::get_provider_icon(provider_name.as_str());
let icon = ui::git_hosting_provider_icon(provider_name.as_str());
let parsed_remote = ParsedGitRemote {
owner: remote.owner.as_ref().into(),
repo: remote.repo.as_ref().into(),
-14
View File
@@ -60,20 +60,6 @@ pub mod unstaged_diff;
pub use blame_ui::GitBlameStatus;
pub use conflict_view::MergeConflictIndicator;
pub fn get_provider_icon(name: &str) -> IconName {
match name {
"Bitbucket" => IconName::Bitbucket,
"Chromium" => IconName::Gerrit,
"Codeberg" => IconName::Codeberg,
"Forgejo Self-Hosted" => IconName::Forgejo,
"GitHub" => IconName::Github,
"GitLab" => IconName::Gitlab,
"Gitea" => IconName::Gitea,
"SourceHut" => IconName::Sourcehut,
_ => IconName::Link,
}
}
pub fn init(cx: &mut App) {
editor::set_blame_renderer(blame_ui::GitBlameRenderer, cx);
commit_view::init(cx);
+14
View File
@@ -112,6 +112,20 @@ impl From<IconName> for Icon {
}
}
pub fn git_hosting_provider_icon(provider_name: &str) -> IconName {
match provider_name {
"Bitbucket" => IconName::Bitbucket,
"Chromium" => IconName::Gerrit,
"Codeberg" => IconName::Codeberg,
"Forgejo Self-Hosted" => IconName::Forgejo,
"GitHub" => IconName::Github,
"GitLab" => IconName::Gitlab,
"Gitea" => IconName::Gitea,
"SourceHut" => IconName::Sourcehut,
_ => IconName::Link,
}
}
/// The source of an icon.
#[derive(Clone)]
enum IconSource {