Skip to main content

strop_core/
theme.rs

1//! The one strop-owned color seed (0065 D3). Every surface — TUI render
2//! constants and the terminal's default palette — derives from these
3//! values; nothing restates a hex triple locally. Config-file overrides
4//! are 0005's follow-on; until then this module is the single source.
5
6/// An sRGB triple. Frontend and terminal layers convert to their own
7/// color types; the seed itself stays representation-free.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub struct Rgb {
10    pub r: u8,
11    pub g: u8,
12    pub b: u8,
13}
14
15const fn rgb(r: u8, g: u8, b: u8) -> Rgb {
16    Rgb { r, g, b }
17}
18
19// strop default palette (plan 0004 site, --accent amber)
20pub const BASE: Rgb = rgb(0x16, 0x16, 0x1e);
21pub const TEXT: Rgb = rgb(0xe8, 0xe4, 0xda);
22pub const MUTED: Rgb = rgb(0x6b, 0x6f, 0x7e);
23pub const ACCENT: Rgb = rgb(0xf0, 0xa3, 0x5e);
24/// Accent, dimmed — the peek/preview backdrop.
25pub const PREVIEW_BG: Rgb = rgb(0x4a, 0x33, 0x1c);
26/// Accent, stronger — the substitution flash.
27pub const FLASH_BG: Rgb = rgb(0x6b, 0x47, 0x22);
28pub const SELECT_BG: Rgb = rgb(0x2a, 0x2c, 0x3a);
29/// Matching-delimiter overlay: quiet slate one step above the selection.
30pub const PAIR_BG: Rgb = rgb(0x3a, 0x3d, 0x4d);
31/// Useful secondary context: between TEXT and MUTED.
32pub const SECONDARY: Rgb = rgb(0x9b, 0xa0, 0xb1);
33
34// Diagnostic severity colors (one source for gutter sign and EOL note).
35pub const DIAG_ERROR: Rgb = rgb(0xe8, 0x67, 0x7a);
36pub const DIAG_INFO: Rgb = rgb(0x7f, 0xb4, 0xca);
37
38// Syntax-class colors beyond the base constants above.
39pub const CLASS_KEYWORD: Rgb = rgb(0xc5, 0x8a, 0xe8);
40pub const CLASS_FUNCTION: Rgb = DIAG_INFO;
41pub const CLASS_TYPE: Rgb = rgb(0x94, 0xd2, 0xbd);
42pub const CLASS_STRING: Rgb = rgb(0xa9, 0xc4, 0x7c);
43pub const CLASS_NUMBER: Rgb = rgb(0xe8, 0x97, 0x7a);
44pub const CLASS_OPERATOR: Rgb = rgb(0x9a, 0xa0, 0xae);
45pub const CLASS_PUNCTUATION: Rgb = rgb(0x56, 0x5b, 0x6e);
46pub const CLASS_ATTRIBUTE: Rgb = rgb(0xd0, 0xa4, 0x5e);
47
48/// The terminal's default 16-color set, harmonized with the palette
49/// above: red is the diagnostic error, yellow the held-back amber (its
50/// bright is the accent), blue the diagnostic info, magenta the keyword
51/// violet, cyan the type teal, white a step under TEXT. `ls`, `git` and
52/// prompt themes all draw from these indices, so they must speak the
53/// same language as the chrome around them.
54pub const ANSI16: [Rgb; 16] = [
55    rgb(0x10, 0x10, 0x18), // black: one step below BASE
56    DIAG_ERROR,            // red
57    CLASS_STRING,          // green
58    rgb(0xe0, 0xaf, 0x68), // yellow: the held-back amber
59    DIAG_INFO,             // blue
60    CLASS_KEYWORD,         // magenta
61    CLASS_TYPE,            // cyan
62    rgb(0xcf, 0xcb, 0xc0), // white: a step under TEXT
63    CLASS_PUNCTUATION,     // bright black
64    rgb(0xf2, 0x88, 0x96), // bright red
65    rgb(0xbc, 0xd7, 0x95), // bright green
66    ACCENT,                // bright yellow: the accent itself
67    rgb(0x97, 0xc8, 0xdc), // bright blue
68    rgb(0xd3, 0xa4, 0xee), // bright magenta
69    rgb(0xab, 0xe2, 0xd0), // bright cyan
70    TEXT,                  // bright white
71];
72
73/// One cube/grayscale component: the standard xterm ramp.
74const fn cube_level(index: u8) -> u8 {
75    if index == 0 {
76        0
77    } else {
78        55 + 40 * index
79    }
80}
81
82/// The full 256-index default palette: ANSI16 harmonized by the seed,
83/// then the standard 6×6×6 color cube and grayscale ramp unchanged —
84/// programs addressing those indices expect the conventional ramp.
85pub const fn ansi256() -> [Rgb; 256] {
86    let mut colors = [rgb(0, 0, 0); 256];
87    let mut index = 0;
88    while index < 16 {
89        colors[index] = ANSI16[index];
90        index += 1;
91    }
92    while index < 232 {
93        let offset = index - 16;
94        colors[index] = rgb(
95            cube_level((offset / 36) as u8),
96            cube_level(((offset % 36) / 6) as u8),
97            cube_level((offset % 6) as u8),
98        );
99        index += 1;
100    }
101    while index < 256 {
102        let level = 8 + 10 * (index - 232) as u8;
103        colors[index] = rgb(level, level, level);
104        index += 1;
105    }
106    colors
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn ansi256_plants_the_seed_and_the_standard_ramps() {
115        let colors = ansi256();
116        assert_eq!(&colors[..16], &ANSI16);
117        // cube corners: black, pure red (5·36 into the cube), white
118        assert_eq!(colors[16], rgb(0, 0, 0));
119        assert_eq!(colors[196], rgb(255, 0, 0));
120        assert_eq!(colors[231], rgb(255, 255, 255));
121        // grayscale bounds
122        assert_eq!(colors[232], rgb(8, 8, 8));
123        assert_eq!(colors[255], rgb(238, 238, 238));
124    }
125}