settings_file.rs

  1use crate::{settings_store::SettingsStore, Setting, DEFAULT_SETTINGS_ASSET_PATH};
  2use anyhow::Result;
  3use assets::Assets;
  4use fs::Fs;
  5use futures::{channel::mpsc, StreamExt};
  6use gpui::{executor::Background, AppContext, AssetSource};
  7use std::{borrow::Cow, io::ErrorKind, path::PathBuf, str, sync::Arc, time::Duration};
  8use util::{paths, ResultExt};
  9
 10pub fn register<T: Setting>(cx: &mut AppContext) {
 11    cx.update_global::<SettingsStore, _, _>(|store, cx| {
 12        store.register_setting::<T>(cx);
 13    });
 14}
 15
 16pub fn get<'a, T: Setting>(cx: &'a AppContext) -> &'a T {
 17    cx.global::<SettingsStore>().get(None)
 18}
 19
 20pub fn default_settings() -> Cow<'static, str> {
 21    match Assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap() {
 22        Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
 23        Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
 24    }
 25}
 26
 27pub const EMPTY_THEME_NAME: &'static str = "empty-theme";
 28
 29#[cfg(any(test, feature = "test-support"))]
 30pub fn test_settings() -> String {
 31    let mut value = crate::settings_store::parse_json_with_comments::<serde_json::Value>(
 32        default_settings().as_ref(),
 33    )
 34    .unwrap();
 35    util::merge_non_null_json_value_into(
 36        serde_json::json!({
 37            "buffer_font_family": "Courier",
 38            "buffer_font_features": {},
 39            "buffer_font_size": 14,
 40            "theme": EMPTY_THEME_NAME,
 41        }),
 42        &mut value,
 43    );
 44    value.as_object_mut().unwrap().remove("languages");
 45    serde_json::to_string(&value).unwrap()
 46}
 47
 48pub fn watch_config_file(
 49    executor: Arc<Background>,
 50    fs: Arc<dyn Fs>,
 51    path: PathBuf,
 52) -> mpsc::UnboundedReceiver<String> {
 53    let (tx, rx) = mpsc::unbounded();
 54    executor
 55        .spawn(async move {
 56            let events = fs.watch(&path, Duration::from_millis(100)).await;
 57            futures::pin_mut!(events);
 58
 59            let contents = fs.load(&path).await.unwrap_or_default();
 60            if tx.unbounded_send(contents).is_err() {
 61                return;
 62            }
 63
 64            loop {
 65                if events.next().await.is_none() {
 66                    break;
 67                }
 68
 69                if let Ok(contents) = fs.load(&path).await {
 70                    if !tx.unbounded_send(contents).is_ok() {
 71                        break;
 72                    }
 73                }
 74            }
 75        })
 76        .detach();
 77    rx
 78}
 79
 80pub fn handle_settings_file_changes(
 81    mut user_settings_file_rx: mpsc::UnboundedReceiver<String>,
 82    cx: &mut AppContext,
 83) {
 84    let user_settings_content = cx.background().block(user_settings_file_rx.next()).unwrap();
 85    cx.update_global::<SettingsStore, _, _>(|store, cx| {
 86        store
 87            .set_user_settings(&user_settings_content, cx)
 88            .log_err();
 89    });
 90    cx.spawn(move |mut cx| async move {
 91        while let Some(user_settings_content) = user_settings_file_rx.next().await {
 92            cx.update(|cx| {
 93                cx.update_global::<SettingsStore, _, _>(|store, cx| {
 94                    store
 95                        .set_user_settings(&user_settings_content, cx)
 96                        .log_err();
 97                });
 98                cx.refresh_windows();
 99            });
100        }
101    })
102    .detach();
103}
104
105async fn load_settings(fs: &Arc<dyn Fs>) -> Result<String> {
106    match fs.load(&paths::SETTINGS).await {
107        result @ Ok(_) => result,
108        Err(err) => {
109            if let Some(e) = err.downcast_ref::<std::io::Error>() {
110                if e.kind() == ErrorKind::NotFound {
111                    return Ok(crate::initial_user_settings_content(&Assets).to_string());
112                }
113            }
114            return Err(err);
115        }
116    }
117}
118
119pub fn update_settings_file<T: Setting>(
120    fs: Arc<dyn Fs>,
121    cx: &mut AppContext,
122    update: impl 'static + Send + FnOnce(&mut T::FileContent),
123) {
124    cx.spawn(|cx| async move {
125        let old_text = cx
126            .background()
127            .spawn({
128                let fs = fs.clone();
129                async move { load_settings(&fs).await }
130            })
131            .await?;
132
133        let new_text = cx.read(|cx| {
134            cx.global::<SettingsStore>()
135                .new_text_for_update::<T>(old_text, update)
136        });
137
138        cx.background()
139            .spawn(async move { fs.atomic_write(paths::SETTINGS.clone(), new_text).await })
140            .await?;
141        anyhow::Ok(())
142    })
143    .detach_and_log_err(cx);
144}