Просмотр исходного кода

feat: theme file configuration base

PThorpe92 2 лет назад
Родитель
Сommit
8715840d3a
7 измененных файлов с 184 добавлено и 90 удалено
  1. 6 4
      man/eza.1.md
  2. 134 0
      src/options/config.rs
  3. 2 1
      src/options/flags.rs
  4. 1 2
      src/options/mod.rs
  5. 4 8
      src/options/theme.rs
  6. 26 21
      src/theme/mod.rs
  7. 11 54
      src/theme/ui_styles.rs

+ 6 - 4
man/eza.1.md

@@ -47,6 +47,11 @@ META OPTIONS
 `-v`, `--version`
 `-v`, `--version`
 : Show version of eza.
 : Show version of eza.
 
 
+`--write-theme=DIR`
+: Write _eza_ default theme.yml file to the directory passed as argument, or defaults to the current working directory.
+
+`--config` [if eza was built with config support]
+: Specify a custom path to configuration file.
 
 
 DISPLAY OPTIONS
 DISPLAY OPTIONS
 ===============
 ===============
@@ -257,9 +262,6 @@ Alternatively, `<FORMAT>` can be a two line string, the first line will be used
 `--stdin`
 `--stdin`
 : When you wish to pipe directories to eza/read from stdin. Separate one per line or define custom separation char in `EZA_STDIN_SEPARATOR` env variable.
 : When you wish to pipe directories to eza/read from stdin. Separate one per line or define custom separation char in `EZA_STDIN_SEPARATOR` env variable.
 
 
-`--write-theme=DIR`
-: Write _eza_ default theme.yml file to the directory passed as argument, or defaults to the current working directory.
-
 `-@`, `--extended`
 `-@`, `--extended`
 : List each file’s extended attributes and sizes.
 : List each file’s extended attributes and sizes.
 
 
@@ -350,7 +352,7 @@ Any explicit use of the `--icons=WHEN` flag overrides this behavior.
 
 
 Specifies the separator to use when file names are piped from stdin. Defaults to newline.
 Specifies the separator to use when file names are piped from stdin. Defaults to newline.
 
 
-## EZA_CONFIG_DIR
+## `EZA_CONFIG_DIR`
 
 
 Specifies the directory where eza will look for its configuration and theme files. Defaults to `$XDG_CONFIG_HOME/eza` or `$HOME/.config/eza` if `XDG_CONFIG_HOME` is not set.
 Specifies the directory where eza will look for its configuration and theme files. Defaults to `$XDG_CONFIG_HOME/eza` or `$HOME/.config/eza` if `XDG_CONFIG_HOME` is not set.
 
 

+ 134 - 0
src/options/config.rs

