main.rs

  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}