1use anstyle::{Ansi256Color, AnsiColor, Color, RgbColor};
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum DiffTheme {
14 Dark,
15 Light,
16}
17
18impl DiffTheme {
19 pub fn detect() -> Self {
21 let term = std::env::var("TERM").unwrap_or_default().to_lowercase();
22 if term.contains("light") {
23 Self::Light
24 } else {
25 Self::Dark
26 }
27 }
28
29 pub fn is_light(self) -> bool {
30 self == Self::Light
31 }
32}
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum DiffColorLevel {
37 TrueColor,
38 Ansi256,
39 Ansi16,
40}
41
42impl DiffColorLevel {
43 pub fn detect() -> Self {
45 let colorterm = std::env::var("COLORTERM").unwrap_or_default();
46 let term = std::env::var("TERM").unwrap_or_default();
47
48 if colorterm.contains("truecolor") || colorterm.contains("24bit") {
49 Self::TrueColor
50 } else if term.contains("256") {
51 Self::Ansi256
52 } else {
53 Self::Ansi16
54 }
55 }
56}
57
58const DARK_TC_ADD_LINE_BG: (u8, u8, u8) = (25, 45, 35); const DARK_TC_DEL_LINE_BG: (u8, u8, u8) = (90, 40, 40); const LIGHT_TC_ADD_LINE_BG: (u8, u8, u8) = (215, 240, 215); const LIGHT_TC_DEL_LINE_BG: (u8, u8, u8) = (255, 235, 235); const LIGHT_TC_ADD_NUM_BG: (u8, u8, u8) = (175, 225, 175); const LIGHT_TC_DEL_NUM_BG: (u8, u8, u8) = (250, 210, 210); const LIGHT_TC_GUTTER_FG: (u8, u8, u8) = (25, 25, 25); const DARK_256_ADD_LINE_BG: u8 = 22; const DARK_256_DEL_LINE_BG: u8 = 52; const LIGHT_256_ADD_LINE_BG: u8 = 194; const LIGHT_256_DEL_LINE_BG: u8 = 224; const LIGHT_256_ADD_NUM_BG: u8 = 157; const LIGHT_256_DEL_NUM_BG: u8 = 217; const LIGHT_256_GUTTER_FG: u8 = 236; fn rgb(t: (u8, u8, u8)) -> Color {
87 Color::Rgb(RgbColor(t.0, t.1, t.2))
88}
89
90fn indexed(i: u8) -> Color {
91 Color::Ansi256(Ansi256Color(i))
92}
93
94pub fn diff_add_bg(theme: DiffTheme, level: DiffColorLevel) -> Color {
96 match (theme, level) {
97 (DiffTheme::Dark, DiffColorLevel::TrueColor) => rgb(DARK_TC_ADD_LINE_BG),
98 (DiffTheme::Dark, DiffColorLevel::Ansi256) => indexed(DARK_256_ADD_LINE_BG),
99 (DiffTheme::Dark, DiffColorLevel::Ansi16) => Color::Ansi(AnsiColor::Green),
100 (DiffTheme::Light, DiffColorLevel::TrueColor) => rgb(LIGHT_TC_ADD_LINE_BG),
101 (DiffTheme::Light, DiffColorLevel::Ansi256) => indexed(LIGHT_256_ADD_LINE_BG),
102 (DiffTheme::Light, DiffColorLevel::Ansi16) => Color::Ansi(AnsiColor::BrightGreen),
103 }
104}
105
106pub fn diff_del_bg(theme: DiffTheme, level: DiffColorLevel) -> Color {
108 match (theme, level) {
109 (DiffTheme::Dark, DiffColorLevel::TrueColor) => rgb(DARK_TC_DEL_LINE_BG),
110 (DiffTheme::Dark, DiffColorLevel::Ansi256) => indexed(DARK_256_DEL_LINE_BG),
111 (DiffTheme::Dark, DiffColorLevel::Ansi16) => Color::Ansi(AnsiColor::Red),
112 (DiffTheme::Light, DiffColorLevel::TrueColor) => rgb(LIGHT_TC_DEL_LINE_BG),
113 (DiffTheme::Light, DiffColorLevel::Ansi256) => indexed(LIGHT_256_DEL_LINE_BG),
114 (DiffTheme::Light, DiffColorLevel::Ansi16) => Color::Ansi(AnsiColor::BrightRed),
115 }
116}
117
118pub fn diff_gutter_fg_light(level: DiffColorLevel) -> Color {
120 match level {
121 DiffColorLevel::TrueColor => rgb(LIGHT_TC_GUTTER_FG),
122 DiffColorLevel::Ansi256 => indexed(LIGHT_256_GUTTER_FG),
123 DiffColorLevel::Ansi16 => Color::Ansi(AnsiColor::Black),
124 }
125}
126
127pub fn diff_gutter_bg_add_light(level: DiffColorLevel) -> Color {
129 match level {
130 DiffColorLevel::TrueColor => rgb(LIGHT_TC_ADD_NUM_BG),
131 DiffColorLevel::Ansi256 => indexed(LIGHT_256_ADD_NUM_BG),
132 DiffColorLevel::Ansi16 => Color::Ansi(AnsiColor::BrightGreen),
133 }
134}
135
136pub fn diff_gutter_bg_del_light(level: DiffColorLevel) -> Color {
138 match level {
139 DiffColorLevel::TrueColor => rgb(LIGHT_TC_DEL_NUM_BG),
140 DiffColorLevel::Ansi256 => indexed(LIGHT_256_DEL_NUM_BG),
141 DiffColorLevel::Ansi16 => Color::Ansi(AnsiColor::BrightRed),
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn dark_truecolor_add_bg_is_rgb() {
151 let bg = diff_add_bg(DiffTheme::Dark, DiffColorLevel::TrueColor);
152 assert!(matches!(bg, Color::Rgb(RgbColor(25, 45, 35))));
153 }
154
155 #[test]
156 fn dark_truecolor_del_bg_is_rgb() {
157 let bg = diff_del_bg(DiffTheme::Dark, DiffColorLevel::TrueColor);
158 assert!(matches!(bg, Color::Rgb(RgbColor(90, 40, 40))));
159 }
160
161 #[test]
162 fn light_truecolor_add_bg_is_accessible() {
163 let bg = diff_add_bg(DiffTheme::Light, DiffColorLevel::TrueColor);
164 assert!(matches!(bg, Color::Rgb(RgbColor(215, 240, 215))));
165 }
166
167 #[test]
168 fn light_truecolor_del_bg_is_accessible() {
169 let bg = diff_del_bg(DiffTheme::Light, DiffColorLevel::TrueColor);
170 assert!(matches!(bg, Color::Rgb(RgbColor(255, 235, 235))));
171 }
172
173 #[test]
174 fn dark_256_uses_indexed_colors() {
175 let add = diff_add_bg(DiffTheme::Dark, DiffColorLevel::Ansi256);
176 let del = diff_del_bg(DiffTheme::Dark, DiffColorLevel::Ansi256);
177 assert!(matches!(add, Color::Ansi256(Ansi256Color(22))));
178 assert!(matches!(del, Color::Ansi256(Ansi256Color(52))));
179 }
180
181 #[test]
182 fn dark_ansi16_uses_named_colors() {
183 let add = diff_add_bg(DiffTheme::Dark, DiffColorLevel::Ansi16);
184 let del = diff_del_bg(DiffTheme::Dark, DiffColorLevel::Ansi16);
185 assert_eq!(add, Color::Ansi(AnsiColor::Green));
186 assert_eq!(del, Color::Ansi(AnsiColor::Red));
187 }
188}