diff --git a/CHANGELOG.md b/CHANGELOG.md index 91f134778..2782ea80b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added support for the MISSING / mi= dircolors variable for broken symlink targets. - Add support for theme from [zwpaper](https://github.com/zwpaper) [#452](https://github.com/Peltoche/lsd/pull/452) +- Update theme to support partial themes [zwpaper](https://github.com/zwpaper) [#591](https://github.com/Peltoche/lsd/pull/591) - Update minimal rust version to 1.42.0 from [zwpaper](https://github.com/zwpaper) [#534](https://github.com/Peltoche/lsd/issues/534) - [`NO_COLOR`](https://no-color.org/) environment variable support from [AnInternetTroll](https://github.com/aninternettroll) ### Changed @@ -17,7 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `--config-file` flag to read configuration file from a custom location - Clarify custom date format for `date` field in configuration file in the README. ### Fixed -- Support all `strftime` like formatting [#532](https://github.com/Peltoche/lsd/issues/532) +- Support all `strftime` like formatting [#532](https://github.com/Peltoche/lsd/issues/532) ## [0.20.1] - 2021-03-07 ### Fixed diff --git a/README.md b/README.md index e1fc54d14..09c6900b3 100644 --- a/README.md +++ b/README.md @@ -228,12 +228,12 @@ Check [Theme file content](#theme-file-content) for details. ### Theme file content Theme file use the [crossterm](https://crates.io/crates/crossterm) -configure the colors, check [crossterm](https://docs.rs/crossterm/0.20.0/crossterm/style/enum.Color.html) -for the supported colors. +to configure the colors, check [crossterm](https://docs.rs/crossterm/0.20.0/crossterm/style/enum.Color.html) +for supported colors. Color table: https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg -Please notice that color value would ignore case, both lowercase and UPPERCASE is supported. +Please notice that color values would ignore the case, both lowercase and UPPERCASE is supported. This is the default theme scheme shipped with `lsd`. @@ -264,6 +264,12 @@ links: tree-edge: 245 ``` +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 @@ -306,11 +312,11 @@ To check if the font you are using is setup correctly, try running the following ```sh echo $'\uf115' ``` - + ### Icons missing or not rendering correctly using PuTTY/KiTTY on Windows - + First of all, make sure a patched font is installed and PuTTY/KiTTY is configurated to use it, please check [Prerequisites](#prerequisites). - + There are problems for PuTTY/KiTTY to show 2 char wide icons, make sure using a 1 char wide font like [Hack Regular Nerd Font Complete Mono Windows Compatible](https://github.com/ryanoasis/nerd-fonts/blob/master/patched-fonts/Hack/Regular/complete/Hack%20Regular%20Nerd%20Font%20Complete%20Mono%20Windows%20Compatible.ttf), check [this issue](https://github.com/Peltoche/lsd/issues/331) for detail. ### Colors diff --git a/src/color.rs b/src/color.rs index c85f63536..b5636bf8d 100644 --- a/src/color.rs +++ b/src/color.rs @@ -109,15 +109,12 @@ impl Elem { Elem::User => theme.user, Elem::Group => theme.group, - Elem::NonFile => theme.size.none, Elem::FileLarge => theme.size.large, Elem::FileMedium => theme.size.medium, Elem::FileSmall => theme.size.small, - Elem::INode { valid: false } => theme.inode.valid, Elem::INode { valid: true } => theme.inode.invalid, - Elem::TreeEdge => theme.tree_edge, Elem::Links { valid: false } => theme.links.invalid, Elem::Links { valid: true } => theme.links.valid, @@ -187,7 +184,7 @@ impl Colors { fn style_default(&self, elem: &Elem) -> ContentStyle { if let Some(t) = &self.theme { - let style_fg = ContentStyle::default().with(elem.get_color(&t)); + let style_fg = ContentStyle::default().with(elem.get_color(t)); if elem.has_suid() { style_fg.on(Color::AnsiValue(124)) // Red3 } else { @@ -254,10 +251,11 @@ fn to_content_style(ls: &lscolors::Style) -> ContentStyle { lscolors::style::Color::Cyan => Color::DarkCyan, lscolors::style::Color::White => Color::White, }; - let mut style = ContentStyle::default(); - - style.foreground_color = ls.foreground.as_ref().map(to_crossterm_color); - style.background_color = ls.background.as_ref().map(to_crossterm_color); + let mut style = ContentStyle { + foreground_color: ls.foreground.as_ref().map(to_crossterm_color), + background_color: ls.background.as_ref().map(to_crossterm_color), + ..ContentStyle::default() + }; if ls.font_style.bold { style.attributes.set(Attribute::Bold); @@ -381,7 +379,7 @@ mod elem { } #[test] - fn test_default_file() { + fn test_default_theme_color() { assert_eq!( Elem::File { exec: true, diff --git a/src/color/theme.rs b/src/color/theme.rs index c5bf11b18..4d31174da 100644 --- a/src/color/theme.rs +++ b/src/color/theme.rs @@ -13,6 +13,7 @@ use std::path::Path; #[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] +#[serde(default)] pub struct Theme { pub user: Color, pub group: Color, @@ -30,6 +31,7 @@ pub struct Theme { #[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] +#[serde(default)] pub struct Permission { pub read: Color, pub write: Color, @@ -41,6 +43,7 @@ pub struct Permission { #[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] +#[serde(default)] pub struct FileType { pub file: File, pub dir: Dir, @@ -55,6 +58,7 @@ pub struct FileType { #[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] +#[serde(default)] pub struct File { pub exec_uid: Color, pub uid_no_exec: Color, @@ -65,6 +69,7 @@ pub struct File { #[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] +#[serde(default)] pub struct Dir { pub uid: Color, pub no_uid: Color, @@ -73,6 +78,7 @@ pub struct Dir { #[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] +#[serde(default)] pub struct Symlink { pub default: Color, pub broken: Color, @@ -82,6 +88,7 @@ pub struct Symlink { #[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] +#[serde(default)] pub struct Date { pub hour_old: Color, pub day_old: Color, @@ -91,6 +98,7 @@ pub struct Date { #[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] +#[serde(default)] pub struct Size { pub none: Color, pub small: Color, @@ -101,6 +109,7 @@ pub struct Size { #[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] +#[serde(default)] pub struct INode { pub valid: Color, pub invalid: Color, @@ -109,14 +118,97 @@ pub struct INode { #[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] +#[serde(default)] pub struct Links { pub valid: Color, pub invalid: Color, } +impl Default for Permission { + fn default() -> Self { + Permission { + read: Color::DarkGreen, + write: Color::DarkYellow, + exec: Color::DarkRed, + exec_sticky: Color::AnsiValue(5), + no_access: Color::AnsiValue(245), // Grey + } + } +} impl Default for FileType { fn default() -> Self { - Theme::default_dark().file_type + FileType { + file: File::default(), + dir: Dir::default(), + symlink: Symlink::default(), + pipe: Color::AnsiValue(44), // DarkTurquoise + block_device: Color::AnsiValue(44), // DarkTurquoise + char_device: Color::AnsiValue(172), // Orange3 + socket: Color::AnsiValue(44), // DarkTurquoise + special: Color::AnsiValue(44), // DarkTurquoise + } + } +} +impl Default for File { + fn default() -> Self { + 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 + } + } +} +impl Default for Dir { + fn default() -> Self { + Dir { + uid: Color::AnsiValue(33), // DodgerBlue1 + no_uid: Color::AnsiValue(33), // DodgerBlue1 + } + } +} +impl Default for Symlink { + fn default() -> Self { + Symlink { + default: Color::AnsiValue(44), // DarkTurquoise + broken: Color::AnsiValue(124), // Red3 + missing_target: Color::AnsiValue(124), // Red3 + } + } +} +impl Default for Date { + fn default() -> Self { + Date { + hour_old: Color::AnsiValue(40), // Green3 + day_old: Color::AnsiValue(42), // SpringGreen2 + older: Color::AnsiValue(36), // DarkCyan + } + } +} +impl Default for Size { + fn default() -> Self { + Size { + none: Color::AnsiValue(245), // Grey + small: Color::AnsiValue(229), // Wheat1 + medium: Color::AnsiValue(216), // LightSalmon1 + large: Color::AnsiValue(172), // Orange3 + } + } +} +impl Default for INode { + fn default() -> Self { + INode { + valid: Color::AnsiValue(13), // Pink + invalid: Color::AnsiValue(245), // Grey + } + } +} +impl Default for Links { + fn default() -> Self { + Links { + valid: Color::AnsiValue(13), // Pink + invalid: Color::AnsiValue(245), // Grey + } } } @@ -181,54 +273,12 @@ impl Theme { Theme { user: Color::AnsiValue(230), // Cornsilk1 group: Color::AnsiValue(187), // LightYellow3 - permission: Permission { - read: Color::DarkGreen, - write: Color::DarkYellow, - exec: Color::DarkRed, - exec_sticky: Color::AnsiValue(5), - no_access: Color::AnsiValue(245), // Grey - }, - file_type: FileType { - file: 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: Dir { - uid: Color::AnsiValue(33), // DodgerBlue1 - no_uid: Color::AnsiValue(33), // DodgerBlue1 - }, - pipe: Color::AnsiValue(44), // DarkTurquoise - symlink: Symlink { - default: Color::AnsiValue(44), // DarkTurquoise - broken: Color::AnsiValue(124), // Red3 - missing_target: Color::AnsiValue(124), // Red3 - }, - block_device: Color::AnsiValue(44), // DarkTurquoise - char_device: Color::AnsiValue(172), // Orange3 - socket: Color::AnsiValue(44), // DarkTurquoise - special: Color::AnsiValue(44), // DarkTurquoise - }, - date: Date { - hour_old: Color::AnsiValue(40), // Green3 - day_old: Color::AnsiValue(42), // SpringGreen2 - older: Color::AnsiValue(36), // DarkCyan - }, - size: Size { - none: Color::AnsiValue(245), // Grey - small: Color::AnsiValue(229), // Wheat1 - medium: Color::AnsiValue(216), // LightSalmon1 - large: Color::AnsiValue(172), // Orange3 - }, - inode: INode { - valid: Color::AnsiValue(13), // Pink - invalid: Color::AnsiValue(245), // Grey - }, - links: Links { - valid: Color::AnsiValue(13), // Pink - invalid: Color::AnsiValue(245), // Grey - }, + permission: Permission::default(), + file_type: FileType::default(), + date: Date::default(), + size: Size::default(), + inode: INode::default(), + links: Links::default(), tree_edge: Color::AnsiValue(245), // Grey } } @@ -290,4 +340,41 @@ mod tests { Theme::from_path(theme.to_str().unwrap()).unwrap() ); } + + #[test] + 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".into()).unwrap(); // 230 is the default value + let default_theme = Theme::default_dark(); + assert_eq!(empty_theme, default_theme); + } + + #[test] + 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".into()).unwrap(); + let mut theme = Theme::default_dark(); + use crossterm::style::Color; + theme.user = Color::AnsiValue(130); + assert_eq!(empty_theme, theme); + } + + #[test] + 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( + r#"--- +permission: + read: 130"# + .into(), + ) + .unwrap(); + let mut theme = Theme::default_dark(); + use crossterm::style::Color; + theme.permission.read = Color::AnsiValue(130); + assert_eq!(empty_theme, theme); + } } diff --git a/src/meta/mod.rs b/src/meta/mod.rs index 0a55d7052..72da1c38b 100644 --- a/src/meta/mod.rs +++ b/src/meta/mod.rs @@ -159,7 +159,7 @@ impl Meta { } } - fn calculate_total_file_size(path: &PathBuf) -> u64 { + fn calculate_total_file_size(path: &Path) -> u64 { let metadata = path.symlink_metadata(); let metadata = match metadata { Ok(meta) => meta, diff --git a/src/meta/size.rs b/src/meta/size.rs index 5a16bd4dc..f4dd7637f 100644 --- a/src/meta/size.rs +++ b/src/meta/size.rs @@ -1,7 +1,6 @@ use crate::color::{ColoredString, Colors, Elem}; use crate::flags::{Flags, SizeFlag}; use std::fs::Metadata; -use std::iter::repeat; #[derive(Clone, Debug, PartialEq, Eq)] pub enum Unit { @@ -62,9 +61,7 @@ impl Size { let unit_content = self.render_unit(colors, flags); let left_pad = if let Some(align) = val_alignment { - repeat(" ") - .take(align - val_content.content().len()) - .collect::() + " ".repeat(align - val_content.content().len()) } else { "".to_string() };