exa/src/output/grid_details.rs

299 lines
10 KiB
Rust
Raw Normal View History

2017-08-12 11:50:59 +00:00
//! The grid-details view lists several details views side-by-side.
2020-10-12 23:54:06 +00:00
use std::io::{self, Write};
2018-04-02 00:43:08 +00:00
use ansi_term::{ANSIGenericString, ANSIStrings};
use term_grid as grid;
2018-12-07 23:43:31 +00:00
use crate::fs::{Dir, File};
use crate::fs::feature::git::GitCache;
use crate::fs::feature::xattr::FileAttributes;
use crate::fs::filter::FileFilter;
use crate::output::cell::TextCell;
use crate::output::details::{Options as DetailsOptions, Row as DetailsRow, Render as DetailsRender};
use crate::output::file_name::FileStyle;
use crate::output::grid::Options as GridOptions;
use crate::output::icons::painted_icon;
2018-12-07 23:43:31 +00:00
use crate::output::table::{Table, Row as TableRow, Options as TableOptions};
use crate::output::tree::{TreeParams, TreeDepth};
use crate::style::Colours;
#[derive(PartialEq, 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 wouldnt 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 its harder to see whats going on. So it can be enabled just for
/// larger directory listings.
#[derive(PartialEq, Debug, Copy, Clone)]
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> {
2017-08-12 11:50:59 +00:00
/// The directory thats being rendered here.
/// We need this to know which columns to put in the output.
pub dir: Option<&'a Dir>,
2017-08-12 11:50:59 +00:00
/// The files that have been read from the directory. They should all
/// hold a reference to it.
pub files: Vec<File<'a>>,
2017-08-12 11:50:59 +00:00
/// How to colour various pieces of text.
pub colours: &'a Colours,
2017-08-12 11:50:59 +00:00
/// How to format filenames.
pub style: &'a FileStyle,
2017-08-12 11:50:59 +00:00
/// The grid part of the grid-details view.
pub grid: &'a GridOptions,
2017-08-12 11:50:59 +00:00
/// The details part of the grid-details view.
pub details: &'a DetailsOptions,
2017-08-12 11:50:59 +00:00
/// 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,
/// Whether we are skipping Git-ignored files.
pub git_ignoring: bool,
}
impl<'a> Render<'a> {
/// Create a temporary Details render that gets used for the columns of
/// the grid-details render thats 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 columns table, not all of them.
pub fn details(&self) -> DetailsRender<'a> {
DetailsRender {
dir: self.dir,
files: Vec::new(),
colours: self.colours,
style: self.style,
opts: self.details,
recurse: None,
filter: self.filter,
git_ignoring: self.git_ignoring,
}
}
/// Create a Details render for when this grid-details render doesnt 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,
git_ignoring: self.git_ignoring,
}
}
// This doesnt take an IgnoreCache even though the details one does
// because grid-details has no tree view.
2020-10-12 23:54:06 +00:00
pub fn render<W: Write>(self, git: Option<&GitCache>, w: &mut W) -> io::Result<()> {
if let Some((grid, width)) = self.find_fitting_grid(git) {
write!(w, "{}", grid.fit_into_columns(width))
}
else {
self.give_up().render(git, w)
}
}
pub fn find_fitting_grid(&self, git: Option<&GitCache>) -> Option<(grid::Grid, grid::Width)> {
let options = self.details.table.as_ref().expect("Details table options not given!");
2017-08-12 11:50:59 +00:00
let drender = self.details();
let (first_table, _) = self.make_table(options, git, &drender);
let rows = self.files.iter()
.map(|file| first_table.row_for_file(file, file_has_xattrs(file)))
.collect::<Vec<_>>();
Replace Cells with growable TextCells A recent change to ansi-term [1] means that `ANSIString`s can now hold either owned *or* borrowed data (Rust calls this the Cow type). This means that we can delay formatting ANSIStrings into ANSI-control-code-formatted strings until it's absolutely necessary. The process for doing this was: 1. Replace the `Cell` type with a `TextCell` type that holds a vector of `ANSIString` values instead of a formatted string. It still does the width tracking. 2. Rework the details module's `render` functions to emit values of this type. 3. Similarly, rework the functions that produce cells containing filenames to use a `File` value's `name` field, which is an owned `String` that can now be re-used. 4. Update the printing, formatting, and width-calculating code in the details and grid-details views to produce a table by adding vectors together instead of adding strings together, delaying the formatting as long as it can. This results in fewer allocations (as fewer `String` values are produced), and makes the API tidier (as fewer `String` values are being passed around without having their contents specified). This also paves the way to Windows support, or at least support for non-ANSI terminals: by delaying the time until strings are formatted, it'll now be easier to change *how* they are formatted. Casualties include: - Bump to ansi_term v0.7.1, which impls `PartialEq` and `Debug` on `ANSIString`. - The grid_details and lines views now need to take a vector of files, rather than a borrowed slice, so the filename cells produced now own the filename strings that get taken from files. - Fixed the signature of `File#link_target` to specify that the file produced refers to the same directory, rather than some phantom directory with the same lifetime as the file. (This was wrong from the start, but it broke nothing until now) References: [1]: ansi-term@f6a6579ba8174de1cae64d181ec04af32ba2a4f0
2015-12-17 00:25:20 +00:00
let file_names = self.files.iter()
2018-04-02 00:43:08 +00:00
.map(|file| {
if self.details.icons {
let mut icon_cell = TextCell::default();
icon_cell.push(ANSIGenericString::from(painted_icon(file, self.style)), 2);
2018-04-02 00:43:08 +00:00
let file_cell = self.style.for_file(file, self.colours).paint().promote();
icon_cell.append(file_cell);
icon_cell
}
else {
2018-04-02 00:43:08 +00:00
self.style.for_file(file, self.colours).paint().promote()
}
})
.collect::<Vec<_>>();
let mut last_working_table = self.make_grid(1, options, git, &file_names, rows.clone(), &drender);
// If we cant 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, git, &file_names, rows.clone(), &drender);
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 {
// If weve figured out how many columns can fit in the users
// terminal, and it turns out there arent 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));
}
}
None
}
2018-06-19 12:58:03 +00:00
fn make_table(&'a self, options: &'a TableOptions, mut git: Option<&'a GitCache>, drender: &DetailsRender) -> (Table<'a>, Vec<DetailsRow>) {
match (git, self.dir) {
(Some(g), Some(d)) => if ! g.has_anything_for(&d.path) { git = None },
(Some(g), None) => if ! self.files.iter().any(|f| g.has_anything_for(&f.path)) { git = None },
(None, _) => {/* Keep Git how it is */},
}
let mut table = Table::new(options, git, self.colours);
let mut rows = Vec::new();
if self.details.header {
let row = table.header_row();
table.add_widths(&row);
rows.push(drender.render_header(row));
}
(table, rows)
2015-06-29 13:47:07 +00:00
}
fn make_grid(&'a self, column_count: usize, options: &'a TableOptions, git: Option<&GitCache>, file_names: &[TextCell], rows: Vec<TableRow>, drender: &DetailsRender) -> grid::Grid {
let mut tables = Vec::new();
for _ in 0 .. column_count {
tables.push(self.make_table(options, git, drender));
}
let mut num_cells = rows.len();
if self.details.header {
num_cells += column_count;
}
let original_height = divide_rounding_up(rows.len(), column_count);
2015-06-29 13:47:07 +00:00
let height = divide_rounding_up(num_cells, column_count);
2015-06-28 18:11:39 +00:00
for (i, (file_name, row)) in file_names.iter().zip(rows.into_iter()).enumerate() {
let index = if self.grid.across {
i % column_count
}
else {
i / original_height
};
let (ref mut table, ref mut rows) = tables[index];
table.add_widths(&row);
2017-07-03 22:25:56 +00:00
let details_row = drender.render_file(row, file_name.clone(), TreeParams::new(TreeDepth::root(), false));
rows.push(details_row);
}
let columns = tables
.into_iter()
.map(|(table, details_rows)| {
drender.iterate_with_table(table, details_rows)
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
let direction = if self.grid.across { grid::Direction::LeftToRight }
else { grid::Direction::TopToBottom };
let filling = grid::Filling::Spaces(4);
let mut grid = grid::Grid::new(grid::GridOptions { direction, filling });
if self.grid.across {
for row in 0 .. height {
2017-03-31 16:08:11 +00:00
for column in &columns {
if row < column.len() {
let cell = grid::Cell {
Replace Cells with growable TextCells A recent change to ansi-term [1] means that `ANSIString`s can now hold either owned *or* borrowed data (Rust calls this the Cow type). This means that we can delay formatting ANSIStrings into ANSI-control-code-formatted strings until it's absolutely necessary. The process for doing this was: 1. Replace the `Cell` type with a `TextCell` type that holds a vector of `ANSIString` values instead of a formatted string. It still does the width tracking. 2. Rework the details module's `render` functions to emit values of this type. 3. Similarly, rework the functions that produce cells containing filenames to use a `File` value's `name` field, which is an owned `String` that can now be re-used. 4. Update the printing, formatting, and width-calculating code in the details and grid-details views to produce a table by adding vectors together instead of adding strings together, delaying the formatting as long as it can. This results in fewer allocations (as fewer `String` values are produced), and makes the API tidier (as fewer `String` values are being passed around without having their contents specified). This also paves the way to Windows support, or at least support for non-ANSI terminals: by delaying the time until strings are formatted, it'll now be easier to change *how* they are formatted. Casualties include: - Bump to ansi_term v0.7.1, which impls `PartialEq` and `Debug` on `ANSIString`. - The grid_details and lines views now need to take a vector of files, rather than a borrowed slice, so the filename cells produced now own the filename strings that get taken from files. - Fixed the signature of `File#link_target` to specify that the file produced refers to the same directory, rather than some phantom directory with the same lifetime as the file. (This was wrong from the start, but it broke nothing until now) References: [1]: ansi-term@f6a6579ba8174de1cae64d181ec04af32ba2a4f0
2015-12-17 00:25:20 +00:00
contents: ANSIStrings(&column[row].contents).to_string(),
width: *column[row].width,
};
grid.add(cell);
}
}
}
}
else {
2017-03-31 16:08:11 +00:00
for column in &columns {
for cell in column.iter() {
let cell = grid::Cell {
Replace Cells with growable TextCells A recent change to ansi-term [1] means that `ANSIString`s can now hold either owned *or* borrowed data (Rust calls this the Cow type). This means that we can delay formatting ANSIStrings into ANSI-control-code-formatted strings until it's absolutely necessary. The process for doing this was: 1. Replace the `Cell` type with a `TextCell` type that holds a vector of `ANSIString` values instead of a formatted string. It still does the width tracking. 2. Rework the details module's `render` functions to emit values of this type. 3. Similarly, rework the functions that produce cells containing filenames to use a `File` value's `name` field, which is an owned `String` that can now be re-used. 4. Update the printing, formatting, and width-calculating code in the details and grid-details views to produce a table by adding vectors together instead of adding strings together, delaying the formatting as long as it can. This results in fewer allocations (as fewer `String` values are produced), and makes the API tidier (as fewer `String` values are being passed around without having their contents specified). This also paves the way to Windows support, or at least support for non-ANSI terminals: by delaying the time until strings are formatted, it'll now be easier to change *how* they are formatted. Casualties include: - Bump to ansi_term v0.7.1, which impls `PartialEq` and `Debug` on `ANSIString`. - The grid_details and lines views now need to take a vector of files, rather than a borrowed slice, so the filename cells produced now own the filename strings that get taken from files. - Fixed the signature of `File#link_target` to specify that the file produced refers to the same directory, rather than some phantom directory with the same lifetime as the file. (This was wrong from the start, but it broke nothing until now) References: [1]: ansi-term@f6a6579ba8174de1cae64d181ec04af32ba2a4f0
2015-12-17 00:25:20 +00:00
contents: ANSIStrings(&cell.contents).to_string(),
width: *cell.width,
};
grid.add(cell);
}
}
}
grid
}
}
2015-06-29 13:47:07 +00:00
fn divide_rounding_up(a: usize, b: usize) -> usize {
let mut result = a / b;
if a % b != 0 {
result += 1;
}
2015-06-29 13:47:07 +00:00
result
2017-05-10 08:26:50 +00:00
}
fn file_has_xattrs(file: &File) -> bool {
match file.path.attributes() {
Ok(attrs) => ! attrs.is_empty(),
Err(_) => false,
}
}