1use std::sync::Arc;
10
11use crate::error::PaletteError;
12use crate::manifest::ManifestSection;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
16#[cfg_attr(feature = "snapshot", derive(serde::Serialize))]
17pub struct StyleModifiers {
18 pub bold: bool,
20 pub italic: bool,
22 pub underline: bool,
24}
25
26impl StyleModifiers {
27 pub fn parse(s: &str, section: &str, field: &str) -> Result<Self, PaletteError> {
32 let trimmed = s.trim();
33 if trimmed.is_empty() {
34 return Err(PaletteError::InvalidStyle {
35 section: Arc::from(section),
36 field: Arc::from(field),
37 value: Arc::from(s),
38 });
39 }
40
41 let mut result = Self::default();
42 for part in trimmed.split(',') {
43 match part.trim() {
44 "bold" => result.bold = true,
45 "italic" => result.italic = true,
46 "underline" => result.underline = true,
47 _ => {
48 return Err(PaletteError::InvalidStyle {
49 section: Arc::from(section),
50 field: Arc::from(field),
51 value: Arc::from(s),
52 });
53 }
54 }
55 }
56 Ok(result)
57 }
58
59 pub fn is_empty(self) -> bool {
61 !self.bold && !self.italic && !self.underline
62 }
63
64 pub fn to_css_value(self) -> &'static str {
69 match (self.bold, self.italic, self.underline) {
70 (false, false, false) => "normal",
71 (true, false, false) => "bold",
72 (false, true, false) => "italic",
73 (false, false, true) => "underline",
74 (true, true, false) => "bold italic",
75 (true, false, true) => "bold underline",
76 (false, true, true) => "italic underline",
77 (true, true, true) => "bold italic underline",
78 }
79 }
80}
81
82impl std::fmt::Display for StyleModifiers {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 f.write_str(self.to_css_value())
85 }
86}
87
88fn resolve_style(
89 section: &ManifestSection,
90 section_name: &str,
91 field: &str,
92) -> Result<Option<StyleModifiers>, PaletteError> {
93 match section.get(field) {
94 None => Ok(None),
95 Some(val) => StyleModifiers::parse(val, section_name, field).map(Some),
96 }
97}
98
99macro_rules! style_group {
100 ($(#[$_meta:meta])* $_color_type:ident { $($field:ident),+ $(,)? }) => {
101 #[derive(Debug, Clone, Default, PartialEq, Eq)]
105 #[cfg_attr(feature = "snapshot", derive(serde::Serialize))]
106 pub struct SyntaxStyles {
107 $(
108 #[doc = concat!("`", stringify!($field), "` slot.")]
109 pub $field: Option<StyleModifiers>,
110 )+
111 }
112
113 impl SyntaxStyles {
114 pub fn from_section(
116 section: &ManifestSection,
117 section_name: &str,
118 ) -> Result<Self, PaletteError> {
119 Ok(Self {
120 $($field: resolve_style(section, section_name, stringify!($field))?,)+
121 })
122 }
123
124 pub fn merge(&self, fallback: &Self) -> Self {
126 Self {
127 $($field: self.$field.or(fallback.$field),)+
128 }
129 }
130
131 pub fn populated_slots(&self) -> impl Iterator<Item = (&'static str, &StyleModifiers)> {
133 [$(
134 (stringify!($field), self.$field.as_ref()),
135 )+]
136 .into_iter()
137 .filter_map(|(name, style)| style.map(|s| (name, s)))
138 }
139 }
140 };
141}
142
143crate::palette::syntax_fields!(style_group);
144
145macro_rules! resolved_style_group {
146 ($(#[$_meta:meta])* $_color_type:ident { $($field:ident),+ $(,)? }) => {
147 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
154 #[cfg_attr(feature = "snapshot", derive(serde::Serialize))]
155 pub struct ResolvedSyntaxStyles {
156 $(
157 #[doc = concat!("`", stringify!($field), "` slot.")]
158 pub $field: StyleModifiers,
159 )+
160 }
161
162 impl ResolvedSyntaxStyles {
163 fn from_group(group: &SyntaxStyles) -> Self {
164 Self {
165 $($field: group.$field.unwrap_or_default(),)+
166 }
167 }
168
169 pub fn all_slots(&self) -> impl Iterator<Item = (&'static str, &StyleModifiers)> {
171 [$(
172 (stringify!($field), &self.$field),
173 )+]
174 .into_iter()
175 }
176 }
177 };
178}
179
180crate::palette::syntax_fields!(resolved_style_group);
181
182impl ResolvedSyntaxStyles {
183 pub fn from_group_with_fallback(group: &SyntaxStyles) -> Self {
186 let mut resolved = Self::from_group(group);
187 crate::palette::resolve_syntax_fallback!(resolved, group);
188 resolved
189 }
190}