@@ -0,0 +1,134 @@
+use crate::options::{MatchedFlags, Vars};
+use crate::output::color_scale::ColorScaleOptions;
+use crate::theme::UiStyles;
+use dirs;
+use serde_yaml;
+use std::{ffi::OsStr, path::PathBuf};
+
+use super::{flags, OptionsError};
+
+#[derive(Debug, Default, Eq, PartialEq)]
+pub struct ThemeConfig {
+    pub location: ConfigLoc,
+    pub theme: UiStyles,
+}
+
+#[derive(Debug, Default, PartialEq, Eq)]
+pub enum ConfigLoc {
+    #[default]
+    Default, // $XDG_CONFIG_HOME/eza/config|theme.yml
+    Env(PathBuf), // $EZA_CONFIG_DIR
+    Arg(PathBuf), // --config path/to/config|theme.yml
+}
+
+impl ThemeConfig {
+    pub fn write_default_theme_file(path: Option<&OsStr>) -> std::io::Result<()> {
+        if path.is_some_and(|path| std::path::Path::new(path).is_dir()) {
+            let path = std::path::Path::new(path.unwrap());
+            let path = path.join("theme.yml");
+            let file = std::fs::File::create(path.clone())?;
+            println!("Writing default theme to {:?}", path);
+            serde_yaml::to_writer(file, &UiStyles::default())
+                .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
+        } else {
+            let default_path = std::env::var("EZA_CONFIG_DIR")
+                .map(|dir| PathBuf::from(&dir))
+                .unwrap_or(dirs::config_dir().unwrap_or_default().join("eza"));
+            if !default_path.exists() {
+                std::fs::create_dir_all(&default_path)?;
+            }
+            println!("Writing default theme to {:?}", default_path);
+            let default_file = default_path.join("theme.yml");
+            let file = std::fs::File::create(default_file)?;
+            let default = UiStyles::default();
+            serde_yaml::to_writer(file, &default)
+                .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
+        }
+    }
+
+    pub fn theme_from_yaml(file: Option<&str>) -> UiStyles {
+        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 UiStyles::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}");
+                UiStyles::default()
+            });
+            theme
+        } else {
+            UiStyles::default()
+        }
+    }
+    pub fn deduce<V: Vars>(
+        matches: &MatchedFlags<'_>,
+        vars: &V,
+        opts: ColorScaleOptions,
+    ) -> Result<ThemeConfig, crate::options::OptionsError> {
+        println!("Deducing theme");
+        if matches.has(&flags::WRITE_THEME)? {
+            let path = matches.get(&flags::WRITE_THEME)?;
+            println!("Writing default theme to {:?}", path);
+            let err = Self::write_default_theme_file(path).map_err(|e| e.to_string());
+            if let Err(err) = err {
+                return Err(OptionsError::WriteTheme(err));
+            }
+        }
+        let theme_file = if matches.has(&flags::THEME)? {
+            let path = matches.get(&flags::THEME)?;
+            // passing --config will require a value as we will check default location
+            if path.is_none() {
+                return Err(OptionsError::BadArgument(&flags::THEME, "no value".into()));
+            }
+            path.map(|p| p.to_string_lossy().to_string())
+        } else {
+            None
+        };
+        Ok(Self::find_with_fallback(theme_file, vars, opts))
+    }
+
+    pub fn find_with_fallback<V: Vars>(
+        path: Option<String>,
+        vars: &V,
+        opts: ColorScaleOptions,
+    ) -> Self {
+        if let Some(path) = path {
+            let path = std::path::PathBuf::from(path);
+            if path.is_dir() && path.exists() {
+                let path = path
+                    .join("theme.yml")
+                    .exists()
+                    .then(|| path.join("theme.yml"));
+                match path {
+                    Some(path) => {
+                        let file = std::fs::read_to_string(&path).unwrap_or_default();
+                        let uistyles: Option<UiStyles> = serde_yaml::from_str(&file).ok();
+                        return Self {
+                            location: ConfigLoc::Arg(path),
+                            theme: uistyles.unwrap_or(UiStyles::default_theme(opts)),
+                        };
+                    }
+                    None => return Self::default(),
+                }
+            }
+        } else if vars.get("EZA_CONFIG_DIR").is_some() {
+            let path = std::path::PathBuf::from(&format!(
+                "{}/theme.yml",
+                vars.get("EZA_CONFIG_DIR").unwrap().to_string_lossy()
+            ));
+            if path.exists() {
+                let file = std::fs::read_to_string(&path).unwrap_or_default();
+                let uistyles: Option<UiStyles> = serde_yaml::from_str(&file).ok();
+                return Self {
+                    location: ConfigLoc::Env(path),
+                    theme: uistyles.unwrap_or(UiStyles::default_theme(opts)),
+                };
+            }
+            return Self::default();
+        };
+        Self::default()
+    }
+}

+ 2 - 1
src/options/flags.rs

