settings_file.rs

  1use crate::{Settings, settings_store::SettingsStore};
  2use collections::HashSet;
  3use fs::{Fs, PathEventKind};
  4use futures::{StreamExt, channel::mpsc};
  5use gpui::{App, BackgroundExecutor, ReadGlobal};
  6use std::{path::PathBuf, sync::Arc, time::Duration};
  7
  8pub const EMPTY_THEME_NAME: &str = "empty-theme";
  9
 10#[cfg(any(test, feature = "test-support"))]
 11pub fn test_settings() -> String {
 12    let mut value =
 13        crate::parse_json_with_comments::<serde_json::Value>(crate::default_settings().as_ref())
 14            .unwrap();
 15    #[cfg(not(target_os = "windows"))]
 16    util::merge_non_null_json_value_into(
 17        serde_json::json!({
 18            "ui_font_family": "Courier",
 19            "ui_font_features": {},
 20            "ui_font_size": 14,
 21            "ui_font_fallback": [],
 22            "buffer_font_family": "Courier",
 23            "buffer_font_features": {},
 24            "buffer_font_size": 14,
 25            "buffer_font_fallback": [],
 26            "theme": EMPTY_THEME_NAME,
 27        }),
 28        &mut value,
 29    );
 30    #[cfg(target_os = "windows")]
 31    util::merge_non_null_json_value_into(
 32        serde_json::json!({
 33            "ui_font_family": "Courier New",
 34            "ui_font_features": {},
 35            "ui_font_size": 14,
 36            "ui_font_fallback": [],
 37            "buffer_font_family": "Courier New",
 38            "buffer_font_features": {},
 39            "buffer_font_size": 14,
 40            "buffer_font_fallback": [],
 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: &BackgroundExecutor,
 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
 60            let contents = fs.load(&path).await.unwrap_or_default();
 61            if tx.unbounded_send(contents).is_err() {
 62                return;
 63            }
 64
 65            loop {
 66                if events.next().await.is_none() {
 67                    break;
 68                }
 69
 70                if let Ok(contents) = fs.load(&path).await
 71                    && tx.unbounded_send(contents).is_err()
 72                {
 73                    break;
 74                }
 75            }
 76        })
 77        .detach();
 78    rx
 79}
 80
 81pub fn watch_config_dir(
 82    executor: &BackgroundExecutor,
 83    fs: Arc<dyn Fs>,
 84    dir_path: PathBuf,
 85    config_paths: HashSet<PathBuf>,
 86) -> mpsc::UnboundedReceiver<String> {
 87    let (tx, rx) = mpsc::unbounded();
 88    executor
 89        .spawn(async move {
 90            for file_path in &config_paths {
 91                if fs.metadata(file_path).await.is_ok_and(|v| v.is_some())
 92                    && let Ok(contents) = fs.load(file_path).await
 93                    && tx.unbounded_send(contents).is_err()
 94                {
 95                    return;
 96                }
 97            }
 98
 99            let (events, _) = fs.watch(&dir_path, Duration::from_millis(100)).await;
100            futures::pin_mut!(events);
101
102            while let Some(event_batch) = events.next().await {
103                for event in event_batch {
104                    if config_paths.contains(&event.path) {
105                        match event.kind {
106                            Some(PathEventKind::Removed) => {
107                                if tx.unbounded_send(String::new()).is_err() {
108                                    return;
109                                }
110                            }
111                            Some(PathEventKind::Created) | Some(PathEventKind::Changed) => {
112                                if let Ok(contents) = fs.load(&event.path).await
113                                    && tx.unbounded_send(contents).is_err()
114                                {
115                                    return;
116                                }
117                            }
118                            _ => {}
119                        }
120                    }
121                }
122            }
123        })
124        .detach();
125
126    rx
127}
128
129pub fn update_settings_file<T: Settings>(
130    fs: Arc<dyn Fs>,
131    cx: &App,
132    update: impl 'static + Send + FnOnce(&mut T::FileContent, &App),
133) {
134    SettingsStore::global(cx).update_settings_file::<T>(fs, update);
135}