From 728f9f18042502ac7b85bf7016f3e65d5cad31b6 Mon Sep 17 00:00:00 2001 From: Ben S Date: Sun, 6 Jul 2014 17:33:40 +0100 Subject: [PATCH 01/10] 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. --- README.md | 3 ++- src/exa.rs | 37 +++++++++++++++++++++++++++++-------- src/options.rs | 48 ++++++++++++++++++++++++++++++++---------------- 3 files changed, 63 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 7f6ec2a..54c6aeb 100644 --- a/README.md +++ b/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 diff --git a/src/exa.rs b/src/exa.rs index 37a6ebd..2d38a95 100644 --- a/src/exa.rs +++ b/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, 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> = 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 = range(0, options.columns.len()) + let column_widths: Vec = 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 { diff --git a/src/options.rs b/src/options.rs index 3afa8b9..ab35448 100644 --- a/src/options.rs +++ b/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, - pub columns: Vec, - 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), + Grid, +} + +pub struct Options { + pub show_invisibles: bool, + pub sort_field: SortField, + pub reverse: bool, + pub dirs: Vec, + pub view: View, + pub header: bool, +} + + impl Options { pub fn getopts(args: Vec) -> Result { 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 { 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| { From 92ccf821ffaeef8fdf1b9de41d45031540785662 Mon Sep 17 00:00:00 2001 From: Ben S Date: Sun, 6 Jul 2014 18:00:27 +0100 Subject: [PATCH 02/10] Colour file names in grid view --- src/exa.rs | 6 ++++-- src/file.rs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/exa.rs b/src/exa.rs index 2d38a95..6cc12e6 100644 --- a/src/exa.rs +++ b/src/exa.rs @@ -72,8 +72,10 @@ fn grid_view(options: &Options, dir: Dir) { 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)); + let file = files.get(y * num_columns + x); + let file_name = file.name.clone(); + let styled_name = file.file_colour().paint(file_name.as_slice()); + print!("{}", Left.pad_string(&styled_name, max_column_length - file_name.len() + 1)); } print!("\n"); } diff --git a/src/file.rs b/src/file.rs index e7d12c2..8ea24b2 100644 --- a/src/file.rs +++ b/src/file.rs @@ -210,7 +210,7 @@ impl<'a> File<'a> { } } - fn file_colour(&self) -> Style { + pub fn file_colour(&self) -> Style { self.get_type().style() } From 03c51f0bfdb86b0b364ebc48e90ffbe980191dc8 Mon Sep 17 00:00:00 2001 From: Ben S Date: Mon, 7 Jul 2014 18:42:09 +0100 Subject: [PATCH 03/10] Correctly calculate number of rows --- src/exa.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/exa.rs b/src/exa.rs index 6cc12e6..0a72a15 100644 --- a/src/exa.rs +++ b/src/exa.rs @@ -68,10 +68,19 @@ fn grid_view(options: &Options, dir: Dir) { 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; + let num_columns = (console_width + 1) / (max_column_length + 1); + + let mut num_rows = files.len() / num_columns; + if files.len() % num_columns != 0 { + num_rows += 1; + } - for y in range(0, files.len() / num_columns) { + for y in range(0, num_rows) { for x in range(0, num_columns) { + if y * num_columns + x >= files.len() { + continue; + } + let file = files.get(y * num_columns + x); let file_name = file.name.clone(); let styled_name = file.file_colour().paint(file_name.as_slice()); From 240cbf7b417775f8e54dc56d697808b6e2036aaf Mon Sep 17 00:00:00 2001 From: Ben S Date: Mon, 7 Jul 2014 19:18:09 +0100 Subject: [PATCH 04/10] Add --across parameter --- README.md | 2 +- src/exa.rs | 20 ++++++++++++++------ src/options.rs | 5 +++-- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 54c6aeb..94e65db 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Options - **-r**, **--reverse**: reverse sort order - **-s**, **--sort=(name, size, ext)**: field to sort by - **-S**, **--blocks**: show number of file system blocks - +- **-x**, **--across**: sort multi-column view entries across Installation ------------ diff --git a/src/exa.rs b/src/exa.rs index 0a72a15..96d5cc6 100644 --- a/src/exa.rs +++ b/src/exa.rs @@ -51,7 +51,7 @@ fn exa(opts: &Options) { if print_dir_names { println!("{}:", dir_name); } match opts.view { Lines(ref cols) => lines_view(opts, cols, dir), - Grid => grid_view(opts, dir), + Grid(bool) => grid_view(opts, bool, dir), } } Err(e) => { @@ -62,26 +62,34 @@ fn exa(opts: &Options) { } } -fn grid_view(options: &Options, dir: Dir) { +fn grid_view(options: &Options, across: bool, 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 + 1) / (max_column_length + 1); + let count = files.len(); - let mut num_rows = files.len() / num_columns; - if files.len() % num_columns != 0 { + let mut num_rows = count / num_columns; + if count % num_columns != 0 { num_rows += 1; } for y in range(0, num_rows) { for x in range(0, num_columns) { - if y * num_columns + x >= files.len() { + let num = if across { + y * num_columns + x + } + else { + y + num_rows * x + }; + + if num >= count { continue; } - let file = files.get(y * num_columns + x); + let file = files.get(num); let file_name = file.name.clone(); let styled_name = file.file_colour().paint(file_name.as_slice()); print!("{}", Left.pad_string(&styled_name, max_column_length - file_name.len() + 1)); diff --git a/src/options.rs b/src/options.rs index ab35448..35f0ca7 100644 --- a/src/options.rs +++ b/src/options.rs @@ -22,7 +22,7 @@ impl SortField { pub enum View { Lines(Vec), - Grid, + Grid(bool), } pub struct Options { @@ -48,6 +48,7 @@ impl Options { 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"), + getopts::optflag("x", "across", "sort multi-column view entries across"), ]; match getopts::getopts(args.tail(), opts) { @@ -68,7 +69,7 @@ impl Options { Lines(Options::columns(matches)) } else { - Grid + Grid(matches.opt_present("across")) } } From fc90f4bfc93b5f9ce948ee9fa1d06dc1e9884e88 Mon Sep 17 00:00:00 2001 From: Ben S Date: Mon, 7 Jul 2014 20:11:30 +0100 Subject: [PATCH 05/10] Don't pad the final column --- src/exa.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/exa.rs b/src/exa.rs index 96d5cc6..ff7e3ea 100644 --- a/src/exa.rs +++ b/src/exa.rs @@ -92,7 +92,12 @@ fn grid_view(options: &Options, across: bool, dir: Dir) { let file = files.get(num); let file_name = file.name.clone(); let styled_name = file.file_colour().paint(file_name.as_slice()); - print!("{}", Left.pad_string(&styled_name, max_column_length - file_name.len() + 1)); + if x == num_columns - 1 { + print!("{}", styled_name); + } + else { + print!("{}", Left.pad_string(&styled_name, max_column_length - file_name.len() + 1)); + } } print!("\n"); } From 90099f28cfa31af316f78ee3a5c1ab536d680fd3 Mon Sep 17 00:00:00 2001 From: Ben S Date: Thu, 10 Jul 2014 22:20:52 +0100 Subject: [PATCH 06/10] Upgrade to latest Rust nightly - change to_string() on numbers to to_str() --- README.md | 2 +- src/file.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 94e65db..799e2f4 100644 --- a/README.md +++ b/README.md @@ -28,4 +28,4 @@ Options Installation ------------ -exa is written in [Rust](http://www.rust-lang.org). It compiles with Rust 0.11, the latest version - 0.10 will not do, as there have been too many breaking changes since. You will also need [Cargo](http://crates.io), the Rust package manager. Once you have them both set up, a simple `cargo build` will pull in all the dependencies and compile exa. +exa is written in [Rust](http://www.rust-lang.org). You'll have to use the nightly -- I try to keep it up to date with the latest version when possible. You will also need [Cargo](http://crates.io), the Rust package manager. Once you have them both set up, a simple `cargo build` will pull in all the dependencies and compile exa. diff --git a/src/file.rs b/src/file.rs index 8ea24b2..4a218ae 100644 --- a/src/file.rs +++ b/src/file.rs @@ -109,13 +109,13 @@ impl<'a> File<'a> { // the time. HardLinks => { let style = if self.stat.kind == io::TypeFile && self.stat.unstable.nlink > 1 { Red.on(Yellow) } else { Red.normal() }; - style.paint(self.stat.unstable.nlink.to_str().as_slice()) + style.paint(self.stat.unstable.nlink.to_string().as_slice()) }, - Inode => Purple.paint(self.stat.unstable.inode.to_str().as_slice()), + Inode => Purple.paint(self.stat.unstable.inode.to_string().as_slice()), Blocks => { if self.stat.kind == io::TypeFile || self.stat.kind == io::TypeSymlink { - Cyan.paint(self.stat.unstable.blocks.to_str().as_slice()) + Cyan.paint(self.stat.unstable.blocks.to_string().as_slice()) } else { Grey.paint("-") @@ -128,13 +128,13 @@ impl<'a> File<'a> { let uid = self.stat.unstable.uid as u32; unix.load_user(uid); let style = if unix.uid == uid { Yellow.bold() } else { Plain }; - let string = unix.get_user_name(uid).unwrap_or(uid.to_str()); + let string = unix.get_user_name(uid).unwrap_or(uid.to_string()); style.paint(string.as_slice()) }, Group => { let gid = self.stat.unstable.gid as u32; unix.load_group(gid); - let name = unix.get_group_name(gid).unwrap_or(gid.to_str()); + let name = unix.get_group_name(gid).unwrap_or(gid.to_string()); let style = if unix.is_group_member(gid) { Yellow.normal() } else { Plain }; style.paint(name.as_slice()) }, From 082af307b42e1323fc981d1317e667f8e2345fe5 Mon Sep 17 00:00:00 2001 From: Michael Gehring Date: Wed, 16 Jul 2014 06:20:43 +0200 Subject: [PATCH 07/10] ToStr::to_str -> ToString::to_string --- src/file.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/file.rs b/src/file.rs index e7d12c2..75c179b 100644 --- a/src/file.rs +++ b/src/file.rs @@ -109,13 +109,13 @@ impl<'a> File<'a> { // the time. HardLinks => { let style = if self.stat.kind == io::TypeFile && self.stat.unstable.nlink > 1 { Red.on(Yellow) } else { Red.normal() }; - style.paint(self.stat.unstable.nlink.to_str().as_slice()) + style.paint(self.stat.unstable.nlink.to_string().as_slice()) }, - Inode => Purple.paint(self.stat.unstable.inode.to_str().as_slice()), + Inode => Purple.paint(self.stat.unstable.inode.to_string().as_slice()), Blocks => { if self.stat.kind == io::TypeFile || self.stat.kind == io::TypeSymlink { - Cyan.paint(self.stat.unstable.blocks.to_str().as_slice()) + Cyan.paint(self.stat.unstable.blocks.to_string().as_slice()) } else { Grey.paint("-") @@ -128,13 +128,13 @@ impl<'a> File<'a> { let uid = self.stat.unstable.uid as u32; unix.load_user(uid); let style = if unix.uid == uid { Yellow.bold() } else { Plain }; - let string = unix.get_user_name(uid).unwrap_or(uid.to_str()); + let string = unix.get_user_name(uid).unwrap_or(uid.to_string()); style.paint(string.as_slice()) }, Group => { let gid = self.stat.unstable.gid as u32; unix.load_group(gid); - let name = unix.get_group_name(gid).unwrap_or(gid.to_str()); + let name = unix.get_group_name(gid).unwrap_or(gid.to_string()); let style = if unix.is_group_member(gid) { Yellow.normal() } else { Plain }; style.paint(name.as_slice()) }, From 4cbc1f063a7494fe78c63b8656f25ea07f9365d9 Mon Sep 17 00:00:00 2001 From: Ben S Date: Mon, 21 Jul 2014 22:05:04 +0100 Subject: [PATCH 08/10] Upgrade to latest Rust nightly - Lifetime changes in unix.rs - lexical_ordering -> cmp - from_utf8_lossy got moved into String - vec.get(n) -> vec[n] --- src/exa.rs | 6 +++--- src/file.rs | 5 ++--- src/options.rs | 3 +-- src/unix.rs | 3 ++- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/exa.rs b/src/exa.rs index ff7e3ea..c36d7d4 100644 --- a/src/exa.rs +++ b/src/exa.rs @@ -89,7 +89,7 @@ fn grid_view(options: &Options, across: bool, dir: Dir) { continue; } - let file = files.get(num); + let file = files[num]; let file_name = file.name.clone(); let styled_name = file.file_colour().paint(file_name.as_slice()); if x == num_columns - 1 { @@ -134,7 +134,7 @@ fn lines_view(options: &Options, columns: &Vec, dir: Dir) { .collect(); let column_widths: Vec = range(0, columns.len()) - .map(|n| lengths.iter().map(|row| *row.get(n)).max().unwrap()) + .map(|n| lengths.iter().map(|row| row[n]).max().unwrap()) .collect(); for (field_widths, row) in lengths.iter().zip(table.iter()) { @@ -147,7 +147,7 @@ fn lines_view(options: &Options, columns: &Vec, dir: Dir) { print!("{}", row.get(num)); } else { - let padding = *column_widths.get(num) - *field_widths.get(num); + let padding = column_widths[num] - field_widths[num]; print!("{}", column.alignment().pad_string(row.get(num), padding)); } } diff --git a/src/file.rs b/src/file.rs index 4a218ae..69c0b90 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,6 +1,5 @@ use std::io::{fs, IoResult}; use std::io; -use std::str::from_utf8_lossy; use ansi_term::{Paint, Colour, Plain, Style, Red, Green, Yellow, Blue, Purple, Cyan, Fixed}; @@ -32,7 +31,7 @@ pub struct File<'a> { impl<'a> File<'a> { pub fn from_path(path: &'a Path, parent: &'a Dir) -> IoResult> { let v = path.filename().unwrap(); // fails if / or . or .. - let filename = from_utf8_lossy(v).to_string(); + let filename = String::from_utf8_lossy(v).to_string(); // Use lstat here instead of file.stat(), as it doesn't follow // symbolic links. Otherwise, the stat() call will fail if it @@ -160,7 +159,7 @@ impl<'a> File<'a> { fn target_file_name_and_arrow(&self, target_path: Path) -> String { let v = target_path.filename().unwrap(); - let filename = from_utf8_lossy(v).to_string(); + let filename = String::from_utf8_lossy(v).to_string(); let link_target = fs::stat(&target_path).map(|stat| File { path: &target_path, diff --git a/src/options.rs b/src/options.rs index 35f0ca7..750c423 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,7 +1,6 @@ extern crate getopts; use file::File; -use std::cmp::lexical_ordering; use column::{Column, Permissions, FileName, FileSize, User, Group, HardLinks, Inode, Blocks}; use std::ascii::StrAsciiExt; @@ -122,7 +121,7 @@ impl Options { Extension => files.sort_by(|a, b| { let exts = a.ext.clone().map(|e| e.as_slice().to_ascii_lower()).cmp(&b.ext.clone().map(|e| e.as_slice().to_ascii_lower())); let names = a.name.as_slice().to_ascii_lower().cmp(&b.name.as_slice().to_ascii_lower()); - lexical_ordering(exts, names) + exts.cmp(&names) }), } diff --git a/src/unix.rs b/src/unix.rs index 4d6d567..a14a1bb 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -50,7 +50,8 @@ pub struct Unix { impl Unix { pub fn empty_cache() -> Unix { let uid = unsafe { c::getuid() }; - let info = unsafe { c::getpwuid(uid as i32).to_option().unwrap() }; // the user has to have a name + let infoptr = unsafe { c::getpwuid(uid as i32) }; + let info = unsafe { infoptr.to_option().unwrap() }; // the user has to have a name let username = unsafe { from_c_str(info.pw_name) }; From b1560edb854d0e0d1f0c37f176f6bce6571d00a5 Mon Sep 17 00:00:00 2001 From: Ben S Date: Mon, 21 Jul 2014 22:05:39 +0100 Subject: [PATCH 09/10] Use string width, rather than length, to calculate column size --- src/exa.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/exa.rs b/src/exa.rs index c36d7d4..b388b8e 100644 --- a/src/exa.rs +++ b/src/exa.rs @@ -3,6 +3,10 @@ extern crate regex; #[phase(plugin)] extern crate regex_macros; extern crate ansi_term; +extern crate unicode; +use std::char::UnicodeChar; +use std::iter::AdditiveIterator; + use std::os; use file::File; @@ -62,11 +66,19 @@ fn exa(opts: &Options) { } } +fn width(string: &str) -> uint { + string.as_slice().chars() + .map(|c| c.width(true)) + .filter(|o| o.is_some()) + .map(|o| o.unwrap()) + .sum() +} + fn grid_view(options: &Options, across: bool, 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 max_column_length = files.iter().map(|f| width(f.name.as_slice())).max().unwrap(); let console_width = 80; let num_columns = (console_width + 1) / (max_column_length + 1); let count = files.len(); From cf3e32c9c166c16aa57a3ce2c0eff34796c5cd0a Mon Sep 17 00:00:00 2001 From: Ben S Date: Tue, 22 Jul 2014 15:41:20 +0100 Subject: [PATCH 10/10] Get terminal width for grid view (resolve #1) --- src/exa.rs | 16 +++------------ src/file.rs | 5 +++++ src/term.rs | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/unix.rs | 1 + 4 files changed, 66 insertions(+), 13 deletions(-) create mode 100644 src/term.rs diff --git a/src/exa.rs b/src/exa.rs index b388b8e..5621174 100644 --- a/src/exa.rs +++ b/src/exa.rs @@ -2,10 +2,7 @@ extern crate regex; #[phase(plugin)] extern crate regex_macros; extern crate ansi_term; - extern crate unicode; -use std::char::UnicodeChar; -use std::iter::AdditiveIterator; use std::os; @@ -25,6 +22,7 @@ pub mod filetype; pub mod unix; pub mod options; pub mod sort; +pub mod term; fn main() { let args = os::args(); @@ -66,20 +64,12 @@ fn exa(opts: &Options) { } } -fn width(string: &str) -> uint { - string.as_slice().chars() - .map(|c| c.width(true)) - .filter(|o| o.is_some()) - .map(|o| o.unwrap()) - .sum() -} - fn grid_view(options: &Options, across: bool, dir: Dir) { let unsorted_files = dir.files(); let files: Vec<&File> = options.transform_files(&unsorted_files); - let max_column_length = files.iter().map(|f| width(f.name.as_slice())).max().unwrap(); - let console_width = 80; + let max_column_length = files.iter().map(|f| f.file_name_width()).max().unwrap(); + let (console_width, _) = term::dimensions().unwrap_or((80, 24)); let num_columns = (console_width + 1) / (max_column_length + 1); let count = files.len(); diff --git a/src/file.rs b/src/file.rs index 69c0b90..0de2655 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,5 +1,6 @@ use std::io::{fs, IoResult}; use std::io; +use unicode::str::UnicodeStrSlice; use ansi_term::{Paint, Colour, Plain, Style, Red, Green, Yellow, Blue, Purple, Cyan, Fixed}; @@ -157,6 +158,10 @@ impl<'a> File<'a> { } } + pub fn file_name_width(&self) -> uint { + self.name.as_slice().width(false) + } + fn target_file_name_and_arrow(&self, target_path: Path) -> String { let v = target_path.filename().unwrap(); let filename = String::from_utf8_lossy(v).to_string(); diff --git a/src/term.rs b/src/term.rs new file mode 100644 index 0000000..9e88adf --- /dev/null +++ b/src/term.rs @@ -0,0 +1,57 @@ + +mod c { + #![allow(non_camel_case_types)] + extern crate libc; + pub use self::libc::{ + c_int, + c_ushort, + c_ulong, + STDOUT_FILENO, + }; + use std::mem::zeroed; + + // Getting the terminal size is done using an ioctl command that + // takes the file handle to the terminal (which in our case is + // stdout), and populates a structure with the values. + + pub struct winsize { + pub ws_row: c_ushort, + pub ws_col: c_ushort, + } + + // Unfortunately the actual command is not standardised... + + #[cfg(target_os = "linux")] + #[cfg(target_os = "android")] + static TIOCGWINSZ: c_ulong = 0x5413; + + #[cfg(target_os = "freebsd")] + #[cfg(target_os = "macos")] + static TIOCGWINSZ: c_ulong = 0x40087468; + + extern { + pub fn ioctl(fd: c_int, request: c_ulong, ...) -> c_int; + } + + pub fn dimensions() -> winsize { + unsafe { + let mut window: winsize = zeroed(); + ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut window as *mut winsize); + window + } + } +} + +pub fn dimensions() -> Option<(uint, uint)> { + let w = c::dimensions(); + + // If either of the dimensions is 0 then the command failed, + // usually because output isn't to a terminal (instead to a file + // or pipe or something) + if w.ws_col == 0 || w.ws_row == 0 { + None + } + else { + Some((w.ws_col as uint, w.ws_row as uint)) + } +} diff --git a/src/unix.rs b/src/unix.rs index a14a1bb..b1257ec 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -39,6 +39,7 @@ mod c { pub fn getuid() -> libc::c_int; } } + pub struct Unix { user_names: HashMap>, // mapping of user IDs to user names group_names: HashMap>, // mapping of groups IDs to group names