settings_file.rs

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