mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-08 07:35:01 +03:00
Extend theme_importer
in preparation for importing Zed1 themes (#3791)
This PR extends the `theme_importer` with the overall structure required to support importing themes from Zed1. Release Notes: - N/A
This commit is contained in:
parent
c34a81152f
commit
dc64411cca
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -9834,6 +9834,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.4.4",
|
||||
"convert_case 0.6.0",
|
||||
"gpui",
|
||||
"gpui2",
|
||||
"indexmap 1.9.3",
|
||||
"json_comments",
|
||||
@ -9843,6 +9844,7 @@ dependencies = [
|
||||
"serde",
|
||||
"simplelog",
|
||||
"strum",
|
||||
"theme",
|
||||
"theme2",
|
||||
"uuid 1.4.1",
|
||||
]
|
||||
|
@ -25,7 +25,7 @@ use window::MacWindow;
|
||||
|
||||
use crate::executor;
|
||||
|
||||
pub(crate) fn platform() -> Arc<dyn super::Platform> {
|
||||
pub fn platform() -> Arc<dyn super::Platform> {
|
||||
Arc::new(MacPlatform::new())
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,12 @@ publish = false
|
||||
[features]
|
||||
test-support = ["backtrace", "dhat", "env_logger", "collections/test-support", "util/test-support"]
|
||||
|
||||
# Suppress a panic when both GPUI1 and GPUI2 are loaded.
|
||||
#
|
||||
# This is used in the `theme_importer` where we need to depend on both
|
||||
# GPUI1 and GPUI2 in order to convert Zed1 themes to Zed2 themes.
|
||||
allow-multiple-gpui-versions = ["util/allow-multiple-gpui-versions"]
|
||||
|
||||
[lib]
|
||||
path = "src/gpui2.rs"
|
||||
doctest = false
|
||||
|
@ -29,16 +29,16 @@ use crate::UserThemeFamily;
|
||||
|
||||
pub(crate) fn all_user_themes() -> Vec<UserThemeFamily> {
|
||||
vec![
|
||||
rose_pine(),
|
||||
night_owl(),
|
||||
andromeda(),
|
||||
synthwave_84(),
|
||||
palenight(),
|
||||
dracula(),
|
||||
solarized(),
|
||||
nord(),
|
||||
noctis(),
|
||||
ayu(),
|
||||
dracula(),
|
||||
gruvbox(),
|
||||
night_owl(),
|
||||
noctis(),
|
||||
nord(),
|
||||
palenight(),
|
||||
rose_pine(),
|
||||
solarized(),
|
||||
synthwave_84(),
|
||||
]
|
||||
}
|
||||
|
@ -4,14 +4,13 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
any_ascii = "0.3.2"
|
||||
anyhow.workspace = true
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
convert_case = "0.6.0"
|
||||
gpui = { package = "gpui2", path = "../gpui2" }
|
||||
gpui = { package = "gpui2", path = "../gpui2", features = ["allow-multiple-gpui-versions"] }
|
||||
gpui1 = { package = "gpui", path = "../gpui" }
|
||||
indexmap = { version = "1.6.2", features = ["serde"] }
|
||||
json_comments = "0.2.2"
|
||||
log.workspace = true
|
||||
@ -21,4 +20,5 @@ serde.workspace = true
|
||||
simplelog = "0.9"
|
||||
strum = { version = "0.25.0", features = ["derive"] }
|
||||
theme = { package = "theme2", path = "../theme2", features = ["importing-themes"] }
|
||||
theme1 = { package = "theme", path = "../theme" }
|
||||
uuid.workspace = true
|
||||
|
26
crates/theme_importer/src/assets.rs
Normal file
26
crates/theme_importer/src/assets.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use gpui::{AssetSource, SharedString};
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "../../assets"]
|
||||
#[include = "fonts/**/*"]
|
||||
#[exclude = "*.DS_Store"]
|
||||
pub struct Assets;
|
||||
|
||||
impl AssetSource for Assets {
|
||||
fn load(&self, path: &str) -> Result<Cow<[u8]>> {
|
||||
Self::get(path)
|
||||
.map(|f| f.data)
|
||||
.ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
|
||||
}
|
||||
|
||||
fn list(&self, path: &str) -> Result<Vec<SharedString>> {
|
||||
Ok(Self::iter()
|
||||
.filter(|p| p.starts_with(path))
|
||||
.map(SharedString::from)
|
||||
.collect())
|
||||
}
|
||||
}
|
@ -1,29 +1,36 @@
|
||||
mod assets;
|
||||
mod color;
|
||||
mod theme_printer;
|
||||
mod util;
|
||||
mod vscode;
|
||||
mod zed1;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use any_ascii::any_ascii;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use clap::Parser;
|
||||
use convert_case::{Case, Casing};
|
||||
use gpui::serde_json;
|
||||
use gpui::{serde_json, AssetSource};
|
||||
use indexmap::IndexMap;
|
||||
use json_comments::StripComments;
|
||||
use log::LevelFilter;
|
||||
use serde::Deserialize;
|
||||
use simplelog::{TermLogger, TerminalMode};
|
||||
use theme::{Appearance, UserThemeFamily};
|
||||
use theme::{Appearance, UserTheme, UserThemeFamily};
|
||||
use theme1::Theme as Zed1Theme;
|
||||
|
||||
use crate::assets::Assets;
|
||||
use crate::theme_printer::UserThemeFamilyPrinter;
|
||||
use crate::vscode::VsCodeTheme;
|
||||
use crate::vscode::VsCodeThemeConverter;
|
||||
use crate::zed1::Zed1ThemeConverter;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct FamilyMetadata {
|
||||
@ -66,6 +73,10 @@ pub struct ThemeMetadata {
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// Whether to import Zed1 themes.
|
||||
#[arg(long)]
|
||||
zed1: bool,
|
||||
|
||||
/// Whether to warn when values are missing from the theme.
|
||||
#[arg(long)]
|
||||
warn_on_missing: bool,
|
||||
@ -176,6 +187,102 @@ fn main() -> Result<()> {
|
||||
theme_families.push(theme_family);
|
||||
}
|
||||
|
||||
if args.zed1 {
|
||||
let zed1_themes_path = PathBuf::from_str("assets/themes")?;
|
||||
|
||||
let zed1_theme_familes = [
|
||||
"Andromeda",
|
||||
"Atelier",
|
||||
"Ayu",
|
||||
"Gruvbox",
|
||||
"One",
|
||||
"Rosé Pine",
|
||||
"Sandcastle",
|
||||
"Solarized",
|
||||
"Summercamp",
|
||||
];
|
||||
|
||||
let mut zed1_themes_by_family: HashMap<String, Vec<UserTheme>> = HashMap::from_iter(
|
||||
zed1_theme_familes
|
||||
.into_iter()
|
||||
.map(|family| (family.to_string(), Vec::new())),
|
||||
);
|
||||
|
||||
let platform = gpui1::platform::current::platform();
|
||||
let zed1_font_cache = Arc::new(gpui1::FontCache::new(platform.fonts()));
|
||||
|
||||
let mut embedded_fonts = Vec::new();
|
||||
for font_path in Assets.list("fonts")? {
|
||||
if font_path.ends_with(".ttf") {
|
||||
let font_bytes = Assets.load(&font_path)?.to_vec();
|
||||
embedded_fonts.push(Arc::from(font_bytes));
|
||||
}
|
||||
}
|
||||
|
||||
platform.fonts().add_fonts(&embedded_fonts)?;
|
||||
|
||||
for entry in fs::read_dir(&zed1_themes_path)? {
|
||||
let entry = entry?;
|
||||
|
||||
if entry.file_type()?.is_dir() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match entry.path().extension() {
|
||||
None => continue,
|
||||
Some(extension) => {
|
||||
if extension != "json" {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let theme_file_path = entry.path();
|
||||
|
||||
let theme_file = match File::open(&theme_file_path) {
|
||||
Ok(file) => file,
|
||||
Err(_) => {
|
||||
log::info!("Failed to open file at path: {:?}", theme_file_path);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let theme_without_comments = StripComments::new(theme_file);
|
||||
|
||||
let zed1_theme: Zed1Theme =
|
||||
gpui1::fonts::with_font_cache(zed1_font_cache.clone(), || {
|
||||
serde_json::from_reader(theme_without_comments)
|
||||
.context(format!("failed to parse theme {theme_file_path:?}"))
|
||||
})?;
|
||||
|
||||
let theme_name = zed1_theme.meta.name.clone();
|
||||
|
||||
let converter = Zed1ThemeConverter::new(zed1_theme);
|
||||
|
||||
let theme = converter.convert()?;
|
||||
|
||||
let Some((_, themes_for_family)) = zed1_themes_by_family
|
||||
.iter_mut()
|
||||
.find(|(family, _)| theme_name.starts_with(*family))
|
||||
else {
|
||||
log::warn!("No theme family found for '{}'.", theme_name);
|
||||
continue;
|
||||
};
|
||||
|
||||
themes_for_family.push(theme);
|
||||
}
|
||||
|
||||
for (family, themes) in zed1_themes_by_family {
|
||||
let theme_family = UserThemeFamily {
|
||||
name: format!("{family} (Zed1)"),
|
||||
author: "Zed Industries".to_string(),
|
||||
themes,
|
||||
};
|
||||
|
||||
theme_families.push(theme_family);
|
||||
}
|
||||
}
|
||||
|
||||
let themes_output_path = PathBuf::from_str(OUT_PATH)?;
|
||||
|
||||
if !themes_output_path.exists() {
|
||||
@ -188,7 +295,10 @@ fn main() -> Result<()> {
|
||||
let mut theme_modules = Vec::new();
|
||||
|
||||
for theme_family in theme_families {
|
||||
let theme_family_slug = any_ascii(&theme_family.name).to_case(Case::Snake);
|
||||
let theme_family_slug = any_ascii(&theme_family.name)
|
||||
.replace("(", "")
|
||||
.replace(")", "")
|
||||
.to_case(Case::Snake);
|
||||
|
||||
let mut output_file =
|
||||
File::create(themes_output_path.join(format!("{theme_family_slug}.rs")))?;
|
||||
@ -222,6 +332,8 @@ fn main() -> Result<()> {
|
||||
theme_modules.push(theme_family_slug);
|
||||
}
|
||||
|
||||
theme_modules.sort();
|
||||
|
||||
let themes_vector_contents = format!(
|
||||
r#"
|
||||
use crate::UserThemeFamily;
|
||||
|
3
crates/theme_importer/src/zed1.rs
Normal file
3
crates/theme_importer/src/zed1.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod converter;
|
||||
|
||||
pub use converter::*;
|
176
crates/theme_importer/src/zed1/converter.rs
Normal file
176
crates/theme_importer/src/zed1/converter.rs
Normal file
@ -0,0 +1,176 @@
|
||||
use anyhow::Result;
|
||||
use gpui::{Hsla, Rgba};
|
||||
use gpui1::color::Color as Zed1Color;
|
||||
use gpui1::fonts::HighlightStyle as Zed1HighlightStyle;
|
||||
use theme::{
|
||||
Appearance, StatusColorsRefinement, ThemeColorsRefinement, UserFontStyle, UserFontWeight,
|
||||
UserHighlightStyle, UserSyntaxTheme, UserTheme, UserThemeStylesRefinement,
|
||||
};
|
||||
use theme1::Theme as Zed1Theme;
|
||||
|
||||
fn zed1_color_to_hsla(color: Zed1Color) -> Hsla {
|
||||
let r = color.r as f32 / 255.;
|
||||
let g = color.g as f32 / 255.;
|
||||
let b = color.b as f32 / 255.;
|
||||
let a = color.a as f32 / 255.;
|
||||
|
||||
Hsla::from(Rgba { r, g, b, a })
|
||||
}
|
||||
|
||||
fn zed1_highlight_style_to_user_highlight_style(
|
||||
highlight: Zed1HighlightStyle,
|
||||
) -> UserHighlightStyle {
|
||||
UserHighlightStyle {
|
||||
color: highlight.color.map(zed1_color_to_hsla),
|
||||
font_style: highlight.italic.map(|is_italic| {
|
||||
if is_italic {
|
||||
UserFontStyle::Italic
|
||||
} else {
|
||||
UserFontStyle::Normal
|
||||
}
|
||||
}),
|
||||
font_weight: highlight.weight.map(|weight| UserFontWeight(weight.0)),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Zed1ThemeConverter {
|
||||
theme: Zed1Theme,
|
||||
}
|
||||
|
||||
impl Zed1ThemeConverter {
|
||||
pub fn new(theme: Zed1Theme) -> Self {
|
||||
Self { theme }
|
||||
}
|
||||
|
||||
pub fn convert(self) -> Result<UserTheme> {
|
||||
let appearance = match self.theme.meta.is_light {
|
||||
true => Appearance::Light,
|
||||
false => Appearance::Dark,
|
||||
};
|
||||
|
||||
let status_colors_refinement = self.convert_status_colors()?;
|
||||
let theme_colors_refinement = self.convert_theme_colors()?;
|
||||
let syntax_theme = self.convert_syntax_theme()?;
|
||||
|
||||
Ok(UserTheme {
|
||||
name: format!("{} (Zed1)", self.theme.meta.name),
|
||||
appearance,
|
||||
styles: UserThemeStylesRefinement {
|
||||
colors: theme_colors_refinement,
|
||||
status: status_colors_refinement,
|
||||
syntax: Some(syntax_theme),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn convert_status_colors(&self) -> Result<StatusColorsRefinement> {
|
||||
fn convert(color: Zed1Color) -> Option<Hsla> {
|
||||
Some(zed1_color_to_hsla(color))
|
||||
}
|
||||
|
||||
let diff_style = self.theme.editor.diff.clone();
|
||||
|
||||
Ok(StatusColorsRefinement {
|
||||
created: convert(diff_style.inserted),
|
||||
modified: convert(diff_style.modified),
|
||||
deleted: convert(diff_style.deleted),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn convert_theme_colors(&self) -> Result<ThemeColorsRefinement> {
|
||||
fn convert(color: Zed1Color) -> Option<Hsla> {
|
||||
Some(zed1_color_to_hsla(color))
|
||||
}
|
||||
|
||||
let tab_bar = self.theme.workspace.tab_bar.clone();
|
||||
let active_tab = self.theme.workspace.tab_bar.tab_style(true, true).clone();
|
||||
let inactive_tab = self.theme.workspace.tab_bar.tab_style(true, false).clone();
|
||||
let toolbar = self.theme.workspace.toolbar.clone();
|
||||
let scrollbar = self.theme.editor.scrollbar.clone();
|
||||
|
||||
let zed1_titlebar_border = convert(self.theme.titlebar.container.border.color);
|
||||
|
||||
Ok(ThemeColorsRefinement {
|
||||
border: zed1_titlebar_border,
|
||||
border_variant: zed1_titlebar_border,
|
||||
background: convert(self.theme.workspace.background),
|
||||
title_bar_background: self
|
||||
.theme
|
||||
.titlebar
|
||||
.container
|
||||
.background_color
|
||||
.map(zed1_color_to_hsla),
|
||||
status_bar_background: self
|
||||
.theme
|
||||
.workspace
|
||||
.status_bar
|
||||
.container
|
||||
.background_color
|
||||
.map(zed1_color_to_hsla),
|
||||
text: convert(self.theme.editor.text_color),
|
||||
tab_bar_background: tab_bar.container.background_color.map(zed1_color_to_hsla),
|
||||
tab_active_background: active_tab
|
||||
.container
|
||||
.background_color
|
||||
.map(zed1_color_to_hsla),
|
||||
tab_inactive_background: inactive_tab
|
||||
.container
|
||||
.background_color
|
||||
.map(zed1_color_to_hsla),
|
||||
toolbar_background: toolbar.container.background_color.map(zed1_color_to_hsla),
|
||||
editor_foreground: convert(self.theme.editor.text_color),
|
||||
editor_background: convert(self.theme.editor.background),
|
||||
editor_gutter_background: convert(self.theme.editor.gutter_background),
|
||||
editor_line_number: convert(self.theme.editor.line_number),
|
||||
editor_active_line_number: convert(self.theme.editor.line_number_active),
|
||||
editor_wrap_guide: convert(self.theme.editor.wrap_guide),
|
||||
editor_active_wrap_guide: convert(self.theme.editor.active_wrap_guide),
|
||||
scrollbar_track_background: scrollbar.track.background_color.map(zed1_color_to_hsla),
|
||||
scrollbar_track_border: convert(scrollbar.track.border.color),
|
||||
scrollbar_thumb_background: scrollbar.thumb.background_color.map(zed1_color_to_hsla),
|
||||
scrollbar_thumb_border: convert(scrollbar.thumb.border.color),
|
||||
scrollbar_thumb_hover_background: scrollbar
|
||||
.thumb
|
||||
.background_color
|
||||
.map(zed1_color_to_hsla),
|
||||
terminal_background: convert(self.theme.terminal.background),
|
||||
terminal_ansi_bright_black: convert(self.theme.terminal.bright_black),
|
||||
terminal_ansi_bright_red: convert(self.theme.terminal.bright_red),
|
||||
terminal_ansi_bright_green: convert(self.theme.terminal.bright_green),
|
||||
terminal_ansi_bright_yellow: convert(self.theme.terminal.bright_yellow),
|
||||
terminal_ansi_bright_blue: convert(self.theme.terminal.bright_blue),
|
||||
terminal_ansi_bright_magenta: convert(self.theme.terminal.bright_magenta),
|
||||
terminal_ansi_bright_cyan: convert(self.theme.terminal.bright_cyan),
|
||||
terminal_ansi_bright_white: convert(self.theme.terminal.bright_white),
|
||||
terminal_ansi_black: convert(self.theme.terminal.black),
|
||||
terminal_ansi_red: convert(self.theme.terminal.red),
|
||||
terminal_ansi_green: convert(self.theme.terminal.green),
|
||||
terminal_ansi_yellow: convert(self.theme.terminal.yellow),
|
||||
terminal_ansi_blue: convert(self.theme.terminal.blue),
|
||||
terminal_ansi_magenta: convert(self.theme.terminal.magenta),
|
||||
terminal_ansi_cyan: convert(self.theme.terminal.cyan),
|
||||
terminal_ansi_white: convert(self.theme.terminal.white),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn convert_syntax_theme(&self) -> Result<UserSyntaxTheme> {
|
||||
Ok(UserSyntaxTheme {
|
||||
highlights: self
|
||||
.theme
|
||||
.editor
|
||||
.syntax
|
||||
.highlights
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|(name, highlight_style)| {
|
||||
(
|
||||
name,
|
||||
zed1_highlight_style_to_user_highlight_style(highlight_style),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
}
|
@ -11,6 +11,12 @@ doctest = true
|
||||
[features]
|
||||
test-support = ["tempdir", "git2"]
|
||||
|
||||
# Suppress a panic when both GPUI1 and GPUI2 are loaded.
|
||||
#
|
||||
# This is used in the `theme_importer` where we need to depend on both
|
||||
# GPUI1 and GPUI2 in order to convert Zed1 themes to Zed2 themes.
|
||||
allow-multiple-gpui-versions = []
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
backtrace = "0.3"
|
||||
|
@ -13,10 +13,12 @@ use std::{
|
||||
ops::{AddAssign, Range, RangeInclusive},
|
||||
panic::Location,
|
||||
pin::Pin,
|
||||
sync::atomic::AtomicU32,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "allow-multiple-gpui-versions"))]
|
||||
use std::sync::atomic::AtomicU32;
|
||||
|
||||
pub use backtrace::Backtrace;
|
||||
use futures::Future;
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
@ -434,15 +436,18 @@ impl<T: Ord + Clone> RangeExt<T> for RangeInclusive<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "allow-multiple-gpui-versions"))]
|
||||
static GPUI_LOADED: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
pub fn gpui2_loaded() {
|
||||
#[cfg(not(feature = "allow-multiple-gpui-versions"))]
|
||||
if GPUI_LOADED.fetch_add(2, std::sync::atomic::Ordering::SeqCst) != 0 {
|
||||
panic!("=========\nYou are loading both GPUI1 and GPUI2 in the same build!\nFix Your Dependencies with cargo tree!\n=========")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gpui1_loaded() {
|
||||
#[cfg(not(feature = "allow-multiple-gpui-versions"))]
|
||||
if GPUI_LOADED.fetch_add(1, std::sync::atomic::Ordering::SeqCst) != 0 {
|
||||
panic!("=========\nYou are loading both GPUI1 and GPUI2 in the same build!\nFix Your Dependencies with cargo tree!\n=========")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user