1mod assets;
2mod color;
3mod theme_printer;
4mod util;
5mod vscode;
6mod zed1;
7
8use std::collections::HashMap;
9use std::fs::{self, File};
10use std::io::Write;
11use std::path::PathBuf;
12use std::process::Command;
13use std::str::FromStr;
14
15use any_ascii::any_ascii;
16use anyhow::{anyhow, Context, Result};
17use clap::Parser;
18use convert_case::{Case, Casing};
19use gpui::Hsla;
20use indexmap::IndexMap;
21use indoc::formatdoc;
22use json_comments::StripComments;
23use log::LevelFilter;
24use serde::Deserialize;
25use simplelog::{TermLogger, TerminalMode};
26use theme::{
27 Appearance, FontWeightContent, HighlightStyleContent, PlayerColorContent, StatusColorsContent,
28 ThemeColorsContent, ThemeContent, ThemeFamilyContent, ThemeStyleContent, UserTheme,
29 UserThemeFamily,
30};
31
32use crate::theme_printer::UserThemeFamilyPrinter;
33use crate::vscode::VsCodeTheme;
34use crate::vscode::VsCodeThemeConverter;
35use crate::zed1::theme::Theme as Zed1Theme;
36use crate::zed1::{zed1_theme_licenses, Zed1ThemeConverter};
37
38#[derive(Debug, Deserialize)]
39struct FamilyMetadata {
40 pub name: String,
41 pub author: String,
42 pub themes: Vec<ThemeMetadata>,
43
44 /// Overrides for specific syntax tokens.
45 ///
46 /// Use this to ensure certain Zed syntax tokens are matched
47 /// to an exact set of scopes when it is not otherwise possible
48 /// to rely on the default mappings in the theme importer.
49 #[serde(default)]
50 pub syntax: IndexMap<String, Vec<String>>,
51}
52
53#[derive(Debug, Clone, Copy, Deserialize)]
54#[serde(rename_all = "snake_case")]
55pub enum ThemeAppearanceJson {
56 Light,
57 Dark,
58}
59
60impl From<ThemeAppearanceJson> for Appearance {
61 fn from(value: ThemeAppearanceJson) -> Self {
62 match value {
63 ThemeAppearanceJson::Light => Self::Light,
64 ThemeAppearanceJson::Dark => Self::Dark,
65 }
66 }
67}
68
69#[derive(Debug, Deserialize)]
70pub struct ThemeMetadata {
71 pub name: String,
72 pub file_name: String,
73 pub appearance: ThemeAppearanceJson,
74}
75
76#[derive(Parser)]
77#[command(author, version, about, long_about = None)]
78struct Args {
79 /// Whether to warn when values are missing from the theme.
80 #[arg(long)]
81 warn_on_missing: bool,
82}
83
84fn main() -> Result<()> {
85 const SOURCE_PATH: &str = "assets/themes/src/vscode";
86 const OUT_PATH: &str = "crates/theme/src/themes";
87
88 let args = Args::parse();
89
90 let log_config = {
91 let mut config = simplelog::ConfigBuilder::new();
92 config
93 .set_level_color(log::Level::Trace, simplelog::Color::Cyan)
94 .set_level_color(log::Level::Info, simplelog::Color::Blue)
95 .set_level_color(log::Level::Warn, simplelog::Color::Yellow)
96 .set_level_color(log::Level::Error, simplelog::Color::Red);
97
98 if !args.warn_on_missing {
99 config.add_filter_ignore_str("theme_printer");
100 }
101
102 config.build()
103 };
104
105 TermLogger::init(LevelFilter::Trace, log_config, TerminalMode::Mixed)
106 .expect("could not initialize logger");
107
108 if 1 < 2 {
109 let themes: Vec<UserThemeFamily> = Vec::new();
110 // Uncomment this line when you need to regenerate themes.
111 // let themes = theme::all_user_themes();
112
113 let mut families = Vec::new();
114
115 for family in themes {
116 families.push(convert_family(family));
117 }
118
119 for family in families {
120 let theme_family_slug = any_ascii(&family.name)
121 .replace("(", "")
122 .replace(")", "")
123 .to_case(Case::Snake);
124
125 let output_dir = PathBuf::from("assets/themes/").join(&theme_family_slug);
126
127 fs::create_dir_all(&output_dir)?;
128
129 let mut output_file =
130 File::create(output_dir.join(format!("{theme_family_slug}.json")))?;
131
132 let theme_json = serde_json::to_string_pretty(&family).unwrap();
133
134 output_file.write_all(format!("{theme_json}\n").as_bytes())?;
135 }
136
137 return Ok(());
138 }
139
140 let mut theme_families = Vec::new();
141
142 /// Whether VS Code themes should be imported.
143 const IMPORT_VS_CODE_THEMES: bool = false;
144
145 if IMPORT_VS_CODE_THEMES {
146 log::info!("Loading themes source...");
147 let vscode_themes_path = PathBuf::from_str(SOURCE_PATH)?;
148 if !vscode_themes_path.exists() {
149 return Err(anyhow!(format!(
150 "Couldn't find {}, make sure it exists",
151 SOURCE_PATH
152 )));
153 }
154
155 for theme_family_dir in fs::read_dir(&vscode_themes_path)? {
156 let theme_family_dir = theme_family_dir?;
157
158 if !theme_family_dir.file_type()?.is_dir() {
159 continue;
160 }
161
162 let theme_family_slug = theme_family_dir
163 .path()
164 .file_stem()
165 .ok_or(anyhow!("no file stem"))
166 .map(|stem| stem.to_string_lossy().to_string())?;
167
168 let family_metadata_file = File::open(theme_family_dir.path().join("family.json"))
169 .context(format!(
170 "no `family.json` found for '{}'",
171 theme_family_slug
172 ))?;
173
174 let license_file_path = theme_family_dir.path().join("LICENSE");
175
176 if !license_file_path.exists() {
177 log::info!("Skipping theme family '{}' because it does not have a LICENSE file. This theme will only be imported once a LICENSE file is provided.", theme_family_slug);
178 continue;
179 }
180
181 let family_metadata: FamilyMetadata = serde_json::from_reader(family_metadata_file)
182 .context(format!(
183 "failed to parse `family.json` for '{theme_family_slug}'"
184 ))?;
185
186 let mut themes = Vec::new();
187
188 for theme_metadata in family_metadata.themes {
189 log::info!("Converting '{}' theme", &theme_metadata.name);
190
191 let theme_file_path = theme_family_dir.path().join(&theme_metadata.file_name);
192
193 let theme_file = match File::open(&theme_file_path) {
194 Ok(file) => file,
195 Err(_) => {
196 log::info!("Failed to open file at path: {:?}", theme_file_path);
197 continue;
198 }
199 };
200
201 let theme_without_comments = StripComments::new(theme_file);
202 let vscode_theme: VsCodeTheme = serde_json::from_reader(theme_without_comments)
203 .context(format!("failed to parse theme {theme_file_path:?}"))?;
204
205 let converter = VsCodeThemeConverter::new(
206 vscode_theme,
207 theme_metadata,
208 family_metadata.syntax.clone(),
209 );
210
211 let theme = converter.convert()?;
212
213 themes.push(theme);
214 }
215
216 let theme_family = UserThemeFamily {
217 name: family_metadata.name.into(),
218 author: family_metadata.author.into(),
219 themes,
220 };
221
222 theme_families.push(theme_family);
223 }
224 }
225
226 let zed1_themes_path = PathBuf::from_str("assets/themes")?;
227
228 let zed1_theme_families = [
229 "Andromeda",
230 "Atelier",
231 "Ayu",
232 "Gruvbox",
233 "One",
234 "Rosé Pine",
235 "Sandcastle",
236 "Solarized",
237 "Summercamp",
238 ];
239
240 let zed1_licenses_by_theme: HashMap<String, zed1::Zed1ThemeLicense> = HashMap::from_iter(
241 zed1_theme_licenses()
242 .into_iter()
243 .map(|theme_license| (theme_license.theme.clone(), theme_license)),
244 );
245
246 let mut zed1_themes_by_family: IndexMap<String, Vec<UserTheme>> = IndexMap::from_iter(
247 zed1_theme_families
248 .into_iter()
249 .map(|family| (family.to_string(), Vec::new())),
250 );
251
252 for entry in fs::read_dir(&zed1_themes_path)? {
253 let entry = entry?;
254
255 if entry.file_type()?.is_dir() {
256 continue;
257 }
258
259 match entry.path().extension() {
260 None => continue,
261 Some(extension) => {
262 if extension != "json" {
263 continue;
264 }
265 }
266 }
267
268 let theme_file_path = entry.path();
269
270 let theme_file = match File::open(&theme_file_path) {
271 Ok(file) => file,
272 Err(_) => {
273 log::info!("Failed to open file at path: {:?}", theme_file_path);
274 continue;
275 }
276 };
277
278 let theme_without_comments = StripComments::new(theme_file);
279
280 let zed1_theme: Zed1Theme = serde_json::from_reader(theme_without_comments)
281 .context(format!("failed to parse theme {theme_file_path:?}"))?;
282
283 let theme_name = zed1_theme.meta.name.clone();
284
285 let converter = Zed1ThemeConverter::new(zed1_theme);
286
287 let theme = converter.convert()?;
288
289 let Some((_, themes_for_family)) = zed1_themes_by_family
290 .iter_mut()
291 .find(|(family, _)| theme_name.starts_with(*family))
292 else {
293 log::warn!("No theme family found for '{}'.", theme_name);
294 continue;
295 };
296
297 themes_for_family.push(theme);
298 }
299
300 zed1_themes_by_family.sort_keys();
301
302 let mut licenses = Vec::new();
303
304 for (family, themes) in zed1_themes_by_family {
305 let mut theme_family = UserThemeFamily {
306 name: family,
307 author: "Zed Industries".to_string(),
308 themes,
309 };
310
311 theme_family
312 .themes
313 .sort_unstable_by_key(|theme| theme.name.clone());
314
315 for theme in &theme_family.themes {
316 let license = zed1_licenses_by_theme
317 .get(&theme.name)
318 .ok_or_else(|| anyhow!("missing license for theme: '{}'", theme.name))?;
319
320 let license_header = match license.license_url.as_ref() {
321 Some(license_url) => {
322 format!("[{theme_name}]({license_url})", theme_name = theme.name)
323 }
324 None => theme.name.clone(),
325 };
326
327 licenses.push(formatdoc!(
328 "
329 ## {license_header}
330
331 {license_text}
332 ********************************************************************************
333 ",
334 license_text = license.license_text
335 ));
336 }
337
338 theme_families.push(theme_family);
339 }
340
341 let themes_output_path = PathBuf::from_str(OUT_PATH)?;
342
343 if !themes_output_path.exists() {
344 log::info!("Creating directory: {:?}", themes_output_path);
345 fs::create_dir_all(&themes_output_path)?;
346 }
347
348 let mut mod_rs_file = File::create(themes_output_path.join(format!("mod.rs")))?;
349
350 let mut theme_modules = Vec::new();
351
352 for theme_family in theme_families {
353 let theme_family_slug = any_ascii(&theme_family.name)
354 .replace("(", "")
355 .replace(")", "")
356 .to_case(Case::Snake);
357
358 let mut output_file =
359 File::create(themes_output_path.join(format!("{theme_family_slug}.rs")))?;
360 log::info!(
361 "Creating file: {:?}",
362 themes_output_path.join(format!("{theme_family_slug}.rs"))
363 );
364
365 let theme_module = format!(
366 r#"
367 // This file was generated by the `theme_importer`.
368 // Be careful when modifying it by hand.
369
370 use gpui::rgba;
371
372 #[allow(unused)]
373 use crate::{{
374 Appearance, PlayerColor, PlayerColors, StatusColorsRefinement, ThemeColorsRefinement,
375 UserHighlightStyle, UserSyntaxTheme, UserTheme, UserThemeFamily, UserThemeStylesRefinement,
376 UserFontWeight, UserFontStyle
377 }};
378
379 pub fn {theme_family_slug}() -> UserThemeFamily {{
380 {theme_family_definition}
381 }}
382 "#,
383 theme_family_definition = format!("{:#?}", UserThemeFamilyPrinter::new(theme_family))
384 );
385
386 output_file.write_all(theme_module.as_bytes())?;
387
388 theme_modules.push(theme_family_slug);
389 }
390
391 theme_modules.sort();
392
393 let themes_vector_contents = format!(
394 r#"
395 use crate::UserThemeFamily;
396
397 pub(crate) fn all_user_themes() -> Vec<UserThemeFamily> {{
398 vec![{all_themes}]
399 }}
400 "#,
401 all_themes = theme_modules
402 .iter()
403 .map(|module| format!("{}()", module))
404 .collect::<Vec<_>>()
405 .join(", ")
406 );
407
408 let mod_rs_contents = format!(
409 r#"
410 // This file was generated by the `theme_importer`.
411 // Be careful when modifying it by hand.
412
413 {mod_statements}
414
415 {use_statements}
416
417 {themes_vector_contents}
418 "#,
419 mod_statements = theme_modules
420 .iter()
421 .map(|module| format!("mod {module};"))
422 .collect::<Vec<_>>()
423 .join("\n"),
424 use_statements = theme_modules
425 .iter()
426 .map(|module| format!("pub use {module}::*;"))
427 .collect::<Vec<_>>()
428 .join("\n"),
429 themes_vector_contents = themes_vector_contents
430 );
431
432 mod_rs_file.write_all(mod_rs_contents.as_bytes())?;
433
434 log::info!("Writing LICENSES file...");
435
436 let mut licenses_file = File::create(themes_output_path.join(format!("LICENSES")))?;
437
438 licenses_file.write_all(licenses.join("\n").as_bytes())?;
439
440 log::info!("Formatting themes...");
441
442 let format_result = format_themes_crate()
443 // We need to format a second time to catch all of the formatting issues.
444 .and_then(|_| format_themes_crate());
445
446 if let Err(err) = format_result {
447 log::error!("Failed to format themes: {}", err);
448 }
449
450 log::info!("Done!");
451
452 Ok(())
453}
454
455fn format_themes_crate() -> std::io::Result<std::process::Output> {
456 Command::new("cargo")
457 .args(["fmt", "--package", "theme"])
458 .output()
459}
460
461fn convert_family(family: UserThemeFamily) -> ThemeFamilyContent {
462 ThemeFamilyContent {
463 name: family.name,
464 author: family.author,
465 themes: family.themes.into_iter().map(convert_theme).collect(),
466 }
467}
468
469fn convert_theme(theme: UserTheme) -> ThemeContent {
470 ThemeContent {
471 name: theme.name,
472 appearance: match theme.appearance {
473 Appearance::Light => theme::AppearanceContent::Light,
474 Appearance::Dark => theme::AppearanceContent::Dark,
475 },
476 style: convert_theme_styles(theme.styles),
477 }
478}
479
480fn serialize_color(color: Hsla) -> String {
481 let rgba = color.to_rgb();
482 format!("#{:08x}", u32::from(rgba))
483}
484
485fn convert_theme_styles(styles: theme::UserThemeStylesRefinement) -> ThemeStyleContent {
486 ThemeStyleContent {
487 colors: ThemeColorsContent {
488 border: styles.colors.border.map(serialize_color),
489 border_variant: styles.colors.border_variant.map(serialize_color),
490 border_focused: styles.colors.border_focused.map(serialize_color),
491 border_selected: styles.colors.border_selected.map(serialize_color),
492 border_transparent: styles.colors.border_transparent.map(serialize_color),
493 border_disabled: styles.colors.border_disabled.map(serialize_color),
494 elevated_surface_background: styles
495 .colors
496 .elevated_surface_background
497 .map(serialize_color),
498 surface_background: styles.colors.surface_background.map(serialize_color),
499 background: styles.colors.background.map(serialize_color),
500 element_background: styles.colors.element_background.map(serialize_color),
501 element_hover: styles.colors.element_hover.map(serialize_color),
502 element_active: styles.colors.element_active.map(serialize_color),
503 element_selected: styles.colors.element_selected.map(serialize_color),
504 element_disabled: styles.colors.element_disabled.map(serialize_color),
505 drop_target_background: styles.colors.drop_target_background.map(serialize_color),
506 ghost_element_background: styles.colors.ghost_element_background.map(serialize_color),
507 ghost_element_hover: styles.colors.ghost_element_hover.map(serialize_color),
508 ghost_element_active: styles.colors.ghost_element_active.map(serialize_color),
509 ghost_element_selected: styles.colors.ghost_element_selected.map(serialize_color),
510 ghost_element_disabled: styles.colors.ghost_element_disabled.map(serialize_color),
511 text: styles.colors.text.map(serialize_color),
512 text_muted: styles.colors.text_muted.map(serialize_color),
513 text_placeholder: styles.colors.text_placeholder.map(serialize_color),
514 text_disabled: styles.colors.text_disabled.map(serialize_color),
515 text_accent: styles.colors.text_accent.map(serialize_color),
516 icon: styles.colors.icon.map(serialize_color),
517 icon_muted: styles.colors.icon_muted.map(serialize_color),
518 icon_disabled: styles.colors.icon_disabled.map(serialize_color),
519 icon_placeholder: styles.colors.icon_placeholder.map(serialize_color),
520 icon_accent: styles.colors.icon_accent.map(serialize_color),
521 status_bar_background: styles.colors.status_bar_background.map(serialize_color),
522 title_bar_background: styles.colors.title_bar_background.map(serialize_color),
523 toolbar_background: styles.colors.toolbar_background.map(serialize_color),
524 tab_bar_background: styles.colors.tab_bar_background.map(serialize_color),
525 tab_inactive_background: styles.colors.tab_inactive_background.map(serialize_color),
526 tab_active_background: styles.colors.tab_active_background.map(serialize_color),
527 search_match_background: styles.colors.search_match_background.map(serialize_color),
528 panel_background: styles.colors.panel_background.map(serialize_color),
529 panel_focused_border: styles.colors.panel_focused_border.map(serialize_color),
530 pane_focused_border: styles.colors.pane_focused_border.map(serialize_color),
531 scrollbar_thumb_background: styles
532 .colors
533 .scrollbar_thumb_background
534 .map(serialize_color),
535 scrollbar_thumb_hover_background: styles
536 .colors
537 .scrollbar_thumb_hover_background
538 .map(serialize_color),
539 scrollbar_thumb_border: styles.colors.scrollbar_thumb_border.map(serialize_color),
540 scrollbar_track_background: styles
541 .colors
542 .scrollbar_track_background
543 .map(serialize_color),
544 scrollbar_track_border: styles.colors.scrollbar_track_border.map(serialize_color),
545 editor_foreground: styles.colors.editor_foreground.map(serialize_color),
546 editor_background: styles.colors.editor_background.map(serialize_color),
547 editor_gutter_background: styles.colors.editor_gutter_background.map(serialize_color),
548 editor_subheader_background: styles
549 .colors
550 .editor_subheader_background
551 .map(serialize_color),
552 editor_active_line_background: styles
553 .colors
554 .editor_active_line_background
555 .map(serialize_color),
556 editor_highlighted_line_background: styles
557 .colors
558 .editor_highlighted_line_background
559 .map(serialize_color),
560 editor_line_number: styles.colors.editor_line_number.map(serialize_color),
561 editor_active_line_number: styles.colors.editor_active_line_number.map(serialize_color),
562 editor_invisible: styles.colors.editor_invisible.map(serialize_color),
563 editor_wrap_guide: styles.colors.editor_wrap_guide.map(serialize_color),
564 editor_active_wrap_guide: styles.colors.editor_active_wrap_guide.map(serialize_color),
565 editor_document_highlight_read_background: styles
566 .colors
567 .editor_document_highlight_read_background
568 .map(serialize_color),
569 editor_document_highlight_write_background: styles
570 .colors
571 .editor_document_highlight_write_background
572 .map(serialize_color),
573 terminal_background: styles.colors.terminal_background.map(serialize_color),
574 terminal_foreground: styles.colors.terminal_foreground.map(serialize_color),
575 terminal_bright_foreground: styles
576 .colors
577 .terminal_bright_foreground
578 .map(serialize_color),
579 terminal_dim_foreground: styles.colors.terminal_dim_foreground.map(serialize_color),
580 terminal_ansi_black: styles.colors.terminal_ansi_black.map(serialize_color),
581 terminal_ansi_bright_black: styles
582 .colors
583 .terminal_ansi_bright_black
584 .map(serialize_color),
585 terminal_ansi_dim_black: styles.colors.terminal_ansi_dim_black.map(serialize_color),
586 terminal_ansi_red: styles.colors.terminal_ansi_red.map(serialize_color),
587 terminal_ansi_bright_red: styles.colors.terminal_ansi_bright_red.map(serialize_color),
588 terminal_ansi_dim_red: styles.colors.terminal_ansi_dim_red.map(serialize_color),
589 terminal_ansi_green: styles.colors.terminal_ansi_green.map(serialize_color),
590 terminal_ansi_bright_green: styles
591 .colors
592 .terminal_ansi_bright_green
593 .map(serialize_color),
594 terminal_ansi_dim_green: styles.colors.terminal_ansi_dim_green.map(serialize_color),
595 terminal_ansi_yellow: styles.colors.terminal_ansi_yellow.map(serialize_color),
596 terminal_ansi_bright_yellow: styles
597 .colors
598 .terminal_ansi_bright_yellow
599 .map(serialize_color),
600 terminal_ansi_dim_yellow: styles.colors.terminal_ansi_dim_yellow.map(serialize_color),
601 terminal_ansi_blue: styles.colors.terminal_ansi_blue.map(serialize_color),
602 terminal_ansi_bright_blue: styles.colors.terminal_ansi_bright_blue.map(serialize_color),
603 terminal_ansi_dim_blue: styles.colors.terminal_ansi_dim_blue.map(serialize_color),
604 terminal_ansi_magenta: styles.colors.terminal_ansi_magenta.map(serialize_color),
605 terminal_ansi_bright_magenta: styles
606 .colors
607 .terminal_ansi_bright_magenta
608 .map(serialize_color),
609 terminal_ansi_dim_magenta: styles.colors.terminal_ansi_dim_magenta.map(serialize_color),
610 terminal_ansi_cyan: styles.colors.terminal_ansi_cyan.map(serialize_color),
611 terminal_ansi_bright_cyan: styles.colors.terminal_ansi_bright_cyan.map(serialize_color),
612 terminal_ansi_dim_cyan: styles.colors.terminal_ansi_dim_cyan.map(serialize_color),
613 terminal_ansi_white: styles.colors.terminal_ansi_white.map(serialize_color),
614 terminal_ansi_bright_white: styles
615 .colors
616 .terminal_ansi_bright_white
617 .map(serialize_color),
618 terminal_ansi_dim_white: styles.colors.terminal_ansi_dim_white.map(serialize_color),
619 link_text_hover: styles.colors.link_text_hover.map(serialize_color),
620 },
621 status: StatusColorsContent {
622 conflict: styles.status.conflict.map(serialize_color),
623 conflict_background: styles.status.conflict_background.map(serialize_color),
624 conflict_border: styles.status.conflict_border.map(serialize_color),
625 created: styles.status.created.map(serialize_color),
626 created_background: styles.status.created_background.map(serialize_color),
627 created_border: styles.status.created_border.map(serialize_color),
628 deleted: styles.status.deleted.map(serialize_color),
629 deleted_background: styles.status.deleted_background.map(serialize_color),
630 deleted_border: styles.status.deleted_border.map(serialize_color),
631 error: styles.status.error.map(serialize_color),
632 error_background: styles.status.error_background.map(serialize_color),
633 error_border: styles.status.error_border.map(serialize_color),
634 hidden: styles.status.hidden.map(serialize_color),
635 hidden_background: styles.status.hidden_background.map(serialize_color),
636 hidden_border: styles.status.hidden_border.map(serialize_color),
637 hint: styles.status.hint.map(serialize_color),
638 hint_background: styles.status.hint_background.map(serialize_color),
639 hint_border: styles.status.hint_border.map(serialize_color),
640 ignored: styles.status.ignored.map(serialize_color),
641 ignored_background: styles.status.ignored_background.map(serialize_color),
642 ignored_border: styles.status.ignored_border.map(serialize_color),
643 info: styles.status.info.map(serialize_color),
644 info_background: styles.status.info_background.map(serialize_color),
645 info_border: styles.status.info_border.map(serialize_color),
646 modified: styles.status.modified.map(serialize_color),
647 modified_background: styles.status.modified_background.map(serialize_color),
648 modified_border: styles.status.modified_border.map(serialize_color),
649 predictive: styles.status.predictive.map(serialize_color),
650 predictive_background: styles.status.predictive_background.map(serialize_color),
651 predictive_border: styles.status.predictive_border.map(serialize_color),
652 renamed: styles.status.renamed.map(serialize_color),
653 renamed_background: styles.status.renamed_background.map(serialize_color),
654 renamed_border: styles.status.renamed_border.map(serialize_color),
655 success: styles.status.success.map(serialize_color),
656 success_background: styles.status.success_background.map(serialize_color),
657 success_border: styles.status.success_border.map(serialize_color),
658 unreachable: styles.status.unreachable.map(serialize_color),
659 unreachable_background: styles.status.unreachable_background.map(serialize_color),
660 unreachable_border: styles.status.unreachable_border.map(serialize_color),
661 warning: styles.status.warning.map(serialize_color),
662 warning_background: styles.status.warning_background.map(serialize_color),
663 warning_border: styles.status.warning_border.map(serialize_color),
664 },
665 players: styles
666 .player
667 .map(|players| {
668 players
669 .0
670 .into_iter()
671 .map(|player_color| PlayerColorContent {
672 cursor: Some(player_color.cursor).map(serialize_color),
673 background: Some(player_color.background).map(serialize_color),
674 selection: Some(player_color.selection).map(serialize_color),
675 })
676 .collect()
677 })
678 .unwrap_or_default(),
679 syntax: styles
680 .syntax
681 .map(|syntax| {
682 IndexMap::from_iter(syntax.highlights.into_iter().map(|(name, style)| {
683 (
684 name,
685 HighlightStyleContent {
686 color: style.color.map(serialize_color),
687 font_style: style.font_style.map(|font_style| match font_style {
688 theme::UserFontStyle::Normal => theme::FontStyleContent::Normal,
689 theme::UserFontStyle::Italic => theme::FontStyleContent::Italic,
690 theme::UserFontStyle::Oblique => theme::FontStyleContent::Oblique,
691 }),
692 font_weight: style.font_weight.map(|font_weight| match font_weight.0 {
693 _ if font_weight.0 == 100.0 => FontWeightContent::Thin,
694 _ if font_weight.0 == 200.0 => FontWeightContent::ExtraLight,
695 _ if font_weight.0 == 300.0 => FontWeightContent::Light,
696 _ if font_weight.0 == 400.0 => FontWeightContent::Normal,
697 _ if font_weight.0 == 500.0 => FontWeightContent::Medium,
698 _ if font_weight.0 == 600.0 => FontWeightContent::Semibold,
699 _ if font_weight.0 == 700.0 => FontWeightContent::Bold,
700 _ if font_weight.0 == 800.0 => FontWeightContent::ExtraBold,
701 _ if font_weight.0 == 900.0 => FontWeightContent::Black,
702 _ => unreachable!(),
703 }),
704 },
705 )
706 }))
707 })
708 .unwrap_or_default(),
709 }
710}