file_icons: Add support for multiple file extensions (#36342)

Currently most icon theme extensions already support file types like
stories.tsx and stories.svelte. However within Zed itself these file
type overrides are not supported yet. This change adds support for those

Release Notes:

- Added support for icons on file extensions such as stories.tsx and
stories.svelte
This commit is contained in:
Jacob
2025-10-03 11:41:59 +02:00
committed by GitHub
parent 1fffcb99ba
commit 39bd03b92d
2 changed files with 51 additions and 0 deletions
+9
View File
@@ -52,6 +52,15 @@ impl FileIcons {
}
}
// handle cases where the file extension is made up of multiple important
// parts (e.g Component.stories.tsx) that refer to an alternative icon style
if let Some(suffix) = path.multiple_extensions() {
let maybe_path = get_icon_from_suffix(suffix.as_str());
if maybe_path.is_some() {
return maybe_path;
}
}
// primary case: check if the files extension or the hidden file name
// matches some icon path
if let Some(suffix) = path.extension_or_hidden_file_name() {
+42
View File
@@ -1,4 +1,5 @@
use globset::{Glob, GlobSet, GlobSetBuilder};
use itertools::Itertools;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
@@ -60,6 +61,7 @@ pub trait PathExt {
}
}
fn local_to_wsl(&self) -> Option<PathBuf>;
fn multiple_extensions(&self) -> Option<String>;
}
impl<T: AsRef<Path>> PathExt for T {
@@ -130,6 +132,27 @@ impl<T: AsRef<Path>> PathExt for T {
Some(new_path.into())
}
/// Returns a file's "full" joined collection of extensions, in the case where a file does not
/// just have a singular extension but instead has multiple (e.g File.tar.gz, Component.stories.tsx)
///
/// Will provide back the extensions joined together such as tar.gz or stories.tsx
fn multiple_extensions(&self) -> Option<String> {
let path = self.as_ref();
let file_name = path.file_name()?.to_str()?;
let parts: Vec<&str> = file_name
.split('.')
// Skip the part with the file name extension
.skip(1)
.collect();
if parts.len() < 2 {
return None;
}
Some(parts.into_iter().join("."))
}
}
/// In memory, this is identical to `Path`. On non-Windows conversions to this type are no-ops. On
@@ -1727,4 +1750,23 @@ mod tests {
assert_eq!(natural_sort("file-1.2", "file-1.10"), Ordering::Less);
assert_eq!(natural_sort("file-1.10", "file-1.2"), Ordering::Greater);
}
#[test]
fn test_multiple_extensions() {
// No extensions
let path = Path::new("/a/b/c/file_name");
assert_eq!(path.multiple_extensions(), None);
// Only one extension
let path = Path::new("/a/b/c/file_name.tsx");
assert_eq!(path.multiple_extensions(), None);
// Stories sample extension
let path = Path::new("/a/b/c/file_name.stories.tsx");
assert_eq!(path.multiple_extensions(), Some("stories.tsx".to_string()));
// Longer sample extension
let path = Path::new("/a/b/c/long.app.tar.gz");
assert_eq!(path.multiple_extensions(), Some("app.tar.gz".to_string()));
}
}