Skip to main content

declutter/
palette.rs

1use ratatui::style::{Color, Modifier, Style};
2
3use crate::highlight::Class;
4
5/// The viewer's colours, tuned for a dark terminal: a quiet tint behind changed lines,
6/// a stronger one behind the words that changed, and only four syntax colours — none of
7/// them loud — so the diff, not the syntax, is what stands out.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct Palette {
10    pub removed: Style,
11    pub added: Style,
12    pub removed_emphasis: Style,
13    pub added_emphasis: Style,
14    pub moved_away: Style,
15    pub moved_here: Style,
16    pub removed_sign: Color,
17    pub added_sign: Color,
18    pub moved_sign: Color,
19    pub keyword: Color,
20    pub string: Color,
21    pub comment: Color,
22    pub number: Color,
23    pub search: Style,
24}
25
26const fn rgb(hex: u32) -> Color {
27    Color::Rgb((hex >> 16) as u8, (hex >> 8) as u8, hex as u8)
28}
29
30impl Palette {
31    /// For terminals with 24-bit colour, which allows tints dark enough to sit quietly
32    /// behind syntax colours.
33    pub const TRUE_COLOR: Palette = Palette {
34        removed: Style::new().bg(rgb(0x2d1b1f)),
35        added: Style::new().bg(rgb(0x1a2b20)),
36        removed_emphasis: Style::new().bg(rgb(0x5c2b33)),
37        added_emphasis: Style::new().bg(rgb(0x2b5237)),
38        moved_away: Style::new().bg(rgb(0x262033)),
39        moved_here: Style::new().bg(rgb(0x1b2a33)),
40        removed_sign: rgb(0xe0707a),
41        added_sign: rgb(0x7fc48a),
42        moved_sign: rgb(0x8fb3d9),
43        keyword: rgb(0xc2a8dc),
44        string: rgb(0xd2b98c),
45        comment: rgb(0x8c939e),
46        number: rgb(0xd9a07c),
47        search: Style::new().fg(Color::Black).bg(rgb(0xd7b84a)),
48    };
49
50    /// For 256-colour terminals. Their darkest reds and greens are too strong to fill a
51    /// whole line, so changed lines get no tint — removed ones are dimmed instead — and
52    /// only the changed words get a background.
53    pub const COLOR_256: Palette = Palette {
54        removed: Style::new().add_modifier(Modifier::DIM),
55        added: Style::new(),
56        removed_emphasis: Style::new().bg(Color::Indexed(52)),
57        added_emphasis: Style::new().bg(Color::Indexed(22)),
58        moved_away: Style::new().add_modifier(Modifier::DIM),
59        moved_here: Style::new(),
60        removed_sign: Color::Indexed(167),
61        added_sign: Color::Indexed(114),
62        moved_sign: Color::Indexed(110),
63        keyword: Color::Indexed(140),
64        string: Color::Indexed(180),
65        comment: Color::Indexed(245),
66        number: Color::Indexed(173),
67        search: Style::new().fg(Color::Black).bg(Color::Indexed(179)),
68    };
69
70    /// True colour when the terminal says it has it (`COLORTERM`), 256 colours otherwise.
71    pub fn detect() -> Palette {
72        match std::env::var("COLORTERM").as_deref() {
73            Ok("truecolor" | "24bit") => Palette::TRUE_COLOR,
74            _ => Palette::COLOR_256,
75        }
76    }
77
78    /// The colour of a syntax class, if it gets one: types, functions and attributes are
79    /// left in the terminal's own text colour.
80    pub fn syntax(&self, class: Class) -> Option<Style> {
81        let fg = match class {
82            Class::Keyword => self.keyword,
83            Class::String => self.string,
84            Class::Number | Class::Constant => self.number,
85            Class::Comment => {
86                return Some(Style::new().fg(self.comment).add_modifier(Modifier::ITALIC));
87            }
88            Class::Type | Class::Function | Class::Attribute => return None,
89        };
90        Some(Style::new().fg(fg))
91    }
92}