1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::fmt;
4
5use anyhow::{anyhow, Context, Result};
6use clap::Parser;
7use gpui2::Hsla;
8use gpui2::{serde_json, AssetSource, SharedString};
9use log::LevelFilter;
10use rust_embed::RustEmbed;
11use serde::de::Visitor;
12use serde::{Deserialize, Deserializer};
13use simplelog::SimpleLogger;
14
15#[derive(Parser)]
16#[command(author, version, about, long_about = None)]
17struct Args {
18 /// The name of the theme to convert.
19 theme: String,
20}
21
22fn main() -> Result<()> {
23 SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
24
25 let args = Args::parse();
26
27 let theme = load_theme(args.theme)?;
28
29 Ok(())
30}
31
32#[derive(RustEmbed)]
33#[folder = "../../assets"]
34#[include = "fonts/**/*"]
35#[include = "icons/**/*"]
36#[include = "themes/**/*"]
37#[include = "sounds/**/*"]
38#[include = "*.md"]
39#[exclude = "*.DS_Store"]
40pub struct Assets;
41
42impl AssetSource for Assets {
43 fn load(&self, path: &str) -> Result<Cow<[u8]>> {
44 Self::get(path)
45 .map(|f| f.data)
46 .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
47 }
48
49 fn list(&self, path: &str) -> Result<Vec<SharedString>> {
50 Ok(Self::iter()
51 .filter(|p| p.starts_with(path))
52 .map(SharedString::from)
53 .collect())
54 }
55}
56
57fn convert_theme(theme: LegacyTheme) -> Result<theme2::Theme> {
58 let theme = theme2::Theme {
59
60 }
61}
62
63#[derive(Deserialize)]
64struct JsonTheme {
65 pub base_theme: serde_json::Value,
66}
67
68/// Loads the [`Theme`] with the given name.
69pub fn load_theme(name: String) -> Result<LegacyTheme> {
70 let theme_contents = Assets::get(&format!("themes/{name}.json"))
71 .with_context(|| format!("theme file not found: '{name}'"))?;
72
73 let json_theme: JsonTheme = serde_json::from_str(std::str::from_utf8(&theme_contents.data)?)
74 .context("failed to parse legacy theme")?;
75
76 let legacy_theme: LegacyTheme = serde_json::from_value(json_theme.base_theme.clone())
77 .context("failed to parse `base_theme`")?;
78
79 Ok(legacy_theme)
80}
81
82#[derive(Deserialize, Clone, Default, Debug)]
83pub struct LegacyTheme {
84 pub name: String,
85 pub is_light: bool,
86 pub lowest: Layer,
87 pub middle: Layer,
88 pub highest: Layer,
89 pub popover_shadow: Shadow,
90 pub modal_shadow: Shadow,
91 #[serde(deserialize_with = "deserialize_player_colors")]
92 pub players: Vec<PlayerColors>,
93 #[serde(deserialize_with = "deserialize_syntax_colors")]
94 pub syntax: HashMap<String, Hsla>,
95}
96
97#[derive(Deserialize, Clone, Default, Debug)]
98pub struct Layer {
99 pub base: StyleSet,
100 pub variant: StyleSet,
101 pub on: StyleSet,
102 pub accent: StyleSet,
103 pub positive: StyleSet,
104 pub warning: StyleSet,
105 pub negative: StyleSet,
106}
107
108#[derive(Deserialize, Clone, Default, Debug)]
109pub struct StyleSet {
110 #[serde(rename = "default")]
111 pub default: ContainerColors,
112 pub hovered: ContainerColors,
113 pub pressed: ContainerColors,
114 pub active: ContainerColors,
115 pub disabled: ContainerColors,
116 pub inverted: ContainerColors,
117}
118
119#[derive(Deserialize, Clone, Default, Debug)]
120pub struct ContainerColors {
121 pub background: Hsla,
122 pub foreground: Hsla,
123 pub border: Hsla,
124}
125
126#[derive(Deserialize, Clone, Default, Debug)]
127pub struct PlayerColors {
128 pub selection: Hsla,
129 pub cursor: Hsla,
130}
131
132#[derive(Deserialize, Clone, Default, Debug)]
133pub struct Shadow {
134 pub blur: u8,
135 pub color: Hsla,
136 pub offset: Vec<u8>,
137}
138
139fn deserialize_player_colors<'de, D>(deserializer: D) -> Result<Vec<PlayerColors>, D::Error>
140where
141 D: Deserializer<'de>,
142{
143 struct PlayerArrayVisitor;
144
145 impl<'de> Visitor<'de> for PlayerArrayVisitor {
146 type Value = Vec<PlayerColors>;
147
148 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
149 formatter.write_str("an object with integer keys")
150 }
151
152 fn visit_map<A: serde::de::MapAccess<'de>>(
153 self,
154 mut map: A,
155 ) -> Result<Self::Value, A::Error> {
156 let mut players = Vec::with_capacity(8);
157 while let Some((key, value)) = map.next_entry::<usize, PlayerColors>()? {
158 if key < 8 {
159 players.push(value);
160 } else {
161 return Err(serde::de::Error::invalid_value(
162 serde::de::Unexpected::Unsigned(key as u64),
163 &"a key in range 0..7",
164 ));
165 }
166 }
167 Ok(players)
168 }
169 }
170
171 deserializer.deserialize_map(PlayerArrayVisitor)
172}
173
174fn deserialize_syntax_colors<'de, D>(deserializer: D) -> Result<HashMap<String, Hsla>, D::Error>
175where
176 D: serde::Deserializer<'de>,
177{
178 #[derive(Deserialize)]
179 struct ColorWrapper {
180 color: Hsla,
181 }
182
183 struct SyntaxVisitor;
184
185 impl<'de> Visitor<'de> for SyntaxVisitor {
186 type Value = HashMap<String, Hsla>;
187
188 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
189 formatter.write_str("a map with keys and objects with a single color field as values")
190 }
191
192 fn visit_map<M>(self, mut map: M) -> Result<HashMap<String, Hsla>, M::Error>
193 where
194 M: serde::de::MapAccess<'de>,
195 {
196 let mut result = HashMap::new();
197 while let Some(key) = map.next_key()? {
198 let wrapper: ColorWrapper = map.next_value()?; // Deserialize values as Hsla
199 result.insert(key, wrapper.color);
200 }
201 Ok(result)
202 }
203 }
204 deserializer.deserialize_map(SyntaxVisitor)
205}