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