1use std::str::FromStr;
2
3#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
5#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
6#[cfg_attr(feature = "cli", clap(rename_all = "lowercase"))]
7pub enum Theme {
8 Light,
9 Dark,
10 #[default]
12 Auto,
13}
14
15impl Theme {
16 pub fn detect_terminal_theme() -> Self {
20 use std::time::Duration;
21 use terminal_colorsaurus::{QueryOptions, ThemeMode};
22
23 let mut options = QueryOptions::default();
24
25 let is_ssh = std::env::var("SSH_CONNECTION").is_ok()
27 || std::env::var("SSH_CLIENT").is_ok()
28 || std::env::var("SSH_TTY").is_ok();
29 let timeout = if is_ssh || cfg!(windows) {
30 Duration::from_millis(1000)
31 } else {
32 Duration::from_millis(200)
33 };
34 options.timeout = timeout;
35
36 match terminal_colorsaurus::theme_mode(options) {
37 Ok(ThemeMode::Light) => Theme::Light,
38 Ok(ThemeMode::Dark) => Theme::Dark,
39 Err(_) => Theme::Dark,
40 }
41 }
42}
43
44impl FromStr for Theme {
45 type Err = String;
46
47 fn from_str(s: &str) -> Result<Self, Self::Err> {
48 match s.to_lowercase().as_str() {
49 "light" => Ok(Theme::Light),
50 "dark" => Ok(Theme::Dark),
51 "auto" => Ok(Theme::Auto),
52 _ => Err(format!(
53 "invalid theme: '{}', must be 'light', 'dark', or 'auto'",
54 s
55 )),
56 }
57 }
58}
59
60impl Theme {
61 pub fn resolve(&self) -> Theme {
65 match self {
66 Theme::Auto => Self::detect_terminal_theme(),
67 other => *other,
68 }
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn test_theme_parsing() {
78 assert_eq!("light".parse::<Theme>().unwrap(), Theme::Light);
79 assert_eq!("Light".parse::<Theme>().unwrap(), Theme::Light);
80 assert_eq!("LIGHT".parse::<Theme>().unwrap(), Theme::Light);
81 assert_eq!("dark".parse::<Theme>().unwrap(), Theme::Dark);
82 assert_eq!("Dark".parse::<Theme>().unwrap(), Theme::Dark);
83 assert_eq!("auto".parse::<Theme>().unwrap(), Theme::Auto);
84 assert!("invalid".parse::<Theme>().is_err());
85 }
86
87 #[test]
88 fn test_theme_resolve() {
89 assert_eq!(Theme::Light.resolve(), Theme::Light);
90 assert_eq!(Theme::Dark.resolve(), Theme::Dark);
91 let resolved = Theme::Auto.resolve();
93 assert!(resolved == Theme::Light || resolved == Theme::Dark);
94 }
95}