use std::io::{self, Write};
use ansi_width;
use grid::{Direction, Filling, Grid, GridOptions};
use term_grid as grid;
use crate::fs::feature::git::GitCache;
use crate::fs::filter::FileFilter;
use crate::fs::{Dir, File};
use crate::output::cell::TextCell;
use crate::output::color_scale::ColorScaleInformation;
use crate::output::details::{Options as DetailsOptions, Render as DetailsRender};
use crate::output::file_name::Options as FileStyle;
use crate::output::table::{Options as TableOptions, Table};
use crate::theme::Theme;
#[derive(PartialEq, Eq, Debug)]
pub struct Options {
pub details: DetailsOptions,
pub row_threshold: RowThreshold,
}
impl Options {
#[must_use]
pub fn to_details_options(&self) -> &DetailsOptions {
&self.details
}
}
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub enum RowThreshold {
MinimumRows(usize),
AlwaysGrid,
}
pub struct Render<'a> {
pub dir: Option<&'a Dir>,
pub files: Vec<File<'a>>,
pub theme: &'a Theme,
pub file_style: &'a FileStyle,
pub details: &'a DetailsOptions,
pub filter: &'a FileFilter,
#[allow(dead_code)]
pub row_threshold: RowThreshold,
pub git_ignoring: bool,
pub git: Option<&'a GitCache>,
pub console_width: usize,
pub git_repos: bool,
}
impl<'a> Render<'a> {
fn details_for_column(&self) -> DetailsRender<'a> {
#[rustfmt::skip]
return DetailsRender {
dir: self.dir,
files: Vec::new(),
theme: self.theme,
file_style: self.file_style,
opts: self.details,
recurse: None,
filter: self.filter,
git_ignoring: self.git_ignoring,
git: self.git,
git_repos: self.git_repos,
};
}
pub fn render<W: Write>(mut self, w: &mut W) -> io::Result<()> {
let options = self
.details
.table
.as_ref()
.expect("Details table options not given!");
let drender = self.details_for_column();
let color_scale_info = ColorScaleInformation::from_color_scale(
self.details.color_scale,
&self.files,
self.filter.dot_filter,
self.git,
self.git_ignoring,
None,
);
let mut table = self.make_table(options);
let rows: Vec<_> = self
.files
.iter()
.map(|file| {
let row = table.row_for_file(file, drender.show_xattr_hint(file), color_scale_info);
table.add_widths(&row);
row
})
.collect();
let cells = rows
.into_iter()
.zip(&self.files)
.map(|(row, file)| {
let filename = self
.file_style
.for_file(file, self.theme)
.paint()
.strings()
.to_string();
let details = table.render(row).strings().to_string();
let padding = " ".repeat(if self.details.header {
4usize.saturating_sub(ansi_width::ansi_width(&filename))
} else {
0
});
format!("{details} {filename}{padding}")
})
.collect();
let grid = Grid::new(
cells,
GridOptions {
filling: Filling::Spaces(4),
direction: Direction::TopToBottom,
width: self.console_width,
},
);
if let RowThreshold::MinimumRows(minimum_rows) = self.row_threshold {
if grid.row_count() < minimum_rows {
let Self {
dir,
files,
theme,
file_style,
details: opts,
filter,
git_ignoring,
git,
git_repos,
..
} = self;
let r = DetailsRender {
dir,
files,
theme,
file_style,
opts,
recurse: None,
filter,
git_ignoring,
git,
git_repos,
};
return r.render(w);
}
}
if self.details.header {
let row = table.header_row();
let name = TextCell::paint_str(self.theme.ui.header.unwrap_or_default(), "Name")
.strings()
.to_string();
let s = table.render(row).strings().to_string();
let combined_header = format!("{s} {name}");
let header_width = ansi_width::ansi_width(&combined_header);
for column_width in grid.column_widths() {
let padding = " ".repeat((column_width + 4).saturating_sub(header_width));
write!(w, "{combined_header}{padding}")?;
}
writeln!(w)?;
}
write!(w, "{grid}")?;
Ok(())
}
fn make_table(&mut self, options: &'a TableOptions) -> Table<'a> {
match (self.git, self.dir) {
(Some(g), Some(d)) => {
if !g.has_anything_for(&d.path) {
self.git = None;
}
}
(Some(g), None) => {
if !self.files.iter().any(|f| g.has_anything_for(&f.path)) {
self.git = None;
}
}
(None, _) => { }
}
let mut table = Table::new(options, self.git, self.theme, self.git_repos);
if self.details.header {
let row = table.header_row();
table.add_widths(&row);
}
table
}
}