| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
use std::path::PathBuf; |
| 9 |
|
| 10 |
use alloy_tui::Theme; |
| 11 |
use anyhow::{Context, Result}; |
| 12 |
|
| 13 |
|
| 14 |
pub const DEFAULT_LIGHT: &str = "akari-dawn"; |
| 15 |
|
| 16 |
|
| 17 |
pub const DEFAULT_DARK: &str = "akari-night"; |
| 18 |
|
| 19 |
|
| 20 |
|
| 21 |
|
| 22 |
fn search_path() -> Vec<(PathBuf, bool)> { |
| 23 |
let mut dirs = Vec::new(); |
| 24 |
|
| 25 |
if let Some(config) = dirs_config_home() { |
| 26 |
dirs.push((config.join("alloy").join("themes"), true)); |
| 27 |
} |
| 28 |
dirs.push((PathBuf::from("/usr/share/alloy/themes"), false)); |
| 29 |
|
| 30 |
|
| 31 |
|
| 32 |
|
| 33 |
|
| 34 |
if let Some(bundled) = makeover::bundled_themes_dir() { |
| 35 |
dirs.push((bundled, false)); |
| 36 |
} |
| 37 |
|
| 38 |
dirs |
| 39 |
} |
| 40 |
|
| 41 |
fn dirs_config_home() -> Option<PathBuf> { |
| 42 |
|
| 43 |
|
| 44 |
if let Some(xdg) = std::env::var_os("XDG_CONFIG_HOME") { |
| 45 |
let path = PathBuf::from(xdg); |
| 46 |
if path.is_absolute() { |
| 47 |
return Some(path); |
| 48 |
} |
| 49 |
} |
| 50 |
std::env::var_os("HOME").map(|home| PathBuf::from(home).join(".config")) |
| 51 |
} |
| 52 |
|
| 53 |
|
| 54 |
pub fn load(id: Option<&str>) -> Result<Theme> { |
| 55 |
let id = id.map(str::to_string).unwrap_or_else(default_theme_id); |
| 56 |
let dirs = search_path(); |
| 57 |
|
| 58 |
let colors = makeover::load_theme(&dirs, &id) |
| 59 |
.map_err(anyhow::Error::msg) |
| 60 |
.with_context(|| { |
| 61 |
let searched: Vec<String> = dirs |
| 62 |
.iter() |
| 63 |
.map(|(path, _)| path.display().to_string()) |
| 64 |
.collect(); |
| 65 |
format!("loading theme `{id}` (searched: {})", searched.join(", ")) |
| 66 |
})?; |
| 67 |
|
| 68 |
Theme::from_theme(&colors).with_context(|| format!("theme `{id}` is incomplete")) |
| 69 |
} |
| 70 |
|
| 71 |
|
| 72 |
|
| 73 |
|
| 74 |
|
| 75 |
|
| 76 |
|
| 77 |
|
| 78 |
fn default_theme_id() -> String { |
| 79 |
let dark = std::env::var("COLORFGBG") |
| 80 |
.ok() |
| 81 |
.and_then(|value| { |
| 82 |
value |
| 83 |
.rsplit(';') |
| 84 |
.next() |
| 85 |
.and_then(|bg| bg.trim().parse::<u8>().ok()) |
| 86 |
}) |
| 87 |
.is_some_and(|bg| bg <= 6 || bg == 8); |
| 88 |
|
| 89 |
if dark { DEFAULT_DARK.into() } else { DEFAULT_LIGHT.into() } |
| 90 |
} |
| 91 |
|
| 92 |
#[cfg(test)] |
| 93 |
mod tests { |
| 94 |
use super::*; |
| 95 |
|
| 96 |
|
| 97 |
|
| 98 |
#[test] |
| 99 |
fn shipped_defaults_load_and_resolve() { |
| 100 |
for id in [DEFAULT_LIGHT, DEFAULT_DARK] { |
| 101 |
let theme = load(Some(id)); |
| 102 |
assert!(theme.is_ok(), "default theme `{id}` failed to load: {theme:?}"); |
| 103 |
} |
| 104 |
} |
| 105 |
|
| 106 |
#[test] |
| 107 |
fn a_missing_theme_is_an_error_not_a_fallback() { |
| 108 |
assert!(load(Some("no-such-theme")).is_err()); |
| 109 |
} |
| 110 |
} |
| 111 |
|