theme_converter/
vscode.rs1use 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}