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