use std::sync::LazyLock;
use nu_ansi_term::Style;
use super::ui_styles::UiStyles;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum ThemeFamily {
FileKinds,
Permissions,
Size,
Users,
Links,
Vcs,
Punctuation,
DateBulk,
DateModified,
DateAccessed,
DateChanged,
DateCreated,
Columns,
Symlinks,
}
pub enum StyleAccess {
Direct {
get: fn(&UiStyles) -> Style,
set: fn(&mut UiStyles, Style),
},
Bulk { set: fn(&mut UiStyles, Style) },
}
pub struct ThemeKeyDef {
pub name: &'static str,
pub aliases: &'static [&'static str],
pub family: ThemeFamily,
pub access: StyleAccess,
}
impl ThemeKeyDef {
pub fn from_name(name: &str) -> Option<&'static ThemeKeyDef> {
THEME_KEY_REGISTRY
.iter()
.find(|d| d.name == name || d.aliases.contains(&name))
}
pub fn dumpable() -> impl Iterator<Item = &'static ThemeKeyDef> {
THEME_KEY_REGISTRY
.iter()
.filter(|d| matches!(d.access, StyleAccess::Direct { .. }))
}
}
pub static THEME_KEY_REGISTRY: LazyLock<Vec<ThemeKeyDef>> = LazyLock::new(build_registry);
fn direct(
name: &'static str,
aliases: &'static [&'static str],
family: ThemeFamily,
get: fn(&UiStyles) -> Style,
set: fn(&mut UiStyles, Style),
) -> ThemeKeyDef {
ThemeKeyDef {
name,
aliases,
family,
access: StyleAccess::Direct { get, set },
}
}
fn bulk(name: &'static str, family: ThemeFamily, set: fn(&mut UiStyles, Style)) -> ThemeKeyDef {
ThemeKeyDef {
name,
aliases: &[],
family,
access: StyleAccess::Bulk { set },
}
}
#[rustfmt::skip]
fn build_registry() -> Vec<ThemeKeyDef> {
let mut r = Vec::with_capacity(120);
file_kinds(&mut r);
permissions(&mut r);
size(&mut r);
users(&mut r);
links(&mut r);
vcs(&mut r);
punctuation(&mut r);
date(&mut r);
columns(&mut r);
symlinks(&mut r);
r
}
#[rustfmt::skip]
fn file_kinds(r: &mut Vec<ThemeKeyDef>) {
use ThemeFamily::FileKinds as F;
r.push(direct("normal", &[], F, |u| u.filekinds.normal, |u, s| u.filekinds.normal = s));
r.push(direct("directory", &[], F, |u| u.filekinds.directory, |u, s| u.filekinds.directory = s));
r.push(direct("symlink", &[], F, |u| u.filekinds.symlink, |u, s| u.filekinds.symlink = s));
r.push(direct("pipe", &[], F, |u| u.filekinds.pipe, |u, s| u.filekinds.pipe = s));
r.push(direct("block-device", &[], F, |u| u.filekinds.block_device, |u, s| u.filekinds.block_device = s));
r.push(direct("char-device", &[], F, |u| u.filekinds.char_device, |u, s| u.filekinds.char_device = s));
r.push(direct("socket", &[], F, |u| u.filekinds.socket, |u, s| u.filekinds.socket = s));
r.push(direct("special", &[], F, |u| u.filekinds.special, |u, s| u.filekinds.special = s));
r.push(direct("executable", &[], F, |u| u.filekinds.executable, |u, s| u.filekinds.executable = s));
}
#[rustfmt::skip]
fn permissions(r: &mut Vec<ThemeKeyDef>) {
use ThemeFamily::Permissions as F;
r.push(direct("permissions-user-read", &["perm-user-read", "mode-user-read"], F,
|u| u.perms.user_read, |u, s| u.perms.user_read = s));
r.push(direct("permissions-user-write", &["perm-user-write", "mode-user-write"], F,
|u| u.perms.user_write, |u, s| u.perms.user_write = s));
r.push(direct("permissions-user-execute", &["perm-user-exec", "mode-user-exec"], F,
|u| u.perms.user_execute_file, |u, s| u.perms.user_execute_file = s));
r.push(direct("permissions-user-execute-other", &["perm-user-exec-other", "mode-user-exec-other"], F,
|u| u.perms.user_execute_other, |u, s| u.perms.user_execute_other = s));
r.push(direct("permissions-group-read", &["perm-group-read", "mode-group-read"], F,
|u| u.perms.group_read, |u, s| u.perms.group_read = s));
r.push(direct("permissions-group-write", &["perm-group-write", "mode-group-write"], F,
|u| u.perms.group_write, |u, s| u.perms.group_write = s));
r.push(direct("permissions-group-execute", &["perm-group-exec", "mode-group-exec"], F,
|u| u.perms.group_execute, |u, s| u.perms.group_execute = s));
r.push(direct("permissions-other-read", &["perm-other-read", "mode-other-read"], F,
|u| u.perms.other_read, |u, s| u.perms.other_read = s));
r.push(direct("permissions-other-write", &["perm-other-write", "mode-other-write"], F,
|u| u.perms.other_write, |u, s| u.perms.other_write = s));
r.push(direct("permissions-other-execute", &["perm-other-exec", "mode-other-exec"], F,
|u| u.perms.other_execute, |u, s| u.perms.other_execute = s));
r.push(direct("permissions-special-user", &["perm-special-user", "mode-special-user"], F,
|u| u.perms.special_user_file, |u, s| u.perms.special_user_file = s));
r.push(direct("permissions-special-other", &["perm-special-other", "mode-special-other"], F,
|u| u.perms.special_other, |u, s| u.perms.special_other = s));
r.push(direct("permissions-attribute", &["perm-attribute", "mode-attribute"], F,
|u| u.perms.attribute, |u, s| u.perms.attribute = s));
}
#[rustfmt::skip]
fn size(r: &mut Vec<ThemeKeyDef>) {
use ThemeFamily::Size as F;
r.push(bulk("size-number", F, UiStyles::set_number_style));
r.push(bulk("size-unit", F, UiStyles::set_unit_style));
r.push(direct("size-major", &[], F, |u| u.size.major, |u, s| u.size.major = s));
r.push(direct("size-minor", &[], F, |u| u.size.minor, |u, s| u.size.minor = s));
r.push(direct("size-number-byte", &[], F, |u| u.size.number_byte, |u, s| u.size.number_byte = s));
r.push(direct("size-number-kilo", &[], F, |u| u.size.number_kilo, |u, s| u.size.number_kilo = s));
r.push(direct("size-number-mega", &[], F, |u| u.size.number_mega, |u, s| u.size.number_mega = s));
r.push(direct("size-number-giga", &[], F, |u| u.size.number_giga, |u, s| u.size.number_giga = s));
r.push(direct("size-number-huge", &[], F, |u| u.size.number_huge, |u, s| u.size.number_huge = s));
r.push(direct("size-unit-byte", &[], F, |u| u.size.unit_byte, |u, s| u.size.unit_byte = s));
r.push(direct("size-unit-kilo", &[], F, |u| u.size.unit_kilo, |u, s| u.size.unit_kilo = s));
r.push(direct("size-unit-mega", &[], F, |u| u.size.unit_mega, |u, s| u.size.unit_mega = s));
r.push(direct("size-unit-giga", &[], F, |u| u.size.unit_giga, |u, s| u.size.unit_giga = s));
r.push(direct("size-unit-huge", &[], F, |u| u.size.unit_huge, |u, s| u.size.unit_huge = s));
}
#[rustfmt::skip]
fn users(r: &mut Vec<ThemeKeyDef>) {
use ThemeFamily::Users as F;
r.push(direct("user-you", &[], F, |u| u.users.user_you, |u, s| u.users.user_you = s));
r.push(direct("user-other", &[], F, |u| u.users.user_someone_else, |u, s| u.users.user_someone_else = s));
r.push(direct("group-yours", &[], F, |u| u.users.group_yours, |u, s| u.users.group_yours = s));
r.push(direct("group-member", &[], F, |u| u.users.group_member, |u, s| u.users.group_member = s));
r.push(direct("group-other", &[], F, |u| u.users.group_not_yours, |u, s| u.users.group_not_yours = s));
r.push(direct("uid-you", &[], F, |u| u.users.uid_you, |u, s| u.users.uid_you = s));
r.push(direct("uid-other", &[], F, |u| u.users.uid_someone_else, |u, s| u.users.uid_someone_else = s));
r.push(direct("gid-yours", &[], F, |u| u.users.gid_yours, |u, s| u.users.gid_yours = s));
r.push(direct("gid-member", &[], F, |u| u.users.gid_member, |u, s| u.users.gid_member = s));
r.push(direct("gid-other", &[], F, |u| u.users.gid_not_yours, |u, s| u.users.gid_not_yours = s));
}
#[rustfmt::skip]
fn links(r: &mut Vec<ThemeKeyDef>) {
use ThemeFamily::Links as F;
r.push(direct("links", &[], F, |u| u.links.normal, |u, s| u.links.normal = s));
r.push(direct("links-multi", &[], F, |u| u.links.multi_link_file, |u, s| u.links.multi_link_file = s));
}
#[rustfmt::skip]
fn vcs(r: &mut Vec<ThemeKeyDef>) {
use ThemeFamily::Vcs as F;
r.push(direct("vcs-new", &[], F, |u| u.vcs.new, |u, s| u.vcs.new = s));
r.push(direct("vcs-modified", &[], F, |u| u.vcs.modified, |u, s| u.vcs.modified = s));
r.push(direct("vcs-deleted", &[], F, |u| u.vcs.deleted, |u, s| u.vcs.deleted = s));
r.push(direct("vcs-renamed", &[], F, |u| u.vcs.renamed, |u, s| u.vcs.renamed = s));
r.push(direct("vcs-typechange", &[], F, |u| u.vcs.typechange, |u, s| u.vcs.typechange = s));
r.push(direct("vcs-ignored", &[], F, |u| u.vcs.ignored, |u, s| u.vcs.ignored = s));
r.push(direct("vcs-conflicted", &[], F, |u| u.vcs.conflicted, |u, s| u.vcs.conflicted = s));
}
#[rustfmt::skip]
fn punctuation(r: &mut Vec<ThemeKeyDef>) {
use ThemeFamily::Punctuation as F;
r.push(direct("punctuation", &[], F, |u| u.punctuation, |u, s| u.punctuation = s));
}
#[rustfmt::skip]
fn date(r: &mut Vec<ThemeKeyDef>) {
use ThemeFamily::{DateBulk, DateModified, DateAccessed, DateChanged, DateCreated};
r.push(bulk("date", DateBulk, |u, s| u.date_for_each(|d| d.set_all(s))));
r.push(bulk("date-now", DateBulk, |u, s| u.date_for_each(|d| d.now = s)));
r.push(bulk("date-today", DateBulk, |u, s| u.date_for_each(|d| d.today = s)));
r.push(bulk("date-week", DateBulk, |u, s| u.date_for_each(|d| d.week = s)));
r.push(bulk("date-month", DateBulk, |u, s| u.date_for_each(|d| d.month = s)));
r.push(bulk("date-year", DateBulk, |u, s| u.date_for_each(|d| d.year = s)));
r.push(bulk("date-old", DateBulk, |u, s| u.date_for_each(|d| d.old = s)));
r.push(bulk("date-flat", DateBulk, |u, s| u.date_for_each(|d| d.flat = s)));
r.push(bulk("date-modified", DateModified, |u, s| u.date_modified.set_all(s)));
r.push(direct("date-modified-now", &[], DateModified, |u| u.date_modified.now, |u, s| u.date_modified.now = s));
r.push(direct("date-modified-today", &[], DateModified, |u| u.date_modified.today, |u, s| u.date_modified.today = s));
r.push(direct("date-modified-week", &[], DateModified, |u| u.date_modified.week, |u, s| u.date_modified.week = s));
r.push(direct("date-modified-month", &[], DateModified, |u| u.date_modified.month, |u, s| u.date_modified.month = s));
r.push(direct("date-modified-year", &[], DateModified, |u| u.date_modified.year, |u, s| u.date_modified.year = s));
r.push(direct("date-modified-old", &[], DateModified, |u| u.date_modified.old, |u, s| u.date_modified.old = s));
r.push(direct("date-modified-flat", &[], DateModified, |u| u.date_modified.flat, |u, s| u.date_modified.flat = s));
r.push(bulk("date-accessed", DateAccessed, |u, s| u.date_accessed.set_all(s)));
r.push(direct("date-accessed-now", &[], DateAccessed, |u| u.date_accessed.now, |u, s| u.date_accessed.now = s));
r.push(direct("date-accessed-today", &[], DateAccessed, |u| u.date_accessed.today, |u, s| u.date_accessed.today = s));
r.push(direct("date-accessed-week", &[], DateAccessed, |u| u.date_accessed.week, |u, s| u.date_accessed.week = s));
r.push(direct("date-accessed-month", &[], DateAccessed, |u| u.date_accessed.month, |u, s| u.date_accessed.month = s));
r.push(direct("date-accessed-year", &[], DateAccessed, |u| u.date_accessed.year, |u, s| u.date_accessed.year = s));
r.push(direct("date-accessed-old", &[], DateAccessed, |u| u.date_accessed.old, |u, s| u.date_accessed.old = s));
r.push(direct("date-accessed-flat", &[], DateAccessed, |u| u.date_accessed.flat, |u, s| u.date_accessed.flat = s));
r.push(bulk("date-changed", DateChanged, |u, s| u.date_changed.set_all(s)));
r.push(direct("date-changed-now", &[], DateChanged, |u| u.date_changed.now, |u, s| u.date_changed.now = s));
r.push(direct("date-changed-today", &[], DateChanged, |u| u.date_changed.today, |u, s| u.date_changed.today = s));
r.push(direct("date-changed-week", &[], DateChanged, |u| u.date_changed.week, |u, s| u.date_changed.week = s));
r.push(direct("date-changed-month", &[], DateChanged, |u| u.date_changed.month, |u, s| u.date_changed.month = s));
r.push(direct("date-changed-year", &[], DateChanged, |u| u.date_changed.year, |u, s| u.date_changed.year = s));
r.push(direct("date-changed-old", &[], DateChanged, |u| u.date_changed.old, |u, s| u.date_changed.old = s));
r.push(direct("date-changed-flat", &[], DateChanged, |u| u.date_changed.flat, |u, s| u.date_changed.flat = s));
r.push(bulk("date-created", DateCreated, |u, s| u.date_created.set_all(s)));
r.push(direct("date-created-now", &[], DateCreated, |u| u.date_created.now, |u, s| u.date_created.now = s));
r.push(direct("date-created-today", &[], DateCreated, |u| u.date_created.today, |u, s| u.date_created.today = s));
r.push(direct("date-created-week", &[], DateCreated, |u| u.date_created.week, |u, s| u.date_created.week = s));
r.push(direct("date-created-month", &[], DateCreated, |u| u.date_created.month, |u, s| u.date_created.month = s));
r.push(direct("date-created-year", &[], DateCreated, |u| u.date_created.year, |u, s| u.date_created.year = s));
r.push(direct("date-created-old", &[], DateCreated, |u| u.date_created.old, |u, s| u.date_created.old = s));
r.push(direct("date-created-flat", &[], DateCreated, |u| u.date_created.flat, |u, s| u.date_created.flat = s));
}
#[rustfmt::skip]
fn columns(r: &mut Vec<ThemeKeyDef>) {
use ThemeFamily::Columns as F;
r.push(direct("inode", &[], F, |u| u.inode, |u, s| u.inode = s));
r.push(direct("blocks", &[], F, |u| u.blocks, |u, s| u.blocks = s));
r.push(direct("header", &[], F, |u| u.header, |u, s| u.header = s));
r.push(direct("octal", &[], F, |u| u.octal, |u, s| u.octal = s));
r.push(direct("flags", &[], F, |u| u.flags, |u, s| u.flags = s));
}
#[rustfmt::skip]
fn symlinks(r: &mut Vec<ThemeKeyDef>) {
use ThemeFamily::Symlinks as F;
r.push(direct("symlink-path", &[], F, |u| u.symlink_path, |u, s| u.symlink_path = s));
r.push(direct("control-char", &[], F, |u| u.control_char, |u, s| u.control_char = s));
r.push(direct("broken-symlink", &[], F, |u| u.broken_symlink, |u, s| u.broken_symlink = s));
r.push(direct("broken-overlay", &[], F, |u| u.broken_path_overlay, |u, s| u.broken_path_overlay = s));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn registry_is_populated() {
assert!(THEME_KEY_REGISTRY.len() >= 100);
}
#[test]
fn lookup_canonical_name() {
let def = ThemeKeyDef::from_name("permissions-user-read").unwrap();
assert_eq!(def.name, "permissions-user-read");
}
#[test]
fn lookup_alias() {
let def = ThemeKeyDef::from_name("perm-user-read").unwrap();
assert_eq!(def.name, "permissions-user-read");
let def = ThemeKeyDef::from_name("mode-user-read").unwrap();
assert_eq!(def.name, "permissions-user-read");
}
#[test]
fn unknown_key_returns_none() {
assert!(ThemeKeyDef::from_name("not-a-real-key").is_none());
}
#[test]
fn no_duplicate_names_or_aliases() {
let mut seen = std::collections::HashSet::new();
for def in THEME_KEY_REGISTRY.iter() {
assert!(seen.insert(def.name), "duplicate key name: {}", def.name);
for alias in def.aliases {
assert!(seen.insert(alias), "duplicate alias: {}", alias);
}
}
}
#[test]
fn dumpable_skips_bulk() {
let dumpable_count = ThemeKeyDef::dumpable().count();
let total = THEME_KEY_REGISTRY.len();
assert!(dumpable_count < total);
assert!(dumpable_count > 0);
}
#[test]
fn direct_round_trip() {
let mut ui = UiStyles::default();
let style = Style::new().bold();
for def in ThemeKeyDef::dumpable() {
if let StyleAccess::Direct { get, set } = def.access {
set(&mut ui, style);
assert_eq!(get(&ui), style, "round-trip failed for key '{}'", def.name);
}
}
}
}