watched_json.rs

  1use fs::Fs;
  2use futures::StreamExt;
  3use gpui::{executor, AppContext};
  4use postage::sink::Sink as _;
  5use postage::{prelude::Stream, watch};
  6use serde::Deserialize;
  7
  8use std::{path::Path, sync::Arc, time::Duration};
  9use theme::ThemeRegistry;
 10use util::ResultExt;
 11
 12use crate::{parse_json_with_comments, KeymapFileContent, Settings, SettingsFileContent};
 13
 14#[derive(Clone)]
 15pub struct WatchedJsonFile<T>(pub watch::Receiver<T>);
 16
 17impl<T> WatchedJsonFile<T>
 18where
 19    T: 'static + for<'de> Deserialize<'de> + Clone + Default + Send + Sync,
 20{
 21    pub async fn new(
 22        fs: Arc<dyn Fs>,
 23        executor: &executor::Background,
 24        path: impl Into<Arc<Path>>,
 25    ) -> Self {
 26        let path = path.into();
 27        let settings = Self::load(fs.clone(), &path).await.unwrap_or_default();
 28        let mut events = fs.watch(&path, Duration::from_millis(500)).await;
 29        let (mut tx, rx) = watch::channel_with(settings);
 30        executor
 31            .spawn(async move {
 32                while events.next().await.is_some() {
 33                    if let Some(settings) = Self::load(fs.clone(), &path).await {
 34                        if tx.send(settings).await.is_err() {
 35                            break;
 36                        }
 37                    }
 38                }
 39            })
 40            .detach();
 41        Self(rx)
 42    }
 43
 44    ///Loads the given watched JSON file. In the special case that the file is
 45    ///empty (ignoring whitespace) or is not a file, this will return T::default()
 46    async fn load(fs: Arc<dyn Fs>, path: &Path) -> Option<T> {
 47        if !fs.is_file(path).await {
 48            return Some(T::default());
 49        }
 50
 51        fs.load(path).await.log_err().and_then(|data| {
 52            if data.trim().is_empty() {
 53                Some(T::default())
 54            } else {
 55                parse_json_with_comments(&data).log_err()
 56            }
 57        })
 58    }
 59
 60    pub fn current(&self) -> T {
 61        self.0.borrow().clone()
 62    }
 63}
 64
 65pub fn watch_files(
 66    defaults: Settings,
 67    settings_file: WatchedJsonFile<SettingsFileContent>,
 68    theme_registry: Arc<ThemeRegistry>,
 69    keymap_file: WatchedJsonFile<KeymapFileContent>,
 70    cx: &mut AppContext,
 71) {
 72    watch_settings_file(defaults, settings_file, theme_registry, cx);
 73    watch_keymap_file(keymap_file, cx);
 74}
 75
 76pub(crate) fn watch_settings_file(
 77    defaults: Settings,
 78    mut file: WatchedJsonFile<SettingsFileContent>,
 79    theme_registry: Arc<ThemeRegistry>,
 80    cx: &mut AppContext,
 81) {
 82    settings_updated(&defaults, file.0.borrow().clone(), &theme_registry, cx);
 83    cx.spawn(|mut cx| async move {
 84        while let Some(content) = file.0.recv().await {
 85            cx.update(|cx| settings_updated(&defaults, content, &theme_registry, cx));
 86        }
 87    })
 88    .detach();
 89}
 90
 91fn keymap_updated(content: KeymapFileContent, cx: &mut AppContext) {
 92    cx.clear_bindings();
 93    KeymapFileContent::load_defaults(cx);
 94    content.add_to_cx(cx).log_err();
 95}
 96
 97fn settings_updated(
 98    defaults: &Settings,
 99    content: SettingsFileContent,
100    theme_registry: &Arc<ThemeRegistry>,
101    cx: &mut AppContext,
102) {
103    let mut settings = defaults.clone();
104    settings.set_user_settings(content, theme_registry, cx.font_cache());
105    cx.set_global(settings);
106    cx.refresh_windows();
107}
108
109fn watch_keymap_file(mut file: WatchedJsonFile<KeymapFileContent>, cx: &mut AppContext) {
110    cx.spawn(|mut cx| async move {
111        let mut settings_subscription = None;
112        while let Some(content) = file.0.recv().await {
113            cx.update(|cx| {
114                let old_base_keymap = cx.global::<Settings>().base_keymap;
115                keymap_updated(content.clone(), cx);
116                settings_subscription = Some(cx.observe_global::<Settings, _>(move |cx| {
117                    let settings = cx.global::<Settings>();
118                    if settings.base_keymap != old_base_keymap {
119                        keymap_updated(content.clone(), cx);
120                    }
121                }));
122            });
123        }
124    })
125    .detach();
126}