Make file types live reload

Mikayla Maki created

Change summary

assets/icons/file_icons/file_types.json       |  3 +
crates/project_panel/src/file_associations.rs | 58 +++++++++++---------
crates/project_panel/src/project_panel.rs     | 15 +++--
crates/zed/src/main.rs                        | 25 +++++++++
4 files changed, 68 insertions(+), 33 deletions(-)

Detailed changes

assets/icons/file_icons/file_types.json 🔗

@@ -67,6 +67,9 @@
     "log": "log"
   },
   "types": {
+    "default": {
+      "icon": "icons/file_icons/quill/file.svg"
+    },
     "directory": {
       "icon": "icons/file_icons/quill/folder.svg"
     },

crates/project_panel/src/file_associations.rs 🔗

@@ -4,6 +4,7 @@ use collections::HashMap;
 
 use gpui::{AppContext, AssetSource};
 use serde_derive::Deserialize;
+use util::iife;
 
 #[derive(Deserialize, Debug)]
 struct TypeConfig {
@@ -16,9 +17,9 @@ pub struct FileAssociations {
     types: HashMap<String, TypeConfig>,
 }
 
-pub const TEXT_FILE_ASSET: &'static str = "icons/file_icons/quill/file.svg";
 const DIRECTORY_TYPE: &'static str = "directory";
 const EXPANDED_DIRECTORY_TYPE: &'static str = "expanded_directory";
+pub const FILE_TYPES_ASSET: &'static str = "icons/file_icons/file_types.json";
 
 pub fn init(assets: impl AssetSource, cx: &mut AppContext) {
     cx.set_global(FileAssociations::new(assets))
@@ -28,8 +29,9 @@ impl FileAssociations {
     pub fn new(assets: impl AssetSource) -> Self {
         assets
             .load("icons/file_icons/file_types.json")
-            .map(|file| {
-                serde_json::from_str::<FileAssociations>(str::from_utf8(&file).unwrap()).unwrap()
+            .and_then(|file| {
+                serde_json::from_str::<FileAssociations>(str::from_utf8(&file).unwrap())
+                    .map_err(Into::into)
             })
             .unwrap_or_else(|_| FileAssociations {
                 suffixes: HashMap::default(),
@@ -37,35 +39,37 @@ impl FileAssociations {
             })
     }
 
-    pub fn get_icon(path: &Path, cx: &AppContext) -> Option<Arc<str>> {
-        if !cx.has_global::<Self>() {
-            return None;
-        }
+    pub fn get_icon(path: &Path, cx: &AppContext) -> Arc<str> {
+        iife!({
+            let this = cx.has_global::<Self>().then(|| cx.global::<Self>())?;
 
-        let this = cx.global::<Self>();
-        let suffix = path.extension()?.to_str()?;
+            iife!({
+                let suffix = path.extension()?.to_str()?;
 
-        this.suffixes
-            .get(suffix)
-            .and_then(|type_str| this.types.get(type_str))
-            .map(|type_config| type_config.icon.clone())
+                this.suffixes
+                    .get(suffix)
+                    .and_then(|type_str| this.types.get(type_str))
+                    .map(|type_config| type_config.icon.clone())
+            })
+            .or_else(|| this.types.get("default").map(|config| config.icon.clone()))
+        })
+        .unwrap_or_else(|| Arc::from("".to_string()))
     }
 
-    pub fn get_folder_icon(expanded: bool, cx: &AppContext) -> Option<Arc<str>> {
-        if !cx.has_global::<Self>() {
-            return None;
-        }
-
-        let this = cx.global::<Self>();
+    pub fn get_folder_icon(expanded: bool, cx: &AppContext) -> Arc<str> {
+        iife!({
+            let this = cx.has_global::<Self>().then(|| cx.global::<Self>())?;
 
-        let key = if expanded {
-            EXPANDED_DIRECTORY_TYPE
-        } else {
-            DIRECTORY_TYPE
-        };
+            let key = if expanded {
+                EXPANDED_DIRECTORY_TYPE
+            } else {
+                DIRECTORY_TYPE
+            };
 
-        this.types
-            .get(key)
-            .map(|type_config| type_config.icon.clone())
+            this.types
+                .get(key)
+                .map(|type_config| type_config.icon.clone())
+        })
+        .unwrap_or_else(|| Arc::from("".to_string()))
     }
 }

crates/project_panel/src/project_panel.rs 🔗

@@ -1,11 +1,12 @@
-mod file_associations;
+pub mod file_associations;
 mod project_panel_settings;
 
 use context_menu::{ContextMenu, ContextMenuItem};
 use db::kvp::KEY_VALUE_STORE;
 use drag_and_drop::{DragAndDrop, Draggable};
 use editor::{Cancel, Editor};
-use file_associations::{FileAssociations, TEXT_FILE_ASSET};
+use file_associations::FileAssociations;
+
 use futures::stream::StreamExt;
 use gpui::{
     actions,
@@ -234,6 +235,10 @@ impl ProjectPanel {
             })
             .detach();
 
+            cx.observe_global::<FileAssociations, _>(|_, cx| {
+                cx.notify();
+            }).detach();
+
             let view_id = cx.view_id();
             let mut this = Self {
                 project: project.clone(),
@@ -1189,11 +1194,9 @@ impl ProjectPanel {
                     let is_expanded = expanded_entry_ids.binary_search(&entry.id).is_ok();
                     let icon = show_file_icons
                         .then(|| match entry.kind {
-                            EntryKind::File(_) => FileAssociations::get_icon(&entry.path, cx)
-                                .or_else(|| Some(TEXT_FILE_ASSET.into())),
+                            EntryKind::File(_) => FileAssociations::get_icon(&entry.path, cx),
                             _ => FileAssociations::get_folder_icon(is_expanded, cx),
-                        })
-                        .flatten();
+                        });
 
                     let mut details = EntryDetails {
                         filename: entry

crates/zed/src/main.rs 🔗

@@ -166,6 +166,7 @@ fn main() {
         cx.spawn(|cx| watch_themes(fs.clone(), cx)).detach();
         cx.spawn(|_| watch_languages(fs.clone(), languages.clone()))
             .detach();
+        watch_file_types(fs.clone(), cx);
 
         languages.set_theme(theme::current(cx).clone());
         cx.observe_global::<SettingsStore, _>({
@@ -685,6 +686,25 @@ async fn watch_languages(fs: Arc<dyn Fs>, languages: Arc<LanguageRegistry>) -> O
     Some(())
 }
 
+#[cfg(debug_assertions)]
+fn watch_file_types(fs: Arc<dyn Fs>, cx: &mut AppContext) {
+    cx.spawn(|mut cx| async move {
+        let mut events = fs
+            .watch(
+                "assets/icons/file_icons/file_types.json".as_ref(),
+                Duration::from_millis(100),
+            )
+            .await;
+        while (events.next().await).is_some() {
+            cx.update(|cx| {
+                cx.update_global(|file_types, _| {
+                    *file_types = project_panel::file_associations::FileAssociations::new(Assets);
+                });
+            })
+        }
+    }).detach()
+}
+
 #[cfg(not(debug_assertions))]
 async fn watch_themes(_fs: Arc<dyn Fs>, _cx: AsyncAppContext) -> Option<()> {
     None
@@ -695,6 +715,11 @@ async fn watch_languages(_: Arc<dyn Fs>, _: Arc<LanguageRegistry>) -> Option<()>
     None
 }
 
+#[cfg(not(debug_assertions))]
+fn watch_file_types(fs: Arc<dyn Fs>, cx: &mut AppContext) {
+    None
+}
+
 fn connect_to_cli(
     server_name: &str,
 ) -> Result<(mpsc::Receiver<CliRequest>, IpcSender<CliResponse>)> {