theme_converter/
vscode.rs

1use std::fs;
2
3use plist::{Dictionary, Value};
4use serde::Deserialize;
5
6use crate::parser::Parser;
7
8#[derive(Debug, Deserialize)]
9#[serde(untagged)]
10enum TokenColour {
11    Single {
12        scope: String,
13        settings: Settings,
14    },
15    Multi {
16        scope: Vec<String>,
17        settings: Settings,
18    },
19}
20
21#[derive(Debug, Deserialize)]
22struct VSCodeTheme {
23    #[serde(default)]
24    name: Option<String>,
25    #[serde(default)]
26    colors: serde_json::Value,
27    #[serde(rename = "tokenColors", default)]
28    token_colors: Vec<TokenColour>,
29}
30
31#[derive(Debug, Clone, Deserialize)]
32struct Settings {
33    #[serde(default)]
34    foreground: Option<String>,
35    #[serde(default)]
36    background: Option<String>,
37    #[serde(default, rename = "fontStyle")]
38    font_style: Option<String>,
39}
40
41pub struct VSCodeThemeParser {
42    theme: VSCodeTheme,
43}
44
45impl Parser for VSCodeThemeParser {
46    fn parse(&self, name: &str) -> Dictionary {
47        let get_color = |key: &str| -> Option<String> {
48            self.theme
49                .colors
50                .get(key)
51                .and_then(|v| v.as_str())
52                .map(|s| s.into())
53        };
54
55        let global_settings = Some(self.build_settings_dict(
56            "",
57            &Settings {
58                foreground: get_color("editor.foreground"),
59                background: get_color("editor.background"),
60                font_style: get_color("editor.fontStyle"),
61            },
62        ));
63
64        let token_settings = self.theme.token_colors.iter().map(|tok| match tok {
65            TokenColour::Single { scope, settings } => self.build_settings_dict(scope, settings),
66            TokenColour::Multi { scope, settings } => {
67                self.build_settings_dict(&scope.join(", "), settings)
68            }
69        });
70
71        let settings_array: Vec<Value> =
72            global_settings.into_iter().chain(token_settings).collect();
73
74        Dictionary::from_iter([
75            ("name", Value::String(name.to_string())),
76            ("uuid", Value::String(uuid::Uuid::new_v4().to_string())),
77            ("settings", Value::Array(settings_array)),
78        ])
79    }
80
81    fn from_config(file_name: &str) -> anyhow::Result<Self> {
82        let theme: VSCodeTheme = serde_json::from_str(&fs::read_to_string(file_name)?)?;
83
84        Ok(Self { theme })
85    }
86}
87
88impl VSCodeThemeParser {
89    fn build_settings_dict(&self, scope: impl Into<String>, s: &Settings) -> Value {
90        let inner = Dictionary::from_iter(
91            [
92                s.foreground
93                    .clone()
94                    .map(|v| ("foreground", Value::String(v))),
95                s.background
96                    .clone()
97                    .map(|v| ("background", Value::String(v))),
98                s.font_style
99                    .clone()
100                    .map(|v| ("fontStyle", Value::String(v))),
101            ]
102            .into_iter()
103            .flatten(),
104        );
105
106        Value::Dictionary(Dictionary::from_iter([
107            ("scope", Value::String(scope.into())),
108            ("settings", Value::Dictionary(inner)),
109        ]))
110    }
111}