Merge branch 'long-grid-view'

This commit is contained in:
Ben S 2015-06-29 14:50:47 +01:00
commit 090cebe669
8 changed files with 273 additions and 121 deletions

14
Cargo.lock generated
View File

@ -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)",

View File

@ -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);
}
}

View File

@ -181,6 +181,7 @@ impl<'dir> Exa<'dir> {
match self.options.view {
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),
}
}

View File

@ -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,46 +274,33 @@ impl View {
colours: if dimensions().is_some() { Colours::colourful() } else { Colours::plain() },
};
Ok(View::Details(details))
Ok(details)
}
};
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"));
}
}
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") {
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() {
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"))
@ -344,6 +333,26 @@ impl View {
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));
}
}
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"))
}
}

View File

@ -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
src/output/grid_details.rs Normal file
View File

@ -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
}

View File

@ -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));
}
}
}

View File

@ -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 {