@@ -87,6 +87,7 @@ pub static SECURITY_CONTEXT:  Arg = Arg { short: Some(b'Z'), long: "context",
 pub static STDIN:             Arg = Arg { short: None,       long: "stdin",                takes_value: TakesValue::Forbidden };
 pub static STDIN:             Arg = Arg { short: None,       long: "stdin",                takes_value: TakesValue::Forbidden };
 pub static FILE_FLAGS:        Arg = Arg { short: Some(b'O'), long: "flags",                takes_value: TakesValue::Forbidden };
 pub static FILE_FLAGS:        Arg = Arg { short: Some(b'O'), long: "flags",                takes_value: TakesValue::Forbidden };
 pub static WRITE_THEME:       Arg = Arg { short: None,       long: "write-theme",          takes_value: TakesValue::Optional(None, ".")};
 pub static WRITE_THEME:       Arg = Arg { short: None,       long: "write-theme",          takes_value: TakesValue::Optional(None, ".")};
+pub static THEME:             Arg = Arg { short: None,       long: "theme",                takes_value: TakesValue::Optional(None, ".")};
 pub static ALL_ARGS: Args = Args(&[
 pub static ALL_ARGS: Args = Args(&[
     &VERSION, &HELP,
     &VERSION, &HELP,
 
 
@@ -102,5 +103,5 @@ pub static ALL_ARGS: Args = Args(&[
     &NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &SMART_GROUP, &NO_SYMLINKS, &SHOW_SYMLINKS,
     &NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &SMART_GROUP, &NO_SYMLINKS, &SHOW_SYMLINKS,
 
 
     &GIT, &NO_GIT, &GIT_REPOS, &GIT_REPOS_NO_STAT,
     &GIT, &NO_GIT, &GIT_REPOS, &GIT_REPOS_NO_STAT,
-    &EXTENDED, &OCTAL, &SECURITY_CONTEXT, &STDIN, &FILE_FLAGS, &WRITE_THEME
+    &EXTENDED, &OCTAL, &SECURITY_CONTEXT, &STDIN, &FILE_FLAGS, &WRITE_THEME, &THEME
 ]);
 ]);

+ 1 - 2
src/options/mod.rs

@@ -95,7 +95,7 @@ use self::parser::MatchedFlags;
 
 
 pub mod vars;
 pub mod vars;
 pub use self::vars::Vars;
 pub use self::vars::Vars;
-
+pub mod config;
 pub mod stdin;
 pub mod stdin;
 mod version;
 mod version;
 
 
@@ -200,7 +200,6 @@ impl Options {
                 "Options --git and --git-ignore can't be used because `git` feature was disabled in this build of exa"
                 "Options --git and --git-ignore can't be used because `git` feature was disabled in this build of exa"
             )));
             )));
         }
         }
-
         let view = View::deduce(matches, vars)?;
         let view = View::deduce(matches, vars)?;
         let dir_action = DirAction::deduce(matches, matches!(view.mode, Mode::Details(_)))?;
         let dir_action = DirAction::deduce(matches, matches!(view.mode, Mode::Details(_)))?;
         let filter = FileFilter::deduce(matches)?;
         let filter = FileFilter::deduce(matches)?;

+ 4 - 8
src/options/theme.rs

@@ -1,20 +1,15 @@
 use crate::options::parser::MatchedFlags;
 use crate::options::parser::MatchedFlags;
 use crate::options::{flags, vars, OptionsError, Vars};
 use crate::options::{flags, vars, OptionsError, Vars};
 use crate::output::color_scale::ColorScaleOptions;
 use crate::output::color_scale::ColorScaleOptions;
-use crate::theme::UiStyles;
 use crate::theme::{Definitions, Options, UseColours};
 use crate::theme::{Definitions, Options, UseColours};
 
 
+use super::config::ThemeConfig;
+
 impl Options {
 impl Options {
     pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
     pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
         let use_colours = UseColours::deduce(matches, vars)?;
         let use_colours = UseColours::deduce(matches, vars)?;
         let colour_scale = ColorScaleOptions::deduce(matches, vars)?;
         let colour_scale = ColorScaleOptions::deduce(matches, vars)?;
-        if matches.has(&flags::WRITE_THEME)? {
-            let path = matches.get(&flags::WRITE_THEME)?;
-            let err = UiStyles::write_default_theme_file(path).map_err(|e| e.to_string());
-            if let Err(err) = err {
-                return Err(OptionsError::WriteTheme(err));
-            }
-        }
+        let theme_config = ThemeConfig::deduce(matches, vars, colour_scale)?;
         let definitions = if use_colours == UseColours::Never {
         let definitions = if use_colours == UseColours::Never {
             Definitions::default()
             Definitions::default()
         } else {
         } else {
@@ -25,6 +20,7 @@ impl Options {
             use_colours,
             use_colours,
             colour_scale,
             colour_scale,
             definitions,
             definitions,
+            theme_config,
         })
         })
     }
     }
 }
 }

