From c54fe0f3e5e50a1e235559519efdcf3e869a406f Mon Sep 17 00:00:00 2001 From: Jongchan Date: Mon, 16 Mar 2026 23:06:37 +0900 Subject: [PATCH] git_ui: Add file and folder icons to the Git panel (#51000) Closes https://github.com/zed-industries/zed/discussions/49740 Adds optional file and folder icons to the Git panel so its file list is easier to scan, especially in larger repositories. - add folder icons for Git panel entries - add Git panel settings for file and folder icon visibility --- Release Notes: - Made the Git Panel aware of icon themes. - Added the ability to render file type icons in the Git panel. --------- Co-authored-by: Danilo Leal --- Cargo.lock | 1 + assets/settings/default.json | 8 ++ crates/git_ui/Cargo.toml | 1 + crates/git_ui/src/git_panel.rs | 81 ++++++++++++++++--- crates/git_ui/src/git_panel_settings.rs | 4 + .../settings_content/src/settings_content.rs | 11 +++ crates/settings_ui/src/page_data.rs | 38 ++++++++- 7 files changed, 132 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 36f5d5d5ab4b19e4746052e57ba0bf2823660d7f..29bfcef3a1c55fb1dd199a62dad99142550b92c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7356,6 +7356,7 @@ dependencies = [ "db", "editor", "feature_flags", + "file_icons", "futures 0.3.31", "fuzzy", "git", diff --git a/assets/settings/default.json b/assets/settings/default.json index 29bddd6b3df1af14e66ed3a0aff2e3d8c0cb59d4..563f0371f9612e5b69cce570fd84fdab325824c4 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -898,6 +898,14 @@ // Choices: label_color, icon // Default: icon "status_style": "icon", + // Whether to show file icons in the git panel. + // + // Default: false + "file_icons": false, + // Whether to show folder icons or chevrons for directories in the git panel. + // + // Default: true + "folder_icons": true, // What branch name to use if `init.defaultBranch` is not set // // Default: main diff --git a/crates/git_ui/Cargo.toml b/crates/git_ui/Cargo.toml index 4493cb58471aed9dcf4a259f5a82117992b1dedb..464a71489e2a0ed9118ca672299c9b7310855668 100644 --- a/crates/git_ui/Cargo.toml +++ b/crates/git_ui/Cargo.toml @@ -26,6 +26,7 @@ collections.workspace = true component.workspace = true db.workspace = true editor.workspace = true +file_icons.workspace = true futures.workspace = true feature_flags.workspace = true fuzzy.workspace = true diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 8205f5ee7b6a9966a37a8406331d171d8ca57f1d..ac1c4f97d4ebdbd387da0a0bb3306c58dde8c11e 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -20,6 +20,7 @@ use editor::{ actions::ExpandAllDiffHunks, }; use editor::{EditorStyle, RewrapOptions}; +use file_icons::FileIcons; use futures::StreamExt as _; use git::commit::ParsedCommitMessage; use git::repository::{ @@ -714,11 +715,16 @@ impl GitPanel { let mut was_sort_by_path = GitPanelSettings::get_global(cx).sort_by_path; let mut was_tree_view = GitPanelSettings::get_global(cx).tree_view; + let mut was_file_icons = GitPanelSettings::get_global(cx).file_icons; + let mut was_folder_icons = GitPanelSettings::get_global(cx).folder_icons; let mut was_diff_stats = GitPanelSettings::get_global(cx).diff_stats; cx.observe_global_in::(window, move |this, window, cx| { - let sort_by_path = GitPanelSettings::get_global(cx).sort_by_path; - let tree_view = GitPanelSettings::get_global(cx).tree_view; - let diff_stats = GitPanelSettings::get_global(cx).diff_stats; + let settings = GitPanelSettings::get_global(cx); + let sort_by_path = settings.sort_by_path; + let tree_view = settings.tree_view; + let file_icons = settings.file_icons; + let folder_icons = settings.folder_icons; + let diff_stats = settings.diff_stats; if tree_view != was_tree_view { this.view_mode = GitPanelViewMode::from_settings(cx); } @@ -731,12 +737,22 @@ impl GitPanel { if (diff_stats != was_diff_stats) || update_entries { this.update_visible_entries(window, cx); } + if file_icons != was_file_icons || folder_icons != was_folder_icons { + cx.notify(); + } was_sort_by_path = sort_by_path; was_tree_view = tree_view; + was_file_icons = file_icons; + was_folder_icons = folder_icons; was_diff_stats = diff_stats; }) .detach(); + cx.observe_global::(|_, cx| { + cx.notify(); + }) + .detach(); + // just to let us render a placeholder editor. // Once the active git repo is set, this buffer will be replaced. let temporary_buffer = cx.new(|cx| Buffer::local("", cx)); @@ -5020,15 +5036,21 @@ impl GitPanel { window: &Window, cx: &Context, ) -> AnyElement { - let tree_view = GitPanelSettings::get_global(cx).tree_view; + let settings = GitPanelSettings::get_global(cx); + let tree_view = settings.tree_view; let path_style = self.project.read(cx).path_style(cx); let git_path_style = ProjectSettings::get_global(cx).git.path_style; let display_name = entry.display_name(path_style); let selected = self.selected_entry == Some(ix); let marked = self.marked_entries.contains(&ix); - let status_style = GitPanelSettings::get_global(cx).status_style; + let status_style = settings.status_style; let status = entry.status; + let file_icon = if settings.file_icons { + FileIcons::get_icon(entry.repo_path.as_std_path(), cx) + } else { + None + }; let has_conflict = status.is_conflicted(); let is_modified = status.is_modified(); @@ -5105,6 +5127,21 @@ impl GitPanel { .min_w_0() .flex_1() .gap_1() + .when(settings.file_icons, |this| { + this.child( + file_icon + .map(|file_icon| { + Icon::from_path(file_icon) + .size(IconSize::Small) + .color(Color::Muted) + }) + .unwrap_or_else(|| { + Icon::new(IconName::File) + .size(IconSize::Small) + .color(Color::Muted) + }), + ) + }) .child(git_status_icon(status)) .map(|this| { if tree_view { @@ -5273,10 +5310,24 @@ impl GitPanel { ) }; - let folder_icon = if entry.expanded { - IconName::FolderOpen + let settings = GitPanelSettings::get_global(cx); + let folder_icon = if settings.folder_icons { + FileIcons::get_folder_icon(entry.expanded, entry.key.path.as_std_path(), cx) + } else { + FileIcons::get_chevron_icon(entry.expanded, cx) + }; + let fallback_folder_icon = if settings.folder_icons { + if entry.expanded { + IconName::FolderOpen + } else { + IconName::Folder + } } else { - IconName::Folder + if entry.expanded { + IconName::ChevronDown + } else { + IconName::ChevronRight + } }; let stage_status = if let Some(repo) = &self.active_repository { @@ -5299,9 +5350,17 @@ impl GitPanel { .gap_1() .pl(px(entry.depth as f32 * TREE_INDENT)) .child( - Icon::new(folder_icon) - .size(IconSize::Small) - .color(Color::Muted), + folder_icon + .map(|folder_icon| { + Icon::from_path(folder_icon) + .size(IconSize::Small) + .color(Color::Muted) + }) + .unwrap_or_else(|| { + Icon::new(fallback_folder_icon) + .size(IconSize::Small) + .color(Color::Muted) + }), ) .child(self.entry_label(entry.name.clone(), label_color).truncate()); diff --git a/crates/git_ui/src/git_panel_settings.rs b/crates/git_ui/src/git_panel_settings.rs index 2a7480de355a6190494211d823e4aa440d191371..d48cf44232afa93a8d9ce4e441364912a7200c45 100644 --- a/crates/git_ui/src/git_panel_settings.rs +++ b/crates/git_ui/src/git_panel_settings.rs @@ -20,6 +20,8 @@ pub struct GitPanelSettings { pub dock: DockPosition, pub default_width: Pixels, pub status_style: StatusStyle, + pub file_icons: bool, + pub folder_icons: bool, pub scrollbar: ScrollbarSettings, pub fallback_branch_name: String, pub sort_by_path: bool, @@ -52,6 +54,8 @@ impl Settings for GitPanelSettings { dock: git_panel.dock.unwrap().into(), default_width: px(git_panel.default_width.unwrap()), status_style: git_panel.status_style.unwrap(), + file_icons: git_panel.file_icons.unwrap(), + folder_icons: git_panel.folder_icons.unwrap(), scrollbar: ScrollbarSettings { show: git_panel.scrollbar.unwrap().show.map(Into::into), }, diff --git a/crates/settings_content/src/settings_content.rs b/crates/settings_content/src/settings_content.rs index 023f4954388c0a5e163fe61aba8d970364d84f43..95fc79e2718859baf60ebdd548a172bdc5526468 100644 --- a/crates/settings_content/src/settings_content.rs +++ b/crates/settings_content/src/settings_content.rs @@ -593,6 +593,17 @@ pub struct GitPanelSettingsContent { /// /// Default: icon pub status_style: Option, + + /// Whether to show file icons in the git panel. + /// + /// Default: false + pub file_icons: Option, + + /// Whether to show folder icons or chevrons for directories in the git panel. + /// + /// Default: true + pub folder_icons: Option, + /// How and when the scrollbar should be displayed. /// /// Default: inherits editor scrollbar settings diff --git a/crates/settings_ui/src/page_data.rs b/crates/settings_ui/src/page_data.rs index 5a8e1c1440c7899269fe0382c03b0068937781b5..e6c4ba58c39968985478067527fd7109a22db3b5 100644 --- a/crates/settings_ui/src/page_data.rs +++ b/crates/settings_ui/src/page_data.rs @@ -5048,7 +5048,7 @@ fn panels_page() -> SettingsPage { ] } - fn git_panel_section() -> [SettingsPageItem; 11] { + fn git_panel_section() -> [SettingsPageItem; 13] { [ SettingsPageItem::SectionHeader("Git Panel"), SettingsPageItem::SettingItem(SettingItem { @@ -5190,6 +5190,42 @@ fn panels_page() -> SettingsPage { metadata: None, files: USER, }), + SettingsPageItem::SettingItem(SettingItem { + title: "File Icons", + description: "Show file icons next to the Git status icon.", + field: Box::new(SettingField { + json_path: Some("git_panel.file_icons"), + pick: |settings_content| { + settings_content.git_panel.as_ref()?.file_icons.as_ref() + }, + write: |settings_content, value| { + settings_content + .git_panel + .get_or_insert_default() + .file_icons = value; + }, + }), + metadata: None, + files: USER, + }), + SettingsPageItem::SettingItem(SettingItem { + title: "Folder Icons", + description: "Whether to show folder icons or chevrons for directories in the git panel.", + field: Box::new(SettingField { + json_path: Some("git_panel.folder_icons"), + pick: |settings_content| { + settings_content.git_panel.as_ref()?.folder_icons.as_ref() + }, + write: |settings_content, value| { + settings_content + .git_panel + .get_or_insert_default() + .folder_icons = value; + }, + }), + metadata: None, + files: USER, + }), SettingsPageItem::SettingItem(SettingItem { title: "Diff Stats", description: "Whether to show the addition/deletion change count next to each file in the Git panel.",