diff --git a/src/color.rs b/src/color.rs index db4f140..21c8075 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 ee6329e..59bbf13 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). @@ -80,7 +77,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 481d374..084d97a 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 b004f50..dd3b9a8 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 352395a..704e3ed 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 31e0569..1bdfe3b 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 96582c8..a11b8a3 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::>() } }