提交 fc60838f 编写于 作者: B Benjamin Sago

Extract table from details and grid_details

This commit extracts the common table element from the details and grid_details modules, and makes it its own reusable thing.

- A Table no longer holds the values it’s rendering; it just holds a continually-updated version of the maximum widths for each column. This means that all of the resulting values that turn into Rows — which here are either files, or file eggs — need to be stored *somewhere*, and that somewhere is a secondary vector that gets passed around and modified alongside the Table.
- Likewise, all the mutable methods that were on Table that added a Row now *return* the row that would have been added, hoping that the row does get stored somewhere. (It does, don’t worry.)
- Because rendering with mock users is tested in the user-field-rendering module, we don’t need to bother threading different types of U through the Environment, so now it’s just been specialised to UsersCache.
- Accidentally speed up printing a table by not buffering its entire output first when not necessary.
上级 22c6fb04
......@@ -174,7 +174,7 @@ impl<'w, W: Write + 'w> Exa<'w, W> {
Mode::Lines => lines::Render { files, colours, classify }.render(self.writer),
Mode::Grid(ref opts) => grid::Render { files, colours, classify, opts }.render(self.writer),
Mode::Details(ref opts) => details::Render { dir, files, colours, classify, 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, classify, grid, details }.render(self.writer),
Mode::GridDetails(ref grid, ref details) => grid_details::Render { dir, files, colours, classify, grid, details, filter: &self.options.filter }.render(self.writer),
}
}
else {
......
此差异已折叠。
use std::io::{Write, Result as IOResult};
use std::sync::Arc;
use ansi_term::ANSIStrings;
use users::UsersCache;
use term_grid as grid;
use fs::{Dir, File};
use fs::feature::xattr::FileAttributes;
use options::FileFilter;
use output::cell::TextCell;
use output::column::Column;
use output::colours::Colours;
use output::details::{Table, Environment, Options as DetailsOptions};
use output::details::{Options as DetailsOptions, Row as DetailsRow, Render as DetailsRender};
use output::grid::Options as GridOptions;
use output::file_name::{Classify, LinkStyle};
use output::file_name::{FileName, LinkStyle, Classify};
use output::table::{Table, Environment, Row as TableRow};
pub struct Render<'a> {
......@@ -23,9 +23,22 @@ pub struct Render<'a> {
pub classify: Classify,
pub grid: &'a GridOptions,
pub details: &'a DetailsOptions,
pub filter: &'a FileFilter,
}
impl<'a> Render<'a> {
pub fn details(&self) -> DetailsRender<'a> {
DetailsRender {
dir: self.dir.clone(),
files: Vec::new(),
colours: self.colours,
classify: self.classify,
opts: self.details,
recurse: None,
filter: self.filter,
}
}
pub fn render<W: Write>(&self, w: &mut W) -> IOResult<()> {
let columns_for_dir = match self.details.columns {
......@@ -33,27 +46,24 @@ impl<'a> Render<'a> {
None => Vec::new(),
};
let env = Arc::new(Environment::default());
let env = Environment::default();
let (cells, file_names) = {
let drender = self.clone().details();
let first_table = self.make_table(env.clone(), &*columns_for_dir, self.colours, self.classify);
let (mut first_table, _) = self.make_table(&env, &columns_for_dir, &drender);
let cells = self.files.iter()
.map(|file| first_table.cells_for_file(file, file_has_xattrs(file)))
.collect::<Vec<_>>();
let rows = self.files.iter()
.map(|file| first_table.row_for_file(file, file_has_xattrs(file)))
.collect::<Vec<TableRow>>();
let file_names = self.files.iter()
.map(|file| first_table.filename(file, LinkStyle::JustFilenames).promote())
.collect::<Vec<_>>();
(cells, file_names)
};
let file_names = self.files.iter()
.map(|file| FileName::new(file, LinkStyle::JustFilenames, self.classify, self.colours).paint().promote())
.collect::<Vec<TextCell>>();
let mut last_working_table = self.make_grid(env.clone(), 1, &columns_for_dir, &file_names, cells.clone(), self.colours, self.classify);
let mut last_working_table = self.make_grid(&env, 1, &columns_for_dir, &file_names, rows.clone(), &drender);
for column_count in 2.. {
let grid = self.make_grid(env.clone(), column_count, &columns_for_dir, &file_names, cells.clone(), self.colours, self.classify);
let grid = self.make_grid(&env, column_count, &columns_for_dir, &file_names, rows.clone(), &drender);
let the_grid_fits = {
let d = grid.fit_into_columns(column_count);
......@@ -71,33 +81,33 @@ impl<'a> Render<'a> {
Ok(())
}
fn make_table<'g>(&'g self, env: Arc<Environment<UsersCache>>, columns_for_dir: &'g [Column], colours: &'g Colours, classify: Classify) -> Table<UsersCache> {
let mut table = Table {
columns: columns_for_dir,
colours, classify, env,
xattr: self.details.xattr,
rows: Vec::new(),
};
fn make_table<'t>(&'a self, env: &'a Environment, columns_for_dir: &'a [Column], drender: &DetailsRender) -> (Table<'a>, Vec<DetailsRow>) {
let mut table = Table::new(columns_for_dir, self.colours, env);
let mut rows = Vec::new();
if self.details.header { table.add_header() }
table
if self.details.header {
rows.push(drender.render_header(table.header_row()));
}
(table, rows)
}
fn make_grid<'g>(&'g self, env: Arc<Environment<UsersCache>>, column_count: usize, columns_for_dir: &'g [Column], file_names: &[TextCell], cells: Vec<Vec<TextCell>>, colours: &'g Colours, classify: Classify) -> grid::Grid {
fn make_grid(&'a self, env: &'a Environment, column_count: usize, columns_for_dir: &'a [Column], 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(env.clone(), columns_for_dir, colours, classify));
tables.push(self.make_table(env.clone(), columns_for_dir, drender));
}
let mut num_cells = cells.len();
let mut num_cells = rows.len();
if self.details.header {
num_cells += column_count;
}
let original_height = divide_rounding_up(cells.len(), column_count);
let original_height = divide_rounding_up(rows.len(), column_count);
let height = divide_rounding_up(num_cells, column_count);
for (i, (file_name, row)) in file_names.iter().zip(cells.into_iter()).enumerate() {
for (i, (file_name, row)) in file_names.iter().zip(rows.into_iter()).enumerate() {
let index = if self.grid.across {
i % column_count
}
......@@ -105,10 +115,15 @@ impl<'a> Render<'a> {
i / original_height
};
tables[index].add_file_with_cells(row, file_name.clone(), 0, false);
let (ref mut table, ref mut rows) = tables[index];
table.add_widths(&row);
let details_row = drender.render_file(row, file_name.clone(), 0, false);
rows.push(details_row);
}
let columns: Vec<_> = tables.into_iter().map(|t| t.print_table()).collect();
let columns: Vec<_> = tables.into_iter().map(|(table, details_rows)| {
drender.iterate(&table, details_rows).collect::<Vec<_>>()
}).collect();
let direction = if self.grid.across { grid::Direction::LeftToRight }
else { grid::Direction::TopToBottom };
......
......@@ -14,3 +14,4 @@ mod colours;
mod escape;
mod render;
mod tree;
mod table;
use std::cmp::max;
use std::sync::{Mutex, MutexGuard};
use datetime::fmt::DateFormat;
use datetime::{LocalDateTime, DatePiece};
use datetime::TimeZone;
use zoneinfo_compiled::{CompiledData, Result as TZResult};
use locale;
use users::UsersCache;
use output::cell::TextCell;
use output::colours::Colours;
use output::column::{Alignment, Column};
use fs::{File, fields as f};
/// The **environment** struct contains any data that could change between
/// running instances of exa, depending on the user's computer's configuration.
///
/// Any environment field should be able to be mocked up for test runs.
pub struct Environment {
/// The year of the current time. This gets used to determine which date
/// format to use.
current_year: i64,
/// Localisation rules for formatting numbers.
numeric: locale::Numeric,
/// Localisation rules for formatting timestamps.
time: locale::Time,
/// Date format for printing out timestamps that are in the current year.
date_and_time: DateFormat<'static>,
/// Date format for printing out timestamps that *aren’t*.
date_and_year: DateFormat<'static>,
/// The computer's current time zone. This gets used to determine how to
/// offset files' timestamps.
tz: Option<TimeZone>,
/// Mapping cache of user IDs to usernames.
users: Mutex<UsersCache>,
}
impl Environment {
pub fn lock_users(&self) -> MutexGuard<UsersCache> {
self.users.lock().unwrap()
}
}
impl Default for Environment {
fn default() -> Self {
use unicode_width::UnicodeWidthStr;
let tz = determine_time_zone();
if let Err(ref e) = tz {
println!("Unable to determine time zone: {}", e);
}
let numeric = locale::Numeric::load_user_locale()
.unwrap_or_else(|_| locale::Numeric::english());
let time = locale::Time::load_user_locale()
.unwrap_or_else(|_| locale::Time::english());
// Some locales use a three-character wide month name (Jan to Dec);
// others vary between three and four (1月 to 12月). We assume that
// December is the month with the maximum width, and use the width of
// that to determine how to pad the other months.
let december_width = UnicodeWidthStr::width(&*time.short_month_name(11));
let date_and_time = match december_width {
4 => DateFormat::parse("{2>:D} {4>:M} {2>:h}:{02>:m}").unwrap(),
_ => DateFormat::parse("{2>:D} {:M} {2>:h}:{02>:m}").unwrap(),
};
let date_and_year = match december_width {
4 => DateFormat::parse("{2>:D} {4>:M} {5>:Y}").unwrap(),
_ => DateFormat::parse("{2>:D} {:M} {5>:Y}").unwrap()
};
Environment {
current_year: LocalDateTime::now().year(),
numeric: numeric,
date_and_time: date_and_time,
date_and_year: date_and_year,
time: time,
tz: tz.ok(),
users: Mutex::new(UsersCache::new()),
}
}
}
fn determine_time_zone() -> TZResult<TimeZone> {
TimeZone::from_file("/etc/localtime")
}
pub struct Table<'a> {
columns: &'a [Column],
colours: &'a Colours,
env: &'a Environment,
widths: Vec<usize>,
}
#[derive(Clone)]
pub struct Row {
cells: Vec<TextCell>,
}
impl<'a, 'f> Table<'a> {
pub fn new(columns: &'a [Column], colours: &'a Colours, env: &'a Environment) -> Table<'a> {
let widths = vec![ 0; columns.len() ];
Table { columns, colours, env, widths }
}
pub fn columns_count(&self) -> usize {
self.columns.len()
}
pub fn widths(&self) -> &[usize] {
&self.widths
}
pub fn header_row(&mut self) -> Row {
let mut cells = Vec::with_capacity(self.columns.len());
for (old_width, column) in self.widths.iter_mut().zip(self.columns.iter()) {
let column = TextCell::paint_str(self.colours.header, column.header());
*old_width = max(*old_width, *column.width);
cells.push(column);
}
Row { cells }
}
pub fn row_for_file(&mut self, file: &File, xattrs: bool) -> Row {
let mut cells = Vec::with_capacity(self.columns.len());
let other = self.columns.iter().map(|c| self.display(file, c, xattrs)).collect::<Vec<_>>();
for (old_width, column) in self.widths.iter_mut().zip(other.into_iter()) {
*old_width = max(*old_width, *column.width);
cells.push(column);
}
Row { cells }
}
pub fn add_widths(&mut self, row: &Row) {
for (old_width, cell) in self.widths.iter_mut().zip(row.cells.iter()) {
*old_width = max(*old_width, *cell.width);
}
}
fn permissions_plus(&self, file: &File, xattrs: bool) -> f::PermissionsPlus {
f::PermissionsPlus {
file_type: file.type_char(),
permissions: file.permissions(),
xattrs: xattrs,
}
}
fn display(&self, file: &File, column: &Column, xattrs: bool) -> TextCell {
use output::column::TimeType::*;
match *column {
Column::Permissions => self.permissions_plus(file, xattrs).render(&self.colours),
Column::FileSize(fmt) => file.size().render(&self.colours, fmt, &self.env.numeric),
Column::Timestamp(Modified) => file.modified_time().render(&self.colours, &self.env.tz, &self.env.date_and_time, &self.env.date_and_year, &self.env.time, self.env.current_year),
Column::Timestamp(Created) => file.created_time().render( &self.colours, &self.env.tz, &self.env.date_and_time, &self.env.date_and_year, &self.env.time, self.env.current_year),
Column::Timestamp(Accessed) => file.accessed_time().render(&self.colours, &self.env.tz, &self.env.date_and_time, &self.env.date_and_year, &self.env.time, self.env.current_year),
Column::HardLinks => file.links().render(&self.colours, &self.env.numeric),
Column::Inode => file.inode().render(&self.colours),
Column::Blocks => file.blocks().render(&self.colours),
Column::User => file.user().render(&self.colours, &*self.env.lock_users()),
Column::Group => file.group().render(&self.colours, &*self.env.lock_users()),
Column::GitStatus => file.git_status().render(&self.colours),
}
}
pub fn render(&self, row: Row) -> TextCell {
let mut cell = TextCell::default();
for (n, (this_cell, width)) in row.cells.into_iter().zip(self.widths.iter()).enumerate() {
let padding = width - *this_cell.width;
match self.columns[n].alignment() {
Alignment::Left => { cell.append(this_cell); cell.add_spaces(padding); }
Alignment::Right => { cell.add_spaces(padding); cell.append(this_cell); }
}
cell.add_spaces(1);
}
cell
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册