412 lines · 14.4 KB · Rust
RawDownload
#![deny(missing_docs)] //! # Theme Settings//!//! This crate provides theme settings integration for Zed,//! bridging the theme system with the settings infrastructure. mod schema;mod settings; use std::sync::Arc; use ::settings::{IntoGpui, Settings, SettingsStore};use anyhow::{Context as _, Result};use gpui::{App, Font, HighlightStyle, Pixels, Refineable};use gpui_util::ResultExt;use theme::{    AccentColors, Appearance, AppearanceContent, DEFAULT_DARK_THEME, DEFAULT_ICON_THEME_NAME,    GlobalTheme, LoadThemes, PlayerColor, PlayerColors, StatusColors, SyntaxTheme,    SystemAppearance, SystemColors, Theme, ThemeColors, ThemeFamily, ThemeRegistry,    ThemeSettingsProvider, ThemeStyles, default_color_scales, try_parse_color,}; pub use crate::schema::{    FontStyleContent, FontWeightContent, HighlightStyleContent, StatusColorsContent,    ThemeColorsContent, ThemeContent, ThemeFamilyContent, ThemeStyleContent,    WindowBackgroundContent, status_colors_refinement, syntax_overrides, theme_colors_refinement,};pub use crate::settings::{    AgentFontSize, BufferLineHeight, FontFamilyName, IconThemeName, IconThemeSelection,    ThemeAppearanceMode, ThemeName, ThemeSelection, ThemeSettings, adjust_agent_buffer_font_size,    adjust_agent_ui_font_size, adjust_buffer_font_size, adjust_ui_font_size, adjusted_font_size,    appearance_to_mode, clamp_font_size, default_theme, observe_buffer_font_size_adjustment,    reset_agent_buffer_font_size, reset_agent_ui_font_size, reset_buffer_font_size,    reset_ui_font_size, set_icon_theme, set_mode, set_theme, setup_ui_font,};pub use theme::UiDensity; struct ThemeSettingsProviderImpl; impl ThemeSettingsProvider for ThemeSettingsProviderImpl {    fn ui_font<'a>(&'a self, cx: &'a App) -> &'a Font {        &ThemeSettings::get_global(cx).ui_font    }     fn buffer_font<'a>(&'a self, cx: &'a App) -> &'a Font {        &ThemeSettings::get_global(cx).buffer_font    }     fn ui_font_size(&self, cx: &App) -> Pixels {        ThemeSettings::get_global(cx).ui_font_size(cx)    }     fn buffer_font_size(&self, cx: &App) -> Pixels {        ThemeSettings::get_global(cx).buffer_font_size(cx)    }     fn ui_density(&self, cx: &App) -> UiDensity {        ThemeSettings::get_global(cx).ui_density    }} /// Initialize the theme system with settings integration.////// This is the full initialization for the application. It calls [`theme::init`]/// and then wires up settings observation for theme/font changes.pub fn init(themes_to_load: LoadThemes, cx: &mut App) {    let load_user_themes = matches!(&themes_to_load, LoadThemes::All(_));     theme::init(themes_to_load, cx);    theme::set_theme_settings_provider(Box::new(ThemeSettingsProviderImpl), cx);     if load_user_themes {        let registry = ThemeRegistry::global(cx);        load_bundled_themes(&registry);    }     let theme = configured_theme(cx);    let icon_theme = configured_icon_theme(cx);    GlobalTheme::update_theme(cx, theme);    GlobalTheme::update_icon_theme(cx, icon_theme);     let settings = ThemeSettings::get_global(cx);     let mut prev_buffer_font_size_settings = settings.buffer_font_size_settings();    let mut prev_ui_font_size_settings = settings.ui_font_size_settings();    let mut prev_agent_ui_font_size_settings = settings.agent_ui_font_size_settings();    let mut prev_agent_buffer_font_size_settings = settings.agent_buffer_font_size_settings();    let mut prev_theme_name = settings.theme.name(SystemAppearance::global(cx).0);    let mut prev_icon_theme_name = settings.icon_theme.name(SystemAppearance::global(cx).0);    let mut prev_theme_overrides = (        settings.experimental_theme_overrides.clone(),        settings.theme_overrides.clone(),    );     cx.observe_global::<SettingsStore>(move |cx| {        let settings = ThemeSettings::get_global(cx);         let buffer_font_size_settings = settings.buffer_font_size_settings();        let ui_font_size_settings = settings.ui_font_size_settings();        let agent_ui_font_size_settings = settings.agent_ui_font_size_settings();        let agent_buffer_font_size_settings = settings.agent_buffer_font_size_settings();        let theme_name = settings.theme.name(SystemAppearance::global(cx).0);        let icon_theme_name = settings.icon_theme.name(SystemAppearance::global(cx).0);        let theme_overrides = (            settings.experimental_theme_overrides.clone(),            settings.theme_overrides.clone(),        );         if buffer_font_size_settings != prev_buffer_font_size_settings {            prev_buffer_font_size_settings = buffer_font_size_settings;            reset_buffer_font_size(cx);        }         if ui_font_size_settings != prev_ui_font_size_settings {            prev_ui_font_size_settings = ui_font_size_settings;            reset_ui_font_size(cx);        }         if agent_ui_font_size_settings != prev_agent_ui_font_size_settings {            prev_agent_ui_font_size_settings = agent_ui_font_size_settings;            reset_agent_ui_font_size(cx);        }         if agent_buffer_font_size_settings != prev_agent_buffer_font_size_settings {            prev_agent_buffer_font_size_settings = agent_buffer_font_size_settings;            reset_agent_buffer_font_size(cx);        }         if theme_name != prev_theme_name || theme_overrides != prev_theme_overrides {            prev_theme_name = theme_name;            prev_theme_overrides = theme_overrides;            reload_theme(cx);        }         if icon_theme_name != prev_icon_theme_name {            prev_icon_theme_name = icon_theme_name;            reload_icon_theme(cx);        }    })    .detach();} fn configured_theme(cx: &mut App) -> Arc<Theme> {    let themes = ThemeRegistry::default_global(cx);    let theme_settings = ThemeSettings::get_global(cx);    let system_appearance = SystemAppearance::global(cx);     let theme_name = theme_settings.theme.name(*system_appearance);     let theme = match themes.get(&theme_name.0) {        Ok(theme) => theme,        Err(err) => {            if themes.extensions_loaded() {                log::error!("{err}");            }            themes                .get(default_theme(*system_appearance))                .unwrap_or_else(|_| themes.get(DEFAULT_DARK_THEME).unwrap())        }    };    theme_settings.apply_theme_overrides(theme)} fn configured_icon_theme(cx: &mut App) -> Arc<theme::IconTheme> {    let themes = ThemeRegistry::default_global(cx);    let theme_settings = ThemeSettings::get_global(cx);    let system_appearance = SystemAppearance::global(cx);     let icon_theme_name = theme_settings.icon_theme.name(*system_appearance);     match themes.get_icon_theme(&icon_theme_name.0) {        Ok(theme) => theme,        Err(err) => {            if themes.extensions_loaded() {                log::error!("{err}");            }            themes.get_icon_theme(DEFAULT_ICON_THEME_NAME).unwrap()        }    }} /// Reloads the current theme from settings.pub fn reload_theme(cx: &mut App) {    let theme = configured_theme(cx);    GlobalTheme::update_theme(cx, theme);    cx.refresh_windows();} /// Reloads the current icon theme from settings.pub fn reload_icon_theme(cx: &mut App) {    let icon_theme = configured_icon_theme(cx);    GlobalTheme::update_icon_theme(cx, icon_theme);    cx.refresh_windows();} /// Loads the themes bundled with the Zed binary into the registry.pub fn load_bundled_themes(registry: &ThemeRegistry) {    let theme_paths = registry        .assets()        .list("themes/")        .expect("failed to list theme assets")        .into_iter()        .filter(|path| path.ends_with(".json"));     for path in theme_paths {        let Some(theme) = registry.assets().load(&path).log_err().flatten() else {            continue;        };         let Some(theme_family) = serde_json::from_slice(&theme)            .with_context(|| format!("failed to parse theme at path \"{path}\""))            .log_err()        else {            continue;        };         let refined = refine_theme_family(theme_family);        registry.insert_theme_families([refined]);    }} /// Loads a user theme from the given bytes into the registry.pub fn load_user_theme(registry: &ThemeRegistry, bytes: &[u8]) -> Result<()> {    let theme = deserialize_user_theme(bytes)?;    let refined = refine_theme_family(theme);    registry.insert_theme_families([refined]);    Ok(())} /// Deserializes a user theme from the given bytes.pub fn deserialize_user_theme(bytes: &[u8]) -> Result<ThemeFamilyContent> {    let theme_family: ThemeFamilyContent = serde_json_lenient::from_slice(bytes)?;     for theme in &theme_family.themes {        if theme            .style            .colors            .deprecated_scrollbar_thumb_background            .is_some()        {            log::warn!(                r#"Theme "{theme_name}" is using a deprecated style property: scrollbar_thumb.background. Use `scrollbar.thumb.background` instead."#,                theme_name = theme.name            )        }    }     Ok(theme_family)} /// Refines a [`ThemeFamilyContent`] and its [`ThemeContent`]s into a [`ThemeFamily`].pub fn refine_theme_family(theme_family_content: ThemeFamilyContent) -> ThemeFamily {    let id = uuid::Uuid::new_v4().to_string();    let name = theme_family_content.name.clone();    let author = theme_family_content.author.clone();     let themes: Vec<Theme> = theme_family_content        .themes        .iter()        .map(|theme_content| refine_theme(theme_content))        .collect();     ThemeFamily {        id,        name: name.into(),        author: author.into(),        themes,        scales: default_color_scales(),    }} /// Refines a [`ThemeContent`] into a [`Theme`].pub fn refine_theme(theme: &ThemeContent) -> Theme {    let appearance = match theme.appearance {        AppearanceContent::Light => Appearance::Light,        AppearanceContent::Dark => Appearance::Dark,    };     let mut refined_status_colors = match theme.appearance {        AppearanceContent::Light => StatusColors::light(),        AppearanceContent::Dark => StatusColors::dark(),    };    let mut status_colors_refinement = status_colors_refinement(&theme.style.status);    theme::apply_status_color_defaults(&mut status_colors_refinement);    refined_status_colors.refine(&status_colors_refinement);     let mut refined_player_colors = match theme.appearance {        AppearanceContent::Light => PlayerColors::light(),        AppearanceContent::Dark => PlayerColors::dark(),    };    merge_player_colors(&mut refined_player_colors, &theme.style.players);     let mut refined_theme_colors = match theme.appearance {        AppearanceContent::Light => ThemeColors::light(),        AppearanceContent::Dark => ThemeColors::dark(),    };    let mut theme_colors_refinement =        theme_colors_refinement(&theme.style.colors, &status_colors_refinement);    theme::apply_theme_color_defaults(&mut theme_colors_refinement, &refined_player_colors);    refined_theme_colors.refine(&theme_colors_refinement);     let mut refined_accent_colors = match theme.appearance {        AppearanceContent::Light => AccentColors::light(),        AppearanceContent::Dark => AccentColors::dark(),    };    merge_accent_colors(&mut refined_accent_colors, &theme.style.accents);     let syntax_highlights = theme.style.syntax.iter().map(|(syntax_token, highlight)| {        (            syntax_token.clone(),            HighlightStyle {                color: highlight                    .color                    .as_ref()                    .and_then(|color| try_parse_color(color).ok()),                background_color: highlight                    .background_color                    .as_ref()                    .and_then(|color| try_parse_color(color).ok()),                font_style: highlight.font_style.map(|s| s.into_gpui()),                font_weight: highlight.font_weight.map(|w| w.into_gpui()),                ..Default::default()            },        )    });    let syntax_theme = Arc::new(SyntaxTheme::new(syntax_highlights));     let window_background_appearance = theme        .style        .window_background_appearance        .map(|w| w.into_gpui())        .unwrap_or_default();     Theme {        id: uuid::Uuid::new_v4().to_string(),        name: theme.name.clone().into(),        appearance,        styles: ThemeStyles {            system: SystemColors::default(),            window_background_appearance,            accents: refined_accent_colors,            colors: refined_theme_colors,            status: refined_status_colors,            player: refined_player_colors,            syntax: syntax_theme,        },    }} /// Merges player color overrides into the given [`PlayerColors`].pub fn merge_player_colors(    player_colors: &mut PlayerColors,    user_player_colors: &[::settings::PlayerColorContent],) {    if user_player_colors.is_empty() {        return;    }     for (idx, player) in user_player_colors.iter().enumerate() {        let cursor = player            .cursor            .as_ref()            .and_then(|color| try_parse_color(color).ok());        let background = player            .background            .as_ref()            .and_then(|color| try_parse_color(color).ok());        let selection = player            .selection            .as_ref()            .and_then(|color| try_parse_color(color).ok());         if let Some(player_color) = player_colors.0.get_mut(idx) {            *player_color = PlayerColor {                cursor: cursor.unwrap_or(player_color.cursor),                background: background.unwrap_or(player_color.background),                selection: selection.unwrap_or(player_color.selection),            };        } else {            player_colors.0.push(PlayerColor {                cursor: cursor.unwrap_or_default(),                background: background.unwrap_or_default(),                selection: selection.unwrap_or_default(),            });        }    }} /// Merges accent color overrides into the given [`AccentColors`].pub fn merge_accent_colors(    accent_colors: &mut AccentColors,    user_accent_colors: &[::settings::AccentContent],) {    if user_accent_colors.is_empty() {        return;    }     let colors = user_accent_colors        .iter()        .filter_map(|accent_color| {            accent_color                .0                .as_ref()                .and_then(|color| try_parse_color(color).ok())        })        .collect::<Vec<_>>();     if !colors.is_empty() {        accent_colors.0 = Arc::from(colors);    }}