chore: audit components

This commit is contained in:
2026-11-10 15:06:16 +07:00
parent af2177c6c8
commit 03369f501e
92 changed files with 2744 additions and 1947 deletions
+40 -6
View File
@@ -4,7 +4,9 @@
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{Mutex, OnceLock};
use std::sync::{Mutex, OnceLock, PoisonError};
use gpui::SharedString;
use crate::IconRole;
@@ -144,14 +146,33 @@ fn icon_score(path: &Path, requested: u32) -> SizeScore {
.unwrap_or(SizeScore::Any)
}
/// The discovered icon file for a role at a size, cached for the process.
type IconCache = Mutex<HashMap<(&'static str, u32), Option<PathBuf>>>;
/// A resolved icon file and the asset key its bytes are served under.
struct CachedIcon {
path: PathBuf,
asset_key: SharedString,
}
/// The discovered icons, cached for the process.
type IconCache = Mutex<HashMap<(&'static str, u32), Option<CachedIcon>>>;
pub(crate) fn cached_file(role: IconRole, requested: u32) -> Option<PathBuf> {
cached_icon(role, requested, |icon| icon.path.clone())
}
/// The asset key a resolved freedesktop file is served under.
pub(crate) fn cached_asset_key(role: IconRole, requested: u32) -> Option<SharedString> {
cached_icon(role, requested, |icon| icon.asset_key.clone())
}
fn cached_icon<T, P>(role: IconRole, requested: u32, project: P) -> Option<T>
where
P: FnOnce(&CachedIcon) -> T,
{
static CACHE: OnceLock<IconCache> = OnceLock::new();
let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
let mut cache = cache.lock().expect("icon cache poisoned");
// A poisoned lock means a lookup panicked; its stale data beats a panic.
let mut cache = cache.lock().unwrap_or_else(PoisonError::into_inner);
cache
.entry((role.slug(), requested))
@@ -162,8 +183,16 @@ pub(crate) fn cached_file(role: IconRole, requested: u32) -> Option<PathBuf> {
requested,
&base_dirs(),
)
.map(|path| CachedIcon {
asset_key: SharedString::from(format!(
"icons/freedesktop/{}-{requested}.svg",
role.slug()
)),
path,
})
})
.clone()
.as_ref()
.map(project)
}
#[cfg(target_os = "linux")]
@@ -253,7 +282,12 @@ mod tests {
fn scratch(name: &str) -> PathBuf {
let root =
std::env::temp_dir().join(format!("gpui-native-icons-{name}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
// NotFound is the expected first run; anything else is a setup failure.
if let Err(error) = std::fs::remove_dir_all(&root)
&& error.kind() != std::io::ErrorKind::NotFound
{
panic!("failed to clear scratch dir {root:?}: {error}");
}
std::fs::create_dir_all(&root).expect("scratch dir");
root
}
+25 -9
View File
@@ -92,7 +92,8 @@ impl IconRole {
}
}
fn slug(self) -> &'static str {
/// The stable identifier used for element and debug ids.
pub fn slug(self) -> &'static str {
match self {
Self::Check => "check",
Self::Close => "close",
@@ -223,11 +224,14 @@ pub struct Icon {
transformation: Option<Transformation>,
}
/// Creates an icon for the role at the default size of 16px.
/// The default icon size; this crate sits beside the theme crate without depending on it.
const DEFAULT_ICON_SIZE: Pixels = px(16.0);
/// Creates an icon for the role at the default size.
pub fn icon(role: IconRole) -> Icon {
Icon {
glyph: Glyph::Role(role),
size: px(16.0),
size: DEFAULT_ICON_SIZE,
color: None,
transformation: None,
}
@@ -241,7 +245,7 @@ impl Icon {
{
Self {
glyph: Glyph::Named(named.path()),
size: px(16.0),
size: DEFAULT_ICON_SIZE,
color: None,
transformation: None,
}
@@ -267,8 +271,7 @@ impl Icon {
#[cfg(target_os = "linux")]
fn freedesktop_asset_path(size: Pixels, role: &IconRole) -> Option<SharedString> {
let size = size.as_f32().max(1.0).round() as u32;
freedesktop::cached_file(*role, size)
.map(|_| SharedString::from(format!("icons/freedesktop/{}-{size}.svg", role.slug())))
freedesktop::cached_asset_key(*role, size)
}
/// Roles resolve per platform.
@@ -295,9 +298,9 @@ impl Icon {
.into_any_element()
}
/// Windows: the Segoe Fluent codepoint drawn as text in the system font.
/// Windows: the Segoe Fluent glyph drawn as text in the system font.
#[cfg(target_os = "windows")]
fn render_glyph(size: Pixels, color: Option<Hsla>, glyph: char) -> AnyElement {
fn render_glyph(size: Pixels, color: Option<Hsla>, glyph: &'static str) -> AnyElement {
div()
.flex_none()
.size(size)
@@ -309,7 +312,7 @@ impl Icon {
.font_family(segoe::FONT_FAMILY)
.text_size(size)
.when_some(color, |el, color| el.text_color(color))
.child(glyph.to_string()),
.child(SharedString::new_static(glyph)),
)
.into_any_element()
}
@@ -424,6 +427,19 @@ impl AssetSource for IconAssets {
mod tests {
use super::*;
#[test]
fn every_role_has_a_unique_slug() {
for (index, role) in IconRole::all().iter().enumerate() {
for other in &IconRole::all()[index + 1..] {
assert_ne!(
role.slug(),
other.slug(),
"{role:?} and {other:?} share a slug"
);
}
}
}
#[test]
fn every_role_resolves_to_a_bundled_asset() {
for role in IconRole::all() {
+25 -26
View File
@@ -3,34 +3,33 @@
use crate::IconRole;
/// The Segoe Fluent Icons codepoint for the role.
pub(crate) fn glyph(role: IconRole) -> Option<char> {
let codepoint = match role {
// No Segoe spinner glyph; the icon falls back to the bundled SVG,
// which the spinner rotates.
/// The Segoe Fluent Icons glyph for the role.
pub(crate) fn glyph(role: IconRole) -> Option<&'static str> {
let glyph = match role {
IconRole::Check => "\u{E73E}", // CheckMark
IconRole::Close => "\u{E8BB}", // ChromeClose
IconRole::ChevronDown => "\u{E70D}", // ChevronDown
IconRole::ChevronLeft => "\u{E76B}", // ChevronLeft
IconRole::ChevronRight => "\u{E76C}", // ChevronRight
IconRole::ChevronUp => "\u{E70E}", // ChevronUp
IconRole::Plus => "\u{E710}", // Add
IconRole::Minus => "\u{E738}", // Remove
IconRole::Search => "\u{E721}", // Search
IconRole::Folder => "\u{E8B7}", // Folder
IconRole::Warning => "\u{E7BA}", // Warning
IconRole::Info => "\u{E946}", // Info
IconRole::Copy => "\u{E8C8}", // Copy
IconRole::Eye => "\u{E7B3}", // RedEye
IconRole::EyeOff => "\u{ED1A}", // Hide
IconRole::PanelLeft => "\u{E90C}", // DockLeft
IconRole::PanelRight => "\u{E90D}", // DockRight
IconRole::WindowMinimize => "\u{E921}", // ChromeMinimize
IconRole::WindowMaximize => "\u{E922}", // ChromeMaximize
IconRole::WindowRestore => "\u{E923}", // ChromeRestore
// No Segoe spinner glyph, the icon falls back to the bundled SVG.
IconRole::Loader => return None,
IconRole::Check => 0xE73E, // CheckMark
IconRole::Close => 0xE8BB, // ChromeClose
IconRole::ChevronDown => 0xE70D, // ChevronDown
IconRole::ChevronLeft => 0xE76B, // ChevronLeft
IconRole::ChevronRight => 0xE76C, // ChevronRight
IconRole::ChevronUp => 0xE70E, // ChevronUp
IconRole::Plus => 0xE710, // Add
IconRole::Minus => 0xE738, // Remove
IconRole::Search => 0xE721, // Search
IconRole::Folder => 0xE8B7, // Folder
IconRole::Warning => 0xE7BA, // Warning
IconRole::Info => 0xE946, // Info
IconRole::Copy => 0xE8C8, // Copy
IconRole::Eye => 0xE7B3, // RedEye
IconRole::EyeOff => 0xED1A, // Hide
IconRole::PanelLeft => 0xE90C, // DockLeft
IconRole::PanelRight => 0xE90D, // DockRight
IconRole::WindowMinimize => 0xE921, // ChromeMinimize
IconRole::WindowMaximize => 0xE922, // ChromeMaximize
IconRole::WindowRestore => 0xE923, // ChromeRestore
};
char::from_u32(codepoint)
Some(glyph)
}
/// The system font the codepoints resolve against (Windows 11+).