syntect 5.2.0

library for high quality syntax highlighting and code intelligence using Sublime Text's grammars
Documentation
// Code based on https://github.com/defuz/sublimate/blob/master/src/core/syntax/theme.rs
// released under the MIT license by @defuz

use std::str::FromStr;

use super::settings::{ParseSettings, Settings};
use super::style::*;
use super::selector::*;
use super::theme::*;
use crate::parsing::ParseScopeError;

use self::ParseThemeError::*;

#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ParseThemeError {
    #[error("Incorrect underline option")]
    IncorrectUnderlineOption,
    #[error("Incorrect font style: {0}")]
    IncorrectFontStyle(String),
    #[error("Incorrect color")]
    IncorrectColor,
    #[error("Incorrect syntax")]
    IncorrectSyntax,
    #[error("Incorrect settings")]
    IncorrectSettings,
    #[error("Undefined settings")]
    UndefinedSettings,
    #[error("Undefined scope settings: {0}")]
    UndefinedScopeSettings(String),
    #[error("Color sheme scope is not object")]
    ColorShemeScopeIsNotObject,
    #[error("Color sheme settings is not object")]
    ColorShemeSettingsIsNotObject,
    #[error("Scope selector is not string: {0}")]
    ScopeSelectorIsNotString(String),
    #[error("Duplicate settings")]
    DuplicateSettings,
    #[error("Scope parse error: {0}")]
    ScopeParse(#[from] ParseScopeError),
}


impl FromStr for UnderlineOption {
    type Err = ParseThemeError;

    fn from_str(s: &str) -> Result<UnderlineOption, Self::Err> {
        Ok(match s {
            "underline" => UnderlineOption::Underline,
            "stippled_underline" => UnderlineOption::StippledUnderline,
            "squiggly_underline" => UnderlineOption::SquigglyUnderline,
            _ => return Err(IncorrectUnderlineOption),
        })
    }
}

impl ParseSettings for UnderlineOption {
    type Error = ParseThemeError;

    fn parse_settings(settings: Settings) -> Result<UnderlineOption, Self::Error> {
        match settings {
            Settings::String(value) => UnderlineOption::from_str(&value),
            _ => Err(IncorrectUnderlineOption),
        }
    }
}

impl FromStr for FontStyle {
    type Err = ParseThemeError;

    fn from_str(s: &str) -> Result<FontStyle, Self::Err> {
        let mut font_style = FontStyle::empty();
        for i in s.split_whitespace() {
            font_style.insert(match i {
                "bold" => FontStyle::BOLD,
                "underline" => FontStyle::UNDERLINE,
                "italic" => FontStyle::ITALIC,
                "normal" |
                "regular" => FontStyle::empty(),
                s => return Err(IncorrectFontStyle(s.to_owned())),
            })
        }
        Ok(font_style)
    }
}

impl ParseSettings for FontStyle {
    type Error = ParseThemeError;

    fn parse_settings(settings: Settings) -> Result<FontStyle, Self::Error> {
        match settings {
            Settings::String(value) => FontStyle::from_str(&value),
            c => Err(IncorrectFontStyle(c.to_string())),
        }
    }
}

impl FromStr for Color {
    type Err = ParseThemeError;

    fn from_str(s: &str) -> Result<Color, Self::Err> {
        let mut chars = s.chars();
        if chars.next() != Some('#') {
            return Err(IncorrectColor);
        }
        let mut d = Vec::new();
        for char in chars {
            d.push(char.to_digit(16).ok_or(IncorrectColor)? as u8);
        }
        Ok(match d.len() {
            3 => {
                Color {
                    r: d[0],
                    g: d[1],
                    b: d[2],
                    a: 255,
                }
            }
            6 => {
                Color {
                    r: d[0] * 16 + d[1],
                    g: d[2] * 16 + d[3],
                    b: d[4] * 16 + d[5],
                    a: 255,
                }
            }
            8 => {
                Color {
                    r: d[0] * 16 + d[1],
                    g: d[2] * 16 + d[3],
                    b: d[4] * 16 + d[5],
                    a: d[6] * 16 + d[7],
                }
            }
            _ => return Err(IncorrectColor),
        })
    }
}

impl ParseSettings for Color {
    type Error = ParseThemeError;

    fn parse_settings(settings: Settings) -> Result<Color, Self::Error> {
        match settings {
            Settings::String(value) => Color::from_str(&value),
            _ => Err(IncorrectColor),
        }
    }
}

impl ParseSettings for StyleModifier {
    type Error = ParseThemeError;

    fn parse_settings(settings: Settings) -> Result<StyleModifier, Self::Error> {
        let mut obj = match settings {
            Settings::Object(obj) => obj,
            _ => return Err(ColorShemeScopeIsNotObject),
        };
        let font_style = match obj.remove("fontStyle") {
            Some(Settings::String(value)) => Some(FontStyle::from_str(&value)?),
            None => None,
            Some(c) => return Err(IncorrectFontStyle(c.to_string())),
        };
        let foreground = match obj.remove("foreground") {
            Some(Settings::String(value)) => Some(Color::from_str(&value)?),
            None => None,
            _ => return Err(IncorrectColor),
        };
        let background = match obj.remove("background") {
            Some(Settings::String(value)) => Some(Color::from_str(&value)?),
            None => None,
            _ => return Err(IncorrectColor),
        };

        Ok(StyleModifier {
            foreground,
            background,
            font_style,
        })
    }
}

impl ParseSettings for ThemeItem {
    type Error = ParseThemeError;

