summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrabite <rabite@posteo.de>2019-05-09 16:52:23 +0200
committerrabite <rabite@posteo.de>2019-05-09 16:53:30 +0200
commit7817cc1d600535e054da7d4435bb7406751d9963 (patch)
treed9448518f48885f198e5038e290b282b0851c5b0
parent572a217e17b0cc2c30cd94b57e5a94ede75e0707 (diff)
add icons to file list
-rw-r--r--src/config.rs8
-rw-r--r--src/files.rs6
-rw-r--r--src/icon.rs395
-rw-r--r--src/listview.rs11
-rw-r--r--src/main.rs1
5 files changed, 417 insertions, 4 deletions
diff --git a/src/config.rs b/src/config.rs
index 5b0f8dc..496ce9b 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -6,7 +6,8 @@ pub struct Config {
pub animation: bool,
pub show_hidden: bool,
pub select_cmd: String,
- pub cd_cmd: String
+ pub cd_cmd: String,
+ pub icons: bool
}
@@ -16,7 +17,8 @@ impl Config {
animation: true,
show_hidden: false,
select_cmd: "find -type f | fzf -m".to_string(),
- cd_cmd: "find -type d | fzf".to_string()
+ cd_cmd: "find -type d | fzf".to_string(),
+ icons: false
}
}
@@ -35,6 +37,8 @@ impl Config {
Ok(("animation", "off")) => { config.animation = false; },
Ok(("show_hidden", "on")) => { config.show_hidden = true; },
Ok(("show_hidden", "off")) => { config.show_hidden = false; },
+ Ok(("icons", "on")) => config.icons = true,
+ Ok(("icons", "off")) => config.icons = false,
Ok(("select_cmd", cmd)) => {
let cmd = cmd.to_string();
config.select_cmd = cmd;
diff --git a/src/files.rs b/src/files.rs
index a191873..a5b9b46 100644
--- a/src/files.rs
+++ b/src/files.rs
@@ -24,11 +24,13 @@ use crate::fail::{HResult, HError, ErrorLog};
use crate::dirty::{AsyncDirtyBit, DirtyBit, Dirtyable};
use crate::preview::{Async, Stale};
use crate::widget::Events;
+use crate::icon::Icons;
lazy_static! {
static ref COLORS: LsColors = LsColors::from_env().unwrap_or_default();
static ref TAGS: RwLock<(bool, Vec<PathBuf>)> = RwLock::new((false, vec![]));
+ static ref ICONS: Icons = Icons::new();
}
fn make_pool(sender: Option<Sender<Events>>) -> ThreadPool {
@@ -1010,6 +1012,10 @@ impl File {
Some(time.format("%F %R").to_string())
}
+ pub fn icon(&self) -> &'static str {
+ ICONS.get(&self.path)
+ }
+
pub fn short_path(&self) -> PathBuf {
self.path.short_path()
}
diff --git a/src/icon.rs b/src/icon.rs
new file mode 100644
index 0000000..f261d1c
--- /dev/null
+++ b/src/icon.rs
@@ -0,0 +1,395 @@
+// Stolen from lsd: https://github.com/Peltoche/lsd
+// Apache License 2.0
+
+use std::path::PathBuf;
+use std::collections::HashMap;
+
+pub struct Icons {
+ icons_by_name: HashMap<&'static str, &'static str>,
+ icons_by_extension: HashMap<&'static str, &'static str>,
+ default_folder_icon: &'static str,
+ default_file_icon: &'static str,
+}
+
+// In order to add a new icon, write the unicode value like "\ue5fb" then
+// run the command below in vim:
+//
+// s#\\u[0-9a-f]*#\=eval('"'.submatch(0).'"')#
+impl Icons {
+ pub fn new() -> Self {
+ let (icons_by_name,
+ icons_by_extension,
+ default_file_icon,
+ default_folder_icon) = (Self::get_default_icons_by_name(),
+ Self::get_default_icons_by_extension(),
+ "\u{f016}", // 
+ "\u{f115}"); // 
+
+ Self {
+ icons_by_name,
+ icons_by_extension,
+ default_file_icon,
+ default_folder_icon,
+ }
+ }
+
+ pub fn get(&self, name: &PathBuf) -> &'static str {
+ let file_name = name.file_name()
+ .and_then(|name| name.to_str())
+ .unwrap_or("");
+
+ let extension = name.extension()
+ .and_then(|ext| ext.to_str());
+
+ // Check the known names.
+ if let Some(icon) = self.icons_by_name.get(file_name) {
+ return icon;
+ }
+
+ // Check the known extensions.
+ if let Some(extension) = extension {
+ if let Some(icon) = self.icons_by_extension.get(extension) {
+ return icon;
+ }
+ }
+
+ if name.is_dir() {
+ return self.default_folder_icon;
+ }
+
+ // Use the default icons.
+ return self.default_file_icon;
+ }
+
+ fn get_default_icons_by_name() -> HashMap<&'static str, &'static str> {
+ let mut m = HashMap::new();
+
+ m.insert(".Trash", "\u{f1f8}"); // ""
+ m.insert(".atom", "\u{e764}"); // ""
+ m.insert(".bashprofile", "\u{e615}"); // ""
+ m.insert(".bashrc", "\u{f489}"); // ""
+ m.insert(".git", "\u{f1d3}"); // ""
+ m.insert(".gitconfig", "\u{f1d3}"); // ""
+ m.insert(".github", "\u{f408}"); // ""
+ m.insert(".gitignore", "\u{f1d3}"); // ""
+ m.insert(".rvm", "\u{e21e}"); // ""
+ m.insert(".vimrc", "\u{e62b}"); // ""
+ m.insert(".vscode", "\u{e70c}"); // ""
+ m.insert(".zshrc", "\u{f489}"); // ""
+ m.insert("bin", "\u{e5fc}"); // ""
+ m.insert("config", "\u{e5fc}"); // ""
+ m.insert("docker-compose.yml", "\u{f308}"); // ""
+ m.insert("dockerfile", "\u{f308}"); // ""
+ m.insert("ds_store", "\u{f179}"); // ""
+ m.insert("gitignore_global", "\u{f1d3}"); // ""
+ m.insert("gradle", "\u{e70e}"); // ""
+ m.insert("gruntfile.coffee", "\u{e611}"); // ""
+ m.insert("gruntfile.js", "\u{e611}"); // ""
+ m.insert("gruntfile.ls", "\u{e611}"); // ""
+ m.insert("gulpfile.coffee", "\u{e610}"); // ""
+ m.insert("gulpfile.js", "\u{e610}"); // ""
+ m.insert("gulpfile.ls", "\u{e610}"); // ""
+ m.insert("hidden", "\u{f023}"); // ""
+ m.insert("include", "\u{e5fc}"); // ""
+ m.insert("lib", "\u{f121}"); // ""
+ m.insert("localized", "\u{f179}"); // ""
+ m.insert("node_modules", "\u{e718}"); // ""
+ m.insert("npmignore", "\u{e71e}"); // ""
+ m.insert("rubydoc", "\u{e73b}"); // ""
+ m.insert("yarn.lock", "\u{e718}"); // ""
+
+ m
+ }
+
+ fn get_default_icons_by_extension() -> HashMap<&'static str, &'static str> {
+ let mut m = HashMap::new();
+
+ m.insert("apk", "\u{e70e}"); // ""
+ m.insert("apk", "\u{e70e}"); // ""
+ m.insert("avi", "\u{f03d}"); // ""
+ m.insert("avro", "\u{e60b}"); // ""
+ m.insert("awk", "\u{f489}"); // ""
+ m.insert("bash", "\u{f489}"); // ""
+ m.insert("bash_history", "\u{f489}"); // ""
+ m.insert("bash_profile", "\u{f489}"); // ""
+ m.insert("bashrc", "\u{f489}"); // ""
+ m.insert("bat", "\u{f17a}"); // ""
+ m.insert("bmp", "\u{f1c5}"); // ""
+ m.insert("c", "\u{e61e}"); // ""
+ m.insert("c++", "\u{e61d}"); // ""
+ m.insert("cc", "\u{e61d}"); // ""
+ m.insert("cfg", "\u{e615}"); // ""
+ m.insert("clj", "\u{e768}"); // ""
+ m.insert("cljs", "\u{e76a}"); // ""
+ m.insert("cls", "\u{e600}"); // ""
+ m.insert("coffee", "\u{f0f4}"); // ""
+ m.insert("conf", "\u{e615}"); // ""
+ m.insert("cp", "\u{e61d}"); // ""
+ m.insert("cpp", "\u{e61d}"); // ""
+ m.insert("csh", "\u{f489}"); // ""
+ m.insert("css", "\u{e749}"); // ""
+ m.insert("csv", "\u{f1c3}"); // ""
+ m.insert("cxx", "\u{e61d}"); // ""
+ m.insert("d", "\u{e7af}"); // ""
+ m.insert("dart", "\u{e798}"); // ""
+ m.insert("db", "\u{f1c0}"); // ""
+ m.insert("diff", "\u{f440}"); // ""
+ m.insert("doc", "\u{f1c2}"); // ""
+ m.insert("docx", "\u{f1c2}"); // ""
+ m.insert("ds_store", "\u{f179}"); // ""
+ m.insert("dump", "\u{f1c0}"); // ""
+ m.insert("ebook", "\u{e28b}"); // ""
+ m.insert("editorconfig", "\u{e615}"); // ""
+ m.insert("ejs", "\u{e618}"); // ""
+ m.insert("env", "\u{f462}"); // ""
+ m.insert("eot", "\u{f031}"); // ""
+ m.insert("epub", "\u{e28a}"); // ""
+ m.insert("erb", "\u{e73b}"); // ""
+ m.insert("erl", "\u{e7b1}"); // ""
+ m.insert("exe", "\u{f17a}"); // ""
+ m.insert("fish", "\u{f489}"); // ""
+ m.insert("flac", "\u{f001}"); // ""
+ m.insert("flv", "\u{f03d}"); // ""
+ m.insert("font", "\u{f031}"); // ""
+ m.insert("gdoc", "\u{f1c2}"); // ""
+ m.insert("gemfile", "\u{e21e}"); // ""
+ m.insert("gemspec", "\u{e21e}"); // ""
+ m.insert("gform", "\u{f298}"); // ""
+ m.insert("gif", "\u{f1c5}"); // ""
+ m.insert("git", "\u{f1d3}"); // ""
+ m.insert("go", "\u{e626}"); // ""
+ m.insert("gradle", "\u{e70e}"); // ""
+ m.insert("gsheet", "\u{f1c3}"); // ""
+ m.insert("gslides", "\u{f1c4}"); // ""
+ m.insert("guardfile", "\u{e21e}"); // ""
+ m.insert("gz", "\u{f410}"); // ""
+ m.insert("h", "\u{f0fd}"); // ""
+ m.insert("hbs", "\u{e60f}"); // ""
+ m.insert("hpp", "\u{f0fd}"); // ""
+ m.insert("hs", "\u{e777}"); // ""
+ m.insert("htm", "\u{f13b}"); // ""
+ m.insert("html", "\u{f13b}"); // ""
+ m.insert("hxx", "\u{f0fd}"); // ""
+ m.insert("ico", "\u{f1c5}"); // ""
+ m.insert("image", "\u{f1c5}"); // ""
+ m.insert("iml", "\u{e7b5}"); // ""
+ m.insert("ini", "\u{f17a}"); // ""
+ m.insert("ipynb", "\u{e606}"); // ""
+ m.insert("jar", "\u{e204}"); // ""
+ m.insert("java", "\u{e204}"); // ""
+ m.insert("jpeg", "\u{f1c5}"); // ""
+ m.insert("jpg", "\u{f1c5}"); // ""
+ m.insert("js", "\u{e74e}"); // ""
+ m.insert("json", "\u{e60b}"); // ""
+ m.insert("jsx", "\u{e7ba}"); // ""
+ m.insert("ksh", "\u{f489}"); // ""
+ m.insert("less", "\u{e758}"); // ""
+ m.insert("lhs", "\u{e777}"); // ""
+ m.insert("license", "\u{f48a}"); // ""
+ m.insert("localized", "\u{f179}"); // ""
+ m.insert("lock", "\u{e21e}"); // ""
+ m.insert("log", "\u{f18d}"); // ""
+ m.insert("lua", "\u{e620}"); // ""
+ m.insert("m4a", "\u{f001}"); // ""
+ m.insert("markdown", "\u{f48a}"); // ""
+ m.insert("md", "\u{f48a}"); // ""
+ m.insert("mkd", "\u{f48a}"); // ""
+ m.insert("mkv", "\u{f03d}"); // ""
+ m.insert("mobi", "\u{e28b}"); // ""
+ m.insert("mov", "\u{f03d}"); // ""
+ m.insert("mp3", "\u{f001}"); // ""
+ m.insert("mp4", "\u{f03d}"); // ""
+ m.insert("mustache", "\u{e60f}"); // ""
+ m.insert("npmignore", "\u{e71e}"); // ""
+ m.insert("ogg", "\u{f001}"); // ""
+ m.insert("ogv", "\u{f03d}"); // ""
+ m.insert("otf", "\u{f031}"); // ""
+ m.insert("pdf", "\u{f1c1}"); // ""
+ m.insert("php", "\u{e73d}"); // ""
+ m.insert("pl", "\u{e769}"); // ""
+ m.insert("png", "\u{f1c5}"); // ""
+ m.insert("ppt", "\u{f1c4}"); // ""
+ m.insert("pptx", "\u{f1c4}"); // ""
+ m.insert("procfile", "\u{e21e}"); // ""
+ m.insert("properties", "\u{e60b}"); // ""
+ m.insert("ps1", "\u{f489}"); // ""
+ m.insert("psd", "\u{e7b8}"); // ""
+ m.insert("pxm", "\u{f1c5}"); // ""
+ m.insert("py", "\u{e606}"); // ""
+ m.insert("pyc", "\u{e606}"); // ""
+ m.insert("r", "\u{f25d}"); // ""
+ m.insert("rakefile", "\u{e21e}"); // ""
+ m.insert("rar", "\u{f410}"); // ""
+ m.insert("rb", "\u{e21e}"); // ""
+ m.insert("rdata", "\u{f25d}"); // ""
+ m.insert("rdb", "\u{e76d}"); // ""
+ m.insert("rdoc", "\u{f48a}"); // ""
+ m.insert("rds", "\u{f25d}"); // ""
+ m.insert("readme", "\u{f48a}"); // ""
+ m.insert("rlib", "\u{e7a8}"); // ""
+ m.insert("rmd", "\u{f48a}"); // ""
+ m.insert("rs", "\u{e7a8}"); // ""
+ m.insert("rspec", "\u{e21e}"); // ""
+ m.insert("rspec_parallel", "\u{e21e}"); // ""
+ m.insert("rspec_status", "\u{e21e}"); // ""
+ m.insert("rss", "\u{f09e}"); // ""
+ m.insert("ru", "\u{e21e}"); // ""
+ m.insert("rubydoc", "\u{e73b}"); // ""
+ m.insert("sass", "\u{e603}"); // ""
+ m.insert("scala", "\u{e737}"); // ""
+ m.insert("scss", "\u{e749}"); // ""
+ m.insert("sh", "\u{f489}"); // ""
+ m.insert("shell", "\u{f489}"); // ""
+ m.insert("slim", "\u{e73b}"); // ""
+ m.insert("sql", "\u{f1c0}"); // ""
+ m.insert("sqlite3", "\u{e7c4}"); // ""
+ m.insert("styl", "\u{e600}"); // ""
+ m.insert("stylus", "\u{e600}"); // ""
+ m.insert("svg", "\u{f1c5}"); // ""
+ m.insert("swift", "\u{e755}"); // ""
+ m.insert("tar", "\u{f410}"); // ""
+ m.insert("tex", "\u{e600}"); // ""
+ m.insert("tiff", "\u{f1c5}"); // ""
+ m.insert("ts", "\u{e628}"); // ""
+ m.insert("tsx", "\u{e7ba}"); // ""
+ m.insert("ttf", "\u{f031}"); // ""
+ m.insert("twig", "\u{e61c}"); // ""
+ m.insert("txt", "\u{f15c}"); // ""
+ m.insert("video", "\u{f03d}"); // ""
+ m.insert("vim", "\u{e62b}"); // ""
+ m.insert("vue", "\u{fd42}"); // "﵂"
+ m.insert("wav", "\u{f001}"); // ""
+ m.insert("webm", "\u{f03d}"); // ""
+ m.insert("webp", "\u{f1c5}"); // ""
+ m.insert("windows", "\u{f17a}"); // ""
+ m.insert("woff", "\u{f031}"); // ""
+ m.insert("woff2", "\u{f031}"); // ""
+ m.insert("xls", "\u{f1c3}"); // ""
+ m.insert("xlsx", "\u{f1c3}"); // ""
+ m.insert("xml", "\u{e619}"); // ""
+ m.insert("xul", "\u{e619}"); // ""
+ m.insert("yaml", "\u{f481}"); // ""
+ m.insert("yml", "\u{f481}"); // ""
+ m.insert("zip", "\u{f410}"); // ""
+ m.insert("zsh", "\u{f489}"); // ""
+ m.insert("zsh-theme", "\u{f489}"); // ""
+ m.insert("zshrc", "\u{f489}"); // ""
+
+ m
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::{Icons, Theme, ICON_SPACE};
+ use crate::meta::Meta;
+ use std::fs::File;
+ use tempdir::TempDir;
+
+ #[test]
+ fn get_no_icon() {
+ let tmp_dir = TempDir::new("test_file_type").expect("failed to create temp dir");
+ let file_path = tmp_dir.path().join("file.txt");
+ File::create(&file_path).expect("failed to create file");
+ let meta = Meta::from_path(&file_path).unwrap();
+
+ let icon = Icons::new(Theme::NoIcon);
+ let icon = icon.get(&meta.name);
+
+ assert_eq!(icon, "");
+ }
+
+ #[test]
+ fn get_default_file_icon() {
+ let tmp_dir = TempDir::new("test_file_type").expect("failed to create temp dir");
+ let file_path = tmp_dir.path().join("file");
+ File::create(&file_path).expect("failed to create file");
+ let meta = Meta::from_path(&file_path).unwrap();
+
+ let icon = Icons::new(Theme::Fancy);
+ let icon = icon.get(&meta.name);
+
+ assert_eq!(icon, format!("{}{}", "\u{f016}", ICON_SPACE)); // 
+ }
+
+ #[test]
+ fn get_default_file_icon_unicode() {
+ let tmp_dir = TempDir::new("test_file_type").expect("failed to create temp dir");
+ let file_path = tmp_dir.path().join("file");
+ File::create(&file_path).expect("failed to create file");
+ let meta = Meta::from_path(&file_path).unwrap();
+
+ let icon = Icons::new(Theme::Unicode);
+ let icon = icon.get(&meta.name);
+
+ assert_eq!(icon, format!("{}{}", "\u{1f5cb}", ICON_SPACE));
+ }
+
+ #[test]
+ fn get_directory_icon() {
+ let tmp_dir = TempDir::new("test_file_type").expect("failed to create temp dir");
+ let file_path = tmp_dir.path();
+ let meta = Meta::from_path(&file_path.to_path_buf()).unwrap();
+
+ let icon = Icons::new(Theme::Fancy);
+ let icon = icon.get(&meta.name);
+
+ assert_eq!(icon, format!("{}{}", "\u{f115}", ICON_SPACE)); // 
+ }
+
+ #[test]
+ fn get_directory_icon_unicode() {
+ let tmp_dir = TempDir::new("test_file_type").expect("failed to create temp dir");
+ let file_path = tmp_dir.path();
+ let meta = Meta::from_path(&file_path.to_path_buf()).unwrap();
+
+ let icon = Icons::new(Theme::Unicode);
+ let icon = icon.get(&meta.name);
+
+ assert_eq!(icon, format!("{}{}", "\u{1f5c1}", ICON_SPACE));
+ }
+
+ #[test]
+ fn get_directory_icon_with_ext() {
+ let tmp_dir = TempDir::new("test_file_type.rs").expect("failed to create temp dir");
+ let file_path = tmp_dir.path();
+ let meta = Meta::from_path(&file_path.to_path_buf()).unwrap();
+
+ let icon = Icons::new(Theme::Fancy);
+ let icon = icon.get(&meta.name);
+
+ assert_eq!(icon, format!("{}{}", "\u{f115}", ICON_SPACE)); // 
+ }
+
+ #[test]
+ fn get_icon_by_name() {
+ let tmp_dir = TempDir::new("test_file_type").expect("failed to create temp dir");
+
+ for (file_name, file_icon) in &Icons::get_default_icons_by_name() {
+ let file_path = tmp_dir.path().join(file_name);
+ File::create(&file_path).expect("failed to create file");
+ let meta = Meta::from_path(&file_path).unwrap();
+
+ let icon = Icons::new(Theme::Fancy);
+ let icon = icon.get(&meta.name);
+
+ assert_eq!(icon, format!("{}{}", file_icon, ICON_SPACE));
+ }
+ }
+
+ #[test]
+ fn get_icon_by_extension() {
+ let tmp_dir = TempDir::new("test_file_type").expect("failed to create temp dir");
+
+ for (ext, file_icon) in &Icons::get_default_icons_by_extension() {
+ let file_path = tmp_dir.path().join(format!("file.{}", ext));
+ File::create(&file_path).expect("failed to create file");
+ let meta = Meta::from_path(&file_path).unwrap();
+
+ let icon = Icons::new(Theme::Fancy);
+ let icon = icon.get(&meta.name);
+
+ assert_eq!(icon, format!("{}{}", file_icon, ICON_SPACE));
+ }
+ }
+}
diff --git a/src/listview.rs b/src/listview.rs
index 3eac914..4c637b9 100644
--- a/src/listview.rs
+++ b/src/listview.rs
@@ -511,8 +511,15 @@ impl ListView<Files>
}
fn render_line(&self, file: &File) -> String {
- let name = &file.name;
+ let icon = if self.config().icons {
+ file.icon()
+ } else { "" };
+
+ let name = String::from(icon) + &file.name;
let (size, unit) = file.calculate_size().unwrap_or((0, "".to_string()));
+
+
+
let tag = match file.is_tagged() {
Ok(true) => term::color_red() + "*",
_ => "".to_string()
@@ -521,7 +528,7 @@ impl ListView<Files>
let selection_gap = " ".to_string();
let (name, selection_color) = if file.is_selected() {
- (selection_gap + name, crate::term::color_yellow())
+ (selection_gap + &name, crate::term::color_yellow())
} else { (name.clone(), "".to_string()) };
let (link_indicator, link_indicator_len) = if file.target.is_some() {
diff --git a/src/main.rs b/src/main.rs
index 03dcfd4..1a06eb0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -50,6 +50,7 @@ mod dirty;
mod fscache;
mod config;
mod stats;
+mod icon;