Skip to main content

standout_render/theme/
icon_mode.rs

1//! Icon mode detection for adaptive icon rendering.
2//!
3//! This module provides icon mode detection for icons that adapt between
4//! classic Unicode characters and Nerd Font glyphs.
5//!
6//! # Usage
7//!
8//! Icon mode detection is typically handled automatically by the render
9//! functions. Use [`set_icon_detector`] to override detection for testing.
10//!
11//! ```rust
12//! use standout_render::{IconMode, set_icon_detector};
13//!
14//! // Force Nerd Font mode for testing
15//! set_icon_detector(|| IconMode::NerdFont);
16//!
17//! // Force classic mode
18//! set_icon_detector(|| IconMode::Classic);
19//! ```
20//!
21//! # Auto Detection
22//!
23//! In [`IconMode::Auto`] (the default), the icon mode is resolved by checking
24//! the `NERD_FONT` environment variable. If set to `1` or `true`, Nerd Font
25//! mode is used; otherwise classic mode is used.
26//!
27//! There is no reliable way to automatically detect Nerd Font availability
28//! in a terminal. The environment variable approach is the community standard
29//! used by tools like Starship and Oh My Posh.
30
31use once_cell::sync::Lazy;
32use std::sync::Mutex;
33
34/// The icon rendering mode.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum IconMode {
37    /// Use classic Unicode characters (works in all terminals).
38    Classic,
39    /// Use Nerd Font glyphs (requires a Nerd Font to be installed).
40    NerdFont,
41    /// Auto-detect: check `NERD_FONT` env var, fall back to Classic.
42    Auto,
43}
44
45type IconDetector = fn() -> IconMode;
46
47static ICON_DETECTOR: Lazy<Mutex<IconDetector>> = Lazy::new(|| Mutex::new(default_icon_detector));
48
49/// Overrides the detector used to determine icon mode.
50///
51/// This is useful for testing or when you want to force a specific icon mode.
52///
53/// # Example
54///
55/// ```rust
56/// use standout_render::{IconMode, set_icon_detector};
57///
58/// // Force Nerd Font mode for testing
59/// set_icon_detector(|| IconMode::NerdFont);
60/// ```
61pub fn set_icon_detector(detector: IconDetector) {
62    let mut guard = ICON_DETECTOR.lock().unwrap();
63    *guard = detector;
64}
65
66/// Detects the current icon mode.
67///
68/// Uses the configured detector (default: auto-detect via `NERD_FONT` env var).
69/// Always returns a resolved mode (`Classic` or `NerdFont`), never `Auto`.
70///
71/// The detector can be overridden via [`set_icon_detector`] for testing.
72///
73/// # Returns
74///
75/// - [`IconMode::NerdFont`] if Nerd Font is detected/configured
76/// - [`IconMode::Classic`] otherwise
77pub fn detect_icon_mode() -> IconMode {
78    let detector = ICON_DETECTOR.lock().unwrap();
79    let mode = (*detector)();
80    match mode {
81        IconMode::Auto => resolve_auto(),
82        other => other,
83    }
84}
85
86/// Resolves Auto mode by checking the `NERD_FONT` environment variable.
87fn resolve_auto() -> IconMode {
88    match std::env::var("NERD_FONT") {
89        Ok(val)
90            if val == "1"
91                || val.eq_ignore_ascii_case("true")
92                || val.eq_ignore_ascii_case("yes") =>
93        {
94            IconMode::NerdFont
95        }
96        _ => IconMode::Classic,
97    }
98}
99
100fn default_icon_detector() -> IconMode {
101    IconMode::Auto
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107    use serial_test::serial;
108
109    #[test]
110    #[serial]
111    fn test_detect_icon_mode_default_is_classic() {
112        // Reset to default detector
113        set_icon_detector(default_icon_detector);
114        // Without NERD_FONT env var, should resolve to Classic
115        std::env::remove_var("NERD_FONT");
116        let mode = detect_icon_mode();
117        assert_eq!(mode, IconMode::Classic);
118    }
119
120    #[test]
121    #[serial]
122    fn test_detect_icon_mode_with_env_var() {
123        set_icon_detector(default_icon_detector);
124        std::env::set_var("NERD_FONT", "1");
125        let mode = detect_icon_mode();
126        assert_eq!(mode, IconMode::NerdFont);
127        std::env::remove_var("NERD_FONT");
128    }
129
130    #[test]
131    #[serial]
132    fn test_detect_icon_mode_with_env_var_true() {
133        set_icon_detector(default_icon_detector);
134        std::env::set_var("NERD_FONT", "true");
135        let mode = detect_icon_mode();
136        assert_eq!(mode, IconMode::NerdFont);
137        std::env::remove_var("NERD_FONT");
138    }
139
140    #[test]
141    #[serial]
142    fn test_detect_icon_mode_with_env_var_yes() {
143        set_icon_detector(default_icon_detector);
144        std::env::set_var("NERD_FONT", "YES");
145        let mode = detect_icon_mode();
146        assert_eq!(mode, IconMode::NerdFont);
147        std::env::remove_var("NERD_FONT");
148    }
149
150    #[test]
151    #[serial]
152    fn test_detect_icon_mode_with_env_var_false() {
153        set_icon_detector(default_icon_detector);
154        std::env::set_var("NERD_FONT", "0");
155        let mode = detect_icon_mode();
156        assert_eq!(mode, IconMode::Classic);
157        std::env::remove_var("NERD_FONT");
158    }
159
160    #[test]
161    #[serial]
162    fn test_set_icon_detector_override() {
163        set_icon_detector(|| IconMode::NerdFont);
164        assert_eq!(detect_icon_mode(), IconMode::NerdFont);
165
166        set_icon_detector(|| IconMode::Classic);
167        assert_eq!(detect_icon_mode(), IconMode::Classic);
168
169        // Reset
170        set_icon_detector(default_icon_detector);
171    }
172
173    #[test]
174    #[serial]
175    fn test_detect_never_returns_auto() {
176        set_icon_detector(|| IconMode::Auto);
177        let mode = detect_icon_mode();
178        assert_ne!(mode, IconMode::Auto);
179        // Reset
180        set_icon_detector(default_icon_detector);
181    }
182}