theme: Implement icon theme reloading (#24449)

Finn Evers created

Closes #24353 

This PR implements icon theme reload to ensure file icons are properly
updated whenever an icon theme extension is upgraded or uninstalled.

Currently, on both upgrade and uninstall of an icon theme extension the
file icons from the previously installed version will stay visibile and
will not be updated as shown in the linked issue. With this change, file
icons will properly be updated on extension upgrade or reinstall.

The code is primarily a copy for reloading the current color theme
adapted to work for icon themes. Happy for any feedback!


Release Notes:

- Fixed file icons not being properly updated upon icon theme upgrade or
uninstall.

Change summary

crates/extension/src/extension_host_proxy.rs  | 10 +++++
crates/extension_host/src/extension_host.rs   |  1 
crates/theme/src/settings.rs                  | 42 +++++++++++++++++++++
crates/theme_extension/src/theme_extension.rs |  4 ++
4 files changed, 57 insertions(+)

Detailed changes

crates/extension/src/extension_host_proxy.rs 🔗

@@ -118,6 +118,8 @@ pub trait ExtensionThemeProxy: Send + Sync + 'static {
         icons_root_dir: PathBuf,
         fs: Arc<dyn Fs>,
     ) -> Task<Result<()>>;
+
+    fn reload_current_icon_theme(&self, cx: &mut App);
 }
 
 impl ExtensionThemeProxy for ExtensionHostProxy {
@@ -185,6 +187,14 @@ impl ExtensionThemeProxy for ExtensionHostProxy {
 
         proxy.load_icon_theme(icon_theme_path, icons_root_dir, fs)
     }
+
+    fn reload_current_icon_theme(&self, cx: &mut App) {
+        let Some(proxy) = self.theme_proxy.read().clone() else {
+            return;
+        };
+
+        proxy.reload_current_icon_theme(cx)
+    }
 }
 
 pub trait ExtensionGrammarProxy: Send + Sync + 'static {

crates/theme/src/settings.rs 🔗

@@ -164,6 +164,30 @@ impl ThemeSettings {
             }
         }
     }
+
+    /// Reloads the current icon theme.
+    ///
+    /// Reads the [`ThemeSettings`] to know which icon theme should be loaded.
+    pub fn reload_current_icon_theme(cx: &mut App) {
+        let mut theme_settings = ThemeSettings::get_global(cx).clone();
+
+        let active_theme = theme_settings.active_icon_theme.clone();
+        let mut icon_theme_name = active_theme.name.as_ref();
+
+        // If the selected theme doesn't exist, fall back to the default theme.
+        let theme_registry = ThemeRegistry::global(cx);
+        if theme_registry
+            .get_icon_theme(icon_theme_name)
+            .ok()
+            .is_none()
+        {
+            icon_theme_name = DEFAULT_ICON_THEME_NAME;
+        };
+
+        if let Some(_theme) = theme_settings.switch_icon_theme(icon_theme_name, cx) {
+            ThemeSettings::override_global(theme_settings, cx);
+        }
+    }
 }
 
 /// The appearance of the system.
@@ -487,6 +511,24 @@ impl ThemeSettings {
             self.active_theme = Arc::new(base_theme);
         }
     }
+
+    /// Switches to the icon theme with the given name, if it exists.
+    ///
+    /// Returns a `Some` containing the new icon theme if it was successful.
+    /// Returns `None` otherwise.
+    pub fn switch_icon_theme(&mut self, icon_theme: &str, cx: &mut App) -> Option<Arc<IconTheme>> {
+        let themes = ThemeRegistry::default_global(cx);
+
+        let mut new_icon_theme = None;
+
+        if let Some(icon_theme) = themes.get_icon_theme(icon_theme).log_err() {
+            self.active_icon_theme = icon_theme.clone();
+            new_icon_theme = Some(icon_theme);
+            cx.refresh_windows();
+        }
+
+        new_icon_theme
+    }
 }
 
 // TODO: Make private, change usages to use `get_ui_font_size` instead.