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