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]