mirror of
https://github.com/Llewellynvdm/exa.git
synced 2024-12-28 10:40:48 +00:00
Merge branch 'asoderman-glyphs'
This commit is contained in:
commit
2e0e29da22
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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(_)));
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
|
120
src/output/icons.rs
Normal file
120
src/output/icons.rs
Normal 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}'
|
||||
}
|
||||
}
|
||||
}
|
@ -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(())
|
||||
|
@ -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),
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user