files
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
//! Иконки вкладок.
|
||||
//!
|
||||
//! Имена совпадают с названиями 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<HashMap<String, IconName>> = 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<IconName> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user