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