watched_json.rs

  1use fs::Fs;
  2use futures::StreamExt;
  3use gpui::{executor, MutableAppContext};
  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_settings_file(
 66    defaults: Settings,
 67    mut file: WatchedJsonFile<SettingsFileContent>,
 68    theme_registry: Arc<ThemeRegistry>,
 69    cx: &mut MutableAppContext,
 70) {
 71    settings_updated(&defaults, file.0.borrow().clone(), &theme_registry, cx);
 72    cx.spawn(|mut cx| async move {
 73        while let Some(content) = file.0.recv().await {
 74            cx.update(|cx| settings_updated(&defaults, content, &theme_registry, cx));
 75        }
 76    })
 77    .detach();
 78}
 79
 80pub fn keymap_updated(content: KeymapFileContent, cx: &mut MutableAppContext) {
 81    cx.clear_bindings();
 82    KeymapFileContent::load_defaults(cx);
 83    content.add_to_cx(cx).log_err();
 84}
 85
 86pub fn settings_updated(
 87    defaults: &Settings,
 88    content: SettingsFileContent,
 89    theme_registry: &Arc<ThemeRegistry>,
 90    cx: &mut MutableAppContext,
 91) {
 92    let mut settings = defaults.clone();
 93    settings.set_user_settings(content, theme_registry, cx.font_cache());
 94    cx.set_global(settings);
 95    cx.refresh_windows();
 96}
 97
 98pub fn watch_keymap_file(mut file: WatchedJsonFile<KeymapFileContent>, cx: &mut MutableAppContext) {
 99    cx.spawn(|mut cx| async move {
100        while let Some(content) = file.0.recv().await {
101            cx.update(|cx| keymap_updated(content, cx));
102        }
103    })
104    .detach();
105}