file_icons.rs

  1use std::sync::Arc;
  2use std::{path::Path, str};
  3
  4use gpui::{App, SharedString};
  5use settings::Settings;
  6use theme::{IconTheme, ThemeRegistry, ThemeSettings};
  7use util::paths::PathExt;
  8
  9#[derive(Debug)]
 10pub struct FileIcons {
 11    icon_theme: Arc<IconTheme>,
 12}
 13
 14impl FileIcons {
 15    pub fn get(cx: &App) -> Self {
 16        let theme_settings = ThemeSettings::get_global(cx);
 17
 18        Self {
 19            icon_theme: theme_settings.active_icon_theme.clone(),
 20        }
 21    }
 22
 23    pub fn get_icon(path: &Path, cx: &App) -> Option<SharedString> {
 24        let this = Self::get(cx);
 25
 26        let get_icon_from_suffix = |suffix: &str| -> Option<SharedString> {
 27            this.icon_theme
 28                .file_stems
 29                .get(suffix)
 30                .or_else(|| this.icon_theme.file_suffixes.get(suffix))
 31                .and_then(|typ| this.get_icon_for_type(typ, cx))
 32        };
 33        // TODO: Associate a type with the languages and have the file's language
 34        //       override these associations
 35
 36        // check if file name is in suffixes
 37        // e.g. catch file named `eslint.config.js` instead of `.eslint.config.js`
 38        if let Some(typ) = path.file_name().and_then(|typ| typ.to_str()) {
 39            let maybe_path = get_icon_from_suffix(typ);
 40            if maybe_path.is_some() {
 41                return maybe_path;
 42            }
 43        }
 44
 45        // primary case: check if the files extension or the hidden file name
 46        // matches some icon path
 47        if let Some(suffix) = path.extension_or_hidden_file_name() {
 48            let maybe_path = get_icon_from_suffix(suffix);
 49            if maybe_path.is_some() {
 50                return maybe_path;
 51            }
 52        }
 53
 54        // this _should_ only happen when the file is hidden (has leading '.')
 55        // and is not a "special" file we have an icon (e.g. not `.eslint.config.js`)
 56        // that should be caught above. In the remaining cases, we want to check
 57        // for a normal supported extension e.g. `.data.json` -> `json`
 58        let extension = path.extension().and_then(|ext| ext.to_str());
 59        if let Some(extension) = extension {
 60            let maybe_path = get_icon_from_suffix(extension);
 61            if maybe_path.is_some() {
 62                return maybe_path;
 63            }
 64        }
 65        return this.get_icon_for_type("default", cx);
 66    }
 67
 68    fn default_icon_theme(cx: &App) -> Option<Arc<IconTheme>> {
 69        let theme_registry = ThemeRegistry::global(cx);
 70        theme_registry.default_icon_theme().ok()
 71    }
 72
 73    pub fn get_icon_for_type(&self, typ: &str, cx: &App) -> Option<SharedString> {
 74        fn get_icon_for_type(icon_theme: &Arc<IconTheme>, typ: &str) -> Option<SharedString> {
 75            icon_theme
 76                .file_icons
 77                .get(typ)
 78                .map(|icon_definition| icon_definition.path.clone())
 79        }
 80
 81        get_icon_for_type(&ThemeSettings::get_global(cx).active_icon_theme, typ).or_else(|| {
 82            Self::default_icon_theme(cx).and_then(|icon_theme| get_icon_for_type(&icon_theme, typ))
 83        })
 84    }
 85
 86    pub fn get_folder_icon(expanded: bool, cx: &App) -> Option<SharedString> {
 87        fn get_folder_icon(icon_theme: &Arc<IconTheme>, expanded: bool) -> Option<SharedString> {
 88            if expanded {
 89                icon_theme.directory_icons.expanded.clone()
 90            } else {
 91                icon_theme.directory_icons.collapsed.clone()
 92            }
 93        }
 94
 95        get_folder_icon(&ThemeSettings::get_global(cx).active_icon_theme, expanded).or_else(|| {
 96            Self::default_icon_theme(cx)
 97                .and_then(|icon_theme| get_folder_icon(&icon_theme, expanded))
 98        })
 99    }
100
101    pub fn get_chevron_icon(expanded: bool, cx: &App) -> Option<SharedString> {
102        fn get_chevron_icon(icon_theme: &Arc<IconTheme>, expanded: bool) -> Option<SharedString> {
103            if expanded {
104                icon_theme.chevron_icons.expanded.clone()
105            } else {
106                icon_theme.chevron_icons.collapsed.clone()
107            }
108        }
109
110        get_chevron_icon(&ThemeSettings::get_global(cx).active_icon_theme, expanded).or_else(|| {
111            Self::default_icon_theme(cx)
112                .and_then(|icon_theme| get_chevron_icon(&icon_theme, expanded))
113        })
114    }
115}