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