+ 26 - 21
src/theme/mod.rs

@@ -2,6 +2,7 @@ use nu_ansi_term::Style;
 
 
 use crate::fs::File;
 use crate::fs::File;
 use crate::info::filetype::FileType;
 use crate::info::filetype::FileType;
+use crate::options::config::ThemeConfig;
 use crate::output::color_scale::ColorScaleOptions;
 use crate::output::color_scale::ColorScaleOptions;
 use crate::output::file_name::Colours as FileNameColours;
 use crate::output::file_name::Colours as FileNameColours;
 use crate::output::render;
 use crate::output::render;
@@ -21,6 +22,8 @@ pub struct Options {
     pub colour_scale: ColorScaleOptions,
     pub colour_scale: ColorScaleOptions,
 
 
     pub definitions: Definitions,
     pub definitions: Definitions,
+
+    pub theme_config: ThemeConfig,
 }
 }
 
 
 /// Under what circumstances we should display coloured, rather than plain,
 /// Under what circumstances we should display coloured, rather than plain,
@@ -55,27 +58,29 @@ pub struct Theme {
 
 
 impl Options {
 impl Options {
     pub fn to_theme(&self, isatty: bool) -> Theme {
     pub fn to_theme(&self, isatty: bool) -> Theme {
+        // If the user has explicitly turned off colours, or if we’re not
+        // outputting to a terminal, then we don’t want to use them.
         if self.use_colours == UseColours::Never
         if self.use_colours == UseColours::Never
             || (self.use_colours == UseColours::Automatic && !isatty)
             || (self.use_colours == UseColours::Automatic && !isatty)
         {
         {
             let ui = UiStyles::plain();
             let ui = UiStyles::plain();
             let exts = Box::new(NoFileStyle);
             let exts = Box::new(NoFileStyle);
-            return Theme { ui, exts };
+            Theme { ui, exts }
+        } else {
+            Theme {
+                ui: self.theme_config.theme.clone(),
+                exts: Box::new(FileTypes),
+            }
         }
         }
-
-        // Parse the environment variables into colours and extension mappings
-        let mut ui = UiStyles::default_theme(self.colour_scale);
-        let (exts, use_default_filetypes) = self.definitions.parse_color_vars(&mut ui);
-
-        // Use between 0 and 2 file name highlighters
-        let exts: Box<dyn FileStyle> = match (exts.is_non_empty(), use_default_filetypes) {
-            (false, false) => Box::new(NoFileStyle),
-            (false, true) => Box::new(FileTypes),
-            (true, false) => Box::new(exts),
-            (true, true) => Box::new((exts, FileTypes)),
-        };
-
-        Theme { ui, exts }
+        //     // Use between 0 and 2 file name highlighters
+        //     let exts: Box<dyn FileStyle> = match (exts.is_non_empty(), use_default_filetypes) {
+        //         (false, false) => Box::new(NoFileStyle),
+        //         (false, true) => Box::new(FileTypes),
+        //         (true, false) => Box::new(exts),
+        //         (true, true) => Box::new((exts, FileTypes)),
+        //     };
+        //
+        //     Theme { ui, exts }
     }
     }
 }
 }
 
 
@@ -264,7 +269,7 @@ impl render::FiletypeColours for Theme {
 
 
 #[rustfmt::skip]
 #[rustfmt::skip]
 impl render::GitColours for Theme {
 impl render::GitColours for Theme {
-    fn not_modified(&self)  -> Style { self.ui.punctuation.unwrap_or_default() }
+    fn not_modified(&self)  -> Style { self.ui.punctuation() }
     #[allow(clippy::new_ret_no_self)]
     #[allow(clippy::new_ret_no_self)]
     fn new(&self)           -> Style { self.ui.git.unwrap_or_default().new() }
     fn new(&self)           -> Style { self.ui.git.unwrap_or_default().new() }
     fn modified(&self)      -> Style { self.ui.git.unwrap_or_default().modified() }
     fn modified(&self)      -> Style { self.ui.git.unwrap_or_default().modified() }
@@ -277,11 +282,11 @@ impl render::GitColours for Theme {
 
 
 #[rustfmt::skip]
 #[rustfmt::skip]
 impl render::GitRepoColours for Theme {
 impl render::GitRepoColours for Theme {
-    fn branch_main(&self)  -> Style { self.ui.git_repo.unwrap_or_default().branch_main.unwrap_or_default() }
-    fn branch_other(&self) -> Style { self.ui.git_repo.unwrap_or_default().branch_other.unwrap_or_default() }
-    fn no_repo(&self)      -> Style { self.ui.punctuation.unwrap_or_default() }
-    fn git_clean(&self)    -> Style { self.ui.git_repo.unwrap_or_default().git_clean.unwrap_or_default() }
-    fn git_dirty(&self)    -> Style { self.ui.git_repo.unwrap_or_default().git_dirty.unwrap_or_default() }
+    fn branch_main(&self)  -> Style { self.ui.git_repo.unwrap_or_default().branch_main() }
+    fn branch_other(&self) -> Style { self.ui.git_repo.unwrap_or_default().branch_other() }
+    fn no_repo(&self)      -> Style { self.ui.punctuation() }
+    fn git_clean(&self)    -> Style { self.ui.git_repo.unwrap_or_default().git_clean() }
+    fn git_dirty(&self)    -> Style { self.ui.git_repo.unwrap_or_default().git_dirty() }
 }
 }
 
 
 #[rustfmt::skip]
 #[rustfmt::skip]

+ 11 - 54
src/theme/ui_styles.rs

@@ -3,11 +3,9 @@ use nu_ansi_term::Color::*;
 use nu_ansi_term::Style;
 use nu_ansi_term::Style;
 use serde::{Deserialize, Serialize};
 use serde::{Deserialize, Serialize};
 use std::default::Default;
 use std::default::Default;
-use std::ffi::OsStr;
-use std::path::PathBuf;
 
 
 #[rustfmt::skip]
 #[rustfmt::skip]
-#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
 pub struct UiStyles {
 pub struct UiStyles {
     pub colourful: Option<bool>,
     pub colourful: Option<bool>,
 
 
@@ -36,7 +34,6 @@ pub struct UiStyles {
     pub broken_path_overlay:  Option<Style>,  // bO
     pub broken_path_overlay:  Option<Style>,  // bO
 }
 }
 // Macro to generate .unwrap_or_default getters for each field to cut down boilerplate
 // Macro to generate .unwrap_or_default getters for each field to cut down boilerplate
-#[allow(clippy::new_without_default)]
 macro_rules! field_accessors {
 macro_rules! field_accessors {
     ($struct_name:ident, $($field_name:ident: Option<$type:ty>),*) => {
     ($struct_name:ident, $($field_name:ident: Option<$type:ty>),*) => {
         impl $struct_name {
         impl $struct_name {
@@ -73,7 +70,7 @@ field_accessors!(UiStyles, punctuation: Option<Style>, date: Option<Style>, inod
     control_char: Option<Style>, broken_symlink: Option<Style>, broken_path_overlay: Option<Style>);
     control_char: Option<Style>, broken_symlink: Option<Style>, broken_path_overlay: Option<Style>);
 
 
 #[rustfmt::skip]
 #[rustfmt::skip]
-#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
+#[derive(Clone, Eq, Copy, Debug, PartialEq, Serialize, Deserialize)]
 pub struct FileKinds {
 pub struct FileKinds {
     pub normal: Option<Style>,        // fi
     pub normal: Option<Style>,        // fi
     pub directory: Option<Style>,     // di
     pub directory: Option<Style>,     // di
@@ -106,7 +103,7 @@ impl Default for FileKinds {
 field_accessors!(FileKinds, normal: Option<Style>, directory: Option<Style>, symlink: Option<Style>, pipe: Option<Style>, block_device: Option<Style>, char_device: Option<Style>, socket: Option<Style>, special: Option<Style>, executable: Option<Style>, mount_point: Option<Style>);
 field_accessors!(FileKinds, normal: Option<Style>, directory: Option<Style>, symlink: Option<Style>, pipe: Option<Style>, block_device: Option<Style>, char_device: Option<Style>, socket: Option<Style>, special: Option<Style>, executable: Option<Style>, mount_point: Option<Style>);
 
 
 #[rustfmt::skip]
 #[rustfmt::skip]
-#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
+#[derive(Clone, Copy,Eq, Debug, Default, PartialEq, Serialize, Deserialize)]
 pub struct Permissions {
 pub struct Permissions {
     pub user_read:         Option<Style>,  // ur
     pub user_read:         Option<Style>,  // ur
     pub user_write:         Option<Style>,  // uw
     pub user_write:         Option<Style>,  // uw
@@ -129,7 +126,7 @@ pub struct Permissions {
 field_accessors!(Permissions, user_read: Option<Style>, user_write: Option<Style>, user_execute_file: Option<Style>, user_execute_other: Option<Style>, group_read: Option<Style>, group_write: Option<Style>, group_execute: Option<Style>, other_read: Option<Style>, other_write: Option<Style>, other_execute: Option<Style>, special_user_file: Option<Style>, special_other: Option<Style>, attribute: Option<Style>);
 field_accessors!(Permissions, user_read: Option<Style>, user_write: Option<Style>, user_execute_file: Option<Style>, user_execute_other: Option<Style>, group_read: Option<Style>, group_write: Option<Style>, group_execute: Option<Style>, other_read: Option<Style>, other_write: Option<Style>, other_execute: Option<Style>, special_user_file: Option<Style>, special_other: Option<Style>, attribute: Option<Style>);
 
 
 #[rustfmt::skip]
 #[rustfmt::skip]
-#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
+#[derive(Clone, Copy, Eq, Debug, Default, PartialEq, Serialize, Deserialize)]
 pub struct Size {
 pub struct Size {
     pub major: Option<Style>,        // df
     pub major: Option<Style>,        // df
     pub minor: Option<Style>,        // ds
     pub minor: Option<Style>,        // ds
@@ -149,7 +146,7 @@ pub struct Size {
 field_accessors!(Size, major: Option<Style>, minor: Option<Style>, number_byte: Option<Style>, number_kilo: Option<Style>, number_mega: Option<Style>, number_giga: Option<Style>, number_huge: Option<Style>, unit_byte: Option<Style>, unit_kilo: Option<Style>, unit_mega: Option<Style>, unit_giga: Option<Style>, unit_huge: Option<Style>);
 field_accessors!(Size, major: Option<Style>, minor: Option<Style>, number_byte: Option<Style>, number_kilo: Option<Style>, number_mega: Option<Style>, number_giga: Option<Style>, number_huge: Option<Style>, unit_byte: Option<Style>, unit_kilo: Option<Style>, unit_mega: Option<Style>, unit_giga: Option<Style>, unit_huge: Option<Style>);
 
 
 #[rustfmt::skip]
 #[rustfmt::skip]
-#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
+#[derive(Clone, Copy, Debug,Eq, Default, PartialEq, Serialize, Deserialize)]
 pub struct Users {
 pub struct Users {
     pub user_you: Option<Style>,           // uu
     pub user_you: Option<Style>,           // uu
     pub user_root: Option<Style>,          // uR
     pub user_root: Option<Style>,          // uR
@@ -161,7 +158,7 @@ pub struct Users {
 field_accessors!(Users, user_you: Option<Style>, user_root: Option<Style>, user_other: Option<Style>, group_yours: Option<Style>, group_other: Option<Style>, group_root: Option<Style>);
 field_accessors!(Users, user_you: Option<Style>, user_root: Option<Style>, user_other: Option<Style>, group_yours: Option<Style>, group_other: Option<Style>, group_root: Option<Style>);
 
 
 #[rustfmt::skip]
 #[rustfmt::skip]
-#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
+#[derive(Clone, Copy, Debug, Eq, Default, PartialEq, Serialize, Deserialize)]
 pub struct Links {
 pub struct Links {
     pub normal: Option<Style>,           // lc
     pub normal: Option<Style>,           // lc
     pub multi_link_file: Option<Style>,  // lm
     pub multi_link_file: Option<Style>,  // lm
@@ -169,7 +166,7 @@ pub struct Links {
 field_accessors!(Links, normal: Option<Style>, multi_link_file: Option<Style>);
 field_accessors!(Links, normal: Option<Style>, multi_link_file: Option<Style>);
 
 
 #[rustfmt::skip]
 #[rustfmt::skip]
-#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
+#[derive(Clone, Copy, Debug,Eq, PartialEq, Serialize, Deserialize)]
 pub struct Git {
 pub struct Git {
     pub new: Option<Style>,         // ga
     pub new: Option<Style>,         // ga
     pub modified: Option<Style>,    // gm
     pub modified: Option<Style>,    // gm
@@ -196,7 +193,7 @@ impl Default for Git {
 }
 }
 
 
 #[rustfmt::skip]
 #[rustfmt::skip]
-#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
 pub struct GitRepo {
 pub struct GitRepo {
     pub branch_main: Option<Style>,  //Gm
     pub branch_main: Option<Style>,  //Gm
     pub branch_other: Option<Style>, //Go
     pub branch_other: Option<Style>, //Go
@@ -215,7 +212,7 @@ impl Default for GitRepo {
     }
     }
 }
 }
 
 
-#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
+#[derive(Clone, Copy, Debug, Eq, Default, PartialEq, Serialize, Deserialize)]
 pub struct SELinuxContext {
 pub struct SELinuxContext {
     pub colon: Option<Style>,
     pub colon: Option<Style>,
     pub user: Option<Style>,  // Su
     pub user: Option<Style>,  // Su
@@ -226,7 +223,7 @@ pub struct SELinuxContext {
 field_accessors!(SELinuxContext, colon: Option<Style>, user: Option<Style>, role: Option<Style>, typ: Option<Style>, range: Option<Style>);
 field_accessors!(SELinuxContext, colon: Option<Style>, user: Option<Style>, role: Option<Style>, typ: Option<Style>, range: Option<Style>);
 
 
 #[rustfmt::skip]
 #[rustfmt::skip]
-#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
+#[derive(Clone, Eq, Copy, Debug, PartialEq, Serialize, Deserialize)]
 pub struct SecurityContext {
 pub struct SecurityContext {
     pub none:    Option<Style>, // Sn
     pub none:    Option<Style>, // Sn
     pub selinux: Option<SELinuxContext>,
     pub selinux: Option<SELinuxContext>,
@@ -250,7 +247,7 @@ impl Default for SecurityContext {
 
 
 /// Drawing styles based on the type of file (video, image, compressed, etc)
 /// Drawing styles based on the type of file (video, image, compressed, etc)
 #[rustfmt::skip]
 #[rustfmt::skip]
-#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
+#[derive(Clone, Copy, Debug, Eq, Default, PartialEq, Serialize, Deserialize)]
 pub struct FileType {
 pub struct FileType {
     pub image: Option<Style>,       // im - image file
     pub image: Option<Style>,       // im - image file
     pub video: Option<Style>,       // vi - video file
     pub video: Option<Style>,       // vi - video file
@@ -270,46 +267,6 @@ impl UiStyles {
         Self::default()
         Self::default()
     }
     }
 
 
-    pub fn write_default_theme_file(path: Option<&OsStr>) -> std::io::Result<()> {
-        let default_path = std::env::var("EZA_CONFIG_DIR").map(|dir| PathBuf::from(&dir)).unwrap_or({
-             dirs::config_dir().unwrap_or_default().join("eza")
-        });
-        if let Ok(dir) = std::env::var("EZA_CONFIG_DIR") {
-            let dir = std::path::PathBuf::from(&dir);
-            if !dir.exists() {
-            std::fs::create_dir_all(dir)?;
-            }
-        }
-        if path.is_some_and(|path| std::path::PathBuf::from(path).is_dir()) {
-            let path = PathBuf::from(path.unwrap());
-            let path = path.join(PathBuf::from("default-theme.yml")); 
-            let file = std::fs::File::create(path)?;
-            serde_yaml::to_writer(file, &Self::default()).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
-        } else {
-            let default_file = default_path.join("default-theme.yml");
-            let file = std::fs::File::create(default_file)?;
-            let default = Self::default();
-            serde_yaml::to_writer(file, &default).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
-        }
-    }
-
-    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 {
 impl UiStyles {