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