Bläddra i källkod

Add grid view by default

This makes it more like ls. The --long (-l) argument has been added to get at the old behaviour, and the --links argument is now on -H. I can't crib this behaviour from ls because it shows it by default.

TODO: The terminal size is currently assumed to be 80, and it uses the string length, rather than the width.
Ben S 11 år sedan
förälder
incheckning
728f9f1804
3 ändrade filer med 63 tillägg och 25 borttagningar
  1. 2 1
      README.md
  2. 29 8
      src/exa.rs
  3. 32 16
      src/options.rs

+ 2 - 1
README.md

@@ -17,8 +17,9 @@ Options
 - **-b**, **--binary**: use binary (power of two) file sizes
 - **-g**, **--group**: show group as well as user
 - **-h**, **--header**: show a header row
+- **-H**, **--links**: show number of hard links column
 - **-i**, **--inode**: show inode number column
-- **-l**, **--links**: show number of hard links column
+- **-l**, **--long**: display extended details and attributes
 - **-r**, **--reverse**: reverse sort order
 - **-s**, **--sort=(name, size, ext)**: field to sort by
 - **-S**, **--blocks**: show number of file system blocks

+ 29 - 8
src/exa.rs

@@ -7,7 +7,8 @@ use std::os;
 
 use file::File;
 use dir::Dir;
-use options::Options;
+use column::{Column, Left};
+use options::{Options, Lines, Grid};
 use unix::Unix;
 
 use ansi_term::{Paint, Plain, strip_formatting};
