Allow dynamic set_theme based on Appearance (#42812)

Tracking Issue (does not close):
https://github.com/zed-industries/zed/issues/35552

This is somewhat of a blocker for
https://github.com/zed-industries/zed/pull/40035 (but also the current
behavior doesn't really make sense).

The current behavior of `ThemeSelectorDelegate::set_theme` (the theme
selector menu) is to simply set the in-memory settings to `Static`,
regardless of if it is currently `Dynamic`. The reason this doesn't
matter now is that the `theme::set_theme` function that updates the
user's settings file _will_ make this check, so dynamic settings stay
dynamic in `settings.json`, but not in memory.

But this is also sort of strange, because `theme::set_theme` will set
the setting of whatever the old appearance was to the new theme name. In
other words, if I am currently on a light mode theme and I change my
theme to a dark mode theme using the theme selector, the `light` field
of `theme` in `settings.json` is set to a dark mode theme!

_I think this is because displaying the new theme in the theme selector
does not update the global context, so
`ThemeSettings::get_global(cx).theme.name(appearance).0` returns the
original theme appearance, not the new one._

---

This PR makes `ThemeSelectorDelegate::set_theme` keep the current
`ThemeSelection`, as well as changes the behavior of the
`theme::set_theme` call to always choose the correct setting to update.

One edge case that might be slightly strange now is that if the user has
specified the mode as `System`, this will now override that with the
appearance of the new theme. I think this is fine, as otherwise a user
might set a dark theme and nothing will change because the
`ThemeAppearanceMode` is set to `light` or `system` (where `system` is
also light).

I also have an `unreachable!` in there that I'm pretty sure is true but
I don't really know how to formally prove that...

Release Notes:

- N/A *or* Added/Fixed/Improved ...

---------

Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
This commit is contained in:
Connor Tsui
2025-12-01 20:52:57 -07:00
committed by GitHub
parent 464c0be2b7
commit 6216af9b5a
4 changed files with 203 additions and 50 deletions
+38 -19
View File
@@ -304,31 +304,50 @@ impl IconThemeSelection {
}
}
// impl ThemeSettingsContent {
/// Sets the theme for the given appearance to the theme with the specified name.
///
/// The caller should make sure that the [`Appearance`] matches the theme associated with the name.
///
/// If the current [`ThemeAppearanceMode`] is set to [`System`] and the user's system [`Appearance`]
/// is different than the new theme's [`Appearance`], this function will update the
/// [`ThemeAppearanceMode`] to the new theme's appearance in order to display the new theme.
///
/// [`System`]: ThemeAppearanceMode::System
pub fn set_theme(
current: &mut SettingsContent,
theme_name: impl Into<Arc<str>>,
appearance: Appearance,
theme_appearance: Appearance,
system_appearance: Appearance,
) {
if let Some(selection) = current.theme.theme.as_mut() {
let theme_to_update = match selection {
settings::ThemeSelection::Static(theme) => theme,
settings::ThemeSelection::Dynamic { mode, light, dark } => match mode {
ThemeAppearanceMode::Light => light,
ThemeAppearanceMode::Dark => dark,
ThemeAppearanceMode::System => match appearance {
Appearance::Light => light,
Appearance::Dark => dark,
},
},
};
let theme_name = ThemeName(theme_name.into());
*theme_to_update = ThemeName(theme_name.into());
} else {
current.theme.theme = Some(settings::ThemeSelection::Static(ThemeName(
theme_name.into(),
)));
let Some(selection) = current.theme.theme.as_mut() else {
current.theme.theme = Some(settings::ThemeSelection::Static(theme_name));
return;
};
match selection {
settings::ThemeSelection::Static(theme) => {
*theme = theme_name;
}
settings::ThemeSelection::Dynamic { mode, light, dark } => {
// Update the appropriate theme slot based on appearance.
match theme_appearance {
Appearance::Light => *light = theme_name,
Appearance::Dark => *dark = theme_name,
}
// Don't update the theme mode if it is set to system and the new theme has the same
// appearance.
let should_update_mode =
!(mode == &ThemeAppearanceMode::System && theme_appearance == system_appearance);
if should_update_mode {
// Update the mode to the specified appearance (otherwise we might set the theme and
// nothing gets updated because the system specified the other mode appearance).
*mode = ThemeAppearanceMode::from(theme_appearance);
}
}
}
}
+9
View File
@@ -84,6 +84,15 @@ impl From<WindowAppearance> for Appearance {
}
}
impl From<Appearance> for ThemeAppearanceMode {
fn from(value: Appearance) -> Self {
match value {
Appearance::Light => Self::Light,
Appearance::Dark => Self::Dark,
}
}
}
/// Which themes should be loaded. This is used primarily for testing.
pub enum LoadThemes {
/// Only load the base theme.