瀏覽代碼

feat: begin implementation of config file

PThorpe92 2 年之前
父節點
當前提交
52f9cc1c70
共有 9 個文件被更改,包括 629 次插入363 次删除
  1. 26 4
      Cargo.lock
  2. 7 3
      Cargo.toml
  3. 133 0
      src/options/config.rs
  4. 2 1
      src/options/mod.rs
  5. 11 1
      src/output/color_scale.rs
  6. 10 6
      src/output/table.rs
  7. 123 124
      src/theme/default_theme.rs
  8. 14 14
      src/theme/mod.rs
  9. 303 210
      src/theme/ui_styles.rs

+ 26 - 4
Cargo.lock

@@ -408,6 +408,8 @@ dependencies = [
  "plist",
  "proc-mounts",
  "rayon",
+ "serde",
+ "serde_yaml",
  "terminal_size",
  "timeago",
  "trycmd",
@@ -685,6 +687,7 @@ version = "0.50.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dd2800e1520bdc966782168a627aa5d1ad92e33b984bf7c7615d31280c83ff14"
 dependencies = [
+ "serde",
  "windows-sys 0.48.0",
 ]
 
@@ -1043,18 +1046,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
 [[package]]
 name = "serde"
-version = "1.0.188"
+version = "1.0.193"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
+checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.188"
+version = "1.0.193"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
+checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1081,6 +1084,19 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "serde_yaml"
+version = "0.9.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a15e0ef66bf939a7c890a0bf6d5a733c70202225f9888a89ed5c62298b019129"
+dependencies = [
+ "indexmap",
+ "itoa",
+ "ryu",
+ "serde",
+ "unsafe-libyaml",
+]
+
 [[package]]
 name = "shlex"
 version = "1.3.0"
@@ -1315,6 +1331,12 @@ version = "0.1.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
 
+[[package]]
+name = "unsafe-libyaml"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
+
 [[package]]
 name = "url"
 version = "2.2.1"

+ 7 - 3
Cargo.toml

@@ -71,8 +71,12 @@ name = "eza"
 
 
 [dependencies]
-nu-ansi-term = "0.50.0"
+rayon = "1.10.0"
 chrono = { version = "0.4.34", default-features = false, features = ["clock"] }
+nu-ansi-term = { version = "0.50.0", features = [
+  "serde",
+  "derive_serde_style",
+] }
 glob = "0.3"
 libc = "0.2"
 locale = "0.2"
@@ -90,9 +94,9 @@ terminal_size = "0.3.0"
 timeago = { version = "0.4.2", default-features = false }
 unicode-width = "0.1"
 zoneinfo_compiled = "0.5.1"
-rayon = "1.10.0"
 ansi-width = "0.1.0"
-
+serde = { version = "1.0.193", features = ["derive"] }
+serde_yaml = "0.9.29"
 [dependencies.git2]
 version = "0.19"
 optional = true

+ 133 - 0
src/options/config.rs

@@ -0,0 +1,133 @@
+use crate::options::theme::ui_styles::UiStyles;
+use nu_ansi_term::Color;
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Config {
+    display: Option<DisplayOptions>,
+    theme: Option<Theme>,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct DisplayOptions {
+    icons: Option<DisplayMode>,
+    color: Option<DisplayMode>,
+    hyperlinks: Option<DisplayMode>,
+    quotes: Option<DisplayMode>,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub enum DisplayMode {
+    Always,
+    Auto,
+    Never,
+}
+
+// This will be the base layout for the options in a "Theme" file, so we can separate the
+// verbose theme file from the config file.
+#[derive(Debug, Serialize, Deserialize)]
+pub struct ThemeMapping(pub HashMap<ThemeOption, Vec<Color>>);
+
+// This will represent the theme.yaml file itself
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Theme {
+    pub name: String,
+    pub colors: ThemeMapping,
+}
+
+#[derive(Debug, Serialize, Deserialize, Hash, Eq, PartialEq)]
+pub enum ThemeOption {
+    Directories,
+    Executables,
+    RegularFiles,
+    NamedPipes,
+    Sockets,
+    BlockDevices,
+    CharacterDevices,
+    Symlinks,
+    Orphans,
+    OctalPermissions,
+    UserRead,
+    UserWrite,
+    UserExecuteRegular,
+    UserExecuteOther,
+    GroupRead,
+    GroupWrite,
+    GroupExecuteRegular,
+    GroupExecuteOther,
+    OthersRead,
+    OthersWrite,
+    OthersExecuteRegular,
+    OthersExecuteOther,
+    SetIdsStickyBitFiles,
+    SetIdsStickyBitOther,
+    ExtendedAttribute,
+    FileSizeNumbers(Vec<Option<FileSize>>),
+    FileSizeUnits(Vec<Option<FileSize>>),
+    DeviceMajorId,
+    DeviceMinorId,
+    UserYou,
+    UserRoot,
+    UserElse,
+    GroupYou,
+    GroupRoot,
+    GroupElse,
+    NumberHardLinks,
+    NumberHardLinksRegular,
+    GitNew,
+    GitModified,
+    GitDeleted,
+    GitRenamed,
+    GitMetadataModified,
+    GitIgnored,
+    GitConflicted,
+    GitMainBranch,
+    GitOtherBranch,
+    GitClean,
+    GitDirty,
+    Punctuation,
+    FileDate,
+    FileInode,
+    FileBlocks,
+    TableHeader,
+    SymlinkPath,
+    FilenameEscapedCharacter,
+    BrokenSymlinkOverlay,
+    Special,
+    MountPoint,
+    ImageFile,
+    VideoFile,
+    LossyMusicFile,
+    LosslessMusicFile,
+    CryptographyFile,
+    DocumentFile,
+    CompressedFile,
+    TemporaryFile,
+    CompilationArtifact,
+    BuildFile,
+    SourceCodeFile,
+    NoSecurityContext,
+    SelinuxUser,
+    SelinuxRole,
+    SelinuxType,
+    SelinuxLevel,
+    BsdFileFlags,
+}
+
+#[derive(Debug, Serialize, Deserialize, Hash, Eq, PartialEq)]
+pub enum FileSize {
+    Bytes,
+    Kilobytes,
+    Megabytes,
+    Gigabytes,
+    Terabytes,
+}
+impl Default for Theme {
+    fn default() -> Self {
+        Self {
+            name: "default".to_string(),
+            colors: ThemeMapping::default(),
+        }
+    }
+}

+ 2 - 1
src/options/mod.rs

@@ -81,10 +81,11 @@ mod file_name;
 mod filter;
 #[rustfmt::skip] // this module becomes unreadable with rustfmt
 mod flags;
+mod config;
+mod error;
 mod theme;
 mod view;
 
-mod error;
 pub use self::error::{NumberSource, OptionsError};
 
 mod help;

+ 11 - 1
src/output/color_scale.rs

@@ -11,11 +11,21 @@ use crate::{
 pub struct ColorScaleOptions {
     pub mode: ColorScaleMode,
     pub min_luminance: isize,
-
     pub size: bool,
     pub age: bool,
 }
 
+impl Default for ColorScaleOptions {
+    fn default() -> Self {
+        Self {
+            mode: ColorScaleMode::Fixed,
+            min_luminance: 50,
+            size: false,
+            age: false,
+        }
+    }
+}
+
 #[derive(PartialEq, Eq, Debug, Copy, Clone)]
 pub enum ColorScaleMode {
     Fixed,

+ 10 - 6
src/output/table.rs

@@ -451,7 +451,7 @@ impl<'a> Table<'a> {
         let cells = self
             .columns
             .iter()
-            .map(|c| TextCell::paint_str(self.theme.ui.header, c.header()))
+            .map(|c| TextCell::paint_str(self.theme.ui.header.unwrap_or_default(), c.header()))
             .collect();
 
         Row { cells }
@@ -520,7 +520,7 @@ impl<'a> Table<'a> {
             #[cfg(unix)]
             Column::HardLinks => file.links().render(self.theme, &self.env.numeric),
             #[cfg(unix)]
-            Column::Inode => file.inode().render(self.theme.ui.inode),
+            Column::Inode => file.inode().render(self.theme.ui.inode.unwrap_or_default()),
             #[cfg(unix)]
             Column::Blocksize => {
                 file.blocksize()
@@ -541,22 +541,26 @@ impl<'a> Table<'a> {
             ),
             #[cfg(unix)]
             Column::SecurityContext => file.security_context().render(self.theme),
-            Column::FileFlags => file.flags().render(self.theme.ui.flags, self.flags_format),
+            Column::FileFlags => file
+                .flags()
+                .render(self.theme.ui.flags.unwrap_or_default(), self.flags_format),
             Column::GitStatus => self.git_status(file).render(self.theme),
             Column::SubdirGitRepo(status) => self.subdir_git_repo(file, status).render(self.theme),
             #[cfg(unix)]
-            Column::Octal => self.octal_permissions(file).render(self.theme.ui.octal),
+            Column::Octal => self
+                .octal_permissions(file)
+                .render(self.theme.ui.octal.unwrap_or_default()),
 
             Column::Timestamp(time_type) => time_type.get_corresponding_time(file).render(
                 if color_scale_info.is_some_and(|csi| csi.options.mode == ColorScaleMode::Gradient)
                 {
                     color_scale_info.unwrap().apply_time_gradient(
-                        self.theme.ui.date,
+                        self.theme.ui.date.unwrap_or_default(),
                         file,
                         time_type,
                     )
                 } else {
-                    self.theme.ui.date
+                    self.theme.ui.date.unwrap_or_default()
                 },
                 self.env.time_offset,
                 self.time_format.clone(),

+ 123 - 124
src/theme/default_theme.rs

@@ -5,123 +5,122 @@ use std::default::Default;
 use crate::output::color_scale::{ColorScaleMode, ColorScaleOptions};
 use crate::theme::ui_styles::*;
 
-impl UiStyles {
-    pub fn default_theme(scale: ColorScaleOptions) -> Self {
+impl Default for UiStyles {
+    fn default(scale: Option<ColorScaleOptions>) -> Self {
         Self {
-            colourful: true,
+            colourful: Some(true),
 
             #[rustfmt::skip]
-            filekinds: FileKinds {
-                normal:       Style::default(),
-                directory:    Blue.bold(),
-                symlink:      Cyan.normal(),
-                pipe:         Yellow.normal(),
-                block_device: Yellow.bold(),
-                char_device:  Yellow.bold(),
-                socket:       Red.bold(),
-                special:      Yellow.normal(),
-                executable:   Green.bold(),
-                mount_point:  Blue.bold().underline(),
-            },
+            filekinds: Some(FileKinds {
+            normal: Some(Style::default()),
+            directory: Some(Blue.bold()),
+            symlink: Some(Cyan.normal()),
+            pipe: Some(Yellow.normal()),
+            block_device: Some(Yellow.bold()),
+            char_device: Some(Yellow.bold()),
+            socket: Some(Red.bold()),
+            special: Some(Yellow.normal()),
+            executable: Some(Green.bold()),
+            mount_point: Some(Blue.bold().underline()),
+            }),
 
             #[rustfmt::skip]
-            perms: Permissions {
-                user_read:           Yellow.bold(),
-                user_write:          Red.bold(),
-                user_execute_file:   Green.bold().underline(),
-                user_execute_other:  Green.bold(),
+            perms: Some(Permissions {
+                user_read:           Some(Yellow.bold()),
+                user_write:          Some(Red.bold()),
+                user_execute_file:   Some(Green.bold().underline()),
+                user_execute_other:  Some(Green.bold()),
 
-                group_read:          Yellow.normal(),
-                group_write:         Red.normal(),
-                group_execute:       Green.normal(),
+                group_read:          Some(Yellow.normal()),
+                group_write:         Some(Red.normal()),
+                group_execute:       Some(Green.normal()),
 
-                other_read:          Yellow.normal(),
-                other_write:         Red.normal(),
-                other_execute:       Green.normal(),
+                other_read:          Some(Yellow.normal()),
+                other_write:         Some(Red.normal()),
+                other_execute:       Some(Green.normal()),
 
-                special_user_file:   Purple.normal(),
-                special_other:       Purple.normal(),
+                special_user_file:   Some(Purple.normal()),
+                special_other:       Some(Purple.normal()),
 
-                attribute:           Style::default(),
-            },
+                attribute:           Some(Style::default()),
+            }),
 
-            size: Size::colourful(scale),
+            size: Some(Size::colourful(scale.unwrap_or_default())),
 
             #[rustfmt::skip]
-            users: Users {
-                user_you:                       Yellow.bold(),
-                user_other:                     Style::default(),
-                user_root:                      Style::default(),
-                group_yours:                    Yellow.bold(),
-                group_other:                    Style::default(),
-                group_root:                     Style::default(),
-            },
+            users:Some(Users {
+                user_you:                       Some(Yellow.bold()),
+                user_other:                     Some(Style::default()),
+                user_root:                      Some(Style::default()),
+                group_yours:                    Some(Yellow.bold()),
+                group_other:                    Some(Style::default()),
+                group_root:                     Some(Style::default()),
+            }),
 
             #[rustfmt::skip]
-            links: Links {
-                normal:          Red.bold(),
-                multi_link_file: Red.on(Yellow),
-            },
+            links: Some(Links {
+                normal:          Some(Red.bold()),
+                multi_link_file: Some(Red.on(Yellow)),
+            }),
 
             #[rustfmt::skip]
-            git: Git {
-                new:         Green.normal(),
-                modified:    Blue.normal(),
-                deleted:     Red.normal(),
-                renamed:     Yellow.normal(),
-                typechange:  Purple.normal(),
-                ignored:     Style::default().dimmed(),
-                conflicted:  Red.normal(),
-            },
-
-            git_repo: GitRepo {
-                branch_main: Green.normal(),
-                branch_other: Yellow.normal(),
-                git_clean: Green.normal(),
-                git_dirty: Yellow.bold(),
-            },
-
-            security_context: SecurityContext {
-                none: Style::default(),
+            git: Some(Git {
+                new:         Some(Green.normal()),
+                modified:    Some(Blue.normal()),
+                deleted:     Some(Red.normal()),
+                renamed:     Some(Yellow.normal()),
+                typechange:  Some(Purple.normal()),
+                ignored:     Some(Style::default().dimmed()),
+                conflicted:  Some(Red.normal()),
+            }),
+
+            git_repo: Some(GitRepo {
+                branch_main: Some(Green.normal()),
+                branch_other: Some(Yellow.normal()),
+                git_clean: Some(Green.normal()),
+                git_dirty: Some(Yellow.bold()),
+            }),
+
+            security_context: Some(SecurityContext {
+                none: Some(Style::default()),
                 #[rustfmt::skip]
-                selinux: SELinuxContext {
-                    colon: Style::default().dimmed(),
-                    user:  Blue.normal(),
-                    role:  Green.normal(),
-                    typ:   Yellow.normal(),
-                    range: Cyan.normal(),
-                },
-            },
+                selinux: Some(SELinuxContext {
+                    colon: Some(Style::default().dimmed()),
+                    user:  Some(Blue.normal()),
+                    role:  Some(Green.normal()),
+                    typ:   Some(Yellow.normal()),
+                    range: Some(Cyan.normal()),
+                }),
+            }),
 
             #[rustfmt::skip]
-            file_type: FileType {
-                image:      Purple.normal(),
-                video:      Purple.bold(),
-                music:      Cyan.normal(),
-                lossless:   Cyan.bold(),
-                crypto:     Green.bold(),
-                document:   Green.normal(),
-                compressed: Red.normal(),
-                temp:       White.normal(),
-                compiled:   Yellow.normal(),
-                build:      Yellow.bold().underline(),
-                source:     Yellow.bold(), // Need to discuss color
-            },
-
-            punctuation: DarkGray.bold(),
-            date: Blue.normal(),
-            inode: Purple.normal(),
-            blocks: Cyan.normal(),
-            octal: Purple.normal(),
-            flags: Style::default(),
-            header: Style::default().underline(),
+            file_type: Some(FileType {
+                image:      Some(Purple.normal()),
+                video:      Some(Purple.bold()),
+                music:      Some(Cyan.normal()),
+                lossless:   Some(Cyan.bold()),
+                crypto:     Some(Green.bold()),
+                document:   Some(Green.normal()),
+                compressed: Some(Red.normal()),
+                temp:       Some(White.normal()),
+                compiled:   Some(Yellow.normal()),
+                build:      Some(Yellow.bold().underline()),
+                source:     Some(Yellow.bold()), // Need to discuss color
+            }),
+
+            punctuation: Some(DarkGray.bold()),
+            date: Some(Blue.normal()),
+            inode: Some(Purple.normal()),
+            blocks: Some(Cyan.normal()),
+            octal: Some(Purple.normal()),
+            flags: Some(Style::default()),
+            header: Some(Style::default().underline()),
 
             icon: None,
-
-            symlink_path: Cyan.normal(),
-            control_char: Red.normal(),
-            broken_symlink: Red.normal(),
-            broken_path_overlay: Style::default().underline(),
+            symlink_path: Some(Cyan.normal()),
+            control_char: Some(Red.normal()),
+            broken_symlink: Some(Red.normal()),
+            broken_path_overlay: Some(Style::default().underline()),
         }
     }
 }
@@ -137,39 +136,39 @@ impl Size {
 
     fn colourful_fixed() -> Self {
         Self {
-            major: Green.bold(),
-            minor: Green.normal(),
-
-            number_byte: Green.bold(),
-            number_kilo: Green.bold(),
-            number_mega: Green.bold(),
-            number_giga: Green.bold(),
-            number_huge: Green.bold(),
-
-            unit_byte: Green.normal(),
-            unit_kilo: Green.normal(),
-            unit_mega: Green.normal(),
-            unit_giga: Green.normal(),
-            unit_huge: Green.normal(),
+            major: Some(Green.bold()),
+            minor: Some(Green.normal()),
+
+            number_byte: Some(Green.bold()),
+            number_kilo: Some(Green.bold()),
+            number_mega: Some(Green.bold()),
+            number_giga: Some(Green.bold()),
+            number_huge: Some(Green.bold()),
+
+            unit_byte: Some(Green.normal()),
+            unit_kilo: Some(Green.normal()),
+            unit_mega: Some(Green.normal()),
+            unit_giga: Some(Green.normal()),
+            unit_huge: Some(Green.normal()),
         }
     }
 
     fn colourful_gradient() -> Self {
         Self {
-            major: Green.bold(),
-            minor: Green.normal(),
-
-            number_byte: Green.normal(),
-            number_kilo: Green.bold(),
-            number_mega: Yellow.normal(),
-            number_giga: Red.normal(),
-            number_huge: Purple.normal(),
-
-            unit_byte: Green.normal(),
-            unit_kilo: Green.bold(),
-            unit_mega: Yellow.normal(),
-            unit_giga: Red.normal(),
-            unit_huge: Purple.normal(),
+            major: Some(Green.bold()),
+            minor: Some(Green.normal()),
+
+            number_byte: Some(Green.normal()),
+            number_kilo: Some(Green.bold()),
+            number_mega: Some(Yellow.normal()),
+            number_giga: Some(Red.normal()),
+            number_huge: Some(Purple.normal()),
+
+            unit_byte: Some(Green.normal()),
+            unit_kilo: Some(Green.bold()),
+            unit_mega: Some(Yellow.normal()),
+            unit_giga: Some(Red.normal()),
+            unit_huge: Some(Purple.normal()),
         }
     }
 }

+ 14 - 14
src/theme/mod.rs

@@ -64,7 +64,7 @@ impl Options {
         }
 
         // Parse the environment variables into colours and extension mappings
-        let mut ui = UiStyles::default_theme(self.colour_scale);
+        let mut ui = UiStyles::default(self.colour_scale);
         let (exts, use_default_filetypes) = self.definitions.parse_color_vars(&mut ui);
 
         // Use between 0 and 2 file name highlighters
@@ -496,7 +496,7 @@ mod customs_test {
     test!(ls_bd:   ls "bd=36", exa ""  =>  colours c -> { c.filekinds.block_device = Cyan.normal();   });
     test!(ls_cd:   ls "cd=35", exa ""  =>  colours c -> { c.filekinds.char_device  = Purple.normal(); });
     test!(ls_ln:   ls "ln=34", exa ""  =>  colours c -> { c.filekinds.symlink      = Blue.normal();   });
-    test!(ls_or:   ls "or=33", exa ""  =>  colours c -> { c.broken_symlink         = Yellow.normal(); });
+    test!(ls_or:   ls "or=33", exa ""  =>  colours c -> { c.broken_symlink         = Some(Yellow.normal()); });
 
     // EZA_COLORS can affect all those colours too:
     test!(exa_di:  ls "", exa "di=32"  =>  colours c -> { c.filekinds.directory    = Green.normal();  });
@@ -507,7 +507,7 @@ mod customs_test {
     test!(exa_bd:  ls "", exa "bd=35"  =>  colours c -> { c.filekinds.block_device = Purple.normal(); });
     test!(exa_cd:  ls "", exa "cd=34"  =>  colours c -> { c.filekinds.char_device  = Blue.normal();   });
     test!(exa_ln:  ls "", exa "ln=33"  =>  colours c -> { c.filekinds.symlink      = Yellow.normal(); });
-    test!(exa_or:  ls "", exa "or=32"  =>  colours c -> { c.broken_symlink         = Green.normal();  });
+    test!(exa_or:  ls "", exa "or=32"  =>  colours c -> { c.broken_symlink         = Some(Green.normal());  });
 
     // EZA_COLORS will even override options from LS_COLORS:
     test!(ls_exa_di: ls "di=31", exa "di=32"  =>  colours c -> { c.filekinds.directory  = Green.normal();  });
@@ -575,16 +575,16 @@ mod customs_test {
     test!(exa_gi:  ls "", exa "gi=38;5;128"  =>  colours c -> { c.git.ignored                           = Fixed(128).normal(); });
     test!(exa_gc:  ls "", exa "gc=38;5;129"  =>  colours c -> { c.git.conflicted                        = Fixed(129).normal(); });
 
-    test!(exa_xx:  ls "", exa "xx=38;5;128"  =>  colours c -> { c.punctuation                           = Fixed(128).normal(); });
-    test!(exa_da:  ls "", exa "da=38;5;129"  =>  colours c -> { c.date                                  = Fixed(129).normal(); });
-    test!(exa_in:  ls "", exa "in=38;5;130"  =>  colours c -> { c.inode                                 = Fixed(130).normal(); });
-    test!(exa_bl:  ls "", exa "bl=38;5;131"  =>  colours c -> { c.blocks                                = Fixed(131).normal(); });
-    test!(exa_hd:  ls "", exa "hd=38;5;132"  =>  colours c -> { c.header                                = Fixed(132).normal(); });
-    test!(exa_lp:  ls "", exa "lp=38;5;133"  =>  colours c -> { c.symlink_path                          = Fixed(133).normal(); });
-    test!(exa_cc:  ls "", exa "cc=38;5;134"  =>  colours c -> { c.control_char                          = Fixed(134).normal(); });
-    test!(exa_oc:  ls "", exa "oc=38;5;135"  =>  colours c -> { c.octal                                 = Fixed(135).normal(); });
-    test!(exa_ff:  ls "", exa "ff=38;5;136"  =>  colours c -> { c.flags                                 = Fixed(136).normal(); });
-    test!(exa_bo:  ls "", exa "bO=4"         =>  colours c -> { c.broken_path_overlay                   = Style::default().underline(); });
+    test!(exa_xx:  ls "", exa "xx=38;5;128"  =>  colours c -> { c.punctuation                           = Some(Fixed(128).normal()); });
+    test!(exa_da:  ls "", exa "da=38;5;129"  =>  colours c -> { c.date                                  = Some(Fixed(129).normal()); });
+    test!(exa_in:  ls "", exa "in=38;5;130"  =>  colours c -> { c.inode                                 = Some(Fixed(130).normal()); });
+    test!(exa_bl:  ls "", exa "bl=38;5;131"  =>  colours c -> { c.blocks                                = Some(Fixed(131).normal()); });
+    test!(exa_hd:  ls "", exa "hd=38;5;132"  =>  colours c -> { c.header                                = Some(Fixed(132).normal()); });
+    test!(exa_lp:  ls "", exa "lp=38;5;133"  =>  colours c -> { c.symlink_path                          = Some(Fixed(133).normal()); });
+    test!(exa_cc:  ls "", exa "cc=38;5;134"  =>  colours c -> { c.control_char                          = Some(Fixed(134).normal()); });
+    test!(exa_oc:  ls "", exa "oc=38;5;135"  =>  colours c -> { c.octal                                 = Some(Fixed(135).normal()); });
+    test!(exa_ff:  ls "", exa "ff=38;5;136"  =>  colours c -> { c.flags                                 = Some(Fixed(136).normal()); });
+    test!(exa_bo:  ls "", exa "bO=4"         =>  colours c -> { c.broken_path_overlay                   = Some(Style::default().underline()); });
 
     test!(exa_mp:  ls "", exa "mp=1;34;4"    =>  colours c -> { c.filekinds.mount_point                 = Blue.bold().underline(); });
     test!(exa_sp:  ls "", exa "sp=1;35;4"    =>  colours c -> { c.filekinds.special                     = Purple.bold().underline(); });
@@ -636,7 +636,7 @@ mod customs_test {
 
     // Finally, colours get applied right-to-left:
     test!(ls_overwrite:  ls "pi=31:pi=32:pi=33", exa ""  =>  colours c -> { c.filekinds.pipe = Yellow.normal(); });
-    test!(exa_overwrite: ls "", exa "da=36:da=35:da=34"  =>  colours c -> { c.date = Blue.normal(); });
+    test!(exa_overwrite: ls "", exa "da=36:da=35:da=34"  =>  colours c -> { c.date = Some(Blue.normal()); });
 
     // Parse keys and extensions
     test!(ls_fi_ls_txt:   ls "fi=33:*.txt=31", exa "" => colours c -> { c.filekinds.normal = Yellow.normal(); }, exts [ ("*.txt", Red.normal()) ]);

+ 303 - 210
src/theme/ui_styles.rs

@@ -1,170 +1,265 @@
-use nu_ansi_term::Style;
-
+use crate::output::color_scale::ColorScaleOptions;
 use crate::theme::lsc::Pair;
+use libc::SYS_socketpair;
+use nu_ansi_term::Color::{self, *};
+use nu_ansi_term::Style;
+use serde::{Deserialize, Serialize};
+use std::default::Default;
 
 #[rustfmt::skip]
-#[derive(Debug, Default, PartialEq)]
+#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
 pub struct UiStyles {
-    pub colourful: bool,
-
-    pub filekinds:        FileKinds,
-    pub perms:            Permissions,
-    pub size:             Size,
-    pub users:            Users,
-    pub links:            Links,
-    pub git:              Git,
-    pub git_repo:         GitRepo,
-    pub security_context: SecurityContext,
-    pub file_type:        FileType,
-
-    pub punctuation:  Style,          // xx
-    pub date:         Style,          // da
-    pub inode:        Style,          // in
-    pub blocks:       Style,          // bl
-    pub header:       Style,          // hd
-    pub octal:        Style,          // oc
-    pub flags:        Style,          // ff
+    pub colourful: Option<bool>,
+
+    pub filekinds:        Option<FileKinds>,
+    pub perms:            Option<Permissions>,
+    pub size:             Option<Size>,
+    pub users:            Option<Users>,
+    pub links:           Option<Links>,
+    pub git:              Option<Git>,
+    pub git_repo:         Option<GitRepo>,
+    pub security_context: Option<SecurityContext>,
+    pub file_type:        Option<FileType>,
+
+    pub punctuation:  Option<Style>,          // xx
+    pub date:         Option<Style>,          // da
+    pub inode:        Option<Style>,          // in
+    pub blocks:       Option<Style>,          // bl
+    pub header:       Option<Style>,          // hd
+    pub octal:        Option<Style>,          // oc
+    pub flags:        Option<Style>,          // ff
 
     pub icon:         Option<Style>,  // ic
-
-    pub symlink_path:         Style,  // lp
-    pub control_char:         Style,  // cc
-    pub broken_symlink:       Style,  // or
-    pub broken_path_overlay:  Style,  // bO
+    pub symlink_path:         Option<Style>,  // lp
+    pub control_char:         Option<Style>,  // cc
+    pub broken_symlink:       Option<Style>,  // or
+    pub broken_path_overlay:  Option<Style>,  // bO
 }
 
 #[rustfmt::skip]
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
+#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
 pub struct FileKinds {
-    pub normal: Style,        // fi
-    pub directory: Style,     // di
-    pub symlink: Style,       // ln
-    pub pipe: Style,          // pi
-    pub block_device: Style,  // bd
-    pub char_device: Style,   // cd
-    pub socket: Style,        // so
-    pub special: Style,       // sp
-    pub executable: Style,    // ex
-    pub mount_point: Style,   // mp
+    pub normal: Option<Style>,        // fi
+    pub directory: Option<Style>,     // di
+    pub symlink: Option<Style>,       // ln
+    pub pipe: Option<Style>,          // pi
+    pub block_device: Option<Style>,  // bd
+    pub char_device: Option<Style>,   // cd
+    pub socket: Option<Style>,        // so
+    pub special: Option<Style>,       // sp
+    pub executable: Option<Style>,    // ex
+    pub mount_point: Option<Style>,   // mp
+}
+
+impl Default for FileKinds {
+    fn default() -> Self {
+        Self {
+            normal: Some(Style::default()),
+            directory: Some(Blue.bold()),
+            symlink: Some(Cyan.normal()),
+            pipe: Some(Yellow.normal()),
+            block_device: Some(Yellow.bold()),
+            char_device: Some(Yellow.bold()),
+            socket: Some(Red.bold()),
+            special: Some(Yellow.normal()),
+            executable: Some(Green.bold()),
+            mount_point: Some(Blue.bold().underline()),
+        }
+    }
+}
+
+impl FileKinds {
+    pub fn colourful(scale: ColorScaleOptions) -> Self {
+        Self {
+            normal: Some(Style::default()),
+            directory: Some(scale.blue.bold()),
+            symlink: Some(scale.cyan.normal()),
+            pipe: Some(scale.yellow.normal()),
+            block_device: Some(scale.yellow.bold()),
+            char_device: Some(scale.yellow.bold()),
+            socket: Some(scale.red.bold()),
+            special: Some(scale.yellow.normal()),
+            executable: Some(scale.green.bold()),
+            mount_point: Some(scale.blue.bold().underline()),
+        }
+    }
 }
 
 #[rustfmt::skip]
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
+#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
 pub struct Permissions {
-    pub user_read:          Style,  // ur
-    pub user_write:         Style,  // uw
-    pub user_execute_file:  Style,  // ux
-    pub user_execute_other: Style,  // ue
+    pub user_read:         Option<Style>,  // ur
+    pub user_write:         Option<Style>,  // uw
+    pub user_execute_file:  Option<Style>,  // ux
+    pub user_execute_other: Option<Style>,  // ue
 
-    pub group_read:    Style,       // gr
-    pub group_write:   Style,       // gw
-    pub group_execute: Style,       // gx
+    pub group_read:    Option<Style>,       // gr
+    pub group_write:   Option<Style>,       // gw
+    pub group_execute: Option<Style>,       // gx
 
-    pub other_read:    Style,       // tr
-    pub other_write:   Style,       // tw
-    pub other_execute: Style,       // tx
+    pub other_read:    Option<Style>,       // tr
+    pub other_write:   Option<Style>,       // tw
+    pub other_execute: Option<Style>,       // tx
 
-    pub special_user_file: Style,   // su
-    pub special_other:     Style,   // sf
+    pub special_user_file: Option<Style>,   // su
+    pub special_other:     Option<Style>,   // sf
 
-    pub attribute: Style,           // xa
+    pub attribute: Option<Style>,           // xa
 }
 
 #[rustfmt::skip]
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
+#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
 pub struct Size {
-    pub major: Style,        // df
-    pub minor: Style,        // ds
-
-    pub number_byte: Style,  // sn nb
-    pub number_kilo: Style,  // sn nk
-    pub number_mega: Style,  // sn nm
-    pub number_giga: Style,  // sn ng
-    pub number_huge: Style,  // sn nt
-
-    pub unit_byte: Style,    // sb ub
-    pub unit_kilo: Style,    // sb uk
-    pub unit_mega: Style,    // sb um
-    pub unit_giga: Style,    // sb ug
-    pub unit_huge: Style,    // sb ut
+    pub major: Option<Style>,        // df
+    pub minor: Option<Style>,        // ds
+
+    pub number_byte: Option<Style>,  // sn nb
+    pub number_kilo: Option<Style>,  // sn nk
+    pub number_mega: Option<Style>,  // sn nm
+    pub number_giga: Option<Style>,  // sn ng
+    pub number_huge: Option<Style>,  // sn nt
+
+    pub unit_byte: Option<Style>,    // sb ub
+    pub unit_kilo: Option<Style>,    // sb uk
+    pub unit_mega: Option<Style>,    // sb um
+    pub unit_giga: Option<Style>,    // sb ug
+    pub unit_huge: Option<Style>,    // sb ut
 }
 
 #[rustfmt::skip]
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
+#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
 pub struct Users {
-    pub user_you: Style,           // uu
-    pub user_root: Style,          // uR
-    pub user_other: Style,         // un
-    pub group_yours: Style,        // gu
-    pub group_other: Style,        // gn
-    pub group_root: Style,         // gR
+    pub user_you: Option<Style>,           // uu
+    pub user_root: Option<Style>,          // uR
+    pub user_other: Option<Style>,         // un
+    pub group_yours: Option<Style>,        // gu
+    pub group_other: Option<Style>,        // gn
+    pub group_root: Option<Style>,         // gR
 }
 
 #[rustfmt::skip]
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
+#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
 pub struct Links {
-    pub normal: Style,           // lc
-    pub multi_link_file: Style,  // lm
+    pub normal: Option<Style>,           // lc
+    pub multi_link_file: Option<Style>,  // lm
 }
 
 #[rustfmt::skip]
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
+#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
 pub struct Git {
-    pub new: Style,         // ga
-    pub modified: Style,    // gm
-    pub deleted: Style,     // gd
-    pub renamed: Style,     // gv
-    pub typechange: Style,  // gt
-    pub ignored: Style,     // gi
-    pub conflicted: Style,  // gc
+    pub new: Option<Style>,         // ga
+    pub modified: Option<Style>,    // gm
+    pub deleted: Option<Style>,     // gd
+    pub renamed: Option<Style>,     // gv
+    pub typechange: Option<Style>,  // gt
+    pub ignored: Option<Style>,     // gi
+    pub conflicted: Option<Style>,  // gc
+}
+
+impl Default for Git {
+    fn default() -> Self {
+        Git {
+            new: Some(Green.normal()),
+            modified: Some(Blue.normal()),
+            deleted: Some(Red.normal()),
+            renamed: Some(Yellow.normal()),
+            typechange: Some(Purple.normal()),
+            ignored: Some(Style::default().dimmed()),
+            conflicted: Some(Red.normal()),
+        }
+    }
 }
 
 #[rustfmt::skip]
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
+#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
 pub struct GitRepo {
-    pub branch_main: Style,  //Gm
-    pub branch_other: Style, //Go
-    pub git_clean: Style,    //Gc
-    pub git_dirty: Style,    //Gd
+    pub branch_main: Option<Style>,  //Gm
+    pub branch_other: Option<Style>, //Go
+    pub git_clean: Option<Style>,    //Gc
+    pub git_dirty: Option<Style>,    //Gd
+}
+
+impl Default for GitRepo {
+    fn default() -> Self {
+        Self {
+            branch_main: Some(Green.normal()),
+            branch_other: Some(Yellow.normal()),
+            git_clean: Some(Green.normal()),
+            git_dirty: Some(Yellow.bold()),
+        }
+    }
 }
 
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
+#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
 pub struct SELinuxContext {
-    pub colon: Style,
-    pub user: Style,  // Su
-    pub role: Style,  // Sr
-    pub typ: Style,   // St
-    pub range: Style, // Sl
+    pub colon: Option<Style>,
+    pub user: Option<Style>,  // Su
+    pub role: Option<Style>,  // Sr
+    pub typ: Option<Style>,   // St
+    pub range: Option<Style>, // Sl
 }
 
 #[rustfmt::skip]
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
+#[derive(Clone, Copy, Debug, PartialEq)]
 pub struct SecurityContext {
-    pub none:    Style, // Sn
-    pub selinux: SELinuxContext,
+    pub none:    Option<Style>, // Sn
+    pub selinux: Option<SELinuxContext>,
+}
+
+impl Default for SecurityContext {
+    fn default() -> Self {
+        SecurityContext {
+            none: Some(Style::default()),
+            selinux: Some(SELinuxContext {
+                colon: Some(Style::default().dimmed()),
+                user: Some(Blue.normal()),
+                role: Some(Green.normal()),
+                typ: Some(Yellow.normal()),
+                range: Some(Cyan.normal()),
+            }),
+        }
+    }
 }
 
 /// Drawing styles based on the type of file (video, image, compressed, etc)
 #[rustfmt::skip]
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
+#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
 pub struct FileType {
-    pub image: Style,       // im - image file
-    pub video: Style,       // vi - video file
-    pub music: Style,       // mu - lossy music
-    pub lossless: Style,    // lo - lossless music
-    pub crypto: Style,      // cr - related to cryptography
-    pub document: Style,    // do - document file
-    pub compressed: Style,  // co - compressed file
-    pub temp: Style,        // tm - temporary file
-    pub compiled: Style,    // cm - compilation artifact
-    pub build: Style,       // bu - file that is used to build a project
-    pub source: Style,      // sc - source code
+    pub image: Option<Style>,       // im - image file
+    pub video: Option<Style>,       // vi - video file
+    pub music: Option<Style>,       // mu - lossy music
+    pub lossless: Option<Style>,    // lo - lossless music
+    pub crypto: Option<Style>,      // cr - related to cryptography
+    pub document: Option<Style>,    // do - document file
+    pub compressed: Option<Style>,  // co - compressed file
+    pub temp: Option<Style>,        // tm - temporary file
+    pub compiled: Option<Style>,    // cm - compilation artifact
+    pub build: Option<Style>,       // bu - file that is used to build a project
+    pub source: Option<Style>,      // sc - source code
 }
 
 impl UiStyles {
     pub fn plain() -> Self {
         Self::default()
     }
+
+    pub fn from_yaml(file: Option<&str>) -> Self {
+        if let Some(file) = file {
+            let file = std::fs::File::open(file);
+            if let Err(e) = file {
+                eprintln!("Could not open theme file: {}", e);
+                return Self::default();
+            }
+            let file = file.expect("Could not open theme file");
+            let theme: UiStyles = serde_yaml::from_reader(file).unwrap_or_else(|e| {
+                eprintln!("Could not parse theme file: {}", e);
+                Self::default()
+            });
+            theme
+        } else {
+            Self::default()
+        }
+    }
 }
 
 impl UiStyles {
@@ -174,15 +269,15 @@ impl UiStyles {
     pub fn set_ls(&mut self, pair: &Pair<'_>) -> bool {
         #[rustfmt::skip]
         match pair.key {
-            "di" => self.filekinds.directory    = pair.to_style(),  // DIR
-            "ex" => self.filekinds.executable   = pair.to_style(),  // EXEC
-            "fi" => self.filekinds.normal       = pair.to_style(),  // FILE
-            "pi" => self.filekinds.pipe         = pair.to_style(),  // FIFO
-            "so" => self.filekinds.socket       = pair.to_style(),  // SOCK
-            "bd" => self.filekinds.block_device = pair.to_style(),  // BLK
-            "cd" => self.filekinds.char_device  = pair.to_style(),  // CHR
-            "ln" => self.filekinds.symlink      = pair.to_style(),  // LINK
-            "or" => self.broken_symlink         = pair.to_style(),  // ORPHAN
+            "di" => self.filekinds.directory    = Some(pair.to_style()),  // DIR
+            "ex" => self.filekinds.executable   = Some(pair.to_style()),  // EXEC
+            "fi" => self.filekinds.normal       = Some(pair.to_style()),  // FILE
+            "pi" => self.filekinds.pipe         = Some(pair.to_style()),  // FIFO
+            "so" => self.filekinds.socket       = Some(pair.to_style()),  // SOCK
+            "bd" => self.filekinds.block_device = Some(pair.to_style()),  // BLK
+            "cd" => self.filekinds.char_device  = Some(pair.to_style()),  // CHR
+            "ln" => self.filekinds.symlink      = Some(pair.to_style()),  // LINK
+            "or" => self.broken_symlink         = Some(pair.to_style()),  // ORPHAN
              _   => return false,
              // Codes we don’t do anything with:
              // MULTIHARDLINK, DOOR, SETUID, SETGID, CAPABILITY,
@@ -198,92 +293,90 @@ impl UiStyles {
     pub fn set_exa(&mut self, pair: &Pair<'_>) -> bool {
         #[rustfmt::skip]
         match pair.key {
-            "ur" => self.perms.user_read                = pair.to_style(),
-            "uw" => self.perms.user_write               = pair.to_style(),
-            "ux" => self.perms.user_execute_file        = pair.to_style(),
-            "ue" => self.perms.user_execute_other       = pair.to_style(),
-            "gr" => self.perms.group_read               = pair.to_style(),
-            "gw" => self.perms.group_write              = pair.to_style(),
-            "gx" => self.perms.group_execute            = pair.to_style(),
-            "tr" => self.perms.other_read               = pair.to_style(),
-            "tw" => self.perms.other_write              = pair.to_style(),
-            "tx" => self.perms.other_execute            = pair.to_style(),
-            "su" => self.perms.special_user_file        = pair.to_style(),
-            "sf" => self.perms.special_other            = pair.to_style(),
-            "xa" => self.perms.attribute                = pair.to_style(),
+            "ur" => self.perms.user_read                = Some(pair.to_style()),
+            "uw" => self.perms.user_write               = Some(pair.to_style()),
+            "ux" => self.perms.user_execute_file        = Some(pair.to_style()),
+            "ue" => self.perms.user_execute_other       = Some(pair.to_style()),
+            "gr" => self.perms.group_read               = Some(pair.to_style()),
+            "gw" => self.perms.group_write              = Some(pair.to_style()),
+            "gx" => self.perms.group_execute            = Some(pair.to_style()),
+            "tr" => self.perms.other_read               = Some(pair.to_style()),
+            "tw" => self.perms.other_write              = Some(pair.to_style()),
+            "tx" => self.perms.other_execute            = Some(pair.to_style()),
+            "su" => self.perms.special_user_file        = Some(pair.to_style()),
+            "sf" => self.perms.special_other            = Some(pair.to_style()),
+            "xa" => self.perms.attribute                = Some(pair.to_style()),
 
             "sn" => self.set_number_style(pair.to_style()),
             "sb" => self.set_unit_style(pair.to_style()),
-            "nb" => self.size.number_byte               = pair.to_style(),
-            "nk" => self.size.number_kilo               = pair.to_style(),
-            "nm" => self.size.number_mega               = pair.to_style(),
-            "ng" => self.size.number_giga               = pair.to_style(),
-            "nt" => self.size.number_huge               = pair.to_style(),
-            "ub" => self.size.unit_byte                 = pair.to_style(),
-            "uk" => self.size.unit_kilo                 = pair.to_style(),
-            "um" => self.size.unit_mega                 = pair.to_style(),
-            "ug" => self.size.unit_giga                 = pair.to_style(),
-            "ut" => self.size.unit_huge                 = pair.to_style(),
-            "df" => self.size.major                     = pair.to_style(),
-            "ds" => self.size.minor                     = pair.to_style(),
-
-            "uu" => self.users.user_you                 = pair.to_style(),
-            "un" => self.users.user_other               = pair.to_style(),
-            "uR" => self.users.user_root                = pair.to_style(),
-            "gu" => self.users.group_yours              = pair.to_style(),
-            "gn" => self.users.group_other              = pair.to_style(),
-            "gR" => self.users.group_root               = pair.to_style(),
-
-            "lc" => self.links.normal                   = pair.to_style(),
-            "lm" => self.links.multi_link_file          = pair.to_style(),
-
-            "ga" => self.git.new                        = pair.to_style(),
-            "gm" => self.git.modified                   = pair.to_style(),
-            "gd" => self.git.deleted                    = pair.to_style(),
-            "gv" => self.git.renamed                    = pair.to_style(),
-            "gt" => self.git.typechange                 = pair.to_style(),
-            "gi" => self.git.ignored                    = pair.to_style(),
-            "gc" => self.git.conflicted                 = pair.to_style(),
-
-            "Gm" => self.git_repo.branch_main           = pair.to_style(),
-            "Go" => self.git_repo.branch_other          = pair.to_style(),
-            "Gc" => self.git_repo.git_clean             = pair.to_style(),
-            "Gd" => self.git_repo.git_dirty             = pair.to_style(),
-
-            "xx" => self.punctuation                    = pair.to_style(),
-            "da" => self.date                           = pair.to_style(),
-            "in" => self.inode                          = pair.to_style(),
-            "bl" => self.blocks                         = pair.to_style(),
-            "hd" => self.header                         = pair.to_style(),
-            "oc" => self.octal                          = pair.to_style(),
-            "ff" => self.flags                          = pair.to_style(),
+            "nb" => self.size.number_byte               = Some(pair.to_style()),
+            "nk" => self.size.number_kilo               = Some(pair.to_style()),
+            "nm" => self.size.number_mega               = Some(pair.to_style()),
+            "ng" => self.size.number_giga               = Some(pair.to_style()),
+            "nt" => self.size.number_huge               = Some(pair.to_style()),
+            "ub" => self.size.unit_byte                 = Some(pair.to_style()),
+            "uk" => self.size.unit_kilo                 = Some(pair.to_style()),
+            "um" => self.size.unit_mega                 = Some(pair.to_style()),
+            "ug" => self.size.unit_giga                 = Some(pair.to_style()),
+            "ut" => self.size.unit_huge                 = Some(pair.to_style()),
+            "df" => self.size.major                     = Some(pair.to_style()),
+            "ds" => self.size.minor                     = Some(pair.to_style()),
+
+            "uu" => self.users.user_you                 = Some(pair.to_style()),
+            "un" => self.users.user_other               = Some(pair.to_style()),
+            "uR" => self.users.user_root                = Some(pair.to_style()),
+            "gu" => self.users.group_yours              = Some(pair.to_style()),
+            "gn" => self.users.group_other              = Some(pair.to_style()),
+            "gR" => self.users.group_root               = Some(pair.to_style()),
+
+            "lc" => self.links.normal                   = Some(pair.to_style()),
+            "lm" => self.links.multi_link_file          = Some(pair.to_style()),
+
+            "ga" => self.git.new                        = Some(pair.to_style()),
+            "gm" => self.git.modified                   = Some(pair.to_style()),
+            "gd" => self.git.deleted                    = Some(pair.to_style()),
+            "gv" => self.git.renamed                    = Some(pair.to_style()),
+            "gt" => self.git.typechange                 = Some(pair.to_style()),
+            "gi" => self.git.ignored                    = Some(pair.to_style()),
+            "gc" => self.git.conflicted                 = Some(pair.to_style()),
+
+            "Gm" => self.git_repo.branch_main           = Some(pair.to_style()),
+            "Go" => self.git_repo.branch_other          = Some(pair.to_style()),
+            "Gc" => self.git_repo.git_clean             = Some(pair.to_style()),
+            "Gd" => self.git_repo.git_dirty             = Some(pair.to_style()),
 
             "ic" => self.icon                           = Some(pair.to_style()),
 
-            "lp" => self.symlink_path                   = pair.to_style(),
-            "cc" => self.control_char                   = pair.to_style(),
-            "bO" => self.broken_path_overlay            = pair.to_style(),
-
-            "mp" => self.filekinds.mount_point          = pair.to_style(),
-            "sp" => self.filekinds.special              = pair.to_style(),  // Catch-all for unrecognized file kind
-
-            "im" => self.file_type.image                = pair.to_style(),
-            "vi" => self.file_type.video                = pair.to_style(),
-            "mu" => self.file_type.music                = pair.to_style(),
-            "lo" => self.file_type.lossless             = pair.to_style(),
-            "cr" => self.file_type.crypto               = pair.to_style(),
-            "do" => self.file_type.document             = pair.to_style(),
-            "co" => self.file_type.compressed           = pair.to_style(),
-            "tm" => self.file_type.temp                 = pair.to_style(),
-            "cm" => self.file_type.compiled             = pair.to_style(),
-            "bu" => self.file_type.build                = pair.to_style(),
-            "sc" => self.file_type.source               = pair.to_style(),
-
-            "Sn" => self.security_context.none          = pair.to_style(),
-            "Su" => self.security_context.selinux.user  = pair.to_style(),
-            "Sr" => self.security_context.selinux.role  = pair.to_style(),
-            "St" => self.security_context.selinux.typ   = pair.to_style(),
-            "Sl" => self.security_context.selinux.range = pair.to_style(),
+            "xx" => self.punctuation                    = Some(pair.to_style()),
+            "da" => self.date                           = Some(pair.to_style()),
+            "in" => self.inode                          = Some(pair.to_style()),
+            "bl" => self.blocks                         = Some(pair.to_style()),
+            "hd" => self.header                         = Some(pair.to_style()),
+            "oc" => self.octal                          = Some(pair.to_style()),
+            "ff" => self.flags                          = Some(pair.to_style()),
+            "lp" => self.symlink_path                   = Some(pair.to_style()),
+            "cc" => self.control_char                   = Some(pair.to_style()),
+            "bO" => self.broken_path_overlay            = Some(pair.to_style()),
+            "mp" => self.filekinds.mount_point          = Some(pair.to_style()),
+            "sp" => self.filekinds.special              = Some(pair.to_style()),  // Catch-all for unrecognized file kind
+
+            "im" => self.file_type.image                = Some(pair.to_style()),
+            "vi" => self.file_type.video                = Some(pair.to_style()),
+            "mu" => self.file_type.music                = Some(pair.to_style()),
+            "lo" => self.file_type.lossless             = Some(pair.to_style()),
+            "cr" => self.file_type.crypto               = Some(pair.to_style()),
+            "do" => self.file_type.document             = Some(pair.to_style()),
+            "co" => self.file_type.compressed           = Some(pair.to_style()),
+            "tm" => self.file_type.temp                 = Some(pair.to_style()),
+            "cm" => self.file_type.compiled             = Some(pair.to_style()),
+            "bu" => self.file_type.build                = Some(pair.to_style()),
+            "sc" => self.file_type.source               = Some(pair.to_style()),
+
+            "Sn" => self.security_context.none          = Some(pair.to_style()),
+            "Su" => self.security_context.selinux.user  = Some(pair.to_style()),
+            "Sr" => self.security_context.selinux.role  = Some(pair.to_style()),
+            "St" => self.security_context.selinux.typ   = Some(pair.to_style()),
+            "Sl" => self.security_context.selinux.range = Some(pair.to_style()),
 
              _   => return false,
         };
@@ -292,18 +385,18 @@ impl UiStyles {
     }
 
     pub fn set_number_style(&mut self, style: Style) {
-        self.size.number_byte = style;
-        self.size.number_kilo = style;
-        self.size.number_mega = style;
-        self.size.number_giga = style;
-        self.size.number_huge = style;
+        self.size.number_byte = Some(style);
+        self.size.number_kilo = Some(style);
+        self.size.number_mega = Some(style);
+        self.size.number_giga = Some(style);
+        self.size.number_huge = Some(style);
     }
 
     pub fn set_unit_style(&mut self, style: Style) {
-        self.size.unit_byte = style;
-        self.size.unit_kilo = style;
-        self.size.unit_mega = style;
-        self.size.unit_giga = style;
-        self.size.unit_huge = style;
+        self.size.unit_byte = Some(style);
+        self.size.unit_kilo = Some(style);
+        self.size.unit_mega = Some(style);
+        self.size.unit_giga = Some(style);
+        self.size.unit_huge = Some(style);
     }
 }