1mod assets;
2mod color;
3mod vscode;
4
5use std::fs::File;
6use std::io::Write;
7use std::path::PathBuf;
8
9use anyhow::{Context as _, Result};
10use clap::Parser;
11use indexmap::IndexMap;
12use log::LevelFilter;
13use serde::Deserialize;
14use simplelog::ColorChoice;
15use simplelog::{TermLogger, TerminalMode};
16use theme::{Appearance, AppearanceContent};
17
18use crate::vscode::VsCodeTheme;
19use crate::vscode::VsCodeThemeConverter;
20
21const ZED_THEME_SCHEMA_URL: &str = "https://zed.dev/schema/themes/v0.2.0.json";
22
23#[derive(Debug, Deserialize)]
24struct FamilyMetadata {
25 pub name: String,
26 pub author: String,
27 pub themes: Vec<ThemeMetadata>,
28
29 /// Overrides for specific syntax tokens.
30 ///
31 /// Use this to ensure certain Zed syntax tokens are matched
32 /// to an exact set of scopes when it is not otherwise possible
33 /// to rely on the default mappings in the theme importer.
34 #[serde(default)]
35 pub syntax: IndexMap<String, Vec<String>>,
36}
37
38#[derive(Debug, Clone, Copy, Deserialize)]
39#[serde(rename_all = "snake_case")]
40pub enum ThemeAppearanceJson {
41 Light,
42 Dark,
43}
44
45impl From<ThemeAppearanceJson> for AppearanceContent {
46 fn from(value: ThemeAppearanceJson) -> Self {
47 match value {
48 ThemeAppearanceJson::Light => Self::Light,
49 ThemeAppearanceJson::Dark => Self::Dark,
50 }
51 }
52}
53
54impl From<ThemeAppearanceJson> for Appearance {
55 fn from(value: ThemeAppearanceJson) -> Self {
56 match value {
57 ThemeAppearanceJson::Light => Self::Light,
58 ThemeAppearanceJson::Dark => Self::Dark,
59 }
60 }
61}
62
63#[derive(Debug, Deserialize)]
64pub struct ThemeMetadata {
65 pub name: String,
66 pub file_name: String,
67 pub appearance: ThemeAppearanceJson,
68}
69
70#[derive(Parser)]
71#[command(author, version, about, long_about = None)]
72struct Args {
73 /// The path to the theme to import.
74 theme_path: PathBuf,
75
76 /// Whether to warn when values are missing from the theme.
77 #[arg(long)]
78 warn_on_missing: bool,
79
80 /// The path to write the output to.
81 #[arg(long, short)]
82 output: Option<PathBuf>,
83}
84
85fn main() -> Result<()> {
86 let args = Args::parse();
87
88 let log_config = {
89 let mut config = simplelog::ConfigBuilder::new();
90
91 if !args.warn_on_missing {
92 config.add_filter_ignore_str("theme_printer");
93 }
94
95 config.build()
96 };
97
98 TermLogger::init(
99 LevelFilter::Trace,
100 log_config,
101 TerminalMode::Stderr,
102 ColorChoice::Auto,
103 )
104 .expect("could not initialize logger");
105
106 let theme_file_path = args.theme_path;
107
108 let theme_file = match File::open(&theme_file_path) {
109 Ok(file) => file,
110 Err(err) => {
111 log::info!("Failed to open file at path: {:?}", theme_file_path);
112 return Err(err)?;
113 }
114 };
115
116 let vscode_theme: VsCodeTheme = serde_json_lenient::from_reader(theme_file)
117 .context(format!("failed to parse theme {theme_file_path:?}"))?;
118
119 let theme_metadata = ThemeMetadata {
120 name: vscode_theme.name.clone().unwrap_or("".to_string()),
121 appearance: ThemeAppearanceJson::Dark,
122 file_name: "".to_string(),
123 };
124
125 let converter = VsCodeThemeConverter::new(vscode_theme, theme_metadata, IndexMap::new());
126
127 let theme = converter.convert()?;
128 let mut theme = serde_json::to_value(theme).unwrap();
129 theme.as_object_mut().unwrap().insert(
130 "$schema".to_string(),
131 serde_json::Value::String(ZED_THEME_SCHEMA_URL.to_string()),
132 );
133 let theme_json = serde_json::to_string_pretty(&theme).unwrap();
134
135 if let Some(output) = args.output {
136 let mut file = File::create(output)?;
137 file.write_all(theme_json.as_bytes())?;
138 } else {
139 println!("{}", theme_json);
140 }
141
142 log::info!("Done!");
143
144 Ok(())
145}