fancy_tree/config/colors/
mod.rs1use super::ConfigFile;
3use crate::color::Color;
4use crate::colors;
5use crate::git::status::{self, Status};
6use crate::lua::interop;
7use crate::tree::{
8 Entry,
9 entry::{Attributes, attributes::FileAttributes},
10};
11use mlua::{FromLua, Lua};
12use owo_colors::AnsiColors;
13use std::path::Path;
14
15#[derive(Debug, Default)]
17pub struct Colors {
18 for_icon: Option<mlua::Function>,
20 git_statuses: GitStatuses,
21}
22
23impl Colors {
24 const DEFAULT_FILE_COLOR: Option<Color> = None;
26 const DEFAULT_EXECUTABLE_COLOR: Option<Color> = Some(Color::Ansi(AnsiColors::Green));
28 const DEFAULT_DIRECTORY_COLOR: Option<Color> = Some(Color::Ansi(AnsiColors::Blue));
30 const DEFAULT_SYMLINK_COLOR: Option<Color> = Some(Color::Ansi(AnsiColors::Cyan));
32
33 pub fn for_icon<P>(&self, entry: &Entry<P>) -> Option<Color>
35 where
36 P: AsRef<Path>,
37 {
38 let path = entry.path();
39 let default = colors::for_path(entry.path()).or_else(|| Self::default_entry_color(entry));
40 let attributes = interop::FileAttributes::from(entry);
41
42 self.for_icon
44 .as_ref()
45 .map_or(Ok(default), |f| {
46 f.call::<Option<Color>>((path, attributes, default))
47 })
48 .unwrap_or(default)
49 }
50
51 pub fn for_untracked_git_status(&self, status: Status) -> Option<Color> {
53 self.git_statuses.get_untracked_color(status)
54 }
55
56 pub fn for_tracked_git_status(&self, status: Status) -> Option<Color> {
58 self.git_statuses.get_tracked_color(status)
59 }
60
61 fn default_entry_color<P>(entry: &Entry<P>) -> Option<Color>
62 where
63 P: AsRef<Path>,
64 {
65 match entry.attributes() {
66 Attributes::Directory(_) => Self::DEFAULT_DIRECTORY_COLOR,
67 Attributes::File(attributes) => Self::get_file_color(attributes),
68 Attributes::Symlink(_) => Self::DEFAULT_SYMLINK_COLOR,
69 }
70 }
71
72 fn get_file_color(attributes: &FileAttributes) -> Option<Color> {
74 attributes
75 .language()
76 .map(|language| language.rgb())
77 .map(|(r, g, b)| Color::Rgb(r, g, b))
78 .or_else(|| {
79 attributes
80 .is_executable()
81 .then_some(Self::DEFAULT_EXECUTABLE_COLOR)
82 .flatten()
83 })
84 .or(Self::DEFAULT_FILE_COLOR)
85 }
86}
87
88impl ConfigFile for Colors {
89 const FILENAME: &'static str = "colors.lua";
90 const DEFAULT_MODULE: &'static str = include_str!("./colors.lua");
91}
92
93impl FromLua for Colors {
94 fn from_lua(value: mlua::Value, lua: &Lua) -> mlua::Result<Self> {
95 const FOR_ICON_KEY: &str = "icons";
96 const GIT_STATUSES_KEY: &str = "git_statuses";
97
98 let table = mlua::Table::from_lua(value, lua)?;
99 let for_icon = table.get(FOR_ICON_KEY)?;
100 let git_statuses = table
101 .get::<Option<GitStatuses>>(GIT_STATUSES_KEY)?
102 .unwrap_or_default();
103
104 let colors = Self {
105 for_icon,
106 git_statuses,
107 };
108 Ok(colors)
109 }
110}
111
112#[derive(Debug, Default)]
114struct GitStatuses {
115 tracked: Option<mlua::Function>,
117 untracked: Option<mlua::Function>,
119}
120
121impl GitStatuses {
122 const fn get_default_color<S>(status: Status) -> Option<Color>
124 where
125 S: StatusColor,
126 {
127 let color = match status {
128 Status::Added => S::DEFAULT_ADDED,
129 Status::Modified => S::DEFAULT_MODIFIED,
130 Status::Removed => S::DEFAULT_REMOVED,
131 Status::Renamed => S::DEFAULT_RENAMED,
132 };
133 Some(Color::Ansi(color))
134 }
135
136 fn get_tracked_color(&self, status: Status) -> Option<Color> {
138 let default = Self::get_default_color::<status::Tracked>(status);
139 self.tracked.as_ref().map_or(default, |f| {
141 f.call::<Option<Color>>((status, default))
142 .unwrap_or(default)
143 })
144 }
145
146 fn get_untracked_color(&self, status: Status) -> Option<Color> {
148 let default = Self::get_default_color::<status::Untracked>(status);
149 self.untracked.as_ref().map_or(default, |f| {
151 f.call::<Option<Color>>((status, default))
152 .unwrap_or(default)
153 })
154 }
155}
156
157impl FromLua for GitStatuses {
158 fn from_lua(value: mlua::Value, lua: &Lua) -> mlua::Result<Self> {
159 const TRACKED_KEY: &str = "tracked";
160 const UNTRACKED_KEY: &str = "untracked";
161
162 let table = mlua::Table::from_lua(value, lua)?;
163 let tracked = table.get(TRACKED_KEY)?;
164 let untracked = table.get(UNTRACKED_KEY)?;
165
166 let git_statuses = Self { tracked, untracked };
167 Ok(git_statuses)
168 }
169}
170
171trait StatusColor {
173 const DEFAULT_ADDED: AnsiColors;
175 const DEFAULT_MODIFIED: AnsiColors;
177 const DEFAULT_REMOVED: AnsiColors;
179 const DEFAULT_RENAMED: AnsiColors;
181}
182
183impl StatusColor for status::Tracked {
184 const DEFAULT_ADDED: AnsiColors = AnsiColors::Green;
185 const DEFAULT_MODIFIED: AnsiColors = AnsiColors::Yellow;
186 const DEFAULT_REMOVED: AnsiColors = AnsiColors::Red;
187 const DEFAULT_RENAMED: AnsiColors = AnsiColors::Cyan;
188}
189
190impl StatusColor for status::Untracked {
191 const DEFAULT_ADDED: AnsiColors = AnsiColors::BrightGreen;
192 const DEFAULT_MODIFIED: AnsiColors = AnsiColors::BrightYellow;
193 const DEFAULT_REMOVED: AnsiColors = AnsiColors::BrightRed;
194 const DEFAULT_RENAMED: AnsiColors = AnsiColors::BrightCyan;
195}