| 72 |
72 |
|
use anyhow::{Context, Result, anyhow, bail};
|
| 73 |
73 |
|
use makeover::{Rgb, ThemeColors};
|
| 74 |
74 |
|
|
| 75 |
|
- |
/// The twelve chromatic ANSI slots, as the intents they are painted with.
|
| 76 |
|
- |
///
|
| 77 |
|
- |
/// One table, because there were two and they disagreed. `kargs.d/10-alloy.toml`
|
| 78 |
|
- |
/// carried this mapping for the Linux console and claimed in a comment that it
|
| 79 |
|
- |
/// "matches `etc/skel/.config/rio/config.toml`'s `[colors]` block, so a terminal
|
| 80 |
|
- |
/// and a bare VT agree on what red means". Three slots did not match: rio put
|
| 81 |
|
- |
/// `surface.raised` at 7 where the console had `surface.page`, `content.secondary`
|
| 82 |
|
- |
/// at 14 where the console had `category.six`, and `content.primary` at 15 where
|
| 83 |
|
- |
/// the console had `surface.raised`.
|
| 84 |
|
- |
///
|
| 85 |
|
- |
/// Two of those three are achromatic and come from [`achromatic_slot`] instead;
|
| 86 |
|
- |
/// the third, slot 14, is settled here as `category.six`, which is what both
|
| 87 |
|
- |
/// the console and `vtrgb.py` had and only rio disagreed with. Rio renders from
|
| 88 |
|
- |
/// this table now, which makes the kargs comment's claim structural rather than
|
| 89 |
|
- |
/// aspirational.
|
| 90 |
|
- |
///
|
| 91 |
|
- |
/// Indexed 1-6 and 9-14. The hues do not depend on whether the theme is light
|
| 92 |
|
- |
/// or dark — red is the theme's danger tone either way — which is exactly why
|
| 93 |
|
- |
/// the four achromatic slots are not in this table.
|
| 94 |
|
- |
const CHROMATIC: [(usize, &str); 12] = [
|
| 95 |
|
- |
(1, "status.danger"),
|
| 96 |
|
- |
(2, "status.success"),
|
| 97 |
|
- |
(3, "status.warning"),
|
| 98 |
|
- |
(4, "status.info"),
|
| 99 |
|
- |
(5, "category.five"),
|
| 100 |
|
- |
(6, "category.six"),
|
| 101 |
|
- |
(9, "action.primary"), // bright red — the theme's warm accent
|
| 102 |
|
- |
(10, "status.success"),
|
| 103 |
|
- |
(11, "status.warning"),
|
| 104 |
|
- |
(12, "status.info"),
|
| 105 |
|
- |
(13, "category.five"),
|
| 106 |
|
- |
(14, "category.six"),
|
| 107 |
|
- |
];
|
| 108 |
|
- |
|
| 109 |
|
- |
/// The four achromatic slots — 0, 7, 8, 15 — which invert with the theme.
|
| 110 |
|
- |
///
|
| 111 |
|
- |
/// These are the slots a naive table gets wrong. ANSI 0 is "black" and 7 is
|
| 112 |
|
- |
/// "white", but what a terminal actually wants there is *the darkest tone* and
|
| 113 |
|
- |
/// *the lightest tone*, and which intent that is flips with the theme's
|
| 114 |
|
- |
/// polarity. A light theme's darkest tone is its ink; a dark theme's is its
|
| 115 |
|
- |
/// deepest surface. Pinning slot 0 to `content.primary` reads correctly on
|
| 116 |
|
- |
/// Akari Dawn and inverts on Akari Night, where it would hand the console a
|
| 117 |
|
- |
/// pale cream as "black".
|
| 118 |
|
- |
///
|
| 119 |
|
- |
/// Not a hypothetical: the two hand-authored Helix themes already did it this
|
| 120 |
|
- |
/// way, dawn putting its ink at `black` and night putting its sunken surface
|
| 121 |
|
- |
/// there. That divergence was the evidence the rule exists.
|
| 122 |
|
- |
///
|
| 123 |
|
- |
/// The arrangement below is `tools/vtrgb.py`'s, which had it right and is the
|
| 124 |
|
- |
/// reason that script no longer exists. There turned out to be *three* copies
|
| 125 |
|
- |
/// of this table, not the two the kargs comment admitted to, and all three
|
| 126 |
|
- |
/// disagreed:
|
| 127 |
|
- |
///
|
| 128 |
|
- |
/// - `vtrgb.py` (light): 7 = `surface.raised`, 15 = `surface.overlay`
|
| 129 |
|
- |
/// - `rio`'s `[colors]`: 7 = `surface.raised`, 15 = `content.primary`
|
| 130 |
|
- |
/// - `kargs.d` (light): 7 = `surface.page`, 15 = `surface.raised`
|
| 131 |
|
- |
///
|
| 132 |
|
- |
/// vtrgb.py wins on two grounds. It was the only one that varied by polarity at
|
| 133 |
|
- |
/// all, and it was the only one carrying a reason: slot 7 is *the login card*,
|
| 134 |
|
- |
/// so the greeter reads as a light card on a darker field, and slot 0 — which
|
| 135 |
|
- |
/// the kernel also uses as the console background — stays the darkest tone in
|
| 136 |
|
- |
/// either mode. It is also the copy that decided the running system, since
|
| 137 |
|
- |
/// `alloy-vtrgb.service` applies its table after the kernel has applied kargs'.
|
| 138 |
|
- |
fn achromatic_slot(index: usize, variant: &str) -> Option<&'static str> {
|
| 139 |
|
- |
// Anything that is not `dark` — including `high-contrast` — follows the
|
| 140 |
|
- |
// light anchors, as vtrgb.py did.
|
| 141 |
|
- |
let dark = variant == "dark";
|
| 142 |
|
- |
Some(match (index, dark) {
|
| 143 |
|
- |
(0, false) => "content.primary", // darkest text tone
|
| 144 |
|
- |
(0, true) => "surface.sunken", // darkest surface
|
| 145 |
|
- |
(7, false) => "surface.raised", // the login card
|
| 146 |
|
- |
(7, true) => "content.secondary", // a readable light tone
|
| 147 |
|
- |
(8, _) => "content.muted", // muted chrome, either way
|
| 148 |
|
- |
(15, false) => "surface.overlay", // lightest surface
|
| 149 |
|
- |
(15, true) => "content.primary", // lightest text tone
|
| 150 |
|
- |
_ => return None,
|
| 151 |
|
- |
})
|
| 152 |
|
- |
}
|
| 153 |
|
- |
|
| 154 |
75 |
|
/// The intent painting ANSI slot `index` under a theme of `variant`.
|
| 155 |
|
- |
pub fn ansi_intent(index: usize, variant: &str) -> Option<&'static str> {
|
| 156 |
|
- |
achromatic_slot(index, variant).or_else(|| {
|
| 157 |
|
- |
CHROMATIC
|
| 158 |
|
- |
.iter()
|
| 159 |
|
- |
.find(|(slot, _)| *slot == index)
|
| 160 |
|
- |
.map(|(_, intent)| *intent)
|
| 161 |
|
- |
})
|
| 162 |
|
- |
}
|
|
76 |
+ |
///
|
|
77 |
+ |
/// The table itself is `makeover::ansi_intent` as of makeover 2.4.0. It was
|
|
78 |
+ |
/// written here, because this crate is where three disagreeing hand-maintained
|
|
79 |
+ |
/// copies were folded into one: `kargs.d/10-alloy.toml` for the Linux console,
|
|
80 |
+ |
/// rio's `[colors]` block, and `tools/vtrgb.py` for the greeter, which was the
|
|
81 |
+ |
/// copy that had it right and no longer exists. Three slots differed between
|
|
82 |
+ |
/// them, two achromatic and one chromatic (14, settled as `category.six`).
|
|
83 |
+ |
///
|
|
84 |
+ |
/// It moved out on 2026-07-31 for the reason it was collected in the first
|
|
85 |
+ |
/// place. `shop`, which replaces rio as Alloy's terminal, paints its palette at
|
|
86 |
+ |
/// runtime from a theme rather than reading a generated config, so it needs the
|
|
87 |
+ |
/// mapping as code and not as rendered hex. Copying it into shop would have
|
|
88 |
+ |
/// made a fourth copy of the table this crate exists to have prevented.
|
|
89 |
+ |
///
|
|
90 |
+ |
/// Kept as a re-export rather than deleted: templates reach it through
|
|
91 |
+ |
/// `@{ansi.N}` and [`Palette::ansi`], and the callers here read better naming
|
|
92 |
+ |
/// the crate that owns the rest of the skeleton.
|
|
93 |
+ |
pub use makeover::ansi_intent;
|
| 163 |
94 |
|
|
| 164 |
95 |
|
/// Every color a template can name, resolved from one theme.
|
| 165 |
96 |
|
///
|