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                        break;
 73                    }
 74            }
 75        })
 76        .detach();
 77    rx
 78}
 79
 80pub fn watch_config_dir(
 81    executor: &BackgroundExecutor,
 82    fs: Arc<dyn Fs>,
 83    dir_path: PathBuf,
 84    config_paths: HashSet<PathBuf>,
 85) -> mpsc::UnboundedReceiver<String> {
 86    let (tx, rx) = mpsc::unbounded();
 87    executor
 88        .spawn(async move {
 89            for file_path in &config_paths {
 90                if fs.metadata(file_path).await.is_ok_and(|v| v.is_some())
 91                    && let Ok(contents) = fs.load(file_path).await
 92                        && tx.unbounded_send(contents).is_err() {
 93                            return;
 94                        }
 95            }
 96
 97            let (events, _) = fs.watch(&dir_path, Duration::from_millis(100)).await;
 98            futures::pin_mut!(events);
 99
100            while let Some(event_batch) = events.next().await {
101                for event in event_batch {
102                    if config_paths.contains(&event.path) {
103                        match event.kind {
104                            Some(PathEventKind::Removed) => {
105                                if tx.unbounded_send(String::new()).is_err() {
106                                    return;
107                                }
108                            }
109                            Some(PathEventKind::Created) | Some(PathEventKind::Changed) => {
110                                if let Ok(contents) = fs.load(&event.path).await
111                                    && tx.unbounded_send(contents).is_err() {
112                                        return;
113                                    }
114                            }
115                            _ => {}
116                        }
117                    }
118                }
119            }
120        })
121        .detach();
122
123    rx
124}
125
126pub fn update_settings_file<T: Settings>(
127    fs: Arc<dyn Fs>,
128    cx: &App,
129    update: impl 'static + Send + FnOnce(&mut T::FileContent, &App),
130) {
131    SettingsStore::global(cx).update_settings_file::<T>(fs, update);
132}