tree_painter/
theme.rs

1use crate::renderer::HIGHLIGHT_NAMES;
2use crate::Error;
3use std::collections::HashMap;
4use std::convert::From;
5use toml::value::Table;
6use toml::Value;
7
8pub(crate) struct Style {
9    pub color: String,
10    pub is_bold: bool,
11    pub is_italic: bool,
12}
13
14impl From<&String> for Style {
15    fn from(color: &String) -> Self {
16        Style {
17            color: color.clone(),
18            is_bold: false,
19            is_italic: false,
20        }
21    }
22}
23
24/// A theme defining colors and modifiers to be used for syntax highlighting.
25pub struct Theme {
26    pub(crate) style_map: HashMap<usize, Style>,
27    pub(crate) foreground: Style,
28    pub(crate) background: Style,
29}
30
31impl Theme {
32    /// Load theme from a Helix [compatible](https://docs.helix-editor.com/themes.html) theme
33    /// description stored in `data`.
34    ///
35    /// # Errors
36    ///
37    /// If the theme cannot be parsed either because it is not a TOML file or does not adhere to
38    /// the Helix syntax expectations, this function returns an [`Error`].
39    pub fn from_helix(data: &str) -> Result<Self, Error> {
40        let root = match data.parse::<toml::Value>()? {
41            Value::Table(table) => table,
42            _ => return Err(Error::InvalidTheme),
43        };
44
45        let palette = root.get("palette").ok_or(Error::InvalidTheme)?;
46
47        let referenced_color = |table: &Table, name: &str| -> Result<Style, Error> {
48            if let Some(Value::String(reference)) = table.get(name) {
49                if let Some(Value::String(color)) = palette.get(reference) {
50                    return Ok(Style::from(color));
51                }
52            }
53
54            Err(Error::InvalidColorReference(name.to_string()))
55        };
56
57        let fg_color = |name: &str| -> Result<Option<Style>, Error> {
58            if let Some(value) = root.get(name) {
59                match value {
60                    Value::String(reference) => {
61                        if let Some(Value::String(color)) = palette.get(reference) {
62                            return Ok(Some(Style::from(color)));
63                        }
64                    }
65                    Value::Table(table) => {
66                        let mut style = referenced_color(table, "fg")?;
67
68                        if let Some(Value::Array(modifiers)) = table.get("modifiers") {
69                            for modifier in modifiers {
70                                if let Value::String(modifier) = modifier {
71                                    if modifier == "italic" {
72                                        style.is_italic = true;
73                                    } else if modifier == "bold" {
74                                        style.is_bold = true;
75                                    }
76                                }
77                            }
78                        }
79
80                        return Ok(Some(style));
81                    }
82                    _ => {}
83                }
84            }
85
86            Ok(None)
87        };
88
89        let mut style_map = HashMap::default();
90
91        for (index, name) in HIGHLIGHT_NAMES.iter().enumerate() {
92            if let Some(style) = fg_color(*name)? {
93                style_map.insert(index, style);
94            }
95        }
96
97        let background = match root.get("ui.background") {
98            Some(Value::Table(table)) => referenced_color(table, "bg")?,
99            _ => Style::from(&"#000".to_string()),
100        };
101
102        let foreground = fg_color("ui.text")?.unwrap_or_else(|| Style::from(&"#fff".to_string()));
103
104        Ok(Self {
105            style_map,
106            foreground,
107            background,
108        })
109    }
110}