From 0bc51382b2f49039fc21f4b6624d86c21bcc7337 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 2 Nov 2023 18:24:38 -0400 Subject: [PATCH] Add basic `VsCodeThemeConverter` --- assets/themes/src/vscode/ayu/family.json | 3 + assets/themes/src/vscode/dracula/family.json | 1 + crates/theme_importer/src/main.rs | 109 +++++-------------- crates/theme_importer/src/vscode.rs | 61 ++++++++++- 4 files changed, 92 insertions(+), 82 deletions(-) diff --git a/assets/themes/src/vscode/ayu/family.json b/assets/themes/src/vscode/ayu/family.json index 8c0d0c5c55..05092a335d 100644 --- a/assets/themes/src/vscode/ayu/family.json +++ b/assets/themes/src/vscode/ayu/family.json @@ -4,14 +4,17 @@ "themes": [ { "name": "Ayu Light", + "file_name": "ayu-light.json", "appearance": "light" }, { "name": "Ayu Mirage", + "file_name": "ayu-mirage.json", "appearance": "dark" }, { "name": "Ayu Dark", + "file_name": "ayu-dark.json", "appearance": "dark" } ] diff --git a/assets/themes/src/vscode/dracula/family.json b/assets/themes/src/vscode/dracula/family.json index 50d5940a7b..b3bf2cf04a 100644 --- a/assets/themes/src/vscode/dracula/family.json +++ b/assets/themes/src/vscode/dracula/family.json @@ -4,6 +4,7 @@ "themes": [ { "name": "Dracula", + "file_name": "dracula.json", "appearance": "dark" } ] diff --git a/crates/theme_importer/src/main.rs b/crates/theme_importer/src/main.rs index b616b44e47..7fcc5c2f6c 100644 --- a/crates/theme_importer/src/main.rs +++ b/crates/theme_importer/src/main.rs @@ -1,19 +1,17 @@ -use std::borrow::Cow; use std::fs::{self, File}; use std::path::PathBuf; use std::str::FromStr; use anyhow::{anyhow, Context, Result}; -use convert_case::Case; -use gpui::{serde_json, AssetSource, SharedString}; +use gpui::serde_json; use log::LevelFilter; -use rust_embed::RustEmbed; use serde::Deserialize; use simplelog::SimpleLogger; use theme::{ default_color_scales, Appearance, GitStatusColors, PlayerColors, StatusColors, SyntaxTheme, - SystemColors, ThemeColors, ThemeColorsRefinement, ThemeFamily, ThemeStyles, ThemeVariant, + SystemColors, ThemeColors, ThemeFamily, ThemeStyles, ThemeVariant, }; +use vscode::VsCodeThemeConverter; use crate::vscode::VsCodeTheme; @@ -30,10 +28,10 @@ pub(crate) fn new_theme_family(name: String, author: String) -> ThemeFamily { } #[derive(Debug, Deserialize)] -struct FamilyJson { +struct FamilyMetadata { pub name: String, pub author: String, - pub themes: Vec, + pub themes: Vec, } #[derive(Debug, Deserialize)] @@ -43,28 +41,22 @@ enum ThemeAppearanceJson { Dark, } +impl From for Appearance { + fn from(value: ThemeAppearanceJson) -> Self { + match value { + ThemeAppearanceJson::Light => Self::Light, + ThemeAppearanceJson::Dark => Self::Dark, + } + } +} + #[derive(Debug, Deserialize)] -struct ThemeVariantJson { +struct ThemeMetadata { pub name: String, + pub file_name: String, pub appearance: ThemeAppearanceJson, } -struct ImportedThemeFamily { - pub id: String, - pub name: String, - pub author: String, - pub url: Option, - // App should panic if we try to load a theme without a lisence - pub license: String, - pub themes: Vec, -} - -struct ImportedThemeVariant { - pub id: String, - pub name: String, - pub colors: ThemeColorsRefinement, -} - // Load a vscode theme from json // Load it's LICENSE from the same folder // Create a ThemeFamily for the theme @@ -92,35 +84,25 @@ fn main() -> Result<()> { let family_metadata_file = File::open(theme_family_dir.path().join("family.json")) .context(format!("no `family.json` found for '{theme_family_slug}'"))?; - let family_metadata: FamilyJson = serde_json::from_reader(family_metadata_file).context( - format!("failed to parse `family.json` for '{theme_family_slug}'"), - )?; + let family_metadata: FamilyMetadata = serde_json::from_reader(family_metadata_file) + .context(format!( + "failed to parse `family.json` for '{theme_family_slug}'" + ))?; let mut themes = Vec::new(); - for theme_entry in fs::read_dir(vscode_themes_path.join(theme_family_slug))? { - let theme_entry = theme_entry?; - - let theme_file_path = theme_entry.path(); - - let file_name = theme_file_path - .file_name() - .ok_or(anyhow!("no file stem")) - .map(|file_name| file_name.to_string_lossy())?; - - if !file_name.ends_with(".json") { - continue; - } - - if file_name == "family.json" { - continue; - } + for theme_metadata in family_metadata.themes { + let theme_file_path = theme_family_dir.path().join(&theme_metadata.file_name); let theme_file = File::open(&theme_file_path)?; - let theme: VsCodeTheme = serde_json::from_reader(theme_file) + let vscode_theme: VsCodeTheme = serde_json::from_reader(theme_file) .context(format!("failed to parse theme {theme_file_path:?}"))?; + let converter = VsCodeThemeConverter::new(vscode_theme, theme_metadata); + + let theme = converter.convert()?; + themes.push(theme); } @@ -128,22 +110,7 @@ fn main() -> Result<()> { id: uuid::Uuid::new_v4().to_string(), name: family_metadata.name.into(), author: family_metadata.author.into(), - themes: themes - .into_iter() - .map(|theme| ThemeVariant { - id: uuid::Uuid::new_v4().to_string(), - name: "".into(), - appearance: Appearance::Dark, - styles: ThemeStyles { - system: SystemColors::default(), - colors: ThemeColors::default_dark(), - status: StatusColors::default(), - git: GitStatusColors::default(), - player: PlayerColors::default(), - syntax: SyntaxTheme::default_dark(), - }, - }) - .collect(), + themes, scales: default_color_scales(), }; @@ -152,23 +119,3 @@ fn main() -> Result<()> { Ok(()) } - -#[derive(RustEmbed)] -#[folder = "../../assets"] -#[include = "themes/**/*"] -pub struct Assets; - -impl AssetSource for Assets { - fn load(&self, path: &str) -> Result> { - Self::get(path) - .map(|f| f.data) - .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path)) - } - - fn list(&self, path: &str) -> Result> { - Ok(Self::iter() - .filter(|p| p.starts_with(path)) - .map(SharedString::from) - .collect()) - } -} diff --git a/crates/theme_importer/src/vscode.rs b/crates/theme_importer/src/vscode.rs index c664108c83..bc3ebd22d1 100644 --- a/crates/theme_importer/src/vscode.rs +++ b/crates/theme_importer/src/vscode.rs @@ -1,8 +1,16 @@ use std::path::{Path, PathBuf}; use anyhow::Result; +use gpui::{Hsla, Refineable, Rgba}; use serde::Deserialize; -use theme::{default_color_scales, ColorScales, ThemeFamily}; +use theme::{ + default_color_scales, Appearance, ColorScales, GitStatusColors, PlayerColors, StatusColors, + SyntaxTheme, SystemColors, ThemeColors, ThemeColorsRefinement, ThemeFamily, ThemeStyles, + ThemeVariant, +}; +use uuid::Uuid; + +use crate::ThemeMetadata; #[derive(Deserialize, Debug)] pub struct VsCodeTheme { @@ -61,6 +69,57 @@ pub(crate) fn new_theme_family_from_vsc(path: &Path) -> Result { // Ok(theme_family) } +fn try_parse_color(color: &str) -> Result { + Ok(Rgba::try_from(color)?.into()) +} + +pub struct VsCodeThemeConverter { + theme: VsCodeTheme, + theme_metadata: ThemeMetadata, +} + +impl VsCodeThemeConverter { + pub fn new(theme: VsCodeTheme, theme_metadata: ThemeMetadata) -> Self { + Self { + theme, + theme_metadata, + } + } + + pub fn convert(self) -> Result { + let appearance = self.theme_metadata.appearance.into(); + + let mut theme_colors = match appearance { + Appearance::Light => ThemeColors::default_light(), + Appearance::Dark => ThemeColors::default_dark(), + }; + + let vscode_colors = &self.theme.colors; + + let theme_colors_refinements = ThemeColorsRefinement { + background: Some(try_parse_color(&vscode_colors.editor)?), + text: Some(try_parse_color(&vscode_colors.text)?), + ..Default::default() + }; + + theme_colors.refine(&theme_colors_refinements); + + Ok(ThemeVariant { + id: uuid::Uuid::new_v4().to_string(), + name: self.theme_metadata.name.into(), + appearance, + styles: ThemeStyles { + system: SystemColors::default(), + colors: theme_colors, + status: StatusColors::default(), + git: GitStatusColors::default(), + player: PlayerColors::default(), + syntax: SyntaxTheme::default_dark(), + }, + }) + } +} + #[cfg(test)] mod tests { use super::*;