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            loop {
 59                if let Ok(contents) = fs.load(&path).await {
 60                    if !tx.unbounded_send(contents).is_ok() {
 61                        break;
 62                    }
 63                }
 64                if events.next().await.is_none() {
 65                    break;
 66                }
 67            }
 68        })
 69        .detach();
 70    rx
 71}
 72
 73pub fn handle_settings_file_changes(
 74    mut user_settings_file_rx: mpsc::UnboundedReceiver<String>,
 75    cx: &mut AppContext,
 76) {
 77    let user_settings_content = cx.background().block(user_settings_file_rx.next()).unwrap();
 78    cx.update_global::<SettingsStore, _, _>(|store, cx| {
 79        store
 80            .set_user_settings(&user_settings_content, cx)
 81            .log_err();
 82    });
 83    cx.spawn(move |mut cx| async move {
 84        while let Some(user_settings_content) = user_settings_file_rx.next().await {
 85            cx.update(|cx| {
 86                cx.update_global::<SettingsStore, _, _>(|store, cx| {
 87                    store
 88                        .set_user_settings(&user_settings_content, cx)
 89                        .log_err();
 90                });
 91                cx.refresh_windows();
 92            });
 93        }
 94    })
 95    .detach();
 96}
 97
 98async fn load_settings(fs: &Arc<dyn Fs>) -> Result<String> {
 99    match fs.load(&paths::SETTINGS).await {
100        result @ Ok(_) => result,
101        Err(err) => {
102            if let Some(e) = err.downcast_ref::<std::io::Error>() {
103                if e.kind() == ErrorKind::NotFound {
104                    return Ok(crate::initial_user_settings_content(&Assets).to_string());
105                }
106            }
107            return Err(err);
108        }
109    }
110}
111
112pub fn update_settings_file<T: Setting>(
113    fs: Arc<dyn Fs>,
114    cx: &mut AppContext,
115    update: impl 'static + Send + FnOnce(&mut T::FileContent),
116) {
117    cx.spawn(|cx| async move {
118        let old_text = cx
119            .background()
120            .spawn({
121                let fs = fs.clone();
122                async move { load_settings(&fs).await }
123            })
124            .await?;
125
126        let new_text = cx.read(|cx| {
127            cx.global::<SettingsStore>()
128                .new_text_for_update::<T>(old_text, update)
129        });
130
131        cx.background()
132            .spawn(async move { fs.atomic_write(paths::SETTINGS.clone(), new_text).await })
133            .await?;
134        anyhow::Ok(())
135    })
136    .detach_and_log_err(cx);
137}