feat(style): allow add/sub modifiers to be omitted in Style serialization. (#2057)

It's really useful that Style supports Deserialize, this allows TUI
apps to have configurable theming without much extra code.

However, deserializing a style currently fails if `add_modifier` and
`sub_modifier` are
not specified. That means the following TOML config:

```toml
[theme.highlight]
fg = "white"
bg = "black"
```

Will fail to deserialize with "missing field `add_modifier`". It should
be possible
to omit modifiers and have them default to "none".
This commit is contained in:
Ryan Roden-Corrent 2025-09-19 14:57:55 -04:00 committed by GitHub
parent ecef506a2b
commit 03f3f6df35
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -238,15 +238,26 @@ impl fmt::Debug for Modifier {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Style {
/// The foreground color.
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub fg: Option<Color>,
/// The background color.
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub bg: Option<Color>,
/// The underline color.
#[cfg(feature = "underline-color")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub underline_color: Option<Color>,
/// The modifiers to add.
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Modifier::is_empty")
)]
pub add_modifier: Modifier,
/// The modifiers to remove.
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Modifier::is_empty")
)]
pub sub_modifier: Modifier,
}
@ -923,4 +934,59 @@ mod tests {
.remove_modifier(Modifier::DIM)
);
}
#[cfg(feature = "serde")]
#[test]
fn serialize_then_deserialize() {
let style = Style {
fg: Some(Color::Rgb(255, 0, 255)),
bg: Some(Color::White),
#[cfg(feature = "underline-color")]
underline_color: Some(Color::Indexed(3)),
add_modifier: Modifier::UNDERLINED,
sub_modifier: Modifier::CROSSED_OUT,
};
let json_str = serde_json::to_string(&style).unwrap();
let json_value: serde_json::Value = serde_json::from_str(&json_str).unwrap();
let mut expected_json = serde_json::json!({
"fg": "#FF00FF",
"bg": "White",
"add_modifier": "UNDERLINED",
"sub_modifier": "CROSSED_OUT"
});
#[cfg(feature = "underline-color")]
{
expected_json
.as_object_mut()
.unwrap()
.insert("underline_color".into(), "3".into());
}
assert_eq!(json_value, expected_json);
let deserialized: Style = serde_json::from_str(&json_str).unwrap();
assert_eq!(deserialized, style);
}
#[cfg(feature = "serde")]
#[test]
fn deserialize_defaults() {
let style = Style {
fg: None,
bg: None,
#[cfg(feature = "underline-color")]
underline_color: None,
add_modifier: Modifier::empty(),
sub_modifier: Modifier::empty(),
};
let json_str = serde_json::to_string(&style).unwrap();
assert_eq!(json_str, "{}");
let deserialized: Style = serde_json::from_str(&json_str).unwrap();
assert_eq!(deserialized, style);
}
}