mirror of
https://github.com/Llewellynvdm/exa.git
synced 2025-03-21 08:32:22 +00:00
Merge branch 'row-threshold'
This merge adds the EXA_GRID_ROWS environment variable, which disables the grid-details view if it doesn’t result in enough rows of output. Fixes #138.
This commit is contained in:
commit
e933fa6a88
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -14,7 +14,7 @@ dependencies = [
|
||||
"num_cpus 1.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"number_prefix 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"scoped_threadpool 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"term_grid 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"term_grid 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"users 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -321,7 +321,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "term_grid"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -438,7 +438,7 @@ dependencies = [
|
||||
"checksum rand 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "eb250fd207a4729c976794d03db689c9be1d634ab5a1c9da9492a13d8fecbcdf"
|
||||
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
|
||||
"checksum scoped_threadpool 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3ef399c8893e8cb7aa9696e895427fab3a6bf265977bb96e126f24ddd2cda85a"
|
||||
"checksum term_grid 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ccc202875496cf72a683a1ecd66f0742a830e73c202bdbd21867d73dfaac8343"
|
||||
"checksum term_grid 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b56a46b68f4aa347ba5512b1abc12dcb641ff0e9aa3cb49b007595a320e369c5"
|
||||
"checksum term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b6b55df3198cc93372e85dd2ed817f0e38ce8cc0f22eb32391bfad9c4bf209"
|
||||
"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
|
||||
"checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f"
|
||||
|
@ -34,7 +34,7 @@ natord = "1.0.7"
|
||||
num_cpus = "1.3.0"
|
||||
number_prefix = "0.2.3"
|
||||
scoped_threadpool = "0.1.*"
|
||||
term_grid = "0.1.2"
|
||||
term_grid = "0.1.6"
|
||||
unicode-width = "0.1.4"
|
||||
users = "0.5.2"
|
||||
term_size = "0.3.0"
|
||||
|
18
src/exa.rs
18
src/exa.rs
@ -69,7 +69,7 @@ impl Vars for LiveVars {
|
||||
impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
|
||||
pub fn new<I>(args: I, writer: &'w mut W) -> Result<Exa<'args, 'w, W>, Misfire>
|
||||
where I: Iterator<Item=&'args OsString> {
|
||||
Options::parse(args, LiveVars).map(move |(options, args)| {
|
||||
Options::parse(args, &LiveVars).map(move |(options, args)| {
|
||||
Exa { options, writer, args }
|
||||
})
|
||||
}
|
||||
@ -181,10 +181,18 @@ impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
|
||||
let View { ref mode, ref colours, ref style } = self.options.view;
|
||||
|
||||
match *mode {
|
||||
Mode::Lines => lines::Render { files, colours, style }.render(self.writer),
|
||||
Mode::Grid(ref opts) => grid::Render { files, colours, style, opts }.render(self.writer),
|
||||
Mode::Details(ref opts) => details::Render { dir, files, colours, style, opts, filter: &self.options.filter, recurse: self.options.dir_action.recurse_options() }.render(self.writer),
|
||||
Mode::GridDetails(ref grid, ref details) => grid_details::Render { dir, files, colours, style, grid, details, filter: &self.options.filter }.render(self.writer),
|
||||
Mode::Lines => {
|
||||
lines::Render { files, colours, style }.render(self.writer)
|
||||
}
|
||||
Mode::Grid(ref opts) => {
|
||||
grid::Render { files, colours, style, opts }.render(self.writer)
|
||||
}
|
||||
Mode::Details(ref opts) => {
|
||||
details::Render { dir, files, colours, style, opts, filter: &self.options.filter, recurse: self.options.dir_action.recurse_options() }.render(self.writer)
|
||||
}
|
||||
Mode::GridDetails(ref opts) => {
|
||||
grid_details::Render { dir, files, colours, style, grid: &opts.grid, details: &opts.details, filter: &self.options.filter, row_threshold: opts.row_threshold }.render(self.writer)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -130,21 +130,21 @@ mod test {
|
||||
#[test]
|
||||
fn help() {
|
||||
let args = [ os("--help") ];
|
||||
let opts = Options::parse(&args, None);
|
||||
let opts = Options::parse(&args, &None);
|
||||
assert!(opts.is_err())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help_with_file() {
|
||||
let args = [ os("--help"), os("me") ];
|
||||
let opts = Options::parse(&args, None);
|
||||
let opts = Options::parse(&args, &None);
|
||||
assert!(opts.is_err())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unhelpful() {
|
||||
let args = [];
|
||||
let opts = Options::parse(&args, None);
|
||||
let opts = Options::parse(&args, &None);
|
||||
assert!(opts.is_ok()) // no help when --help isn’t passed
|
||||
}
|
||||
}
|
||||
|
@ -73,8 +73,7 @@ use std::ffi::{OsStr, OsString};
|
||||
|
||||
use fs::dir_action::DirAction;
|
||||
use fs::filter::FileFilter;
|
||||
use output::{View, Mode};
|
||||
use output::details;
|
||||
use output::{View, Mode, details, grid_details};
|
||||
|
||||
mod dir_action;
|
||||
mod filter;
|
||||
@ -116,7 +115,7 @@ impl Options {
|
||||
/// struct and a list of free filenames, using the environment variables
|
||||
/// for extra options.
|
||||
#[allow(unused_results)]
|
||||
pub fn parse<'args, I, V>(args: I, vars: V) -> Result<(Options, Vec<&'args OsStr>), Misfire>
|
||||
pub fn parse<'args, I, V>(args: I, vars: &V) -> Result<(Options, Vec<&'args OsStr>), Misfire>
|
||||
where I: IntoIterator<Item=&'args OsString>,
|
||||
V: Vars {
|
||||
use options::parser::{Matches, Strictness};
|
||||
@ -145,14 +144,14 @@ impl Options {
|
||||
pub fn should_scan_for_git(&self) -> bool {
|
||||
match self.view.mode {
|
||||
Mode::Details(details::Options { table: Some(ref table), .. }) |
|
||||
Mode::GridDetails(_, details::Options { table: Some(ref table), .. }) => table.extra_columns.should_scan_for_git(),
|
||||
Mode::GridDetails(grid_details::Options { details: details::Options { table: Some(ref table), .. }, .. }) => table.extra_columns.should_scan_for_git(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines the complete set of options based on the given command-line
|
||||
/// arguments, after they’ve been parsed.
|
||||
fn deduce<V: Vars>(matches: &MatchedFlags, vars: V) -> Result<Options, Misfire> {
|
||||
fn deduce<V: Vars>(matches: &MatchedFlags, vars: &V) -> Result<Options, Misfire> {
|
||||
let dir_action = DirAction::deduce(matches)?;
|
||||
let filter = FileFilter::deduce(matches)?;
|
||||
let view = View::deduce(matches, vars)?;
|
||||
@ -231,28 +230,28 @@ pub mod test {
|
||||
#[test]
|
||||
fn files() {
|
||||
let args = [ os("this file"), os("that file") ];
|
||||
let outs = Options::parse(&args, None).unwrap().1;
|
||||
let outs = Options::parse(&args, &None).unwrap().1;
|
||||
assert_eq!(outs, vec![ &os("this file"), &os("that file") ])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_args() {
|
||||
let nothing: Vec<OsString> = Vec::new();
|
||||
let outs = Options::parse(¬hing, None).unwrap().1;
|
||||
let outs = Options::parse(¬hing, &None).unwrap().1;
|
||||
assert!(outs.is_empty()); // Listing the `.` directory is done in main.rs
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_across() {
|
||||
let args = [ os("--long"), os("--across") ];
|
||||
let opts = Options::parse(&args, None);
|
||||
let opts = Options::parse(&args, &None);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless(&flags::ACROSS, true, &flags::LONG))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oneline_across() {
|
||||
let args = [ os("--oneline"), os("--across") ];
|
||||
let opts = Options::parse(&args, None);
|
||||
let opts = Options::parse(&args, &None);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless(&flags::ACROSS, true, &flags::ONE_LINE))
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ mod test {
|
||||
#[test]
|
||||
fn help() {
|
||||
let args = [ os("--version") ];
|
||||
let opts = Options::parse(&args, None);
|
||||
let opts = Options::parse(&args, &None);
|
||||
assert!(opts.is_err())
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use output::Colours;
|
||||
use output::{View, Mode, grid, details};
|
||||
use output::grid_details::{self, RowThreshold};
|
||||
use output::table::{TimeTypes, Environment, SizeFormat, Columns, Options as TableOptions};
|
||||
use output::file_name::{Classify, FileStyle};
|
||||
use output::time::TimeFormat;
|
||||
@ -13,7 +14,7 @@ use info::filetype::FileExtensions;
|
||||
impl View {
|
||||
|
||||
/// Determine which view to use and all of that view’s arguments.
|
||||
pub fn deduce<V: Vars>(matches: &MatchedFlags, vars: V) -> Result<View, Misfire> {
|
||||
pub fn deduce<V: Vars>(matches: &MatchedFlags, vars: &V) -> Result<View, Misfire> {
|
||||
let mode = Mode::deduce(matches, vars)?;
|
||||
let colours = Colours::deduce(matches)?;
|
||||
let style = FileStyle::deduce(matches)?;
|
||||
@ -25,7 +26,7 @@ impl View {
|
||||
impl Mode {
|
||||
|
||||
/// Determine the mode from the command-line arguments.
|
||||
pub fn deduce<V: Vars>(matches: &MatchedFlags, vars: V) -> Result<Mode, Misfire> {
|
||||
pub fn deduce<V: Vars>(matches: &MatchedFlags, vars: &V) -> Result<Mode, Misfire> {
|
||||
use options::misfire::Misfire::*;
|
||||
|
||||
let long = || {
|
||||
@ -95,10 +96,14 @@ impl Mode {
|
||||
if matches.has(&flags::LONG)? {
|
||||
let details = long()?;
|
||||
if matches.has(&flags::GRID)? {
|
||||
match other_options_scan()? {
|
||||
Mode::Grid(grid) => return Ok(Mode::GridDetails(grid, details)),
|
||||
others => return Ok(others),
|
||||
};
|
||||
let other_options_mode = other_options_scan()?;
|
||||
if let Mode::Grid(grid) = other_options_mode {
|
||||
let row_threshold = RowThreshold::deduce(vars)?;
|
||||
return Ok(Mode::GridDetails(grid_details::Options { grid, details, row_threshold }));
|
||||
}
|
||||
else {
|
||||
return Ok(other_options_mode);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return Ok(Mode::Details(details));
|
||||
@ -149,7 +154,7 @@ impl TerminalWidth {
|
||||
/// Determine a requested terminal width from the command-line arguments.
|
||||
///
|
||||
/// Returns an error if a requested width doesn’t parse to an integer.
|
||||
fn deduce<V: Vars>(vars: V) -> Result<TerminalWidth, Misfire> {
|
||||
fn deduce<V: Vars>(vars: &V) -> Result<TerminalWidth, Misfire> {
|
||||
if let Some(columns) = vars.get("COLUMNS").and_then(|s| s.into_string().ok()) {
|
||||
match columns.parse() {
|
||||
Ok(width) => Ok(TerminalWidth::Set(width)),
|
||||
@ -174,6 +179,24 @@ impl TerminalWidth {
|
||||
}
|
||||
|
||||
|
||||
impl RowThreshold {
|
||||
|
||||
/// Determine whether to use a row threshold based on the given
|
||||
/// environment variables.
|
||||
fn deduce<V: Vars>(vars: &V) -> Result<RowThreshold, Misfire> {
|
||||
if let Some(columns) = vars.get("EXA_GRID_ROWS").and_then(|s| s.into_string().ok()) {
|
||||
match columns.parse() {
|
||||
Ok(rows) => Ok(RowThreshold::MinimumRows(rows)),
|
||||
Err(e) => Err(Misfire::FailedParse(e)),
|
||||
}
|
||||
}
|
||||
else {
|
||||
Ok(RowThreshold::AlwaysGrid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl TableOptions {
|
||||
fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
|
||||
let env = Environment::load_all();
|
||||
@ -478,7 +501,7 @@ mod test {
|
||||
/// Like above, but with $vars.
|
||||
#[test]
|
||||
fn $name() {
|
||||
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, $vars)) {
|
||||
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, &$vars)) {
|
||||
assert_eq!(result.unwrap_err(), $result);
|
||||
}
|
||||
}
|
||||
@ -488,7 +511,7 @@ mod test {
|
||||
/// Like further above, but with $vars.
|
||||
#[test]
|
||||
fn $name() {
|
||||
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, $vars)) {
|
||||
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, &$vars)) {
|
||||
println!("Testing {:?}", result);
|
||||
match result {
|
||||
$pat => assert!(true),
|
||||
@ -643,8 +666,8 @@ mod test {
|
||||
test!(ell: Mode <- ["-l"], None; Both => like Ok(Mode::Details(_)));
|
||||
|
||||
// Grid-details views
|
||||
test!(lid: Mode <- ["--long", "--grid"], None; Both => like Ok(Mode::GridDetails(_, _)));
|
||||
test!(leg: Mode <- ["-lG"], None; Both => like Ok(Mode::GridDetails(_, _)));
|
||||
test!(lid: Mode <- ["--long", "--grid"], None; Both => like Ok(Mode::GridDetails(_)));
|
||||
test!(leg: Mode <- ["-lG"], None; Both => like Ok(Mode::GridDetails(_)));
|
||||
|
||||
|
||||
// Options that do nothing without --long
|
||||
|
@ -1,3 +1,5 @@
|
||||
//! The grid-details view lists several details views side-by-side.
|
||||
|
||||
use std::io::{Write, Result as IOResult};
|
||||
|
||||
use ansi_term::ANSIStrings;
|
||||
@ -16,17 +18,72 @@ use output::table::{Table, Row as TableRow, Options as TableOptions};
|
||||
use output::tree::{TreeParams, TreeDepth};
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Options {
|
||||
pub grid: GridOptions,
|
||||
pub details: DetailsOptions,
|
||||
pub row_threshold: RowThreshold,
|
||||
}
|
||||
|
||||
/// The grid-details view can be configured to revert to just a details view
|
||||
/// (with one column) if it wouldn’t produce enough rows of output.
|
||||
///
|
||||
/// Doing this makes the resulting output look a bit better: when listing a
|
||||
/// small directory of four files in four columns, the files just look spaced
|
||||
/// out and it’s harder to see what’s going on. So it can be enabled just for
|
||||
/// larger directory listings.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum RowThreshold {
|
||||
|
||||
/// Only use grid-details view if it would result in at least this many
|
||||
/// rows of output.
|
||||
MinimumRows(usize),
|
||||
|
||||
/// Use the grid-details view no matter what.
|
||||
AlwaysGrid,
|
||||
}
|
||||
|
||||
|
||||
pub struct Render<'a> {
|
||||
|
||||
/// The directory that’s being rendered here.
|
||||
/// We need this to know which columns to put in the output.
|
||||
pub dir: Option<&'a Dir>,
|
||||
|
||||
/// The files that have been read from the directory. They should all
|
||||
/// hold a reference to it.
|
||||
pub files: Vec<File<'a>>,
|
||||
|
||||
/// How to colour various pieces of text.
|
||||
pub colours: &'a Colours,
|
||||
|
||||
/// How to format filenames.
|
||||
pub style: &'a FileStyle,
|
||||
|
||||
/// The grid part of the grid-details view.
|
||||
pub grid: &'a GridOptions,
|
||||
|
||||
/// The details part of the grid-details view.
|
||||
pub details: &'a DetailsOptions,
|
||||
|
||||
/// How to filter files after listing a directory. The files in this
|
||||
/// render will already have been filtered and sorted, but any directories
|
||||
/// that we recurse into will have to have this applied.
|
||||
pub filter: &'a FileFilter,
|
||||
|
||||
/// The minimum number of rows that there need to be before grid-details
|
||||
/// mode is activated.
|
||||
pub row_threshold: RowThreshold,
|
||||
}
|
||||
|
||||
impl<'a> Render<'a> {
|
||||
|
||||
/// Create a temporary Details render that gets used for the columns of
|
||||
/// the grid-details render that's being generated.
|
||||
///
|
||||
/// This includes an empty files vector because the files get added to
|
||||
/// the table in *this* file, not in details: we only want to insert every
|
||||
/// *n* files into each column’s table, not all of them.
|
||||
pub fn details(&self) -> DetailsRender<'a> {
|
||||
DetailsRender {
|
||||
dir: self.dir.clone(),
|
||||
@ -39,11 +96,33 @@ impl<'a> Render<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render<W: Write>(&self, w: &mut W) -> IOResult<()> {
|
||||
/// Create a Details render for when this grid-details render doesn’t fit
|
||||
/// in the terminal (or something has gone wrong) and we have given up.
|
||||
pub fn give_up(self) -> DetailsRender<'a> {
|
||||
DetailsRender {
|
||||
dir: self.dir,
|
||||
files: self.files,
|
||||
colours: self.colours,
|
||||
style: self.style,
|
||||
opts: self.details,
|
||||
recurse: None,
|
||||
filter: &self.filter,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render<W: Write>(self, w: &mut W) -> IOResult<()> {
|
||||
if let Some((grid, width)) = self.find_fitting_grid() {
|
||||
write!(w, "{}", grid.fit_into_columns(width))
|
||||
}
|
||||
else {
|
||||
self.give_up().render(w)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_fitting_grid(&self) -> Option<(grid::Grid, grid::Width)> {
|
||||
let options = self.details.table.as_ref().expect("Details table options not given!");
|
||||
|
||||
let drender = self.clone().details();
|
||||
let drender = self.details();
|
||||
|
||||
let (first_table, _) = self.make_table(options, &drender);
|
||||
|
||||
@ -57,7 +136,9 @@ impl<'a> Render<'a> {
|
||||
|
||||
let mut last_working_table = self.make_grid(1, options, &file_names, rows.clone(), &drender);
|
||||
|
||||
for column_count in 2.. {
|
||||
// If we can’t fit everything in a grid 100 columns wide, then
|
||||
// something has gone seriously awry
|
||||
for column_count in 2..100 {
|
||||
let grid = self.make_grid(column_count, options, &file_names, rows.clone(), &drender);
|
||||
|
||||
let the_grid_fits = {
|
||||
@ -69,11 +150,20 @@ impl<'a> Render<'a> {
|
||||
last_working_table = grid;
|
||||
}
|
||||
else {
|
||||
return write!(w, "{}", last_working_table.fit_into_columns(column_count - 1));
|
||||
// If we’ve figured out how many columns can fit in the user’s
|
||||
// terminal, and it turns out there aren’t enough rows to
|
||||
// make it worthwhile, then just resort to the lines view.
|
||||
if let RowThreshold::MinimumRows(thresh) = self.row_threshold {
|
||||
if last_working_table.fit_into_columns(column_count - 1).row_count() < thresh {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
return Some((last_working_table, column_count - 1));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
None
|
||||
}
|
||||
|
||||
fn make_table<'t>(&'a self, options: &'a TableOptions, drender: &DetailsRender) -> (Table<'a>, Vec<DetailsRow>) {
|
||||
|
@ -33,6 +33,6 @@ pub struct View {
|
||||
pub enum Mode {
|
||||
Grid(grid::Options),
|
||||
Details(details::Options),
|
||||
GridDetails(grid::Options, details::Options),
|
||||
GridDetails(grid_details::Options),
|
||||
Lines,
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user