1use std::path::Path;
4
5use f00_core::{Entry, EntryKind};
6use lscolors::{LsColors, Style};
7use nu_ansi_term::Color;
8
9#[derive(Clone)]
11pub struct Colorizer {
12 enabled: bool,
13 ls: LsColors,
14}
15
16impl Colorizer {
17 pub fn new(enabled: bool) -> Self {
19 Self {
20 enabled,
21 ls: LsColors::from_env().unwrap_or_default(),
22 }
23 }
24
25 pub fn from_ls_colors(enabled: bool, ls_colors: &str) -> Self {
27 Self {
28 enabled,
29 ls: LsColors::from_string(ls_colors),
30 }
31 }
32
33 pub fn ls_colors(&self) -> &LsColors {
35 &self.ls
36 }
37
38 pub fn enabled(&self) -> bool {
39 self.enabled
40 }
41
42 pub fn paint_name(&self, entry: &Entry, text: &str) -> String {
44 if !self.enabled {
45 return text.to_string();
46 }
47
48 if let Some(style) = self.style_for_entry(entry) {
50 return paint_with_ls_style(text, style);
51 }
52
53 match entry.kind {
55 EntryKind::Directory => Color::Blue.bold().paint(text).to_string(),
56 EntryKind::Symlink => Color::Cyan.paint(text).to_string(),
57 EntryKind::File if entry_is_exec(entry) => Color::Green.bold().paint(text).to_string(),
58 EntryKind::File => text.to_string(),
59 EntryKind::Other => Color::Yellow.paint(text).to_string(),
60 }
61 }
62
63 pub fn style_for_entry<'a>(&'a self, entry: &'a Entry) -> Option<&'a Style> {
65 if let Some(style) = self.ls.style_for_path(&entry.path) {
67 return Some(style);
68 }
69 if let Some(style) = self.ls.style_for_path(Path::new(&entry.name)) {
71 return Some(style);
72 }
73 use lscolors::Indicator;
75 let indicator = match entry.kind {
76 EntryKind::Directory => Some(Indicator::Directory),
77 EntryKind::Symlink => Some(Indicator::SymbolicLink),
78 EntryKind::File if entry_is_exec(entry) => Some(Indicator::ExecutableFile),
79 EntryKind::File => Some(Indicator::RegularFile),
80 EntryKind::Other => {
81 #[cfg(unix)]
82 {
83 match entry.mode & 0o170000 {
84 0o010000 => Some(Indicator::FIFO),
85 0o140000 => Some(Indicator::Socket),
86 0o060000 => Some(Indicator::BlockDevice),
87 0o020000 => Some(Indicator::CharacterDevice),
88 _ => None,
89 }
90 }
91 #[cfg(not(unix))]
92 {
93 None
94 }
95 }
96 };
97 indicator.and_then(|i| self.ls.style_for_indicator(i))
98 }
99
100 pub fn paint_git_char(&self, ch: char) -> String {
101 if !self.enabled {
102 return ch.to_string();
103 }
104 let s = ch.to_string();
105 match ch {
106 'M' => Color::Yellow.bold().paint(s).to_string(),
107 'A' => Color::Green.bold().paint(s).to_string(),
108 'D' | 'U' => Color::Red.bold().paint(s).to_string(),
109 '?' => Color::Purple.paint(s).to_string(),
110 '!' => Color::DarkGray.paint(s).to_string(),
111 'R' => Color::Cyan.bold().paint(s).to_string(),
112 _ => s,
113 }
114 }
115}
116
117fn paint_with_ls_style(text: &str, style: &Style) -> String {
118 style.to_nu_ansi_term_style().paint(text).to_string()
119}
120
121fn entry_is_exec(entry: &Entry) -> bool {
122 #[cfg(unix)]
123 {
124 entry.mode & 0o111 != 0
125 }
126 #[cfg(not(unix))]
127 {
128 let _ = entry;
129 false
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use f00_core::{Entry, EntryKind, GitStatus};
137 use std::path::PathBuf;
138
139 fn file(name: &str) -> Entry {
140 Entry {
141 path: PathBuf::from(name),
142 name: name.into(),
143 kind: EntryKind::File,
144 size: 0,
145 modified: None,
146 created: None,
147 accessed: None,
148 changed: None,
149 mode: 0o644,
150 readonly: false,
151 symlink_target: None,
152 depth: 0,
153 git_status: GitStatus::Clean,
154 is_dir_header: false,
155 nlink: 1,
156 uid: 0,
157 gid: 0,
158 inode: 0,
159 blocks: 0,
160 owner: "u".into(),
161 group: "g".into(),
162 author: "u".into(),
163 context: String::new(),
164 }
165 }
166
167 #[test]
168 fn disabled_no_ansi() {
169 let c = Colorizer::from_ls_colors(false, "*.rs=01;31");
170 let e = file("main.rs");
171 assert_eq!(c.paint_name(&e, "main.rs"), "main.rs");
172 }
173
174 #[test]
175 fn ls_colors_extension() {
176 let c = Colorizer::from_ls_colors(true, "*.rs=01;31:");
177 let e = file("main.rs");
178 let painted = c.paint_name(&e, "main.rs");
179 assert!(painted.contains("main.rs"), "{painted:?}");
181 let _ = c.style_for_entry(&e);
183 }
184}