summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBen S <ogham@bsago.me>2015-06-29 14:50:47 +0100
committerBen S <ogham@bsago.me>2015-06-29 14:50:47 +0100
commit090cebe669ecb1875a0066b207e841caeb4f039d (patch)
tree91ff30f676f11668584a787fe897dacfddddf3b7
parent8d6f62840a5cf404313c6dff06a213a4747e913e (diff)
parent56895ab84fff784b2e8c550fd47ea1dd3698814b (diff)
Merge branch 'long-grid-view'
-rw-r--r--Cargo.lock14
-rw-r--r--src/column.rs42
-rw-r--r--src/main.rs7
-rw-r--r--src/options.rs138
-rw-r--r--src/output/details.rs68
-rw-r--r--src/output/grid_details.rs118
-rw-r--r--src/output/lines.rs2
-rw-r--r--src/output/mod.rs7
8 files changed, 274 insertions, 122 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 3fda465..761c51c 100644
--- a/Cargo.lock
+++ b/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)",
diff --git a/src/column.rs b/src/column.rs
index 23e2a4b..0da3651 100644
--- a/src/column.rs
+++ b/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);
+ }
}
diff --git a/src/main.rs b/src/main.rs
index 28042f8..22af23c 100644
--- a/src/main.rs
+++ b/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),
}
}
}
diff --git a/src/options.rs b/src/options.rs
index f0a49e2..91c7f0f 100644
--- a/src/options.rs
+++ b/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"))
}
-
}
diff --git a/src/output/details.rs b/src/output/details.rs
index 84da4de..9988b69 100644
--- a/src/output/details.rs
+++ b/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
}
}
diff --git a/src/output/grid_details.rs b/src/output/grid_details.rs
new file mode 100644
index 0000000..92b373c
--- /dev/null
+++ b/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
+} \ No newline at end of file
diff --git a/src/output/lines.rs b/src/output/lines.rs
index 1d6dade..7f19105 100644
--- a/src/output/lines.rs
+++ b/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));
}
}
}
diff --git a/src/output/mod.rs b/src/output/mod.rs
index ea48507..b4d9bba 100644
--- a/src/output/mod.rs
+++ b/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 {