themes: add support for optional field theme value links

Theme attribute values can refer to another theme key instead of
defining a value. Add support for optionally defining the theme key's
field by appending a ".fg" or ".bg" suffix to the link's key.
memfd
Manos Pitsidianakis 2020-06-02 06:12:48 +03:00
parent 9c0ee76ff4
commit fa96a4e905
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
1 changed files with 187 additions and 61 deletions

View File

@ -65,7 +65,7 @@ pub fn fg_color(context: &Context, key: &'static str) -> Color {
.get(t) .get(t)
.unwrap_or(&context.settings.terminal.themes.dark), .unwrap_or(&context.settings.terminal.themes.dark),
}; };
unlink_fg(theme, &Cow::from(key)) unlink_fg(theme, &ColorField::Fg, &Cow::from(key))
} }
#[inline(always)] #[inline(always)]
@ -81,7 +81,7 @@ pub fn bg_color(context: &Context, key: &'static str) -> Color {
.get(t) .get(t)
.unwrap_or(&context.settings.terminal.themes.dark), .unwrap_or(&context.settings.terminal.themes.dark),
}; };
unlink_bg(theme, &Cow::from(key)) unlink_bg(theme, &ColorField::Bg, &Cow::from(key))
} }
#[inline(always)] #[inline(always)]
@ -106,8 +106,8 @@ fn unlink<'k, 't: 'k>(
key: &'k Cow<'static, str>, key: &'k Cow<'static, str>,
) -> ThemeAttribute { ) -> ThemeAttribute {
ThemeAttribute { ThemeAttribute {
fg: unlink_fg(theme, key), fg: unlink_fg(theme, &ColorField::Fg, key),
bg: unlink_bg(theme, key), bg: unlink_bg(theme, &ColorField::Bg, key),
attrs: unlink_attrs(theme, key), attrs: unlink_attrs(theme, key),
} }
} }
@ -115,12 +115,25 @@ fn unlink<'k, 't: 'k>(
#[inline(always)] #[inline(always)]
fn unlink_fg<'k, 't: 'k>( fn unlink_fg<'k, 't: 'k>(
theme: &'t HashMap<Cow<'static, str>, ThemeAttributeInner>, theme: &'t HashMap<Cow<'static, str>, ThemeAttributeInner>,
mut field: &'k ColorField,
mut key: &'k Cow<'static, str>, mut key: &'k Cow<'static, str>,
) -> Color { ) -> Color {
loop { loop {
match &theme[key].fg { match field {
ThemeValue::Link(ref new_key) => key = new_key, ColorField::LikeSelf | ColorField::Fg => match &theme[key].fg {
ThemeValue::Value(val) => return *val, ThemeValue::Link(ref new_key, ref new_field) => {
key = new_key;
field = new_field
}
ThemeValue::Value(val) => return *val,
},
ColorField::Bg => match &theme[key].bg {
ThemeValue::Link(ref new_key, ref new_field) => {
key = new_key;
field = new_field
}
ThemeValue::Value(val) => return *val,
},
} }
} }
} }
@ -128,12 +141,25 @@ fn unlink_fg<'k, 't: 'k>(
#[inline(always)] #[inline(always)]
fn unlink_bg<'k, 't: 'k>( fn unlink_bg<'k, 't: 'k>(
theme: &'t HashMap<Cow<'static, str>, ThemeAttributeInner>, theme: &'t HashMap<Cow<'static, str>, ThemeAttributeInner>,
mut field: &'k ColorField,
mut key: &'k Cow<'static, str>, mut key: &'k Cow<'static, str>,
) -> Color { ) -> Color {
loop { loop {
match &theme[key].bg { match field {
ThemeValue::Link(ref new_key) => key = new_key, ColorField::LikeSelf | ColorField::Bg => match &theme[key].bg {
ThemeValue::Value(val) => return *val, ThemeValue::Link(ref new_key, ref new_field) => {
key = new_key;
field = new_field
}
ThemeValue::Value(val) => return *val,
},
ColorField::Fg => match &theme[key].fg {
ThemeValue::Link(ref new_key, ref new_field) => {
key = new_key;
field = new_field
}
ThemeValue::Value(val) => return *val,
},
} }
} }
} }
@ -145,7 +171,7 @@ fn unlink_attrs<'k, 't: 'k>(
) -> Attr { ) -> Attr {
loop { loop {
match &theme[key].attrs { match &theme[key].attrs {
ThemeValue::Link(ref new_key) => key = new_key, ThemeValue::Link(ref new_key, ()) => key = new_key,
ThemeValue::Value(val) => return *val, ThemeValue::Value(val) => return *val,
} }
} }
@ -242,15 +268,44 @@ impl Default for ThemeAttributeInner {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
/// Holds either an actual value or refers to the key name of the attribute that holds the value. pub enum ColorField {
pub enum ThemeValue<T> { // Like self, i.e. either Fg or Bg
Value(T), LikeSelf,
Link(Cow<'static, str>), Fg,
Bg,
} }
impl<T> From<&'static str> for ThemeValue<T> { /// The field a ThemeValue::Link refers to.
trait ThemeLink {
type LinkType;
}
/// A color value that's a link can either refer to .fg or .bg field
impl ThemeLink for Color {
type LinkType = ColorField;
}
/// An attr value that's a link can only refer to an .attr field
impl ThemeLink for Attr {
type LinkType = ();
}
#[derive(Debug, Clone)]
/// Holds either an actual value or refers to the key name of the attribute that holds the value.
enum ThemeValue<T: ThemeLink> {
Value(T),
Link(Cow<'static, str>, T::LinkType),
}
impl From<&'static str> for ThemeValue<Color> {
fn from(from: &'static str) -> Self { fn from(from: &'static str) -> Self {
ThemeValue::Link(from.into()) ThemeValue::Link(from.into(), ColorField::LikeSelf)
}
}
impl From<&'static str> for ThemeValue<Attr> {
fn from(from: &'static str) -> Self {
ThemeValue::Link(from.into(), ())
} }
} }
@ -287,7 +342,7 @@ impl<'de> Deserialize<'de> for ThemeValue<Attr> {
if let Ok(c) = Attr::from_string_de::<'de, D, String>(s.clone()) { if let Ok(c) = Attr::from_string_de::<'de, D, String>(s.clone()) {
Ok(ThemeValue::Value(c)) Ok(ThemeValue::Value(c))
} else { } else {
Ok(ThemeValue::Link(s.into())) Ok(ThemeValue::Link(s.into(), ()))
} }
} else { } else {
Err(de::Error::custom("invalid theme attribute value")) Err(de::Error::custom("invalid theme attribute value"))
@ -295,14 +350,32 @@ impl<'de> Deserialize<'de> for ThemeValue<Attr> {
} }
} }
impl<T: Serialize> Serialize for ThemeValue<T> { impl Serialize for ThemeValue<Attr> {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
{ {
match self { match self {
ThemeValue::Value(s) => s.serialize(serializer), ThemeValue::Value(s) => s.serialize(serializer),
ThemeValue::Link(s) => serializer.serialize_str(s.as_ref()), ThemeValue::Link(s, ()) => serializer.serialize_str(s.as_ref()),
}
}
}
impl Serialize for ThemeValue<Color> {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
ThemeValue::Value(s) => s.serialize(serializer),
ThemeValue::Link(s, ColorField::LikeSelf) => serializer.serialize_str(s.as_ref()),
ThemeValue::Link(s, ColorField::Fg) => {
serializer.serialize_str(format!("{}.fg", s).as_ref())
}
ThemeValue::Link(s, ColorField::Bg) => {
serializer.serialize_str(format!("{}.bg", s).as_ref())
}
} }
} }
} }
@ -315,8 +388,18 @@ impl<'de> Deserialize<'de> for ThemeValue<Color> {
if let Ok(s) = <String>::deserialize(deserializer) { if let Ok(s) = <String>::deserialize(deserializer) {
if let Ok(c) = Color::from_string_de::<'de, D>(s.clone()) { if let Ok(c) = Color::from_string_de::<'de, D>(s.clone()) {
Ok(ThemeValue::Value(c)) Ok(ThemeValue::Value(c))
} else if s.ends_with(".fg") {
Ok(ThemeValue::Link(
s[..s.len() - 3].to_string().into(),
ColorField::Fg,
))
} else if s.ends_with(".bg") {
Ok(ThemeValue::Link(
s[..s.len() - 3].to_string().into(),
ColorField::Bg,
))
} else { } else {
Ok(ThemeValue::Link(s.into())) Ok(ThemeValue::Link(s.into(), ColorField::LikeSelf))
} }
} else { } else {
Err(de::Error::custom("invalid theme color value")) Err(de::Error::custom("invalid theme color value"))
@ -431,7 +514,7 @@ impl Themes {
} }
}) })
.chain(theme.iter().filter_map(|(key, a)| { .chain(theme.iter().filter_map(|(key, a)| {
if let ThemeValue::Link(ref r) = a.fg { if let ThemeValue::Link(ref r, _) = a.fg {
if !hash_set.contains(&r.as_ref()) { if !hash_set.contains(&r.as_ref()) {
Some((Some(key), "fg link", r.as_ref())) Some((Some(key), "fg link", r.as_ref()))
} else { } else {
@ -442,7 +525,7 @@ impl Themes {
} }
})) }))
.chain(theme.iter().filter_map(|(key, a)| { .chain(theme.iter().filter_map(|(key, a)| {
if let ThemeValue::Link(ref r) = a.bg { if let ThemeValue::Link(ref r, _) = a.bg {
if !hash_set.contains(&r.as_ref()) { if !hash_set.contains(&r.as_ref()) {
Some((Some(key), "bg link", r.as_ref())) Some((Some(key), "bg link", r.as_ref()))
} else { } else {
@ -453,7 +536,7 @@ impl Themes {
} }
})) }))
.chain(theme.iter().filter_map(|(key, a)| { .chain(theme.iter().filter_map(|(key, a)| {
if let ThemeValue::Link(ref r) = a.attrs { if let ThemeValue::Link(ref r, _) = a.attrs {
if !hash_set.contains(&r.as_ref()) { if !hash_set.contains(&r.as_ref()) {
Some((Some(key), "attrs link", r.as_ref())) Some((Some(key), "attrs link", r.as_ref()))
} else { } else {
@ -526,8 +609,8 @@ impl Themes {
format!( format!(
"\"{}\" = {{ fg = {}, bg = {}, attrs = {} }}\n", "\"{}\" = {{ fg = {}, bg = {}, attrs = {} }}\n",
k, k,
toml::to_string(&unlink_fg(&theme, k)).unwrap(), toml::to_string(&unlink_fg(&theme, &ColorField::Fg, k)).unwrap(),
toml::to_string(&unlink_bg(&theme, k)).unwrap(), toml::to_string(&unlink_bg(&theme, &ColorField::Bg, k)).unwrap(),
toml::to_string(&unlink_attrs(&theme, k)).unwrap(), toml::to_string(&unlink_attrs(&theme, k)).unwrap(),
) )
.chars(), .chars(),
@ -927,8 +1010,8 @@ impl Serialize for Themes {
dark.insert( dark.insert(
k.clone(), k.clone(),
ThemeAttribute { ThemeAttribute {
fg: unlink_fg(&self.dark, k), fg: unlink_fg(&self.dark, &ColorField::Fg, k),
bg: unlink_bg(&self.dark, k), bg: unlink_bg(&self.dark, &ColorField::Bg, k),
attrs: unlink_attrs(&self.dark, k), attrs: unlink_attrs(&self.dark, k),
}, },
); );
@ -938,8 +1021,8 @@ impl Serialize for Themes {
light.insert( light.insert(
k.clone(), k.clone(),
ThemeAttribute { ThemeAttribute {
fg: unlink_fg(&self.light, k), fg: unlink_fg(&self.light, &ColorField::Fg, k),
bg: unlink_bg(&self.light, k), bg: unlink_bg(&self.light, &ColorField::Bg, k),
attrs: unlink_attrs(&self.light, k), attrs: unlink_attrs(&self.light, k),
}, },
); );
@ -952,8 +1035,8 @@ impl Serialize for Themes {
new_map.insert( new_map.insert(
k.clone(), k.clone(),
ThemeAttribute { ThemeAttribute {
fg: unlink_fg(&t, k), fg: unlink_fg(&t, &ColorField::Fg, k),
bg: unlink_bg(&t, k), bg: unlink_bg(&t, &ColorField::Bg, k),
attrs: unlink_attrs(&t, k), attrs: unlink_attrs(&t, k),
}, },
); );
@ -971,30 +1054,45 @@ impl Serialize for Themes {
fn is_cyclic( fn is_cyclic(
theme: &HashMap<Cow<'static, str>, ThemeAttributeInner>, theme: &HashMap<Cow<'static, str>, ThemeAttributeInner>,
) -> std::result::Result<(), String> { ) -> std::result::Result<(), String> {
#[derive(Hash, Copy, Clone, PartialEq, Eq)]
enum Course { enum Course {
Fg, Fg,
Bg, Bg,
Attrs, Attrs,
} }
fn is_cyclic_util<'a>( fn is_cyclic_util<'a>(
course: &Course, course: Course,
k: &'a Cow<'static, str>, k: &'a Cow<'static, str>,
visited: &mut HashMap<&'a Cow<'static, str>, bool>, visited: &mut HashMap<(&'a Cow<'static, str>, Course), bool>,
stack: &mut HashMap<&'a Cow<'static, str>, bool>, stack: &mut HashMap<(&'a Cow<'static, str>, Course), bool>,
path: &mut SmallVec<[&'a Cow<'static, str>; 16]>, path: &mut SmallVec<[(&'a Cow<'static, str>, Course); 16]>,
theme: &'a HashMap<Cow<'static, str>, ThemeAttributeInner>, theme: &'a HashMap<Cow<'static, str>, ThemeAttributeInner>,
) -> bool { ) -> bool {
if !visited[k] { if !visited[&(k, course)] {
visited.entry(k).and_modify(|e| *e = true); visited.entry((k, course)).and_modify(|e| *e = true);
stack.entry(k).and_modify(|e| *e = true); stack.entry((k, course)).and_modify(|e| *e = true);
match course { match course {
Course::Fg => match theme[k].fg { Course::Fg => match theme[k].fg {
ThemeValue::Link(ref l) => { ThemeValue::Link(ref l, ColorField::LikeSelf)
path.push(l); | ThemeValue::Link(ref l, ColorField::Fg) => {
if !visited[l] && is_cyclic_util(course, l, visited, stack, path, theme) { path.push((l, Course::Fg));
if !visited[&(l, Course::Fg)]
&& is_cyclic_util(course, l, visited, stack, path, theme)
{
return true; return true;
} else if stack[l] { } else if stack[&(l, Course::Fg)] {
return true;
}
path.pop();
}
ThemeValue::Link(ref l, ColorField::Bg) => {
path.push((l, Course::Bg));
if !visited[&(l, Course::Bg)]
&& is_cyclic_util(Course::Bg, l, visited, stack, path, theme)
{
return true;
} else if stack[&(l, Course::Bg)] {
return true; return true;
} }
path.pop(); path.pop();
@ -1002,11 +1100,25 @@ fn is_cyclic(
_ => {} _ => {}
}, },
Course::Bg => match theme[k].bg { Course::Bg => match theme[k].bg {
ThemeValue::Link(ref l) => { ThemeValue::Link(ref l, ColorField::LikeSelf)
path.push(l); | ThemeValue::Link(ref l, ColorField::Bg) => {
if !visited[l] && is_cyclic_util(course, l, visited, stack, path, theme) { path.push((l, Course::Bg));
if !visited[&(l, Course::Bg)]
&& is_cyclic_util(Course::Bg, l, visited, stack, path, theme)
{
return true; return true;
} else if stack[l] { } else if stack[&(l, Course::Bg)] {
return true;
}
path.pop();
}
ThemeValue::Link(ref l, ColorField::Fg) => {
path.push((l, Course::Fg));
if !visited[&(l, Course::Fg)]
&& is_cyclic_util(Course::Fg, l, visited, stack, path, theme)
{
return true;
} else if stack[&(l, Course::Fg)] {
return true; return true;
} }
path.pop(); path.pop();
@ -1014,11 +1126,13 @@ fn is_cyclic(
_ => {} _ => {}
}, },
Course::Attrs => match theme[k].attrs { Course::Attrs => match theme[k].attrs {
ThemeValue::Link(ref l) => { ThemeValue::Link(ref l, _) => {
path.push(l); path.push((l, course));
if !visited[l] && is_cyclic_util(course, l, visited, stack, path, theme) { if !visited[&(l, course)]
&& is_cyclic_util(course, l, visited, stack, path, theme)
{
return true; return true;
} else if stack[l] { } else if stack[&(l, course)] {
return true; return true;
} }
path.pop(); path.pop();
@ -1027,27 +1141,39 @@ fn is_cyclic(
}, },
} }
} }
stack.entry(k).and_modify(|e| *e = false); stack.entry((k, course)).and_modify(|e| *e = false);
return false; return false;
} }
let mut path = SmallVec::new(); let mut path = SmallVec::new();
let mut visited = theme let mut visited = theme
.keys() .keys()
.map(|k| (k, false)) .map(|k| {
.collect::<HashMap<&Cow<'static, str>, bool>>(); std::iter::once(((k, Course::Fg), false))
.chain(std::iter::once(((k, Course::Bg), false)))
.chain(std::iter::once(((k, Course::Attrs), false)))
})
.flatten()
.collect::<HashMap<(&Cow<'static, str>, Course), bool>>();
let mut stack = theme let mut stack = visited.clone();
.keys()
.map(|k| (k, false))
.collect::<HashMap<&Cow<'static, str>, bool>>();
for k in theme.keys() { for k in theme.keys() {
for course in [Course::Fg, Course::Bg, Course::Attrs].iter() { for &course in [Course::Fg, Course::Bg, Course::Attrs].iter() {
path.push(k); path.push((k, course));
if is_cyclic_util(course, k, &mut visited, &mut stack, &mut path, &theme) { if is_cyclic_util(course, k, &mut visited, &mut stack, &mut path, &theme) {
let path = path let path = path
.into_iter() .into_iter()
.map(|k| k.to_string()) .map(|(k, c)| {
format!(
"{}.{}",
k,
match c {
Course::Fg => "fg",
Course::Bg => "bg",
Course::Attrs => "attrs",
}
)
})
.collect::<Vec<String>>(); .collect::<Vec<String>>();
return Err(format!( return Err(format!(
"{} {}", "{} {}",