Merge branch 'asoderman-glyphs'

This commit is contained in:
Benjamin Sago 2019-07-15 03:54:46 +01:00
commit 2e0e29da22
10 changed files with 224 additions and 27 deletions

View File

@ -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; let View { ref mode, ref colours, ref style } = self.options.view;
match *mode { match *mode {
Mode::Lines => { Mode::Lines(ref opts) => {
let r = lines::Render { files, colours, style }; let r = lines::Render { files, colours, style, opts };
r.render(self.writer) r.render(self.writer)
} }

View File

@ -8,6 +8,7 @@ use ansi_term::Style;
use fs::File; use fs::File;
use output::file_name::FileColours; use output::file_name::FileColours;
use output::icons::FileIcon;
#[derive(Debug, Default, PartialEq)] #[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,
})
}
}

View File

@ -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 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 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 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 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 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 }; 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, &ALL, &LIST_DIRS, &LEVEL, &REVERSE, &SORT, &DIRS_FIRST,
&IGNORE_GLOB, &GIT_IGNORE, &ONLY_DIRS, &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, &BLOCKS, &TIME, &ACCESSED, &CREATED, &TIME_STYLE,
&GIT, &EXTENDED, &GIT, &EXTENDED,

View File

@ -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::grid_details::{self, RowThreshold};
use output::table::{TimeTypes, Environment, SizeFormat, Columns, Options as TableOptions}; use output::table::{TimeTypes, Environment, SizeFormat, Columns, Options as TableOptions};
use output::time::TimeFormat; use output::time::TimeFormat;
@ -41,6 +41,7 @@ impl Mode {
table: Some(TableOptions::deduce(matches, vars)?), table: Some(TableOptions::deduce(matches, vars)?),
header: matches.has(&flags::HEADER)?, header: matches.has(&flags::HEADER)?,
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?, 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)) Err(Useless(&flags::ACROSS, true, &flags::ONE_LINE))
} }
else { else {
Ok(Mode::Lines) let lines = lines::Options { icons: matches.has(&flags::ICONS)? };
Ok(Mode::Lines(lines))
} }
} }
else if matches.has(&flags::TREE)? { else if matches.has(&flags::TREE)? {
@ -60,6 +62,7 @@ impl Mode {
table: None, table: None,
header: false, header: false,
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?, xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
icons: matches.has(&flags::ICONS)?,
}; };
Ok(Mode::Details(details)) Ok(Mode::Details(details))
@ -68,11 +71,13 @@ impl Mode {
let grid = grid::Options { let grid = grid::Options {
across: matches.has(&flags::ACROSS)?, across: matches.has(&flags::ACROSS)?,
console_width: width, console_width: width,
icons: matches.has(&flags::ICONS)?,
}; };
Ok(Mode::Grid(grid)) Ok(Mode::Grid(grid))
} }
} }
// If the terminal width couldnt be matched for some reason, such // If the terminal width couldnt be matched for some reason, such
// as the programs stdout being connected to a file, then // as the programs stdout being connected to a file, then
// fallback to the lines view. // fallback to the lines view.
@ -81,12 +86,14 @@ impl Mode {
table: None, table: None,
header: false, header: false,
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?, xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
icons: matches.has(&flags::ICONS)?,
}; };
Ok(Mode::Details(details)) Ok(Mode::Details(details))
} }
else { 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, static TEST_ARGS: &[&Arg] = &[ &flags::BINARY, &flags::BYTES, &flags::TIME_STYLE,
&flags::TIME, &flags::MODIFIED, &flags::CHANGED, &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::HEADER, &flags::GROUP, &flags::INODE, &flags::GIT,
&flags::LINKS, &flags::BLOCKS, &flags::LONG, &flags::LEVEL, &flags::LINKS, &flags::BLOCKS, &flags::LONG, &flags::LEVEL,
&flags::GRID, &flags::ACROSS, &flags::ONE_LINE ]; &flags::GRID, &flags::ACROSS, &flags::ONE_LINE ];
@ -563,19 +570,22 @@ mod test {
mod views { mod views {
use super::*; use super::*;
use output::grid::Options as GridOptions; use output::grid::Options as GridOptions;
use output::lines::Options as LineOptions;
// Default // Default
test!(empty: Mode <- [], None; Both => like Ok(Mode::Grid(_))); test!(empty: Mode <- [], None; Both => like Ok(Mode::Grid(_)));
// Grid views // Grid views
test!(original_g: Mode <- ["-G"], None; Both => like Ok(Mode::Grid(GridOptions { across: false, 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: _ }))); 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: _ }))); 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: _ }))); 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 // Lines views
test!(lines: Mode <- ["--oneline"], 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)); 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 // Details views
test!(long: Mode <- ["--long"], None; Both => like Ok(Mode::Details(_))); test!(long: Mode <- ["--long"], None; Both => like Ok(Mode::Details(_)));

View File

