//! Иконки вкладок. //! //! Имена совпадают с названиями Lucide (`file`, `file-code`, `book-open`) — //! именно их принимает `app:set_tab_icon(index, name)`. Неизвестное имя не //! подменяется молча: API возвращает `false`, а вкладка продолжает показывать //! иконку по умолчанию. use std::collections::HashMap; use std::sync::LazyLock; use gpui_kit::assets::IconName; /// Иконка вкладки, пока сценарий не назначил другую. pub const DEFAULT_TAB_ICON: &str = "file"; /// Каталог иконок по каноническому имени. static ICONS: LazyLock> = LazyLock::new(|| { IconName::ALL .iter() .map(|icon| (asset_name(*icon), *icon)) .collect() }); /// Имя иконки в каталоге: `icons/file-code.svg` → `file-code`. fn asset_name(icon: IconName) -> String { let path = icon.path(); let path = path.as_str(); let stem = path.strip_prefix("icons/").unwrap_or(path); stem.strip_suffix(".svg").unwrap_or(stem).to_owned() } /// Приводит имя из API к виду каталога. /// /// Регистр не важен; `_` и пробелы считаются дефисом, префикс `icons/` и /// расширение `.svg` можно не писать. fn normalize(name: &str) -> String { let mut normalized = name.trim().to_ascii_lowercase().replace(['_', ' '], "-"); if let Some(rest) = normalized.strip_prefix("icons/") { normalized = rest.to_owned(); } if let Some(rest) = normalized.strip_suffix(".svg") { normalized = rest.to_owned(); } normalized } /// Каноническое имя известной иконки. pub fn canonical_name(name: &str) -> Option<&'static str> { let normalized = normalize(name); ICONS .get_key_value(&normalized) .map(|(name, _)| name.as_str()) } /// Иконка по имени из API; `None`, если такого имени нет в каталоге. pub fn from_name(name: &str) -> Option { let normalized = normalize(name); ICONS.get(&normalized).copied() } /// Иконка вкладки по умолчанию. pub fn default_icon() -> IconName { IconName::File } /// Иконка вкладки: назначенная сценарием или стандартная. pub fn tab_icon(name: Option<&str>) -> IconName { name.and_then(from_name).unwrap_or_else(default_icon) } #[cfg(test)] mod tests { use super::*; #[test] fn names_are_normalized_before_lookup() { assert_eq!(canonical_name("file"), Some("file")); assert_eq!(canonical_name(" File "), Some("file")); assert_eq!(canonical_name("file_code"), Some("file-code")); assert_eq!(canonical_name("icons/file-code.svg"), Some("file-code")); assert_eq!(canonical_name("not-an-icon"), None); } #[test] fn default_icon_is_in_the_catalog() { let icon = default_icon(); assert_eq!(from_name(DEFAULT_TAB_ICON), Some(icon)); assert_eq!(tab_icon(None), icon); assert_eq!(tab_icon(Some("unknown")), icon); } }