diff --git a/assets/icons/file_icons/docker.svg b/assets/icons/file_icons/docker.svg new file mode 100644 index 0000000000000000000000000000000000000000..9ca7a1e04ed8e59c96b277984f3d0cdf8d68fcf5 --- /dev/null +++ b/assets/icons/file_icons/docker.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/file_icons/file_types.json b/assets/icons/file_icons/file_types.json index 75719fb4966091764018837c2877a74ad9a0e56d..dea3c4ef83da0fdd4289ee58bd11642375c16270 100644 --- a/assets/icons/file_icons/file_types.json +++ b/assets/icons/file_icons/file_types.json @@ -1,4 +1,9 @@ { + "stems": { + "Podfile": "ruby", + "Procfile": "heroku", + "Dockerfile": "docker" + }, "suffixes": { "astro": "astro", "Emakefile": "erlang", @@ -86,6 +91,7 @@ "mdb": "storage", "mdf": "storage", "mdx": "document", + "metadata": "code", "mkv": "video", "mjs": "code", "mka": "audio", @@ -189,6 +195,9 @@ "default": { "icon": "icons/file_icons/file.svg" }, + "docker": { + "icon": "icons/file_icons/docker.svg" + }, "document": { "icon": "icons/file_icons/book.svg" }, @@ -216,6 +225,9 @@ "haskell": { "icon": "icons/file_icons/haskell.svg" }, + "heroku": { + "icon": "icons/file_icons/heroku.svg" + }, "go": { "icon": "icons/file_icons/go.svg" }, @@ -228,7 +240,7 @@ "java": { "icon": "icons/file_icons/java.svg" }, - "kotlin":{ + "kotlin": { "icon": "icons/file_icons/kotlin.svg" }, "lock": { diff --git a/assets/icons/file_icons/heroku.svg b/assets/icons/file_icons/heroku.svg new file mode 100644 index 0000000000000000000000000000000000000000..86f8a197a83b1205127fcb8c0054865b2acb0417 --- /dev/null +++ b/assets/icons/file_icons/heroku.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/crates/project_panel/src/file_associations.rs b/crates/project_panel/src/file_associations.rs index 5f596440ac9f8b09f96a804493f34a38e7fa73f1..d27ab05b662ac8fd4f718cc1d94cc7cb29b9e65a 100644 --- a/crates/project_panel/src/file_associations.rs +++ b/crates/project_panel/src/file_associations.rs @@ -13,6 +13,7 @@ struct TypeConfig { #[derive(Deserialize, Debug)] pub struct FileAssociations { + stems: HashMap, suffixes: HashMap, types: HashMap, } @@ -38,6 +39,7 @@ impl FileAssociations { .map_err(Into::into) }) .unwrap_or_else(|_| FileAssociations { + stems: HashMap::default(), suffixes: HashMap::default(), types: HashMap::default(), }) @@ -49,7 +51,14 @@ impl FileAssociations { // FIXME: Associate a type with the languages and have the file's language // override these associations maybe!({ - let suffix = path.icon_suffix()?; + let suffix = path.icon_stem_or_suffix()?; + + if let Some(type_str) = this.stems.get(suffix) { + return this + .types + .get(type_str) + .map(|type_config| type_config.icon.clone()); + } this.suffixes .get(suffix) diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index acd93c5cec1f7f42e3495c58f877f9f2a7b25512..9a1bc73c9d3cd76ea89852fa322ee1449891d73a 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -48,7 +48,7 @@ lazy_static::lazy_static! { pub trait PathExt { fn compact(&self) -> PathBuf; - fn icon_suffix(&self) -> Option<&str>; + fn icon_stem_or_suffix(&self) -> Option<&str>; fn extension_or_hidden_file_name(&self) -> Option<&str>; fn try_from_bytes<'a>(bytes: &'a [u8]) -> anyhow::Result where @@ -100,17 +100,17 @@ impl> PathExt for T { } } - /// Returns a suffix of the path that is used to determine which file icon to use - fn icon_suffix(&self) -> Option<&str> { - let file_name = self.as_ref().file_name()?.to_str()?; - + /// Returns either the suffix if available, or the file stem otherwise to determine which file icon to use + fn icon_stem_or_suffix(&self) -> Option<&str> { + let path = self.as_ref(); + let file_name = path.file_name()?.to_str()?; if file_name.starts_with('.') { return file_name.strip_prefix('.'); } - self.as_ref() - .extension() - .and_then(|extension| extension.to_str()) + path.extension() + .and_then(|e| e.to_str()) + .or_else(|| path.file_stem()?.to_str()) } /// Returns a file's extension or, if the file is hidden, its name without the leading dot @@ -403,26 +403,30 @@ mod tests { } #[test] - fn test_icon_suffix() { + fn test_icon_stem_or_suffix() { // No dots in name let path = Path::new("/a/b/c/file_name.rs"); - assert_eq!(path.icon_suffix(), Some("rs")); + assert_eq!(path.icon_stem_or_suffix(), Some("rs")); // Single dot in name let path = Path::new("/a/b/c/file.name.rs"); - assert_eq!(path.icon_suffix(), Some("rs")); + assert_eq!(path.icon_stem_or_suffix(), Some("rs")); + + // No suffix + let path = Path::new("/a/b/c/file"); + assert_eq!(path.icon_stem_or_suffix(), Some("file")); // Multiple dots in name let path = Path::new("/a/b/c/long.file.name.rs"); - assert_eq!(path.icon_suffix(), Some("rs")); + assert_eq!(path.icon_stem_or_suffix(), Some("rs")); // Hidden file, no extension let path = Path::new("/a/b/c/.gitignore"); - assert_eq!(path.icon_suffix(), Some("gitignore")); + assert_eq!(path.icon_stem_or_suffix(), Some("gitignore")); // Hidden file, with extension let path = Path::new("/a/b/c/.eslintrc.js"); - assert_eq!(path.icon_suffix(), Some("eslintrc.js")); + assert_eq!(path.icon_stem_or_suffix(), Some("eslintrc.js")); } #[test]