rust_devicons/
icons.rs

1use std::{
2    fmt::{self, Display, Formatter},
3    path::Path,
4};
5
6pub mod dark;
7pub mod light;
8
9pub struct File<'a> {
10    path: &'a Path,
11    pub name: &'a str,
12    ext: Option<String>,
13}
14
15impl File<'_> {
16    pub fn new<'a>(path: &'a Path) -> File<'a> {
17        let path = path;
18        let name = path.file_name().unwrap().to_str().unwrap();
19        let ext = Self::ext(path);
20
21        File { path, name, ext }
22    }
23
24    fn points_to_directory(&self) -> bool {
25        self.path.display().to_string().ends_with('/')
26    }
27
28    fn ext(path: &Path) -> Option<String> {
29        if let Some(ext) = path.extension() {
30            return Some(ext.to_string_lossy().to_string());
31        }
32        let name = path.file_name().map(|f| f.to_string_lossy().to_string())?;
33
34        name.rfind('.').map(|p| name[p + 1..].to_ascii_lowercase())
35    }
36}
37
38pub enum Theme {
39    Light,
40    Dark,
41}
42
43#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
44pub struct FileIcon {
45    pub icon: char,
46    pub color: &'static str,
47}
48
49impl Display for FileIcon {
50    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
51        write!(f, "{}", self.icon)
52    }
53}
54
55impl Default for FileIcon {
56    fn default() -> Self {
57        DEFAULT_FILE_ICON
58    }
59}
60
61const DEFAULT_FILE_ICON: FileIcon = FileIcon {
62    icon: '\u{f016}',
63    color: "#7e8e91",
64};
65
66const DEFAULT_DIR_ICON: FileIcon = FileIcon {
67    icon: '\u{f115}',
68    color: "#7e8e91",
69};
70
71pub fn icon_for_file(file: &File<'_>, theme: Option<Theme>) -> FileIcon {
72    match theme {
73        Some(Theme::Light) => light_icon_for_file(file),
74        Some(Theme::Dark) => dark_icon_for_file(file),
75        None => dark_icon_for_file(file),
76    }
77}
78
79fn dark_icon_for_file(file: &File<'_>) -> FileIcon {
80    if let Some(icon) = dark::ICONS_MAP.get(file.name) {
81        return icon.clone();
82    } else if let Some(extension) = &file.ext {
83        if let Some(icon) = dark::ICONS_MAP.get(extension.as_str()) {
84            return icon.clone();
85        } else if let Some(icon) = dark::ICONS_MAP.get(extension.to_lowercase().as_str()) {
86            return icon.clone();
87        } else {
88            DEFAULT_FILE_ICON
89        }
90    } else if file.points_to_directory() {
91        DEFAULT_DIR_ICON
92    } else {
93        DEFAULT_FILE_ICON
94    }
95}
96
97fn light_icon_for_file(file: &File<'_>) -> FileIcon {
98    if let Some(icon) = light::ICONS_MAP.get(file.name) {
99        return icon.clone();
100    } else if let Some(extension) = &file.ext {
101        if let Some(icon) = light::ICONS_MAP.get(extension.as_str()) {
102            return icon.clone();
103        } else if let Some(icon) = light::ICONS_MAP.get(extension.to_lowercase().as_str()) {
104            return icon.clone();
105        } else {
106            DEFAULT_FILE_ICON
107        }
108    } else if file.points_to_directory() {
109        DEFAULT_DIR_ICON
110    } else {
111        DEFAULT_FILE_ICON
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use std::path::Path;
119
120    #[test]
121    fn test_file_new() {
122        let path = Path::new("example.txt");
123        let file = File::new(path);
124
125        assert_eq!(file.name, "example.txt");
126        assert_eq!(file.ext, Some("txt".to_string()));
127    }
128
129    #[test]
130    fn test_file_new_directory() {
131        let path = Path::new("some_directory/");
132        let file = File::new(path);
133
134        assert_eq!(file.name, "some_directory");
135        assert_eq!(file.ext, None);
136    }
137
138    #[test]
139    fn test_points_to_directory() {
140        let dir_path = Path::new("some_directory/");
141        let file = File::new(dir_path);
142
143        assert!(file.points_to_directory());
144
145        let file_path = Path::new("file.txt");
146        let file = File::new(file_path);
147
148        assert!(!file.points_to_directory());
149    }
150
151    #[test]
152    fn test_file_extension() {
153        let path = Path::new("file.txt");
154        let ext = File::ext(path);
155        assert_eq!(ext, Some("txt".to_string()));
156
157        let no_ext_path = Path::new("file");
158        let ext = File::ext(no_ext_path);
159        assert_eq!(ext, None);
160    }
161
162    #[test]
163    fn test_icon_for_file_with_light_theme() {
164        let path = Path::new("file.txt");
165        let file = File::new(path);
166
167        let icon = icon_for_file(&file, Some(Theme::Light));
168        assert_eq!(icon.icon, '󰈙');
169        assert_eq!(icon.color, "#447028");
170    }
171
172    #[test]
173    fn test_icon_for_file_with_dark_theme() {
174        let path = Path::new("file.txt");
175        let file = File::new(path);
176
177        let icon = icon_for_file(&file, Some(Theme::Dark));
178        assert_eq!(icon.icon, '󰈙');
179        assert_eq!(icon.color, "#89e051");
180    }
181
182    #[test]
183    fn test_default_icon_for_directory() {
184        let path = Path::new("some_directory/");
185        let file = File::new(path);
186
187        let icon = icon_for_file(&file, Some(Theme::Dark));
188        assert_eq!(icon.icon, '\u{f115}'); // Default directory icon
189        assert_eq!(icon.color, "#7e8e91");
190    }
191
192    #[test]
193    fn test_icon_for_file_with_no_theme() {
194        let path = Path::new("file.txt");
195        let file = File::new(path);
196
197        let icon = icon_for_file(&file, None); // Should default to Dark theme
198        assert_eq!(icon.icon, '󰈙');
199        assert_eq!(icon.color, "#89e051");
200    }
201}