mottle 0.5.0

Create themes for VS Code with ease
Documentation
use super::Color;
use indexmap::IndexMap;
use serde::ser::SerializeStruct;
use serde::Serialize;
use std::borrow::Cow;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Highlighting {
    Off,
    On { rules: IndexMap<Selector, Style> },
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Selector {
    pub kind: TokenKind,
    pub modifiers: Vec<Identifier>,
    pub language: Option<Identifier>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Style {
    pub foreground: Option<Color>,
    pub font_style: FontStyle,
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum TokenKind {
    Wildcard,
    Specific(Identifier),
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Identifier(Cow<'static, str>);

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FontStyle {
    pub bold: FontStyleSetting,
    pub italic: FontStyleSetting,
    pub underline: FontStyleSetting,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FontStyleSetting {
    True,
    False,
    Inherit,
}

impl Serialize for Highlighting {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        match self {
            Self::Off => {
                let mut strukt = serializer.serialize_struct("SemanticHighlighting", 1)?;
                strukt.serialize_field("semanticHighlighting", &false)?;
                strukt.end()
            }

            Self::On { rules } => {
                let mut strukt = serializer.serialize_struct("SemanticHighlighting", 2)?;
                strukt.serialize_field("semanticHighlighting", &true)?;
                strukt.serialize_field("semanticTokenColors", &rules)?;
                strukt.end()
            }
        }
    }
}

impl Serialize for Selector {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let mut s = String::new();

        match &self.kind {
            TokenKind::Wildcard => s.push('*'),
            TokenKind::Specific(kind) => s.push_str(&kind.0),
        }

        for modifier in &self.modifiers {
            s.push('.');
            s.push_str(&modifier.0);
        }

        if let Some(language) = &self.language {
            s.push(':');
            s.push_str(&language.0);
        }

        serializer.serialize_str(&s)
    }
}

impl Serialize for Style {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let mut strukt = serializer.serialize_struct("Style", 1)?;

        if let Some(foreground) = self.foreground {
            strukt.serialize_field("foreground", &foreground)?;
        }

        match self.font_style.bold {
            FontStyleSetting::True => strukt.serialize_field("bold", &true)?,
            FontStyleSetting::False => strukt.serialize_field("bold", &false)?,
            FontStyleSetting::Inherit => {}
        }

        match self.font_style.italic {
            FontStyleSetting::True => strukt.serialize_field("italic", &true)?,
            FontStyleSetting::False => strukt.serialize_field("italic", &false)?,
            FontStyleSetting::Inherit => {}
        }

        match self.font_style.underline {
            FontStyleSetting::True => strukt.serialize_field("underline", &true)?,
            FontStyleSetting::False => strukt.serialize_field("underline", &false)?,
            FontStyleSetting::Inherit => {}
        }

        strukt.end()
    }
}

impl Identifier {
    pub fn new(s: impl Into<Cow<'static, str>>) -> Result<Self, String> {
        let s = s.into();

        for c in s.chars() {
            if !c.is_ascii_alphanumeric() && c != '-' {
                return Err(format!("invalid character in ‘{s}"));
            }
        }

        Ok(Self(s))
    }
}