file_icons.rs

  1use std::sync::Arc;
  2use std::{path::Path, str};
  3
  4use collections::HashMap;
  5
  6use gpui::{AppContext, AssetSource, Global, SharedString};
  7use serde_derive::Deserialize;
  8use settings::Settings;
  9use theme::{IconTheme, ThemeRegistry, ThemeSettings};
 10use util::{maybe, paths::PathExt};
 11
 12#[derive(Deserialize, Debug)]
 13pub struct FileIcons {
 14    stems: HashMap<String, String>,
 15    suffixes: HashMap<String, String>,
 16}
 17
 18impl Global for FileIcons {}
 19
 20pub const FILE_TYPES_ASSET: &str = "icons/file_icons/file_types.json";
 21
 22pub fn init(assets: impl AssetSource, cx: &mut AppContext) {
 23    cx.set_global(FileIcons::new(assets))
 24}
 25
 26impl FileIcons {
 27    pub fn get(cx: &AppContext) -> &Self {
 28        cx.global::<FileIcons>()
 29    }
 30
 31    pub fn new(assets: impl AssetSource) -> Self {
 32        assets
 33            .load(FILE_TYPES_ASSET)
 34            .ok()
 35            .flatten()
 36            .and_then(|file| serde_json::from_str::<FileIcons>(str::from_utf8(&file).unwrap()).ok())
 37            .unwrap_or_else(|| FileIcons {
 38                stems: HashMap::default(),
 39                suffixes: HashMap::default(),
 40            })
 41    }
 42
 43    pub fn get_icon(path: &Path, cx: &AppContext) -> Option<SharedString> {
 44        let this = cx.try_global::<Self>()?;
 45
 46        // TODO: Associate a type with the languages and have the file's language
 47        //       override these associations
 48        maybe!({
 49            let suffix = path.icon_stem_or_suffix()?;
 50
 51            if let Some(type_str) = this.stems.get(suffix) {
 52                return this.get_icon_for_type(type_str, cx);
 53            }
 54
 55            this.suffixes
 56                .get(suffix)
 57                .and_then(|type_str| this.get_icon_for_type(type_str, cx))
 58        })
 59        .or_else(|| this.get_icon_for_type("default", cx))
 60    }
 61
 62    fn default_icon_theme(cx: &AppContext) -> Option<Arc<IconTheme>> {
 63        let theme_registry = ThemeRegistry::global(cx);
 64        theme_registry.default_icon_theme().ok()
 65    }
 66
 67    pub fn get_icon_for_type(&self, typ: &str, cx: &AppContext) -> Option<SharedString> {
 68        fn get_icon_for_type(icon_theme: &Arc<IconTheme>, typ: &str) -> Option<SharedString> {
 69            icon_theme
 70                .file_icons
 71                .get(typ)
 72                .map(|icon_definition| icon_definition.path.clone())
 73        }
 74
 75        get_icon_for_type(&ThemeSettings::get_global(cx).active_icon_theme, typ).or_else(|| {
 76            Self::default_icon_theme(cx).and_then(|icon_theme| get_icon_for_type(&icon_theme, typ))
 77        })
 78    }
 79
 80    pub fn get_folder_icon(expanded: bool, cx: &AppContext) -> Option<SharedString> {
 81        fn get_folder_icon(icon_theme: &Arc<IconTheme>, expanded: bool) -> Option<SharedString> {
 82            if expanded {
 83                icon_theme.directory_icons.expanded.clone()
 84            } else {
 85                icon_theme.directory_icons.collapsed.clone()
 86            }
 87        }
 88
 89        get_folder_icon(&ThemeSettings::get_global(cx).active_icon_theme, expanded).or_else(|| {
 90            Self::default_icon_theme(cx)
 91                .and_then(|icon_theme| get_folder_icon(&icon_theme, expanded))
 92        })
 93    }
 94
 95    pub fn get_chevron_icon(expanded: bool, cx: &AppContext) -> Option<SharedString> {
 96        fn get_chevron_icon(icon_theme: &Arc<IconTheme>, expanded: bool) -> Option<SharedString> {
 97            if expanded {
 98                icon_theme.chevron_icons.expanded.clone()
 99            } else {
100                icon_theme.chevron_icons.collapsed.clone()
101            }
102        }
103
104        get_chevron_icon(&ThemeSettings::get_global(cx).active_icon_theme, expanded).or_else(|| {
105            Self::default_icon_theme(cx)
106                .and_then(|icon_theme| get_chevron_icon(&icon_theme, expanded))
107        })
108    }
109}