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
27pub const EMPTY_THEME_NAME: &'static str = "empty-theme";
28
29#[cfg(any(test, feature = "test-support"))]
30pub fn test_settings() -> String {
31 let mut value = crate::settings_store::parse_json_with_comments::<serde_json::Value>(
32 default_settings().as_ref(),
33 )
34 .unwrap();
35 util::merge_non_null_json_value_into(
36 serde_json::json!({
37 "buffer_font_family": "Courier",
38 "buffer_font_features": {},
39 "buffer_font_size": 14,
40 "theme": EMPTY_THEME_NAME,
41 }),
42 &mut value,
43 );
44 value.as_object_mut().unwrap().remove("languages");
45 serde_json::to_string(&value).unwrap()
46}
47
48pub fn watch_config_file(
49 executor: Arc<Background>,
50 fs: Arc<dyn Fs>,
51 path: PathBuf,
52) -> mpsc::UnboundedReceiver<String> {
53 let (tx, rx) = mpsc::unbounded();
54 executor
55 .spawn(async move {
56 let events = fs.watch(&path, Duration::from_millis(100)).await;
57 futures::pin_mut!(events);
58 loop {
59 if let Ok(contents) = fs.load(&path).await {
60 if !tx.unbounded_send(contents).is_ok() {
61 break;
62 }
63 }
64 if events.next().await.is_none() {
65 break;
66 }
67 }
68 })
69 .detach();
70 rx
71}
72
73pub fn handle_settings_file_changes(
74 mut user_settings_file_rx: mpsc::UnboundedReceiver<String>,
75 cx: &mut AppContext,
76) {
77 let user_settings_content = cx.background().block(user_settings_file_rx.next()).unwrap();
78 cx.update_global::<SettingsStore, _, _>(|store, cx| {
79 store
80 .set_user_settings(&user_settings_content, cx)
81 .log_err();
82 });
83 cx.spawn(move |mut cx| async move {
84 while let Some(user_settings_content) = user_settings_file_rx.next().await {
85 cx.update(|cx| {
86 cx.update_global::<SettingsStore, _, _>(|store, cx| {
87 store
88 .set_user_settings(&user_settings_content, cx)
89 .log_err();
90 });
91 cx.refresh_windows();
92 });
93 }
94 })
95 .detach();
96}
97
98async fn load_settings(fs: &Arc<dyn Fs>) -> Result<String> {
99 match fs.load(&paths::SETTINGS).await {
100 result @ Ok(_) => result,
101 Err(err) => {
102 if let Some(e) = err.downcast_ref::<std::io::Error>() {
103 if e.kind() == ErrorKind::NotFound {
104 return Ok(crate::initial_user_settings_content(&Assets).to_string());
105 }
106 }
107 return Err(err);
108 }
109 }
110}
111
112pub fn update_settings_file<T: Setting>(
113 fs: Arc<dyn Fs>,
114 cx: &mut AppContext,
115 update: impl 'static + Send + FnOnce(&mut T::FileContent),
116) {
117 cx.spawn(|cx| async move {
118 let old_text = cx
119 .background()
120 .spawn({
121 let fs = fs.clone();
122 async move { load_settings(&fs).await }
123 })
124 .await?;
125
126 let new_text = cx.read(|cx| {
127 cx.global::<SettingsStore>()
128 .new_text_for_update::<T>(old_text, update)
129 });
130
131 cx.background()
132 .spawn(async move { fs.atomic_write(paths::SETTINGS.clone(), new_text).await })
133 .await?;
134 anyhow::Ok(())
135 })
136 .detach_and_log_err(cx);
137}