use crossterm::style::{Color, SetBackgroundColor, SetForegroundColor};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TerminalTheme {
Light,
Dark,
}
static THEME_OVERRIDE: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0);
pub fn set_theme_override(theme: Option<TerminalTheme>) {
let value = match theme {
None => 0,
Some(TerminalTheme::Light) => 1,
Some(TerminalTheme::Dark) => 2,
};
THEME_OVERRIDE.store(value, std::sync::atomic::Ordering::Relaxed);
}
pub fn detect_terminal_theme() -> TerminalTheme {
match THEME_OVERRIDE.load(std::sync::atomic::Ordering::Relaxed) {
1 => return TerminalTheme::Light,
2 => return TerminalTheme::Dark,
_ => {}
}
std::env::var("COLORFGBG")
.ok()
.and_then(|value| {
value
.split(';')
.next_back()
.and_then(|part| part.parse::<u8>().ok())
})
.map(|background| {
if matches!(background, 7 | 15) {
TerminalTheme::Light
} else {
TerminalTheme::Dark
}
})
.unwrap_or(TerminalTheme::Dark)
}
pub fn default_fg_color(selected: bool) -> &'static str {
let _ = selected;
match detect_terminal_theme() {
TerminalTheme::Light => "black",
TerminalTheme::Dark => "white",
}
}
pub fn default_bg_color(selected: bool) -> &'static str {
let _ = selected;
match detect_terminal_theme() {
TerminalTheme::Light => "white",
TerminalTheme::Dark => "black",
}
}
pub fn default_border_color(selected: bool) -> &'static str {
match (detect_terminal_theme(), selected) {
(TerminalTheme::Light, false) => "dim_gray",
(TerminalTheme::Light, true) => "focus_blue",
(TerminalTheme::Dark, false) => "white",
(TerminalTheme::Dark, true) => "focus_blue",
}
}
pub fn default_title_fg_color(selected: bool) -> &'static str {
let _ = selected;
match detect_terminal_theme() {
TerminalTheme::Light => "black",
TerminalTheme::Dark => "white",
}
}
pub fn default_title_bg_color(selected: bool) -> &'static str {
let _ = selected;
match (detect_terminal_theme(), selected) {
(TerminalTheme::Light, _) => "bright_white",
(TerminalTheme::Dark, false) => "black",
(TerminalTheme::Dark, true) => "black",
}
}
pub fn default_selected_title_bg_color() -> &'static str {
"dim_gray"
}
pub fn default_selected_title_fg_color() -> &'static str {
"bright_white"
}
pub fn default_focused_title_bg_color() -> &'static str {
"focus_blue"
}
pub fn default_menu_fg_color() -> &'static str {
default_fg_color(false)
}
pub fn default_menu_bg_color() -> &'static str {
default_bg_color(false)
}
pub fn default_selected_menu_fg_color() -> &'static str {
"bright_white"
}
pub fn default_selected_menu_bg_color() -> &'static str {
"dim_gray"
}
pub fn default_hover_fg_color() -> &'static str {
"black"
}
pub fn default_hover_bg_color() -> &'static str {
"bright_yellow"
}
pub fn get_fg_color_transparent(color: &Option<String>) -> String {
match color {
Some(color_str) => get_fg_color(color_str),
None => String::new(), }
}
pub fn get_bg_color_transparent(color: &Option<String>) -> String {
match color {
Some(color_str) => get_bg_color(color_str),
None => String::new(), }
}
pub fn should_draw_color(color: &Option<String>) -> bool {
color.is_some()
}
pub fn get_fg_color(color: &str) -> String {
match color {
"red" => format!("{}", SetForegroundColor(Color::Red)),
"green" => format!("{}", SetForegroundColor(Color::Green)),
"yellow" => format!("{}", SetForegroundColor(Color::Yellow)),
"blue" => format!("{}", SetForegroundColor(Color::Blue)),
"magenta" => format!("{}", SetForegroundColor(Color::Magenta)),
"cyan" => format!("{}", SetForegroundColor(Color::Cyan)),
"white" => format!("{}", SetForegroundColor(Color::White)),
"black" => format!("{}", SetForegroundColor(Color::Black)),
"dark_gray" | "gray" | "grey" => format!("{}", SetForegroundColor(Color::AnsiValue(8))),
"dim_gray" => format!("{}", SetForegroundColor(Color::AnsiValue(238))),
"focus_blue" => format!("{}", SetForegroundColor(Color::AnsiValue(25))),
"reset" => format!("{}", SetForegroundColor(Color::Reset)),
"bright_black" => format!("{}", SetForegroundColor(Color::AnsiValue(8))),
"bright_red" => format!("{}", SetForegroundColor(Color::AnsiValue(9))),
"bright_green" => format!("{}", SetForegroundColor(Color::AnsiValue(10))),
"bright_yellow" => format!("{}", SetForegroundColor(Color::AnsiValue(11))),
"bright_blue" => format!("{}", SetForegroundColor(Color::AnsiValue(12))),
"bright_magenta" => format!("{}", SetForegroundColor(Color::AnsiValue(13))),
"bright_cyan" => format!("{}", SetForegroundColor(Color::AnsiValue(14))),
"bright_white" => format!("{}", SetForegroundColor(Color::AnsiValue(15))),
_ => format!("{}", SetForegroundColor(Color::Reset)),
}
}
pub fn get_bg_color(color: &str) -> String {
match color {
"red" => format!("{}", SetBackgroundColor(Color::Red)),
"green" => format!("{}", SetBackgroundColor(Color::Green)),
"yellow" => format!("{}", SetBackgroundColor(Color::Yellow)),
"blue" => format!("{}", SetBackgroundColor(Color::Blue)),
"magenta" => format!("{}", SetBackgroundColor(Color::Magenta)),
"cyan" => format!("{}", SetBackgroundColor(Color::Cyan)),
"white" => format!("{}", SetBackgroundColor(Color::White)),
"black" => format!("{}", SetBackgroundColor(Color::Black)),
"dark_gray" | "gray" | "grey" => format!("{}", SetBackgroundColor(Color::AnsiValue(8))),
"dim_gray" => format!("{}", SetBackgroundColor(Color::AnsiValue(238))),
"focus_blue" => format!("{}", SetBackgroundColor(Color::AnsiValue(25))),
"reset" => format!("{}", SetBackgroundColor(Color::Reset)),
"bright_black" => format!("{}", SetBackgroundColor(Color::AnsiValue(8))),
"bright_red" => format!("{}", SetBackgroundColor(Color::AnsiValue(9))),
"bright_green" => format!("{}", SetBackgroundColor(Color::AnsiValue(10))),
"bright_yellow" => format!("{}", SetBackgroundColor(Color::AnsiValue(11))),
"bright_blue" => format!("{}", SetBackgroundColor(Color::AnsiValue(12))),
"bright_magenta" => format!("{}", SetBackgroundColor(Color::AnsiValue(13))),
"bright_cyan" => format!("{}", SetBackgroundColor(Color::AnsiValue(14))),
"bright_white" => format!("{}", SetBackgroundColor(Color::AnsiValue(15))),
_ => format!("{}", SetBackgroundColor(Color::Reset)),
}
}
#[cfg(test)]
mod theme_override_tests {
use super::*;
struct ResetGuard;
impl Drop for ResetGuard {
fn drop(&mut self) {
set_theme_override(None);
}
}
#[test]
fn test_theme_override_and_faithful_dark_inverse() {
let _guard = ResetGuard;
let dark = ["black", "bright_black"];
let light = ["white", "bright_white"];
set_theme_override(Some(TerminalTheme::Light));
assert_eq!(detect_terminal_theme(), TerminalTheme::Light);
assert_eq!(default_bg_color(false), "white");
set_theme_override(Some(TerminalTheme::Dark));
assert_eq!(detect_terminal_theme(), TerminalTheme::Dark);
assert_eq!(default_bg_color(false), "black");
for selected in [false, true] {
assert!(
dark.contains(&default_bg_color(selected)),
"dark panel bg must be dark (selected={selected}), got {}",
default_bg_color(selected)
);
assert!(
light.contains(&default_fg_color(selected)),
"dark text must be light (selected={selected})"
);
assert!(
dark.contains(&default_title_bg_color(selected)),
"dark title bar bg must be dark (selected={selected}), got {}",
default_title_bg_color(selected)
);
}
assert!(dark.contains(&default_menu_bg_color()));
set_theme_override(Some(TerminalTheme::Light));
let light_sel_bg = default_selected_menu_bg_color();
let light_hover_bg = default_hover_bg_color();
let light_hover_fg = default_hover_fg_color();
set_theme_override(Some(TerminalTheme::Dark));
assert_eq!(default_selected_menu_bg_color(), light_sel_bg);
assert_eq!(default_hover_bg_color(), light_hover_bg);
assert_eq!(default_hover_fg_color(), light_hover_fg);
}
}