@@ -48,7 +49,10 @@ fn exa(opts: &Options) {
         match Dir::readdir(Path::new(dir_name.clone())) {
             Ok(dir) => {
                 if print_dir_names { println!("{}:", dir_name); }
-                lines_view(opts, dir);
+                match opts.view {
+                    Lines(ref cols) => lines_view(opts, cols, dir),
+                    Grid => grid_view(opts, dir),
+                }
             }
             Err(e) => {
                 println!("{}: {}", dir_name, e);
@@ -58,7 +62,24 @@ fn exa(opts: &Options) {
     }
 }
 
-fn lines_view(options: &Options, dir: Dir) {
+fn grid_view(options: &Options, dir: Dir) {
+    let unsorted_files = dir.files();
+    let files: Vec<&File> = options.transform_files(&unsorted_files);
+    
+    let max_column_length = files.iter().map(|f| f.name.len()).max().unwrap();
+    let console_width = 80;
+    let num_columns = console_width / max_column_length;
+    
+    for y in range(0, files.len() / num_columns) {
+        for x in range(0, num_columns) {
+            let file_name = files.get(y * num_columns + x).name.clone();
+            print!("{}", Left.pad_string(&file_name, max_column_length - strip_formatting(file_name.clone()).len() + 1));
+        }
+        print!("\n");
+    }
+}
+
+fn lines_view(options: &Options, columns: &Vec<Column>, dir: Dir) {
     let unsorted_files = dir.files();
     let files: Vec<&File> = options.transform_files(&unsorted_files);
 
@@ -71,11 +92,11 @@ fn lines_view(options: &Options, dir: Dir) {
     let mut cache = Unix::empty_cache();
 
     let mut table: Vec<Vec<String>> = files.iter()
-        .map(|f| options.columns.iter().map(|c| f.display(c, &mut cache)).collect())
+        .map(|f| columns.iter().map(|c| f.display(c, &mut cache)).collect())
         .collect();
 
     if options.header {
-        table.unshift(options.columns.iter().map(|c| Plain.underline().paint(c.header())).collect());
+        table.unshift(columns.iter().map(|c| Plain.underline().paint(c.header())).collect());
     }
 
     // Each column needs to have its invisible colour-formatting
@@ -88,17 +109,17 @@ fn lines_view(options: &Options, dir: Dir) {
         .map(|row| row.iter().map(|col| strip_formatting(col.clone()).len()).collect())
         .collect();
 
-    let column_widths: Vec<uint> = range(0, options.columns.len())
+    let column_widths: Vec<uint> = range(0, columns.len())
         .map(|n| lengths.iter().map(|row| *row.get(n)).max().unwrap())
         .collect();
 
     for (field_widths, row) in lengths.iter().zip(table.iter()) {
-        for (num, column) in options.columns.iter().enumerate() {
+        for (num, column) in columns.iter().enumerate() {
             if num != 0 {
                 print!(" ");
             }
 
-            if num == options.columns.len() - 1 {
+            if num == columns.len() - 1 {
                 print!("{}", row.get(num));
             }
             else {

+ 32 - 16
src/options.rs

@@ -9,15 +9,6 @@ pub enum SortField {
     Name, Extension, Size
 }
 
-pub struct Options {
-    pub showInvisibles: bool,
-    pub sortField: SortField,
-    pub reverse: bool,
-    pub dirs: Vec<String>,
-    pub columns: Vec<Column>,
-    pub header: bool,
-}
-
 impl SortField {
     fn from_word(word: String) -> SortField {
         match word.as_slice() {
@@ -29,6 +20,21 @@ impl SortField {
     }
 }
 
+pub enum View {
+    Lines(Vec<Column>),
+    Grid,
+}
+
+pub struct Options {
+    pub show_invisibles: bool,
+    pub sort_field: SortField,
+    pub reverse: bool,
+    pub dirs: Vec<String>,
+    pub view: View,
+    pub header: bool,
+}
+
+
 impl Options {
     pub fn getopts(args: Vec<String>) -> Result<Options, getopts::Fail_> {
         let opts = [
@@ -36,8 +42,9 @@ impl Options {
             getopts::optflag("b", "binary", "use binary prefixes in file sizes"),
             getopts::optflag("g", "group", "show group as well as user"),
             getopts::optflag("h", "header", "show a header row at the top"),
+            getopts::optflag("H", "links", "show number of hard links"),
+            getopts::optflag("l", "long", "display extended details and attributes"),
             getopts::optflag("i", "inode", "show each file's inode number"),
-            getopts::optflag("l", "links", "show number of hard links"),
             getopts::optflag("r", "reverse", "reverse order of files"),
             getopts::optopt("s", "sort", "field to sort by", "WORD"),
             getopts::optflag("S", "blocks", "show number of file system blocks"),
@@ -46,16 +53,25 @@ impl Options {
         match getopts::getopts(args.tail(), opts) {
             Err(f) => Err(f),
             Ok(matches) => Ok(Options {
-                showInvisibles: matches.opt_present("all"),
+                show_invisibles: matches.opt_present("all"),
                 reverse: matches.opt_present("reverse"),
                 header: matches.opt_present("header"),
-                sortField: matches.opt_str("sort").map(|word| SortField::from_word(word)).unwrap_or(Name),
+                sort_field: matches.opt_str("sort").map(|word| SortField::from_word(word)).unwrap_or(Name),
                 dirs: if matches.free.is_empty() { vec![ ".".to_string() ] } else { matches.free.clone() },
-                columns: Options::columns(matches),
+                view: Options::view(matches),
             })
         }
     }
-
+    
+    fn view(matches: getopts::Matches) -> View {
+        if matches.opt_present("long") {
+            Lines(Options::columns(matches))
+        }
+        else {
+            Grid
+        }
+    }
+    
     fn columns(matches: getopts::Matches) -> Vec<Column> {
         let mut columns = vec![];
 
@@ -87,7 +103,7 @@ impl Options {
     }
 
     fn should_display(&self, f: &File) -> bool {
-        if self.showInvisibles {
+        if self.show_invisibles {
             true
         } else {
             !f.name.as_slice().starts_with(".")
@@ -99,7 +115,7 @@ impl Options {
             .filter(|&f| self.should_display(f))
             .collect();
 
-        match self.sortField {
+        match self.sort_field {
             Name => files.sort_by(|a, b| a.parts.cmp(&b.parts)),
             Size => files.sort_by(|a, b| a.stat.size.cmp(&b.stat.size)),
             Extension => files.sort_by(|a, b| {