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