@ -64,7 +64,7 @@ use std::io::{Write, Error as IOError, Result as IOResult};
use std::path::PathBuf; use std::path::PathBuf;
use std::vec::IntoIter as VecIntoIter; use std::vec::IntoIter as VecIntoIter;
use ansi_term::Style; use ansi_term::{ANSIGenericString, Style};
use fs::{Dir, File}; use fs::{Dir, File};
use fs::dir_action::RecurseOptions; use fs::dir_action::RecurseOptions;
@ -77,6 +77,7 @@ use output::cell::TextCell;
use output::tree::{TreeTrunk, TreeParams, TreeDepth}; use output::tree::{TreeTrunk, TreeParams, TreeDepth};
use output::file_name::FileStyle; use output::file_name::FileStyle;
use output::table::{Table, Options as TableOptions, Row as TableRow}; use output::table::{Table, Options as TableOptions, Row as TableRow};
use output::icons::painted_icon;
use scoped_threadpool::Pool; use scoped_threadpool::Pool;
@ -105,6 +106,9 @@ pub struct Options {
/// Whether to show each file's extended attributes. /// Whether to show each file's extended attributes.
pub xattr: bool, pub xattr: bool,
/// Enables --icons mode
pub icons: bool,
} }
@ -132,6 +136,7 @@ struct Egg<'a> {
errors: Vec<(IOError, Option<PathBuf>)>, errors: Vec<(IOError, Option<PathBuf>)>,
dir: Option<Dir>, dir: Option<Dir>,
file: &'a File<'a>, file: &'a File<'a>,
icon: Option<String>,
} }
impl<'a> AsRef<File<'a>> for Egg<'a> { impl<'a> AsRef<File<'a>> for Egg<'a> {
@ -194,7 +199,7 @@ impl<'a> Render<'a> {
let table = table.as_ref(); let table = table.as_ref();
for file in src { for file in src {
let file_eggs = file_eggs.clone(); let file_eggs = Arc::clone(&file_eggs);
scoped.execute(move || { scoped.execute(move || {
let mut errors = Vec::new(); 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); file_eggs.lock().unwrap().push(egg);
}); });
} }
@ -271,12 +280,20 @@ impl<'a> Render<'a> {
t.add_widths(row); 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 { let row = Row {
tree: tree_params, tree: tree_params,
cells: egg.table_row, cells: egg.table_row,
name: self.style.for_file(&egg.file, self.colours) name: name_cell,
.with_link_paths()
.paint().promote(),
}; };
rows.push(row); rows.push(row);

View File

@ -5,12 +5,15 @@ use term_grid as tg;
use fs::File; use fs::File;
use style::Colours; use style::Colours;
use output::file_name::FileStyle; use output::file_name::FileStyle;
use output::icons::painted_icon;
use output::cell::DisplayWidth;
#[derive(PartialEq, Debug, Copy, Clone)] #[derive(PartialEq, Debug, Copy, Clone)]
pub struct Options { pub struct Options {
pub across: bool, pub across: bool,
pub console_width: usize, pub console_width: usize,
pub icons: bool,
} }
impl Options { impl Options {
@ -38,11 +41,16 @@ impl<'a> Render<'a> {
grid.reserve(self.files.len()); grid.reserve(self.files.len());
for file in &self.files { 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 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 { 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, width: *width,
}); });
} }

View File

@ -2,7 +2,7 @@
use std::io::{Write, Result as IOResult}; use std::io::{Write, Result as IOResult};
use ansi_term::ANSIStrings; use ansi_term::{ANSIGenericString, ANSIStrings};
use term_grid as grid; use term_grid as grid;
use fs::{Dir, File}; use fs::{Dir, File};
@ -17,7 +17,7 @@ use output::grid::Options as GridOptions;
use output::file_name::FileStyle; use output::file_name::FileStyle;
use output::table::{Table, Row as TableRow, Options as TableOptions}; use output::table::{Table, Row as TableRow, Options as TableOptions};
use output::tree::{TreeParams, TreeDepth}; use output::tree::{TreeParams, TreeDepth};
use output::icons::painted_icon;
#[derive(Debug)] #[derive(Debug)]
pub struct Options { pub struct Options {
@ -135,7 +135,17 @@ impl<'a> Render<'a> {
.collect::<Vec<TableRow>>(); .collect::<Vec<TableRow>>();
let file_names = self.files.iter() 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>>(); .collect::<Vec<TextCell>>();
let mut last_working_table = self.make_grid(1, options, git, &file_names, rows.clone(), &drender); let mut last_working_table = self.make_grid(1, options, git, &file_names, rows.clone(), &drender);

120
src/output/icons.rs Normal file
View File

@ -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}'
}
}
}

View File

@ -1,24 +1,40 @@
use std::io::{Write, Result as IOResult}; use std::io::{Write, Result as IOResult};
use ansi_term::ANSIStrings; use ansi_term::{ANSIStrings, ANSIGenericString};
use fs::File; use fs::File;
use output::file_name::{FileName, FileStyle}; use output::file_name::{FileName, FileStyle};
use style::Colours; 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. /// The lines view literally just displays each file, line-by-line.
pub struct Render<'a> { pub struct Render<'a> {
pub files: Vec<File<'a>>, pub files: Vec<File<'a>>,
pub colours: &'a Colours, pub colours: &'a Colours,
pub style: &'a FileStyle, pub style: &'a FileStyle,
pub opts: &'a Options,
} }
impl<'a> Render<'a> { impl<'a> Render<'a> {
pub fn render<W: Write>(&self, w: &mut W) -> IOResult<()> { pub fn render<W: Write>(&self, w: &mut W) -> IOResult<()> {
for file in &self.files { for file in &self.files {
let name_cell = self.render_file(file).paint(); 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(()) Ok(())

View File

@ -8,6 +8,7 @@ pub mod details;
pub mod file_name; pub mod file_name;
pub mod grid_details; pub mod grid_details;
pub mod grid; pub mod grid;
pub mod icons;
pub mod lines; pub mod lines;
pub mod render; pub mod render;
pub mod table; pub mod table;
@ -34,5 +35,5 @@ pub enum Mode {
Grid(grid::Options), Grid(grid::Options),
Details(details::Options), Details(details::Options),
GridDetails(grid_details::Options), GridDetails(grid_details::Options),
Lines, Lines(lines::Options),
} }