1use fs::Fs;
2use futures::StreamExt;
3use gpui::{executor, MutableAppContext};
4use postage::sink::Sink as _;
5use postage::{prelude::Stream, watch};
6use serde::Deserialize;
7
8use std::{path::Path, sync::Arc, time::Duration};
9use theme::ThemeRegistry;
10use util::ResultExt;
11
12use crate::{parse_json_with_comments, KeymapFileContent, Settings, SettingsFileContent};
13
14#[derive(Clone)]
15pub struct WatchedJsonFile<T>(pub watch::Receiver<T>);
16
17impl<T> WatchedJsonFile<T>
18where
19 T: 'static + for<'de> Deserialize<'de> + Clone + Default + Send + Sync,
20{
21 pub async fn new(
22 fs: Arc<dyn Fs>,
23 executor: &executor::Background,
24 path: impl Into<Arc<Path>>,
25 ) -> Self {
26 let path = path.into();
27 let settings = Self::load(fs.clone(), &path).await.unwrap_or_default();
28 let mut events = fs.watch(&path, Duration::from_millis(500)).await;
29 let (mut tx, rx) = watch::channel_with(settings);
30 executor
31 .spawn(async move {
32 while events.next().await.is_some() {
33 if let Some(settings) = Self::load(fs.clone(), &path).await {
34 if tx.send(settings).await.is_err() {
35 break;
36 }
37 }
38 }
39 })
40 .detach();
41 Self(rx)
42 }
43
44 ///Loads the given watched JSON file. In the special case that the file is
45 ///empty (ignoring whitespace) or is not a file, this will return T::default()
46 async fn load(fs: Arc<dyn Fs>, path: &Path) -> Option<T> {
47 if !fs.is_file(path).await {
48 return Some(T::default());
49 }
50
51 fs.load(path).await.log_err().and_then(|data| {
52 if data.trim().is_empty() {
53 Some(T::default())
54 } else {
55 parse_json_with_comments(&data).log_err()
56 }
57 })
58 }
59
60 pub fn current(&self) -> T {
61 self.0.borrow().clone()
62 }
63}
64
65pub fn watch_settings_file(
66 defaults: Settings,
67 mut file: WatchedJsonFile<SettingsFileContent>,
68 theme_registry: Arc<ThemeRegistry>,
69 cx: &mut MutableAppContext,
70) {
71 settings_updated(&defaults, file.0.borrow().clone(), &theme_registry, cx);
72 cx.spawn(|mut cx| async move {
73 while let Some(content) = file.0.recv().await {
74 cx.update(|cx| settings_updated(&defaults, content, &theme_registry, cx));
75 }
76 })
77 .detach();
78}
79
80pub fn keymap_updated(content: KeymapFileContent, cx: &mut MutableAppContext) {
81 cx.clear_bindings();
82 KeymapFileContent::load_defaults(cx);
83 content.add_to_cx(cx).log_err();
84}
85
86pub fn settings_updated(
87 defaults: &Settings,
88 content: SettingsFileContent,
89 theme_registry: &Arc<ThemeRegistry>,
90 cx: &mut MutableAppContext,
91) {
92 let mut settings = defaults.clone();
93 settings.set_user_settings(content, theme_registry, cx.font_cache());
94 cx.set_global(settings);
95 cx.refresh_windows();
96}
97
98pub fn watch_keymap_file(mut file: WatchedJsonFile<KeymapFileContent>, cx: &mut MutableAppContext) {
99 cx.spawn(|mut cx| async move {
100 while let Some(content) = file.0.recv().await {
101 cx.update(|cx| keymap_updated(content, cx));
102 }
103 })
104 .detach();
105}