theme: add linux system theme sources

This commit is contained in:
2026-10-31 07:57:27 +07:00
parent dacdd84aa4
commit c45f939211
11 changed files with 1187 additions and 10 deletions
+221
View File
@@ -0,0 +1,221 @@
//! Omarchy adapter: reads the current theme's `colors.toml` from the state
//! directory (`~/.local/state/omarchy/current/theme/`). The parse is pure,
//! so fixtures run on every OS; the IO lives behind the Linux cfg.
use gpui::Hsla;
use toml::Table;
use crate::system::{PartialPalette, SourceKind, SystemTheme};
use crate::{AccessibilityPreferences, ThemeVariant};
/// The palette Omarchy deploys with the current theme.
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct OmarchyTheme {
pub(crate) variant: ThemeVariant,
pub(crate) accent: Option<Hsla>,
pub(crate) palette: PartialPalette,
}
impl OmarchyTheme {
pub(crate) fn parse(text: &str) -> Option<Self> {
let table = text.parse::<Table>().ok()?;
let background = hex_color(&table, "background")?;
let foreground = hex_color(&table, "foreground")?;
let variant = table
.get("mode")
.and_then(toml::Value::as_str)
.and_then(mode_variant)
.unwrap_or_else(|| variant_by_lightness(background));
let accent = hex_color(&table, "accent").or_else(|| hex_color(&table, "color4"));
let selection = hex_color(&table, "selection");
let muted_foreground =
hex_color(&table, "dark_foreground").or_else(|| hex_color(&table, "color8"));
let palette = PartialPalette {
background: Some(background),
foreground: Some(foreground),
surface: hex_color(&table, "lighter_background"),
surface_foreground: Some(foreground),
muted_foreground,
destructive: hex_color(&table, "red").or_else(|| hex_color(&table, "color1")),
success: hex_color(&table, "green").or_else(|| hex_color(&table, "color2")),
warning: hex_color(&table, "yellow").or_else(|| hex_color(&table, "orange")),
ring: accent,
selection,
..PartialPalette::default()
};
Some(Self {
variant,
accent,
palette,
})
}
pub(crate) fn into_system(self, accessibility: AccessibilityPreferences) -> SystemTheme {
SystemTheme {
variant: self.variant,
accent: self.accent,
palette: self.palette,
control_radius: None,
input_height: None,
root_font_size: None,
accessibility,
source: SourceKind::Omarchy,
}
}
}
fn mode_variant(mode: &str) -> Option<ThemeVariant> {
match mode {
"dark" => Some(ThemeVariant::Dark),
"light" => Some(ThemeVariant::Light),
_ => None,
}
}
fn variant_by_lightness(background: Hsla) -> ThemeVariant {
if background.l >= 0.5 {
ThemeVariant::Light
} else {
ThemeVariant::Dark
}
}
fn hex_color(table: &Table, key: &str) -> Option<Hsla> {
let value = table.get(key)?.as_str()?;
let digits = value.trim().strip_prefix('#')?;
if digits.len() != 6 {
return None;
}
let hex = u32::from_str_radix(digits, 16).ok()?;
Some(Hsla::from(gpui::rgb(hex)))
}
#[cfg(target_os = "linux")]
pub(crate) mod io {
use std::path::PathBuf;
use super::OmarchyTheme;
fn home_dir() -> Option<PathBuf> {
std::env::var_os("HOME").map(PathBuf::from)
}
/// The directory theme-set swaps on every theme change.
pub(crate) fn current_dir() -> Option<PathBuf> {
Some(home_dir()?.join(".local/state/omarchy/current"))
}
pub(crate) fn read_current() -> Option<OmarchyTheme> {
let text = std::fs::read_to_string(
home_dir()?.join(".local/state/omarchy/current/theme/colors.toml"),
)
.ok()?;
OmarchyTheme::parse(&text)
}
}
#[cfg(test)]
mod tests {
use super::*;
const TOKYO_NIGHT: &str = "\
mode = \"dark\"
accent = \"#7aa2f7\"
selection = \"#292e42\"
muted = \"#414868\"
background = \"#1a1b26\"
dark_background = \"#13141c\"
darker_background = \"#0e0e14\"
lighter_background = \"#24283b\"
foreground = \"#a9b1d6\"
dark_foreground = \"#565f89\"
red = \"#f7768e\"
yellow = \"#e0af68\"
orange = \"#eb927b\"
green = \"#9ece6a\"
";
const ANSI_GENERATED: &str = "\
accent = \"#0060ff\"
selection = \"#c1c8d2\"
background = \"#e8e8ec\"
foreground = \"#251f32\"
color0 = \"#e8e8ec\"
color1 = \"#ab2833\"
color2 = \"#3f7c38\"
color3 = \"#8f6235\"
color4 = \"#0060ff\"
color5 = \"#8d439d\"
color6 = \"#3e7d96\"
color7 = \"#251f32\"
color8 = \"#625e6a\"
color9 = \"#cd3d45\"
color10 = \"#44a540\"
color11 = \"#c18a41\"
color12 = \"#278aff\"
color13 = \"#a659b9\"
color14 = \"#4b9fc0\"
color15 = \"#635d6f\"
";
#[test]
fn semantic_keys_map_onto_roles() {
let theme = OmarchyTheme::parse(TOKYO_NIGHT).expect("tokyo-night parses");
assert_eq!(theme.variant, ThemeVariant::Dark);
assert_eq!(theme.accent, Some(Hsla::from(gpui::rgb(0x7aa2f7))));
let palette = theme.palette;
let background = palette.background.expect("background stated");
assert_eq!(background, Hsla::from(gpui::rgb(0x1a1b26)));
assert_eq!(palette.surface, Some(Hsla::from(gpui::rgb(0x24283b))));
assert_eq!(
palette.muted_foreground,
Some(Hsla::from(gpui::rgb(0x565f89)))
);
assert_eq!(palette.destructive, Some(Hsla::from(gpui::rgb(0xf7768e))));
assert_eq!(palette.success, Some(Hsla::from(gpui::rgb(0x9ece6a))));
assert_eq!(palette.warning, Some(Hsla::from(gpui::rgb(0xe0af68))));
assert_eq!(palette.selection, Some(Hsla::from(gpui::rgb(0x292e42))));
assert_eq!(palette.ring, theme.accent);
}
#[test]
fn ansi_fallback_derives_the_variant_from_the_background() {
let theme = OmarchyTheme::parse(ANSI_GENERATED).expect("ANSI palette parses");
assert_eq!(theme.variant, ThemeVariant::Light);
assert_eq!(theme.accent, Some(Hsla::from(gpui::rgb(0x0060ff))));
assert_eq!(
theme.palette.destructive,
Some(Hsla::from(gpui::rgb(0xab2833)))
);
assert_eq!(
theme.palette.muted_foreground,
Some(Hsla::from(gpui::rgb(0x625e6a)))
);
}
#[test]
fn a_stated_mode_beats_the_derivation() {
let text = "mode = \"light\"\naccent = \"#7aa2f7\"\nbackground = \"#1a1b26\"\nforeground = \"#a9b1d6\"\n";
let theme = OmarchyTheme::parse(text).expect("parses");
assert_eq!(theme.variant, ThemeVariant::Light);
}
#[test]
fn non_toml_text_is_rejected() {
assert!(OmarchyTheme::parse("not a theme").is_none());
}
}