From c4083c3cf6e603298f4048d07eb321867c4151b9 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 31 Jan 2024 18:17:31 -0500 Subject: [PATCH] Watch the themes directory for changes (#7173) This PR makes Zed watch the themes directory for changes. When theme files are added or modified, we reload the theme and apply any changes to Zed. Release Notes: - Added live reloading for the themes directory. --- crates/theme/src/registry.rs | 22 ++++--- crates/zed/src/main.rs | 107 +++++++++++++++++++++++++---------- 2 files changed, 91 insertions(+), 38 deletions(-) diff --git a/crates/theme/src/registry.rs b/crates/theme/src/registry.rs index 2bbb5c6120..fac119ff1d 100644 --- a/crates/theme/src/registry.rs +++ b/crates/theme/src/registry.rs @@ -255,19 +255,23 @@ impl ThemeRegistry { continue; }; - let Some(reader) = fs.open_sync(&theme_path).await.log_err() else { - continue; - }; - - let Some(theme) = serde_json_lenient::from_reader(reader).log_err() else { - continue; - }; - - self.insert_user_theme_families([theme]); + self.load_user_theme(&theme_path, fs.clone()) + .await + .log_err(); } Ok(()) } + + /// Loads the user theme from the specified path and adds it to the registry. + pub async fn load_user_theme(&self, theme_path: &Path, fs: Arc) -> Result<()> { + let reader = fs.open_sync(&theme_path).await?; + let theme = serde_json_lenient::from_reader(reader)?; + + self.insert_user_theme_families([theme]); + + Ok(()) + } } impl Default for ThemeRegistry { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index a7d7b42c91..a2436ffb5d 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -11,6 +11,7 @@ use db::kvp::KEY_VALUE_STORE; use editor::Editor; use env_logger::Builder; use fs::RealFs; +use fsevent::StreamFlags; use futures::StreamExt; use gpui::{App, AppContext, AsyncAppContext, Context, SemanticVersion, Task}; use isahc::{prelude::Configurable, Request}; @@ -171,35 +172,8 @@ fn main() { ); assistant::init(cx); - // TODO: Should we be loading the themes in a different spot? - cx.spawn({ - let fs = fs.clone(); - |cx| async move { - if let Some(theme_registry) = - cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err() - { - if let Some(()) = theme_registry - .load_user_themes(&paths::THEMES_DIR.clone(), fs) - .await - .log_err() - { - cx.update(|cx| { - let mut theme_settings = ThemeSettings::get_global(cx).clone(); - - if let Some(requested_theme) = theme_settings.requested_theme.clone() { - if let Some(_theme) = - theme_settings.switch_theme(&requested_theme, cx) - { - ThemeSettings::override_global(theme_settings, cx); - } - } - }) - .log_err(); - } - } - } - }) - .detach(); + load_user_themes_in_background(fs.clone(), cx); + watch_themes(fs.clone(), cx); cx.spawn(|_| watch_languages(fs.clone(), languages.clone())) .detach(); @@ -903,6 +877,81 @@ fn load_embedded_fonts(cx: &AppContext) { .unwrap(); } +/// Spawns a background task to load the user themes from the themes directory. +fn load_user_themes_in_background(fs: Arc, cx: &mut AppContext) { + cx.spawn({ + let fs = fs.clone(); + |cx| async move { + if let Some(theme_registry) = + cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err() + { + if let Some(()) = theme_registry + .load_user_themes(&paths::THEMES_DIR.clone(), fs) + .await + .log_err() + { + cx.update(|cx| { + let mut theme_settings = ThemeSettings::get_global(cx).clone(); + + if let Some(requested_theme) = theme_settings.requested_theme.clone() { + if let Some(_theme) = theme_settings.switch_theme(&requested_theme, cx) + { + ThemeSettings::override_global(theme_settings, cx); + } + } + }) + .log_err(); + } + } + } + }) + .detach(); +} + +/// Spawns a background task to watch the themes directory for changes. +fn watch_themes(fs: Arc, cx: &mut AppContext) { + cx.spawn(|cx| async move { + let mut events = fs + .watch(&paths::THEMES_DIR.clone(), Duration::from_millis(100)) + .await; + + while let Some(events) = events.next().await { + for event in events { + if event.flags.contains(StreamFlags::ITEM_REMOVED) { + // Theme was removed, don't need to reload. + // We may want to remove the theme from the registry, in this case. + } else { + if let Some(theme_registry) = + cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err() + { + if let Some(()) = theme_registry + .load_user_theme(&event.path, fs.clone()) + .await + .log_err() + { + cx.update(|cx| { + let mut theme_settings = ThemeSettings::get_global(cx).clone(); + + if let Some(requested_theme) = + theme_settings.requested_theme.clone() + { + if let Some(_theme) = + theme_settings.switch_theme(&requested_theme, cx) + { + ThemeSettings::override_global(theme_settings, cx); + } + } + }) + .log_err(); + } + } + } + } + } + }) + .detach() +} + async fn watch_languages(fs: Arc, languages: Arc) { let reload_debounce = Duration::from_millis(250);