Files
qode/src/icons.rs
T
2026-10-30 23:11:51 +03:00

92 lines
3.2 KiB
Rust

//! Иконки вкладок.
//!
//! Имена совпадают с названиями 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);
}
}