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) -> 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            (
76                "name",
77                Value::String(
78                    self.theme
79                        .name
80                        .clone()
81                        .unwrap_or_else(|| "Converted Theme".into()),
82                ),
83            ),
84            ("uuid", Value::String(uuid::Uuid::new_v4().to_string())),
85            ("settings", Value::Array(settings_array)),
86        ])
87    }
88
89    fn from_config(file_name: &str) -> anyhow::Result<Self> {
90        let theme: VSCodeTheme = serde_json::from_str(&fs::read_to_string(file_name)?)?;
91
92        Ok(Self { theme })
93    }
94}
95
96impl VSCodeThemeParser {
97    fn build_settings_dict(&self, scope: impl Into<String>, s: &Settings) -> Value {
98        let inner = Dictionary::from_iter(
99            [
100                s.foreground
101                    .clone()
102                    .map(|v| ("foreground", Value::String(v))),
103                s.background
104                    .clone()
105                    .map(|v| ("background", Value::String(v))),
106                s.font_style
107                    .clone()
108                    .map(|v| ("fontStyle", Value::String(v))),
109            ]
110            .into_iter()
111            .flatten(),
112        );
113
114        Value::Dictionary(Dictionary::from_iter([
115            ("scope", Value::String(scope.into())),
116            ("settings", Value::Dictionary(inner)),
117        ]))
118    }
119}