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}