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                    if tx.unbounded_send(contents).is_err() {
 72                        break;
 73                    }
 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                    if let Ok(contents) = fs.load(file_path).await {
 93                        if tx.unbounded_send(contents).is_err() {
 94                            return;
 95                        }
 96                    }
 97                }
 98            }
 99
100            let (events, _) = fs.watch(&dir_path, Duration::from_millis(100)).await;
101            futures::pin_mut!(events);
102
103            while let Some(event_batch) = events.next().await {
104                for event in event_batch {
105                    if config_paths.contains(&event.path) {
106                        match event.kind {
107                            Some(PathEventKind::Removed) => {
108                                if tx.unbounded_send(String::new()).is_err() {
109                                    return;
110                                }
111                            }
112                            Some(PathEventKind::Created) | Some(PathEventKind::Changed) => {
113                                if let Ok(contents) = fs.load(&event.path).await {
114                                    if tx.unbounded_send(contents).is_err() {
115                                        return;
116                                    }
117                                }
118                            }
119                            _ => {}
120                        }
121                    }
122                }
123            }
124        })
125        .detach();
126
127    rx
128}
129
130pub fn update_settings_file<T: Settings>(
131    fs: Arc<dyn Fs>,
132    cx: &App,
133    update: impl 'static + Send + FnOnce(&mut T::FileContent, &App),
134) {
135    SettingsStore::global(cx).update_settings_file::<T>(fs, update);
136}