1use fs::Fs;
2use futures::StreamExt;
3use gpui::{executor, AppContext};
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_files(
66 defaults: Settings,
67 settings_file: WatchedJsonFile<SettingsFileContent>,
68 theme_registry: Arc<ThemeRegistry>,
69 keymap_file: WatchedJsonFile<KeymapFileContent>,
70 cx: &mut AppContext,
71) {
72 watch_settings_file(defaults, settings_file, theme_registry, cx);
73 watch_keymap_file(keymap_file, cx);
74}
75
76pub(crate) fn watch_settings_file(
77 defaults: Settings,
78 mut file: WatchedJsonFile<SettingsFileContent>,
79 theme_registry: Arc<ThemeRegistry>,
80 cx: &mut AppContext,
81) {
82 settings_updated(&defaults, file.0.borrow().clone(), &theme_registry, cx);
83 cx.spawn(|mut cx| async move {
84 while let Some(content) = file.0.recv().await {
85 cx.update(|cx| settings_updated(&defaults, content, &theme_registry, cx));
86 }
87 })
88 .detach();
89}
90
91fn keymap_updated(content: KeymapFileContent, cx: &mut AppContext) {
92 cx.clear_bindings();
93 KeymapFileContent::load_defaults(cx);
94 content.add_to_cx(cx).log_err();
95}
96
97fn settings_updated(
98 defaults: &Settings,
99 content: SettingsFileContent,
100 theme_registry: &Arc<ThemeRegistry>,
101 cx: &mut AppContext,
102) {
103 let mut settings = defaults.clone();
104 settings.set_user_settings(content, theme_registry, cx.font_cache());
105 cx.set_global(settings);
106 cx.refresh_windows();
107}
108
109fn watch_keymap_file(mut file: WatchedJsonFile<KeymapFileContent>, cx: &mut AppContext) {
110 cx.spawn(|mut cx| async move {
111 let mut settings_subscription = None;
112 while let Some(content) = file.0.recv().await {
113 cx.update(|cx| {
114 let old_base_keymap = cx.global::<Settings>().base_keymap;
115 keymap_updated(content.clone(), cx);
116 settings_subscription = Some(cx.observe_global::<Settings, _>(move |cx| {
117 let settings = cx.global::<Settings>();
118 if settings.base_keymap != old_base_keymap {
119 keymap_updated(content.clone(), cx);
120 }
121 }));
122 });
123 }
124 })
125 .detach();
126}