diff --git a/crates/acp_thread/src/mention.rs b/crates/acp_thread/src/mention.rs index 6fa0887e22..601b630a6e 100644 --- a/crates/acp_thread/src/mention.rs +++ b/crates/acp_thread/src/mention.rs @@ -162,8 +162,15 @@ impl MentionUri { FileIcons::get_icon(abs_path, cx).unwrap_or_else(|| IconName::File.path().into()) } MentionUri::PastedImage => IconName::Image.path().into(), - MentionUri::Directory { .. } => FileIcons::get_folder_icon(false, cx) - .unwrap_or_else(|| IconName::Folder.path().into()), + MentionUri::Directory { abs_path } => { + let name = abs_path + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or_default(); + + FileIcons::get_folder_icon(false, name, cx) + .unwrap_or_else(|| IconName::Folder.path().into()) + } MentionUri::Symbol { .. } => IconName::Code.path().into(), MentionUri::Thread { .. } => IconName::Thread.path().into(), MentionUri::TextThread { .. } => IconName::Thread.path().into(), diff --git a/crates/agent_ui/src/context_picker/completion_provider.rs b/crates/agent_ui/src/context_picker/completion_provider.rs index 020d799c79..0dd4d69622 100644 --- a/crates/agent_ui/src/context_picker/completion_provider.rs +++ b/crates/agent_ui/src/context_picker/completion_provider.rs @@ -594,7 +594,8 @@ impl ContextPickerCompletionProvider { }; let crease_icon_path = if is_directory { - FileIcons::get_folder_icon(false, cx).unwrap_or_else(|| IconName::Folder.path().into()) + FileIcons::get_folder_icon(false, &file_name, cx) + .unwrap_or_else(|| IconName::Folder.path().into()) } else { FileIcons::get_icon(Path::new(&full_path), cx) .unwrap_or_else(|| IconName::File.path().into()) diff --git a/crates/agent_ui/src/context_picker/file_context_picker.rs b/crates/agent_ui/src/context_picker/file_context_picker.rs index 6c224caf4c..24997faa37 100644 --- a/crates/agent_ui/src/context_picker/file_context_picker.rs +++ b/crates/agent_ui/src/context_picker/file_context_picker.rs @@ -330,7 +330,7 @@ pub fn render_file_context_entry( }); let file_icon = if is_directory { - FileIcons::get_folder_icon(false, cx) + FileIcons::get_folder_icon(false, &file_name, cx) } else { FileIcons::get_icon(path, cx) } diff --git a/crates/file_finder/src/open_path_prompt.rs b/crates/file_finder/src/open_path_prompt.rs index 4625872e46..af95dfb224 100644 --- a/crates/file_finder/src/open_path_prompt.rs +++ b/crates/file_finder/src/open_path_prompt.rs @@ -633,10 +633,16 @@ impl PickerDelegate for OpenPathDelegate { if !settings.file_icons { return None; } + let path = path::Path::new(&candidate.path.string); + let icon = if candidate.is_dir { - FileIcons::get_folder_icon(false, cx)? + let name = path + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or_default(); + + FileIcons::get_folder_icon(false, name, cx)? } else { - let path = path::Path::new(&candidate.path.string); FileIcons::get_icon(path, cx)? }; Some(Icon::from_path(icon).color(Color::Muted)) diff --git a/crates/file_icons/src/file_icons.rs b/crates/file_icons/src/file_icons.rs index 42c00fb12d..07871e7846 100644 --- a/crates/file_icons/src/file_icons.rs +++ b/crates/file_icons/src/file_icons.rs @@ -93,18 +93,49 @@ impl FileIcons { }) } - pub fn get_folder_icon(expanded: bool, cx: &App) -> Option { - fn get_folder_icon(icon_theme: &Arc, expanded: bool) -> Option { + pub fn get_folder_icon(expanded: bool, name: &str, cx: &App) -> Option { + fn get_folder_icon( + icon_theme: &Arc, + name: &str, + expanded: bool, + ) -> Option { if expanded { + let named_icon = icon_theme + .named_directory_icons + .expanded + .get(name) + .and_then(|key| icon_theme.file_icons.get(key)) + .map(|icon_definition| icon_definition.path.clone()); + + if let Some(named_icon) = named_icon { + return Some(named_icon); + } + icon_theme.directory_icons.expanded.clone() } else { + let named_icon = icon_theme + .named_directory_icons + .collapsed + .get(name) + .and_then(|key| icon_theme.file_icons.get(key)) + .map(|icon_definition| icon_definition.path.clone()); + + if let Some(named_icon) = named_icon { + return Some(named_icon); + } + icon_theme.directory_icons.collapsed.clone() } } - get_folder_icon(&ThemeSettings::get_global(cx).active_icon_theme, expanded).or_else(|| { + get_folder_icon( + &ThemeSettings::get_global(cx).active_icon_theme, + name, + expanded, + ) + .or_else(|| { Self::default_icon_theme(cx) - .and_then(|icon_theme| get_folder_icon(&icon_theme, expanded)) + .and_then(|icon_theme| get_folder_icon(&icon_theme, name, expanded)) }) } diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 10698cead8..7d9571ed85 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -2319,7 +2319,7 @@ impl OutlinePanel { is_active, ); let icon = if settings.folder_icons { - FileIcons::get_folder_icon(is_expanded, cx) + FileIcons::get_folder_icon(is_expanded, &name, cx) } else { FileIcons::get_chevron_icon(is_expanded, cx) } @@ -2416,7 +2416,7 @@ impl OutlinePanel { .unwrap_or_default(); let color = entry_git_aware_label_color(git_status, is_ignored, is_active); let icon = if settings.folder_icons { - FileIcons::get_folder_icon(is_expanded, cx) + FileIcons::get_folder_icon(is_expanded, &name, cx) } else { FileIcons::get_chevron_icon(is_expanded, cx) } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 5a30a3e9bc..149c4b38d1 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -4641,7 +4641,12 @@ impl ProjectPanel { } _ => { if show_folder_icons { - FileIcons::get_folder_icon(is_expanded, cx) + let name = entry + .path + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or_default(); + FileIcons::get_folder_icon(is_expanded, name, cx) } else { FileIcons::get_chevron_icon(is_expanded, cx) } diff --git a/crates/theme/src/icon_theme.rs b/crates/theme/src/icon_theme.rs index c21709559a..59806275a5 100644 --- a/crates/theme/src/icon_theme.rs +++ b/crates/theme/src/icon_theme.rs @@ -28,6 +28,8 @@ pub struct IconTheme { pub appearance: Appearance, /// The icons used for directories. pub directory_icons: DirectoryIcons, + /// The icons used for named directories. + pub named_directory_icons: NamedDirectoryIcons, /// The icons used for chevrons. pub chevron_icons: ChevronIcons, /// The mapping of file stems to their associated icon keys. @@ -47,6 +49,15 @@ pub struct DirectoryIcons { pub expanded: Option, } +/// The icons used for directories with specific names +#[derive(Debug, PartialEq)] +pub struct NamedDirectoryIcons { + /// The paths for icons to use for collapsed directories. + pub collapsed: HashMap, + /// The paths for icons to use for expanded directories. + pub expanded: HashMap, +} + /// The icons used for chevrons. #[derive(Debug, PartialEq)] pub struct ChevronIcons { @@ -392,6 +403,10 @@ static DEFAULT_ICON_THEME: LazyLock> = LazyLock::new(|| { collapsed: Some("icons/file_icons/folder.svg".into()), expanded: Some("icons/file_icons/folder_open.svg".into()), }, + named_directory_icons: NamedDirectoryIcons { + collapsed: Default::default(), + expanded: Default::default(), + }, chevron_icons: ChevronIcons { collapsed: Some("icons/file_icons/chevron_right.svg".into()), expanded: Some("icons/file_icons/chevron_down.svg".into()), diff --git a/crates/theme/src/icon_theme_schema.rs b/crates/theme/src/icon_theme_schema.rs index f73938bc5c..eb53c185dd 100644 --- a/crates/theme/src/icon_theme_schema.rs +++ b/crates/theme/src/icon_theme_schema.rs @@ -21,6 +21,8 @@ pub struct IconThemeContent { #[serde(default)] pub directory_icons: DirectoryIconsContent, #[serde(default)] + pub named_directory_icons: NamedDirectoryIconsContent, + #[serde(default)] pub chevron_icons: ChevronIconsContent, #[serde(default)] pub file_stems: HashMap, @@ -36,6 +38,14 @@ pub struct DirectoryIconsContent { pub expanded: Option, } +#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)] +pub struct NamedDirectoryIconsContent { + #[serde(default)] + pub collapsed: HashMap, + #[serde(default)] + pub expanded: HashMap, +} + #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)] pub struct ChevronIconsContent { pub collapsed: Option, diff --git a/crates/theme/src/registry.rs b/crates/theme/src/registry.rs index f0f0776582..0c369a38ff 100644 --- a/crates/theme/src/registry.rs +++ b/crates/theme/src/registry.rs @@ -13,8 +13,8 @@ use util::ResultExt; use crate::{ Appearance, AppearanceContent, ChevronIcons, DEFAULT_ICON_THEME_NAME, DirectoryIcons, - IconDefinition, IconTheme, Theme, ThemeFamily, ThemeFamilyContent, default_icon_theme, - read_icon_theme, read_user_theme, refine_theme_family, + IconDefinition, IconTheme, NamedDirectoryIcons, Theme, ThemeFamily, ThemeFamilyContent, + default_icon_theme, read_icon_theme, read_user_theme, refine_theme_family, }; /// The metadata for a theme. @@ -298,6 +298,14 @@ impl ThemeRegistry { let mut file_suffixes = default_icon_theme.file_suffixes.clone(); file_suffixes.extend(icon_theme.file_suffixes); + let mut named_directory_icons_expanded = + default_icon_theme.named_directory_icons.expanded.clone(); + named_directory_icons_expanded.extend(icon_theme.named_directory_icons.expanded); + + let mut named_directory_icons_collapsed = + default_icon_theme.named_directory_icons.collapsed.clone(); + named_directory_icons_collapsed.extend(icon_theme.named_directory_icons.collapsed); + let icon_theme = IconTheme { id: uuid::Uuid::new_v4().to_string(), name: icon_theme.name.into(), @@ -309,6 +317,10 @@ impl ThemeRegistry { collapsed: icon_theme.directory_icons.collapsed.map(resolve_icon_path), expanded: icon_theme.directory_icons.expanded.map(resolve_icon_path), }, + named_directory_icons: NamedDirectoryIcons { + collapsed: named_directory_icons_collapsed, + expanded: named_directory_icons_expanded, + }, chevron_icons: ChevronIcons { collapsed: icon_theme.chevron_icons.collapsed.map(resolve_icon_path), expanded: icon_theme.chevron_icons.expanded.map(resolve_icon_path),