use crate::{Color, ColorPair};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColorSupport {
Ansi16,
Ansi256,
Truecolor,
}
impl ColorSupport {
pub fn max(self, other: Self) -> Self {
use ColorSupport::*;
match (self, other) {
(Truecolor, _) | (_, Truecolor) => Truecolor,
(Ansi256, _) | (_, Ansi256) => Ansi256,
_ => Ansi16,
}
}
pub fn min(self, other: Self) -> Self {
use ColorSupport::*;
match (self, other) {
(Ansi16, _) | (_, Ansi16) => Ansi16,
(Ansi256, _) | (_, Ansi256) => Ansi256,
_ => Truecolor,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TerminalCapabilities {
pub color_support: ColorSupport,
pub bracketed_paste: bool,
pub mouse: bool,
pub cursor_style: bool,
}
impl Default for TerminalCapabilities {
fn default() -> Self {
Self {
color_support: ColorSupport::Ansi16,
bracketed_paste: true,
mouse: true,
cursor_style: false,
}
}
}
impl TerminalCapabilities {
pub fn detect() -> Self {
let term = std::env::var("TERM").unwrap_or_default().to_lowercase();
let colorterm = std::env::var("COLORTERM")
.unwrap_or_default()
.to_lowercase();
let mut caps = TerminalCapabilities::default();
if colorterm.contains("truecolor")
|| colorterm.contains("24bit")
|| term.contains("truecolor")
{
caps.color_support = ColorSupport::Truecolor;
} else if term.contains("256color") {
caps.color_support = ColorSupport::Ansi256;
} else {
caps.color_support = ColorSupport::Ansi16;
}
caps
}
pub fn downgrade_pair(self, pair: ColorPair) -> ColorPair {
ColorPair {
fg: self.downgrade_color(pair.fg),
bg: self.downgrade_color(pair.bg),
}
}
pub fn downgrade_color(self, c: Color) -> Color {
match c {
Color::Reset | Color::Transparent => c,
Color::Black
| Color::Red
| Color::Green
| Color::Yellow
| Color::Blue
| Color::Magenta
| Color::Cyan
| Color::White
| Color::DarkGray
| Color::LightRed
| Color::LightGreen
| Color::LightYellow
| Color::LightBlue
| Color::LightMagenta
| Color::LightCyan
| Color::LightGray => c,
Color::AnsiValue(n) => match self.color_support {
ColorSupport::Ansi16 => ansi256_to_ansi16(n),
ColorSupport::Ansi256 | ColorSupport::Truecolor => Color::AnsiValue(n),
},
Color::Rgb { r, g, b } => match self.color_support {
ColorSupport::Truecolor => Color::Rgb { r, g, b },
ColorSupport::Ansi256 => Color::AnsiValue(rgb_to_ansi256(r, g, b)),
ColorSupport::Ansi16 => rgb_to_ansi16(r, g, b),
},
}
}
}
pub fn rgb_to_ansi256(r: u8, g: u8, b: u8) -> u8 {
if r == g && g == b {
let v = r as u16;
let idx = ((v.saturating_sub(8)) * 24 / (255 - 8)).min(23) as u8;
return 232 + idx;
}
let r6 = (r as u16 * 5 / 255) as u8;
let g6 = (g as u16 * 5 / 255) as u8;
let b6 = (b as u16 * 5 / 255) as u8;
16 + 36 * r6 + 6 * g6 + b6
}
pub fn ansi256_to_ansi16(n: u8) -> Color {
match n {
0..=7 => match n {
0 => Color::Black,
1 => Color::Red,
2 => Color::Green,
3 => Color::Yellow,
4 => Color::Blue,
5 => Color::Magenta,
6 => Color::Cyan,
_ => Color::White,
},
8..=15 => match n {
8 => Color::DarkGray,
9 => Color::LightRed,
10 => Color::LightGreen,
11 => Color::LightYellow,
12 => Color::LightBlue,
13 => Color::LightMagenta,
14 => Color::LightCyan,
_ => Color::LightGray,
},
_ => {
let (r, g, b) = ansi256_to_rgb(n);
rgb_to_ansi16(r, g, b)
}
}
}
pub fn rgb_to_ansi16(r: u8, g: u8, b: u8) -> Color {
let brightness = (r as u16 + g as u16 + b as u16) / 3;
let max = r.max(g).max(b);
let min = r.min(g).min(b);
if max.saturating_sub(min) < 20 {
return match brightness {
0..=30 => Color::Black,
31..=90 => Color::DarkGray,
91..=180 => Color::LightGray,
_ => Color::White,
};
}
let bright = brightness >= 140;
let base = if r >= g && r >= b {
if g >= b {
Color::Yellow
} else {
Color::Magenta
}
} else if g >= r && g >= b {
if r >= b { Color::Yellow } else { Color::Green }
} else {
if r >= g { Color::Magenta } else { Color::Cyan }
};
if !bright {
return match base {
Color::Red => Color::Red,
Color::Green => Color::Green,
Color::Yellow => Color::Yellow,
Color::Blue => Color::Blue,
Color::Magenta => Color::Magenta,
Color::Cyan => Color::Cyan,
_ => base,
};
}
match base {
Color::Red => Color::LightRed,
Color::Green => Color::LightGreen,
Color::Yellow => Color::LightYellow,
Color::Blue => Color::LightBlue,
Color::Magenta => Color::LightMagenta,
Color::Cyan => Color::LightCyan,
_ => base,
}
}
pub fn ansi256_to_rgb(n: u8) -> (u8, u8, u8) {
match n {
0..=15 => {
const BASIC: [(u8, u8, u8); 16] = [
(0, 0, 0),
(205, 49, 49),
(13, 188, 121),
(229, 229, 16),
(36, 114, 200),
(188, 63, 188),
(17, 168, 205),
(229, 229, 229),
(102, 102, 102),
(241, 76, 76),
(35, 209, 139),
(245, 245, 67),
(59, 142, 234),
(214, 112, 214),
(41, 184, 219),
(255, 255, 255),
];
BASIC[n as usize]
}
16..=231 => {
let idx = n - 16;
let r = idx / 36;
let g = (idx % 36) / 6;
let b = idx % 6;
let to_level = |v: u8| match v {
0 => 0,
1 => 95,
2 => 135,
3 => 175,
4 => 215,
_ => 255,
};
(to_level(r), to_level(g), to_level(b))
}
232..=255 => {
let i = n - 232;
let v = 8 + 10 * i;
(v, v, v)
}
}
}