@@ -1,5 +1,6 @@
11use std::{
22 collections::HashMap,
3+ convert::TryFrom,
34 path::{Path, PathBuf},
45};
56
@@ -11,8 +12,6 @@ use toml::Value;
1112
1213pub use crate::graphics::{Color, Modifier, Style};
1314
14−/// Color theme for syntax highlighting.
15−
1615pub static DEFAULT_THEME: Lazy<Theme> = Lazy::new(|| {
1716 toml::from_slice(include_bytes!("../../theme.toml")).expect("Failed to parse default theme")
1817});
@@ -54,22 +53,10 @@ impl Loader {
5453 .map(|entries| {
5554 entries
5655 .filter_map(|entry| {
57− if let Ok(entry) = entry {
58− let path = entry.path();
59− if let Some(ext) = path.extension() {
60− if ext != "toml" {
61− return None;
62− }
63− return Some(
64− entry
65− .file_name()
66− .to_string_lossy()
67− .trim_end_matches(".toml")
68− .to_owned(),
69− );
70− }
71− }
72− None
56+ let entry = entry.ok()?;
57+ let path = entry.path();
58+ (path.extension()? == "toml")
59+ .then(|| path.file_stem().unwrap().to_string_lossy().into_owned())
7360 })
7461 .collect()
7562 })
@@ -103,13 +90,23 @@ impl<'de> Deserialize<'de> for Theme {
10390 let mut styles = HashMap::new();
10491
10592 if let Ok(mut colors) = HashMap::<String, Value>::deserialize(deserializer) {
106− let palette = parse_palette(colors.remove("palette"));
107− // scopes.reserve(colors.len());
93+ // TODO: alert user of parsing failures in editor
94+ let palette = colors
95+ .remove("palette")
96+ .map(|value| {
97+ ThemePalette::try_from(value).unwrap_or_else(|err| {
98+ warn!("{}", err);
99+ ThemePalette::default()
100+ })
101+ })
102+ .unwrap_or_default();
103+
108104 styles.reserve(colors.len());
109105 for (name, style_value) in colors {
110106 let mut style = Style::default();
111− parse_style(&mut style, style_value, &palette);
112− // scopes.push(name);
107+ if let Err(err) = palette.parse_style(&mut style, style_value) {
108+ warn!("{}", err);
109+ }
113110 styles.insert(name, style);
114111 }
115112 }
@@ -119,121 +116,120 @@ impl<'de> Deserialize<'de> for Theme {
119116 }
120117}
121118
122−fn parse_palette(value: Option<Value>) -> HashMap<String, Color> {
123− match value {
124− Some(Value::Table(entries)) => entries,
125− _ => return HashMap::default(),
119+impl Theme {
120+ pub fn get(&self, scope: &str) -> Style {
121+ self.try_get(scope)
122+ .unwrap_or_else(|| Style::default().fg(Color::Rgb(0, 0, 255)))
126123 }
127− .into_iter()
128− .filter_map(|(name, value)| {
129− let color = parse_color(value, &HashMap::default())?;
130− Some((name, color))
131− })
132− .collect()
133−}
134124
135−fn parse_style(style: &mut Style, value: Value, palette: &HashMap<String, Color>) {
136− //TODO: alert user of parsing failures
137− if let Value::Table(entries) = value {
138− for (name, value) in entries {
139− match name.as_str() {
140− "fg" => {
141− if let Some(color) = parse_color(value, palette) {
142− *style = style.fg(color);
143− }
144− }
145− "bg" => {
146− if let Some(color) = parse_color(value, palette) {
147− *style = style.bg(color);
148− }
149− }
150− "modifiers" => {
151− if let Value::Array(arr) = value {
152− for modifier in arr.iter().filter_map(parse_modifier) {
153− *style = style.add_modifier(modifier);
154− }
155− }
156− }
157− _ => (),
158− }
159− }
160− } else if let Some(color) = parse_color(value, palette) {
161− *style = style.fg(color);
125+ pub fn try_get(&self, scope: &str) -> Option<Style> {
126+ self.styles.get(scope).copied()
162127 }
163−}
164128
165−fn hex_string_to_rgb(s: &str) -> Option<(u8, u8, u8)> {
166− if s.starts_with('#') && s.len() >= 7 {
167− if let (Ok(red), Ok(green), Ok(blue)) = (
168− u8::from_str_radix(&s[1..3], 16),
169− u8::from_str_radix(&s[3..5], 16),
170− u8::from_str_radix(&s[5..7], 16),
171− ) {
172− Some((red, green, blue))
173− } else {
174− None
175− }
176− } else {
177− None
129+ #[inline]
130+ pub fn scopes(&self) -> &[String] {
131+ &self.scopes
132+ }
133+
134+ pub fn find_scope_index(&self, scope: &str) -> Option<usize> {
135+ self.scopes().iter().position(|s| s == scope)
178136 }
179137}
180138
181−fn parse_color(value: Value, palette: &HashMap<String, Color>) -> Option<Color> {
182− if let Value::String(s) = value {
183− if let Some(color) = palette.get(&s) {
184− Some(*color)
185− } else if let Some((red, green, blue)) = hex_string_to_rgb(&s) {
186− Some(Color::Rgb(red, green, blue))
187− } else {
188− warn!("malformed hexcode in theme: {}", s);
189− None
190− }
191− } else {
192− warn!("unrecognized value in theme: {}", value);
193− None
139+struct ThemePalette {
140+ palette: HashMap<String, Color>,
141+}
142+
143+impl Default for ThemePalette {
144+ fn default() -> Self {
145+ Self::new(HashMap::new())
194146 }
195147}
196148
197−fn parse_modifier(value: &Value) -> Option<Modifier> {
198− if let Value::String(s) = value {
199− match s.as_str() {
200− "bold" => Some(Modifier::BOLD),
201− "dim" => Some(Modifier::DIM),
202− "italic" => Some(Modifier::ITALIC),
203− "underlined" => Some(Modifier::UNDERLINED),
204− "slow_blink" => Some(Modifier::SLOW_BLINK),
205− "rapid_blink" => Some(Modifier::RAPID_BLINK),
206− "reversed" => Some(Modifier::REVERSED),
207− "hidden" => Some(Modifier::HIDDEN),
208− "crossed_out" => Some(Modifier::CROSSED_OUT),
209− _ => {
210− warn!("unrecognized modifier in theme: {}", s);
211− None
149+impl ThemePalette {
150+ pub fn new(palette: HashMap<String, Color>) -> Self {
151+ Self { palette }
152+ }
153+
154+ pub fn hex_string_to_rgb(s: &str) -> Result<Color, String> {
155+ if s.starts_with('#') && s.len() >= 7 {
156+ if let (Ok(red), Ok(green), Ok(blue)) = (
157+ u8::from_str_radix(&s[1..3], 16),
158+ u8::from_str_radix(&s[3..5], 16),
159+ u8::from_str_radix(&s[5..7], 16),
160+ ) {
161+ return Ok(Color::Rgb(red, green, blue));
212162 }
213163 }
214− } else {
215− warn!("unrecognized modifier in theme: {}", value);
216− None
164+
165+ Err(format!("Theme: malformed hexcode: {}", s))
217166 }
218−}
219167
220−impl Theme {
221− pub fn get(&self, scope: &str) -> Style {
222− self.try_get(scope)
223− .unwrap_or_else(|| Style::default().fg(Color::Rgb(0, 0, 255)))
168+ fn parse_value_as_str(value: &Value) -> Result<&str, String> {
169+ value
170+ .as_str()
171+ .ok_or(format!("Theme: unrecognized value: {}", value))
224172 }
225173
226− pub fn try_get(&self, scope: &str) -> Option<Style> {
227− self.styles.get(scope).copied()
174+ pub fn parse_color(&self, value: Value) -> Result<Color, String> {
175+ let value = Self::parse_value_as_str(&value)?;
176+
177+ self.palette
178+ .get(value)
179+ .copied()
180+ .ok_or("")
181+ .or_else(|_| Self::hex_string_to_rgb(value))
228182 }
229183
230− #[inline]
231− pub fn scopes(&self) -> &[String] {
232− &self.scopes
184+ pub fn parse_modifier(value: &Value) -> Result<Modifier, String> {
185+ value
186+ .as_str()
187+ .and_then(|s| s.parse().ok())
188+ .ok_or(format!("Theme: invalid modifier: {}", value))
233189 }
234190
235− pub fn find_scope_index(&self, scope: &str) -> Option<usize> {
236− self.scopes().iter().position(|s| s == scope)
191+ pub fn parse_style(&self, style: &mut Style, value: Value) -> Result<(), String> {
192+ if let Value::Table(entries) = value {
193+ for (name, value) in entries {
194+ match name.as_str() {
195+ "fg" => *style = style.fg(self.parse_color(value)?),
196+ "bg" => *style = style.bg(self.parse_color(value)?),
197+ "modifiers" => {
198+ let modifiers = value
199+ .as_array()
200+ .ok_or("Theme: modifiers should be an array")?;
201+
202+ for modifier in modifiers {
203+ *style = style.add_modifier(Self::parse_modifier(modifier)?);
204+ }
205+ }
206+ _ => return Err(format!("Theme: invalid style attribute: {}", name)),
207+ }
208+ }
209+ } else {
210+ *style = style.fg(self.parse_color(value)?);
211+ }
212+ Ok(())
213+ }
214+}
215+
216+impl TryFrom<Value> for ThemePalette {
217+ type Error = String;
218+
219+ fn try_from(value: Value) -> Result<Self, Self::Error> {
220+ let map = match value {
221+ Value::Table(entries) => entries,
222+ _ => return Ok(Self::default()),
223+ };
224+
225+ let mut palette = HashMap::with_capacity(map.len());
226+ for (name, value) in map {
227+ let value = Self::parse_value_as_str(&value)?;
228+ let color = Self::hex_string_to_rgb(value)?;
229+ palette.insert(name, color);
230+ }
231+
232+ Ok(Self::new(palette))
237233 }
238234}
239235
@@ -242,23 +238,21 @@ fn test_parse_style_string() {
242238 let fg = Value::String("#ffffff".to_string());
243239
244240 let mut style = Style::default();
245− parse_style(&mut style, fg, &HashMap::default());
241+ let palette = ThemePalette::default();
242+ palette.parse_style(&mut style, fg).unwrap();
246243
247244 assert_eq!(style, Style::default().fg(Color::Rgb(255, 255, 255)));
248245}
249246
250247#[test]
251248fn test_palette() {
249+ use helix_core::hashmap;
252250 let fg = Value::String("my_color".to_string());
253251
254252 let mut style = Style::default();
255− parse_style(
256− &mut style,
257− fg,
258− &vec![("my_color".to_string(), Color::Rgb(255, 255, 255))]
259− .into_iter()
260− .collect(),
261− );
253+ let palette =
254+ ThemePalette::new(hashmap! { "my_color".to_string() => Color::Rgb(255, 255, 255) });
255+ palette.parse_style(&mut style, fg).unwrap();
262256
263257 assert_eq!(style, Style::default().fg(Color::Rgb(255, 255, 255)));
264258}
@@ -274,9 +268,10 @@ fn test_parse_style_table() {
274268 };
275269
276270 let mut style = Style::default();
271+ let palette = ThemePalette::default();
277272 if let Value::Table(entries) = table {
278273 for (_name, value) in entries {
279− parse_style(&mut style, value, &HashMap::default());
274+ palette.parse_style(&mut style, value).unwrap();
280275 }
281276 }
282277