From ec56755f9e8194dfe1021b5bd7b07c8e5878af01 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 21 Feb 2025 18:57:55 -0500 Subject: [PATCH] Eagerly load the active theme and icon theme (#25368) 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. --- crates/extension_host/src/extension_host.rs | 29 +++++++++ crates/zed/src/main.rs | 71 ++++++++++++++++++++- 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs index 45fe5dc8ce..6bc6b0468b 100644 --- a/crates/extension_host/src/extension_host.rs +++ b/crates/extension_host/src/extension_host.rs @@ -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 { + 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, @@ -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>, diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 6ec65acb40..b56aceafe1 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -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; @@ -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; @@ -515,10 +519,10 @@ fn main() { zeta::init(cx); cx.observe_global::({ + 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(); @@ -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 { @@ -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, 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, cx: &mut App) { cx.spawn({