syntect/highlighting/
theme_load.rs

1// Code based on https://github.com/defuz/sublimate/blob/master/src/core/syntax/theme.rs
2// released under the MIT license by @defuz
3
4use std::str::FromStr;
5
6use super::settings::{ParseSettings, Settings};
7use super::style::*;
8use super::selector::*;
9use super::theme::*;
10use crate::parsing::ParseScopeError;
11
12use self::ParseThemeError::*;
13
14#[derive(Debug, thiserror::Error)]
15#[non_exhaustive]
16pub enum ParseThemeError {
17    #[error("Incorrect underline option")]
18    IncorrectUnderlineOption,
19    #[error("Incorrect font style: {0}")]
20    IncorrectFontStyle(String),
21    #[error("Incorrect color")]
22    IncorrectColor,
23    #[error("Incorrect syntax")]
24    IncorrectSyntax,
25    #[error("Incorrect settings")]
26    IncorrectSettings,
27    #[error("Undefined settings")]
28    UndefinedSettings,
29    #[error("Undefined scope settings: {0}")]
30    UndefinedScopeSettings(String),
31    #[error("Color sheme scope is not object")]
32    ColorShemeScopeIsNotObject,
33    #[error("Color sheme settings is not object")]
34    ColorShemeSettingsIsNotObject,
35    #[error("Scope selector is not string: {0}")]
36    ScopeSelectorIsNotString(String),
37    #[error("Duplicate settings")]
38    DuplicateSettings,
39    #[error("Scope parse error: {0}")]
40    ScopeParse(#[from] ParseScopeError),
41}
42
43
44impl FromStr for UnderlineOption {
45    type Err = ParseThemeError;
46
47    fn from_str(s: &str) -> Result<UnderlineOption, Self::Err> {
48        Ok(match s {
49            "underline" => UnderlineOption::Underline,
50            "stippled_underline" => UnderlineOption::StippledUnderline,
51            "squiggly_underline" => UnderlineOption::SquigglyUnderline,
52            _ => return Err(IncorrectUnderlineOption),
53        })
54    }
55}
56
57impl ParseSettings for UnderlineOption {
58    type Error = ParseThemeError;
59
60    fn parse_settings(settings: Settings) -> Result<UnderlineOption, Self::Error> {
61        match settings {
62            Settings::String(value) => UnderlineOption::from_str(&value),
63            _ => Err(IncorrectUnderlineOption),
64        }
65    }
66}
67
68impl FromStr for FontStyle {
69    type Err = ParseThemeError;
70
71    fn from_str(s: &str) -> Result<FontStyle, Self::Err> {
72        let mut font_style = FontStyle::empty();
73        for i in s.split_whitespace() {
74            font_style.insert(match i {
75                "bold" => FontStyle::BOLD,
76                "underline" => FontStyle::UNDERLINE,
77                "italic" => FontStyle::ITALIC,
78                "normal" |
79                "regular" => FontStyle::empty(),
80                s => return Err(IncorrectFontStyle(s.to_owned())),
81            })
82        }
83        Ok(font_style)
84    }
85}
86
87impl ParseSettings for FontStyle {
88    type Error = ParseThemeError;
89
90    fn parse_settings(settings: Settings) -> Result<FontStyle, Self::Error> {
91        match settings {
92            Settings::String(value) => FontStyle::from_str(&value),
93            c => Err(IncorrectFontStyle(c.to_string())),
94        }
95    }
96}
97
98impl FromStr for Color {
99    type Err = ParseThemeError;
100
101    fn from_str(s: &str) -> Result<Color, Self::Err> {
102        let mut chars = s.chars();
103        if chars.next() != Some('#') {
104            return Err(IncorrectColor);
105        }
106        let mut d = Vec::new();
107        for char in chars {
108            d.push(char.to_digit(16).ok_or(IncorrectColor)? as u8);
109        }
110        Ok(match d.len() {
111            3 => {
112                Color {
113                    r: d[0],
114                    g: d[1],
115                    b: d[2],
116                    a: 255,
117                }
118            }
119            6 => {
120                Color {
121                    r: d[0] * 16 + d[1],
122                    g: d[2] * 16 + d[3],
123                    b: d[4] * 16 + d[5],
124                    a: 255,
125                }
126            }
127            8 => {
128                Color {
129                    r: d[0] * 16 + d[1],
130                    g: d[2] * 16 + d[3],
131                    b: d[4] * 16 + d[5],
132                    a: d[6] * 16 + d[7],
133                }
134            }
135            _ => return Err(IncorrectColor),
136        })
137    }
138}
139
140impl ParseSettings for Color {
141    type Error = ParseThemeError;
142
143    fn parse_settings(settings: Settings) -> Result<Color, Self::Error> {
144        match settings {
145            Settings::String(value) => Color::from_str(&value),
146            _ => Err(IncorrectColor),
147        }
148    }
149}
150
151impl ParseSettings for StyleModifier {
152    type Error = ParseThemeError;
153
154    fn parse_settings(settings: Settings) -> Result<StyleModifier, Self::Error> {
155        let mut obj = match settings {
156            Settings::Object(obj) => obj,
157            _ => return Err(ColorShemeScopeIsNotObject),
158        };
159        let font_style = match obj.remove("fontStyle") {
160            Some(Settings::String(value)) => Some(FontStyle::from_str(&value)?),
161            None => None,
162            Some(c) => return Err(IncorrectFontStyle(c.to_string())),
163        };
164        let foreground = match obj.remove("foreground") {
165            Some(Settings::String(value)) => Some(Color::from_str(&value)?),
166            None => None,
167            _ => return Err(IncorrectColor),
168        };
169        let background = match obj.remove("background") {
170            Some(Settings::String(value)) => Some(Color::from_str(&value)?),
171            None => None,
172            _ => return Err(IncorrectColor),
173        };
174
175        Ok(StyleModifier {
176            foreground,
177            background,
178            font_style,
179        })
180    }
181}
182
183impl ParseSettings for ThemeItem {
184    type Error = ParseThemeError;
185
186    fn parse_settings(settings: Settings) -> Result<ThemeItem, Self::Error> {
187        let mut obj = match settings {
188            Settings::Object(obj) => obj,
189            _ => return Err(ColorShemeScopeIsNotObject),
190        };
191        let scope = match obj.remove("scope") {
192            Some(Settings::String(value)) => ScopeSelectors::from_str(&value)?,
193            _ => return Err(ScopeSelectorIsNotString(format!("{:?}", obj))),
194        };
195        let style = match obj.remove("settings") {
196            Some(settings) => StyleModifier::parse_settings(settings)?,
197            None => return Err(IncorrectSettings),
198        };
199        Ok(ThemeItem {
200            scope,
201            style,
202        })
203    }
204}
205
206impl ParseSettings for ThemeSettings {
207    type Error = ParseThemeError;
208
209    fn parse_settings(json: Settings) -> Result<ThemeSettings, Self::Error> {
210        let mut settings = ThemeSettings::default();
211
212        let obj = match json {
213            Settings::Object(obj) => obj,
214            _ => return Err(ColorShemeSettingsIsNotObject),
215        };
216
217        for (key, value) in obj {
218            match &key[..] {
219                "foreground" => settings.foreground = Color::parse_settings(value).ok(),
220                "background" => settings.background = Color::parse_settings(value).ok(),
221                "caret" => settings.caret = Color::parse_settings(value).ok(),
222                "lineHighlight" => settings.line_highlight = Color::parse_settings(value).ok(),
223                "misspelling" => settings.misspelling = Color::parse_settings(value).ok(),
224                "minimapBorder" => settings.minimap_border = Color::parse_settings(value).ok(),
225                "accent" => settings.accent = Color::parse_settings(value).ok(),
226
227                "popupCss" => settings.popup_css = value.as_str().map(|s| s.to_owned()),
228                "phantomCss" => settings.phantom_css = value.as_str().map(|s| s.to_owned()),
229
230                "bracketContentsForeground" => {
231                    settings.bracket_contents_foreground = Color::parse_settings(value).ok()
232                }
233                "bracketContentsOptions" => {
234                    settings.bracket_contents_options = UnderlineOption::parse_settings(value).ok()
235                }
236                "bracketsForeground" => {
237                    settings.brackets_foreground = Color::parse_settings(value).ok()
238                }
239                "bracketsBackground" => {
240                    settings.brackets_background = Color::parse_settings(value).ok()
241                }
242                "bracketsOptions" => {
243                    settings.brackets_options = UnderlineOption::parse_settings(value).ok()
244                }
245                "tagsForeground" => settings.tags_foreground = Color::parse_settings(value).ok(),
246                "tagsOptions" => {
247                    settings.tags_options = UnderlineOption::parse_settings(value).ok()
248                }
249                "highlight" => settings.highlight = Color::parse_settings(value).ok(),
250                "findHighlight" => settings.find_highlight = Color::parse_settings(value).ok(),
251                "findHighlightForeground" => {
252                    settings.find_highlight_foreground = Color::parse_settings(value).ok()
253                }
254                "gutter" => settings.gutter = Color::parse_settings(value).ok(),
255                "gutterForeground" => {
256                    settings.gutter_foreground = Color::parse_settings(value).ok()
257                }
258                "selection" => settings.selection = Color::parse_settings(value).ok(),
259                "selectionForeground" => {
260                    settings.selection_foreground = Color::parse_settings(value).ok()
261                }
262                "selectionBorder" => settings.selection_border = Color::parse_settings(value).ok(),
263                "inactiveSelection" => {
264                    settings.inactive_selection = Color::parse_settings(value).ok()
265                }
266                "inactiveSelectionForeground" => {
267                    settings.inactive_selection_foreground = Color::parse_settings(value).ok()
268                }
269                "guide" => settings.guide = Color::parse_settings(value).ok(),
270                "activeGuide" => settings.active_guide = Color::parse_settings(value).ok(),
271                "stackGuide" => settings.stack_guide = Color::parse_settings(value).ok(),
272                "shadow" => settings.shadow = Color::parse_settings(value).ok(),
273                _ => (), // E.g. "shadowWidth" and "invisibles" are ignored
274            }
275        }
276        Ok(settings)
277    }
278}
279
280impl ParseSettings for Theme {
281    type Error = ParseThemeError;
282
283    fn parse_settings(settings: Settings) -> Result<Theme, Self::Error> {
284        let mut obj = match settings {
285            Settings::Object(obj) => obj,
286            _ => return Err(IncorrectSyntax),
287        };
288        let name = match obj.remove("name") {
289            Some(Settings::String(name)) => Some(name),
290            None => None,
291            _ => return Err(IncorrectSyntax),
292        };
293        let author = match obj.remove("author") {
294            Some(Settings::String(author)) => Some(author),
295            None => None,
296            _ => return Err(IncorrectSyntax),
297        };
298        let items = match obj.remove("settings") {
299            Some(Settings::Array(items)) => items,
300            _ => return Err(IncorrectSyntax),
301        };
302        let mut iter = items.into_iter();
303        let mut settings = match iter.next() {
304            Some(Settings::Object(mut obj)) => {
305                match obj.remove("settings") {
306                    Some(settings) => ThemeSettings::parse_settings(settings)?,
307                    None => return Err(UndefinedSettings),
308                }
309            }
310            _ => return Err(UndefinedSettings),
311        };
312        if let Some(Settings::Object(obj)) = obj.remove("gutterSettings") {
313            for (key, value) in obj {
314                let color = Color::parse_settings(value).ok();
315                match &key[..] {
316                    "background" => {
317                        settings.gutter = settings.gutter.or(color)
318                    }
319                    "foreground" => {
320                        settings.gutter_foreground = settings.gutter_foreground.or(color)
321                    }
322                    _ => (),
323                }
324            }
325        }
326        let mut scopes = Vec::new();
327        for json in iter {
328            // TODO option to disable best effort parsing and bubble up warnings
329            if let Ok(item) = ThemeItem::parse_settings(json) {
330                scopes.push(item);
331            }
332        }
333        Ok(Theme {
334            name,
335            author,
336            settings,
337            scopes,
338        })
339    }
340}