1use ratatui::style::Color;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ColorMode {
10 TrueColor,
12 Palette256,
14 Ansi16,
16 Monochrome,
18}
19
20#[derive(Debug, Clone, Copy)]
21pub struct Capabilities {
22 pub color: ColorMode,
23}
24
25impl Capabilities {
26 pub fn accent(self) -> Color {
30 match self.color {
31 ColorMode::TrueColor => Color::Rgb(0xfb, 0x73, 0x85),
32 ColorMode::Palette256 => Color::Indexed(204),
33 ColorMode::Ansi16 => Color::LightMagenta,
34 ColorMode::Monochrome => Color::Reset,
35 }
36 }
37
38 pub fn muted(self) -> Color {
41 match self.color {
42 ColorMode::TrueColor => Color::Rgb(0x88, 0x88, 0x88),
43 ColorMode::Palette256 => Color::Indexed(244),
44 ColorMode::Ansi16 => Color::DarkGray,
45 ColorMode::Monochrome => Color::Reset,
46 }
47 }
48}
49
50pub fn detect_capabilities() -> Capabilities {
54 Capabilities {
55 color: detect_color_from_env(|k| std::env::var(k).ok()),
56 }
57}
58
59fn detect_color_from_env<F: Fn(&str) -> Option<String>>(get: F) -> ColorMode {
60 if get("NO_COLOR").is_some() {
61 return ColorMode::Monochrome;
62 }
63 let term = get("TERM").unwrap_or_default();
64 if term == "dumb" || term.is_empty() {
65 return ColorMode::Monochrome;
66 }
67 if let Some(ct) = get("COLORTERM") {
68 let lower = ct.to_ascii_lowercase();
69 if lower == "truecolor" || lower == "24bit" {
70 return ColorMode::TrueColor;
71 }
72 }
73 if term.contains("256color") {
74 return ColorMode::Palette256;
75 }
76 ColorMode::Ansi16
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 fn env<'a>(pairs: &'a [(&'a str, &'a str)]) -> impl Fn(&str) -> Option<String> + 'a {
84 move |k| {
85 pairs
86 .iter()
87 .find(|(name, _)| *name == k)
88 .map(|(_, v)| (*v).to_string())
89 }
90 }
91
92 #[test]
93 fn no_color_wins_over_everything() {
94 let mode = detect_color_from_env(env(&[
95 ("NO_COLOR", "1"),
96 ("COLORTERM", "truecolor"),
97 ("TERM", "xterm-256color"),
98 ]));
99 assert_eq!(mode, ColorMode::Monochrome);
100 }
101
102 #[test]
103 fn dumb_term_is_monochrome() {
104 assert_eq!(
105 detect_color_from_env(env(&[("TERM", "dumb")])),
106 ColorMode::Monochrome
107 );
108 }
109
110 #[test]
111 fn truecolor_when_colorterm_set() {
112 assert_eq!(
113 detect_color_from_env(env(&[
114 ("COLORTERM", "truecolor"),
115 ("TERM", "xterm-256color"),
116 ])),
117 ColorMode::TrueColor
118 );
119 }
120
121 #[test]
122 fn palette256_when_term_says_so() {
123 assert_eq!(
124 detect_color_from_env(env(&[("TERM", "screen-256color")])),
125 ColorMode::Palette256
126 );
127 }
128
129 #[test]
130 fn ansi16_for_plain_term() {
131 assert_eq!(
132 detect_color_from_env(env(&[("TERM", "xterm")])),
133 ColorMode::Ansi16
134 );
135 }
136
137 #[test]
138 fn empty_term_is_monochrome() {
139 assert_eq!(detect_color_from_env(env(&[])), ColorMode::Monochrome);
140 }
141}