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::{
  8    borrow::Cow,
  9    io::ErrorKind,
 10    path::{Path, PathBuf},
 11    str,
 12    sync::Arc,
 13    time::Duration,
 14};
 15use util::{paths, ResultExt};
 16
 17pub fn register_setting<T: Setting>(cx: &mut AppContext) {
 18    cx.update_global::<SettingsStore, _, _>(|store, cx| {
 19        store.register_setting::<T>(cx);
 20    });
 21}
 22
 23pub fn get_setting<'a, T: Setting>(path: Option<&Path>, cx: &'a AppContext) -> &'a T {
 24    cx.global::<SettingsStore>().get(path)
 25}
 26
 27pub fn default_settings() -> Cow<'static, str> {
 28    match Assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap() {
 29        Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
 30        Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
 31    }
 32}
 33
 34#[cfg(any(test, feature = "test-support"))]
 35pub const EMPTY_THEME_NAME: &'static str = "empty-theme";
 36
 37#[cfg(any(test, feature = "test-support"))]
 38pub fn test_settings() -> String {
 39    let mut value = crate::settings_store::parse_json_with_comments::<serde_json::Value>(
 40        default_settings().as_ref(),
 41    )
 42    .unwrap();
 43    util::merge_non_null_json_value_into(
 44        serde_json::json!({
 45            "buffer_font_family": "Courier",
 46            "buffer_font_features": {},
 47            "buffer_font_size": 14,
 48            "theme": EMPTY_THEME_NAME,
 49        }),
 50        &mut value,
 51    );
 52    value.as_object_mut().unwrap().remove("languages");
 53    serde_json::to_string(&value).unwrap()
 54}
 55
 56pub fn watch_config_file(
 57    executor: Arc<Background>,
 58    fs: Arc<dyn Fs>,
 59    path: PathBuf,
 60) -> mpsc::UnboundedReceiver<String> {
 61    let (tx, rx) = mpsc::unbounded();
 62    executor
 63        .spawn(async move {
 64            let events = fs.watch(&path, Duration::from_millis(100)).await;
 65            futures::pin_mut!(events);
 66            loop {
 67                if let Ok(contents) = fs.load(&path).await {
 68                    if !tx.unbounded_send(contents).is_ok() {
 69                        break;
 70                    }
 71                }
 72                if events.next().await.is_none() {
 73                    break;
 74                }
 75            }
 76        })
 77        .detach();
 78    rx
 79}
 80
 81pub fn handle_settings_file_changes(
 82    mut user_settings_file_rx: mpsc::UnboundedReceiver<String>,
 83    cx: &mut AppContext,
 84) {
 85    let user_settings_content = cx.background().block(user_settings_file_rx.next()).unwrap();
 86    cx.update_global::<SettingsStore, _, _>(|store, cx| {
 87        store
 88            .set_user_settings(&user_settings_content, cx)
 89            .log_err();
 90    });
 91    cx.spawn(move |mut cx| async move {
 92        while let Some(user_settings_content) = user_settings_file_rx.next().await {
 93            cx.update(|cx| {
 94                cx.update_global::<SettingsStore, _, _>(|store, cx| {
 95                    store
 96                        .set_user_settings(&user_settings_content, cx)
 97                        .log_err();
 98                });
 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}