Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:32:16.528Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

theme_settings.rs

448 lines · 16.5 KB · rust
1#![deny(missing_docs)]
2
3//! # Theme Settings
4//!
5//! This crate provides theme settings integration for Zed,
6//! bridging the theme system with the settings infrastructure.
7
8mod schema;
9mod settings;
10
11use std::sync::Arc;
12
13use ::settings::{IntoGpui, Settings, SettingsStore};
14use anyhow::{Context as _, Result};
15use gpui::{App, Font, HighlightStyle, Pixels, Refineable, px};
16use gpui_util::ResultExt;
17use theme::{
18    AccentColors, Appearance, AppearanceContent, DEFAULT_DARK_THEME, DEFAULT_ICON_THEME_NAME,
19    GlobalTheme, LoadThemes, PlayerColor, PlayerColors, StatusColors, SyntaxTheme,
20    SystemAppearance, SystemColors, Theme, ThemeColors, ThemeFamily, ThemeRegistry,
21    ThemeSettingsProvider, ThemeStyles, default_color_scales, try_parse_color,
22};
23
24pub use crate::schema::{
25    FontStyleContent, FontWeightContent, HighlightStyleContent, StatusColorsContent,
26    ThemeColorsContent, ThemeContent, ThemeFamilyContent, ThemeStyleContent,
27    WindowBackgroundContent, status_colors_refinement, syntax_overrides, theme_colors_refinement,
28};
29use crate::settings::adjust_buffer_font_size;
30pub use crate::settings::{
31    AgentBufferFontSize, AgentUiFontSize, BufferLineHeight, FontFamilyName,
32    GitCommitBufferFontSize, IconThemeName, IconThemeSelection, MarkdownPreviewFontSize,
33    ThemeAppearanceMode, ThemeName, ThemeSelection, ThemeSettings, adjust_agent_buffer_font_size,
34    adjust_agent_ui_font_size, adjust_git_commit_buffer_font_size,
35    adjust_markdown_preview_font_size, adjust_ui_font_size, adjusted_font_size, appearance_to_mode,
36    clamp_font_size, default_theme, observe_buffer_font_size_adjustment,
37    reset_agent_buffer_font_size, reset_agent_ui_font_size, reset_buffer_font_size,
38    reset_git_commit_buffer_font_size, reset_markdown_preview_font_size, reset_ui_font_size,
39    set_icon_theme, set_mode, set_theme, setup_ui_font,
40};
41pub use theme::UiDensity;
42
43struct ThemeSettingsProviderImpl;
44
45impl ThemeSettingsProvider for ThemeSettingsProviderImpl {
46    fn ui_font<'a>(&'a self, cx: &'a App) -> &'a Font {
47        &ThemeSettings::get_global(cx).ui_font
48    }
49
50    fn buffer_font<'a>(&'a self, cx: &'a App) -> &'a Font {
51        &ThemeSettings::get_global(cx).buffer_font
52    }
53
54    fn ui_font_size(&self, cx: &App) -> Pixels {
55        ThemeSettings::get_global(cx).ui_font_size(cx)
56    }
57
58    fn buffer_font_size(&self, cx: &App) -> Pixels {
59        ThemeSettings::get_global(cx).buffer_font_size(cx)
60    }
61
62    fn ui_density(&self, cx: &App) -> UiDensity {
63        ThemeSettings::get_global(cx).ui_density
64    }
65}
66
67/// Initialize the theme system with settings integration.
68///
69/// This is the full initialization for the application. It calls [`theme::init`]
70/// and then wires up settings observation for theme/font changes.
71pub fn init(themes_to_load: LoadThemes, cx: &mut App) {
72    let load_user_themes = matches!(&themes_to_load, LoadThemes::All(_));
73
74    theme::init(themes_to_load, cx);
75    theme::set_theme_settings_provider(Box::new(ThemeSettingsProviderImpl), cx);
76
77    if load_user_themes {
78        let registry = ThemeRegistry::global(cx);
79        load_bundled_themes(&registry);
80    }
81
82    let theme = configured_theme(cx);
83    let icon_theme = configured_icon_theme(cx);
84    GlobalTheme::update_theme(cx, theme);
85    GlobalTheme::update_icon_theme(cx, icon_theme);
86
87    let settings = ThemeSettings::get_global(cx);
88
89    let mut prev_buffer_font_size_settings = settings.buffer_font_size_settings();
90    let mut prev_ui_font_size_settings = settings.ui_font_size_settings();
91    let mut prev_agent_ui_font_size_settings = settings.agent_ui_font_size_settings();
92    let mut prev_agent_buffer_font_size_settings = settings.agent_buffer_font_size_settings();
93    let mut prev_git_commit_buffer_font_size_settings =
94        settings.git_commit_buffer_font_size_settings();
95    let mut prev_markdown_preview_font_size_settings =
96        settings.markdown_preview_font_size_settings();
97    let mut prev_theme_name = settings.theme.name(SystemAppearance::global(cx).0);
98    let mut prev_icon_theme_name = settings.icon_theme.name(SystemAppearance::global(cx).0);
99    let mut prev_theme_overrides = (
100        settings.experimental_theme_overrides.clone(),
101        settings.theme_overrides.clone(),
102    );
103
104    cx.observe_global::<SettingsStore>(move |cx| {
105        let settings = ThemeSettings::get_global(cx);
106
107        let buffer_font_size_settings = settings.buffer_font_size_settings();
108        let ui_font_size_settings = settings.ui_font_size_settings();
109        let agent_ui_font_size_settings = settings.agent_ui_font_size_settings();
110        let agent_buffer_font_size_settings = settings.agent_buffer_font_size_settings();
111        let git_commit_buffer_font_size_settings = settings.git_commit_buffer_font_size_settings();
112        let markdown_preview_font_size_settings = settings.markdown_preview_font_size_settings();
113        let theme_name = settings.theme.name(SystemAppearance::global(cx).0);
114        let icon_theme_name = settings.icon_theme.name(SystemAppearance::global(cx).0);
115        let theme_overrides = (
116            settings.experimental_theme_overrides.clone(),
117            settings.theme_overrides.clone(),
118        );
119
120        if buffer_font_size_settings != prev_buffer_font_size_settings {
121            prev_buffer_font_size_settings = buffer_font_size_settings;
122            reset_buffer_font_size(cx);
123        }
124
125        if ui_font_size_settings != prev_ui_font_size_settings {
126            prev_ui_font_size_settings = ui_font_size_settings;
127            reset_ui_font_size(cx);
128        }
129
130        if agent_ui_font_size_settings != prev_agent_ui_font_size_settings {
131            prev_agent_ui_font_size_settings = agent_ui_font_size_settings;
132            reset_agent_ui_font_size(cx);
133        }
134
135        if agent_buffer_font_size_settings != prev_agent_buffer_font_size_settings {
136            prev_agent_buffer_font_size_settings = agent_buffer_font_size_settings;
137            reset_agent_buffer_font_size(cx);
138        }
139
140        if git_commit_buffer_font_size_settings != prev_git_commit_buffer_font_size_settings {
141            prev_git_commit_buffer_font_size_settings = git_commit_buffer_font_size_settings;
142            reset_git_commit_buffer_font_size(cx);
143        }
144
145        if markdown_preview_font_size_settings != prev_markdown_preview_font_size_settings {
146            prev_markdown_preview_font_size_settings = markdown_preview_font_size_settings;
147            reset_markdown_preview_font_size(cx);
148        }
149
150        if theme_name != prev_theme_name || theme_overrides != prev_theme_overrides {
151            prev_theme_name = theme_name;
152            prev_theme_overrides = theme_overrides;
153            reload_theme(cx);
154        }
155
156        if icon_theme_name != prev_icon_theme_name {
157            prev_icon_theme_name = icon_theme_name;
158            reload_icon_theme(cx);
159        }
160    })
161    .detach();
162}
163
164fn configured_theme(cx: &mut App) -> Arc<Theme> {
165    let themes = ThemeRegistry::default_global(cx);
166    let theme_settings = ThemeSettings::get_global(cx);
167    let system_appearance = SystemAppearance::global(cx);
168
169    let theme_name = theme_settings.theme.name(*system_appearance);
170
171    let theme = match themes.get(&theme_name.0) {
172        Ok(theme) => theme,
173        Err(err) => {
174            if themes.extensions_loaded() {
175                log::error!("{err}");
176            }
177            themes
178                .get(default_theme(*system_appearance))
179                .unwrap_or_else(|_| themes.get(DEFAULT_DARK_THEME).unwrap())
180        }
181    };
182    theme_settings.apply_theme_overrides(theme)
183}
184
185fn configured_icon_theme(cx: &mut App) -> Arc<theme::IconTheme> {
186    let themes = ThemeRegistry::default_global(cx);
187    let theme_settings = ThemeSettings::get_global(cx);
188    let system_appearance = SystemAppearance::global(cx);
189
190    let icon_theme_name = theme_settings.icon_theme.name(*system_appearance);
191
192    match themes.get_icon_theme(&icon_theme_name.0) {
193        Ok(theme) => theme,
194        Err(err) => {
195            if themes.extensions_loaded() {
196                log::error!("{err}");
197            }
198            themes.get_icon_theme(DEFAULT_ICON_THEME_NAME).unwrap()
199        }
200    }
201}
202
203/// Reloads the current theme from settings.
204pub fn reload_theme(cx: &mut App) {
205    let theme = configured_theme(cx);
206    GlobalTheme::update_theme(cx, theme);
207    cx.refresh_windows();
208}
209
210/// Reloads the current icon theme from settings.
211pub fn reload_icon_theme(cx: &mut App) {
212    let icon_theme = configured_icon_theme(cx);
213    GlobalTheme::update_icon_theme(cx, icon_theme);
214    cx.refresh_windows();
215}
216
217/// Loads the themes bundled with the Zed binary into the registry.
218pub fn load_bundled_themes(registry: &ThemeRegistry) {
219    let theme_paths = registry
220        .assets()
221        .list("themes/")
222        .expect("failed to list theme assets")
223        .into_iter()
224        .filter(|path| path.ends_with(".json"));
225
226    for path in theme_paths {
227        let Some(theme) = registry.assets().load(&path).log_err().flatten() else {
228            continue;
229        };
230
231        let Some(theme_family) = serde_json::from_slice(&theme)
232            .with_context(|| format!("failed to parse theme at path \"{path}\""))
233            .log_err()
234        else {
235            continue;
236        };
237
238        let refined = refine_theme_family(theme_family);
239        registry.insert_theme_families([refined]);
240    }
241}
242
243/// Loads a user theme from the given bytes into the registry.
244pub fn load_user_theme(registry: &ThemeRegistry, bytes: &[u8]) -> Result<()> {
245    let theme = deserialize_user_theme(bytes)?;
246    let refined = refine_theme_family(theme);
247    registry.insert_theme_families([refined]);
248    Ok(())
249}
250
251/// Deserializes a user theme from the given bytes.
252pub fn deserialize_user_theme(bytes: &[u8]) -> Result<ThemeFamilyContent> {
253    let theme_family: ThemeFamilyContent = serde_json_lenient::from_slice(bytes)?;
254
255    for theme in &theme_family.themes {
256        if theme
257            .style
258            .colors
259            .deprecated_scrollbar_thumb_background
260            .is_some()
261        {
262            log::warn!(
263                r#"Theme "{theme_name}" is using a deprecated style property: scrollbar_thumb.background. Use `scrollbar.thumb.background` instead."#,
264                theme_name = theme.name
265            )
266        }
267    }
268
269    Ok(theme_family)
270}
271
272/// Refines a [`ThemeFamilyContent`] and its [`ThemeContent`]s into a [`ThemeFamily`].
273pub fn refine_theme_family(theme_family_content: ThemeFamilyContent) -> ThemeFamily {
274    let id = uuid::Uuid::new_v4().to_string();
275    let name = theme_family_content.name.clone();
276    let author = theme_family_content.author.clone();
277
278    let themes: Vec<Theme> = theme_family_content
279        .themes
280        .iter()
281        .map(|theme_content| refine_theme(theme_content))
282        .collect();
283
284    ThemeFamily {
285        id,
286        name: name.into(),
287        author: author.into(),
288        themes,
289        scales: default_color_scales(),
290    }
291}
292
293/// Refines a [`ThemeContent`] into a [`Theme`].
294pub fn refine_theme(theme: &ThemeContent) -> Theme {
295    let appearance = match theme.appearance {
296        AppearanceContent::Light => Appearance::Light,
297        AppearanceContent::Dark => Appearance::Dark,
298    };
299
300    let mut refined_status_colors = match theme.appearance {
301        AppearanceContent::Light => StatusColors::light(),
302        AppearanceContent::Dark => StatusColors::dark(),
303    };
304    let mut status_colors_refinement = status_colors_refinement(&theme.style.status);
305    theme::apply_status_color_defaults(&mut status_colors_refinement);
306    refined_status_colors.refine(&status_colors_refinement);
307
308    let mut refined_player_colors = match theme.appearance {
309        AppearanceContent::Light => PlayerColors::light(),
310        AppearanceContent::Dark => PlayerColors::dark(),
311    };
312    merge_player_colors(&mut refined_player_colors, &theme.style.players);
313
314    let mut refined_theme_colors = match theme.appearance {
315        AppearanceContent::Light => ThemeColors::light(),
316        AppearanceContent::Dark => ThemeColors::dark(),
317    };
318    let mut theme_colors_refinement = theme_colors_refinement(
319        &theme.style.colors,
320        &status_colors_refinement,
321        theme.appearance == AppearanceContent::Light,
322    );
323    theme::apply_theme_color_defaults(&mut theme_colors_refinement, &refined_player_colors);
324    refined_theme_colors.refine(&theme_colors_refinement);
325
326    let mut refined_accent_colors = match theme.appearance {
327        AppearanceContent::Light => AccentColors::light(),
328        AppearanceContent::Dark => AccentColors::dark(),
329    };
330    merge_accent_colors(&mut refined_accent_colors, &theme.style.accents);
331
332    let syntax_highlights = theme.style.syntax.iter().map(|(syntax_token, highlight)| {
333        (
334            syntax_token.clone(),
335            HighlightStyle {
336                color: highlight
337                    .color
338                    .as_ref()
339                    .and_then(|color| try_parse_color(color).ok()),
340                background_color: highlight
341                    .background_color
342                    .as_ref()
343                    .and_then(|color| try_parse_color(color).ok()),
344                font_style: highlight.font_style.map(|s| s.into_gpui()),
345                font_weight: highlight.font_weight.map(|w| w.into_gpui()),
346                ..Default::default()
347            },
348        )
349    });
350    let syntax_theme = Arc::new(SyntaxTheme::new(syntax_highlights));
351
352    let window_background_appearance = theme
353        .style
354        .window_background_appearance
355        .map(|w| w.into_gpui())
356        .unwrap_or_default();
357
358    Theme {
359        id: uuid::Uuid::new_v4().to_string(),
360        name: theme.name.clone().into(),
361        appearance,
362        styles: ThemeStyles {
363            system: SystemColors::default(),
364            window_background_appearance,
365            accents: refined_accent_colors,
366            colors: refined_theme_colors,
367            status: refined_status_colors,
368            player: refined_player_colors,
369            syntax: syntax_theme,
370        },
371    }
372}
373
374/// Merges player color overrides into the given [`PlayerColors`].
375pub fn merge_player_colors(
376    player_colors: &mut PlayerColors,
377    user_player_colors: &[::settings::PlayerColorContent],
378) {
379    if user_player_colors.is_empty() {
380        return;
381    }
382
383    for (idx, player) in user_player_colors.iter().enumerate() {
384        let cursor = player
385            .cursor
386            .as_ref()
387            .and_then(|color| try_parse_color(color).ok());
388        let background = player
389            .background
390            .as_ref()
391            .and_then(|color| try_parse_color(color).ok());
392        let selection = player
393            .selection
394            .as_ref()
395            .and_then(|color| try_parse_color(color).ok());
396
397        if let Some(player_color) = player_colors.0.get_mut(idx) {
398            *player_color = PlayerColor {
399                cursor: cursor.unwrap_or(player_color.cursor),
400                background: background.unwrap_or(player_color.background),
401                selection: selection.unwrap_or(player_color.selection),
402            };
403        } else {
404            player_colors.0.push(PlayerColor {
405                cursor: cursor.unwrap_or_default(),
406                background: background.unwrap_or_default(),
407                selection: selection.unwrap_or_default(),
408            });
409        }
410    }
411}
412
413/// Merges accent color overrides into the given [`AccentColors`].
414pub fn merge_accent_colors(
415    accent_colors: &mut AccentColors,
416    user_accent_colors: &[::settings::AccentContent],
417) {
418    if user_accent_colors.is_empty() {
419        return;
420    }
421
422    let colors = user_accent_colors
423        .iter()
424        .filter_map(|accent_color| {
425            accent_color
426                .0
427                .as_ref()
428                .and_then(|color| try_parse_color(color).ok())
429        })
430        .collect::<Vec<_>>();
431
432    if !colors.is_empty() {
433        accent_colors.0 = Arc::from(colors);
434    }
435}
436
437/// Increases the buffer font size by 1 pixel, without persisting the result in the settings.
438/// This will be effective until the app is restarted.
439pub fn increase_buffer_font_size(cx: &mut App) {
440    adjust_buffer_font_size(cx, |size| size + px(1.0));
441}
442
443/// Decreases the buffer font size by 1 pixel, without persisting the result in the settings.
444/// This will be effective until the app is restarted.
445pub fn decrease_buffer_font_size(cx: &mut App) {
446    adjust_buffer_font_size(cx, |size| size - px(1.0));
447}
448
Served at tenant.openagents/omega Member data and write actions are omitted.