Explorar el Código

Merge branch 'long-grid-view'

Ben S hace 10 años
padre
commit
090cebe669
Se han modificado 8 ficheros con 274 adiciones y 122 borrados
  1. 7 7
      Cargo.lock
  2. 20 22
      src/column.rs
  3. 4 3
      src/main.rs
  4. 73 65
      src/options.rs
  5. 47 21
      src/output/details.rs
  6. 118 0
      src/output/grid_details.rs
  7. 1 1
      src/output/lines.rs
  8. 4 3
      src/output/mod.rs

+ 7 - 7
Cargo.lock

@@ -13,7 +13,7 @@ dependencies = [
  "num_cpus 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "number_prefix 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "pad 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "term_grid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "term_grid 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "threadpool 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-width 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "users 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -71,7 +71,7 @@ dependencies = [
 [[package]]
 name = "git2"
 version = "0.2.12"
-source = "git+https://github.com/alexcrichton/git2-rs.git#e5a439b13f45ca6b95fbf5f47ccf4b030d37ed1c"
+source = "git+https://github.com/alexcrichton/git2-rs.git#3a7a990607a766fa65a40b920d70c8289691d2f8"
 dependencies = [
  "bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -87,12 +87,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 [[package]]
 name = "libgit2-sys"
 version = "0.2.17"
-source = "git+https://github.com/alexcrichton/git2-rs.git#e5a439b13f45ca6b95fbf5f47ccf4b030d37ed1c"
+source = "git+https://github.com/alexcrichton/git2-rs.git#3a7a990607a766fa65a40b920d70c8289691d2f8"
 dependencies = [
  "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "libssh2-sys 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
  "libz-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "openssl-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "openssl-sys 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "pkg-config 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -111,7 +111,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "libz-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "openssl-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "openssl-sys 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "pkg-config 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -186,7 +186,7 @@ dependencies = [
 
 [[package]]
 name = "openssl-sys"
-version = "0.6.2"
+version = "0.6.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "gcc 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -262,7 +262,7 @@ dependencies = [
 
 [[package]]
 name = "term_grid"
-version = "0.1.0"
+version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "unicode-width 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",

+ 20 - 22
src/column.rs

@@ -1,5 +1,3 @@
-use std::iter::repeat;
-
 use ansi_term::Style;
 use unicode_width::UnicodeWidthStr;
 
@@ -58,37 +56,37 @@ impl Column {
     }
 }
 
-/// Pad a string with the given number of spaces.
-fn spaces(length: usize) -> String {
-    repeat(" ").take(length).collect()
-}
-
-impl Alignment {
-    /// Pad a string with the given alignment and number of spaces.
-    ///
-    /// This doesn't take the width the string *should* be, rather the number
-    /// of spaces to add: this is because the strings are usually full of
-    /// invisible control characters, so getting the displayed width of the
-    /// string is not as simple as just getting its length.
-    pub fn pad_string(&self, string: &str, padding: usize) -> String {
-        match *self {
-            Alignment::Left  => format!("{}{}", string, spaces(padding)),
-            Alignment::Right => format!("{}{}", spaces(padding), string),
-        }
-    }
-}
 
-#[derive(PartialEq, Debug)]
+#[derive(PartialEq, Debug, Clone)]
 pub struct Cell {
     pub length: usize,
     pub text: String,
 }
 
 impl Cell {
+    pub fn empty() -> Cell {
+        Cell {
+            text: String::new(),
+            length: 0,
+        }
+    }
+
     pub fn paint(style: Style, string: &str) -> Cell {
         Cell {
             text: style.paint(string).to_string(),
             length: UnicodeWidthStr::width(string),
         }
     }
+
+    pub fn add_spaces(&mut self, count: usize) {
+        self.length += count;
+        for _ in 0 .. count {
+            self.text.push(' ');
+        }
+    }
+
+    pub fn append(&mut self, other: &Cell) {
+        self.length += other.length;
+        self.text.push_str(&*other.text);
+    }
 }

+ 4 - 3
src/main.rs

@@ -179,9 +179,10 @@ impl<'dir> Exa<'dir> {
 
     fn print(&self, dir: Option<&Dir>, files: &[File]) {
         match self.options.view {
-            View::Grid(g)     => g.view(files),
-            View::Details(d)  => d.view(dir, files),
-            View::Lines(l)    => l.view(files),
+            View::Grid(g)         => g.view(files),
+            View::Details(d)      => d.view(dir, files),
+            View::GridDetails(gd) => gd.view(dir, files),
+            View::Lines(l)        => l.view(files),
         }
     }
 }

+ 73 - 65
src/options.rs

@@ -13,7 +13,7 @@ use column::Column::*;
 use dir::Dir;
 use feature::Attribute;
 use file::File;
-use output::{Grid, Details, Lines};
+use output::{Grid, Details, GridDetails, Lines};
 use term::dimensions;
 
 
@@ -37,6 +37,7 @@ impl Options {
         opts.optflag("B", "bytes",     "list file sizes in bytes, without prefixes");
         opts.optflag("d", "list-dirs", "list directories as regular files");
         opts.optflag("g", "group",     "show group as well as user");
+        opts.optflag("G", "grid",      "display entries in a grid view (default)");
         opts.optflag("",  "group-directories-first", "list directories before other files");
         opts.optflag("h", "header",    "show a header row at the top");
         opts.optflag("H", "links",     "show number of hard links");
@@ -248,16 +249,17 @@ impl fmt::Display for Misfire {
 #[derive(PartialEq, Debug, Copy, Clone)]
 pub enum View {
     Details(Details),
-    Lines(Lines),
     Grid(Grid),
+    GridDetails(GridDetails),
+    Lines(Lines),
 }
 
 impl View {
     pub fn deduce(matches: &getopts::Matches, filter: FileFilter, dir_action: DirAction) -> Result<View, Misfire> {
         use self::Misfire::*;
 
-        if matches.opt_present("long") {
-            if matches.opt_present("across") {
+        let long = || {
+            if matches.opt_present("across") && !matches.opt_present("grid") {
                 Err(Useless("across", true, "long"))
             }
             else if matches.opt_present("oneline") {
@@ -272,78 +274,85 @@ impl View {
                         colours: if dimensions().is_some() { Colours::colourful() } else { Colours::plain() },
                 };
 
-                Ok(View::Details(details))
+                Ok(details)
             }
-        }
-        else if matches.opt_present("binary") {
-            Err(Useless("binary", false, "long"))
-        }
-        else if matches.opt_present("bytes") {
-            Err(Useless("bytes", false, "long"))
-        }
-        else if matches.opt_present("inode") {
-            Err(Useless("inode", false, "long"))
-        }
-        else if matches.opt_present("links") {
-            Err(Useless("links", false, "long"))
-        }
-        else if matches.opt_present("header") {
-            Err(Useless("header", false, "long"))
-        }
-        else if matches.opt_present("blocks") {
-            Err(Useless("blocks", false, "long"))
-        }
-        else if cfg!(feature="git") && matches.opt_present("git") {
-            Err(Useless("git", false, "long"))
-        }
-        else if matches.opt_present("time") {
-            Err(Useless("time", false, "long"))
-        }
-        else if matches.opt_present("tree") {
-            Err(Useless("tree", false, "long"))
-        }
-        else if matches.opt_present("group") {
-            Err(Useless("group", false, "long"))
-        }
-        else if matches.opt_present("level") && !matches.opt_present("recurse") {
-            Err(Useless2("level", "recurse", "tree"))
-        }
-        else if Attribute::feature_implemented() && matches.opt_present("extended") {
-            Err(Useless("extended", false, "long"))
-        }
-        else if let Some((width, _)) = dimensions() {
-            if matches.opt_present("oneline") {
-                if matches.opt_present("across") {
-                    Err(Useless("across", true, "oneline"))
+        };
+
+        let long_options_scan = || {
+            for option in &[ "binary", "bytes", "inode", "links", "header", "blocks", "time", "tree", "group" ] {
+                if matches.opt_present(option) {
+                    return Err(Useless(option, false, "long"));
+                }
+            }
+
+            if cfg!(feature="git") && matches.opt_present("git") {
+                Err(Useless("git", false, "long"))
+            }
+            else if matches.opt_present("level") && !matches.opt_present("recurse") {
+                Err(Useless2("level", "recurse", "tree"))
+            }
+            else if Attribute::feature_implemented() && matches.opt_present("extended") {
+                Err(Useless("extended", false, "long"))
+            }
+            else {
+                Ok(())
+            }
+        };
+
+        let other_options_scan = || {
+            if let Some((width, _)) = dimensions() {
+                if matches.opt_present("oneline") {
+                    if matches.opt_present("across") {
+                        Err(Useless("across", true, "oneline"))
+                    }
+                    else {
+                        let lines = Lines {
+                             colours: Colours::colourful(),
+                        };
+
+                        Ok(View::Lines(lines))
+                    }
                 }
                 else {
-                    let lines = Lines {
-                         colours: Colours::colourful(),
+                    let grid = Grid {
+                        across: matches.opt_present("across"),
+                        console_width: width,
+                        colours: Colours::colourful(),
                     };
 
-                    Ok(View::Lines(lines))
+                    Ok(View::Grid(grid))
                 }
             }
             else {
-                let grid = Grid {
-                    across: matches.opt_present("across"),
-                    console_width: width,
-                    colours: Colours::colourful(),
+                // 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.
+                let lines = Lines {
+                     colours: Colours::plain(),
                 };
 
-                Ok(View::Grid(grid))
+                Ok(View::Lines(lines))
+            }
+        };
+
+        if matches.opt_present("long") {
+            let long_options = try!(long());
+
+            if matches.opt_present("grid") {
+                match other_options_scan() {
+                    Ok(View::Grid(grid)) => return Ok(View::GridDetails(GridDetails { grid: grid, details: long_options })),
+                    Ok(lines)            => return Ok(lines),
+                    Err(e)               => return Err(e),
+                };
+            }
+            else {
+                return Ok(View::Details(long_options));
             }
         }
-        else {
-            // 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.
-            let lines = Lines {
-                 colours: Colours::plain(),
-            };
-
-            Ok(View::Lines(lines))
-        }
+
+        try!(long_options_scan());
+
+        other_options_scan()
     }
 }
 
@@ -718,5 +727,4 @@ mod test {
         let opts = Options::getopts(&[ "--level".to_string(), "69105".to_string() ]);
         assert_eq!(opts.unwrap_err(), Misfire::Useless2("level", "recurse", "tree"))
     }
-
 }

+ 47 - 21
src/output/details.rs

@@ -1,3 +1,6 @@
+use std::iter::repeat;
+use std::string::ToString;
+
 use colours::Colours;
 use column::{Alignment, Column, Cell};
 use dir::Dir;
@@ -66,14 +69,16 @@ impl Details {
 
         // Then add files to the table and print it out.
         self.add_files_to_table(&mut table, files, 0);
-        table.print_table(self.xattr, self.recurse.is_some());
+        for cell in table.print_table(self.xattr, self.recurse.is_some()) {
+            println!("{}", cell.text);
+        }
     }
 
     /// Adds files to the table - recursively, if the `recurse` option
     /// is present.
     fn add_files_to_table<U: Users>(&self, table: &mut Table<U>, src: &[File], depth: usize) {
         for (index, file) in src.iter().enumerate() {
-            table.add_file(file, depth, index == src.len() - 1);
+            table.add_file(file, depth, index == src.len() - 1, true);
 
             // There are two types of recursion that exa supports: a tree
             // view, which is dealt with here, and multiple listings, which is
@@ -105,7 +110,7 @@ struct Row {
 
     /// This file's name, in coloured output. The name is treated separately
     /// from the other cells, as it never requires padding.
-    name:     String,
+    name:     Cell,
 
     /// How many directories deep into the tree structure this is. Directories
     /// on top have depth 0.
@@ -157,7 +162,7 @@ impl Table<OSUsers> {
 
     /// Create a new, empty Table object, setting the caching fields to their
     /// empty states.
-    fn with_options(colours: Colours, columns: Vec<Column>) -> Table<OSUsers> {
+    pub fn with_options(colours: Colours, columns: Vec<Column>) -> Table<OSUsers> {
         Table {
             columns: columns,
             rows:    Vec::new(),
@@ -177,11 +182,11 @@ impl<U> Table<U> where U: Users {
     /// Add a dummy "header" row to the table, which contains the names of all
     /// the columns, underlined. This has dummy data for the cases that aren't
     /// actually used, such as the depth or list of attributes.
-    fn add_header(&mut self) {
+    pub fn add_header(&mut self) {
         let row = Row {
             depth:    0,
             cells:    self.columns.iter().map(|c| Cell::paint(self.colours.header, c.header())).collect(),
-            name:     self.colours.header.paint("Name").to_string(),
+            name:     Cell::paint(self.colours.header, "Name"),
             last:     false,
             attrs:    Vec::new(),
             children: false,
@@ -191,11 +196,16 @@ impl<U> Table<U> where U: Users {
     }
 
     /// Get the cells for the given file, and add the result to the table.
-    fn add_file(&mut self, file: &File, depth: usize, last: bool) {
+    pub fn add_file(&mut self, file: &File, depth: usize, last: bool, links: bool) {
+        let cells = self.cells_for_file(file);
+        self.add_file_with_cells(cells, file, depth, last, links)
+    }
+
+    pub fn add_file_with_cells(&mut self, cells: Vec<Cell>, file: &File, depth: usize, last: bool, links: bool) {
         let row = Row {
             depth:    depth,
-            cells:    self.cells_for_file(file),
-            name:     filename(file, &self.colours),
+            cells:    cells,
+            name:     Cell { text: filename(file, &self.colours, links), length: file.file_name_width() },
             last:     last,
             attrs:    file.xattrs.clone(),
             children: file.this.is_some(),
@@ -206,7 +216,7 @@ impl<U> Table<U> where U: Users {
 
     /// Use the list of columns to find which cells should be produced for
     /// this file, per-column.
-    fn cells_for_file(&mut self, file: &File) -> Vec<Cell> {
+    pub fn cells_for_file(&mut self, file: &File) -> Vec<Cell> {
         self.columns.clone().iter()
                     .map(|c| self.display(file, c))
                     .collect()
@@ -373,8 +383,9 @@ impl<U> Table<U> where U: Users {
     }
 
     /// Print the table to standard output, consuming it in the process.
-    fn print_table(self, xattr: bool, show_children: bool) {
+    pub fn print_table(&self, xattr: bool, show_children: bool) -> Vec<Cell> {
         let mut stack = Vec::new();
+        let mut cells = Vec::new();
 
         // Work out the list of column widths by finding the longest cell for
         // each column, then formatting each cell in that column to be the
@@ -383,12 +394,21 @@ impl<U> Table<U> where U: Users {
             .map(|n| self.rows.iter().map(|row| row.cells[n].length).max().unwrap_or(0))
             .collect();
 
-        for row in self.rows.into_iter() {
+        for row in self.rows.iter() {
+            let mut cell = Cell::empty();
+
             for (n, width) in column_widths.iter().enumerate() {
-                let padding = width - row.cells[n].length;
-                print!("{} ", self.columns[n].alignment().pad_string(&row.cells[n].text, padding));
+                match self.columns[n].alignment() {
+                    Alignment::Left  => { cell.append(&row.cells[n]); cell.add_spaces(width - row.cells[n].length); }
+                    Alignment::Right => { cell.add_spaces(width - row.cells[n].length); cell.append(&row.cells[n]); }
+                }
+
+                cell.add_spaces(1);
             }
 
+            let mut filename = String::new();
+            let mut filename_length = 0;
+
             // A stack tracks which tree characters should be printed. It's
             // necessary to maintain information about the previously-printed
             // lines, as the output will change based on whether the
@@ -398,7 +418,8 @@ impl<U> Table<U> where U: Users {
                 stack[row.depth] = if row.last { TreePart::Corner } else { TreePart::Edge };
 
                 for i in 1 .. row.depth + 1 {
-                    print!("{}", self.colours.punctuation.paint(stack[i].ascii_art()));
+                    filename.push_str(&*self.colours.punctuation.paint(stack[i].ascii_art()).to_string());
+                    filename_length += 4;
                 }
 
                 if row.children {
@@ -408,24 +429,29 @@ impl<U> Table<U> where U: Users {
                 // If any tree characters have been printed, then add an extra
                 // space, which makes the output look much better.
                 if row.depth != 0 {
-                    print!(" ");
+                    filename.push(' ');
+                    filename_length += 1;
                 }
             }
 
             // Print the name without worrying about padding.
-            print!("{}\n", row.name);
+            filename.push_str(&*row.name.text);
+            filename_length += row.name.length;
 
             if xattr {
                 let width = row.attrs.iter().map(|a| a.name().len()).max().unwrap_or(0);
                 for attr in row.attrs.iter() {
                     let name = attr.name();
-                    println!("{}\t{}",
-                        Alignment::Left.pad_string(name, width - name.len()),
-                        attr.size()
-                    )
+                    let spaces: String = repeat(" ").take(width - name.len()).collect();
+                    filename.push_str(&*format!("\n{}{}  {}", name, spaces, attr.size()))
                 }
             }
+
+            cell.append(&Cell { text: filename, length: filename_length });
+            cells.push(cell);
         }
+
+        cells
     }
 }
 

+ 118 - 0
src/output/grid_details.rs

@@ -0,0 +1,118 @@
+use std::iter::repeat;
+
+use users::OSUsers;
+use term_grid as grid;
+
+use column::{Column, Cell};
+use dir::Dir;
+use file::File;
+use output::details::{Details, Table};
+use output::grid::Grid;
+
+#[derive(PartialEq, Debug, Copy, Clone)]
+pub struct GridDetails {
+    pub grid: Grid,
+    pub details: Details,
+}
+
+impl GridDetails {
+    pub fn view(&self, dir: Option<&Dir>, files: &[File]) {
+        let columns_for_dir = self.details.columns.for_dir(dir);
+        let mut first_table = Table::with_options(self.details.colours, columns_for_dir.clone());
+        let cells: Vec<_> = files.iter().map(|file| first_table.cells_for_file(file)).collect();
+
+        let mut last_working_table = self.make_grid(1, &*columns_for_dir, files, cells.clone());
+
+        for column_count in 2.. {
+            let grid = self.make_grid(column_count, &*columns_for_dir, files, cells.clone());
+
+            let the_grid_fits = {
+                let d = grid.fit_into_columns(column_count);
+                d.is_complete() && d.width() <= self.grid.console_width
+            };
+
+            if the_grid_fits {
+                last_working_table = grid;
+            }
+            else {
+                print!("{}", last_working_table.fit_into_columns(column_count - 1));
+                return;
+            }
+        }
+    }
+
+    fn make_table(&self, columns_for_dir: &[Column]) -> Table<OSUsers> {
+        let mut table = Table::with_options(self.details.colours, columns_for_dir.into());
+        if self.details.header { table.add_header() }
+        table
+    }
+
+    fn make_grid(&self, column_count: usize, columns_for_dir: &[Column], files: &[File], cells: Vec<Vec<Cell>>) -> grid::Grid {
+        let mut tables: Vec<_> = repeat(()).map(|_| self.make_table(columns_for_dir)).take(column_count).collect();
+
+        let mut num_cells = cells.len();
+        if self.details.header {
+            num_cells += column_count;
+        }
+
+        let original_height = divide_rounding_up(cells.len(), column_count);
+        let height = divide_rounding_up(num_cells, column_count);
+
+        for (i, (file, row)) in files.iter().zip(cells.into_iter()).enumerate() {
+            let index = if self.grid.across {
+                    i % column_count
+                }
+                else {
+                    i / original_height
+                };
+
+            tables[index].add_file_with_cells(row, file, 0, false, false);
+        }
+
+        let columns: Vec<_> = tables.iter().map(|t| t.print_table(false, false)).collect();
+
+        let direction = if self.grid.across { grid::Direction::LeftToRight }
+                                       else { grid::Direction::TopToBottom };
+
+        let mut grid = grid::Grid::new(grid::GridOptions {
+            direction:        direction,
+            separator_width:  4,
+        });
+
+        if self.grid.across {
+            for row in 0 .. height {
+                for column in columns.iter() {
+                    if row < column.len() {
+                        let cell = grid::Cell {
+                            contents: column[row].text.clone(),
+                            width:    column[row].length,
+                        };
+
+                        grid.add(cell);
+                    }
+                }
+            }
+        }
+        else {
+            for column in columns.iter() {
+                for cell in column.iter() {
+                    let cell = grid::Cell {
+                        contents: cell.text.clone(),
+                        width:    cell.length,
+                    };
+
+                    grid.add(cell);
+                }
+            }
+        }
+
+        grid
+    }
+}
+
+
+fn divide_rounding_up(a: usize, b: usize) -> usize {
+    let mut result = a / b;
+    if a % b != 0 { result += 1; }
+    result
+}

+ 1 - 1
src/output/lines.rs

@@ -13,7 +13,7 @@ pub struct Lines {
 impl Lines {
     pub fn view(&self, files: &[File]) {
         for file in files {
-            println!("{}", filename(file, &self.colours));
+            println!("{}", filename(file, &self.colours, true));
         }
     }
 }

+ 4 - 3
src/output/mod.rs

@@ -7,14 +7,15 @@ use filetype::file_colour;
 pub use self::details::Details;
 pub use self::grid::Grid;
 pub use self::lines::Lines;
+pub use self::grid_details::GridDetails;
 
 mod grid;
 pub mod details;
 mod lines;
+mod grid_details;
 
-
-pub fn filename(file: &File, colours: &Colours) -> String {
-    if file.is_link() {
+pub fn filename(file: &File, colours: &Colours, links: bool) -> String {
+    if links && file.is_link() {
         symlink_filename(file, colours)
     }
     else {