Detailed changes
@@ -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
@@ -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;
@@ -0,0 +1,3 @@
+mod converter;
+
+pub use converter::*;
@@ -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=========")
}