From fa7b5993bb61b2b5c1b075163f5bbdb96ee7bea4 Mon Sep 17 00:00:00 2001 From: zwPapEr Date: Tue, 26 Jul 2022 11:46:51 +0800 Subject: [PATCH 01/16] :recycle: theme: refactor color to be under theme so that we can add icon theme Signed-off-by: zwPapEr --- src/color.rs | 20 +++---- src/main.rs | 1 + src/theme.rs | 82 ++++++++++++++++++++++++++ src/{color/theme.rs => theme/color.rs} | 66 +++------------------ src/theme/icon.rs | 14 +++++ 5 files changed, 113 insertions(+), 70 deletions(-) create mode 100644 src/theme.rs rename src/{color/theme.rs => theme/color.rs} (86%) create mode 100644 src/theme/icon.rs diff --git a/src/color.rs b/src/color.rs index c3bd938b9..44f193c66 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,13 +1,11 @@ -mod theme; - +use crossterm::style::Color; use crossterm::style::{Attribute, ContentStyle, StyledContent, Stylize}; -use theme::Theme; +use lscolors::{Indicator, LsColors}; +use std::path::Path; +use crate::theme::{Theme,color::ColorTheme}; pub use crate::flags::color::ThemeOption; -use crossterm::style::Color; -use lscolors::{Indicator, LsColors}; -use std::path::Path; #[allow(dead_code)] #[derive(Hash, Debug, Eq, PartialEq, Clone)] @@ -71,7 +69,7 @@ impl Elem { matches!(self, Elem::Dir { uid: true } | Elem::File { uid: true, .. }) } - fn get_color(&self, theme: &theme::Theme) -> Color { + pub fn get_color(&self, theme: &ColorTheme) -> Color { match self { Elem::File { exec: true, @@ -131,7 +129,7 @@ impl Elem { pub type ColoredString = StyledContent; pub struct Colors { - theme: Option, + theme: Option, lscolors: Option, } @@ -139,8 +137,8 @@ impl Colors { pub fn new(t: ThemeOption) -> Self { let theme = match t { ThemeOption::NoColor => None, - ThemeOption::Default | ThemeOption::NoLscolors => Some(Theme::default()), - ThemeOption::Custom(ref file) => Some(Theme::from_path(file).unwrap_or_default()), + ThemeOption::Default | ThemeOption::NoLscolors => Some(Theme::default().color), + ThemeOption::Custom(ref file) => Some(Theme::from_path(file).unwrap_or_default().color), }; let lscolors = match t { ThemeOption::Default | ThemeOption::Custom(_) => { @@ -301,7 +299,7 @@ fn to_content_style(ls: &lscolors::Style) -> ContentStyle { #[cfg(test)] mod tests { use super::Colors; - use crate::color::Theme; + use crate::theme::color_theme::Theme; use crate::color::ThemeOption; #[test] fn test_color_new_no_color_theme() { diff --git a/src/main.rs b/src/main.rs index 44829e301..78bdac926 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,6 +36,7 @@ mod flags; mod icon; mod meta; mod sort; +mod theme; use crate::config_file::Config; use crate::core::Core; diff --git a/src/theme.rs b/src/theme.rs new file mode 100644 index 000000000..8223e245c --- /dev/null +++ b/src/theme.rs @@ -0,0 +1,82 @@ +pub mod color; +pub mod icon; + +use serde::Deserialize; +use std::path::Path; +use std::fs; + +use crate::config_file; +use crate::print_error; + +use color::ColorTheme; +use icon::IconTheme; + +#[derive(Debug, Deserialize, PartialEq)] +#[serde(rename_all = "kebab-case")] +#[serde(deny_unknown_fields)] +#[serde(default)] +pub struct Theme { + pub color: ColorTheme, + pub icon: IconTheme, +} + +impl Default for Theme { + fn default() -> Self { + // TODO(zwpaper): check terminal color and return light or dark + Theme{ + color: ColorTheme::default(), + icon: IconTheme::default(), + } + } +} + +impl Theme { + /// This read theme from file, + /// use the file path if it is absolute + /// prefix the config_file dir to it if it is not + pub fn from_path(file: &str) -> Option { + let real = if let Some(path) = config_file::Config::expand_home(file) { + path + } else { + print_error!("Not a valid theme file path: {}.", &file); + return None; + }; + let path = if Path::new(&real).is_absolute() { + real + } else { + config_file::Config::config_file_path()? + .join("themes") + .join(real) + }; + match fs::read(&path.with_extension("yaml")) { + Ok(f) => match Self::with_yaml(&String::from_utf8_lossy(&f)) { + Ok(t) => Some(t), + Err(e) => { + print_error!("Theme file {} format error: {}.", &file, e); + None + } + }, + Err(_) => { + // try `yml` if `yaml` extension file not found + match fs::read(&path.with_extension("yml")) { + Ok(f) => match Self::with_yaml(&String::from_utf8_lossy(&f)) { + Ok(t) => Some(t), + Err(e) => { + print_error!("Theme file {} format error: {}.", &file, e); + None + } + }, + Err(e) => { + print_error!("Not a valid theme: {}, {}.", path.to_string_lossy(), e); + None + } + } + } + } + } + + /// This constructs a Theme struct with a passed [Yaml] str. + fn with_yaml(yaml: &str) -> Result { + serde_yaml::from_str::(yaml) + } +} diff --git a/src/color/theme.rs b/src/theme/color.rs similarity index 86% rename from src/color/theme.rs rename to src/theme/color.rs index 6a49261da..31e056931 100644 --- a/src/color/theme.rs +++ b/src/theme/color.rs @@ -1,16 +1,12 @@ ///! This module provides methods to create theme from files and operations related to ///! this. -use crate::config_file; -use crate::print_error; - use crossterm::style::Color; use serde::Deserialize; -use std::path::Path; -use std::{fmt, fs}; +use std::fmt; // Custom color deserialize fn deserialize_color<'de, D>(deserializer: D) -> Result -where + where D: serde::de::Deserializer<'de>, { struct ColorVisitor; @@ -82,13 +78,14 @@ where deserializer.deserialize_any(ColorVisitor) } + /// A struct holding the theme configuration /// Color table: https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.avg #[derive(Debug, Deserialize, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] #[serde(default)] -pub struct Theme { +pub struct ColorTheme { #[serde(deserialize_with = "deserialize_color")] pub user: Color, #[serde(deserialize_with = "deserialize_color")] @@ -328,65 +325,16 @@ impl Default for Links { } } -impl Default for Theme { +impl Default for ColorTheme { fn default() -> Self { // TODO(zwpaper): check terminal color and return light or dark Self::default_dark() } } -impl Theme { - /// This read theme from file, - /// use the file path if it is absolute - /// prefix the config_file dir to it if it is not - pub fn from_path(file: &str) -> Option { - let real = if let Some(path) = config_file::Config::expand_home(file) { - path - } else { - print_error!("Not a valid theme file path: {}.", &file); - return None; - }; - let path = if Path::new(&real).is_absolute() { - real - } else { - config_file::Config::config_file_path()? - .join("themes") - .join(real) - }; - match fs::read(&path.with_extension("yaml")) { - Ok(f) => match Self::with_yaml(&String::from_utf8_lossy(&f)) { - Ok(t) => Some(t), - Err(e) => { - print_error!("Theme file {} format error: {}.", &file, e); - None - } - }, - Err(_) => { - // try `yml` if `yaml` extension file not found - match fs::read(&path.with_extension("yml")) { - Ok(f) => match Self::with_yaml(&String::from_utf8_lossy(&f)) { - Ok(t) => Some(t), - Err(e) => { - print_error!("Theme file {} format error: {}.", &file, e); - None - } - }, - Err(e) => { - print_error!("Not a valid theme: {}, {}.", path.to_string_lossy(), e); - None - } - } - } - } - } - - /// This constructs a Theme struct with a passed [Yaml] str. - fn with_yaml(yaml: &str) -> Result { - serde_yaml::from_str::(yaml) - } - +impl ColorTheme { pub fn default_dark() -> Self { - Theme { + ColorTheme { user: Color::AnsiValue(230), // Cornsilk1 group: Color::AnsiValue(187), // LightYellow3 permission: Permission::default(), diff --git a/src/theme/icon.rs b/src/theme/icon.rs new file mode 100644 index 000000000..2b61acc5a --- /dev/null +++ b/src/theme/icon.rs @@ -0,0 +1,14 @@ +use serde::Deserialize; + +#[derive(Debug, Deserialize, PartialEq)] +#[serde(rename_all = "kebab-case")] +#[serde(deny_unknown_fields)] +#[serde(default)] +pub struct IconTheme {} + +impl Default for IconTheme { + fn default() -> Self { + // TODO(zwpaper): check terminal color and return light or dark + IconTheme{} + } +} From 6b5c749eed95fad52ee9dd7fe842d797e54fa757 Mon Sep 17 00:00:00 2001 From: Wei Zhang Date: Fri, 29 Jul 2022 01:35:01 +0800 Subject: [PATCH 02/16] :recycle: refactor theme read file to de trait --- src/color.rs | 2 +- src/theme.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/color.rs b/src/color.rs index 44f193c66..db4f1406e 100644 --- a/src/color.rs +++ b/src/color.rs @@ -138,7 +138,7 @@ impl Colors { let theme = match t { ThemeOption::NoColor => None, ThemeOption::Default | ThemeOption::NoLscolors => Some(Theme::default().color), - ThemeOption::Custom(ref file) => Some(Theme::from_path(file).unwrap_or_default().color), + ThemeOption::Custom(ref file) => Some(Theme::from_path::(file).unwrap_or_default()), }; let lscolors = match t { ThemeOption::Default | ThemeOption::Custom(_) => { diff --git a/src/theme.rs b/src/theme.rs index 8223e245c..352395add 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,7 +1,7 @@ pub mod color; pub mod icon; -use serde::Deserialize; +use serde::{Deserialize, de::DeserializeOwned}; use std::path::Path; use std::fs; @@ -34,7 +34,7 @@ impl Theme { /// This read theme from file, /// use the file path if it is absolute /// prefix the config_file dir to it if it is not - pub fn from_path(file: &str) -> Option { + pub fn from_path(file: &str) -> Option { let real = if let Some(path) = config_file::Config::expand_home(file) { path } else { @@ -76,7 +76,7 @@ impl Theme { } /// This constructs a Theme struct with a passed [Yaml] str. - fn with_yaml(yaml: &str) -> Result { - serde_yaml::from_str::(yaml) + fn with_yaml(yaml: &str) -> Result { + serde_yaml::from_str::(yaml) } } From 0e6deb4abaf26039aeae09bfea2c8d6542d09d87 Mon Sep 17 00:00:00 2001 From: Wei Zhang Date: Fri, 29 Jul 2022 01:35:32 +0800 Subject: [PATCH 03/16] :hammer: theme: move default icon to icon theme --- src/icon.rs | 436 ++-------------------------------------------- src/theme/icon.rs | 402 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 414 insertions(+), 424 deletions(-) diff --git a/src/icon.rs b/src/icon.rs index d7323d0e5..b004f500e 100644 --- a/src/icon.rs +++ b/src/icon.rs @@ -1,20 +1,14 @@ -use crate::meta::{FileType, Name}; use std::collections::HashMap; +use crate::meta::{FileType, Name}; +use crate::flags::IconOption; +use crate::theme::{Theme, icon::IconTheme}; + pub struct Icons { display_icons: bool, - icons_by_name: HashMap<&'static str, &'static str>, - icons_by_extension: HashMap<&'static str, &'static str>, - default_folder_icon: &'static str, - default_file_icon: &'static str, icon_separator: String, -} -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum Theme { - NoIcon, - Fancy, - Unicode, + theme: IconTheme, } // In order to add a new icon, write the unicode value like "\ue5fb" then @@ -22,32 +16,13 @@ pub enum Theme { // // s#\\u[0-9a-f]*#\=eval('"'.submatch(0).'"')# impl Icons { - pub fn new(theme: Theme, icon_separator: String) -> Self { - let display_icons = matches!(theme, Theme::Fancy | Theme::Unicode); - let (icons_by_name, icons_by_extension, default_file_icon, default_folder_icon) = - if theme == Theme::Fancy { - ( - Self::get_default_icons_by_name(), - Self::get_default_icons_by_extension(), - "\u{f016}", // ļ€– - "\u{f115}", // ļ„• - ) - } else { - ( - HashMap::new(), - HashMap::new(), - "\u{1f5cb}", // šŸ—‹ - "\u{1f5c1}", // šŸ— - ) - }; + pub fn new(opt: IconOption, icon_separator: String) -> Self { + let display_icons = !(opt == IconOption::Never); // TODO(zwpaper): Auto Self { display_icons, - icons_by_name, - icons_by_extension, - default_file_icon, - default_folder_icon, icon_separator, + theme: Theme::default().icon, } } @@ -69,6 +44,7 @@ impl Icons { _ => { // Use the known names if let Some(icon) = self + .theme .icons_by_name .get(name.file_name().to_lowercase().as_str()) { @@ -76,18 +52,19 @@ impl Icons { } // Use the known extensions else if let Some(icon) = name.extension().and_then(|extension| { - self.icons_by_extension + self.theme + .icons_by_extension .get(extension.to_lowercase().as_str()) }) { icon } else { match file_type { - FileType::Directory { .. } => self.default_folder_icon, + FileType::Directory { .. } => &self.theme.default_folder_icon, // If a file has no extension and is executable, show an icon. // Except for Windows, it marks everything as an executable. #[cfg(not(windows))] FileType::File { exec: true, .. } => "\u{f489}", // "ļ’‰" - _ => self.default_file_icon, + _ => &self.theme.default_file_icon, } } } @@ -95,391 +72,6 @@ impl Icons { format!("{}{}", icon, self.icon_separator) } - - fn get_default_icons_by_name() -> HashMap<&'static str, &'static str> { - // Note: filenames must be lower-case - HashMap::from([ - (".trash", "\u{f1f8}"), // "ļ‡ø" - (".atom", "\u{e764}"), // "ī¤" - (".bash_profile", "\u{e615}"), // "ī˜•" - (".bash_logout", "\u{e615}"), // "ī˜•" - (".bashrc", "\u{f489}"), // "ļ’‰" - (".cargo", "\u{e7a8}"), // "īžØ" - (".clang-format", "\u{e615}"), // "ī˜•" - (".config", "\u{e5fc}"), // "ī—¼" - (".emacs.d", "\u{e779}"), // "ī¹" - (".doom.d", "\u{e779}"), // "ī¹" - (".git", "\u{e5fb}"), // "ī—»" - (".git-credentials", "\u{e60a}"), // "ī˜Š" - (".gitattributes", "\u{f1d3}"), // "ļ‡“" - (".gitconfig", "\u{f1d3}"), // "ļ‡“" - (".github", "\u{e5fd}"), // "ī—½" - (".gitignore", "\u{f1d3}"), // "ļ‡“" - (".gitlab-ci.yml", "\u{f296}"), // "ļŠ–" - (".gitmodules", "\u{f1d3}"), // "ļ‡“" - (".htaccess", "\u{e615}"), // "ī˜•" - (".htpasswd", "\u{e615}"), // "ī˜•" - (".inputrc", "\u{e615}"), // "ī˜•" - (".node_repl_history", "\u{e718}"), // "īœ˜" - (".npm", "\u{e5fa}"), // "ī—ŗ" - (".profile", "\u{f68c}"), // "ļšŒ" - (".python_history", "\u{e606}"), // "ī˜†" - (".release.toml", "\u{e7a8}"), // "īžØ" - (".rvm", "\u{e21e}"), // "īˆž" - (".ssh", "\u{f023}"), // "ļ€£" - (".vim", "\u{e62b}"), // "ī˜«" - (".vimrc", "\u{e62b}"), // "ī˜«" - (".viminfo", "\u{e62b}"), // "ī˜«" - (".vscode", "\u{e70c}"), // "īœŒ" - (".xauthority", "\u{e615}"), // "ī˜•" - (".xinitrc", "\u{e615}"), // "ī˜•" - (".xresources", "\u{e615}"), // "ī˜•" - (".zshrc", "\u{f489}"), // "ļ’‰" - (".zsh_history", "\u{e615}"), // "ī˜•" - ("a.out", "\u{f489}"), // "ļ’‰" - ("authorized_keys", "\u{e60a}"), // "ī˜Š" - ("bin", "\u{e5fc}"), // "ī—¼" - ("bspwmrc", "\u{e615}"), // "ī˜•" - ("cargo.toml", "\u{e7a8}"), // "īžØ" - ("cargo.lock", "\u{e7a8}"), // "īžØ" - ("changelog", "\u{e609}"), // "ī˜‰" - ("composer.json", "\u{e608}"), // "ī˜ˆ" - ("config", "\u{e5fc}"), // "ī—¼" - ("config.ac", "\u{e615}"), // "ī˜•" - ("config.mk", "\u{e615}"), // "ī˜•" - ("config.el", "\u{e779}"), // "ī¹" - ("custom.el", "\u{e779}"), // "ī¹" - ("contributing", "\u{e60a}"), // "ī˜Š" - ("cron.d", "\u{e5fc}"), // "ī—¼" - ("cron.daily", "\u{e5fc}"), // "ī—¼" - ("cron.hourly", "\u{e5fc}"), // "ī—¼" - ("cron.weekly", "\u{e5fc}"), // "ī—¼" - ("cron.monthly", "\u{e5fc}"), // "ī—¼" - ("crontab", "\u{e615}"), // "ī˜•" - ("crypttab", "\u{e615}"), // "ī˜•" - ("desktop", "\u{f108}"), // "ļ„ˆ" - ("downloads", "\u{f498}"), // "ļ’˜" - ("docker-compose.yml", "\u{f308}"), // "ļŒˆ" - ("dockerfile", "\u{f308}"), // "ļŒˆ" - ("documents", "\u{f02d}"), // "ļ€­" - (".ds_store", "\u{f179}"), // "ļ…¹" - ("etc", "\u{e5fc}"), // "ī—¼" - ("favicon.ico", "\u{f005}"), // "ļ€…" - ("fstab", "\u{f1c0}"), // "ļ‡€" - ("gitignore_global", "\u{f1d3}"), // "ļ‡“" - ("gradle", "\u{e70e}"), // "īœŽ" - ("group", "\u{e615}"), // "ī˜•" - ("gruntfile.coffee", "\u{e611}"), // "ī˜‘" - ("gruntfile.js", "\u{e611}"), // "ī˜‘" - ("gruntfile.ls", "\u{e611}"), // "ī˜‘" - ("gshadow", "\u{e615}"), // "ī˜•" - ("gulpfile.coffee", "\u{e610}"), // "ī˜" - ("gulpfile.js", "\u{e610}"), // "ī˜" - ("gulpfile.ls", "\u{e610}"), // "ī˜" - ("hidden", "\u{f023}"), // "ļ€£" - ("hosts", "\u{f502}"), // "ļ”‚" - ("htoprc", "\u{e615}"), // "ī˜•" - ("include", "\u{e5fc}"), // "ī—¼" - ("init.el", "\u{e779}"), // "ī¹" - ("known_hosts", "\u{e60a}"), // "ī˜Š" - ("lib", "\u{f121}"), // "ļ„”" - ("license", "\u{e60a}"), // "ī˜Š" - ("license.md", "\u{e60a}"), // "ī˜Š" - ("license.txt", "\u{e60a}"), // "ī˜Š" - ("localized", "\u{f179}"), // "ļ…¹" - ("mail", "\u{f6ef}"), // "ļ›Æ" - ("makefile", "\u{e615}"), // "ī˜•" - ("makefile.ac", "\u{e615}"), // "ī˜•" - ("music", "\u{f025}"), // "ļ€„" - ("muttrc", "\u{e615}"), // "ī˜•" - ("node_modules", "\u{e5fa}"), // "ī—ŗ" - ("npmignore", "\u{e71e}"), // "īœž" - ("package.json", "\u{e718}"), // "īœ˜" - ("packages.el", "\u{e779}"), // "ī¹" - ("package-lock.json", "\u{e718}"), // "īœ˜" - ("passwd", "\u{f023}"), // "ļ€£" - ("pictures", "\u{f03e}"), // "ļ€¾" - ("profile", "\u{e615}"), // "ī˜•" - ("readme", "\u{e609}"), // "ī˜‰" - ("rc.lua", "\u{e615}"), // "ī˜•" - ("rubydoc", "\u{e73b}"), // "īœ»" - ("robots.txt", "\u{fba7}"), // "ļ®§" - ("root", "\u{f023}"), // "ļ€£" - ("shadow", "\u{e615}"), // "ī˜•" - ("shells", "\u{e615}"), // "ī˜•" - ("sudoers", "\u{f023}"), // "ļ€£" - ("sxhkdrc", "\u{e615}"), // "ī˜•" - ("tigrc", "\u{e615}"), // "ī˜•" - ("vagrantfile", "\u{e615}"), // "ī˜•" - ("videos", "\u{f03d}"), // "ļ€½" - ("hostname", "\u{e615}"), // "ī˜•" - ("webpack.config.js", "\u{fc29}"), // "ļ°©" - ("xmonad.hs", "\u{e615}"), // "ī˜•" - ("xorg.conf.d", "\u{e5fc}"), // "ī—¼" - ("xbps.d", "\u{e5fc}"), // "ī—¼" - ]) - } - - fn get_default_icons_by_extension() -> HashMap<&'static str, &'static str> { - // Note: extensions must be lower-case - HashMap::from([ - ("1", "\u{f02d}"), // "ļ€­" - ("7z", "\u{f410}"), // "ļ" - ("a", "\u{e624}"), // "ī˜¤" - ("ai", "\u{e7b4}"), // "īž“" - ("ape", "\u{f001}"), // "ļ€" - ("apk", "\u{e70e}"), // "īœŽ" - ("asc", "\u{f023}"), // "ļ€£" - ("asm", "\u{e614}"), // "ī˜”" - ("asp", "\u{f121}"), // "ļ„”" - ("avi", "\u{f008}"), // "ļ€ˆ" - ("avro", "\u{e60b}"), // "ī˜‹" - ("awk", "\u{f489}"), // "ļ’‰" - ("bash", "\u{f489}"), // "ļ’‰" - ("bash_history", "\u{f489}"), // "ļ’‰" - ("bash_profile", "\u{f489}"), // "ļ’‰" - ("bashrc", "\u{f489}"), // "ļ’‰" - ("bat", "\u{f17a}"), // "ļ…ŗ" - ("bin", "\u{f489}"), // "ļ’‰" - ("bio", "\u{f910}"), // "ļ¤" - ("bmp", "\u{f1c5}"), // "ļ‡…" - ("bz2", "\u{f410}"), // "ļ" - ("c", "\u{e61e}"), // "ī˜ž" - ("c++", "\u{e61d}"), // "ī˜" - ("cc", "\u{e61d}"), // "ī˜" - ("cfg", "\u{e615}"), // "ī˜•" - ("cl", "\u{f671}"), // "ļ™±" - ("class", "\u{e738}"), // "īœø" - ("clj", "\u{e768}"), // "īØ" - ("cljs", "\u{e76a}"), // "īŖ" - ("cls", "\u{e600}"), // "ī˜€" - ("coffee", "\u{f0f4}"), // "ļƒ“" - ("conf", "\u{e615}"), // "ī˜•" - ("cp", "\u{e61d}"), // "ī˜" - ("cpp", "\u{e61d}"), // "ī˜" - ("cs", "\u{f81a}"), // "ļ š" - ("cshtml", "\u{f1fa}"), // "ļ‡ŗ" - ("csproj", "\u{f81a}"), // "ļ š" - ("csx", "\u{f81a}"), // "ļ š" - ("csh", "\u{f489}"), // "ļ’‰" - ("css", "\u{e749}"), // "ī‰" - ("csv", "\u{f1c3}"), // "ļ‡ƒ" - ("cue", "\u{f001}"), // "ļ€" - ("cxx", "\u{e61d}"), // "ī˜" - ("dart", "\u{e798}"), // "īž˜" - ("db", "\u{f1c0}"), // "ļ‡€" - ("deb", "\u{f187}"), // "ļ†‡" - ("desktop", "\u{f108}"), // "ļ„ˆ" - ("diff", "\u{e728}"), // "īœØ" - ("dll", "\u{f17a}"), // "ļ…ŗ" - ("doc", "\u{f1c2}"), // "ļ‡‚" - ("dockerfile", "\u{f308}"), // "ļŒˆ" - ("docx", "\u{f1c2}"), // "ļ‡‚" - ("ds_store", "\u{f179}"), // "ļ…¹" - ("dump", "\u{f1c0}"), // "ļ‡€" - ("ebook", "\u{e28b}"), // "īŠ‹" - ("editorconfig", "\u{e615}"), // "ī˜•" - ("ejs", "\u{e618}"), // "ī˜˜" - ("el", "\u{f671}"), // "ļ™±" - ("elc", "\u{f671}"), // "ļ™±" - ("elf", "\u{f489}"), // "ļ’‰" - ("elm", "\u{e62c}"), // "ī˜¬" - ("env", "\u{f462}"), // "ļ‘¢" - ("eot", "\u{f031}"), // "ļ€±" - ("epub", "\u{e28a}"), // "īŠŠ" - ("erb", "\u{e73b}"), // "īœ»" - ("erl", "\u{e7b1}"), // "īž±" - ("exe", "\u{f17a}"), // "ļ…ŗ" - ("ex", "\u{e62d}"), // "ī˜­" - ("exs", "\u{e62d}"), // "ī˜­" - ("fish", "\u{f489}"), // "ļ’‰" - ("flac", "\u{f001}"), // "ļ€" - ("flv", "\u{f008}"), // "ļ€ˆ" - ("font", "\u{f031}"), // "ļ€±" - ("fpl", "\u{f910}"), // "ļ¤" - ("fs", "\u{e7a7}"), // "īž§" - ("fsx", "\u{e7a7}"), // "īž§" - ("fsi", "\u{e7a7}"), // "īž§" - ("gdoc", "\u{f1c2}"), // "ļ‡‚" - ("gemfile", "\u{e21e}"), // "īˆž" - ("gemspec", "\u{e21e}"), // "īˆž" - ("gform", "\u{f298}"), // "ļŠ˜" - ("gif", "\u{f1c5}"), // "ļ‡…" - ("git", "\u{f1d3}"), // "ļ‡“" - ("go", "\u{e627}"), // "ī˜§" - ("gradle", "\u{e70e}"), // "īœŽ" - ("gsheet", "\u{f1c3}"), // "ļ‡ƒ" - ("gslides", "\u{f1c4}"), // "ļ‡„" - ("guardfile", "\u{e21e}"), // "īˆž" - ("gz", "\u{f410}"), // "ļ" - ("h", "\u{f0fd}"), // "ļƒ½" - ("hbs", "\u{e60f}"), // "ī˜" - ("heic", "\u{f1c5}"), // "ļ‡…" - ("heif", "\u{f1c5}"), // "ļ‡…" - ("heix", "\u{f1c5}"), // "ļ‡…" - ("hpp", "\u{f0fd}"), // "ļƒ½" - ("hs", "\u{e777}"), // "ī·" - ("htm", "\u{f13b}"), // "ļ„»" - ("html", "\u{f13b}"), // "ļ„»" - ("hxx", "\u{f0fd}"), // "ļƒ½" - ("ico", "\u{f1c5}"), // "ļ‡…" - ("image", "\u{f1c5}"), // "ļ‡…" - ("img", "\u{f1c0}"), // "ļ‡€" - ("iml", "\u{e7b5}"), // "īžµ" - ("ini", "\u{e615}"), // "ī˜•" - ("ipynb", "\u{e606}"), // "ī˜†" - ("iso", "\u{f1c0}"), // "ļ‡€" - ("jar", "\u{e738}"), // "īœø" - ("java", "\u{e738}"), // "īœø" - ("jpeg", "\u{f1c5}"), // "ļ‡…" - ("jpg", "\u{f1c5}"), // "ļ‡…" - ("js", "\u{e74e}"), // "īŽ" - ("json", "\u{e60b}"), // "ī˜‹" - ("jsx", "\u{e7ba}"), // "īžŗ" - ("jl", "\u{e624}"), // "ī˜¤" - ("key", "\u{e60a}"), // "ī˜Š" - ("ksh", "\u{f489}"), // "ļ’‰" - ("ld", "\u{e624}"), // "ī˜¤" - ("ldb", "\u{f1c0}"), // "ļ‡€" - ("less", "\u{e758}"), // "ī˜" - ("lhs", "\u{e777}"), // "ī·" - ("license", "\u{e60a}"), // "ī˜Š" - ("lisp", "\u{f671}"), // "ļ™±" - ("localized", "\u{f179}"), // "ļ…¹" - ("lock", "\u{f023}"), // "ļ€£" - ("log", "\u{f18d}"), // "ļ†" - ("lua", "\u{e620}"), // "ī˜ " - ("lz", "\u{f410}"), // "ļ" - ("m3u", "\u{f910}"), // "ļ¤" - ("m3u8", "\u{f910}"), // "ļ¤" - ("m4a", "\u{f001}"), // "ļ€" - ("m4v", "\u{f008}"), // "ļ€ˆ" - ("magnet", "\u{f076}"), // "ļ¶" - ("markdown", "\u{e609}"), // "ī˜‰" - ("md", "\u{e609}"), // "ī˜‰" - ("mjs", "\u{e74e}"), // "īŽ" - ("mkd", "\u{e609}"), // "ī˜‰" - ("mkv", "\u{f008}"), // "ļ€ˆ" - ("mobi", "\u{e28b}"), // "īŠ‹" - ("mov", "\u{f008}"), // "ļ€ˆ" - ("mp3", "\u{f001}"), // "ļ€" - ("mp4", "\u{f008}"), // "ļ€ˆ" - ("msi", "\u{f17a}"), // "ļ…ŗ" - ("mustache", "\u{e60f}"), // "ī˜" - ("nix", "\u{f313}"), // "ļŒ“" - ("npmignore", "\u{e71e}"), // "īœž" - ("o", "\u{e624}"), // "ī˜¤" - ("opus", "\u{f001}"), // "ļ€" - ("ogg", "\u{f001}"), // "ļ€" - ("ogv", "\u{f008}"), // "ļ€ˆ" - ("otf", "\u{f031}"), // "ļ€±" - ("pdf", "\u{f1c1}"), // "ļ‡" - ("pem", "\u{f805}"), // "ļ …" - ("phar", "\u{e608}"), // "ī˜ˆ" - ("php", "\u{e608}"), // "ī˜ˆ" - ("pkg", "\u{f187}"), // "ļ†‡" - ("pl", "\u{e769}"), // "ī©" - ("plist", "\u{f302}"), // "ļŒ‚" - ("pls", "\u{f910}"), // "ļ¤" - ("pm", "\u{e769}"), // "ī©" - ("png", "\u{f1c5}"), // "ļ‡…" - ("ppt", "\u{f1c4}"), // "ļ‡„" - ("pptx", "\u{f1c4}"), // "ļ‡„" - ("procfile", "\u{e21e}"), // "īˆž" - ("properties", "\u{e60b}"), // "ī˜‹" - ("ps1", "\u{f489}"), // "ļ’‰" - ("psd", "\u{e7b8}"), // "īžø" - ("pub", "\u{e60a}"), // "ī˜Š" - ("pxm", "\u{f1c5}"), // "ļ‡…" - ("py", "\u{e606}"), // "ī˜†" - ("pyc", "\u{e606}"), // "ī˜†" - ("r", "\u{fcd2}"), // "ļ³’" - ("rakefile", "\u{e21e}"), // "īˆž" - ("rar", "\u{f410}"), // "ļ" - ("razor", "\u{f1fa}"), // "ļ‡ŗ" - ("rb", "\u{e21e}"), // "īˆž" - ("rdata", "\u{fcd2}"), // "ļ³’" - ("rdb", "\u{e76d}"), // "ī­" - ("rdoc", "\u{e609}"), // "ī˜‰" - ("rds", "\u{fcd2}"), // "ļ³’" - ("readme", "\u{e609}"), // "ī˜‰" - ("rlib", "\u{e7a8}"), // "īžØ" - ("rmd", "\u{e609}"), // "ī˜‰" - ("rpm", "\u{f187}"), // "ļ†‡" - ("rproj", "\u{fac5}"), // "ļ«…" - ("rs", "\u{e7a8}"), // "īžØ" - ("rspec", "\u{e21e}"), // "īˆž" - ("rspec_parallel", "\u{e21e}"), // "īˆž" - ("rspec_status", "\u{e21e}"), // "īˆž" - ("rss", "\u{f09e}"), // "ļ‚ž" - ("rtf", "\u{f15c}"), // "ļ…œ" - ("ru", "\u{e21e}"), // "īˆž" - ("rubydoc", "\u{e73b}"), // "īœ»" - ("s", "\u{e614}"), // "ī˜”" - ("sass", "\u{e603}"), // "ī˜ƒ" - ("scala", "\u{e737}"), // "īœ·" - ("scpt", "\u{f302}"), // "ļŒ‚" - ("scss", "\u{e603}"), // "ī˜ƒ" - ("sh", "\u{f489}"), // "ļ’‰" - ("shell", "\u{f489}"), // "ļ’‰" - ("sig", "\u{e60a}"), // "ī˜Š" - ("slim", "\u{e73b}"), // "īœ»" - ("sln", "\u{e70c}"), // "īœŒ" - ("so", "\u{e624}"), // "ī˜¤" - ("sql", "\u{f1c0}"), // "ļ‡€" - ("sqlite3", "\u{e7c4}"), // "īŸ„" - ("srt", "\u{f02d}"), // "ļ€­" - ("styl", "\u{e600}"), // "ī˜€" - ("stylus", "\u{e600}"), // "ī˜€" - ("sub", "\u{f02d}"), // "ļ€­" - ("sublime-package", "\u{e7aa}"), // "īžŖ" - ("sublime-session", "\u{e7aa}"), // "īžŖ" - ("svg", "\u{f1c5}"), // "ļ‡…" - ("swift", "\u{e755}"), // "ī•" - ("swp", "\u{e62b}"), // "ī˜«" - ("sym", "\u{e624}"), // "ī˜¤" - ("t", "\u{e769}"), // "ī©" - ("tar", "\u{f410}"), // "ļ" - ("tex", "\u{e600}"), // "ī˜€" - ("tgz", "\u{f410}"), // "ļ" - ("tiff", "\u{f1c5}"), // "ļ‡…" - ("toml", "\u{e60b}"), // "ī˜‹" - ("torrent", "\u{f98c}"), // "ļ¦Œ" - ("ts", "\u{e628}"), // "ī˜Ø" - ("tsx", "\u{e7ba}"), // "īžŗ" - ("ttc", "\u{f031}"), // "ļ€±" - ("ttf", "\u{f031}"), // "ļ€±" - ("twig", "\u{e61c}"), // "ī˜œ" - ("txt", "\u{f15c}"), // "ļ…œ" - ("video", "\u{f008}"), // "ļ€ˆ" - ("vim", "\u{e62b}"), // "ī˜«" - ("vlc", "\u{f910}"), // "ļ¤" - ("vue", "\u{fd42}"), // "ļµ‚" - ("wav", "\u{f001}"), // "ļ€" - ("webm", "\u{f008}"), // "ļ€ˆ" - ("webp", "\u{f1c5}"), // "ļ‡…" - ("windows", "\u{f17a}"), // "ļ…ŗ" - ("wma", "\u{f001}"), // "ļ€" - ("wmv", "\u{f008}"), // "ļ€ˆ" - ("wpl", "\u{f910}"), // "ļ¤" - ("woff", "\u{f031}"), // "ļ€±" - ("woff2", "\u{f031}"), // "ļ€±" - ("xbps", "\u{f187}"), // "ļ†‡" - ("xcf", "\u{f1c5}"), // "ļ‡…" - ("xls", "\u{f1c3}"), // "ļ‡ƒ" - ("xlsx", "\u{f1c3}"), // "ļ‡ƒ" - ("xml", "\u{f121}"), // "ļ„”" - ("xul", "\u{f269}"), // "ļ‰©" - ("xz", "\u{f410}"), // "ļ" - ("yaml", "\u{e60b}"), // "ī˜‹" - ("yml", "\u{e60b}"), // "ī˜‹" - ("zip", "\u{f410}"), // "ļ" - ("zsh", "\u{f489}"), // "ļ’‰" - ("zsh-theme", "\u{f489}"), // "ļ’‰" - ("zshrc", "\u{f489}"), // "ļ’‰" - ("zst", "\u{f410}"), // "ļ" - ]) - } } #[cfg(test)] @@ -589,7 +181,7 @@ mod test { File::create(&file_path).expect("failed to create file"); let meta = Meta::from_path(&file_path, false).unwrap(); - let icon = Icons::new(Theme::Fancy, " ".to_string()); + let icon = Icons::use Theme::Fancy, " ".to_string()); let icon_str = icon.get(&meta.name); assert_eq!(icon_str, format!("{}{}", file_icon, icon.icon_separator)); diff --git a/src/theme/icon.rs b/src/theme/icon.rs index 2b61acc5a..96582c852 100644 --- a/src/theme/icon.rs +++ b/src/theme/icon.rs @@ -1,14 +1,412 @@ +use std::collections::HashMap; use serde::Deserialize; #[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] #[serde(default)] -pub struct IconTheme {} +pub struct IconTheme { + pub icons_by_name: HashMap, + pub icons_by_extension: HashMap, + // pub icons_by_filetype: HashMap, + pub default_folder_icon: String, + pub default_file_icon: String, +} impl Default for IconTheme { fn default() -> Self { // TODO(zwpaper): check terminal color and return light or dark - IconTheme{} + IconTheme{ + icons_by_name: Self::get_default_icons_by_name(), + icons_by_extension: Self::get_default_icons_by_extension(), + default_folder_icon: "\u{f115}".into(), + default_file_icon: "\u{f016}".into(), + } + } +} + +impl IconTheme { + fn get_default_icons_by_name() -> HashMap { + // Note: filenames must be lower-case + [ + (".trash", "\u{f1f8}"), // "ļ‡ø" + (".atom", "\u{e764}"), // "ī¤" + (".bash_profile", "\u{e615}"), // "ī˜•" + (".bash_logout", "\u{e615}"), // "ī˜•" + (".bashrc", "\u{f489}"), // "ļ’‰" + (".cargo", "\u{e7a8}"), // "īžØ" + (".clang-format", "\u{e615}"), // "ī˜•" + (".config", "\u{e5fc}"), // "ī—¼" + (".emacs.d", "\u{e779}"), // "ī¹" + (".doom.d", "\u{e779}"), // "ī¹" + (".git", "\u{e5fb}"), // "ī—»" + (".git-credentials", "\u{e60a}"), // "ī˜Š" + (".gitattributes", "\u{f1d3}"), // "ļ‡“" + (".gitconfig", "\u{f1d3}"), // "ļ‡“" + (".github", "\u{e5fd}"), // "ī—½" + (".gitignore", "\u{f1d3}"), // "ļ‡“" + (".gitlab-ci.yml", "\u{f296}"), // "ļŠ–" + (".gitmodules", "\u{f1d3}"), // "ļ‡“" + (".htaccess", "\u{e615}"), // "ī˜•" + (".htpasswd", "\u{e615}"), // "ī˜•" + (".inputrc", "\u{e615}"), // "ī˜•" + (".node_repl_history", "\u{e718}"), // "īœ˜" + (".npm", "\u{e5fa}"), // "ī—ŗ" + (".profile", "\u{f68c}"), // "ļšŒ" + (".python_history", "\u{e606}"), // "ī˜†" + (".release.toml", "\u{e7a8}"), // "īžØ" + (".rvm", "\u{e21e}"), // "īˆž" + (".ssh", "\u{f023}"), // "ļ€£" + (".vim", "\u{e62b}"), // "ī˜«" + (".vimrc", "\u{e62b}"), // "ī˜«" + (".viminfo", "\u{e62b}"), // "ī˜«" + (".vscode", "\u{e70c}"), // "īœŒ" + (".xauthority", "\u{e615}"), // "ī˜•" + (".xinitrc", "\u{e615}"), // "ī˜•" + (".xresources", "\u{e615}"), // "ī˜•" + (".zshrc", "\u{f489}"), // "ļ’‰" + (".zsh_history", "\u{e615}"), // "ī˜•" + ("a.out", "\u{f489}"), // "ļ’‰" + ("authorized_keys", "\u{e60a}"), // "ī˜Š" + ("bin", "\u{e5fc}"), // "ī—¼" + ("bspwmrc", "\u{e615}"), // "ī˜•" + ("cargo.toml", "\u{e7a8}"), // "īžØ" + ("cargo.lock", "\u{e7a8}"), // "īžØ" + ("changelog", "\u{e609}"), // "ī˜‰" + ("composer.json", "\u{e608}"), // "ī˜ˆ" + ("config", "\u{e5fc}"), // "ī—¼" + ("config.ac", "\u{e615}"), // "ī˜•" + ("config.mk", "\u{e615}"), // "ī˜•" + ("config.el", "\u{e779}"), // "ī¹" + ("custom.el", "\u{e779}"), // "ī¹" + ("contributing", "\u{e60a}"), // "ī˜Š" + ("cron.d", "\u{e5fc}"), // "ī—¼" + ("cron.daily", "\u{e5fc}"), // "ī—¼" + ("cron.hourly", "\u{e5fc}"), // "ī—¼" + ("cron.weekly", "\u{e5fc}"), // "ī—¼" + ("cron.monthly", "\u{e5fc}"), // "ī—¼" + ("crontab", "\u{e615}"), // "ī˜•" + ("crypttab", "\u{e615}"), // "ī˜•" + ("desktop", "\u{f108}"), // "ļ„ˆ" + ("downloads", "\u{f498}"), // "ļ’˜" + ("docker-compose.yml", "\u{f308}"), // "ļŒˆ" + ("dockerfile", "\u{f308}"), // "ļŒˆ" + ("documents", "\u{f02d}"), // "ļ€­" + (".ds_store", "\u{f179}"), // "ļ…¹" + ("etc", "\u{e5fc}"), // "ī—¼" + ("favicon.ico", "\u{f005}"), // "ļ€…" + ("fstab", "\u{f1c0}"), // "ļ‡€" + ("gitignore_global", "\u{f1d3}"), // "ļ‡“" + ("gradle", "\u{e70e}"), // "īœŽ" + ("group", "\u{e615}"), // "ī˜•" + ("gruntfile.coffee", "\u{e611}"), // "ī˜‘" + ("gruntfile.js", "\u{e611}"), // "ī˜‘" + ("gruntfile.ls", "\u{e611}"), // "ī˜‘" + ("gshadow", "\u{e615}"), // "ī˜•" + ("gulpfile.coffee", "\u{e610}"), // "ī˜" + ("gulpfile.js", "\u{e610}"), // "ī˜" + ("gulpfile.ls", "\u{e610}"), // "ī˜" + ("hidden", "\u{f023}"), // "ļ€£" + ("hosts", "\u{f502}"), // "ļ”‚" + ("htoprc", "\u{e615}"), // "ī˜•" + ("include", "\u{e5fc}"), // "ī—¼" + ("init.el", "\u{e779}"), // "ī¹" + ("known_hosts", "\u{e60a}"), // "ī˜Š" + ("lib", "\u{f121}"), // "ļ„”" + ("license", "\u{e60a}"), // "ī˜Š" + ("license.md", "\u{e60a}"), // "ī˜Š" + ("license.txt", "\u{e60a}"), // "ī˜Š" + ("localized", "\u{f179}"), // "ļ…¹" + ("mail", "\u{f6ef}"), // "ļ›Æ" + ("makefile", "\u{e615}"), // "ī˜•" + ("makefile.ac", "\u{e615}"), // "ī˜•" + ("music", "\u{f025}"), // "ļ€„" + ("muttrc", "\u{e615}"), // "ī˜•" + ("node_modules", "\u{e5fa}"), // "ī—ŗ" + ("npmignore", "\u{e71e}"), // "īœž" + ("package.json", "\u{e718}"), // "īœ˜" + ("packages.el", "\u{e779}"), // "ī¹" + ("package-lock.json", "\u{e718}"), // "īœ˜" + ("passwd", "\u{f023}"), // "ļ€£" + ("pictures", "\u{f03e}"), // "ļ€¾" + ("profile", "\u{e615}"), // "ī˜•" + ("readme", "\u{e609}"), // "ī˜‰" + ("rc.lua", "\u{e615}"), // "ī˜•" + ("rubydoc", "\u{e73b}"), // "īœ»" + ("robots.txt", "\u{fba7}"), // "ļ®§" + ("root", "\u{f023}"), // "ļ€£" + ("shadow", "\u{e615}"), // "ī˜•" + ("shells", "\u{e615}"), // "ī˜•" + ("sudoers", "\u{f023}"), // "ļ€£" + ("sxhkdrc", "\u{e615}"), // "ī˜•" + ("tigrc", "\u{e615}"), // "ī˜•" + ("vagrantfile", "\u{e615}"), // "ī˜•" + ("videos", "\u{f03d}"), // "ļ€½" + ("hostname", "\u{e615}"), // "ī˜•" + ("webpack.config.js", "\u{fc29}"), // "ļ°©" + ("xmonad.hs", "\u{e615}"), // "ī˜•" + ("xorg.conf.d", "\u{e5fc}"), // "ī—¼" + ("xbps.d", "\u{e5fc}"), // "ī—¼" + ].iter().map(|&s| (s.0.to_owned(), s.1.to_owned())).collect::>() + } + + fn get_default_icons_by_extension() -> HashMap { + // Note: extensions must be lower-case + [("1", "\u{f02d}"), // "ļ€­" + ("7z", "\u{f410}"), // "ļ" + ("a", "\u{e624}"), // "ī˜¤" + ("ai", "\u{e7b4}"), // "īž“" + ("ape", "\u{f001}"), // "ļ€" + ("apk", "\u{e70e}"), // "īœŽ" + ("asc", "\u{f023}"), // "ļ€£" + ("asm", "\u{e614}"), // "ī˜”" + ("asp", "\u{f121}"), // "ļ„”" + ("avi", "\u{f008}"), // "ļ€ˆ" + ("avro", "\u{e60b}"), // "ī˜‹" + ("awk", "\u{f489}"), // "ļ’‰" + ("bash", "\u{f489}"), // "ļ’‰" + ("bash_history", "\u{f489}"), // "ļ’‰" + ("bash_profile", "\u{f489}"), // "ļ’‰" + ("bashrc", "\u{f489}"), // "ļ’‰" + ("bat", "\u{f17a}"), // "ļ…ŗ" + ("bin", "\u{f489}"), // "ļ’‰" + ("bio", "\u{f910}"), // "ļ¤" + ("bmp", "\u{f1c5}"), // "ļ‡…" + ("bz2", "\u{f410}"), // "ļ" + ("c", "\u{e61e}"), // "ī˜ž" + ("c++", "\u{e61d}"), // "ī˜" + ("cc", "\u{e61d}"), // "ī˜" + ("cfg", "\u{e615}"), // "ī˜•" + ("cl", "\u{f671}"), // "ļ™±" + ("class", "\u{e738}"), // "īœø" + ("clj", "\u{e768}"), // "īØ" + ("cljs", "\u{e76a}"), // "īŖ" + ("cls", "\u{e600}"), // "ī˜€" + ("coffee", "\u{f0f4}"), // "ļƒ“" + ("conf", "\u{e615}"), // "ī˜•" + ("cp", "\u{e61d}"), // "ī˜" + ("cpp", "\u{e61d}"), // "ī˜" + ("cs", "\u{f81a}"), // "ļ š" + ("cshtml", "\u{f1fa}"), // "ļ‡ŗ" + ("csproj", "\u{f81a}"), // "ļ š" + ("csx", "\u{f81a}"), // "ļ š" + ("csh", "\u{f489}"), // "ļ’‰" + ("css", "\u{e749}"), // "ī‰" + ("csv", "\u{f1c3}"), // "ļ‡ƒ" + ("cue", "\u{f001}"), // "ļ€" + ("cxx", "\u{e61d}"), // "ī˜" + ("dart", "\u{e798}"), // "īž˜" + ("db", "\u{f1c0}"), // "ļ‡€" + ("deb", "\u{f187}"), // "ļ†‡" + ("desktop", "\u{f108}"), // "ļ„ˆ" + ("diff", "\u{e728}"), // "īœØ" + ("dll", "\u{f17a}"), // "ļ…ŗ" + ("doc", "\u{f1c2}"), // "ļ‡‚" + ("dockerfile", "\u{f308}"), // "ļŒˆ" + ("docx", "\u{f1c2}"), // "ļ‡‚" + ("ds_store", "\u{f179}"), // "ļ…¹" + ("dump", "\u{f1c0}"), // "ļ‡€" + ("ebook", "\u{e28b}"), // "īŠ‹" + ("editorconfig", "\u{e615}"), // "ī˜•" + ("ejs", "\u{e618}"), // "ī˜˜" + ("el", "\u{f671}"), // "ļ™±" + ("elc", "\u{f671}"), // "ļ™±" + ("elf", "\u{f489}"), // "ļ’‰" + ("elm", "\u{e62c}"), // "ī˜¬" + ("env", "\u{f462}"), // "ļ‘¢" + ("eot", "\u{f031}"), // "ļ€±" + ("epub", "\u{e28a}"), // "īŠŠ" + ("erb", "\u{e73b}"), // "īœ»" + ("erl", "\u{e7b1}"), // "īž±" + ("exe", "\u{f17a}"), // "ļ…ŗ" + ("ex", "\u{e62d}"), // "ī˜­" + ("exs", "\u{e62d}"), // "ī˜­" + ("fish", "\u{f489}"), // "ļ’‰" + ("flac", "\u{f001}"), // "ļ€" + ("flv", "\u{f008}"), // "ļ€ˆ" + ("font", "\u{f031}"), // "ļ€±" + ("fpl", "\u{f910}"), // "ļ¤" + ("fs", "\u{e7a7}"), // "īž§" + ("fsx", "\u{e7a7}"), // "īž§" + ("fsi", "\u{e7a7}"), // "īž§" + ("gdoc", "\u{f1c2}"), // "ļ‡‚" + ("gemfile", "\u{e21e}"), // "īˆž" + ("gemspec", "\u{e21e}"), // "īˆž" + ("gform", "\u{f298}"), // "ļŠ˜" + ("gif", "\u{f1c5}"), // "ļ‡…" + ("git", "\u{f1d3}"), // "ļ‡“" + ("go", "\u{e627}"), // "ī˜§" + ("gradle", "\u{e70e}"), // "īœŽ" + ("gsheet", "\u{f1c3}"), // "ļ‡ƒ" + ("gslides", "\u{f1c4}"), // "ļ‡„" + ("guardfile", "\u{e21e}"), // "īˆž" + ("gz", "\u{f410}"), // "ļ" + ("h", "\u{f0fd}"), // "ļƒ½" + ("hbs", "\u{e60f}"), // "ī˜" + ("heic", "\u{f1c5}"), // "ļ‡…" + ("heif", "\u{f1c5}"), // "ļ‡…" + ("heix", "\u{f1c5}"), // "ļ‡…" + ("hpp", "\u{f0fd}"), // "ļƒ½" + ("hs", "\u{e777}"), // "ī·" + ("htm", "\u{f13b}"), // "ļ„»" + ("html", "\u{f13b}"), // "ļ„»" + ("hxx", "\u{f0fd}"), // "ļƒ½" + ("ico", "\u{f1c5}"), // "ļ‡…" + ("image", "\u{f1c5}"), // "ļ‡…" + ("img", "\u{f1c0}"), // "ļ‡€" + ("iml", "\u{e7b5}"), // "īžµ" + ("ini", "\u{e615}"), // "ī˜•" + ("ipynb", "\u{e606}"), // "ī˜†" + ("iso", "\u{f1c0}"), // "ļ‡€" + ("jar", "\u{e738}"), // "īœø" + ("java", "\u{e738}"), // "īœø" + ("jpeg", "\u{f1c5}"), // "ļ‡…" + ("jpg", "\u{f1c5}"), // "ļ‡…" + ("js", "\u{e74e}"), // "īŽ" + ("json", "\u{e60b}"), // "ī˜‹" + ("jsx", "\u{e7ba}"), // "īžŗ" + ("jl", "\u{e624}"), // "ī˜¤" + ("key", "\u{e60a}"), // "ī˜Š" + ("ksh", "\u{f489}"), // "ļ’‰" + ("ld", "\u{e624}"), // "ī˜¤" + ("ldb", "\u{f1c0}"), // "ļ‡€" + ("less", "\u{e758}"), // "ī˜" + ("lhs", "\u{e777}"), // "ī·" + ("license", "\u{e60a}"), // "ī˜Š" + ("lisp", "\u{f671}"), // "ļ™±" + ("localized", "\u{f179}"), // "ļ…¹" + ("lock", "\u{f023}"), // "ļ€£" + ("log", "\u{f18d}"), // "ļ†" + ("lua", "\u{e620}"), // "ī˜ " + ("lz", "\u{f410}"), // "ļ" + ("m3u", "\u{f910}"), // "ļ¤" + ("m3u8", "\u{f910}"), // "ļ¤" + ("m4a", "\u{f001}"), // "ļ€" + ("m4v", "\u{f008}"), // "ļ€ˆ" + ("magnet", "\u{f076}"), // "ļ¶" + ("markdown", "\u{e609}"), // "ī˜‰" + ("md", "\u{e609}"), // "ī˜‰" + ("mjs", "\u{e74e}"), // "īŽ" + ("mkd", "\u{e609}"), // "ī˜‰" + ("mkv", "\u{f008}"), // "ļ€ˆ" + ("mobi", "\u{e28b}"), // "īŠ‹" + ("mov", "\u{f008}"), // "ļ€ˆ" + ("mp3", "\u{f001}"), // "ļ€" + ("mp4", "\u{f008}"), // "ļ€ˆ" + ("msi", "\u{f17a}"), // "ļ…ŗ" + ("mustache", "\u{e60f}"), // "ī˜" + ("nix", "\u{f313}"), // "ļŒ“" + ("npmignore", "\u{e71e}"), // "īœž" + ("o", "\u{e624}"), // "ī˜¤" + ("opus", "\u{f001}"), // "ļ€" + ("ogg", "\u{f001}"), // "ļ€" + ("ogv", "\u{f008}"), // "ļ€ˆ" + ("otf", "\u{f031}"), // "ļ€±" + ("pdf", "\u{f1c1}"), // "ļ‡" + ("pem", "\u{f805}"), // "ļ …" + ("phar", "\u{e608}"), // "ī˜ˆ" + ("php", "\u{e608}"), // "ī˜ˆ" + ("pkg", "\u{f187}"), // "ļ†‡" + ("pl", "\u{e769}"), // "ī©" + ("plist", "\u{f302}"), // "ļŒ‚" + ("pls", "\u{f910}"), // "ļ¤" + ("pm", "\u{e769}"), // "ī©" + ("png", "\u{f1c5}"), // "ļ‡…" + ("ppt", "\u{f1c4}"), // "ļ‡„" + ("pptx", "\u{f1c4}"), // "ļ‡„" + ("procfile", "\u{e21e}"), // "īˆž" + ("properties", "\u{e60b}"), // "ī˜‹" + ("ps1", "\u{f489}"), // "ļ’‰" + ("psd", "\u{e7b8}"), // "īžø" + ("pub", "\u{e60a}"), // "ī˜Š" + ("pxm", "\u{f1c5}"), // "ļ‡…" + ("py", "\u{e606}"), // "ī˜†" + ("pyc", "\u{e606}"), // "ī˜†" + ("r", "\u{fcd2}"), // "ļ³’" + ("rakefile", "\u{e21e}"), // "īˆž" + ("rar", "\u{f410}"), // "ļ" + ("razor", "\u{f1fa}"), // "ļ‡ŗ" + ("rb", "\u{e21e}"), // "īˆž" + ("rdata", "\u{fcd2}"), // "ļ³’" + ("rdb", "\u{e76d}"), // "ī­" + ("rdoc", "\u{e609}"), // "ī˜‰" + ("rds", "\u{fcd2}"), // "ļ³’" + ("readme", "\u{e609}"), // "ī˜‰" + ("rlib", "\u{e7a8}"), // "īžØ" + ("rmd", "\u{e609}"), // "ī˜‰" + ("rpm", "\u{f187}"), // "ļ†‡" + ("rproj", "\u{fac5}"), // "ļ«…" + ("rs", "\u{e7a8}"), // "īžØ" + ("rspec", "\u{e21e}"), // "īˆž" + ("rspec_parallel", "\u{e21e}"), // "īˆž" + ("rspec_status", "\u{e21e}"), // "īˆž" + ("rss", "\u{f09e}"), // "ļ‚ž" + ("rtf", "\u{f15c}"), // "ļ…œ" + ("ru", "\u{e21e}"), // "īˆž" + ("rubydoc", "\u{e73b}"), // "īœ»" + ("s", "\u{e614}"), // "ī˜”" + ("sass", "\u{e603}"), // "ī˜ƒ" + ("scala", "\u{e737}"), // "īœ·" + ("scpt", "\u{f302}"), // "ļŒ‚" + ("scss", "\u{e603}"), // "ī˜ƒ" + ("sh", "\u{f489}"), // "ļ’‰" + ("shell", "\u{f489}"), // "ļ’‰" + ("sig", "\u{e60a}"), // "ī˜Š" + ("slim", "\u{e73b}"), // "īœ»" + ("sln", "\u{e70c}"), // "īœŒ" + ("so", "\u{e624}"), // "ī˜¤" + ("sql", "\u{f1c0}"), // "ļ‡€" + ("sqlite3", "\u{e7c4}"), // "īŸ„" + ("srt", "\u{f02d}"), // "ļ€­" + ("styl", "\u{e600}"), // "ī˜€" + ("stylus", "\u{e600}"), // "ī˜€" + ("sub", "\u{f02d}"), // "ļ€­" + ("sublime-package", "\u{e7aa}"), // "īžŖ" + ("sublime-session", "\u{e7aa}"), // "īžŖ" + ("svg", "\u{f1c5}"), // "ļ‡…" + ("swift", "\u{e755}"), // "ī•" + ("swp", "\u{e62b}"), // "ī˜«" + ("sym", "\u{e624}"), // "ī˜¤" + ("t", "\u{e769}"), // "ī©" + ("tar", "\u{f410}"), // "ļ" + ("tex", "\u{e600}"), // "ī˜€" + ("tgz", "\u{f410}"), // "ļ" + ("tiff", "\u{f1c5}"), // "ļ‡…" + ("toml", "\u{e60b}"), // "ī˜‹" + ("torrent", "\u{f98c}"), // "ļ¦Œ" + ("ts", "\u{e628}"), // "ī˜Ø" + ("tsx", "\u{e7ba}"), // "īžŗ" + ("ttc", "\u{f031}"), // "ļ€±" + ("ttf", "\u{f031}"), // "ļ€±" + ("twig", "\u{e61c}"), // "ī˜œ" + ("txt", "\u{f15c}"), // "ļ…œ" + ("video", "\u{f008}"), // "ļ€ˆ" + ("vim", "\u{e62b}"), // "ī˜«" + ("vlc", "\u{f910}"), // "ļ¤" + ("vue", "\u{fd42}"), // "ļµ‚" + ("wav", "\u{f001}"), // "ļ€" + ("webm", "\u{f008}"), // "ļ€ˆ" + ("webp", "\u{f1c5}"), // "ļ‡…" + ("windows", "\u{f17a}"), // "ļ…ŗ" + ("wma", "\u{f001}"), // "ļ€" + ("wmv", "\u{f008}"), // "ļ€ˆ" + ("wpl", "\u{f910}"), // "ļ¤" + ("woff", "\u{f031}"), // "ļ€±" + ("woff2", "\u{f031}"), // "ļ€±" + ("xbps", "\u{f187}"), // "ļ†‡" + ("xcf", "\u{f1c5}"), // "ļ‡…" + ("xls", "\u{f1c3}"), // "ļ‡ƒ" + ("xlsx", "\u{f1c3}"), // "ļ‡ƒ" + ("xml", "\u{f121}"), // "ļ„”" + ("xul", "\u{f269}"), // "ļ‰©" + ("xz", "\u{f410}"), // "ļ" + ("yaml", "\u{e60b}"), // "ī˜‹" + ("yml", "\u{e60b}"), // "ī˜‹" + ("zip", "\u{f410}"), // "ļ" + ("zsh", "\u{f489}"), // "ļ’‰" + ("zsh-theme", "\u{f489}"), // "ļ’‰" + ("zshrc", "\u{f489}"), // "ļ’‰" + ("zst", "\u{f410}"), // "ļ" + ].iter().map(|&s| (s.0.to_owned(), s.1.to_owned())).collect::>() } } From 2e5bceb9f275ffecb32164343691f4a7338c4ec0 Mon Sep 17 00:00:00 2001 From: Wei Zhang Date: Sat, 30 Jul 2022 00:44:01 +0800 Subject: [PATCH 04/16] :sparkles: icon: icon theme functionally works --- src/color.rs | 9 ++-- src/core.rs | 9 ++-- src/flags/icons.rs | 3 +- src/icon.rs | 105 +++++++++++++++++++++++---------------------- src/theme.rs | 6 +-- src/theme/color.rs | 3 +- src/theme/icon.rs | 33 ++++++++++---- 7 files changed, 92 insertions(+), 76 deletions(-) diff --git a/src/color.rs b/src/color.rs index db4f1406e..21c80751b 100644 --- a/src/color.rs +++ b/src/color.rs @@ -3,9 +3,8 @@ use crossterm::style::{Attribute, ContentStyle, StyledContent, Stylize}; use lscolors::{Indicator, LsColors}; use std::path::Path; -use crate::theme::{Theme,color::ColorTheme}; pub use crate::flags::color::ThemeOption; - +use crate::theme::{color::ColorTheme, Theme}; #[allow(dead_code)] #[derive(Hash, Debug, Eq, PartialEq, Clone)] @@ -138,7 +137,9 @@ impl Colors { let theme = match t { ThemeOption::NoColor => None, ThemeOption::Default | ThemeOption::NoLscolors => Some(Theme::default().color), - ThemeOption::Custom(ref file) => Some(Theme::from_path::(file).unwrap_or_default()), + ThemeOption::Custom(ref file) => { + Some(Theme::from_path::(file).unwrap_or_default()) + } }; let lscolors = match t { ThemeOption::Default | ThemeOption::Custom(_) => { @@ -299,8 +300,8 @@ fn to_content_style(ls: &lscolors::Style) -> ContentStyle { #[cfg(test)] mod tests { use super::Colors; - use crate::theme::color_theme::Theme; use crate::color::ThemeOption; + use crate::theme::color_theme::Theme; #[test] fn test_color_new_no_color_theme() { assert!(Colors::new(ThemeOption::NoColor).theme.is_none()); diff --git a/src/core.rs b/src/core.rs index 407d42c9d..ac5e4c6b1 100644 --- a/src/core.rs +++ b/src/core.rs @@ -47,11 +47,8 @@ impl Core { _ => flags.color.theme.clone(), }; - let icon_theme = match (tty_available, flags.icons.when, flags.icons.theme) { - (_, IconOption::Never, _) | (false, IconOption::Auto, _) => icon::Theme::NoIcon, - (_, _, IconTheme::Fancy) => icon::Theme::Fancy, - (_, _, IconTheme::Unicode) => icon::Theme::Unicode, - }; + let icon_when = flags.icons.when; + let icon_theme = flags.icons.theme.clone(); // TODO: Rework this so that flags passed downstream does not // have Auto option for any (icon, color, hyperlink). @@ -78,7 +75,7 @@ impl Core { Self { flags, colors: Colors::new(color_theme), - icons: Icons::new(icon_theme, icon_separator), + icons: Icons::new(tty_available, icon_when, icon_theme, icon_separator), sorters, } } diff --git a/src/flags/icons.rs b/src/flags/icons.rs index 481d37441..084d97a2c 100644 --- a/src/flags/icons.rs +++ b/src/flags/icons.rs @@ -90,12 +90,13 @@ impl Configurable for IconOption { } /// The flag showing which icon theme to use. -#[derive(Clone, Debug, Copy, PartialEq, Eq, Deserialize, Default)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum IconTheme { Unicode, #[default] Fancy, + Custom(String), } impl IconTheme { diff --git a/src/icon.rs b/src/icon.rs index b004f500e..dd3b9a856 100644 --- a/src/icon.rs +++ b/src/icon.rs @@ -1,14 +1,11 @@ -use std::collections::HashMap; - +use crate::flags::{IconOption, IconTheme as FlagTheme}; use crate::meta::{FileType, Name}; -use crate::flags::IconOption; -use crate::theme::{Theme, icon::IconTheme}; +use crate::theme::{icon::IconTheme, Theme}; pub struct Icons { - display_icons: bool, icon_separator: String, - theme: IconTheme, + theme: Option, } // In order to add a new icon, write the unicode value like "\ue5fb" then @@ -16,61 +13,65 @@ pub struct Icons { // // s#\\u[0-9a-f]*#\=eval('"'.submatch(0).'"')# impl Icons { - pub fn new(opt: IconOption, icon_separator: String) -> Self { - let display_icons = !(opt == IconOption::Never); // TODO(zwpaper): Auto + pub fn new(tty: bool, when: IconOption, theme: FlagTheme, icon_separator: String) -> Self { + let icon_theme = match (tty, when, theme) { + (_, IconOption::Never, _) | (false, IconOption::Auto, _) => None, + (_, _, FlagTheme::Fancy) => Some(IconTheme::default()), + (_, _, FlagTheme::Unicode) => Some(IconTheme::unicode()), + (_, _, FlagTheme::Custom(ref file)) => { + Some(Theme::from_path::(file).unwrap_or_default()) + } + }; Self { - display_icons, icon_separator, - theme: Theme::default().icon, + theme: icon_theme, } } pub fn get(&self, name: &Name) -> String { - if !self.display_icons { - return String::new(); - } - - // Check file types - let file_type: FileType = name.file_type(); - let icon = match file_type { - FileType::SymLink { is_dir: true } => "\u{f482}", // "ļ’‚" - FileType::SymLink { is_dir: false } => "\u{f481}", // "ļ’" - FileType::Socket => "\u{f6a7}", // "ļš§" - FileType::Pipe => "\u{f731}", // "ļœ±" - FileType::CharDevice => "\u{e601}", // "ī˜" - FileType::BlockDevice => "\u{fc29}", // "ļ°©" - FileType::Special => "\u{f2dc}", // "ļ‹œ" - _ => { - // Use the known names - if let Some(icon) = self - .theme - .icons_by_name - .get(name.file_name().to_lowercase().as_str()) - { - icon - } - // Use the known extensions - else if let Some(icon) = name.extension().and_then(|extension| { - self.theme - .icons_by_extension - .get(extension.to_lowercase().as_str()) - }) { - icon - } else { - match file_type { - FileType::Directory { .. } => &self.theme.default_folder_icon, - // If a file has no extension and is executable, show an icon. - // Except for Windows, it marks everything as an executable. - #[cfg(not(windows))] - FileType::File { exec: true, .. } => "\u{f489}", // "ļ’‰" - _ => &self.theme.default_file_icon, + match &self.theme { + None => String::new(), + Some(t) => { + // Check file types + let file_type: FileType = name.file_type(); + let icon = match file_type { + FileType::SymLink { is_dir: true } => "\u{f482}", // "ļ’‚" + FileType::SymLink { is_dir: false } => "\u{f481}", // "ļ’" + FileType::Socket => "\u{f6a7}", // "ļš§" + FileType::Pipe => "\u{f731}", // "ļœ±" + FileType::CharDevice => "\u{e601}", // "ī˜" + FileType::BlockDevice => "\u{fc29}", // "ļ°©" + FileType::Special => "\u{f2dc}", // "ļ‹œ" + _ => { + // Use the known names + if let Some(icon) = t + .icons_by_name + .get(name.file_name().to_lowercase().as_str()) + { + icon + } + // Use the known extensions + else if let Some(icon) = name.extension().and_then(|extension| { + t.icons_by_extension.get(extension.to_lowercase().as_str()) + }) { + icon + } else { + match file_type { + FileType::Directory { .. } => &t.default_folder_icon, + // If a file has no extension and is executable, show an icon. + // Except for Windows, it marks everything as an executable. + #[cfg(not(windows))] + FileType::File { exec: true, .. } => "\u{f489}", // "ļ’‰" + _ => &t.default_file_icon, + } + } } - } - } - }; + }; - format!("{}{}", icon, self.icon_separator) + format!("{}{}", icon, self.icon_separator) + } + } } } diff --git a/src/theme.rs b/src/theme.rs index 352395add..704e3edd4 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,9 +1,9 @@ pub mod color; pub mod icon; -use serde::{Deserialize, de::DeserializeOwned}; -use std::path::Path; +use serde::{de::DeserializeOwned, Deserialize}; use std::fs; +use std::path::Path; use crate::config_file; use crate::print_error; @@ -23,7 +23,7 @@ pub struct Theme { impl Default for Theme { fn default() -> Self { // TODO(zwpaper): check terminal color and return light or dark - Theme{ + Theme { color: ColorTheme::default(), icon: IconTheme::default(), } diff --git a/src/theme/color.rs b/src/theme/color.rs index 31e056931..1bdfe3bd8 100644 --- a/src/theme/color.rs +++ b/src/theme/color.rs @@ -6,7 +6,7 @@ use std::fmt; // Custom color deserialize fn deserialize_color<'de, D>(deserializer: D) -> Result - where +where D: serde::de::Deserializer<'de>, { struct ColorVisitor; @@ -78,7 +78,6 @@ fn deserialize_color<'de, D>(deserializer: D) -> Result deserializer.deserialize_any(ColorVisitor) } - /// A struct holding the theme configuration /// Color table: https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.avg #[derive(Debug, Deserialize, PartialEq, Eq)] diff --git a/src/theme/icon.rs b/src/theme/icon.rs index 96582c852..a11b8a32f 100644 --- a/src/theme/icon.rs +++ b/src/theme/icon.rs @@ -1,5 +1,5 @@ -use std::collections::HashMap; use serde::Deserialize; +use std::collections::HashMap; #[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] @@ -16,7 +16,7 @@ pub struct IconTheme { impl Default for IconTheme { fn default() -> Self { // TODO(zwpaper): check terminal color and return light or dark - IconTheme{ + IconTheme { icons_by_name: Self::get_default_icons_by_name(), icons_by_extension: Self::get_default_icons_by_extension(), default_folder_icon: "\u{f115}".into(), @@ -26,6 +26,16 @@ impl Default for IconTheme { } impl IconTheme { + pub fn unicode() -> Self { + // TODO(zwpaper): check terminal color and return light or dark + IconTheme { + icons_by_name: HashMap::new(), + icons_by_extension: HashMap::new(), + default_folder_icon: "\u{1f5cb}".into(), + default_file_icon: "\u{1f5c1}".into(), + } + } + fn get_default_icons_by_name() -> HashMap { // Note: filenames must be lower-case [ @@ -147,15 +157,19 @@ impl IconTheme { ("xmonad.hs", "\u{e615}"), // "ī˜•" ("xorg.conf.d", "\u{e5fc}"), // "ī—¼" ("xbps.d", "\u{e5fc}"), // "ī—¼" - ].iter().map(|&s| (s.0.to_owned(), s.1.to_owned())).collect::>() + ] + .iter() + .map(|&s| (s.0.to_owned(), s.1.to_owned())) + .collect::>() } fn get_default_icons_by_extension() -> HashMap { // Note: extensions must be lower-case - [("1", "\u{f02d}"), // "ļ€­" - ("7z", "\u{f410}"), // "ļ" - ("a", "\u{e624}"), // "ī˜¤" - ("ai", "\u{e7b4}"), // "īž“" + [ + ("1", "\u{f02d}"), // "ļ€­" + ("7z", "\u{f410}"), // "ļ" + ("a", "\u{e624}"), // "ī˜¤" + ("ai", "\u{e7b4}"), // "īž“" ("ape", "\u{f001}"), // "ļ€" ("apk", "\u{e70e}"), // "īœŽ" ("asc", "\u{f023}"), // "ļ€£" @@ -407,6 +421,9 @@ impl IconTheme { ("zsh-theme", "\u{f489}"), // "ļ’‰" ("zshrc", "\u{f489}"), // "ļ’‰" ("zst", "\u{f410}"), // "ļ" - ].iter().map(|&s| (s.0.to_owned(), s.1.to_owned())).collect::>() + ] + .iter() + .map(|&s| (s.0.to_owned(), s.1.to_owned())) + .collect::>() } } From fe2fb32b4d41535b1ad5bdfe811380d53edd2931 Mon Sep 17 00:00:00 2001 From: Wei Zhang Date: Sat, 30 Jul 2022 01:11:02 +0800 Subject: [PATCH 05/16] :hammer: args: allow custom theme for cmd args --- src/app.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app.rs b/src/app.rs index 1e9357f65..9a65facd3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -53,8 +53,6 @@ pub fn build() -> App<'static> { .arg( Arg::with_name("icon-theme") .long("icon-theme") - .possible_value("fancy") - .possible_value("unicode") .default_value("fancy") .multiple_occurrences(true) .takes_value(true) From a44001f1ade034fccbdfba441425c32eb35e8672 Mon Sep 17 00:00:00 2001 From: Wei Zhang Date: Sun, 31 Jul 2022 17:06:30 +0800 Subject: [PATCH 06/16] :hammer: icon: refactor to use icon by file type --- src/core.rs | 7 ++---- src/flags/icons.rs | 2 +- src/icon.rs | 32 +++++++++++------------ src/theme.rs | 12 +-------- src/theme/icon.rs | 63 +++++++++++++++++++++++++++++++++++++++------- 5 files changed, 74 insertions(+), 42 deletions(-) diff --git a/src/core.rs b/src/core.rs index ac5e4c6b1..231437490 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,10 +1,7 @@ use crate::color::Colors; use crate::display; -use crate::flags::{ - ColorOption, Display, Flags, HyperlinkOption, IconOption, IconTheme, Layout, SortOrder, - ThemeOption, -}; -use crate::icon::{self, Icons}; +use crate::flags::{ColorOption, Display, Flags, HyperlinkOption, Layout, SortOrder, ThemeOption}; +use crate::icon::Icons; use crate::meta::Meta; use crate::{print_error, print_output, sort, ExitCode}; use std::path::PathBuf; diff --git a/src/flags/icons.rs b/src/flags/icons.rs index 084d97a2c..638c92ed9 100644 --- a/src/flags/icons.rs +++ b/src/flags/icons.rs @@ -90,7 +90,7 @@ impl Configurable for IconOption { } /// The flag showing which icon theme to use. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum IconTheme { Unicode, diff --git a/src/icon.rs b/src/icon.rs index dd3b9a856..cb8b4f97b 100644 --- a/src/icon.rs +++ b/src/icon.rs @@ -36,34 +36,34 @@ impl Icons { // Check file types let file_type: FileType = name.file_type(); let icon = match file_type { - FileType::SymLink { is_dir: true } => "\u{f482}", // "ļ’‚" - FileType::SymLink { is_dir: false } => "\u{f481}", // "ļ’" - FileType::Socket => "\u{f6a7}", // "ļš§" - FileType::Pipe => "\u{f731}", // "ļœ±" - FileType::CharDevice => "\u{e601}", // "ī˜" - FileType::BlockDevice => "\u{fc29}", // "ļ°©" - FileType::Special => "\u{f2dc}", // "ļ‹œ" + FileType::SymLink { is_dir: true } => &t.icons_by_filetype.symlink_dir, + FileType::SymLink { is_dir: false } => &t.icons_by_filetype.symlink_file, + FileType::Socket => &t.icons_by_filetype.socket, + FileType::Pipe => &t.icons_by_filetype.pipe, + FileType::CharDevice => &t.icons_by_filetype.device_char, + FileType::BlockDevice => &t.icons_by_filetype.device_block, + FileType::Special => &t.icons_by_filetype.special, _ => { - // Use the known names if let Some(icon) = t .icons_by_name .get(name.file_name().to_lowercase().as_str()) { icon - } - // Use the known extensions - else if let Some(icon) = name.extension().and_then(|extension| { - t.icons_by_extension.get(extension.to_lowercase().as_str()) - }) { + } else if let Some(icon) = name + .extension() + .and_then(|ext| t.icons_by_extension.get(ext.to_lowercase().as_str())) + { icon } else { match file_type { - FileType::Directory { .. } => &t.default_folder_icon, + FileType::Directory { .. } => &t.icons_by_filetype.dir, // If a file has no extension and is executable, show an icon. // Except for Windows, it marks everything as an executable. #[cfg(not(windows))] - FileType::File { exec: true, .. } => "\u{f489}", // "ļ’‰" - _ => &t.default_file_icon, + FileType::File { exec: true, .. } => { + &t.icons_by_filetype.executable + } + _ => &t.icons_by_filetype.file, } } } diff --git a/src/theme.rs b/src/theme.rs index 704e3edd4..c28419390 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -11,7 +11,7 @@ use crate::print_error; use color::ColorTheme; use icon::IconTheme; -#[derive(Debug, Deserialize, PartialEq)] +#[derive(Debug, Deserialize, Default, PartialEq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] #[serde(default)] @@ -20,16 +20,6 @@ pub struct Theme { pub icon: IconTheme, } -impl Default for Theme { - fn default() -> Self { - // TODO(zwpaper): check terminal color and return light or dark - Theme { - color: ColorTheme::default(), - icon: IconTheme::default(), - } - } -} - impl Theme { /// This read theme from file, /// use the file path if it is absolute diff --git a/src/theme/icon.rs b/src/theme/icon.rs index a11b8a32f..811f25f77 100644 --- a/src/theme/icon.rs +++ b/src/theme/icon.rs @@ -8,31 +8,76 @@ use std::collections::HashMap; pub struct IconTheme { pub icons_by_name: HashMap, pub icons_by_extension: HashMap, - // pub icons_by_filetype: HashMap, - pub default_folder_icon: String, - pub default_file_icon: String, + pub icons_by_filetype: IconByType, +} + +#[derive(Debug, Deserialize, PartialEq)] +#[serde(rename_all = "kebab-case")] +#[serde(deny_unknown_fields)] +#[serde(default)] +pub struct IconByType { + pub dir: String, + pub file: String, + pub pipe: String, + pub socket: String, + pub executable: String, + pub device_char: String, + pub device_block: String, + pub special: String, + pub symlink_dir: String, + pub symlink_file: String, } impl Default for IconTheme { fn default() -> Self { - // TODO(zwpaper): check terminal color and return light or dark IconTheme { icons_by_name: Self::get_default_icons_by_name(), icons_by_extension: Self::get_default_icons_by_extension(), - default_folder_icon: "\u{f115}".into(), - default_file_icon: "\u{f016}".into(), + icons_by_filetype: IconByType::default(), + } + } +} + +impl Default for IconByType { + fn default() -> IconByType { + IconByType { + dir: "\u{f115}".into(), // ļ„• + file: "\u{f016}".into(), // ļ€– + pipe: "\u{f731}".into(), // ļœ± + socket: "\u{f6a7}".into(), // ļš§ + executable: "\u{f489}".into(), // ļ’‰ + symlink_dir: "\u{f482}".into(), // ļ’‚ + symlink_file: "\u{f481}".into(), // ļ’ + device_char: "\u{e601}".into(), // ī˜ + device_block: "\u{fc29}".into(), // ļ°© + special: "\u{f2dc}".into(), // ļ‹œ + } + } +} + +impl IconByType { + pub fn unicode() -> Self { + IconByType { + dir: "\u{1f4c2}".into(), + file: "\u{1f4c4}".into(), + pipe: "\u{1f4e9}".into(), + socket: "\u{1f4ec}".into(), + executable: "\u{1f3d7}".into(), + symlink_dir: "\u{1f5c2}".into(), + symlink_file: "\u{1f516}".into(), + device_char: "\u{1f5a8}".into(), + device_block: "\u{1f4bd}".into(), + special: "\u{1f4df}".into(), } } } impl IconTheme { pub fn unicode() -> Self { - // TODO(zwpaper): check terminal color and return light or dark IconTheme { icons_by_name: HashMap::new(), icons_by_extension: HashMap::new(), - default_folder_icon: "\u{1f5cb}".into(), - default_file_icon: "\u{1f5c1}".into(), + icons_by_filetype: IconByType::unicode(), } } From 55890fefea1cae0efb348552b493fa220cd11ad0 Mon Sep 17 00:00:00 2001 From: zwPapEr Date: Fri, 5 Aug 2022 14:53:11 +0800 Subject: [PATCH 07/16] :fire: icon: drop custom icon file option Signed-off-by: zwPapEr --- Cargo.lock | 1 + Cargo.toml | 1 + src/app.rs | 2 ++ src/flags/icons.rs | 1 - src/icon.rs | 12 +++++---- src/theme.rs | 62 +++++++++++++++++++++++++--------------------- 6 files changed, 45 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 62665b1c1..872812e8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -435,6 +435,7 @@ dependencies = [ "tempfile", "term_grid", "terminal_size", + "thiserror", "unicode-width", "url", "users", diff --git a/Cargo.toml b/Cargo.toml index 0f6807e75..3e4421a1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ libc = "0.2.*" human-sort = "0.2.2" term_grid = "0.1.*" terminal_size = "0.1.*" +thiserror = "1.0" chrono = "0.4.*" chrono-humanize = "0.1.*" unicode-width = "0.1.*" diff --git a/src/app.rs b/src/app.rs index 9a65facd3..971d01353 100644 --- a/src/app.rs +++ b/src/app.rs @@ -54,6 +54,8 @@ pub fn build() -> App<'static> { Arg::with_name("icon-theme") .long("icon-theme") .default_value("fancy") + .possible_value("fancy") + .possible_value("unicode") .multiple_occurrences(true) .takes_value(true) .number_of_values(1) diff --git a/src/flags/icons.rs b/src/flags/icons.rs index 638c92ed9..fec9aaca1 100644 --- a/src/flags/icons.rs +++ b/src/flags/icons.rs @@ -96,7 +96,6 @@ pub enum IconTheme { Unicode, #[default] Fancy, - Custom(String), } impl IconTheme { diff --git a/src/icon.rs b/src/icon.rs index cb8b4f97b..a23e9b60e 100644 --- a/src/icon.rs +++ b/src/icon.rs @@ -4,7 +4,6 @@ use crate::theme::{icon::IconTheme, Theme}; pub struct Icons { icon_separator: String, - theme: Option, } @@ -16,11 +15,14 @@ impl Icons { pub fn new(tty: bool, when: IconOption, theme: FlagTheme, icon_separator: String) -> Self { let icon_theme = match (tty, when, theme) { (_, IconOption::Never, _) | (false, IconOption::Auto, _) => None, - (_, _, FlagTheme::Fancy) => Some(IconTheme::default()), + (_, _, FlagTheme::Fancy) => { + if let Ok(t) = Theme::from_path::("icons") { + Some(t) + } else { + Some(IconTheme::default()) + } + }, (_, _, FlagTheme::Unicode) => Some(IconTheme::unicode()), - (_, _, FlagTheme::Custom(ref file)) => { - Some(Theme::from_path::(file).unwrap_or_default()) - } }; Self { diff --git a/src/theme.rs b/src/theme.rs index c28419390..ae0cdcc9e 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,9 +1,11 @@ pub mod color; pub mod icon; -use serde::{de::DeserializeOwned, Deserialize}; -use std::fs; use std::path::Path; +use std::{fs, io}; + +use serde::{de::DeserializeOwned, Deserialize}; +use thiserror::Error; use crate::config_file; use crate::print_error; @@ -20,49 +22,53 @@ pub struct Theme { pub icon: IconTheme, } +#[derive(Error, Debug)] +pub enum Error { + #[error("Theme file not existed")] + NotExisted(#[from] io::Error), + #[error("Theme file format invalid")] + InvalidFormat(#[from] serde_yaml::Error), + #[error("Theme file path invalid {0}")] + InvalidPath(String), + #[error("Unknown Theme error")] + Unknown(), +} + impl Theme { /// This read theme from file, /// use the file path if it is absolute /// prefix the config_file dir to it if it is not - pub fn from_path(file: &str) -> Option { + pub fn from_path(file: &str) -> Result { let real = if let Some(path) = config_file::Config::expand_home(file) { path } else { print_error!("Not a valid theme file path: {}.", &file); - return None; + return Err(Error::InvalidPath(file.to_string())); }; let path = if Path::new(&real).is_absolute() { real } else { - config_file::Config::config_file_path()? - .join("themes") - .join(real) + match config_file::Config::config_file_path() { + Some(p) => p.join("themes").join(real), + None => return Err(Error::InvalidPath("config home not existed".into())), + } }; - match fs::read(&path.with_extension("yaml")) { - Ok(f) => match Self::with_yaml(&String::from_utf8_lossy(&f)) { - Ok(t) => Some(t), - Err(e) => { - print_error!("Theme file {} format error: {}.", &file, e); - None - } - }, - Err(_) => { - // try `yml` if `yaml` extension file not found - match fs::read(&path.with_extension("yml")) { - Ok(f) => match Self::with_yaml(&String::from_utf8_lossy(&f)) { - Ok(t) => Some(t), - Err(e) => { - print_error!("Theme file {} format error: {}.", &file, e); - None - } - }, + + // try `yml` if `yaml` extension file not found or error + let mut err: Error = Error::Unknown(); + for ext in ["yaml", "yml"] { + match fs::read(&path.with_extension(ext)) { + Ok(f) => match Self::with_yaml(&String::from_utf8_lossy(&f)) { + Ok(t) => return Ok(t), Err(e) => { - print_error!("Not a valid theme: {}, {}.", path.to_string_lossy(), e); - None + err = Error::from(e); } - } + }, + Err(e) => err = Error::from(e), } } + + Err(err) } /// This constructs a Theme struct with a passed [Yaml] str. From 00a6a9946d7d612b2b6b46d03bdb082ba6e501d1 Mon Sep 17 00:00:00 2001 From: zwPapEr Date: Tue, 9 Aug 2022 14:16:57 +0800 Subject: [PATCH 08/16] :mag: :hammer: fix tests to fit icon theme Signed-off-by: zwPapEr Signed-off-by: Wei Zhang --- src/color.rs | 30 +++++++++++++++--------------- src/display.rs | 24 ++++++++++++------------ src/flags/icons.rs | 2 +- src/icon.rs | 39 +++++++++++++++++++++++++-------------- src/meta/name.rs | 26 +++++++++++++------------- src/theme.rs | 2 +- src/theme/color.rs | 23 ++++++++++++----------- src/theme/icon.rs | 10 ++++++---- 8 files changed, 85 insertions(+), 71 deletions(-) diff --git a/src/color.rs b/src/color.rs index 21c80751b..893885eb3 100644 --- a/src/color.rs +++ b/src/color.rs @@ -301,7 +301,7 @@ fn to_content_style(ls: &lscolors::Style) -> ContentStyle { mod tests { use super::Colors; use crate::color::ThemeOption; - use crate::theme::color_theme::Theme; + use crate::theme::color::ColorTheme; #[test] fn test_color_new_no_color_theme() { assert!(Colors::new(ThemeOption::NoColor).theme.is_none()); @@ -311,7 +311,7 @@ mod tests { fn test_color_new_default_theme() { assert_eq!( Colors::new(ThemeOption::Default).theme, - Some(Theme::default_dark()), + Some(ColorTheme::default_dark()), ); } @@ -319,7 +319,7 @@ mod tests { fn test_color_new_bad_custom_theme() { assert_eq!( Colors::new(ThemeOption::Custom("not-existed".to_string())).theme, - Some(Theme::default_dark()), + Some(ColorTheme::default_dark()), ); } } @@ -327,15 +327,15 @@ mod tests { #[cfg(test)] mod elem { use super::Elem; - use crate::color::{theme, Theme}; + use crate::theme::{color, color::ColorTheme}; use crossterm::style::Color; #[cfg(test)] - fn test_theme() -> Theme { - Theme { + fn test_theme() -> ColorTheme { + ColorTheme { user: Color::AnsiValue(230), // Cornsilk1 group: Color::AnsiValue(187), // LightYellow3 - permission: theme::Permission { + permission: color::Permission { read: Color::Green, write: Color::Yellow, exec: Color::Red, @@ -345,19 +345,19 @@ mod elem { acl: Color::DarkCyan, context: Color::Cyan, }, - file_type: theme::FileType { - file: theme::File { + file_type: color::FileType { + file: color::File { exec_uid: Color::AnsiValue(40), // Green3 uid_no_exec: Color::AnsiValue(184), // Yellow3 exec_no_uid: Color::AnsiValue(40), // Green3 no_exec_no_uid: Color::AnsiValue(184), // Yellow3 }, - dir: theme::Dir { + dir: color::Dir { uid: Color::AnsiValue(33), // DodgerBlue1 no_uid: Color::AnsiValue(33), // DodgerBlue1 }, pipe: Color::AnsiValue(44), // DarkTurquoise - symlink: theme::Symlink { + symlink: color::Symlink { default: Color::AnsiValue(44), // DarkTurquoise broken: Color::AnsiValue(124), // Red3 missing_target: Color::AnsiValue(124), // Red3 @@ -367,22 +367,22 @@ mod elem { socket: Color::AnsiValue(44), // DarkTurquoise special: Color::AnsiValue(44), // DarkTurquoise }, - date: theme::Date { + date: color::Date { hour_old: Color::AnsiValue(40), // Green3 day_old: Color::AnsiValue(42), // SpringGreen2 older: Color::AnsiValue(36), // DarkCyan }, - size: theme::Size { + size: color::Size { none: Color::AnsiValue(245), // Grey small: Color::AnsiValue(229), // Wheat1 medium: Color::AnsiValue(216), // LightSalmon1 large: Color::AnsiValue(172), // Orange3 }, - inode: theme::INode { + inode: color::INode { valid: Color::AnsiValue(13), // Pink invalid: Color::AnsiValue(245), // Grey }, - links: theme::Links { + links: color::Links { valid: Color::AnsiValue(13), // Pink invalid: Color::AnsiValue(245), // Grey }, diff --git a/src/display.rs b/src/display.rs index 21139a36f..83697ec2c 100644 --- a/src/display.rs +++ b/src/display.rs @@ -408,11 +408,11 @@ mod tests { use super::*; use crate::color; use crate::color::Colors; - use crate::flags::HyperlinkOption; + use crate::flags::{HyperlinkOption, IconOption, IconTheme as FlagTheme}; use crate::icon::Icons; use crate::meta::{FileType, Name}; use crate::Config; - use crate::{app, flags, icon, sort}; + use crate::{app, flags, sort}; use assert_fs::prelude::*; use std::path::Path; @@ -438,7 +438,7 @@ mod tests { let output = name .render( &Colors::new(color::ThemeOption::NoColor), - &Icons::new(icon::Theme::NoIcon, " ".to_string()), + &Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()), &DisplayOption::FileName, HyperlinkOption::Never, ) @@ -472,7 +472,7 @@ mod tests { let output = name .render( &Colors::new(color::ThemeOption::NoColor), - &Icons::new(icon::Theme::Fancy, " ".to_string()), + &Icons::new(false, IconOption::Always, FlagTheme::Fancy, " ".to_string()), &DisplayOption::FileName, HyperlinkOption::Never, ) @@ -505,7 +505,7 @@ mod tests { let output = name .render( &Colors::new(color::ThemeOption::NoLscolors), - &Icons::new(icon::Theme::NoIcon, " ".to_string()), + &Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()), &DisplayOption::FileName, HyperlinkOption::Never, ) @@ -546,7 +546,7 @@ mod tests { let output = name .render( &Colors::new(color::ThemeOption::NoColor), - &Icons::new(icon::Theme::NoIcon, " ".to_string()), + &Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()), &DisplayOption::FileName, HyperlinkOption::Never, ) @@ -609,7 +609,7 @@ mod tests { &metas, &flags, &Colors::new(color::ThemeOption::NoColor), - &Icons::new(icon::Theme::NoIcon, " ".to_string()), + &Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()), ); assert_eq!("one.d\nā”œā”€ā”€ .hidden\nā””ā”€ā”€ two\n", output); @@ -640,7 +640,7 @@ mod tests { &metas, &flags, &Colors::new(color::ThemeOption::NoColor), - &Icons::new(icon::Theme::NoIcon, " ".to_string()), + &Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()), ); let length_before_b = |i| -> usize { @@ -680,7 +680,7 @@ mod tests { &metas, &flags, &Colors::new(color::ThemeOption::NoColor), - &Icons::new(icon::Theme::NoIcon, " ".to_string()), + &Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()), ); assert_eq!(output.lines().nth(1).unwrap().chars().next().unwrap(), 'ā””'); @@ -719,7 +719,7 @@ mod tests { &metas, &flags, &Colors::new(color::ThemeOption::NoColor), - &Icons::new(icon::Theme::NoIcon, " ".to_string()), + &Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()), ); assert!(output.ends_with("ā””ā”€ā”€ two\n")); @@ -749,7 +749,7 @@ mod tests { &metas, &flags, &Colors::new(color::ThemeOption::NoColor), - &Icons::new(icon::Theme::NoIcon, " ".to_string()), + &Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()), ); dir.close().unwrap(); @@ -782,7 +782,7 @@ mod tests { &metas, &flags, &Colors::new(color::ThemeOption::NoColor), - &Icons::new(icon::Theme::NoIcon, " ".to_string()), + &Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()), ); dir.close().unwrap(); diff --git a/src/flags/icons.rs b/src/flags/icons.rs index fec9aaca1..5db45885e 100644 --- a/src/flags/icons.rs +++ b/src/flags/icons.rs @@ -131,7 +131,7 @@ impl Configurable for IconTheme { /// this returns its corresponding variant in a [Some]. /// Otherwise this returns [None]. fn from_config(config: &Config) -> Option { - config.icons.as_ref().and_then(|icon| icon.theme) + config.icons.as_ref().and_then(|icon| icon.theme.clone()) } } diff --git a/src/icon.rs b/src/icon.rs index a23e9b60e..20a8b32c0 100644 --- a/src/icon.rs +++ b/src/icon.rs @@ -21,7 +21,7 @@ impl Icons { } else { Some(IconTheme::default()) } - }, + } (_, _, FlagTheme::Unicode) => Some(IconTheme::unicode()), }; @@ -79,7 +79,8 @@ impl Icons { #[cfg(test)] mod test { - use super::{Icons, Theme}; + use super::{IconTheme, Icons}; + use crate::flags::{IconOption, IconTheme as FlagTheme}; use crate::meta::Meta; use std::fs::File; use tempfile::tempdir; @@ -91,7 +92,7 @@ mod test { File::create(&file_path).expect("failed to create file"); let meta = Meta::from_path(&file_path, false).unwrap(); - let icon = Icons::new(Theme::NoIcon, " ".to_string()); + let icon = Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()); let icon = icon.get(&meta.name); assert_eq!(icon, ""); @@ -104,7 +105,7 @@ mod test { File::create(&file_path).expect("failed to create file"); let meta = Meta::from_path(&file_path, false).unwrap(); - let icon = Icons::new(Theme::Fancy, " ".to_string()); + let icon = Icons::new(false, IconOption::Always, FlagTheme::Fancy, " ".to_string()); let icon_str = icon.get(&meta.name); assert_eq!(icon_str, "\u{f016} "); // ļ€– @@ -117,10 +118,15 @@ mod test { File::create(&file_path).expect("failed to create file"); let meta = Meta::from_path(&file_path, false).unwrap(); - let icon = Icons::new(Theme::Unicode, " ".to_string()); + let icon = Icons::new( + false, + IconOption::Always, + FlagTheme::Unicode, + " ".to_string(), + ); let icon_str = icon.get(&meta.name); - assert_eq!(icon_str, "\u{1f5cb} "); + assert_eq!(icon_str, format!("{}{}", "\u{1f4c4}", icon.icon_separator)); } #[test] @@ -129,7 +135,7 @@ mod test { let file_path = tmp_dir.path(); let meta = Meta::from_path(file_path, false).unwrap(); - let icon = Icons::new(Theme::Fancy, " ".to_string()); + let icon = Icons::new(false, IconOption::Always, FlagTheme::Fancy, " ".to_string()); let icon_str = icon.get(&meta.name); assert_eq!(icon_str, "\u{f115} "); // ļ„• @@ -141,10 +147,15 @@ mod test { let file_path = tmp_dir.path(); let meta = Meta::from_path(file_path, false).unwrap(); - let icon = Icons::new(Theme::Unicode, " ".to_string()); + let icon = Icons::new( + false, + IconOption::Always, + FlagTheme::Unicode, + " ".to_string(), + ); let icon_str = icon.get(&meta.name); - assert_eq!(icon_str, "\u{1f5c1} "); + assert_eq!(icon_str, format!("{}{}", "\u{1f4c2}", icon.icon_separator)); } #[test] @@ -153,7 +164,7 @@ mod test { let file_path = tmp_dir.path(); let meta = Meta::from_path(file_path, false).unwrap(); - let icon = Icons::new(Theme::Fancy, " ".to_string()); + let icon = Icons::new(false, IconOption::Always, FlagTheme::Fancy, " ".to_string()); let icon_str = icon.get(&meta.name); assert_eq!(icon_str, "\u{f115} "); // ļ„• @@ -163,12 +174,12 @@ mod test { fn get_icon_by_name() { let tmp_dir = tempdir().expect("failed to create temp dir"); - for (file_name, file_icon) in &Icons::get_default_icons_by_name() { + for (file_name, file_icon) in &IconTheme::get_default_icons_by_name() { let file_path = tmp_dir.path().join(file_name); File::create(&file_path).expect("failed to create file"); let meta = Meta::from_path(&file_path, false).unwrap(); - let icon = Icons::new(Theme::Fancy, " ".to_string()); + let icon = Icons::new(false, IconOption::Always, FlagTheme::Fancy, " ".to_string()); let icon_str = icon.get(&meta.name); assert_eq!(icon_str, format!("{}{}", file_icon, icon.icon_separator)); @@ -179,12 +190,12 @@ mod test { fn get_icon_by_extension() { let tmp_dir = tempdir().expect("failed to create temp dir"); - for (ext, file_icon) in &Icons::get_default_icons_by_extension() { + for (ext, file_icon) in &IconTheme::get_default_icons_by_extension() { let file_path = tmp_dir.path().join(format!("file.{}", ext)); File::create(&file_path).expect("failed to create file"); let meta = Meta::from_path(&file_path, false).unwrap(); - let icon = Icons::use Theme::Fancy, " ".to_string()); + let icon = Icons::new(false, IconOption::Always, FlagTheme::Fancy, " ".to_string()); let icon_str = icon.get(&meta.name); assert_eq!(icon_str, format!("{}{}", file_icon, icon.icon_separator)); diff --git a/src/meta/name.rs b/src/meta/name.rs index a9e092ca9..584046acd 100644 --- a/src/meta/name.rs +++ b/src/meta/name.rs @@ -216,8 +216,8 @@ mod test { use super::DisplayOption; use super::Name; use crate::color::{self, Colors}; - use crate::flags::HyperlinkOption; - use crate::icon::{self, Icons}; + use crate::flags::{HyperlinkOption, IconOption, IconTheme as FlagTheme}; + use crate::icon::Icons; use crate::meta::FileType; use crate::meta::Meta; #[cfg(unix)] @@ -237,7 +237,7 @@ mod test { #[cfg(unix)] // Windows uses different default permissions fn test_print_file_name() { let tmp_dir = tempdir().expect("failed to create temp dir"); - let icons = Icons::new(icon::Theme::Fancy, " ".to_string()); + let icons = Icons::new(false, IconOption::Always, FlagTheme::Fancy, " ".to_string()); // Create the file; let file_path = tmp_dir.path().join("file.txt"); @@ -262,7 +262,7 @@ mod test { #[test] fn test_print_dir_name() { let tmp_dir = tempdir().expect("failed to create temp dir"); - let icons = Icons::new(icon::Theme::Fancy, " ".to_string()); + let icons = &Icons::new(false, IconOption::Always, FlagTheme::Fancy, " ".to_string()); // Create the directory let dir_path = tmp_dir.path().join("directory"); @@ -275,7 +275,7 @@ mod test { "ļ„• directory".to_string().with(Color::AnsiValue(33)), meta.name.render( &colors, - &icons, + icons, &DisplayOption::FileName, HyperlinkOption::Never ) @@ -286,7 +286,7 @@ mod test { #[cfg(unix)] // Symlinks are hard on Windows fn test_print_symlink_name_file() { let tmp_dir = tempdir().expect("failed to create temp dir"); - let icons = Icons::new(icon::Theme::Fancy, " ".to_string()); + let icons = &Icons::new(false, IconOption::Always, FlagTheme::Fancy, " ".to_string()); // Create the file; let file_path = tmp_dir.path().join("file.tmp"); @@ -308,7 +308,7 @@ mod test { "ļ’ target.tmp".to_string().with(Color::AnsiValue(44)), name.render( &colors, - &icons, + icons, &DisplayOption::FileName, HyperlinkOption::Never ) @@ -319,7 +319,7 @@ mod test { #[cfg(unix)] // Symlinks are hard on Windows fn test_print_symlink_name_dir() { let tmp_dir = tempdir().expect("failed to create temp dir"); - let icons = Icons::new(icon::Theme::Fancy, " ".to_string()); + let icons = Icons::new(false, IconOption::Always, FlagTheme::Fancy, " ".to_string()); // Create the directory; let dir_path = tmp_dir.path().join("tmp.d"); @@ -352,7 +352,7 @@ mod test { #[cfg(unix)] fn test_print_other_type_name() { let tmp_dir = tempdir().expect("failed to create temp dir"); - let icons = Icons::new(icon::Theme::Fancy, " ".to_string()); + let icons = &Icons::new(false, IconOption::Always, FlagTheme::Fancy, " ".to_string()); // Create the pipe; let pipe_path = tmp_dir.path().join("pipe.tmp"); @@ -372,7 +372,7 @@ mod test { "ļœ± pipe.tmp".to_string().with(Color::AnsiValue(184)), name.render( &colors, - &icons, + icons, &DisplayOption::FileName, HyperlinkOption::Never ) @@ -382,7 +382,7 @@ mod test { #[test] fn test_print_without_icon_or_color() { let tmp_dir = tempdir().expect("failed to create temp dir"); - let icons = Icons::new(icon::Theme::NoIcon, " ".to_string()); + let icons = Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()); // Create the file; let file_path = tmp_dir.path().join("file.txt"); @@ -407,7 +407,7 @@ mod test { #[test] fn test_print_hyperlink() { let tmp_dir = tempdir().expect("failed to create temp dir"); - let icons = Icons::new(icon::Theme::NoIcon, " ".to_string()); + let icons = Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()); // Create the file; let file_path = tmp_dir.path().join("file.txt"); @@ -633,7 +633,7 @@ mod test { #[cfg(unix)] fn test_special_chars_in_filename() { let tmp_dir = tempdir().expect("failed to create temp dir"); - let icons = Icons::new(icon::Theme::Fancy, " ".to_string()); + let icons = Icons::new(false, IconOption::Always, FlagTheme::Fancy, " ".to_string()); // Create the file; let file_path = tmp_dir.path().join("file\ttab.txt"); diff --git a/src/theme.rs b/src/theme.rs index ae0cdcc9e..843b11c59 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -13,7 +13,7 @@ use crate::print_error; use color::ColorTheme; use icon::IconTheme; -#[derive(Debug, Deserialize, Default, PartialEq)] +#[derive(Debug, Deserialize, Default, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] #[serde(default)] diff --git a/src/theme/color.rs b/src/theme/color.rs index 1bdfe3bd8..05ac7a0cc 100644 --- a/src/theme/color.rs +++ b/src/theme/color.rs @@ -379,13 +379,14 @@ tree-edge: 245 #[cfg(test)] mod tests { - use super::Theme; + use super::ColorTheme; + use crate::theme::Theme; #[test] fn test_default_theme() { assert_eq!( - Theme::default_dark(), - Theme::with_yaml(Theme::default_yaml()).unwrap() + ColorTheme::default_dark(), + Theme::with_yaml(ColorTheme::default_yaml()).unwrap() ); } @@ -396,10 +397,10 @@ mod tests { let dir = assert_fs::TempDir::new().unwrap(); let theme = dir.path().join("theme.yaml"); let mut file = File::create(&theme).unwrap(); - writeln!(file, "{}", Theme::default_yaml()).unwrap(); + writeln!(file, "{}", ColorTheme::default_yaml()).unwrap(); assert_eq!( - Theme::default_dark(), + ColorTheme::default_dark(), Theme::from_path(theme.to_str().unwrap()).unwrap() ); } @@ -408,8 +409,8 @@ mod tests { fn test_empty_theme_return_default() { // Must contain one field at least // ref https://github.com/dtolnay/serde-yaml/issues/86 - let empty_theme = Theme::with_yaml("user: 230").unwrap(); // 230 is the default value - let default_theme = Theme::default_dark(); + let empty_theme: ColorTheme = Theme::with_yaml("user: 230").unwrap(); // 230 is the default value + let default_theme = ColorTheme::default_dark(); assert_eq!(empty_theme, default_theme); } @@ -417,8 +418,8 @@ mod tests { fn test_first_level_theme_return_default_but_changed() { // Must contain one field at least // ref https://github.com/dtolnay/serde-yaml/issues/86 - let empty_theme = Theme::with_yaml("user: 130").unwrap(); - let mut theme = Theme::default_dark(); + let empty_theme: ColorTheme = Theme::with_yaml("user: 130").unwrap(); + let mut theme = ColorTheme::default_dark(); use crossterm::style::Color; theme.user = Color::AnsiValue(130); assert_eq!(empty_theme, theme); @@ -428,13 +429,13 @@ mod tests { fn test_second_level_theme_return_default_but_changed() { // Must contain one field at least // ref https://github.com/dtolnay/serde-yaml/issues/86 - let empty_theme = Theme::with_yaml( + let empty_theme: ColorTheme = Theme::with_yaml( r#"--- permission: read: 130"#, ) .unwrap(); - let mut theme = Theme::default_dark(); + let mut theme = ColorTheme::default_dark(); use crossterm::style::Color; theme.permission.read = Color::AnsiValue(130); assert_eq!(empty_theme, theme); diff --git a/src/theme/icon.rs b/src/theme/icon.rs index 811f25f77..35c257891 100644 --- a/src/theme/icon.rs +++ b/src/theme/icon.rs @@ -1,7 +1,7 @@ use serde::Deserialize; use std::collections::HashMap; -#[derive(Debug, Deserialize, PartialEq)] +#[derive(Debug, Deserialize, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] #[serde(default)] @@ -11,7 +11,7 @@ pub struct IconTheme { pub icons_by_filetype: IconByType, } -#[derive(Debug, Deserialize, PartialEq)] +#[derive(Debug, Deserialize, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] #[serde(default)] @@ -81,7 +81,8 @@ impl IconTheme { } } - fn get_default_icons_by_name() -> HashMap { + // pub only for testing in icons.rs + pub fn get_default_icons_by_name() -> HashMap { // Note: filenames must be lower-case [ (".trash", "\u{f1f8}"), // "ļ‡ø" @@ -208,7 +209,8 @@ impl IconTheme { .collect::>() } - fn get_default_icons_by_extension() -> HashMap { + // pub only for testing in icons.rs + pub fn get_default_icons_by_extension() -> HashMap { // Note: extensions must be lower-case [ ("1", "\u{f02d}"), // "ļ€­" From 61eeaaa8a3b0961e4650491a3136b5cd6e3b4c34 Mon Sep 17 00:00:00 2001 From: Wei Zhang Date: Fri, 19 Aug 2022 01:04:53 +0800 Subject: [PATCH 09/16] :mag: icon theme: add tests for icon theme Signed-off-by: Wei Zhang --- src/icon.rs | 76 ++++++++++++++++++++++++++++++++++------------ src/theme/color.rs | 19 ++++++------ src/theme/icon.rs | 72 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 29 deletions(-) diff --git a/src/icon.rs b/src/icon.rs index 20a8b32c0..d60a160cb 100644 --- a/src/icon.rs +++ b/src/icon.rs @@ -86,20 +86,70 @@ mod test { use tempfile::tempdir; #[test] - fn get_no_icon() { + fn get_no_icon_never_tty() { let tmp_dir = tempdir().expect("failed to create temp dir"); let file_path = tmp_dir.path().join("file.txt"); File::create(&file_path).expect("failed to create file"); let meta = Meta::from_path(&file_path, false).unwrap(); - let icon = Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()); - let icon = icon.get(&meta.name); + let icons = Icons::new(true, IconOption::Never, FlagTheme::Fancy, " ".to_string()); + let icon = icons.get(&meta.name); + + assert_eq!(icon, ""); + } + #[test] + fn get_no_icon_never_not_tty() { + let tmp_dir = tempdir().expect("failed to create temp dir"); + let file_path = tmp_dir.path().join("file.txt"); + File::create(&file_path).expect("failed to create file"); + let meta = Meta::from_path(&file_path, false).unwrap(); + + let icons = Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()); + let icon = icons.get(&meta.name); assert_eq!(icon, ""); } #[test] - fn get_default_file_icon() { + fn get_no_icon_auto() { + let tmp_dir = tempdir().expect("failed to create temp dir"); + let file_path = tmp_dir.path().join("file.txt"); + File::create(&file_path).expect("failed to create file"); + let meta = Meta::from_path(&file_path, false).unwrap(); + + let icons = Icons::new(false, IconOption::Auto, FlagTheme::Fancy, " ".to_string()); + let icon = icons.get(&meta.name); + + assert_eq!(icon, ""); + } + #[test] + fn get_icon_auto_tty() { + let tmp_dir = tempdir().expect("failed to create temp dir"); + let file_path = tmp_dir.path().join("file.txt"); + File::create(&file_path).expect("failed to create file"); + let meta = Meta::from_path(&file_path, false).unwrap(); + + let icons = Icons::new(true, IconOption::Auto, FlagTheme::Fancy, " ".to_string()); + let icon = icons.get(&meta.name); + + assert_eq!(icon, "\u{f15c} "); + } + + #[test] + fn get_icon_always_tty_default_file() { + let tmp_dir = tempdir().expect("failed to create temp dir"); + let file_path = tmp_dir.path().join("file"); + File::create(&file_path).expect("failed to create file"); + let meta = Meta::from_path(&file_path, false).unwrap(); + + let icon = Icons::new(true, IconOption::Always, FlagTheme::Fancy, " ".to_string()); + let icon_str = icon.get(&meta.name); + + assert_eq!(icon_str, "\u{f016} "); // ļ€– + } + + #[test] + fn get_icon_always_not_tty_default_file() { let tmp_dir = tempdir().expect("failed to create temp dir"); let file_path = tmp_dir.path().join("file"); File::create(&file_path).expect("failed to create file"); @@ -112,7 +162,7 @@ mod test { } #[test] - fn get_default_file_icon_unicode() { + fn get_icon_default_file_icon_unicode() { let tmp_dir = tempdir().expect("failed to create temp dir"); let file_path = tmp_dir.path().join("file"); File::create(&file_path).expect("failed to create file"); @@ -130,7 +180,7 @@ mod test { } #[test] - fn get_directory_icon() { + fn get_icon_default_directory() { let tmp_dir = tempdir().expect("failed to create temp dir"); let file_path = tmp_dir.path(); let meta = Meta::from_path(file_path, false).unwrap(); @@ -142,7 +192,7 @@ mod test { } #[test] - fn get_directory_icon_unicode() { + fn get_icon_default_directory_unicode() { let tmp_dir = tempdir().expect("failed to create temp dir"); let file_path = tmp_dir.path(); let meta = Meta::from_path(file_path, false).unwrap(); @@ -158,18 +208,6 @@ mod test { assert_eq!(icon_str, format!("{}{}", "\u{1f4c2}", icon.icon_separator)); } - #[test] - fn get_directory_icon_with_ext() { - let tmp_dir = tempdir().expect("failed to create temp dir"); - let file_path = tmp_dir.path(); - let meta = Meta::from_path(file_path, false).unwrap(); - - let icon = Icons::new(false, IconOption::Always, FlagTheme::Fancy, " ".to_string()); - let icon_str = icon.get(&meta.name); - - assert_eq!(icon_str, "\u{f115} "); // ļ„• - } - #[test] fn get_icon_by_name() { let tmp_dir = tempdir().expect("failed to create temp dir"); diff --git a/src/theme/color.rs b/src/theme/color.rs index 05ac7a0cc..2176791de 100644 --- a/src/theme/color.rs +++ b/src/theme/color.rs @@ -345,9 +345,14 @@ impl ColorTheme { tree_edge: Color::AnsiValue(245), // Grey } } +} + +#[cfg(test)] +mod tests { + use super::ColorTheme; + use crate::theme::Theme; - #[cfg(test)] - pub fn default_yaml() -> &'static str { + fn default_yaml() -> &'static str { r#"--- user: 230 group: 187 @@ -375,18 +380,12 @@ links: tree-edge: 245 "# } -} - -#[cfg(test)] -mod tests { - use super::ColorTheme; - use crate::theme::Theme; #[test] fn test_default_theme() { assert_eq!( ColorTheme::default_dark(), - Theme::with_yaml(ColorTheme::default_yaml()).unwrap() + Theme::with_yaml(default_yaml()).unwrap() ); } @@ -397,7 +396,7 @@ mod tests { let dir = assert_fs::TempDir::new().unwrap(); let theme = dir.path().join("theme.yaml"); let mut file = File::create(&theme).unwrap(); - writeln!(file, "{}", ColorTheme::default_yaml()).unwrap(); + writeln!(file, "{}", default_yaml()).unwrap(); assert_eq!( ColorTheme::default_dark(), diff --git a/src/theme/icon.rs b/src/theme/icon.rs index 35c257891..2ec2cef08 100644 --- a/src/theme/icon.rs +++ b/src/theme/icon.rs @@ -474,3 +474,75 @@ impl IconTheme { .collect::>() } } + +#[cfg(test)] +mod tests { + use super::IconTheme; + use crate::theme::Theme; + + fn partial_default_yaml() -> &'static str { + r#"--- +icons-by-name: + .trash: ļ‡ø + .cargo: īžØ + .emacs.d: ī¹ + a.out: ļ’‰ +icons-by-extension: + go: ī˜§ + hs: ī· + rs: īžØ +icons-by-filetype: + dir: ļ„• + file: ļ€– + pipe: ļœ± + socket: ļš§ + executable: ļ’‰ + symlink-dir: ļ’‚ + symlink-file: ļ’ + device-char: ī˜ + device-block: ļ°© + special: ļ‹œ +"# + } + + fn check_partial_yaml(def: &IconTheme, yaml: &IconTheme) { + assert_eq!(def.icons_by_filetype.dir, yaml.icons_by_filetype.dir,); + } + + #[test] + fn test_default_theme() { + let def = IconTheme::default(); + let yaml = Theme::with_yaml(partial_default_yaml()).unwrap(); + check_partial_yaml(&def, &yaml); + } + + #[test] + fn test_tmp_partial_default_theme_file() { + use std::fs::File; + use std::io::Write; + let dir = assert_fs::TempDir::new().unwrap(); + let theme = dir.path().join("icon.yaml"); + let mut file = File::create(&theme).unwrap(); + writeln!(file, "{}", partial_default_yaml()).unwrap(); + let def = IconTheme::default(); + let decoded = Theme::from_path(theme.to_str().unwrap()).unwrap(); + check_partial_yaml(&def, &decoded); + } + + #[test] + fn test_empty_theme_return_default() { + // Must contain one field at least + // ref https://github.com/dtolnay/serde-yaml/issues/86 + let empty: IconTheme = Theme::with_yaml("icons-by-filetype:\n dir: ļ„•").unwrap(); // ļ„• is the default value + let default = IconTheme::default(); + check_partial_yaml(&empty, &default); + } + + #[test] + fn test_serde_dir_from_yaml() { + // Must contain one field at least + // ref https://github.com/dtolnay/serde-yaml/issues/86 + let empty: IconTheme = Theme::with_yaml("icons-by-filetype:\n dir: ļ€–").unwrap(); + assert_eq!(empty.icons_by_filetype.dir, "ļ€–"); + } +} From f0a0777ae04c3c14285e484f910e206c3fdc09f7 Mon Sep 17 00:00:00 2001 From: Wei Zhang Date: Fri, 19 Aug 2022 01:27:34 +0800 Subject: [PATCH 10/16] :memo: icon theme: add docs Signed-off-by: Wei Zhang --- README.md | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7a115cf95..9444b0dea 100644 --- a/README.md +++ b/README.md @@ -233,9 +233,11 @@ header: false ## Theme -`lsd` can be configured with a theme file to set the colors. +`lsd` can be configured with theme files to set the colors or icons. -Theme can be configured in the [configuration file](#configuration)(color.theme), +### Color Theme + +Color theme can be configured in the [configuration file](#configuration)(color.theme), The valid theme configurations are: - `default`: the default color scheme shipped in `lsd` @@ -247,9 +249,9 @@ when configured with the `theme-file-name` which is a `yaml` file, - relative name: check the themes under XDG Base Directory, e.g. ~/.config/lsd/themes/.yaml - absolute name: use the file path and name to find theme file -Check [Theme file content](#theme-file-content) for details. +Check [Color Theme file content](#color-theme-file-content) for details. -### Theme file content +#### Color Theme file content Theme file use the [crossterm](https://crates.io/crates/crossterm) to configure the colors, check [crossterm](https://docs.rs/crossterm/0.20.0/crossterm/style/enum.Color.html) @@ -297,6 +299,71 @@ and then change its colors, the items missed would fallback to use the default c Please also notice that an empty theme is **NOT** supported due to [a bug in serde lib](https://github.com/dtolnay/serde-yaml/issues/86). +### Icon Theme + +Icon theme can be configured in a fixed location, `LSD_CONFIG_DIR/themes/icons.yaml`, +for example, `~/.config/lsd/themes/icons.yam` on macOS, +please check [Config file location](#config-file-location) to make sure where is `LSD_CONFIG_DIR`. + +As the file name indicated, the icon theme file is a `yaml` file. + +Check [Icon Theme file content](#icon-theme-file-content) for details. + +#### Icon Theme file content + +lsd support 3 kinds of icon configuration: +- icons-by-filetype +- icons-by-name +- icons-by-extension + +The default icon theme scheme shipped with `lsd` can be check in [icon theme source code](src/theme/icon.rs), we will load the default theme, and overwrite it with user defined parts, here is a example for icon theme. + +lsd icon theme support both nerd font and Unicode in the same time, you can use any one to config the theme, or even combine them. + +nerd font: + +```yaml +icons-by-name: + .trash: ļ‡ø + .cargo: īžØ + .emacs.d: ī¹ + a.out: ļ’‰ +icons-by-extension: + go: ī˜§ + hs: ī· + rs: īžØ +icons-by-filetype: + dir: ļ„• + file: ļ€– + pipe: ļœ± + socket: ļš§ + executable: ļ’‰ + symlink-dir: ļ’‚ + symlink-file: ļ’ + device-char: ī˜ + device-block: ļ°© + special: ļ‹œ +``` + +Unicode: + +```yaml +icons-by-name: + .trash: šŸ—‘ +icons-by-extension: + rs: šŸ¦€ +icons-by-filetype: + dir: šŸ“‚ + file: šŸ“„ + pipe: šŸ“© +``` + +When creating a theme for `lsd`, you can specify any part of the default theme, +and then change its colors, the items missed would fallback to use the default colors. + +Please also notice that an empty theme is **NOT** supported due to +[a bug in serde lib](https://github.com/dtolnay/serde-yaml/issues/86). + ## External Configurations ### Required From d97c989eaa56931dce8c70aedf530c31832a1875 Mon Sep 17 00:00:00 2001 From: Wei Zhang Date: Wed, 28 Sep 2022 01:18:51 +0800 Subject: [PATCH 11/16] :hammer: simplify icon theme and updates due to review Signed-off-by: Wei Zhang --- README.md | 29 ++++++++++++-------------- src/icon.rs | 29 +++++++++++--------------- src/theme.rs | 15 +++++++++++--- src/theme/icon.rs | 53 +++++++++++++++++++++++++++-------------------- 4 files changed, 68 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 9444b0dea..832fda4b2 100644 --- a/README.md +++ b/README.md @@ -246,7 +246,7 @@ The valid theme configurations are: when configured with the `theme-file-name` which is a `yaml` file, `lsd` will look up the theme file in the following way: -- relative name: check the themes under XDG Base Directory, e.g. ~/.config/lsd/themes/.yaml +- relative name: check the XDG Base Directory, e.g. ~/.config/lsd/.yaml - absolute name: use the file path and name to find theme file Check [Color Theme file content](#color-theme-file-content) for details. @@ -301,9 +301,9 @@ Please also notice that an empty theme is **NOT** supported due to ### Icon Theme -Icon theme can be configured in a fixed location, `LSD_CONFIG_DIR/themes/icons.yaml`, -for example, `~/.config/lsd/themes/icons.yam` on macOS, -please check [Config file location](#config-file-location) to make sure where is `LSD_CONFIG_DIR`. +Icon theme can be configured in a fixed location, `$XDG_CONFIG_DIR/lsd/icons.yaml`, +for example, `~/.config/lsd/icons.yaml` on macOS, +please check [Config file location](#config-file-location) to make sure where is `$XDG_CONFIG_DIR`. As the file name indicated, the icon theme file is a `yaml` file. @@ -312,9 +312,9 @@ Check [Icon Theme file content](#icon-theme-file-content) for details. #### Icon Theme file content lsd support 3 kinds of icon configuration: -- icons-by-filetype -- icons-by-name -- icons-by-extension +- filetype +- name +- extension The default icon theme scheme shipped with `lsd` can be check in [icon theme source code](src/theme/icon.rs), we will load the default theme, and overwrite it with user defined parts, here is a example for icon theme. @@ -323,16 +323,16 @@ lsd icon theme support both nerd font and Unicode in the same time, you can use nerd font: ```yaml -icons-by-name: +name: .trash: ļ‡ø .cargo: īžØ .emacs.d: ī¹ a.out: ļ’‰ -icons-by-extension: +extension: go: ī˜§ hs: ī· rs: īžØ -icons-by-filetype: +filetype: dir: ļ„• file: ļ€– pipe: ļœ± @@ -348,11 +348,11 @@ icons-by-filetype: Unicode: ```yaml -icons-by-name: +name: .trash: šŸ—‘ -icons-by-extension: +extension: rs: šŸ¦€ -icons-by-filetype: +filetype: dir: šŸ“‚ file: šŸ“„ pipe: šŸ“© @@ -361,9 +361,6 @@ icons-by-filetype: When creating a theme for `lsd`, you can specify any part of the default theme, and then change its colors, the items missed would fallback to use the default colors. -Please also notice that an empty theme is **NOT** supported due to -[a bug in serde lib](https://github.com/dtolnay/serde-yaml/issues/86). - ## External Configurations ### Required diff --git a/src/icon.rs b/src/icon.rs index d60a160cb..a3eb6bdc9 100644 --- a/src/icon.rs +++ b/src/icon.rs @@ -38,34 +38,29 @@ impl Icons { // Check file types let file_type: FileType = name.file_type(); let icon = match file_type { - FileType::SymLink { is_dir: true } => &t.icons_by_filetype.symlink_dir, - FileType::SymLink { is_dir: false } => &t.icons_by_filetype.symlink_file, - FileType::Socket => &t.icons_by_filetype.socket, - FileType::Pipe => &t.icons_by_filetype.pipe, - FileType::CharDevice => &t.icons_by_filetype.device_char, - FileType::BlockDevice => &t.icons_by_filetype.device_block, - FileType::Special => &t.icons_by_filetype.special, + FileType::SymLink { is_dir: true } => &t.filetype.symlink_dir, + FileType::SymLink { is_dir: false } => &t.filetype.symlink_file, + FileType::Socket => &t.filetype.socket, + FileType::Pipe => &t.filetype.pipe, + FileType::CharDevice => &t.filetype.device_char, + FileType::BlockDevice => &t.filetype.device_block, + FileType::Special => &t.filetype.special, _ => { - if let Some(icon) = t - .icons_by_name - .get(name.file_name().to_lowercase().as_str()) - { + if let Some(icon) = t.name.get(name.file_name().to_lowercase().as_str()) { icon } else if let Some(icon) = name .extension() - .and_then(|ext| t.icons_by_extension.get(ext.to_lowercase().as_str())) + .and_then(|ext| t.extension.get(ext.to_lowercase().as_str())) { icon } else { match file_type { - FileType::Directory { .. } => &t.icons_by_filetype.dir, + FileType::Directory { .. } => &t.filetype.dir, // If a file has no extension and is executable, show an icon. // Except for Windows, it marks everything as an executable. #[cfg(not(windows))] - FileType::File { exec: true, .. } => { - &t.icons_by_filetype.executable - } - _ => &t.icons_by_filetype.file, + FileType::File { exec: true, .. } => &t.filetype.executable, + _ => &t.filetype.file, } } } diff --git a/src/theme.rs b/src/theme.rs index 843b11c59..4ff78bf00 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -38,7 +38,10 @@ impl Theme { /// This read theme from file, /// use the file path if it is absolute /// prefix the config_file dir to it if it is not - pub fn from_path(file: &str) -> Result { + pub fn from_path(file: &str) -> Result + where + D: DeserializeOwned + Default, + { let real = if let Some(path) = config_file::Config::expand_home(file) { path } else { @@ -49,7 +52,7 @@ impl Theme { real } else { match config_file::Config::config_file_path() { - Some(p) => p.join("themes").join(real), + Some(p) => p.join(real), None => return Err(Error::InvalidPath("config home not existed".into())), } }; @@ -72,7 +75,13 @@ impl Theme { } /// This constructs a Theme struct with a passed [Yaml] str. - fn with_yaml(yaml: &str) -> Result { + fn with_yaml(yaml: &str) -> Result + where + D: DeserializeOwned + Default, + { + if yaml.trim() == "" { + return Ok(D::default()); + } serde_yaml::from_str::(yaml) } } diff --git a/src/theme/icon.rs b/src/theme/icon.rs index 2ec2cef08..488c0ea2b 100644 --- a/src/theme/icon.rs +++ b/src/theme/icon.rs @@ -6,16 +6,16 @@ use std::collections::HashMap; #[serde(deny_unknown_fields)] #[serde(default)] pub struct IconTheme { - pub icons_by_name: HashMap, - pub icons_by_extension: HashMap, - pub icons_by_filetype: IconByType, + pub name: HashMap, + pub extension: HashMap, + pub filetype: ByType, } #[derive(Debug, Deserialize, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] #[serde(default)] -pub struct IconByType { +pub struct ByType { pub dir: String, pub file: String, pub pipe: String, @@ -31,16 +31,16 @@ pub struct IconByType { impl Default for IconTheme { fn default() -> Self { IconTheme { - icons_by_name: Self::get_default_icons_by_name(), - icons_by_extension: Self::get_default_icons_by_extension(), - icons_by_filetype: IconByType::default(), + name: Self::get_default_icons_by_name(), + extension: Self::get_default_icons_by_extension(), + filetype: ByType::default(), } } } -impl Default for IconByType { - fn default() -> IconByType { - IconByType { +impl Default for ByType { + fn default() -> ByType { + ByType { dir: "\u{f115}".into(), // ļ„• file: "\u{f016}".into(), // ļ€– pipe: "\u{f731}".into(), // ļœ± @@ -55,9 +55,9 @@ impl Default for IconByType { } } -impl IconByType { +impl ByType { pub fn unicode() -> Self { - IconByType { + ByType { dir: "\u{1f4c2}".into(), file: "\u{1f4c4}".into(), pipe: "\u{1f4e9}".into(), @@ -75,9 +75,9 @@ impl IconByType { impl IconTheme { pub fn unicode() -> Self { IconTheme { - icons_by_name: HashMap::new(), - icons_by_extension: HashMap::new(), - icons_by_filetype: IconByType::unicode(), + name: HashMap::new(), + extension: HashMap::new(), + filetype: ByType::unicode(), } } @@ -482,16 +482,16 @@ mod tests { fn partial_default_yaml() -> &'static str { r#"--- -icons-by-name: +name: .trash: ļ‡ø .cargo: īžØ .emacs.d: ī¹ a.out: ļ’‰ -icons-by-extension: +extension: go: ī˜§ hs: ī· rs: īžØ -icons-by-filetype: +filetype: dir: ļ„• file: ļ€– pipe: ļœ± @@ -506,7 +506,7 @@ icons-by-filetype: } fn check_partial_yaml(def: &IconTheme, yaml: &IconTheme) { - assert_eq!(def.icons_by_filetype.dir, yaml.icons_by_filetype.dir,); + assert_eq!(def.filetype.dir, yaml.filetype.dir,); } #[test] @@ -533,7 +533,16 @@ icons-by-filetype: fn test_empty_theme_return_default() { // Must contain one field at least // ref https://github.com/dtolnay/serde-yaml/issues/86 - let empty: IconTheme = Theme::with_yaml("icons-by-filetype:\n dir: ļ„•").unwrap(); // ļ„• is the default value + let empty: IconTheme = Theme::with_yaml(" ").unwrap(); + let default = IconTheme::default(); + check_partial_yaml(&empty, &default); + } + + #[test] + fn test_partial_theme_return_default() { + // Must contain one field at least + // ref https://github.com/dtolnay/serde-yaml/issues/86 + let empty: IconTheme = Theme::with_yaml("filetype:\n dir: ļ„•").unwrap(); // ļ„• is the default value let default = IconTheme::default(); check_partial_yaml(&empty, &default); } @@ -542,7 +551,7 @@ icons-by-filetype: fn test_serde_dir_from_yaml() { // Must contain one field at least // ref https://github.com/dtolnay/serde-yaml/issues/86 - let empty: IconTheme = Theme::with_yaml("icons-by-filetype:\n dir: ļ€–").unwrap(); - assert_eq!(empty.icons_by_filetype.dir, "ļ€–"); + let empty: IconTheme = Theme::with_yaml("filetype:\n dir: ļ€–").unwrap(); + assert_eq!(empty.filetype.dir, "ļ€–"); } } From 11c5e6df8feee2be05bc025f3105ae67ef15aedb Mon Sep 17 00:00:00 2001 From: Wei Zhang Date: Wed, 5 Oct 2022 17:15:32 +0800 Subject: [PATCH 12/16] :bug: fix mistaken edited color theme location Signed-off-by: Wei Zhang --- src/color.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/color.rs b/src/color.rs index 893885eb3..7a589c81b 100644 --- a/src/color.rs +++ b/src/color.rs @@ -138,7 +138,8 @@ impl Colors { ThemeOption::NoColor => None, ThemeOption::Default | ThemeOption::NoLscolors => Some(Theme::default().color), ThemeOption::Custom(ref file) => { - Some(Theme::from_path::(file).unwrap_or_default()) + // TODO: drop the `themes` dir prefix, adding it here only for backwards compatibility + Some(Theme::from_path::(Path::new("themes").join(file).to_str().unwrap_or(file)).unwrap_or_default()) } }; let lscolors = match t { From 6327ef5ce1d60c8d60cdae19d18d0dc749959d44 Mon Sep 17 00:00:00 2001 From: Wei Zhang Date: Wed, 5 Oct 2022 17:16:47 +0800 Subject: [PATCH 13/16] :art: cargo fmt Signed-off-by: Wei Zhang --- src/color.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/color.rs b/src/color.rs index 7a589c81b..4b761a397 100644 --- a/src/color.rs +++ b/src/color.rs @@ -139,7 +139,12 @@ impl Colors { ThemeOption::Default | ThemeOption::NoLscolors => Some(Theme::default().color), ThemeOption::Custom(ref file) => { // TODO: drop the `themes` dir prefix, adding it here only for backwards compatibility - Some(Theme::from_path::(Path::new("themes").join(file).to_str().unwrap_or(file)).unwrap_or_default()) + Some( + Theme::from_path::( + Path::new("themes").join(file).to_str().unwrap_or(file), + ) + .unwrap_or_default(), + ) } }; let lscolors = match t { From 42b23fd4297388293ea0d101beeee5a9b3baf72a Mon Sep 17 00:00:00 2001 From: Wei Zhang Date: Wed, 5 Oct 2022 17:50:03 +0800 Subject: [PATCH 14/16] :tada: add icon theme in changelog Signed-off-by: Wei Zhang --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b60c097f..9bcd57242 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added - In keeping with the coreutils change, add quotes and escapes for necessary filenames from [merelymyself](https://github.com/merelymyself) +- Add support for icon theme from [zwpaper](https://github.com/zwpaper) ## [0.23.1] - 2022-09-13 @@ -20,7 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Reduce the binary size and improve the performance from [sabify](https://github.com/sabify) ### Fixed -- Fix rendering issues in Windows from [meain](https://gitHub.com/meain) +- Fix rendering issues in Windows from [meain](https://github.com/meain) ## [0.22.0] - 2022-06-12 ### Added From cf9f2915c1fb5b6f09c184d4df08974cb039aba5 Mon Sep 17 00:00:00 2001 From: zwPapEr Date: Mon, 10 Oct 2022 11:32:33 +0800 Subject: [PATCH 15/16] :memo: :hammer: fix typos and update description for icon theme Signed-off-by: zwPapEr --- README.md | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 832fda4b2..4e07dfcd2 100644 --- a/README.md +++ b/README.md @@ -246,7 +246,7 @@ The valid theme configurations are: when configured with the `theme-file-name` which is a `yaml` file, `lsd` will look up the theme file in the following way: -- relative name: check the XDG Base Directory, e.g. ~/.config/lsd/.yaml +- relative name: check the XDG Base Directory, e.g. ~/.config/lsd/themes/.yaml - absolute name: use the file path and name to find theme file Check [Color Theme file content](#color-theme-file-content) for details. @@ -318,7 +318,7 @@ lsd support 3 kinds of icon configuration: The default icon theme scheme shipped with `lsd` can be check in [icon theme source code](src/theme/icon.rs), we will load the default theme, and overwrite it with user defined parts, here is a example for icon theme. -lsd icon theme support both nerd font and Unicode in the same time, you can use any one to config the theme, or even combine them. +lsd icon theme support both nerd font and Unicode in the same time, you can use any one to config the theme, this is the default but you can customize this to use glyphs from nerd-font or Unicode emojis in the same time. nerd font: @@ -358,8 +358,36 @@ filetype: pipe: šŸ“© ``` +mixed: + +```yaml +name: + .trash: ļ‡ø + .cargo: īžØ + .emacs.d: ī¹ + a.out: ļ’‰ +extension: + go: ī˜§ + hs: ī· + rs: šŸ¦€ +filetype: + dir: šŸ“‚ + file: šŸ“„ + pipe: šŸ“© + dir: ļ„• + file: ļ€– + pipe: ļœ± + socket: ļš§ + executable: ļ’‰ + symlink-dir: ļ’‚ + symlink-file: ļ’ + device-char: ī˜ + device-block: ļ°© + special: ļ‹œ +``` + When creating a theme for `lsd`, you can specify any part of the default theme, -and then change its colors, the items missed would fallback to use the default colors. +and then change its icons, the items missed would fallback to use the default icons. ## External Configurations From 3f9b0168ee0171818c8efaeb28f2dab7c04c9eae Mon Sep 17 00:00:00 2001 From: Wei Zhang Date: Mon, 10 Oct 2022 13:30:02 +0800 Subject: [PATCH 16/16] :memo: :hammer: refine icon theme description Co-authored-by: Abin Simon --- README.md | 58 ++++--------------------------------------------------- 1 file changed, 4 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 4e07dfcd2..9c8e0990c 100644 --- a/README.md +++ b/README.md @@ -311,54 +311,11 @@ Check [Icon Theme file content](#icon-theme-file-content) for details. #### Icon Theme file content -lsd support 3 kinds of icon configuration: -- filetype -- name -- extension +`lsd` support 3 kinds of icon overrides, by `name`, by `filetype` and by `extension`. +The final set of icons used will be a combination of what is shipped with in `lsd` with overrides from config applied on top of it. +*You can find the default set of icons [here](src/theme/icon.rs).* -The default icon theme scheme shipped with `lsd` can be check in [icon theme source code](src/theme/icon.rs), we will load the default theme, and overwrite it with user defined parts, here is a example for icon theme. - -lsd icon theme support both nerd font and Unicode in the same time, you can use any one to config the theme, this is the default but you can customize this to use glyphs from nerd-font or Unicode emojis in the same time. - -nerd font: - -```yaml -name: - .trash: ļ‡ø - .cargo: īžØ - .emacs.d: ī¹ - a.out: ļ’‰ -extension: - go: ī˜§ - hs: ī· - rs: īžØ -filetype: - dir: ļ„• - file: ļ€– - pipe: ļœ± - socket: ļš§ - executable: ļ’‰ - symlink-dir: ļ’‚ - symlink-file: ļ’ - device-char: ī˜ - device-block: ļ°© - special: ļ‹œ -``` - -Unicode: - -```yaml -name: - .trash: šŸ—‘ -extension: - rs: šŸ¦€ -filetype: - dir: šŸ“‚ - file: šŸ“„ - pipe: šŸ“© -``` - -mixed: +Both nerd font glyphs and unicode emojis can be used for icons. You can find an example of icons customization below. ```yaml name: @@ -374,9 +331,6 @@ filetype: dir: šŸ“‚ file: šŸ“„ pipe: šŸ“© - dir: ļ„• - file: ļ€– - pipe: ļœ± socket: ļš§ executable: ļ’‰ symlink-dir: ļ’‚ @@ -384,10 +338,6 @@ filetype: device-char: ī˜ device-block: ļ°© special: ļ‹œ -``` - -When creating a theme for `lsd`, you can specify any part of the default theme, -and then change its icons, the items missed would fallback to use the default icons. ## External Configurations