diff --git a/CHANGELOG.md b/CHANGELOG.md index 922c2a9dc..1f2e9e180 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [`LS_COLORS`](README.md#Colors) can be used to customize. - Handle dereference (-L) with broken symlink from [r3dArch](https://github.com/r3dArch) - Avoid using Clap's deprecated structs and functions [sudame](https://github.com/sudame) +- Icon theme with overrides from config [sudame](https://github.com/sudame) ## [0.23.1] - 2022-09-13 diff --git a/src/theme/icon.rs b/src/theme/icon.rs index 1c65b2127..28eac3ab9 100644 --- a/src/theme/icon.rs +++ b/src/theme/icon.rs @@ -1,12 +1,48 @@ use serde::Deserialize; use std::collections::HashMap; +enum ByFilename { + Name, + Extension, +} + +fn deserialize_by_filename<'de, D>( + deserializer: D, + by: ByFilename, +) -> Result, D::Error> +where + D: serde::de::Deserializer<'de>, +{ + let default = match by { + ByFilename::Name => IconTheme::get_default_icons_by_name(), + ByFilename::Extension => IconTheme::get_default_icons_by_extension(), + }; + HashMap::<_, _>::deserialize(deserializer) + .map(|input| default.into_iter().chain(input.into_iter()).collect()) +} + +fn deserialize_by_name<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::de::Deserializer<'de>, +{ + deserialize_by_filename(deserializer, ByFilename::Name) +} + +fn deserialize_by_extension<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::de::Deserializer<'de>, +{ + deserialize_by_filename(deserializer, ByFilename::Extension) +} + #[derive(Debug, Deserialize, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] #[serde(default)] pub struct IconTheme { + #[serde(deserialize_with = "deserialize_by_name")] pub name: HashMap, + #[serde(deserialize_with = "deserialize_by_extension")] pub extension: HashMap, pub filetype: ByType, } @@ -653,4 +689,36 @@ filetype: let empty: IconTheme = Theme::with_yaml("filetype:\n dir: ").unwrap(); assert_eq!(empty.filetype.dir, ""); } + + #[test] + fn test_custom_icon_by_name() { + // When a user sets to use 📦-icon for a cargo.toml file, + let theme: IconTheme = Theme::with_yaml("name:\n cargo.toml: 📦").unwrap(); + // 📦-icon should be used for a cargo.toml file. + assert_eq!(theme.name.get("cargo.toml").unwrap(), "📦"); + } + + #[test] + fn test_default_icon_by_name_with_custom_entry() { + // When a user sets to use 📦-icon for a cargo.toml file, + let theme: IconTheme = Theme::with_yaml("name:\n cargo.toml: 📦").unwrap(); + // the default icon  should be used for a cargo.lock file. + assert_eq!(theme.name.get("cargo.lock").unwrap(), "\u{e7a8}"); + } + + #[test] + fn test_custom_icon_by_extension() { + // When a user sets to use 🦀-icon for *.rs files, + let theme: IconTheme = Theme::with_yaml("extension:\n rs: 🦀").unwrap(); + // 🦀-icon should be used for *.rs files. + assert_eq!(theme.extension.get("rs").unwrap(), "🦀"); + } + + #[test] + fn test_default_icon_by_extension_with_custom_entry() { + // When a user sets to use 🦀-icon for *.rs files, + let theme: IconTheme = Theme::with_yaml("extension:\n rs: 🦀").unwrap(); + // the default icon  should be used for *.go files. + assert_eq!(theme.extension.get("go").unwrap(), "\u{e627}"); + } }