    fn parse_settings(settings: Settings) -> Result<ThemeItem, Self::Error> {
        let mut obj = match settings {
            Settings::Object(obj) => obj,
            _ => return Err(ColorShemeScopeIsNotObject),
        };
        let scope = match obj.remove("scope") {
            Some(Settings::String(value)) => ScopeSelectors::from_str(&value)?,
            _ => return Err(ScopeSelectorIsNotString(format!("{:?}", obj))),
        };
        let style = match obj.remove("settings") {
            Some(settings) => StyleModifier::parse_settings(settings)?,
            None => return Err(IncorrectSettings),
        };
        Ok(ThemeItem {
            scope,
            style,
        })
    }
}

impl ParseSettings for ThemeSettings {
    type Error = ParseThemeError;

    fn parse_settings(json: Settings) -> Result<ThemeSettings, Self::Error> {
        let mut settings = ThemeSettings::default();

        let obj = match json {
            Settings::Object(obj) => obj,
            _ => return Err(ColorShemeSettingsIsNotObject),
        };

        for (key, value) in obj {
            match &key[..] {
                "foreground" => settings.foreground = Color::parse_settings(value).ok(),
                "background" => settings.background = Color::parse_settings(value).ok(),
                "caret" => settings.caret = Color::parse_settings(value).ok(),
                "lineHighlight" => settings.line_highlight = Color::parse_settings(value).ok(),
                "misspelling" => settings.misspelling = Color::parse_settings(value).ok(),
                "minimapBorder" => settings.minimap_border = Color::parse_settings(value).ok(),
                "accent" => settings.accent = Color::parse_settings(value).ok(),

                "popupCss" => settings.popup_css = value.as_str().map(|s| s.to_owned()),
                "phantomCss" => settings.phantom_css = value.as_str().map(|s| s.to_owned()),

                "bracketContentsForeground" => {
                    settings.bracket_contents_foreground = Color::parse_settings(value).ok()
                }
                "bracketContentsOptions" => {
                    settings.bracket_contents_options = UnderlineOption::parse_settings(value).ok()
                }
                "bracketsForeground" => {
                    settings.brackets_foreground = Color::parse_settings(value).ok()
                }
                "bracketsBackground" => {
                    settings.brackets_background = Color::parse_settings(value).ok()
                }
                "bracketsOptions" => {
                    settings.brackets_options = UnderlineOption::parse_settings(value).ok()
                }
                "tagsForeground" => settings.tags_foreground = Color::parse_settings(value).ok(),
                "tagsOptions" => {
                    settings.tags_options = UnderlineOption::parse_settings(value).ok()
                }
                "highlight" => settings.highlight = Color::parse_settings(value).ok(),
                "findHighlight" => settings.find_highlight = Color::parse_settings(value).ok(),
                "findHighlightForeground" => {
                    settings.find_highlight_foreground = Color::parse_settings(value).ok()
                }
                "gutter" => settings.gutter = Color::parse_settings(value).ok(),
                "gutterForeground" => {
                    settings.gutter_foreground = Color::parse_settings(value).ok()
                }
                "selection" => settings.selection = Color::parse_settings(value).ok(),
                "selectionForeground" => {
                    settings.selection_foreground = Color::parse_settings(value).ok()
                }
                "selectionBorder" => settings.selection_border = Color::parse_settings(value).ok(),
                "inactiveSelection" => {
                    settings.inactive_selection = Color::parse_settings(value).ok()
                }
                "inactiveSelectionForeground" => {
                    settings.inactive_selection_foreground = Color::parse_settings(value).ok()
                }
                "guide" => settings.guide = Color::parse_settings(value).ok(),
                "activeGuide" => settings.active_guide = Color::parse_settings(value).ok(),
                "stackGuide" => settings.stack_guide = Color::parse_settings(value).ok(),
                "shadow" => settings.shadow = Color::parse_settings(value).ok(),
                _ => (), // E.g. "shadowWidth" and "invisibles" are ignored
            }
        }
        Ok(settings)
    }
}

impl ParseSettings for Theme {
    type Error = ParseThemeError;

    fn parse_settings(settings: Settings) -> Result<Theme, Self::Error> {
        let mut obj = match settings {
            Settings::Object(obj) => obj,
            _ => return Err(IncorrectSyntax),
        };
        let name = match obj.remove("name") {
            Some(Settings::String(name)) => Some(name),
            None => None,
            _ => return Err(IncorrectSyntax),
        };
        let author = match obj.remove("author") {
            Some(Settings::String(author)) => Some(author),
            None => None,
            _ => return Err(IncorrectSyntax),
        };
        let items = match obj.remove("settings") {
            Some(Settings::Array(items)) => items,
            _ => return Err(IncorrectSyntax),
        };
        let mut iter = items.into_iter();
        let mut settings = match iter.next() {
            Some(Settings::Object(mut obj)) => {
                match obj.remove("settings") {
                    Some(settings) => ThemeSettings::parse_settings(settings)?,
                    None => return Err(UndefinedSettings),
                }
            }
            _ => return Err(UndefinedSettings),
        };
        if let Some(Settings::Object(obj)) = obj.remove("gutterSettings") {
            for (key, value) in obj {
                let color = Color::parse_settings(value).ok();
                match &key[..] {
                    "background" => {
                        settings.gutter = settings.gutter.or(color)
                    }
                    "foreground" => {
                        settings.gutter_foreground = settings.gutter_foreground.or(color)
                    }
                    _ => (),
                }
            }
        }
        let mut scopes = Vec::new();
        for json in iter {
            // TODO option to disable best effort parsing and bubble up warnings
            if let Ok(item) = ThemeItem::parse_settings(json) {
                scopes.push(item);
            }
        }
        Ok(Theme {
            name,
            author,
            settings,
            scopes,
        })
    }
}