summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBenjamin Sago <ogham@bsago.me>2019-07-15 03:54:46 +0100
committerBenjamin Sago <ogham@bsago.me>2019-07-15 03:54:46 +0100
commit2e0e29da2284f956a6af1b15f3702b1e20dff76e (patch)
tree3b56bf2b7a0c774ef3a76f72fd48d47fb6d78f95
parent9fcd7ad1502ab10cb3f50e92a2637bfe6e0fc8cc (diff)
parent9497b30b3322c52e9c8117a97abd6d96001dac65 (diff)
Merge branch 'asoderman-glyphs'
-rw-r--r--src/exa.rs4
-rw-r--r--src/info/filetype.rs14
-rw-r--r--src/options/flags.rs3
-rw-r--r--src/options/view.rs30
-rw-r--r--src/output/details.rs29
-rw-r--r--src/output/grid.rs12
-rw-r--r--src/output/grid_details.rs16
-rw-r--r--src/output/icons.rs120
-rw-r--r--src/output/lines.rs20
-rw-r--r--src/output/mod.rs3
10 files changed, 224 insertions, 27 deletions
diff --git a/src/exa.rs b/src/exa.rs
index 9079ea7..a3f4583 100644
--- a/src/exa.rs
+++ b/src/exa.rs
@@ -221,8 +221,8 @@ impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
let View { ref mode, ref colours, ref style } = self.options.view;
match *mode {
- Mode::Lines => {
- let r = lines::Render { files, colours, style };
+ Mode::Lines(ref opts) => {
+ let r = lines::Render { files, colours, style, opts };
r.render(self.writer)
}
diff --git a/src/info/filetype.rs b/src/info/filetype.rs
index f9157b7..a0ebfa1 100644
--- a/src/info/filetype.rs
+++ b/src/info/filetype.rs
@@ -8,6 +8,7 @@ use ansi_term::Style;
use fs::File;
use output::file_name::FileColours;
+use output::icons::FileIcon;
#[derive(Debug, Default, PartialEq)]
@@ -115,3 +116,16 @@ impl FileColours for FileExtensions {
})
}
}
+
+impl FileIcon for FileExtensions {
+ fn icon_file(&self, file: &File) -> Option<char> {
+ use output::icons::Icons;
+
+ Some(match file {
+ f if self.is_music(f) || self.is_lossless(f) => Icons::Audio.value(),
+ f if self.is_image(f) => Icons::Image.value(),
+ f if self.is_video(f) => Icons::Video.value(),
+ _ => return None,
+ })
+ }
+}
diff --git a/src/options/flags.rs b/src/options/flags.rs
index 113e5af..a89c63e 100644
--- a/src/options/flags.rs
+++ b/src/options/flags.rs
@@ -40,6 +40,7 @@ pub static BINARY: Arg = Arg { short: Some(b'b'), long: "binary", takes_
pub static BYTES: Arg = Arg { short: Some(b'B'), long: "bytes", takes_value: TakesValue::Forbidden };
pub static GROUP: Arg = Arg { short: Some(b'g'), long: "group", takes_value: TakesValue::Forbidden };
pub static HEADER: Arg = Arg { short: Some(b'h'), long: "header", takes_value: TakesValue::Forbidden };
+pub static ICONS: Arg = Arg { short: None, long: "icons", takes_value: TakesValue::Forbidden };
pub static INODE: Arg = Arg { short: Some(b'i'), long: "inode", takes_value: TakesValue::Forbidden };
pub static LINKS: Arg = Arg { short: Some(b'H'), long: "links", takes_value: TakesValue::Forbidden };
pub static MODIFIED: Arg = Arg { short: Some(b'm'), long: "modified", takes_value: TakesValue::Forbidden };
@@ -66,7 +67,7 @@ pub static ALL_ARGS: Args = Args(&[
&ALL, &LIST_DIRS, &LEVEL, &REVERSE, &SORT, &DIRS_FIRST,
&IGNORE_GLOB, &GIT_IGNORE, &ONLY_DIRS,
- &BINARY, &BYTES, &GROUP, &HEADER, &INODE, &LINKS, &MODIFIED, &CHANGED,
+ &BINARY, &BYTES, &GROUP, &HEADER, &ICONS, &INODE, &LINKS, &MODIFIED, &CHANGED,
&BLOCKS, &TIME, &ACCESSED, &CREATED, &TIME_STYLE,
&GIT, &EXTENDED,
diff --git a/src/options/view.rs b/src/options/view.rs
index 87e65d1..23e9a44 100644
--- a/src/options/view.rs
+++ b/src/options/view.rs
@@ -1,4 +1,4 @@
-use output::{View, Mode, grid, details};
+use output::{View, Mode, grid, details, lines};
use output::grid_details::{self, RowThreshold};
use output::table::{TimeTypes, Environment, SizeFormat, Columns, Options as TableOptions};
use output::time::TimeFormat;
@@ -41,6 +41,7 @@ impl Mode {
table: Some(TableOptions::deduce(matches, vars)?),
header: matches.has(&flags::HEADER)?,
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
+ icons: matches.has(&flags::ICONS)?,
})
}
};
@@ -52,7 +53,8 @@ impl Mode {
Err(Useless(&flags::ACROSS, true, &flags::ONE_LINE))
}
else {
- Ok(Mode::Lines)
+ let lines = lines::Options { icons: matches.has(&flags::ICONS)? };
+ Ok(Mode::Lines(lines))
}
}
else if matches.has(&flags::TREE)? {
@@ -60,6 +62,7 @@ impl Mode {
table: None,
header: false,
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
+ icons: matches.has(&flags::ICONS)?,
};
Ok(Mode::Details(details))
@@ -68,11 +71,13 @@ impl Mode {
let grid = grid::Options {
across: matches.has(&flags::ACROSS)?,
console_width: width,
+ icons: matches.has(&flags::ICONS)?,
};
Ok(Mode::Grid(grid))
}
}
+
// If the terminal width couldn’t be matched for some reason, such
// as the program’s stdout being connected to a file, then
// fallback to the lines view.
@@ -81,12 +86,14 @@ impl Mode {
table: None,
header: false,
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
+ icons: matches.has(&flags::ICONS)?,
};
Ok(Mode::Details(details))
}
else {
- Ok(Mode::Lines)
+ let lines = lines::Options { icons: matches.has(&flags::ICONS)?, };
+ Ok(Mode::Lines(lines))
}
};
@@ -380,7 +387,7 @@ mod test {
static TEST_ARGS: &[&Arg] = &[ &flags::BINARY, &flags::BYTES, &flags::TIME_STYLE,
&flags::TIME, &flags::MODIFIED, &flags::CHANGED,
- &flags::CREATED, &flags::ACCESSED,
+ &flags::CREATED, &flags::ACCESSED, &flags::ICONS,
&flags::HEADER, &flags::GROUP, &flags::INODE, &flags::GIT,
&flags::LINKS, &flags::BLOCKS, &flags::LONG, &flags::LEVEL,
&flags::GRID, &flags::ACROSS, &flags::ONE_LINE ];
@@ -563,19 +570,22 @@ mod test {
mod views {
use super::*;
use output::grid::Options as GridOptions;
+ use output::lines::Options as LineOptions;
// Default
test!(empty: Mode <- [], None; Both => like Ok(Mode::Grid(_)));
// Grid views
- test!(original_g: Mode <- ["-G"], None; Both => like Ok(Mode::Grid(GridOptions { across: false, console_width: _ })));
- test!(grid: Mode <- ["--grid"], None; Both => like Ok(Mode::Grid(GridOptions { across: false, console_width: _ })));
- test!(across: Mode <- ["--across"], None; Both => like Ok(Mode::Grid(GridOptions { across: true, console_width: _ })));
- test!(gracross: Mode <- ["-xG"], None; Both => like Ok(Mode::Grid(GridOptions { across: true, console_width: _ })));
+ test!(original_g: Mode <- ["-G"], None; Both => like Ok(Mode::Grid(GridOptions { across: false, console_width: _, icons: _ })));
+ test!(grid: Mode <- ["--grid"], None; Both => like Ok(Mode::Grid(GridOptions { across: false, console_width: _, icons: _ })));
+ test!(across: Mode <- ["--across"], None; Both => like Ok(Mode::Grid(GridOptions { across: true, console_width: _, icons: _ })));
+ test!(gracross: Mode <- ["-xG"], None; Both => like Ok(Mode::Grid(GridOptions { across: true, console_width: _, icons: _ })));
+ test!(icons: Mode <- ["--icons"], None; Both => like Ok(Mode::Grid(GridOptions { across: _, console_width: _, icons: true})));
// Lines views
- test!(lines: Mode <- ["--oneline"], None; Both => like Ok(Mode::Lines));
- test!(prima: Mode <- ["-1"], None; Both => like Ok(Mode::Lines));
+ test!(lines: Mode <- ["--oneline"], None; Both => like Ok(Mode::Lines(LineOptions{ icons: _ })));
+ test!(prima: Mode <- ["-1"], None; Both => like Ok(Mode::Lines(LineOptions{ icons: _ })));
+ test!(line_icon: Mode <- ["-1", "--icons"], None; Both => like Ok(Mode::Lines(LineOptions { icons: true })));
// Details views
test!(long: Mode <- ["--long"], None; Both => like Ok(Mode::Details(_)));
diff --git a/src/output/details.rs b/src/output/details.rs
index e30c2b9..577ed74 100644
--- a/src/output/details.rs
+++ b/src/output/details.rs
@@ -64,7 +64,7 @@ use std::io::{Write, Error as IOError, Result as IOResult};
use std::path::PathBuf;
use std::vec::IntoIter as VecIntoIter;
-use ansi_term::Style;
+use ansi_term::{ANSIGenericString, Style};
use fs::{Dir, File};
use fs::dir_action::RecurseOptions;
@@ -77,6 +77,7 @@ use output::cell::TextCell;
use output::tree::{TreeTrunk, TreeParams, TreeDepth};
use output::file_name::FileStyle;
use output::table::{Table, Options as TableOptions, Row as TableRow};
+use output::icons::painted_icon;
use scoped_threadpool::Pool;
@@ -105,6 +106,9 @@ pub struct Options {
/// Whether to show each file's extended attributes.
pub xattr: bool,
+
+ /// Enables --icons mode
+ pub icons: bool,
}
@@ -132,6 +136,7 @@ struct Egg<'a> {
errors: Vec<(IOError, Option<PathBuf>)>,
dir: Option<Dir>,
file: &'a File<'a>,
+ icon: Option<String>,
}
impl<'a> AsRef<File<'a>> for Egg<'a> {
@@ -194,7 +199,7 @@ impl<'a> Render<'a> {
let table = table.as_ref();
for file in src {
- let file_eggs = file_eggs.clone();
+ let file_eggs = Arc::clone(&file_eggs);
scoped.execute(move || {
let mut errors = Vec::new();
@@ -255,7 +260,11 @@ impl<'a> Render<'a> {
}
};
- let egg = Egg { table_row, xattrs, errors, dir, file };
+ let icon = if self.opts.icons {
+ Some(painted_icon(&file, &self.style))
+ } else { None };
+
+ let egg = Egg { table_row, xattrs, errors, dir, file, icon };
file_eggs.lock().unwrap().push(egg);
});
}
@@ -271,12 +280,20 @@ impl<'a> Render<'a> {
t.add_widths(row);
}
+ let mut name_cell = TextCell::default();
+ if let Some(icon) = egg.icon {
+ name_cell.push(ANSIGenericString::from(icon), 2)
+ }
+ name_cell.append(self.style.for_file(&egg.file, self.colours)
+ .with_link_paths()
+ .paint()
+ .promote());
+
+
let row = Row {
tree: tree_params,
cells: egg.table_row,
- name: self.style.for_file(&egg.file, self.colours)
- .with_link_paths()
- .paint().promote(),
+ name: name_cell,
};
rows.push(row);
diff --git a/src/output/grid.rs b/src/output/grid.rs
index c07e7aa..876e100 100644
--- a/src/output/grid.rs
+++ b/src/output/grid.rs
@@ -5,12 +5,15 @@ use term_grid as tg;
use fs::File;
use style::Colours;
use output::file_name::FileStyle;
+use output::icons::painted_icon;
+use output::cell::DisplayWidth;
#[derive(PartialEq, Debug, Copy, Clone)]
pub struct Options {
pub across: bool,
pub console_width: usize,
+ pub icons: bool,
}
impl Options {
@@ -38,11 +41,16 @@ impl<'a> Render<'a> {
grid.reserve(self.files.len());
for file in &self.files {
+ let icon = if self.opts.icons { Some(painted_icon(&file, &self.style)) } else { None };
let filename = self.style.for_file(file, self.colours).paint();
- let width = filename.width();
+ let width = if self.opts.icons {
+ DisplayWidth::from(2) + filename.width()
+ } else {
+ filename.width()
+ };
grid.add(tg::Cell {
- contents: filename.strings().to_string(),
+ contents: format!("{icon}{filename}", icon=&icon.unwrap_or("".to_string()), filename=filename.strings().to_string()),
width: *width,
});
}
diff --git a/src/output/grid_details.rs b/src/output/grid_details.rs
index 408dc9e..ca2ffc8 100644
--- a/src/output/grid_details.rs
+++ b/src/output/grid_details.rs
@@ -2,7 +2,7 @@
use std::io::{Write, Result as IOResult};
-use ansi_term::ANSIStrings;
+use ansi_term::{ANSIGenericString, ANSIStrings};
use term_grid as grid;
use fs::{Dir, File};
@@ -17,7 +17,7 @@ use output::grid::Options as GridOptions;
use output::file_name::FileStyle;
use output::table::{Table, Row as TableRow, Options as TableOptions};
use output::tree::{TreeParams, TreeDepth};
-
+use output::icons::painted_icon;
#[derive(Debug)]
pub struct Options {
@@ -135,7 +135,17 @@ impl<'a> Render<'a> {
.collect::<Vec<TableRow>>();
let file_names = self.files.iter()
- .map(|file| self.style.for_file(file, self.colours).paint().promote())
+ .map(|file| {
+ if self.details.icons {
+ let mut icon_cell = TextCell::default();
+ icon_cell.push(ANSIGenericString::from(painted_icon(&file, &self.style)), 2);
+ let file_cell = self.style.for_file(file, self.colours).paint().promote();
+ icon_cell.append(file_cell);
+ icon_cell
+ } else {
+ self.style.for_file(file, self.colours).paint().promote()
+ }
+ })
.collect::<Vec<TextCell>>();
let mut last_working_table = self.make_grid(1, options, git, &file_names, rows.clone(), &drender);
diff --git a/src/output/icons.rs b/src/output/icons.rs
new file mode 100644
index 0000000..799ead0
--- /dev/null
+++ b/src/output/icons.rs
@@ -0,0 +1,120 @@
+use ansi_term::Style;
+use fs::File;
+use info::filetype::FileExtensions;
+use output::file_name::FileStyle;
+
+pub trait FileIcon {
+ fn icon_file(&self, file: &File) -> Option<char>;
+}
+
+pub enum Icons {
+ Audio,
+ Image,
+ Video,
+}
+
+impl Icons {
+ pub fn value(&self) -> char {
+ match *self {
+ Icons::Audio => '\u{f001}',
+ Icons::Image => '\u{f1c5}',
+ Icons::Video => '\u{f03d}',
+ }
+ }
+}
+
+pub fn painted_icon(file: &File, style: &FileStyle) -> String {
+ let file_icon = icon(&file).to_string();
+ let painted = style.exts
+ .colour_file(&file)
+ .map_or(file_icon.to_string(), |c| {
+ // Remove underline from icon
+ if c.is_underline {
+ match c.foreground {
+ Some(color) => Style::from(color).paint(file_icon).to_string(),
+ None => Style::default().paint(file_icon).to_string(),
+ }
+ } else {
+ c.paint(file_icon).to_string()
+ }
+ });
+ format!("{} ", painted)
+}
+
+fn icon(file: &File) -> char {
+ let extensions = Box::new(FileExtensions);
+ if file.is_directory() { '\u{f115}' }
+ else if let Some(icon) = extensions.icon_file(file) { icon }
+ else {
+ if let Some(ext) = file.ext.as_ref() {
+ match ext.as_str() {
+ "ai" => '\u{e7b4}',
+ "android" => '\u{e70e}',
+ "apple" => '\u{f179}',
+ "avro" => '\u{e60b}',
+ "c" => '\u{e61e}',
+ "clj" => '\u{e768}',
+ "coffee" => '\u{f0f4}',
+ "conf" => '\u{e615}',
+ "cpp" => '\u{e61d}',
+ "css" => '\u{e749}',
+ "d" => '\u{e7af}',
+ "dart" => '\u{e798}',
+ "db" => '\u{f1c0}',
+ "diff" => '\u{f440}',
+ "doc" => '\u{f1c2}',
+ "ebook" => '\u{e28b}',
+ "env" => '\u{f462}',
+ "epub" => '\u{e28a}',
+ "erl" => '\u{e7b1}',
+ "font" => '\u{f031}',
+ "gform" => '\u{f298}',
+ "git" => '\u{f1d3}',
+ "go" => '\u{e626}',
+ "hs" => '\u{e777}',
+ "html" => '\u{f13b}',
+ "iml" => '\u{e7b5}',
+ "java" => '\u{e204}',
+ "js" => '\u{e74e}',
+ "json" => '\u{e60b}',
+ "jsx" => '\u{e7ba}',
+ "less" => '\u{e758}',
+ "log" => '\u{f18d}',
+ "lua" => '\u{e620}',
+ "md" => '\u{f48a}',
+ "mustache" => '\u{e60f}',
+ "npmignore" => '\u{e71e}',
+ "pdf" => '\u{f1c1}',
+ "php" => '\u{e73d}',
+ "pl" => '\u{e769}',
+ "ppt" => '\u{f1c4}',
+ "psd" => '\u{e7b8}',
+ "py" => '\u{e606}',
+ "r" => '\u{f25d}',
+ "rb" => '\u{e21e}',
+ "rdb" => '\u{e76d}',
+ "rs" => '\u{e7a8}',
+ "rss" => '\u{f09e}',
+ "rubydoc" => '\u{e73b}',
+ "sass" => '\u{e603}',
+ "scala" => '\u{e737}',
+ "shell" => '\u{f489}',
+ "sqlite3" => '\u{e7c4}',
+ "styl" => '\u{e600}',
+ "tex" => '\u{e600}',
+ "ts" => '\u{e628}',
+ "twig" => '\u{e61c}',
+ "txt" => '\u{f15c}',
+ "video" => '\u{f03d}',
+ "vim" => '\u{e62b}',
+ "xls" => '\u{f1c3}',
+ "xml" => '\u{e619}',
+ "yml" => '\u{f481}',
+ "zip" => '\u{f410}',
+ _ => '\u{f15b}'
+ }
+ } else {
+ '\u{f15b}'
+ }
+ }
+}
diff --git a/src/output/lines.rs b/src/output/lines.rs
index 31ed017..5326a5d 100644
--- a/src/output/lines.rs
+++ b/src/output/lines.rs
@@ -1,24 +1,40 @@
use std::io::{Write, Result as IOResult};
-use ansi_term::ANSIStrings;
+use ansi_term::{ANSIStrings, ANSIGenericString};
use fs::File;
use output::file_name::{FileName, FileStyle};
use style::Colours;
+use output::icons::painted_icon;
+use output::cell::TextCell;
+#[derive(PartialEq, Debug, Copy, Clone)]
+pub struct Options {
+ pub icons: bool
+}
/// The lines view literally just displays each file, line-by-line.
pub struct Render<'a> {
pub files: Vec<File<'a>>,
pub colours: &'a Colours,
pub style: &'a FileStyle,
+ pub opts: &'a Options,
}
impl<'a> Render<'a> {
pub fn render<W: Write>(&self, w: &mut W) -> IOResult<()> {
for file in &self.files {
let name_cell = self.render_file(file).paint();
- writeln!(w, "{}", ANSIStrings(&name_cell))?;
+ if self.opts.icons {
+ // Create a TextCell for the icon then append the text to it
+ let mut cell = TextCell::default();
+ let icon = painted_icon(&file, self.style);
+ cell.push(ANSIGenericString::from(icon), 2);
+ cell.append(name_cell.promote());
+ writeln!(w, "{}", ANSIStrings(&cell))?;
+ } else {
+ writeln!(w, "{}", ANSIStrings(&name_cell))?;
+ }
}
Ok(())
diff --git a/src/output/mod.rs b/src/output/mod.rs
index ebbca34..f2761e8 100644
--- a/src/output/mod.rs
+++ b/src/output/mod.rs
@@ -8,6 +8,7 @@ pub mod details;
pub mod file_name;
pub mod grid_details;
pub mod grid;
+pub mod icons;
pub mod lines;
pub mod render;
pub mod table;
@@ -34,5 +35,5 @@ pub enum Mode {
Grid(grid::Options),
Details(details::Options),
GridDetails(grid_details::Options),
- Lines,
+ Lines(lines::Options),
}