Repository: eza-community/eza (language: rust)

Issue:
feat:  `LS_ICONS` for icon overrides
This is a continuation of the unaddressed exa issue: https://github.com/ogham/exa/issues/559

`LS_ICONS` would make a good corollary to `LS_COLORS` for overriding eza's internal file extension to icon mappings.
Example derived from (https://github.com/ogham/exa/issues/559#issuecomment-517953531):
```
LS_ICONS="*.rb=e21e:*.vim=e62b:*.sh=f292:"
```



Discussion / hints:

Fix patch:
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index cccc8dc27..92b05c08b 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -49,7 +49,13 @@ completions/nush/* @PThorpe92
 # Grid + Output 
 src/output/grid.rs @PThorpe92
 src/output/grid_details.rs @PThorpe92
-src/output/file_name.rs @PThorpe92
+src/output/file_name.rs @PThorpe92 @gierens
+
+# Config + config parsing
+src/options/config.rs @PThorpe92 @gierens
+src/options/theme.rs @PThorpe92 @gierens
+src/theme/* @PThorpe92 @gierens
+themes/* @PThorpe92 @gierens
 
 # Options parsing
 src/options/* @MartinFillon
diff --git a/Cargo.lock b/Cargo.lock
index aec8240ed..f8b6d70a4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -346,6 +346,27 @@ dependencies = [
  "powerfmt",
 ]
 
+[[package]]
+name = "dirs"
+version = "5.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys 0.48.0",
+]
+
 [[package]]
 name = "dunce"
 version = "1.0.4"
@@ -392,6 +413,7 @@ dependencies = [
  "ansi-width",
  "chrono",
  "criterion",
+ "dirs",
  "git2",
  "glob",
  "libc",
@@ -408,6 +430,8 @@ dependencies = [
  "plist",
  "proc-mounts",
  "rayon",
+ "serde",
+ "serde_yaml",
  "terminal_size",
  "timeago",
  "trycmd",
@@ -452,6 +476,17 @@ dependencies = [
  "percent-encoding",
 ]
 
+[[package]]
+name = "getrandom"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
 [[package]]
 name = "git2"
 version = "0.19.0"
@@ -613,6 +648,17 @@ dependencies = [
  "pkg-config",
 ]
 
+[[package]]
+name = "libredox"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
+dependencies = [
+ "bitflags 2.4.0",
+ "libc",
+ "redox_syscall 0.4.1",
+]
+
 [[package]]
 name = "libz-sys"
 version = "1.1.2"
@@ -685,6 +731,7 @@ version = "0.50.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dd2800e1520bdc966782168a627aa5d1ad92e33b984bf7c7615d31280c83ff14"
 dependencies = [
+ "serde",
  "windows-sys 0.48.0",
 ]
 
@@ -744,6 +791,12 @@ dependencies = [
  "vcpkg",
 ]
 
+[[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
 [[package]]
 name = "os_pipe"
 version = "1.1.4"
@@ -978,6 +1031,26 @@ dependencies = [
  "bitflags 1.3.2",
 ]
 
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
+dependencies = [
+ "getrandom",
+ "libredox",
+ "thiserror",
+]
+
 [[package]]
 name = "regex"
 version = "1.9.5"
@@ -1043,18 +1116,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 +1154,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 +1401,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"
@@ -1377,6 +1469,12 @@ dependencies = [
  "winapi-util",
 ]
 
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
 [[package]]
 name = "wasm-bindgen"
 version = "0.2.87"
diff --git a/Cargo.toml b/Cargo.toml
index 3e1abcc16..cc5ffb210 100644
--- a/Cargo.toml
+++ b/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,10 @@ 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"
+dirs = "5.0.1"
 [dependencies.git2]
 version = "0.19"
 optional = true
diff --git a/man/eza.1.md b/man/eza.1.md
index 56873d9cf..1917ab23f 100644
--- a/man/eza.1.md
+++ b/man/eza.1.md
@@ -47,7 +47,6 @@ META OPTIONS
 `-v`, `--version`
 : Show version of eza.
 
-
 DISPLAY OPTIONS
 ===============
 
@@ -347,6 +346,10 @@ 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.
 
+## `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.
+
 EXIT STATUSES
 =============
 
diff --git a/rustfmt.toml b/rustfmt.toml
deleted file mode 100644
index 8b1378917..000000000
--- a/rustfmt.toml
+++ /dev/null
@@ -1,1 +0,0 @@
-
diff --git a/src/options/config.rs b/src/options/config.rs
new file mode 100644
index 000000000..15597f754
--- /dev/null
+++ b/src/options/config.rs
@@ -0,0 +1,538 @@
+use crate::theme::ThemeFileType as FileType;
+use crate::theme::*;
+use nu_ansi_term::{Color, Style};
+use serde::{Deserialize, Serialize};
+use serde_yaml;
+use std::collections::HashMap;
+use std::path::PathBuf;
+
+#[derive(Debug, Default, Eq, PartialEq)]
+pub struct ThemeConfig {
+    // This is rather bare for now, will be expanded with config file
+    location: ConfigLoc,
+}
+
+#[derive(Debug, Default, PartialEq, Eq)]
+pub enum ConfigLoc {
+    #[default]
+    Default, // $XDG_CONFIG_HOME/eza/config|theme.yml
+    Env(PathBuf), // $EZA_CONFIG_DIR
+}
+
+trait FromOverride<T>: Sized {
+    fn from(value: T, default: Self) -> Self;
+}
+
+impl<S, T> FromOverride<Option<S>> for Option<T>
+where
+    T: FromOverride<S> + Default,
+{
+    fn from(value: Option<S>, default: Option<T>) -> Option<T> {
+        match (value, default) {
+            (Some(value), Some(default)) => Some(FromOverride::from(value, default)),
+            (Some(value), None) => Some(FromOverride::from(value, T::default())),
+            (None, Some(default)) => Some(default),
+            (None, None) => None,
+        }
+    }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Default)]
+pub struct StyleOverride {
+    /// The style's foreground color, if it has one.
+    pub foreground: Option<Color>,
+
+    /// The style's background color, if it has one.
+    pub background: Option<Color>,
+
+    /// Whether this style is bold.
+    pub is_bold: Option<bool>,
+
+    /// Whether this style is dimmed.
+    pub is_dimmed: Option<bool>,
+
+    /// Whether this style is italic.
+    pub is_italic: Option<bool>,
+
+    /// Whether this style is underlined.
+    pub is_underline: Option<bool>,
+
+    /// Whether this style is blinking.
+    pub is_blink: Option<bool>,
+
+    /// Whether this style has reverse colors.
+    pub is_reverse: Option<bool>,
+
+    /// Whether this style is hidden.
+    pub is_hidden: Option<bool>,
+
+    /// Whether this style is struckthrough.
+    pub is_strikethrough: Option<bool>,
+
+    /// Wether this style is always displayed starting with a reset code to clear any remaining style artifacts
+    pub prefix_with_reset: Option<bool>,
+}
+
+impl FromOverride<StyleOverride> for Style {
+    fn from(value: StyleOverride, default: Self) -> Self {
+        let mut style = default;
+        if value.foreground.is_some() {
+            style.foreground = value.foreground;
+        }
+        if value.background.is_some() {
+            style.background = value.background;
+        }
+        if let Some(bold) = value.is_bold {
+            style.is_bold = bold;
+        }
+        if let Some(dimmed) = value.is_dimmed {
+            style.is_dimmed = dimmed;
+        }
+        if let Some(italic) = value.is_italic {
+            style.is_italic = italic;
+        }
+        if let Some(underline) = value.is_underline {
+            style.is_underline = underline;
+        }
+        if let Some(blink) = value.is_blink {
+            style.is_blink = blink;
+        }
+        if let Some(reverse) = value.is_reverse {
+            style.is_reverse = reverse;
+        }
+        if let Some(hidden) = value.is_hidden {
+            style.is_hidden = hidden;
+        }
+        if let Some(strikethrough) = value.is_strikethrough {
+            style.is_strikethrough = strikethrough;
+        }
+        if let Some(reset) = value.prefix_with_reset {
+            style.prefix_with_reset = reset;
+        }
+        style
+    }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
+pub struct IconStyleOverride {
+    pub glyph: Option<char>,
+    pub style: Option<StyleOverride>,
+}
+
+impl FromOverride<char> for char {
+    fn from(value: char, _default: char) -> char {
+        value
+    }
+}
+
+impl FromOverride<IconStyleOverride> for IconStyle {
+    fn from(value: IconStyleOverride, default: Self) -> Self {
+        IconStyle {
+            glyph: FromOverride::from(value.glyph, default.glyph),
+            style: FromOverride::from(value.style, default.style),
+        }
+    }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
+pub struct FileNameStyleOverride {
+    pub icon: Option<IconStyleOverride>,
+    pub filename: Option<StyleOverride>,
+}
+
+impl FromOverride<FileNameStyleOverride> for FileNameStyle {
+    fn from(value: FileNameStyleOverride, default: Self) -> Self {
+        FileNameStyle {
+            icon: FromOverride::from(value.icon, default.icon),
+            filename: FromOverride::from(value.filename, default.filename),
+        }
+    }
+}
+
+impl<R, S, T> FromOverride<HashMap<R, S>> for HashMap<R, T>
+where
+    T: FromOverride<S>,
+    R: Clone + Eq + std::hash::Hash,
+    T: Clone + Eq + Default,
+{
+    fn from(value: HashMap<R, S>, default: HashMap<R, T>) -> HashMap<R, T> {
+        let mut result = default.clone();
+        for (r, s) in value {
+            let t = match default.get(&r) {
+                Some(t) => t.clone(),
+                None => T::default(),
+            };
+            result.insert(r, FromOverride::from(s, t));
+        }
+        result
+    }
+}
+
+#[rustfmt::skip]
+#[derive(Clone, Eq, Copy, Debug, PartialEq, Serialize, Deserialize)]
+pub struct FileKindsOverride {
+    pub normal: Option<StyleOverride>,        // fi
+    pub directory: Option<StyleOverride>,     // di
+    pub symlink: Option<StyleOverride>,       // ln
+    pub pipe: Option<StyleOverride>,          // pi
+    pub block_device: Option<StyleOverride>,  // bd
+    pub char_device: Option<StyleOverride>,   // cd
+    pub socket: Option<StyleOverride>,        // so
+    pub special: Option<StyleOverride>,       // sp
+    pub executable: Option<StyleOverride>,    // ex
+    pub mount_point: Option<StyleOverride>,   // mp
+}
+
+impl FromOverride<FileKindsOverride> for FileKinds {
+    fn from(value: FileKindsOverride, default: Self) -> Self {
+        FileKinds {
+            normal: FromOverride::from(value.normal, default.normal),
+            directory: FromOverride::from(value.directory, default.directory),
+            symlink: FromOverride::from(value.symlink, default.directory),
+            pipe: FromOverride::from(value.pipe, default.pipe),
+            block_device: FromOverride::from(value.block_device, default.block_device),
+            char_device: FromOverride::from(value.char_device, default.char_device),
+            socket: FromOverride::from(value.socket, default.socket),
+            special: FromOverride::from(value.special, default.special),
+            executable: FromOverride::from(value.executable, default.executable),
+            mount_point: FromOverride::from(value.mount_point, default.mount_point),
+        }
+    }
+}
+
+#[rustfmt::skip]
+#[derive(Clone, Copy,Eq, Debug, Default, PartialEq, Serialize, Deserialize)]
+pub struct PermissionsOverride {
+    pub user_read:         Option<StyleOverride>,  // ur
+    pub user_write:         Option<StyleOverride>,  // uw
+    pub user_execute_file:  Option<StyleOverride>,  // ux
+    pub user_execute_other: Option<StyleOverride>,  // ue
+
+    pub group_read:    Option<StyleOverride>,       // gr
+    pub group_write:   Option<StyleOverride>,       // gw
+    pub group_execute: Option<StyleOverride>,       // gx
+
+    pub other_read:    Option<StyleOverride>,       // tr
+    pub other_write:   Option<StyleOverride>,       // tw
+    pub other_execute: Option<StyleOverride>,       // tx
+
+    pub special_user_file: Option<StyleOverride>,   // su
+    pub special_other:     Option<StyleOverride>,   // sf
+
+    pub attribute: Option<StyleOverride>,           // xa
+}
+
+impl FromOverride<PermissionsOverride> for Permissions {
+    fn from(value: PermissionsOverride, default: Self) -> Self {
+        Permissions {
+            user_read: FromOverride::from(value.user_read, default.user_read),
+            user_write: FromOverride::from(value.user_write, default.user_write),
+            user_execute_file: FromOverride::from(
+                value.user_execute_file,
+                default.user_execute_file,
+            ),
+            user_execute_other: FromOverride::from(
+                value.user_execute_other,
+                default.user_execute_other,
+            ),
+            group_read: FromOverride::from(value.group_read, default.group_read),
+            group_write: FromOverride::from(value.group_write, default.group_write),
+            group_execute: FromOverride::from(value.group_execute, default.group_execute),
+            other_read: FromOverride::from(value.other_read, default.other_read),
+            other_write: FromOverride::from(value.other_write, default.other_write),
+            other_execute: FromOverride::from(value.other_execute, default.other_execute),
+            special_user_file: FromOverride::from(
+                value.special_user_file,
+                default.special_user_file,
+            ),
+            special_other: FromOverride::from(value.special_other, default.special_other),
+            attribute: FromOverride::from(value.attribute, default.attribute),
+        }
+    }
+}
+
+#[rustfmt::skip]
+#[derive(Clone, Copy, Eq, Debug, Default, PartialEq, Serialize, Deserialize)]
+pub struct SizeOverride {
+    pub major: Option<StyleOverride>,        // df
+    pub minor: Option<StyleOverride>,        // ds
+
+    pub number_byte: Option<StyleOverride>,  // sn nb
+    pub number_kilo: Option<StyleOverride>,  // sn nk
+    pub number_mega: Option<StyleOverride>,  // sn nm
+    pub number_giga: Option<StyleOverride>,  // sn ng
+    pub number_huge: Option<StyleOverride>,  // sn nt
+
+    pub unit_byte: Option<StyleOverride>,    // sb ub
+    pub unit_kilo: Option<StyleOverride>,    // sb uk
+    pub unit_mega: Option<StyleOverride>,    // sb um
+    pub unit_giga: Option<StyleOverride>,    // sb ug
+    pub unit_huge: Option<StyleOverride>,    // sb ut
+}
+
+impl FromOverride<SizeOverride> for Size {
+    fn from(value: SizeOverride, default: Self) -> Self {
+        Size {
+            major: FromOverride::from(value.major, default.major),
+            minor: FromOverride::from(value.minor, default.minor),
+            number_byte: FromOverride::from(value.number_byte, default.number_byte),
+            number_kilo: FromOverride::from(value.number_kilo, default.number_kilo),
+            number_mega: FromOverride::from(value.number_mega, default.number_mega),
+            number_giga: FromOverride::from(value.number_giga, default.number_giga),
+            number_huge: FromOverride::from(value.number_huge, default.number_huge),
+            unit_byte: FromOverride::from(value.unit_byte, default.unit_byte),
+            unit_kilo: FromOverride::from(value.unit_kilo, default.unit_kilo),
+            unit_mega: FromOverride::from(value.unit_mega, default.unit_mega),
+            unit_giga: FromOverride::from(value.unit_giga, default.unit_giga),
+            unit_huge: FromOverride::from(value.unit_huge, default.unit_huge),
+        }
+    }
+}
+
+#[rustfmt::skip]
+#[derive(Clone, Copy, Debug,Eq, Default, PartialEq, Serialize, Deserialize)]
+pub struct UsersOverride {
+    pub user_you: Option<StyleOverride>,           // uu
+    pub user_root: Option<StyleOverride>,          // uR
+    pub user_other: Option<StyleOverride>,         // un
+    pub group_yours: Option<StyleOverride>,        // gu
+    pub group_other: Option<StyleOverride>,        // gn
+    pub group_root: Option<StyleOverride>,         // gR
+}
+
+impl FromOverride<UsersOverride> for Users {
+    fn from(value: UsersOverride, default: Self) -> Self {
+        Users {
+            user_you: FromOverride::from(value.user_you, default.user_you),
+            user_root: FromOverride::from(value.user_root, default.user_root),
+            user_other: FromOverride::from(value.user_other, default.user_other),
+            group_yours: FromOverride::from(value.group_yours, default.group_yours),
+            group_other: FromOverride::from(value.group_other, default.group_other),
+            group_root: FromOverride::from(value.group_root, default.group_root),
+        }
+    }
+}
+
+#[rustfmt::skip]
+#[derive(Clone, Copy, Debug, Eq, Default, PartialEq, Serialize, Deserialize)]
+pub struct LinksOverride {
+    pub normal: Option<StyleOverride>,           // lc
+    pub multi_link_file: Option<StyleOverride>,  // lm
+}
+
+impl FromOverride<LinksOverride> for Links {
+    fn from(value: LinksOverride, default: Self) -> Self {
+        Links {
+            normal: FromOverride::from(value.normal, default.normal),
+            multi_link_file: FromOverride::from(value.multi_link_file, default.multi_link_file),
+        }
+    }
+}
+
+#[rustfmt::skip]
+#[derive(Clone, Copy, Debug,Eq, PartialEq, Serialize, Deserialize)]
+pub struct GitOverride {
+    pub new: Option<StyleOverride>,         // ga
+    pub modified: Option<StyleOverride>,    // gm
+    pub deleted: Option<StyleOverride>,     // gd
+    pub renamed: Option<StyleOverride>,     // gv
+    pub typechange: Option<StyleOverride>,  // gt
+    pub ignored: Option<StyleOverride>,     // gi
+    pub conflicted: Option<StyleOverride>,  // gc
+}
+
+impl FromOverride<GitOverride> for Git {
+    fn from(value: GitOverride, default: Self) -> Self {
+        Git {
+            new: FromOverride::from(value.new, default.new),
+            modified: FromOverride::from(value.modified, default.modified),
+            deleted: FromOverride::from(value.deleted, default.deleted),
+            renamed: FromOverride::from(value.renamed, default.renamed),
+            typechange: FromOverride::from(value.typechange, default.typechange),
+            ignored: FromOverride::from(value.ignored, default.ignored),
+            conflicted: FromOverride::from(value.conflicted, default.conflicted),
+        }
+    }
+}
+
+#[rustfmt::skip]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
+pub struct GitRepoOverride {
+    pub branch_main: Option<StyleOverride>,  //Gm
+    pub branch_other: Option<StyleOverride>, //Go
+    pub git_clean: Option<StyleOverride>,    //Gc
+    pub git_dirty: Option<StyleOverride>,    //Gd
+}
+
+impl FromOverride<GitRepoOverride> for GitRepo {
+    fn from(value: GitRepoOverride, default: Self) -> Self {
+        GitRepo {
+            branch_main: FromOverride::from(value.branch_main, default.branch_main),
+            branch_other: FromOverride::from(value.branch_other, default.branch_other),
+            git_clean: FromOverride::from(value.git_clean, default.git_clean),
+            git_dirty: FromOverride::from(value.git_dirty, default.git_dirty),
+        }
+    }
+}
+
+#[derive(Clone, Copy, Debug, Eq, Default, PartialEq, Serialize, Deserialize)]
+pub struct SELinuxContextOverride {
+    pub colon: Option<StyleOverride>,
+    pub user: Option<StyleOverride>,  // Su
+    pub role: Option<StyleOverride>,  // Sr
+    pub typ: Option<StyleOverride>,   // St
+    pub range: Option<StyleOverride>, // Sl
+}
+
+impl FromOverride<SELinuxContextOverride> for SELinuxContext {
+    fn from(value: SELinuxContextOverride, default: Self) -> Self {
+        SELinuxContext {
+            colon: FromOverride::from(value.colon, default.colon),
+            user: FromOverride::from(value.user, default.user),
+            role: FromOverride::from(value.role, default.role),
+            typ: FromOverride::from(value.typ, default.typ),
+            range: FromOverride::from(value.range, default.range),
+        }
+    }
+}
+
+#[rustfmt::skip]
+#[derive(Clone, Eq, Copy, Debug, PartialEq, Serialize, Deserialize)]
+pub struct SecurityContextOverride {
+    pub none:    Option<StyleOverride>, // Sn
+    pub selinux: Option<SELinuxContextOverride>,
+}
+
+impl FromOverride<SecurityContextOverride> for SecurityContext {
+    fn from(value: SecurityContextOverride, default: Self) -> Self {
+        SecurityContext {
+            none: FromOverride::from(value.none, default.none),
+            selinux: FromOverride::from(value.selinux, default.selinux),
+        }
+    }
+}
+
+#[rustfmt::skip]
+#[derive(Clone, Copy, Debug, Eq, Default, PartialEq, Serialize, Deserialize)]
+pub struct FileTypeOverride {
+    pub image: Option<StyleOverride>,       // im - image file
+    pub video: Option<StyleOverride>,       // vi - video file
+    pub music: Option<StyleOverride>,       // mu - lossy music
+    pub lossless: Option<StyleOverride>,    // lo - lossless music
+    pub crypto: Option<StyleOverride>,      // cr - related to cryptography
+    pub document: Option<StyleOverride>,    // do - document file
+    pub compressed: Option<StyleOverride>,  // co - compressed file
+    pub temp: Option<StyleOverride>,        // tm - temporary file
+    pub compiled: Option<StyleOverride>,    // cm - compilation artifact
+    pub build: Option<StyleOverride>,       // bu - file that is used to build a project
+    pub source: Option<StyleOverride>,      // sc - source code
+}
+
+impl FromOverride<FileTypeOverride> for FileType {
+    fn from(value: FileTypeOverride, default: Self) -> Self {
+        FileType {
+            image: FromOverride::from(value.image, default.image),
+            video: FromOverride::from(value.video, default.video),
+            music: FromOverride::from(value.music, default.music),
+            lossless: FromOverride::from(value.lossless, default.lossless),
+            crypto: FromOverride::from(value.crypto, default.crypto),
+            document: FromOverride::from(value.document, default.document),
+            compressed: FromOverride::from(value.compressed, default.compressed),
+            temp: FromOverride::from(value.temp, default.temp),
+            compiled: FromOverride::from(value.compiled, default.compiled),
+            build: FromOverride::from(value.build, default.build),
+            source: FromOverride::from(value.source, default.source),
+        }
+    }
+}
+
+#[rustfmt::skip]
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct UiStylesOverride {
+    pub colourful: Option<bool>,
+
+    pub filekinds:        Option<FileKindsOverride>,
+    pub perms:            Option<PermissionsOverride>,
+    pub size:             Option<SizeOverride>,
+    pub users:            Option<UsersOverride>,
+    pub links:            Option<LinksOverride>,
+    pub git:              Option<GitOverride>,
+    pub git_repo:         Option<GitRepoOverride>,
+    pub security_context: Option<SecurityContextOverride>,
+    pub file_type:        Option<FileTypeOverride>,
+
+    pub punctuation:  Option<StyleOverride>,          // xx
+    pub date:         Option<StyleOverride>,          // da
+    pub inode:        Option<StyleOverride>,          // in
+    pub blocks:       Option<StyleOverride>,          // bl
+    pub header:       Option<StyleOverride>,          // hd
+    pub octal:        Option<StyleOverride>,          // oc
+    pub flags:        Option<StyleOverride>,          // ff
+
+    pub symlink_path:         Option<StyleOverride>,  // lp
+    pub control_char:         Option<StyleOverride>,  // cc
+    pub broken_symlink:       Option<StyleOverride>,  // or
+    pub broken_path_overlay:  Option<StyleOverride>,  // bO
+
+    pub filenames: Option<HashMap<String, FileNameStyleOverride>>,
+    pub extensions: Option<HashMap<String, FileNameStyleOverride>>,
+}
+
+impl FromOverride<UiStylesOverride> for UiStyles {
+    fn from(value: UiStylesOverride, default: Self) -> Self {
+        UiStyles {
+            colourful: value.colourful,
+
+            filekinds: FromOverride::from(value.filekinds, default.filekinds),
+            perms: FromOverride::from(value.perms, default.perms),
+            size: FromOverride::from(value.size, default.size),
+            users: FromOverride::from(value.users, default.users),
+            links: FromOverride::from(value.links, default.links),
+            git: FromOverride::from(value.git, default.git),
+            git_repo: FromOverride::from(value.git_repo, default.git_repo),
+            security_context: FromOverride::from(value.security_context, default.security_context),
+            file_type: FromOverride::from(value.file_type, default.file_type),
+
+            punctuation: FromOverride::from(value.punctuation, default.punctuation),
+            date: FromOverride::from(value.date, default.date),
+            inode: FromOverride::from(value.inode, default.inode),
+            blocks: FromOverride::from(value.blocks, default.blocks),
+            header: FromOverride::from(value.header, default.header),
+            octal: FromOverride::from(value.octal, default.octal),
+            flags: FromOverride::from(value.flags, default.flags),
+
+            symlink_path: FromOverride::from(value.symlink_path, default.symlink_path),
+            control_char: FromOverride::from(value.control_char, default.control_char),
+            broken_symlink: FromOverride::from(value.broken_symlink, default.broken_symlink),
+            broken_path_overlay: FromOverride::from(
+                value.broken_path_overlay,
+                default.broken_path_overlay,
+            ),
+
+            filenames: FromOverride::from(value.filenames, default.filenames),
+            extensions: FromOverride::from(value.extensions, default.extensions),
+        }
+    }
+}
+impl ThemeConfig {
+    pub fn from_path(path: &str) -> Self {
+        let path = PathBuf::from(path);
+        ThemeConfig {
+            location: ConfigLoc::Env(path),
+        }
+    }
+    pub fn to_theme(&self) -> Option<UiStyles> {
+        let ui_styles_override: Option<UiStylesOverride> = match &self.location {
+            ConfigLoc::Default => {
+                let path = dirs::config_dir()?.join("eza").join("theme.yml");
+                let file = std::fs::File::open(path).ok()?;
+                serde_yaml::from_reader(&file).ok()
+            }
+            ConfigLoc::Env(path) => {
+                let file = std::fs::File::open(path).ok()?;
+                serde_yaml::from_reader(&file).ok()
+            }
+        };
+        FromOverride::from(ui_styles_override, Some(UiStyles::default()))
+    }
+}
diff --git a/src/options/flags.rs b/src/options/flags.rs
index 8cd538cf3..a707157f0 100644
--- a/src/options/flags.rs
+++ b/src/options/flags.rs
@@ -1,3 +1,4 @@
+#![cfg_attr(rustfmt, rustfmt_skip)]
 use crate::options::parser::{Arg, Args, TakesValue, Values};
 
 // exa options
diff --git a/src/options/mod.rs b/src/options/mod.rs
index c95821522..dbbac960c 100644
--- a/src/options/mod.rs
+++ b/src/options/mod.rs
@@ -79,12 +79,13 @@ use crate::theme::Options as ThemeOptions;
 mod dir_action;
 mod file_name;
 mod filter;
-#[rustfmt::skip] // this module becomes unreadable with rustfmt
+
+mod error;
+#[rustfmt::skip]
 mod flags;
 mod theme;
 mod view;
 
-mod error;
 pub use self::error::{NumberSource, OptionsError};
 
 mod help;
@@ -95,7 +96,7 @@ use self::parser::MatchedFlags;
 
 pub mod vars;
 pub use self::vars::Vars;
-
+pub mod config;
 pub mod stdin;
 mod version;
 
@@ -200,7 +201,6 @@ impl Options {
                 "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 dir_action = DirAction::deduce(matches, matches!(view.mode, Mode::Details(_)))?;
         let filter = FileFilter::deduce(matches)?;
@@ -247,7 +247,7 @@ pub mod test {
     use crate::options::parser::{Arg, MatchedFlags};
     use std::ffi::OsStr;
 
-    #[derive(PartialEq, Eq, Debug)]
+    #[derive(PartialEq, Eq, Debug, Copy, Clone)]
     pub enum Strictnesses {
         Last,
         Complain,
diff --git a/src/options/theme.rs b/src/options/theme.rs
index 80e88f5b4..4b4c83443 100644
--- a/src/options/theme.rs
+++ b/src/options/theme.rs
@@ -2,11 +2,15 @@ use crate::options::parser::MatchedFlags;
 use crate::options::{flags, vars, OptionsError, Vars};
 use crate::output::color_scale::ColorScaleOptions;
 use crate::theme::{Definitions, Options, UseColours};
+use std::path::PathBuf;
+
+use super::config::ThemeConfig;
 
 impl Options {
     pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
         let use_colours = UseColours::deduce(matches, vars)?;
         let colour_scale = ColorScaleOptions::deduce(matches, vars)?;
+        let theme_config = ThemeConfig::deduce(vars);
 
         let definitions = if use_colours == UseColours::Never {
             Definitions::default()
@@ -18,10 +22,33 @@ impl Options {
             use_colours,
             colour_scale,
             definitions,
+            theme_config,
         })
     }
 }
 
+impl ThemeConfig {
+    fn deduce<V: Vars>(vars: &V) -> Option<Self> {
+        if let Some(path) = vars.get("EZA_CONFIG_DIR") {
+            let path = PathBuf::from(path);
+            let path = path.join("theme.yml");
+            if path.exists() {
+                Some(ThemeConfig::from_path(&path.to_string_lossy()))
+            } else {
+                None
+            }
+        } else {
+            let path = dirs::config_dir().unwrap_or_default();
+            let path = path.join("eza").join("theme.yml");
+            if path.exists() {
+                Some(ThemeConfig::default())
+            } else {
+                None
+            }
+        }
+    }
+}
+
 impl UseColours {
     fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
         let default_value = match vars.get(vars::NO_COLOR) {
diff --git a/src/options/vars.rs b/src/options/vars.rs
index cbbe047d8..0f46631da 100644
--- a/src/options/vars.rs
+++ b/src/options/vars.rs
@@ -148,6 +148,5 @@ impl MockVars {
             "NO_COLOR" => self.no_colors = value.clone(),
             _ => (),
         };
-        ()
     }
 }
diff --git a/src/output/color_scale.rs b/src/output/color_scale.rs
index 94a513ab1..33573e2e5 100644
--- a/src/output/color_scale.rs
+++ b/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,
diff --git a/src/output/details.rs b/src/output/details.rs
index 32cd95df7..1a3275a8c 100644
--- a/src/output/details.rs
+++ b/src/output/details.rs
@@ -64,7 +64,7 @@ use std::path::PathBuf;
 use std::vec::IntoIter as VecIntoIter;
 
 use nu_ansi_term::Style;
-use rayon::prelude::*;
+use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
 
 use log::*;
 
@@ -397,7 +397,7 @@ impl<'a> Render<'a> {
         Row {
             tree: TreeParams::new(TreeDepth::root(), false),
             cells: Some(header),
-            name: TextCell::paint_str(self.theme.ui.header, "Name"),
+            name: TextCell::paint_str(self.theme.ui.header.unwrap_or_default(), "Name"),
         }
     }
 
@@ -421,7 +421,10 @@ impl<'a> Render<'a> {
     }
 
     fn render_xattr(&self, xattr: &Attribute, tree: TreeParams) -> Row {
-        let name = TextCell::paint(self.theme.ui.perms.attribute, format!("{xattr}"));
+        let name = TextCell::paint(
+            self.theme.ui.perms.unwrap_or_default().attribute(),
+            format!("{xattr}"),
+        );
         Row {
             cells: None,
             name,
@@ -435,7 +438,7 @@ impl<'a> Render<'a> {
             total_width: table.widths().total(),
             table,
             inner: rows.into_iter(),
-            tree_style: self.theme.ui.punctuation,
+            tree_style: self.theme.ui.punctuation.unwrap_or_default(),
         }
     }
 
@@ -443,7 +446,7 @@ impl<'a> Render<'a> {
         Iter {
             tree_trunk: TreeTrunk::default(),
             inner: rows.into_iter(),
-            tree_style: self.theme.ui.punctuation,
+            tree_style: self.theme.ui.punctuation.unwrap_or_default(),
         }
     }
 }
diff --git a/src/output/file_name.rs b/src/output/file_name.rs
index 1084a7c58..ba8947bc0 100644
--- a/src/output/file_name.rs
+++ b/src/output/file_name.rs
@@ -10,6 +10,7 @@ use crate::output::cell::TextCellContents;
 use crate::output::escape;
 use crate::output::icons::{icon_for_file, iconify_style};
 use crate::output::render::FiletypeColours;
+use crate::theme::FileNameStyle;
 
 /// Basically a file name factory.
 #[derive(Debug, Copy, Clone)]
@@ -189,6 +190,11 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
     /// width calculated.
     pub fn paint(&self) -> TextCellContents {
         let mut bits = Vec::new();
+        let (icon_override, filename_style_override) = match self.colours.style_override(self.file)
+        {
+            Some(FileNameStyle { icon, filename }) => (icon, filename),
+            None => (None, None),
+        };
 
         let spaces_count_opt = match self.options.show_icons {
             ShowIcons::Always(spaces_count) => Some(spaces_count),
@@ -203,9 +209,25 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
         };
 
         if let Some(spaces_count) = spaces_count_opt {
-            let style = iconify_style(self.colours.icon().unwrap_or_else(|| self.style()));
-            let file_icon = icon_for_file(self.file).to_string();
-            bits.push(style.paint(file_icon));
+            let (style, icon) = match icon_override {
+                Some(icon_override) => (
+                    if let Some(style_override) = icon_override.style {
+                        style_override
+                    } else {
+                        iconify_style(self.style())
+                    },
+                    icon_override
+                        .glyph
+                        .unwrap_or_else(|| icon_for_file(self.file))
+                        .to_string(),
+                ),
+                None => (
+                    iconify_style(self.style()),
+                    icon_for_file(self.file).to_string(),
+                ),
+            };
+
+            bits.push(style.paint(icon));
             bits.push(style.paint(" ".repeat(spaces_count as usize)));
         }
 
@@ -222,7 +244,7 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
             // indicate this fact. But when showing targets, we can just
             // colour the path instead (see below), and leave the broken
             // link’s filename as the link colour.
-            for bit in self.escaped_file_name() {
+            for bit in self.escaped_file_name(filename_style_override) {
                 bits.push(bit);
             }
         }
@@ -257,7 +279,7 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
                             mount_style: MountStyle::JustDirectoryNames,
                         };
 
-                        for bit in target_name.escaped_file_name() {
+                        for bit in target_name.escaped_file_name(filename_style_override) {
                             bits.push(bit);
                         }
 
@@ -376,13 +398,16 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
     ///
     /// So in that situation, those characters will be escaped and highlighted in
     /// a different colour.
-    fn escaped_file_name<'unused>(&self) -> Vec<ANSIString<'unused>> {
+    fn escaped_file_name<'unused>(
+        &self,
+        style_override: Option<Style>,
+    ) -> Vec<ANSIString<'unused>> {
         use percent_encoding::{utf8_percent_encode, CONTROLS};
 
         const HYPERLINK_START: &str = "\x1B]8;;";
         const HYPERLINK_END: &str = "\x1B\x5C";
 
-        let file_style = self.style();
+        let file_style = style_override.unwrap_or(self.style());
         let mut bits = Vec::new();
 
         let mut display_hyperlink = false;
@@ -510,7 +535,7 @@ pub trait Colours: FiletypeColours {
     /// The style to paint a directory that has a filesystem mounted on it.
     fn mount_point(&self) -> Style;
 
-    fn icon(&self) -> Option<Style>;
-
     fn colour_file(&self, file: &File<'_>) -> Style;
+
+    fn style_override(&self, file: &File<'_>) -> Option<FileNameStyle>;
 }
diff --git a/src/output/grid_details.rs b/src/output/grid_details.rs
index d3b1f5221..04083b4fa 100644
--- a/src/output/grid_details.rs
+++ b/src/output/grid_details.rs
@@ -216,7 +216,7 @@ impl<'a> Render<'a> {
 
         if self.details.header {
             let row = table.header_row();
-            let name = TextCell::paint_str(self.theme.ui.header, "Name")
+            let name = TextCell::paint_str(self.theme.ui.header.unwrap_or_default(), "Name")
                 .strings()
                 .to_string();
             let s = table.render(row).strings().to_string();
diff --git a/src/output/render/groups.rs b/src/output/render/groups.rs
index 1c4a78514..08b1993bf 100644
--- a/src/output/render/groups.rs
+++ b/src/output/render/groups.rs
@@ -180,7 +180,7 @@ pub mod test {
                 GroupFormat::Regular,
                 file_user
             )
-        )
+        );
     }
 
     #[test]
@@ -203,7 +203,7 @@ pub mod test {
                 GroupFormat::Regular,
                 file_user
             )
-        )
+        );
     }
 
     #[test]
diff --git a/src/output/table.rs b/src/output/table.rs
index fa28a9b23..d951fda3b 100644
--- a/src/output/table.rs
+++ b/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(),
diff --git a/src/theme/default_theme.rs b/src/theme/default_theme.rs
index c9aac2476..560f93a5c 100644
--- a/src/theme/default_theme.rs
+++ b/src/theme/default_theme.rs
@@ -4,124 +4,133 @@ 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 {
         Self {
-            colourful: true,
+            size: Some(Size::colourful(scale)),
+            ..Self::default()
+        }
+    }
+}
+
+impl Default for UiStyles {
+    fn default() -> Self {
+        Self {
+            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(ColorScaleOptions::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(),
-
-            icon: None,
-
-            symlink_path: Cyan.normal(),
-            control_char: Red.normal(),
-            broken_symlink: Red.normal(),
-            broken_path_overlay: 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()),
+
+            symlink_path: Some(Cyan.normal()),
+            control_char: Some(Red.normal()),
+            broken_symlink: Some(Red.normal()),
+            broken_path_overlay: Some(Style::default().underline()),
+
+            filenames: None,
+            extensions: None,
         }
     }
 }
@@ -137,39 +146,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()),
         }
     }
 }
diff --git a/src/theme/mod.rs b/src/theme/mod.rs
index a91f49761..7de9a931b 100644
--- a/src/theme/mod.rs
+++ b/src/theme/mod.rs
@@ -2,12 +2,15 @@ use nu_ansi_term::Style;
 
 use crate::fs::File;
 use crate::info::filetype::FileType;
+use crate::options::config::ThemeConfig;
 use crate::output::color_scale::ColorScaleOptions;
 use crate::output::file_name::Colours as FileNameColours;
 use crate::output::render;
 
 mod ui_styles;
+pub(crate) use self::ui_styles::FileType as ThemeFileType;
 pub use self::ui_styles::UiStyles;
+pub(crate) use self::ui_styles::*;
 
 mod lsc;
 pub use self::lsc::LSColors;
@@ -21,6 +24,8 @@ pub struct Options {
     pub colour_scale: ColorScaleOptions,
 
     pub definitions: Definitions,
+
+    pub theme_config: Option<ThemeConfig>,
 }
 
 /// Under what circumstances we should display coloured, rather than plain,
@@ -61,20 +66,35 @@ impl Options {
             let ui = UiStyles::plain();
             let exts = Box::new(NoFileStyle);
             return Theme { ui, exts };
+        };
+        match self.theme_config {
+            Some(ref theme) => {
+                if let Some(mut ui) = theme.to_theme() {
+                    let (exts, use_default_filetypes) = self.definitions.parse_color_vars(&mut ui);
+                    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)),
+                        };
+                    return Theme { ui, exts };
+                }
+                self.default_theme()
+            }
+            None => self.default_theme(),
         }
+    }
 
-        // Parse the environment variables into colours and extension mappings
+    fn default_theme(&self) -> Theme {
         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 }
     }
 }
@@ -199,19 +219,19 @@ impl FileStyle for FileTypes {
     fn get_style(&self, file: &File<'_>, theme: &Theme) -> Option<Style> {
         #[rustfmt::skip]
         return match FileType::get_file_type(file) {
-            Some(FileType::Image)      => Some(theme.ui.file_type.image),
-            Some(FileType::Video)      => Some(theme.ui.file_type.video),
-            Some(FileType::Music)      => Some(theme.ui.file_type.music),
-            Some(FileType::Lossless)   => Some(theme.ui.file_type.lossless),
-            Some(FileType::Crypto)     => Some(theme.ui.file_type.crypto),
-            Some(FileType::Document)   => Some(theme.ui.file_type.document),
-            Some(FileType::Compressed) => Some(theme.ui.file_type.compressed),
-            Some(FileType::Temp)       => Some(theme.ui.file_type.temp),
-            Some(FileType::Compiled)   => Some(theme.ui.file_type.compiled),
-            Some(FileType::Build)      => Some(theme.ui.file_type.build),
-            Some(FileType::Source)     => Some(theme.ui.file_type.source),
+            Some(FileType::Image)      => theme.ui.file_type.unwrap_or_default().image,
+            Some(FileType::Video)      => theme.ui.file_type.unwrap_or_default().video,
+            Some(FileType::Music)      => theme.ui.file_type.unwrap_or_default().music,
+            Some(FileType::Lossless)   => theme.ui.file_type.unwrap_or_default().lossless,
+            Some(FileType::Crypto)     => theme.ui.file_type.unwrap_or_default().crypto,
+            Some(FileType::Document)   => theme.ui.file_type.unwrap_or_default().document,
+            Some(FileType::Compressed) => theme.ui.file_type.unwrap_or_default().compressed,
+            Some(FileType::Temp)       => theme.ui.file_type.unwrap_or_default().temp,
+            Some(FileType::Compiled)   => theme.ui.file_type.unwrap_or_default().compiled,
+            Some(FileType::Build)      => theme.ui.file_type.unwrap_or_default().build,
+            Some(FileType::Source)     => theme.ui.file_type.unwrap_or_default().source,
             None                       => None
-        };
+    };
     }
 }
 
@@ -221,98 +241,100 @@ impl render::BlocksColours for Theme {
         use number_prefix::Prefix::*;
 
         #[rustfmt::skip]
-        return match prefix {
-            Some(Kilo | Kibi) => self.ui.size.number_kilo,
-            Some(Mega | Mebi) => self.ui.size.number_mega,
-            Some(Giga | Gibi) => self.ui.size.number_giga,
-            Some(_)           => self.ui.size.number_huge,
-            None              => self.ui.size.number_byte,
+        let style = match prefix {
+            Some(Kilo | Kibi) => self.ui.size.unwrap_or_default().number_kilo,
+            Some(Mega | Mebi) => self.ui.size.unwrap_or_default().number_mega,
+            Some(Giga | Gibi) => self.ui.size.unwrap_or_default().number_giga,
+            Some(_)           => self.ui.size.unwrap_or_default().number_huge,
+            None              => self.ui.size.unwrap_or_default().number_byte,
         };
+        style.unwrap_or_default()
     }
 
     fn unit(&self, prefix: Option<number_prefix::Prefix>) -> Style {
         use number_prefix::Prefix::*;
 
         #[rustfmt::skip]
-        return match prefix {
-            Some(Kilo | Kibi) => self.ui.size.unit_kilo,
-            Some(Mega | Mebi) => self.ui.size.unit_mega,
-            Some(Giga | Gibi) => self.ui.size.unit_giga,
-            Some(_)           => self.ui.size.unit_huge,
-            None              => self.ui.size.unit_byte,
+           let style = match prefix {
+            Some(Kilo | Kibi) => self.ui.size.unwrap_or_default().unit_kilo,
+            Some(Mega | Mebi) => self.ui.size.unwrap_or_default().unit_mega,
+            Some(Giga | Gibi) => self.ui.size.unwrap_or_default().unit_giga,
+            Some(_)           => self.ui.size.unwrap_or_default().unit_huge,
+            None              => self.ui.size.unwrap_or_default().unit_byte,
         };
+        style.unwrap_or_default()
     }
 
     fn no_blocksize(&self) -> Style {
-        self.ui.punctuation
+        self.ui.punctuation.unwrap_or_default()
     }
 }
 
 #[rustfmt::skip]
 impl render::FiletypeColours for Theme {
-    fn normal(&self)       -> Style { self.ui.filekinds.normal }
-    fn directory(&self)    -> Style { self.ui.filekinds.directory }
-    fn pipe(&self)         -> Style { self.ui.filekinds.pipe }
-    fn symlink(&self)      -> Style { self.ui.filekinds.symlink }
-    fn block_device(&self) -> Style { self.ui.filekinds.block_device }
-    fn char_device(&self)  -> Style { self.ui.filekinds.char_device }
-    fn socket(&self)       -> Style { self.ui.filekinds.socket }
-    fn special(&self)      -> Style { self.ui.filekinds.special }
+    fn normal(&self)       -> Style { self.ui.filekinds.unwrap_or_default().normal() }
+    fn directory(&self)    -> Style { self.ui.filekinds.unwrap_or_default().directory() }
+    fn pipe(&self)         -> Style { self.ui.filekinds.unwrap_or_default().pipe() }
+    fn symlink(&self)      -> Style { self.ui.filekinds.unwrap_or_default().symlink() }
+    fn block_device(&self) -> Style { self.ui.filekinds.unwrap_or_default().block_device() }
+    fn char_device(&self)  -> Style { self.ui.filekinds.unwrap_or_default().char_device() }
+    fn socket(&self)       -> Style { self.ui.filekinds.unwrap_or_default().socket() }
+    fn special(&self)      -> Style { self.ui.filekinds.unwrap_or_default().special() }
 }
 
 #[rustfmt::skip]
 impl render::GitColours for Theme {
-    fn not_modified(&self)  -> Style { self.ui.punctuation }
+    fn not_modified(&self)  -> Style { self.ui.punctuation() }
     #[allow(clippy::new_ret_no_self)]
-    fn new(&self)           -> Style { self.ui.git.new }
-    fn modified(&self)      -> Style { self.ui.git.modified }
-    fn deleted(&self)       -> Style { self.ui.git.deleted }
-    fn renamed(&self)       -> Style { self.ui.git.renamed }
-    fn type_change(&self)   -> Style { self.ui.git.typechange }
-    fn ignored(&self)       -> Style { self.ui.git.ignored }
-    fn conflicted(&self)    -> Style { self.ui.git.conflicted }
+    fn new(&self)           -> Style { self.ui.git.unwrap_or_default().new() }
+    fn modified(&self)      -> Style { self.ui.git.unwrap_or_default().modified() }
+    fn deleted(&self)       -> Style { self.ui.git.unwrap_or_default().deleted() }
+    fn renamed(&self)       -> Style { self.ui.git.unwrap_or_default().renamed() }
+    fn type_change(&self)   -> Style { self.ui.git.unwrap_or_default().typechange() }
+    fn ignored(&self)       -> Style { self.ui.git.unwrap_or_default().ignored() }
+    fn conflicted(&self)    -> Style { self.ui.git.unwrap_or_default().conflicted() }
 }
 
 #[rustfmt::skip]
 impl render::GitRepoColours for Theme {
-    fn branch_main(&self)  -> Style { self.ui.git_repo.branch_main }
-    fn branch_other(&self) -> Style { self.ui.git_repo.branch_other }
-    fn no_repo(&self)      -> Style { self.ui.punctuation }
-    fn git_clean(&self)    -> Style { self.ui.git_repo.git_clean }
-    fn git_dirty(&self)    -> Style { self.ui.git_repo.git_dirty }
+    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]
 #[cfg(unix)]
 impl render::GroupColours for Theme {
-    fn yours(&self)      -> Style { self.ui.users.group_yours }
-    fn not_yours(&self)  -> Style { self.ui.users.group_other }
-    fn root_group(&self) -> Style { self.ui.users.group_root }
-    fn no_group(&self)   -> Style { self.ui.punctuation }
+    fn yours(&self)      -> Style { self.ui.users.unwrap_or_default().group_yours() }
+    fn not_yours(&self)  -> Style { self.ui.users.unwrap_or_default().group_other() }
+    fn root_group(&self) -> Style { self.ui.users.unwrap_or_default().group_root() }
+    fn no_group(&self)   -> Style { self.ui.punctuation() }
 }
 
 #[rustfmt::skip]
 impl render::LinksColours for Theme {
-    fn normal(&self)           -> Style { self.ui.links.normal }
-    fn multi_link_file(&self)  -> Style { self.ui.links.multi_link_file }
+    fn normal(&self)           -> Style { self.ui.links.unwrap_or_default().normal() }
+    fn multi_link_file(&self)  -> Style { self.ui.links.unwrap_or_default().multi_link_file() }
 }
 
 #[rustfmt::skip]
 impl render::PermissionsColours for Theme {
-    fn dash(&self)               -> Style { self.ui.punctuation }
-    fn user_read(&self)          -> Style { self.ui.perms.user_read }
-    fn user_write(&self)         -> Style { self.ui.perms.user_write }
-    fn user_execute_file(&self)  -> Style { self.ui.perms.user_execute_file }
-    fn user_execute_other(&self) -> Style { self.ui.perms.user_execute_other }
-    fn group_read(&self)         -> Style { self.ui.perms.group_read }
-    fn group_write(&self)        -> Style { self.ui.perms.group_write }
-    fn group_execute(&self)      -> Style { self.ui.perms.group_execute }
-    fn other_read(&self)         -> Style { self.ui.perms.other_read }
-    fn other_write(&self)        -> Style { self.ui.perms.other_write }
-    fn other_execute(&self)      -> Style { self.ui.perms.other_execute }
-    fn special_user_file(&self)  -> Style { self.ui.perms.special_user_file }
-    fn special_other(&self)      -> Style { self.ui.perms.special_other }
-    fn attribute(&self)          -> Style { self.ui.perms.attribute }
+    fn dash(&self)               -> Style { self.ui.punctuation() }
+    fn user_read(&self)          -> Style { self.ui.perms.unwrap_or_default().user_read() }
+    fn user_write(&self)         -> Style { self.ui.perms.unwrap_or_default().user_write() }
+    fn user_execute_file(&self)  -> Style { self.ui.perms.unwrap_or_default().user_execute_file() }
+    fn user_execute_other(&self) -> Style { self.ui.perms.unwrap_or_default().user_execute_other() }
+    fn group_read(&self)         -> Style { self.ui.perms.unwrap_or_default().group_read() }
+    fn group_write(&self)        -> Style { self.ui.perms.unwrap_or_default().group_write() }
+    fn group_execute(&self)      -> Style { self.ui.perms.unwrap_or_default().group_execute() }
+    fn other_read(&self)         -> Style { self.ui.perms.unwrap_or_default().other_read() }
+    fn other_write(&self)        -> Style { self.ui.perms.unwrap_or_default().other_write() }
+    fn other_execute(&self)      -> Style { self.ui.perms.unwrap_or_default().other_execute() }
+    fn special_user_file(&self)  -> Style { self.ui.perms.unwrap_or_default().special_user_file() }
+    fn special_other(&self)      -> Style { self.ui.perms.unwrap_or_default().special_other() }
+    fn attribute(&self)          -> Style { self.ui.perms.unwrap_or_default().attribute() }
 }
 
 impl render::SizeColours for Theme {
@@ -321,11 +343,11 @@ impl render::SizeColours for Theme {
 
         #[rustfmt::skip]
         return match prefix {
-            Some(Kilo | Kibi) => self.ui.size.number_kilo,
-            Some(Mega | Mebi) => self.ui.size.number_mega,
-            Some(Giga | Gibi) => self.ui.size.number_giga,
-            Some(_)           => self.ui.size.number_huge,
-            None              => self.ui.size.number_byte,
+            Some(Kilo | Kibi) => self.ui.size.unwrap_or_default().number_kilo(),
+            Some(Mega | Mebi) => self.ui.size.unwrap_or_default().number_mega(),
+            Some(Giga | Gibi) => self.ui.size.unwrap_or_default().number_giga(),
+            Some(_)           => self.ui.size.unwrap_or_default().number_huge(),
+            None              => self.ui.size.unwrap_or_default().number_byte(),
         };
     }
 
@@ -334,61 +356,77 @@ impl render::SizeColours for Theme {
 
         #[rustfmt::skip]
         return match prefix {
-            Some(Kilo | Kibi) => self.ui.size.unit_kilo,
-            Some(Mega | Mebi) => self.ui.size.unit_mega,
-            Some(Giga | Gibi) => self.ui.size.unit_giga,
-            Some(_)           => self.ui.size.unit_huge,
-            None              => self.ui.size.unit_byte,
+            Some(Kilo | Kibi) => self.ui.size.unwrap_or_default().unit_kilo(),
+            Some(Mega | Mebi) => self.ui.size.unwrap_or_default().unit_mega(),
+            Some(Giga | Gibi) => self.ui.size.unwrap_or_default().unit_giga(),
+            Some(_)           => self.ui.size.unwrap_or_default().unit_huge(),
+            None              => self.ui.size.unwrap_or_default().unit_byte(),
         };
     }
 
     #[rustfmt::skip]
-    fn no_size(&self) -> Style { self.ui.punctuation }
+    fn no_size(&self) -> Style { self.ui.punctuation() }
     #[rustfmt::skip]
-    fn major(&self)   -> Style { self.ui.size.major }
+    fn major(&self)   -> Style { self.ui.size.unwrap_or_default().major() }
     #[rustfmt::skip]
-    fn comma(&self)   -> Style { self.ui.punctuation }
+    fn comma(&self)   -> Style { self.ui.punctuation() }
     #[rustfmt::skip]
-    fn minor(&self)   -> Style { self.ui.size.minor }
+    fn minor(&self)   -> Style { self.ui.size.unwrap_or_default().minor() }
 }
 
 #[rustfmt::skip]
 #[cfg(unix)]
 impl render::UserColours for Theme {
-    fn you(&self)           -> Style { self.ui.users.user_you }
-    fn other(&self)         -> Style { self.ui.users.user_other }
-    fn root(&self)          -> Style { self.ui.users.user_root }
-    fn no_user(&self)       -> Style { self.ui.punctuation }
+    fn you(&self)           -> Style { self.ui.users.unwrap_or_default().user_you() }
+    fn other(&self)         -> Style { self.ui.users.unwrap_or_default().user_other() }
+    fn root(&self)          -> Style { self.ui.users.unwrap_or_default().user_root() }
+    fn no_user(&self)       -> Style { self.ui.punctuation() }
 }
 
 #[rustfmt::skip]
 impl FileNameColours for Theme {
-    fn symlink_path(&self)        -> Style { self.ui.symlink_path }
-    fn normal_arrow(&self)        -> Style { self.ui.punctuation }
-    fn broken_symlink(&self)      -> Style { self.ui.broken_symlink }
-    fn broken_filename(&self)     -> Style { apply_overlay(self.ui.broken_symlink, self.ui.broken_path_overlay) }
-    fn control_char(&self)        -> Style { self.ui.control_char }
-    fn broken_control_char(&self) -> Style { apply_overlay(self.ui.control_char,   self.ui.broken_path_overlay) }
-    fn executable_file(&self)     -> Style { self.ui.filekinds.executable }
-    fn mount_point(&self)         -> Style { self.ui.filekinds.mount_point }
-
-    fn icon(&self)          -> Option<Style> { self.ui.icon }
+    fn symlink_path(&self)        -> Style { self.ui.symlink_path() }
+    fn normal_arrow(&self)        -> Style { self.ui.punctuation() }
+    fn broken_symlink(&self)      -> Style { self.ui.broken_symlink() }
+    fn broken_filename(&self)     -> Style { apply_overlay(self.ui.broken_symlink(), self.ui.broken_path_overlay()) }
+    fn control_char(&self)        -> Style { self.ui.control_char() }
+    fn broken_control_char(&self) -> Style { apply_overlay(self.ui.control_char(),   self.ui.broken_path_overlay()) }
+    fn executable_file(&self)     -> Style { self.ui.filekinds.unwrap_or_default().executable() }
+    fn mount_point(&self)         -> Style { self.ui.filekinds.unwrap_or_default().mount_point() }
 
     fn colour_file(&self, file: &File<'_>) -> Style {
         self.exts
             .get_style(file, self)
-            .unwrap_or(self.ui.filekinds.normal)
+            .unwrap_or(self.ui.filekinds.unwrap_or_default().normal())
+    }
+
+ fn style_override(&self, file: &File<'_>) -> Option<FileNameStyle> {
+        if let Some(ref name_overrides) = self.ui.filenames {
+            if let Some(file_override) = name_overrides.get(&file.name) {
+                return Some(*file_override);
+            }
+        }
+
+        if let Some(ref ext_overrides) = self.ui.extensions {
+            if let Some(ext) = file.ext.clone() {
+                if let Some(file_override) = ext_overrides.get(&ext) {
+                    return Some(*file_override);
+                }
+            }
+        }
+
+        None
     }
 }
 
 #[rustfmt::skip]
 impl render::SecurityCtxColours for Theme {
-    fn none(&self)          -> Style { self.ui.security_context.none }
-    fn selinux_colon(&self) -> Style { self.ui.security_context.selinux.colon }
-    fn selinux_user(&self)  -> Style { self.ui.security_context.selinux.user }
-    fn selinux_role(&self)  -> Style { self.ui.security_context.selinux.role }
-    fn selinux_type(&self)  -> Style { self.ui.security_context.selinux.typ }
-    fn selinux_range(&self) -> Style { self.ui.security_context.selinux.range }
+    fn none(&self)          -> Style { self.ui.security_context.unwrap_or_default().none() }
+    fn selinux_colon(&self) -> Style { self.ui.security_context.unwrap_or_default().selinux().colon() }
+    fn selinux_user(&self)  -> Style { self.ui.security_context.unwrap_or_default().selinux().user() }
+    fn selinux_role(&self)  -> Style { self.ui.security_context.unwrap_or_default().selinux().role() }
+    fn selinux_type(&self)  -> Style { self.ui.security_context.unwrap_or_default().selinux().typ() }
+    fn selinux_range(&self) -> Style { self.ui.security_context.unwrap_or_default().selinux().range() }
 }
 
 /// Some of the styles are **overlays**: although they have the same attribute
@@ -419,7 +457,6 @@ fn apply_overlay(mut base: Style, overlay: Style) -> Style {
 
     base
 }
-// TODO: move this function to the ansiterm crate
 
 #[cfg(test)]
 #[cfg(unix)]
@@ -488,125 +525,125 @@ mod customs_test {
     }
 
     // LS_COLORS can affect all of these colours:
-    test!(ls_di:   ls "di=31", exa ""  =>  colours c -> { c.filekinds.directory    = Red.normal();    });
-    test!(ls_ex:   ls "ex=32", exa ""  =>  colours c -> { c.filekinds.executable   = Green.normal();  });
-    test!(ls_fi:   ls "fi=33", exa ""  =>  colours c -> { c.filekinds.normal       = Yellow.normal(); });
-    test!(ls_pi:   ls "pi=34", exa ""  =>  colours c -> { c.filekinds.pipe         = Blue.normal();   });
-    test!(ls_so:   ls "so=35", exa ""  =>  colours c -> { c.filekinds.socket       = Purple.normal(); });
-    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_di:   ls "di=31", exa ""  =>  colours c -> { c.filekinds().directory    = Some(Red.normal());    });
+    test!(ls_ex:   ls "ex=32", exa ""  =>  colours c -> { c.filekinds().executable   = Some(Green.normal());  });
+    test!(ls_fi:   ls "fi=33", exa ""  =>  colours c -> { c.filekinds().normal       = Some(Yellow.normal()); });
+    test!(ls_pi:   ls "pi=34", exa ""  =>  colours c -> { c.filekinds().pipe         = Some(Blue.normal());   });
+    test!(ls_so:   ls "so=35", exa ""  =>  colours c -> { c.filekinds().socket       = Some(Purple.normal()); });
+    test!(ls_bd:   ls "bd=36", exa ""  =>  colours c -> { c.filekinds().block_device = Some(Cyan.normal());   });
+    test!(ls_cd:   ls "cd=35", exa ""  =>  colours c -> { c.filekinds().char_device  = Some(Purple.normal()); });
+    test!(ls_ln:   ls "ln=34", exa ""  =>  colours c -> { c.filekinds().symlink      = Some(Blue.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();  });
-    test!(exa_ex:  ls "", exa "ex=33"  =>  colours c -> { c.filekinds.executable   = Yellow.normal(); });
-    test!(exa_fi:  ls "", exa "fi=34"  =>  colours c -> { c.filekinds.normal       = Blue.normal();   });
-    test!(exa_pi:  ls "", exa "pi=35"  =>  colours c -> { c.filekinds.pipe         = Purple.normal(); });
-    test!(exa_so:  ls "", exa "so=36"  =>  colours c -> { c.filekinds.socket       = Cyan.normal();   });
-    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_di:  ls "", exa "di=32"  =>  colours c -> { c.filekinds().directory    = Some(Green.normal());  });
+    test!(exa_ex:  ls "", exa "ex=33"  =>  colours c -> { c.filekinds().executable   = Some(Yellow.normal()); });
+    test!(exa_fi:  ls "", exa "fi=34"  =>  colours c -> { c.filekinds().normal       = Some(Blue.normal());   });
+    test!(exa_pi:  ls "", exa "pi=35"  =>  colours c -> { c.filekinds().pipe         = Some(Purple.normal()); });
+    test!(exa_so:  ls "", exa "so=36"  =>  colours c -> { c.filekinds().socket       = Some(Cyan.normal());   });
+    test!(exa_bd:  ls "", exa "bd=35"  =>  colours c -> { c.filekinds().block_device = Some(Purple.normal()); });
+    test!(exa_cd:  ls "", exa "cd=34"  =>  colours c -> { c.filekinds().char_device  = Some(Blue.normal());   });
+    test!(exa_ln:  ls "", exa "ln=33"  =>  colours c -> { c.filekinds().symlink      = Some(Yellow.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();  });
-    test!(ls_exa_ex: ls "ex=32", exa "ex=33"  =>  colours c -> { c.filekinds.executable = Yellow.normal(); });
-    test!(ls_exa_fi: ls "fi=33", exa "fi=34"  =>  colours c -> { c.filekinds.normal     = Blue.normal();   });
+    test!(ls_exa_di: ls "di=31", exa "di=32"  =>  colours c -> { c.filekinds().directory  = Some(Green.normal());  });
+    test!(ls_exa_ex: ls "ex=32", exa "ex=33"  =>  colours c -> { c.filekinds().executable = Some(Yellow.normal()); });
+    test!(ls_exa_fi: ls "fi=33", exa "fi=34"  =>  colours c -> { c.filekinds().normal     = Some(Blue.normal());   });
 
     // But more importantly, EZA_COLORS has its own, special list of colours:
-    test!(exa_ur:  ls "", exa "ur=38;5;100"  =>  colours c -> { c.perms.user_read           = Fixed(100).normal(); });
-    test!(exa_uw:  ls "", exa "uw=38;5;101"  =>  colours c -> { c.perms.user_write          = Fixed(101).normal(); });
-    test!(exa_ux:  ls "", exa "ux=38;5;102"  =>  colours c -> { c.perms.user_execute_file   = Fixed(102).normal(); });
-    test!(exa_ue:  ls "", exa "ue=38;5;103"  =>  colours c -> { c.perms.user_execute_other  = Fixed(103).normal(); });
-    test!(exa_gr:  ls "", exa "gr=38;5;104"  =>  colours c -> { c.perms.group_read          = Fixed(104).normal(); });
-    test!(exa_gw:  ls "", exa "gw=38;5;105"  =>  colours c -> { c.perms.group_write         = Fixed(105).normal(); });
-    test!(exa_gx:  ls "", exa "gx=38;5;106"  =>  colours c -> { c.perms.group_execute       = Fixed(106).normal(); });
-    test!(exa_tr:  ls "", exa "tr=38;5;107"  =>  colours c -> { c.perms.other_read          = Fixed(107).normal(); });
-    test!(exa_tw:  ls "", exa "tw=38;5;108"  =>  colours c -> { c.perms.other_write         = Fixed(108).normal(); });
-    test!(exa_tx:  ls "", exa "tx=38;5;109"  =>  colours c -> { c.perms.other_execute       = Fixed(109).normal(); });
-    test!(exa_su:  ls "", exa "su=38;5;110"  =>  colours c -> { c.perms.special_user_file   = Fixed(110).normal(); });
-    test!(exa_sf:  ls "", exa "sf=38;5;111"  =>  colours c -> { c.perms.special_other       = Fixed(111).normal(); });
-    test!(exa_xa:  ls "", exa "xa=38;5;112"  =>  colours c -> { c.perms.attribute           = Fixed(112).normal(); });
+    test!(exa_ur:  ls "", exa "ur=38;5;100"  =>  colours c -> { c.perms().user_read           = Some(Fixed(100).normal()); });
+    test!(exa_uw:  ls "", exa "uw=38;5;101"  =>  colours c -> { c.perms().user_write          = Some(Fixed(101).normal()); });
+    test!(exa_ux:  ls "", exa "ux=38;5;102"  =>  colours c -> { c.perms().user_execute_file   = Some(Fixed(102).normal()); });
+    test!(exa_ue:  ls "", exa "ue=38;5;103"  =>  colours c -> { c.perms().user_execute_other  = Some(Fixed(103).normal()); });
+    test!(exa_gr:  ls "", exa "gr=38;5;104"  =>  colours c -> { c.perms().group_read          = Some(Fixed(104).normal()); });
+    test!(exa_gw:  ls "", exa "gw=38;5;105"  =>  colours c -> { c.perms().group_write         = Some(Fixed(105).normal()); });
+    test!(exa_gx:  ls "", exa "gx=38;5;106"  =>  colours c -> { c.perms().group_execute       = Some(Fixed(106).normal()); });
+    test!(exa_tr:  ls "", exa "tr=38;5;107"  =>  colours c -> { c.perms().other_read          = Some(Fixed(107).normal()); });
+    test!(exa_tw:  ls "", exa "tw=38;5;108"  =>  colours c -> { c.perms().other_write         = Some(Fixed(108).normal()); });
+    test!(exa_tx:  ls "", exa "tx=38;5;109"  =>  colours c -> { c.perms().other_execute       = Some(Fixed(109).normal()); });
+    test!(exa_su:  ls "", exa "su=38;5;110"  =>  colours c -> { c.perms().special_user_file   = Some(Fixed(110).normal()); });
+    test!(exa_sf:  ls "", exa "sf=38;5;111"  =>  colours c -> { c.perms().special_other       = Some(Fixed(111).normal()); });
+    test!(exa_xa:  ls "", exa "xa=38;5;112"  =>  colours c -> { c.perms().attribute           = Some(Fixed(112).normal()); });
 
     test!(exa_sn:  ls "", exa "sn=38;5;113" => colours c -> {
-        c.size.number_byte = Fixed(113).normal();
-        c.size.number_kilo = Fixed(113).normal();
-        c.size.number_mega = Fixed(113).normal();
-        c.size.number_giga = Fixed(113).normal();
-        c.size.number_huge = Fixed(113).normal();
+        c.size().number_byte = Some(Fixed(113).normal());
+        c.size().number_kilo = Some(Fixed(113).normal());
+        c.size().number_mega = Some(Fixed(113).normal());
+        c.size().number_giga = Some(Fixed(113).normal());
+        c.size().number_huge = Some(Fixed(113).normal());
     });
     test!(exa_sb:  ls "", exa "sb=38;5;114" => colours c -> {
-        c.size.unit_byte = Fixed(114).normal();
-        c.size.unit_kilo = Fixed(114).normal();
-        c.size.unit_mega = Fixed(114).normal();
-        c.size.unit_giga = Fixed(114).normal();
-        c.size.unit_huge = Fixed(114).normal();
+        c.size().unit_byte = Some(Fixed(114).normal());
+        c.size().unit_kilo = Some(Fixed(114).normal());
+        c.size().unit_mega = Some(Fixed(114).normal());
+        c.size().unit_giga = Some(Fixed(114).normal());
+        c.size().unit_huge = Some(Fixed(114).normal());
     });
 
-    test!(exa_nb:  ls "", exa "nb=38;5;115"  =>  colours c -> { c.size.number_byte                      = Fixed(115).normal(); });
-    test!(exa_nk:  ls "", exa "nk=38;5;116"  =>  colours c -> { c.size.number_kilo                      = Fixed(116).normal(); });
-    test!(exa_nm:  ls "", exa "nm=38;5;117"  =>  colours c -> { c.size.number_mega                      = Fixed(117).normal(); });
-    test!(exa_ng:  ls "", exa "ng=38;5;118"  =>  colours c -> { c.size.number_giga                      = Fixed(118).normal(); });
-    test!(exa_nt:  ls "", exa "nt=38;5;119"  =>  colours c -> { c.size.number_huge                      = Fixed(119).normal(); });
-
-    test!(exa_ub:  ls "", exa "ub=38;5;115"  =>  colours c -> { c.size.unit_byte                        = Fixed(115).normal(); });
-    test!(exa_uk:  ls "", exa "uk=38;5;116"  =>  colours c -> { c.size.unit_kilo                        = Fixed(116).normal(); });
-    test!(exa_um:  ls "", exa "um=38;5;117"  =>  colours c -> { c.size.unit_mega                        = Fixed(117).normal(); });
-    test!(exa_ug:  ls "", exa "ug=38;5;118"  =>  colours c -> { c.size.unit_giga                        = Fixed(118).normal(); });
-    test!(exa_ut:  ls "", exa "ut=38;5;119"  =>  colours c -> { c.size.unit_huge                        = Fixed(119).normal(); });
-
-    test!(exa_df:  ls "", exa "df=38;5;115"  =>  colours c -> { c.size.major                            = Fixed(115).normal(); });
-    test!(exa_ds:  ls "", exa "ds=38;5;116"  =>  colours c -> { c.size.minor                            = Fixed(116).normal(); });
-
-    test!(exa_uu:  ls "", exa "uu=38;5;117"  =>  colours c -> { c.users.user_you                        = Fixed(117).normal(); });
-    test!(exa_un:  ls "", exa "un=38;5;118"  =>  colours c -> { c.users.user_other                      = Fixed(118).normal(); });
-    test!(exa_gu:  ls "", exa "gu=38;5;119"  =>  colours c -> { c.users.group_yours                     = Fixed(119).normal(); });
-    test!(exa_gn:  ls "", exa "gn=38;5;120"  =>  colours c -> { c.users.group_other                     = Fixed(120).normal(); });
-
-    test!(exa_lc:  ls "", exa "lc=38;5;121"  =>  colours c -> { c.links.normal                          = Fixed(121).normal(); });
-    test!(exa_lm:  ls "", exa "lm=38;5;122"  =>  colours c -> { c.links.multi_link_file                 = Fixed(122).normal(); });
-
-    test!(exa_ga:  ls "", exa "ga=38;5;123"  =>  colours c -> { c.git.new                               = Fixed(123).normal(); });
-    test!(exa_gm:  ls "", exa "gm=38;5;124"  =>  colours c -> { c.git.modified                          = Fixed(124).normal(); });
-    test!(exa_gd:  ls "", exa "gd=38;5;125"  =>  colours c -> { c.git.deleted                           = Fixed(125).normal(); });
-    test!(exa_gv:  ls "", exa "gv=38;5;126"  =>  colours c -> { c.git.renamed                           = Fixed(126).normal(); });
-    test!(exa_gt:  ls "", exa "gt=38;5;127"  =>  colours c -> { c.git.typechange                        = Fixed(127).normal(); });
-    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_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(); });
-
-    test!(exa_im:  ls "", exa "im=38;5;128"  =>  colours c -> { c.file_type.image                       = Fixed(128).normal(); });
-    test!(exa_vi:  ls "", exa "vi=38;5;129"  =>  colours c -> { c.file_type.video                       = Fixed(129).normal(); });
-    test!(exa_mu:  ls "", exa "mu=38;5;130"  =>  colours c -> { c.file_type.music                       = Fixed(130).normal(); });
-    test!(exa_lo:  ls "", exa "lo=38;5;131"  =>  colours c -> { c.file_type.lossless                    = Fixed(131).normal(); });
-    test!(exa_cr:  ls "", exa "cr=38;5;132"  =>  colours c -> { c.file_type.crypto                      = Fixed(132).normal(); });
-    test!(exa_do:  ls "", exa "do=38;5;133"  =>  colours c -> { c.file_type.document                    = Fixed(133).normal(); });
-    test!(exa_co:  ls "", exa "co=38;5;134"  =>  colours c -> { c.file_type.compressed                  = Fixed(134).normal(); });
-    test!(exa_tm:  ls "", exa "tm=38;5;135"  =>  colours c -> { c.file_type.temp                        = Fixed(135).normal(); });
-    test!(exa_cm:  ls "", exa "cm=38;5;136"  =>  colours c -> { c.file_type.compiled                    = Fixed(136).normal(); });
-    test!(exa_ie:  ls "", exa "bu=38;5;137"  =>  colours c -> { c.file_type.build                       = Fixed(137).normal(); });
-    test!(exa_bu:  ls "", exa "bu=38;5;137"  =>  colours c -> { c.file_type.build                       = Fixed(137).normal(); });
-    test!(exa_sc:  ls "", exa "sc=38;5;138"  =>  colours c -> { c.file_type.source                      = Fixed(138).normal(); });
-
-    test!(exa_Sn:  ls "", exa "Sn=38;5;128"  =>  colours c -> { c.security_context.none                 = Fixed(128).normal(); });
-    test!(exa_Su:  ls "", exa "Su=38;5;129"  =>  colours c -> { c.security_context.selinux.user         = Fixed(129).normal(); });
-    test!(exa_Sr:  ls "", exa "Sr=38;5;130"  =>  colours c -> { c.security_context.selinux.role         = Fixed(130).normal(); });
-    test!(exa_St:  ls "", exa "St=38;5;131"  =>  colours c -> { c.security_context.selinux.typ          = Fixed(131).normal(); });
-    test!(exa_Sl:  ls "", exa "Sl=38;5;132"  =>  colours c -> { c.security_context.selinux.range        = Fixed(132).normal(); });
+    test!(exa_nb:  ls "", exa "nb=38;5;115"  =>  colours c -> { c.size().number_byte                      = Some(Fixed(115).normal()); });
+    test!(exa_nk:  ls "", exa "nk=38;5;116"  =>  colours c -> { c.size().number_kilo                      = Some(Fixed(116).normal()); });
+    test!(exa_nm:  ls "", exa "nm=38;5;117"  =>  colours c -> { c.size().number_mega                      = Some(Fixed(117).normal()); });
+    test!(exa_ng:  ls "", exa "ng=38;5;118"  =>  colours c -> { c.size().number_giga                      = Some(Fixed(118).normal()); });
+    test!(exa_nt:  ls "", exa "nt=38;5;119"  =>  colours c -> { c.size().number_huge                      = Some(Fixed(119).normal()); });
+
+    test!(exa_ub:  ls "", exa "ub=38;5;115"  =>  colours c -> { c.size().unit_byte                        = Some(Fixed(115).normal()); });
+    test!(exa_uk:  ls "", exa "uk=38;5;116"  =>  colours c -> { c.size().unit_kilo                        = Some(Fixed(116).normal()); });
+    test!(exa_um:  ls "", exa "um=38;5;117"  =>  colours c -> { c.size().unit_mega                        = Some(Fixed(117).normal()); });
+    test!(exa_ug:  ls "", exa "ug=38;5;118"  =>  colours c -> { c.size().unit_giga                        = Some(Fixed(118).normal()); });
+    test!(exa_ut:  ls "", exa "ut=38;5;119"  =>  colours c -> { c.size().unit_huge                        = Some(Fixed(119).normal()); });
+
+    test!(exa_df:  ls "", exa "df=38;5;115"  =>  colours c -> { c.size().major                            = Some(Fixed(115).normal()); });
+    test!(exa_ds:  ls "", exa "ds=38;5;116"  =>  colours c -> { c.size().minor                            = Some(Fixed(116).normal()); });
+
+    test!(exa_uu:  ls "", exa "uu=38;5;117"  =>  colours c -> { c.users().user_you                        = Some(Fixed(117).normal()); });
+    test!(exa_un:  ls "", exa "un=38;5;118"  =>  colours c -> { c.users().user_other                      = Some(Fixed(118).normal()); });
+    test!(exa_gu:  ls "", exa "gu=38;5;119"  =>  colours c -> { c.users().group_yours                     = Some(Fixed(119).normal()); });
+    test!(exa_gn:  ls "", exa "gn=38;5;120"  =>  colours c -> { c.users().group_other                     = Some(Fixed(120).normal()); });
+
+    test!(exa_lc:  ls "", exa "lc=38;5;121"  =>  colours c -> { c.links().normal                          = Some(Fixed(121).normal()); });
+    test!(exa_lm:  ls "", exa "lm=38;5;122"  =>  colours c -> { c.links().multi_link_file                 = Some(Fixed(122).normal()); });
+
+    test!(exa_ga:  ls "", exa "ga=38;5;123"  =>  colours c -> { c.git().new                               = Some(Fixed(123).normal()); });
+    test!(exa_gm:  ls "", exa "gm=38;5;124"  =>  colours c -> { c.git().modified                          = Some(Fixed(124).normal()); });
+    test!(exa_gd:  ls "", exa "gd=38;5;125"  =>  colours c -> { c.git().deleted                           = Some(Fixed(125).normal()); });
+    test!(exa_gv:  ls "", exa "gv=38;5;126"  =>  colours c -> { c.git().renamed                           = Some(Fixed(126).normal()); });
+    test!(exa_gt:  ls "", exa "gt=38;5;127"  =>  colours c -> { c.git().typechange                        = Some(Fixed(127).normal()); });
+    test!(exa_gi:  ls "", exa "gi=38;5;128"  =>  colours c -> { c.git().ignored                           = Some(Fixed(128).normal()); });
+    test!(exa_gc:  ls "", exa "gc=38;5;129"  =>  colours c -> { c.git().conflicted                        = Some(Fixed(129).normal()); });
+
+    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                 = Some(Blue.bold().underline()); });
+    test!(exa_sp:  ls "", exa "sp=1;35;4"    =>  colours c -> { c.filekinds().special                     = Some(Purple.bold().underline()); });
+
+    test!(exa_im:  ls "", exa "im=38;5;128"  =>  colours c -> { c.file_type().image                       = Some(Fixed(128).normal()); });
+    test!(exa_vi:  ls "", exa "vi=38;5;129"  =>  colours c -> { c.file_type().video                       = Some(Fixed(129).normal()); });
+    test!(exa_mu:  ls "", exa "mu=38;5;130"  =>  colours c -> { c.file_type().music                       = Some(Fixed(130).normal()); });
+    test!(exa_lo:  ls "", exa "lo=38;5;131"  =>  colours c -> { c.file_type().lossless                    = Some(Fixed(131).normal()); });
+    test!(exa_cr:  ls "", exa "cr=38;5;132"  =>  colours c -> { c.file_type().crypto                      = Some(Fixed(132).normal()); });
+    test!(exa_do:  ls "", exa "do=38;5;133"  =>  colours c -> { c.file_type().document                    = Some(Fixed(133).normal()); });
+    test!(exa_co:  ls "", exa "co=38;5;134"  =>  colours c -> { c.file_type().compressed                  = Some(Fixed(134).normal()); });
+    test!(exa_tm:  ls "", exa "tm=38;5;135"  =>  colours c -> { c.file_type().temp                        = Some(Fixed(135).normal()); });
+    test!(exa_cm:  ls "", exa "cm=38;5;136"  =>  colours c -> { c.file_type().compiled                    = Some(Fixed(136).normal()); });
+    test!(exa_ie:  ls "", exa "bu=38;5;137"  =>  colours c -> { c.file_type().build                       = Some(Fixed(137).normal()); });
+    test!(exa_bu:  ls "", exa "bu=38;5;137"  =>  colours c -> { c.file_type().build                       = Some(Fixed(137).normal()); });
+    test!(exa_sc:  ls "", exa "sc=38;5;138"  =>  colours c -> { c.file_type().source                      = Some(Fixed(138).normal()); });
+
+    test!(exa_Sn:  ls "", exa "Sn=38;5;128"  =>  colours c -> { c.security_context().none                   = Some(Fixed(128).normal()); });
+    test!(exa_Su:  ls "", exa "Su=38;5;129"  =>  colours c -> { c.security_context().selinux().user         = Some(Fixed(129).normal()); });
+    test!(exa_Sr:  ls "", exa "Sr=38;5;130"  =>  colours c -> { c.security_context().selinux().role         = Some(Fixed(130).normal()); });
+    test!(exa_St:  ls "", exa "St=38;5;131"  =>  colours c -> { c.security_context().selinux().typ          = Some(Fixed(131).normal()); });
+    test!(exa_Sl:  ls "", exa "Sl=38;5;132"  =>  colours c -> { c.security_context().selinux().range        = Some(Fixed(132).normal()); });
 
     // All the while, LS_COLORS treats them as filenames:
     test!(ls_uu:   ls "uu=38;5;117", exa ""  =>  exts [ ("uu", Fixed(117).normal()) ]);
@@ -635,12 +672,12 @@ 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!(ls_overwrite:  ls "pi=31:pi=32:pi=33", exa ""  =>  colours c -> { c.filekinds().pipe = Some(Yellow.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()) ]);
-    test!(ls_fi_exa_txt:  ls "fi=33", exa "*.txt=31"  => colours c -> { c.filekinds.normal = Yellow.normal(); }, exts [ ("*.txt", Red.normal()) ]);
-    test!(ls_txt_exa_fi:  ls "*.txt=31", exa "fi=33"  => colours c -> { c.filekinds.normal = Yellow.normal(); }, exts [ ("*.txt", Red.normal()) ]);
-    test!(eza_fi_exa_txt: ls "", exa "fi=33:*.txt=31" => colours c -> { c.filekinds.normal = Yellow.normal(); }, exts [ ("*.txt", Red.normal()) ]);
+    test!(ls_fi_ls_txt:   ls "fi=33:*.txt=31", exa "" => colours c -> { c.filekinds().normal = Some(Yellow.normal()); }, exts [ ("*.txt", Red.normal()) ]);
+    test!(ls_fi_exa_txt:  ls "fi=33", exa "*.txt=31"  => colours c -> { c.filekinds().normal = Some(Yellow.normal()); }, exts [ ("*.txt", Red.normal()) ]);
+    test!(ls_txt_exa_fi:  ls "*.txt=31", exa "fi=33"  => colours c -> { c.filekinds().normal = Some(Yellow.normal()); }, exts [ ("*.txt", Red.normal()) ]);
+    test!(eza_fi_exa_txt: ls "", exa "fi=33:*.txt=31" => colours c -> { c.filekinds().normal = Some(Yellow.normal()); }, exts [ ("*.txt", Red.normal()) ]);
 }
diff --git a/src/theme/ui_styles.rs b/src/theme/ui_styles.rs
index 8860b7b6b..9511e6cd8 100644
--- a/src/theme/ui_styles.rs
+++ b/src/theme/ui_styles.rs
@@ -1,169 +1,498 @@
-use nu_ansi_term::Style;
-
 use crate::theme::lsc::Pair;
+use nu_ansi_term::{Color::*, Style};
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+use std::default::Default;
+
+#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
+pub struct IconStyle {
+    pub glyph: Option<char>,
+    pub style: Option<Style>,
+}
+
+#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
+pub struct FileNameStyle {
+    pub icon: Option<IconStyle>,
+    pub filename: Option<Style>,
+}
 
 #[rustfmt::skip]
-#[derive(Debug, Default, PartialEq)]
+#[derive(Debug, PartialEq, Eq, 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 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 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 symlink_path:         Option<Style>,  // lp
+    pub control_char:         Option<Style>,  // cc
+    pub broken_symlink:       Option<Style>,  // or
+    pub broken_path_overlay:  Option<Style>,  // bO
+
+    pub filenames: Option<HashMap<String, FileNameStyle>>,
+    pub extensions: Option<HashMap<String, FileNameStyle>>,
+}
+// Macro to generate .unwrap_or_default getters for each field to cut down boilerplate
+macro_rules! field_accessors {
+    ($struct_name:ident, $($field_name:ident: Option<$type:ty>),*) => {
+        impl $struct_name {
+            $(
+                #[allow(clippy::wrong_self_convention, clippy::new_ret_no_self)]
+                pub fn $field_name(&self) -> $type {
+                    self.$field_name.unwrap_or_default()
+                }
+            )*
+        }
+    };
 }
+// Macro to generate method that returns a mut ref to each field or creates a default one if it's None
+macro_rules! update_field_accessors {
+    ($struct_name:ident, $($field_name:ident: Option<$type:ty>),*) => {
+        impl $struct_name {
+            $(
+                pub fn $field_name(&mut self) -> &mut $type {
+                    if self.$field_name.is_none() {
+                        self.$field_name = Some(Default::default());
+                    }
+                    // It is safe to unwrap here because we just ensured it's not None
+                    self.$field_name.as_mut().unwrap()
+                }
+            )*
+        }
+    };
+}
+
+update_field_accessors!(
+    UiStyles,
+    colourful: Option<bool>,
+    filekinds: Option<FileKinds>,
+    perms: Option<Permissions>,
+    size: Option<Size>,
+    file_type: Option<FileType>,
+    security_context: Option<SecurityContext>,
+    users: Option<Users>,
+    links: Option<Links>,
+    git: Option<Git>,
+    git_repo: Option<GitRepo>
+);
+
+field_accessors!(
+    UiStyles,
+    punctuation: Option<Style>,
+    date: Option<Style>,
+    inode: Option<Style>,
+    blocks: Option<Style>,
+    header: Option<Style>,
+    octal: Option<Style>,
+    flags: Option<Style>,
+    symlink_path: Option<Style>,
+    control_char: Option<Style>,
+    broken_symlink: Option<Style>,
+    broken_path_overlay: Option<Style>
+);
 
 #[rustfmt::skip]
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
+#[derive(Clone, Eq, 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()),
+        }
+    }
+}
+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]
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
+#[derive(Clone, Copy,Eq, 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
 }
+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]
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
+#[derive(Clone, Copy, Eq, 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
 }
+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]
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
+#[derive(Clone, Copy, Debug,Eq, 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
 }
+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]
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, 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
 }
+field_accessors!(Links, normal: Option<Style>, multi_link_file: Option<Style>);
 
 #[rustfmt::skip]
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
+#[derive(Clone, Copy, Debug,Eq, 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
+}
+
+field_accessors!(
+    Git,
+    new: Option<Style>,
+    modified: Option<Style>,
+    deleted: Option<Style>,
+    renamed: Option<Style>,
+    typechange: Option<Style>,
+    ignored: Option<Style>,
+    conflicted: Option<Style>
+);
+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, Eq, 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
+}
+field_accessors!(
+    GitRepo,
+    branch_main: Option<Style>,
+    branch_other: Option<Style>,
+    git_clean: Option<Style>,
+    git_dirty: Option<Style>
+);
+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, Eq, 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
 }
+field_accessors!(
+    SELinuxContext,
+    colon: Option<Style>,
+    user: Option<Style>,
+    role: Option<Style>,
+    typ: Option<Style>,
+    range: Option<Style>
+);
 
 #[rustfmt::skip]
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
+#[derive(Clone, Eq, Copy, Debug, PartialEq, Serialize, Deserialize)]
 pub struct SecurityContext {
-    pub none:    Style, // Sn
-    pub selinux: SELinuxContext,
+    pub none:    Option<Style>, // Sn
+    pub selinux: Option<SELinuxContext>,
+}
+field_accessors!(
+    SecurityContext,
+    none: Option<Style>,
+    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, Eq, 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()
+        Self {
+            colourful: Some(false),
+
+            #[rustfmt::skip]
+            filekinds: Some(FileKinds {
+            normal: Some(Style::default()),
+            directory: Some(Style::default()),
+            symlink: Some(Style::default()),
+            pipe: Some(Style::default()),
+            block_device: Some(Style::default()),
+            char_device: Some(Style::default()),
+            socket: Some(Style::default()),
+            special: Some(Style::default()),
+            executable: Some(Style::default()),
+            mount_point: Some(Style::default()),
+            }),
+
+            #[rustfmt::skip]
+            perms: Some(Permissions {
+                user_read:           Some(Style::default()),
+                user_write:          Some(Style::default()),
+                user_execute_file:   Some(Style::default()),
+                user_execute_other:  Some(Style::default()),
+
+                group_read:          Some(Style::default()),
+                group_write:         Some(Style::default()),
+                group_execute:       Some(Style::default()),
+
+                other_read:          Some(Style::default()),
+                other_write:         Some(Style::default()),
+                other_execute:       Some(Style::default()),
+
+                special_user_file:   Some(Style::default()),
+                special_other:       Some(Style::default()),
+
+                attribute:           Some(Style::default()),
+            }),
+
+            size: Some(Size::default()),
+
+            #[rustfmt::skip]
+            users:Some(Users {
+                user_you:                       Some(Style::default()),
+                user_other:                     Some(Style::default()),
+                user_root:                      Some(Style::default()),
+                group_yours:                    Some(Style::default()),
+                group_other:                    Some(Style::default()),
+                group_root:                     Some(Style::default()),
+            }),
+
+            #[rustfmt::skip]
+            links: Some(Links {
+                normal:          Some(Style::default()),
+                multi_link_file: Some(Style::default()),
+            }),
+
+            #[rustfmt::skip]
+            git: Some(Git {
+                new:         Some(Style::default()),
+                modified:    Some(Style::default()),
+                deleted:     Some(Style::default()),
+                renamed:     Some(Style::default()),
+                typechange:  Some(Style::default()),
+                ignored:     Some(Style::default()),
+                conflicted:  Some(Style::default()),
+            }),
+
+            git_repo: Some(GitRepo {
+                branch_main: Some(Style::default()),
+                branch_other: Some(Style::default()),
+                git_clean: Some(Style::default()),
+                git_dirty: Some(Style::default()),
+            }),
+
+            security_context: Some(SecurityContext {
+                none: Some(Style::default()),
+                #[rustfmt::skip]
+                selinux: Some(SELinuxContext {
+                    colon: Some(Style::default()),
+                    user:  Some(Style::default()),
+                    role:  Some(Style::default()),
+                    typ:   Some(Style::default()),
+                    range: Some(Style::default()),
+                }),
+            }),
+
+            #[rustfmt::skip]
+            file_type: Some(FileType {
+                image:      Some(Style::default()),
+                video:      Some(Style::default()),
+                music:      Some(Style::default()),
+                lossless:   Some(Style::default()),
+                crypto:     Some(Style::default()),
+                document:   Some(Style::default()),
+                compressed: Some(Style::default()),
+                temp:       Some(Style::default()),
+                compiled:   Some(Style::default()),
+                build:      Some(Style::default()),
+                source:     Some(Style::default()), // Need to discuss color
+            }),
+
+            punctuation: Some(Style::default()),
+            date: Some(Style::default()),
+            inode: Some(Style::default()),
+            blocks: Some(Style::default()),
+            octal: Some(Style::default()),
+            flags: Some(Style::default()),
+            header: Some(Style::default()),
+
+            symlink_path: Some(Style::default()),
+            control_char: Some(Style::default()),
+            broken_symlink: Some(Style::default()),
+            broken_path_overlay: Some(Style::default()),
+
+            filenames: None,
+            extensions: None,
+        }
     }
 }
 
@@ -174,15 +503,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 +527,88 @@ 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(),
-
-            "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(),
+            "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()),
+            "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 +617,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);
     }
 }
diff --git a/themes/theme.yml b/themes/theme.yml
new file mode 100644
index 000000000..0c515702c
--- /dev/null
+++ b/themes/theme.yml
@@ -0,0 +1,51 @@
+filekinds:
+  normal:
+    foreground: Blue
+  directory:
+    foreground: Blue
+  symlink:
+    foreground: Cyan
+  executable:
+    foreground: Green
+perms:
+  user_read:
+    foreground: Yellow
+    is_bold: true
+  user_write:
+    foreground: Red
+    is_bold: true
+  user_execute_file:
+    foreground: Green
+    is_bold: true
+  user_execute_other:
+    foreground: Green
+    is_bold: true
+  group_read:
+    foreground: Yellow
+  group_write:
+    foreground: Red
+  group_execute:
+    foreground: Green
+  other_read:
+    foreground: Yellow
+  other_write:
+    foreground: Red
+  other_execute:
+    foreground: Green
+filenames:
+  # Just change the icon glyph
+  Cargo.toml: {icon: {glyph: 🦀}}
+  Cargo.lock: {icon: {glyph: 🦀}}
+extensions:
+  # Change the filename color and icon
+  rs: {filename: {foreground: Red}, icon: {glyph: 🦀}}
+  # Change the icon glyph and color
+  nix: {icon: {glyph: ❄, style: {foreground: White}}}
+icon_overrides:
+icons:
+  filenames:
+    Cargo.toml: {icon: 🦀}
+    Cargo.lock: {icon: 🦀}
+  extensions:
+    rs: {icon: 🦀}
+    nix: {icon: ❄, style: {foreground: White}}


Test patch: