Skip to content

Commit

Permalink
Eagerly load the active theme and icon theme (#25368)
Browse files Browse the repository at this point in the history
This PR adds eager loading of the active theme and icon theme set in the
user settings.

Previously for themes and icon themes that were provided by extensions,
we would have to wait until extensions were loaded before we could apply
the themes.

In some cases this could lead to a visible delay during which time the
user would see the default themes, and then switch to their desired
themes once extensions had loaded.

To avoid this, we now take a fast path of loading the active themes
directly from the filesystem so that we can load them as soon as
possible.

Closes #10173 and #25305.

Release Notes:

- Added eager loading of the active theme and icon theme. This should
address some reports of seeing the default themes briefly on startup.
  • Loading branch information
maxdeviant authored Feb 21, 2025
1 parent aba89ba commit ec56755
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 2 deletions.
29 changes: 29 additions & 0 deletions crates/extension_host/src/extension_host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,18 @@ impl ExtensionStore {
.filter_map(|(name, theme)| theme.extension.as_ref().eq(extension_id).then_some(name))
}

/// Returns the path to the theme file within an extension, if there is an
/// extension that provides the theme.
pub fn path_to_extension_theme(&self, theme_name: &str) -> Option<PathBuf> {
let entry = self.extension_index.themes.get(theme_name)?;

Some(
self.extensions_dir()
.join(entry.extension.as_ref())
.join(&entry.path),
)
}

/// Returns the names of icon themes provided by extensions.
pub fn extension_icon_themes<'a>(
&'a self,
Expand All @@ -459,6 +471,23 @@ impl ExtensionStore {
})
}

/// Returns the path to the icon theme file within an extension, if there is
/// an extension that provides the icon theme.
pub fn path_to_extension_icon_theme(
&self,
icon_theme_name: &str,
) -> Option<(PathBuf, PathBuf)> {
let entry = self.extension_index.icon_themes.get(icon_theme_name)?;

let icon_theme_path = self
.extensions_dir()
.join(entry.extension.as_ref())
.join(&entry.path);
let icons_root_path = self.extensions_dir().join(entry.extension.as_ref());

Some((icon_theme_path, icons_root_path))
}

pub fn fetch_extensions(
&self,
search: Option<&str>,
Expand Down
71 changes: 69 additions & 2 deletions crates/zed/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE};
use editor::Editor;
use env_logger::Builder;
use extension::ExtensionHostProxy;
use extension_host::ExtensionStore;
use fs::{Fs, RealFs};
use futures::{future, StreamExt};
use git::GitHostingProviderRegistry;
Expand Down Expand Up @@ -44,7 +45,10 @@ use std::{
process,
sync::Arc,
};
use theme::{ActiveTheme, SystemAppearance, ThemeRegistry, ThemeSettings};
use theme::{
ActiveTheme, IconThemeNotFoundError, SystemAppearance, ThemeNotFoundError, ThemeRegistry,
ThemeSettings,
};
use time::UtcOffset;
use util::{maybe, ResultExt, TryFutureExt};
use uuid::Uuid;
Expand Down Expand Up @@ -515,10 +519,10 @@ fn main() {
zeta::init(cx);

cx.observe_global::<SettingsStore>({
let fs = fs.clone();
let languages = app_state.languages.clone();
let http = app_state.client.http_client();
let client = app_state.client.clone();

move |cx| {
for &mut window in cx.windows().iter_mut() {
let background_appearance = cx.theme().window_background_appearance();
Expand All @@ -528,6 +532,9 @@ fn main() {
})
.ok();
}

eager_load_active_theme_and_icon_theme(fs.clone(), cx);

languages.set_theme(cx.theme().clone());
let new_host = &client::ClientSettings::get_global(cx).server_url;
if &http.base_url() != new_host {
Expand Down Expand Up @@ -1062,6 +1069,66 @@ fn load_embedded_fonts(cx: &App) {
.unwrap();
}

/// Eagerly loads the active theme and icon theme based on the selections in the
/// theme settings.
///
/// This fast path exists to load these themes as soon as possible so the user
/// doesn't see the default themes while waiting on extensions to load.
fn eager_load_active_theme_and_icon_theme(fs: Arc<dyn Fs>, cx: &App) {
let extension_store = ExtensionStore::global(cx);
let theme_registry = ThemeRegistry::global(cx);
let theme_settings = ThemeSettings::get_global(cx);
let appearance = cx.window_appearance().into();

if let Some(theme_selection) = theme_settings.theme_selection.as_ref() {
let theme_name = theme_selection.theme(appearance);
if matches!(theme_registry.get(theme_name), Err(ThemeNotFoundError(_))) {
if let Some(theme_path) = extension_store.read(cx).path_to_extension_theme(theme_name) {
cx.spawn({
let theme_registry = theme_registry.clone();
let fs = fs.clone();
|cx| async move {
theme_registry.load_user_theme(&theme_path, fs).await?;

cx.update(|cx| {
ThemeSettings::reload_current_theme(cx);
})
}
})
.detach_and_log_err(cx);
}
}
}

if let Some(icon_theme_selection) = theme_settings.icon_theme_selection.as_ref() {
let icon_theme_name = icon_theme_selection.icon_theme(appearance);
if matches!(
theme_registry.get_icon_theme(icon_theme_name),
Err(IconThemeNotFoundError(_))
) {
if let Some((icon_theme_path, icons_root_path)) = extension_store
.read(cx)
.path_to_extension_icon_theme(icon_theme_name)
{
cx.spawn({
let theme_registry = theme_registry.clone();
let fs = fs.clone();
|cx| async move {
theme_registry
.load_icon_theme(&icon_theme_path, &icons_root_path, fs)
.await?;

cx.update(|cx| {
ThemeSettings::reload_current_icon_theme(cx);
})
}
})
.detach_and_log_err(cx);
}
}
}
}

/// Spawns a background task to load the user themes from the themes directory.
fn load_user_themes_in_background(fs: Arc<dyn fs::Fs>, cx: &mut App) {
cx.spawn({
Expand Down

0 comments on commit ec56755

Please sign in to comment.