Explorar el Código

Merge branch 'asoderman-glyphs'

Benjamin Sago hace 6 años
padre
commit
2e0e29da22
Se han modificado 10 ficheros con 224 adiciones y 27 borrados
  1. 2 2
      src/exa.rs
  2. 14 0
      src/info/filetype.rs
  3. 2 1
      src/options/flags.rs
  4. 20 10
      src/options/view.rs
  5. 23 6
      src/output/details.rs
  6. 10 2
      src/output/grid.rs
  7. 13 3
      src/output/grid_details.rs
  8. 120 0
      src/output/icons.rs
  9. 18 2
      src/output/lines.rs
  10. 2 1
      src/output/mod.rs

+ 2 - 2
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)
                 }
 

+ 14 - 0
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,
+        })
+    }
+}

+ 2 - 1
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,

+ 20 - 10
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(_)));

+ 23 - 6
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);

+ 10 - 2
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,
             });
         }

+ 13 - 3
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);

+ 120 - 0
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}'
+        }
+    }
+}

+ 18 - 2
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(())

+ 2 - 1
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),
 }