themes: Add Theme struct

Wrap HashMap<Cow<'static, str>, ThemeAttributeInner> into a struct, in
order to add more fields in the future.
This commit is contained in:
Manos Pitsidianakis 2020-06-02 15:40:05 +03:00
parent fa96a4e905
commit eca8a30c3f
No known key found for this signature in database
GPG Key ID: 73627C2F690DF710
2 changed files with 164 additions and 69 deletions

View File

@ -391,19 +391,19 @@ impl FileSettings {
dark: default_dark,
..
} = Themes::default();
for (k, v) in default_light.into_iter() {
for (k, v) in default_light.keys.into_iter() {
if !s.terminal.themes.light.contains_key(&k) {
s.terminal.themes.light.insert(k, v);
}
}
for theme in s.terminal.themes.other_themes.values_mut() {
for (k, v) in default_dark.clone().into_iter() {
for (k, v) in default_dark.keys.clone().into_iter() {
if !theme.contains_key(&k) {
theme.insert(k, v);
}
}
}
for (k, v) in default_dark.into_iter() {
for (k, v) in default_dark.keys.into_iter() {
if !s.terminal.themes.dark.contains_key(&k) {
s.terminal.themes.dark.insert(k, v);
}

View File

@ -101,10 +101,7 @@ pub fn attrs(context: &Context, key: &'static str) -> Attr {
}
#[inline(always)]
fn unlink<'k, 't: 'k>(
theme: &'t HashMap<Cow<'static, str>, ThemeAttributeInner>,
key: &'k Cow<'static, str>,
) -> ThemeAttribute {
fn unlink<'k, 't: 'k>(theme: &'t Theme, key: &'k Cow<'static, str>) -> ThemeAttribute {
ThemeAttribute {
fg: unlink_fg(theme, &ColorField::Fg, key),
bg: unlink_bg(theme, &ColorField::Bg, key),
@ -114,7 +111,7 @@ fn unlink<'k, 't: 'k>(
#[inline(always)]
fn unlink_fg<'k, 't: 'k>(
theme: &'t HashMap<Cow<'static, str>, ThemeAttributeInner>,
theme: &'t Theme,
mut field: &'k ColorField,
mut key: &'k Cow<'static, str>,
) -> Color {
@ -140,7 +137,7 @@ fn unlink_fg<'k, 't: 'k>(
#[inline(always)]
fn unlink_bg<'k, 't: 'k>(
theme: &'t HashMap<Cow<'static, str>, ThemeAttributeInner>,
theme: &'t Theme,
mut field: &'k ColorField,
mut key: &'k Cow<'static, str>,
) -> Color {
@ -165,10 +162,7 @@ fn unlink_bg<'k, 't: 'k>(
}
#[inline(always)]
fn unlink_attrs<'k, 't: 'k>(
theme: &'t HashMap<Cow<'static, str>, ThemeAttributeInner>,
mut key: &'k Cow<'static, str>,
) -> Attr {
fn unlink_attrs<'k, 't: 'k>(theme: &'t Theme, mut key: &'k Cow<'static, str>) -> Attr {
loop {
match &theme[key].attrs {
ThemeValue::Link(ref new_key, ()) => key = new_key,
@ -409,9 +403,28 @@ impl<'de> Deserialize<'de> for ThemeValue<Color> {
#[derive(Debug, Clone)]
pub struct Themes {
pub light: HashMap<Cow<'static, str>, ThemeAttributeInner>,
pub dark: HashMap<Cow<'static, str>, ThemeAttributeInner>,
pub other_themes: HashMap<String, HashMap<Cow<'static, str>, ThemeAttributeInner>>,
pub light: Theme,
pub dark: Theme,
pub other_themes: HashMap<String, Theme>,
}
#[derive(Debug, Clone)]
pub struct Theme {
pub keys: HashMap<Cow<'static, str>, ThemeAttributeInner>,
}
use std::ops::{Deref, DerefMut};
impl Deref for Theme {
type Target = HashMap<Cow<'static, str>, ThemeAttributeInner>;
fn deref(&self) -> &Self::Target {
&self.keys
}
}
impl DerefMut for Theme {
fn deref_mut(&mut self) -> &mut HashMap<Cow<'static, str>, ThemeAttributeInner> {
&mut self.keys
}
}
impl<'de> Deserialize<'de> for Themes {
@ -420,15 +433,20 @@ impl<'de> Deserialize<'de> for Themes {
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct ThemeOptions {
struct ThemesOptions {
#[serde(default)]
light: HashMap<Cow<'static, str>, ThemeAttributeInnerOptions>,
light: ThemeOptions,
#[serde(default)]
dark: HashMap<Cow<'static, str>, ThemeAttributeInnerOptions>,
dark: ThemeOptions,
#[serde(flatten, default)]
other_themes: HashMap<String, HashMap<Cow<'static, str>, ThemeAttributeInnerOptions>>,
other_themes: HashMap<String, ThemeOptions>,
}
#[derive(Deserialize)]
#[derive(Deserialize, Default)]
struct ThemeOptions {
#[serde(flatten, default)]
keys: HashMap<Cow<'static, str>, ThemeAttributeInnerOptions>,
}
#[derive(Deserialize, Default)]
struct ThemeAttributeInnerOptions {
#[serde(default)]
fg: Option<ThemeValue<Color>>,
@ -439,71 +457,99 @@ impl<'de> Deserialize<'de> for Themes {
}
let mut ret = Themes::default();
let mut s = <ThemeOptions>::deserialize(deserializer)?;
let mut s = <ThemesOptions>::deserialize(deserializer)?;
for tk in s.other_themes.keys() {
ret.other_themes.insert(tk.clone(), ret.dark.clone());
}
for (k, v) in ret.light.iter_mut() {
if let Some(att) = s.light.get_mut(k).and_then(|att| att.fg.take()) {
v.fg = att;
}
if let Some(att) = s.light.get_mut(k).and_then(|att| att.bg.take()) {
v.bg = att;
}
if let Some(att) = s.light.get_mut(k).and_then(|att| att.attrs.take()) {
v.attrs = att;
if let Some(mut att) = s.light.keys.remove(k) {
if let Some(att) = att.fg.take() {
v.fg = att;
}
if let Some(att) = att.bg.take() {
v.bg = att;
}
if let Some(att) = att.attrs.take() {
v.attrs = att;
}
}
}
if !s.light.keys.is_empty() {
return Err(de::Error::custom(format!(
"light theme contains unrecognized theme keywords: {}",
s.light
.keys
.keys()
.into_iter()
.map(|k| k.as_ref())
.collect::<SmallVec<[_; 128]>>()
.join(", ")
)));
}
for (k, v) in ret.dark.iter_mut() {
if let Some(att) = s.dark.get_mut(k).and_then(|att| att.fg.take()) {
v.fg = att;
}
if let Some(att) = s.dark.get_mut(k).and_then(|att| att.bg.take()) {
v.bg = att;
}
if let Some(att) = s.dark.get_mut(k).and_then(|att| att.attrs.take()) {
v.attrs = att;
if let Some(mut att) = s.dark.keys.remove(k) {
if let Some(att) = att.fg.take() {
v.fg = att;
}
if let Some(att) = att.bg.take() {
v.bg = att;
}
if let Some(att) = att.attrs.take() {
v.attrs = att;
}
}
}
if !s.dark.keys.is_empty() {
return Err(de::Error::custom(format!(
"dark theme contains unrecognized theme keywords: {}",
s.dark
.keys
.keys()
.into_iter()
.map(|k| k.as_ref())
.collect::<SmallVec<[_; 128]>>()
.join(", ")
)));
}
for (tk, t) in ret.other_themes.iter_mut() {
for (k, v) in t.iter_mut() {
if let Some(att) = s
if let Some(mut att) = s
.other_themes
.get_mut(tk)
.and_then(|theme| theme.get_mut(k))
.and_then(|att| att.fg.take())
.and_then(|theme| theme.keys.remove(k))
{
v.fg = att;
}
if let Some(att) = s
.other_themes
.get_mut(tk)
.and_then(|theme| theme.get_mut(k))
.and_then(|att| att.bg.take())
{
v.bg = att;
}
if let Some(att) = s
.other_themes
.get_mut(tk)
.and_then(|theme| theme.get_mut(k))
.and_then(|att| att.attrs.take())
{
v.attrs = att;
if let Some(att) = att.fg.take() {
v.fg = att;
}
if let Some(att) = att.bg.take() {
v.bg = att;
}
if let Some(att) = att.attrs.take() {
v.attrs = att;
}
}
}
if !s.other_themes[tk].keys.is_empty() {
return Err(de::Error::custom(format!(
"{} theme contains unrecognized theme keywords: {}",
tk,
s.other_themes[tk]
.keys
.keys()
.into_iter()
.map(|k| k.as_ref())
.collect::<SmallVec<[_; 128]>>()
.join(", ")
)));
}
}
Ok(ret)
}
}
impl Themes {
fn validate_keys(
name: &str,
theme: &HashMap<Cow<'static, str>, ThemeAttributeInner>,
hash_set: &HashSet<&'static str>,
) -> Result<()> {
fn validate_keys(name: &str, theme: &Theme, hash_set: &HashSet<&'static str>) -> Result<()> {
let keys = theme
.keys()
.filter_map(|k| {
@ -990,8 +1036,8 @@ impl Default for Themes {
add!("pager.highlight_search", light = { fg: Color::White, bg: Color::Byte(6) /* Teal */, attrs: Attr::BOLD }, dark = { fg: Color::White, bg: Color::Byte(6) /* Teal */, attrs: Attr::BOLD });
add!("pager.highlight_search_current", light = { fg: Color::White, bg: Color::Byte(17) /* NavyBlue */, attrs: Attr::BOLD }, dark = { fg: Color::White, bg: Color::Byte(17) /* NavyBlue */, attrs: Attr::BOLD });
Themes {
light,
dark,
light: Theme { keys: light },
dark: Theme { keys: dark },
other_themes,
}
}
@ -1051,9 +1097,7 @@ impl Serialize for Themes {
}
/* Check Theme linked values for cycles */
fn is_cyclic(
theme: &HashMap<Cow<'static, str>, ThemeAttributeInner>,
) -> std::result::Result<(), String> {
fn is_cyclic(theme: &Theme) -> std::result::Result<(), String> {
#[derive(Hash, Copy, Clone, PartialEq, Eq)]
enum Course {
Fg,
@ -1066,7 +1110,7 @@ fn is_cyclic(
visited: &mut HashMap<(&'a Cow<'static, str>, Course), bool>,
stack: &mut HashMap<(&'a Cow<'static, str>, Course), bool>,
path: &mut SmallVec<[(&'a Cow<'static, str>, Course); 16]>,
theme: &'a HashMap<Cow<'static, str>, ThemeAttributeInner>,
theme: &'a Theme,
) -> bool {
if !visited[&(k, course)] {
visited.entry((k, course)).and_modify(|e| *e = true);
@ -1194,3 +1238,54 @@ fn is_cyclic(
return Ok(());
}
#[test]
fn test_theme_parsing() {
let def = Themes::default();
assert!(def.validate().is_ok());
const TEST_STR: &'static str = r##"[dark]
"mail.listing.tag_default" = { fg = "White", bg = "HotPink3" }
"mail.listing.attachment_flag" = { fg = "mail.listing.tag_default.bg" }
"mail.view.headers" = { bg = "mail.listing.tag_default.fg" }
["hunter2"]
"mail.view.body" = { fg = "Black", bg = "White"}"##;
let parsed: Themes = toml::from_str(TEST_STR).unwrap();
assert!(parsed.other_themes.contains_key("hunter2"));
assert_eq!(
unlink_bg(
&parsed.dark,
&ColorField::Bg,
&Cow::from("mail.listing.tag_default")
),
Color::Byte(132)
);
assert_eq!(
unlink_fg(
&parsed.dark,
&ColorField::Fg,
&Cow::from("mail.listing.attachment_flag")
),
Color::Byte(132)
);
assert_eq!(
unlink_bg(
&parsed.dark,
&ColorField::Bg,
&Cow::from("mail.view.headers")
),
Color::Byte(15), // White
);
assert!(parsed.validate().is_ok());
const HAS_CYCLE: &'static str = r##"[dark]
"mail.listing.compact.even" = { fg = "mail.listing.compact.odd" }
"mail.listing.compact.odd" = { fg = "mail.listing.compact.even" }
"##;
let parsed: Themes = toml::from_str(HAS_CYCLE).unwrap();
assert!(parsed.validate().is_err());
const HAS_INVALID_KEYS: &'static str = r##"[dark]
"asdfsafsa" = { fg = "Black" }
"##;
let parsed: std::result::Result<Themes, _> = toml::from_str(HAS_INVALID_KEYS);
assert!(parsed.is_err());
}