diff --git a/crates/file_icons/src/file_icons.rs b/crates/file_icons/src/file_icons.rs index 01fed04243..88b2003635 100644 --- a/crates/file_icons/src/file_icons.rs +++ b/crates/file_icons/src/file_icons.rs @@ -7,7 +7,7 @@ use gpui::{App, AssetSource, Global, SharedString}; use serde_derive::Deserialize; use settings::Settings; use theme::{IconTheme, ThemeRegistry, ThemeSettings}; -use util::{maybe, paths::PathExt}; +use util::paths::PathExt; #[derive(Deserialize, Debug)] pub struct FileIcons { @@ -43,20 +43,45 @@ impl FileIcons { pub fn get_icon(path: &Path, cx: &App) -> Option { let this = cx.try_global::()?; + let get_icon_from_suffix = |suffix: &str| -> Option { + this.stems + .get(suffix) + .or_else(|| this.suffixes.get(suffix)) + .and_then(|typ| this.get_icon_for_type(typ, cx)) + }; // TODO: Associate a type with the languages and have the file's language // override these associations - maybe!({ - let suffix = path.icon_stem_or_suffix()?; - if let Some(type_str) = this.stems.get(suffix) { - return this.get_icon_for_type(type_str, cx); + // check if file name is in suffixes + // e.g. catch file named `eslint.config.js` instead of `.eslint.config.js` + if let Some(typ) = path.to_str().and_then(|typ| this.suffixes.get(typ)) { + let maybe_path = get_icon_from_suffix(typ); + if maybe_path.is_some() { + return maybe_path; } + } - this.suffixes - .get(suffix) - .and_then(|type_str| this.get_icon_for_type(type_str, cx)) - }) - .or_else(|| this.get_icon_for_type("default", cx)) + // 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() { + let maybe_path = get_icon_from_suffix(suffix); + if maybe_path.is_some() { + return maybe_path; + } + } + + // this _should_ only happen when the file is hidden (has leading '.') + // and is not a "special" file we have an icon (e.g. not `.eslint.config.js`) + // that should be caught above. In the remaining cases, we want to check + // for a normal supported extension e.g. `.data.json` -> `json` + let extension = path.extension().and_then(|ext| ext.to_str()); + if let Some(extension) = extension { + let maybe_path = get_icon_from_suffix(extension); + if maybe_path.is_some() { + return maybe_path; + } + } + return this.get_icon_for_type("default", cx); } fn default_icon_theme(cx: &App) -> Option> { diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index f0d314f87d..9d80b2609c 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -31,7 +31,7 @@ use sum_tree::Bias; use text::{Point, Rope}; use theme::Theme; use unicase::UniCase; -use util::{maybe, paths::PathExt, post_inc, ResultExt}; +use util::{maybe, post_inc, ResultExt}; #[derive( Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema, @@ -659,7 +659,7 @@ impl LanguageRegistry { user_file_types: Option<&HashMap, GlobSet>>, ) -> Option { let filename = path.file_name().and_then(|name| name.to_str()); - let extension = path.extension_or_hidden_file_name(); + let extension = path.extension().and_then(|ext| ext.to_str()); let path_suffixes = [extension, filename, path.to_str()]; let empty = GlobSet::empty(); diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index 275895d228..b3d0c28bbb 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -21,7 +21,6 @@ pub fn home_dir() -> &'static PathBuf { pub trait PathExt { fn compact(&self) -> PathBuf; - fn icon_stem_or_suffix(&self) -> Option<&str>; fn extension_or_hidden_file_name(&self) -> Option<&str>; fn to_sanitized_string(&self) -> String; fn try_from_bytes<'a>(bytes: &'a [u8]) -> anyhow::Result @@ -74,8 +73,8 @@ impl> PathExt for T { } } - /// 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> { + /// Returns a file's extension or, if the file is hidden, its name without the leading dot + fn extension_or_hidden_file_name(&self) -> Option<&str> { let path = self.as_ref(); let file_name = path.file_name()?.to_str()?; if file_name.starts_with('.') { @@ -87,15 +86,6 @@ impl> PathExt for T { .or_else(|| path.file_stem()?.to_str()) } - /// Returns a file's extension or, if the file is hidden, its name without the leading dot - fn extension_or_hidden_file_name(&self) -> Option<&str> { - if let Some(extension) = self.as_ref().extension() { - return extension.to_str(); - } - - self.as_ref().file_name()?.to_str()?.split('.').last() - } - /// Returns a sanitized string representation of the path. /// Note, on Windows, this assumes that the path is a valid UTF-8 string and /// is not a UNC path. @@ -811,33 +801,6 @@ mod tests { } } - #[test] - fn test_icon_stem_or_suffix() { - // No dots in name - let path = Path::new("/a/b/c/file_name.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_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_stem_or_suffix(), Some("rs")); - - // Hidden file, no extension - let path = Path::new("/a/b/c/.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_stem_or_suffix(), Some("eslintrc.js")); - } - #[test] fn test_extension_or_hidden_file_name() { // No dots in name @@ -858,7 +821,7 @@ mod tests { // Hidden file, with extension let path = Path::new("/a/b/c/.eslintrc.js"); - assert_eq!(path.extension_or_hidden_file_name(), Some("js")); + assert_eq!(path.extension_or_hidden_file_name(), Some("eslintrc.js")); } #[test]