diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/file.rs | 2 | ||||
-rw-r--r-- | src/main.rs | 4 | ||||
-rw-r--r-- | src/output/cell.rs | 130 | ||||
-rw-r--r-- | src/output/column.rs | 38 | ||||
-rw-r--r-- | src/output/details.rs | 253 | ||||
-rw-r--r-- | src/output/grid_details.rs | 30 | ||||
-rw-r--r-- | src/output/lines.rs | 6 | ||||
-rw-r--r-- | src/output/mod.rs | 37 |
8 files changed, 310 insertions, 190 deletions
diff --git a/src/file.rs b/src/file.rs index 08c45bb..d4c9b9e 100644 --- a/src/file.rs +++ b/src/file.rs @@ -195,7 +195,7 @@ impl<'dir> File<'dir> { /// If statting the file fails (usually because the file on the /// other end doesn't exist), returns the *filename* of the file /// that should be there. - pub fn link_target(&self) -> Result<File, String> { + pub fn link_target(&self) -> Result<File<'dir>, String> { let path = match fs::read_link(&self.path) { Ok(path) => path, Err(_) => return Err(self.name.clone()), diff --git a/src/main.rs b/src/main.rs index 1e9f76c..11a2fc0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -135,8 +135,8 @@ impl Exa { 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::GridDetails(gd) => gd.view(dir, files), + View::Lines(l) => l.view(files), } } } diff --git a/src/output/cell.rs b/src/output/cell.rs new file mode 100644 index 0000000..1d3f6e2 --- /dev/null +++ b/src/output/cell.rs @@ -0,0 +1,130 @@ +//! The `TextCell` type for the details and lines views. + +use ansi_term::{Style, ANSIString, ANSIStrings}; +use unicode_width::UnicodeWidthStr; + + +/// An individual cell that holds text in a table, used in the details and +/// lines views to store ANSI-terminal-formatted data before it is printed. +/// +/// A text cell is made up of zero or more strings coupled with the +/// pre-computed length of all the strings combined. When constructing details +/// or grid-details tables, the length will have to be queried multiple times, +/// so it makes sense to cache it. +/// +/// (This used to be called `Cell`, but was renamed because there’s a Rust +/// type by that name too.) +#[derive(PartialEq, Debug, Clone, Default)] +pub struct TextCell { + + /// The contents of this cell, as a vector of ANSI-styled strings. + pub contents: TextCellContents, + + /// The Unicode “display width” of this cell, in characters. + /// + /// As with the `File` type’s width, this is related to the number of + /// *graphemes*, rather than *characters*, in the cell: most are 1 column + /// wide, but in some contexts, certain characters are two columns wide. + pub length: usize, +} + +impl TextCell { + + /// Creates a new text cell that holds the given text in the given style, + /// computing the Unicode width of the text. + pub fn paint(style: Style, text: String) -> Self { + TextCell { + length: text.width(), + contents: vec![ style.paint(text) ], + } + } + + /// Creates a new text cell that holds the given text in the given style, + /// computing the Unicode width of the text. (This could be merged with + /// `paint`, but.) + pub fn paint_str(style: Style, text: &'static str) -> Self { + TextCell { + length: text.len(), + contents: vec![ style.paint(text) ], + } + } + + /// Creates a new “blank” text cell that contains a single hyphen in the + /// given style, which should be the “punctuation” style from a `Colours` + /// value. + /// + /// This is used in place of empty table cells, as it is easier to read + /// tabular data when there is *something* in each cell. + pub fn blank(style: Style) -> Self { + TextCell { + length: 1, + contents: vec![ style.paint("-") ], + } + } + + /// Adds the given number of unstyled spaces after this cell. + /// + /// This method allocates a `String` to hold the spaces. + pub fn add_spaces(&mut self, count: usize) { + use std::iter::repeat; + + self.length += count; + + let spaces: String = repeat(' ').take(count).collect(); + self.contents.push(Style::default().paint(spaces)); + } + + /// Adds the contents of another `ANSIString` to the end of this cell. + pub fn push(&mut self, string: ANSIString<'static>, length: usize) { + self.contents.push(string); + self.length += length; + } + + /// Adds all the contents of another `TextCell` to the end of this cell. + pub fn append(&mut self, other: TextCell) { + self.length += other.length; + self.contents.extend(other.contents); + } + + /// Produces an `ANSIStrings` value that can be used to print the styled + /// values of this cell as an ANSI-terminal-formatted string. + pub fn strings(&self) -> ANSIStrings { + ANSIStrings(&self.contents) + } +} + + +// I’d like to eventually abstract cells so that instead of *every* cell +// storing a vector, only variable-length cells would, and individual cells +// would just store an array of a fixed length (which would usually be just 1 +// or 2), which wouldn’t require a heap allocation. +// +// For examples, look at the `render_*` methods in the `Table` object in the +// details view: +// +// - `render_blocks`, `inode`, and `links` will always return a +// one-string-long TextCell; +// - `render_size` will return one or two strings in a TextCell, depending on +// the size and whether one is present; +// - `render_permissions` will return ten or eleven strings; +// - `filename` and `symlink_filename` in the output module root return six or +// five strings. +// +// In none of these cases are we dealing with a *truly variable* number of +// strings: it is only when the strings are concatenated together do we need a +// growable, heap-allocated buffer. +// +// So it would be nice to abstract the `TextCell` type so instead of a `Vec`, +// it can use anything of type `T: IntoIterator<Item=ANSIString<’static>>`. +// This would allow us to still hold all the data, but allocate less. +// +// But exa still has bugs and I need to fix those first :( + + +/// The contents of a text cell, as a vector of ANSI-styled strings. +/// +/// It’s possible to use this type directly in the case where you want a +/// `TextCell` but aren’t concerned with tracking its width, because it occurs +/// in the final cell of a table or grid and there’s no point padding it. This +/// happens when dealing with file names. +pub type TextCellContents = Vec<ANSIString<'static>>;
\ No newline at end of file diff --git a/src/output/column.rs b/src/output/column.rs index c2b8568..cfec1f9 100644 --- a/src/output/column.rs +++ b/src/output/column.rs @@ -1,6 +1,3 @@ -use ansi_term::Style; -use unicode_width::UnicodeWidthStr; - use dir::Dir; @@ -194,38 +191,3 @@ impl Default for TimeTypes { TimeTypes { accessed: false, modified: true, created: false } } } - - -#[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); - } -} diff --git a/src/output/details.rs b/src/output/details.rs index a7d1da9..e9e59b2 100644 --- a/src/output/details.rs +++ b/src/output/details.rs @@ -124,9 +124,10 @@ use feature::xattr::{Attribute, FileAttributes}; use file::fields as f; use file::File; use options::{FileFilter, RecurseOptions}; -use output::column::{Alignment, Column, Columns, Cell, SizeFormat}; +use output::column::{Alignment, Column, Columns, SizeFormat}; +use output::cell::TextCell; -use ansi_term::{ANSIString, ANSIStrings, Style}; +use ansi_term::Style; use datetime::local::{LocalDateTime, DatePiece}; use datetime::format::DateFormat; @@ -198,7 +199,7 @@ impl Details { // Then add files to the table and print it out. self.add_files_to_table(&mut table, files, 0); for cell in table.print_table() { - println!("{}", cell.text); + println!("{}", cell.strings()); } } @@ -213,20 +214,18 @@ impl Details { let mut file_eggs = Vec::new(); struct Egg<'_> { - cells: Vec<Cell>, - name: Cell, + cells: Vec<TextCell>, xattrs: Vec<Attribute>, errors: Vec<(io::Error, Option<PathBuf>)>, dir: Option<Dir>, - file: Arc<File<'_>>, + file: File<'_>, } pool.scoped(|scoped| { let file_eggs = Arc::new(Mutex::new(&mut file_eggs)); let table = Arc::new(Mutex::new(&mut table)); - for file in src.into_iter() { - let file: Arc<File> = Arc::new(file); + for file in src { let file_eggs = file_eggs.clone(); let table = table.clone(); @@ -251,11 +250,6 @@ impl Details { let cells = table.lock().unwrap().cells_for_file(&file, !xattrs.is_empty()); - let name = Cell { - text: filename(&file, &self.colours, true), - length: file.file_name_width() - }; - let mut dir = None; if let Some(r) = self.recurse { @@ -268,7 +262,6 @@ impl Details { let egg = Egg { cells: cells, - name: name, xattrs: xattrs, errors: errors, dir: dir, @@ -280,17 +273,22 @@ impl Details { } }); - file_eggs.sort_by(|a, b| self.filter.compare_files(&*a.file, &*b.file)); + file_eggs.sort_by(|a, b| self.filter.compare_files(&a.file, &b.file)); let num_eggs = file_eggs.len(); for (index, egg) in file_eggs.into_iter().enumerate() { let mut files = Vec::new(); let mut errors = egg.errors; + let name = TextCell { + length: egg.file.file_name_width(), + contents: filename(egg.file, &self.colours, true), + }; + let row = Row { depth: depth, cells: Some(egg.cells), - name: egg.name, + name: name, last: index == num_eggs - 1, }; @@ -342,14 +340,11 @@ struct Row { /// almost always be `Some`, containing a vector of cells. It will only be /// `None` for a row displaying an attribute or error, neither of which /// have cells. - cells: Option<Vec<Cell>>, - - // Did You Know? - // A Vec<Cell> and an Option<Vec<Cell>> actually have the same byte size! + cells: Option<Vec<TextCell>>, /// This file's name, in coloured output. The name is treated separately /// from the other cells, as it never requires padding. - name: Cell, + name: TextCell, /// How many directories deep into the tree structure this is. Directories /// on top have depth 0. @@ -429,8 +424,8 @@ impl<U> Table<U> where U: Users { pub fn add_header(&mut self) { let row = Row { depth: 0, - cells: Some(self.columns.iter().map(|c| Cell::paint(self.colours.header, c.header())).collect()), - name: Cell::paint(self.colours.header, "Name"), + cells: Some(self.columns.iter().map(|c| TextCell::paint_str(self.colours.header, c.header())).collect()), + name: TextCell::paint_str(self.colours.header, "Name"), last: false, }; @@ -446,7 +441,7 @@ impl<U> Table<U> where U: Users { let row = Row { depth: depth, cells: None, - name: Cell::paint(self.colours.broken_arrow, &error_message), + name: TextCell::paint(self.colours.broken_arrow, error_message), last: last, }; @@ -457,19 +452,26 @@ impl<U> Table<U> where U: Users { let row = Row { depth: depth, cells: None, - name: Cell::paint(self.colours.perms.attribute, &format!("{} (len {})", xattr.name, xattr.size)), + name: TextCell::paint(self.colours.perms.attribute, format!("{} (len {})", xattr.name, xattr.size)), last: last, }; self.rows.push(row); } - pub fn add_file_with_cells(&mut self, cells: Vec<Cell>, file: &File, depth: usize, last: bool, links: bool) { + pub fn filename_cell(&self, file: File, links: bool) -> TextCell { + TextCell { + length: file.file_name_width(), + contents: filename(file, &self.colours, links), + } + } + + pub fn add_file_with_cells(&mut self, cells: Vec<TextCell>, name_cell: TextCell, depth: usize, last: bool) { let row = Row { - depth: depth, - cells: Some(cells), - name: Cell { text: filename(file, &self.colours, links), length: file.file_name_width() }, - last: last, + depth: depth, + cells: Some(cells), + name: name_cell, + last: last, }; self.rows.push(row); @@ -477,13 +479,13 @@ impl<U> Table<U> where U: Users { /// Use the list of columns to find which cells should be produced for /// this file, per-column. - pub fn cells_for_file(&mut self, file: &File, xattrs: bool) -> Vec<Cell> { + pub fn cells_for_file(&mut self, file: &File, xattrs: bool) -> Vec<TextCell> { self.columns.clone().iter() .map(|c| self.display(file, c, xattrs)) .collect() } - fn display(&mut self, file: &File, column: &Column, xattrs: bool) -> Cell { + fn display(&mut self, file: &File, column: &Column, xattrs: bool) -> TextCell { use output::column::TimeType::*; match *column { @@ -501,7 +503,7 @@ impl<U> Table<U> where U: Users { } } - fn render_permissions(&self, permissions: f::Permissions, xattrs: bool) -> Cell { + fn render_permissions(&self, permissions: f::Permissions, xattrs: bool) -> TextCell { let c = self.colours.perms; let bit = |bit, chr: &'static str, style: Style| { if bit { style.paint(chr) } else { self.colours.punctuation.paint("-") } @@ -518,7 +520,7 @@ impl<U> Table<U> where U: Users { let x_colour = if let f::Type::File = permissions.file_type { c.user_execute_file } else { c.user_execute_other }; - let mut columns = vec![ + let mut chars = vec![ file_type, bit(permissions.user_read, "r", c.user_read), bit(permissions.user_write, "w", c.user_write), @@ -532,63 +534,71 @@ impl<U> Table<U> where U: Users { ]; if xattrs { - columns.push(c.attribute.paint("@")); + chars.push(c.attribute.paint("@")); } - Cell { - text: ANSIStrings(&columns).to_string(), - length: columns.len(), + TextCell { + length: chars.len(), + contents: chars, } } - fn render_links(&self, links: f::Links) -> Cell { + fn render_links(&self, links: f::Links) -> TextCell { let style = if links.multiple { self.colours.links.multi_link_file } else { self.colours.links.normal }; - Cell::paint(style, &self.numeric.format_int(links.count)) + TextCell::paint(style, self.numeric.format_int(links.count)) } - fn render_blocks(&self, blocks: f::Blocks) -> Cell { + fn render_blocks(&self, blocks: f::Blocks) -> TextCell { match blocks { - f::Blocks::Some(blocks) => Cell::paint(self.colours.blocks, &blocks.to_string()), - f::Blocks::None => Cell::paint(self.colours.punctuation, "-"), + f::Blocks::Some(blk) => TextCell::paint(self.colours.blocks, blk.to_string()), + f::Blocks::None => TextCell::blank(self.colours.punctuation), } } - fn render_inode(&self, inode: f::Inode) -> Cell { - Cell::paint(self.colours.inode, &inode.0.to_string()) + fn render_inode(&self, inode: f::Inode) -> TextCell { + TextCell::paint(self.colours.inode, inode.0.to_string()) } - fn render_size(&self, size: f::Size, size_format: SizeFormat) -> Cell { - use number_prefix::{binary_prefix, decimal_prefix, Prefixed, Standalone, PrefixNames}; + fn render_size(&self, size: f::Size, size_format: SizeFormat) -> TextCell { + use number_prefix::{binary_prefix, decimal_prefix}; + use number_prefix::{Prefixed, Standalone, PrefixNames}; - if let f::Size::Some(offset) = size { - let result = match size_format { - SizeFormat::DecimalBytes => decimal_prefix(offset as f64), - SizeFormat::BinaryBytes => binary_prefix(offset as f64), - SizeFormat::JustBytes => return Cell::paint(self.colours.size.numbers, &self.numeric.format_int(offset)), - }; + let size = match size { + f::Size::Some(s) => s, + f::Size::None => return TextCell::blank(self.colours.punctuation), + }; - match result { - Standalone(bytes) => Cell::paint(self.colours.size.numbers, &*bytes.to_string()), - Prefixed(prefix, n) => { - let number = if n < 10f64 { self.numeric.format_float(n, 1) } else { self.numeric.format_int(n as isize) }; - let symbol = prefix.symbol(); + let result = match size_format { + SizeFormat::DecimalBytes => decimal_prefix(size as f64), + SizeFormat::BinaryBytes => binary_prefix(size as f64), + SizeFormat::JustBytes => { + let string = self.numeric.format_int(size); + return TextCell::paint(self.colours.size.numbers, string); + }, + }; - Cell { - text: ANSIStrings( &[ self.colours.size.numbers.paint(&number[..]), self.colours.size.unit.paint(symbol) ]).to_string(), - length: number.len() + symbol.len(), - } - } - } - } - else { - Cell::paint(self.colours.punctuation, "-") + let (prefix, n) = match result { + Standalone(b) => return TextCell::paint(self.colours.size.numbers, b.to_string()), + Prefixed(p, n) => (p, n) + }; + + let symbol = prefix.symbol(); + let number = if n < 10f64 { self.numeric.format_float(n, 1) } + else { self.numeric.format_int(n as isize) }; + + TextCell { + length: number.len() + symbol.len(), + contents: vec![ + self.colours.size.numbers.paint(number), + self.colours.size.unit.paint(symbol), + ], } } #[allow(trivial_numeric_casts)] - fn render_time(&self, timestamp: f::Time) -> Cell { + fn render_time(&self, timestamp: f::Time) -> TextCell { let date = self.tz.at(LocalDateTime::at(timestamp.0 as i64)); let datestamp = if date.year() == self.current_year { @@ -598,60 +608,60 @@ impl<U> Table<U> where U: Users { DATE_AND_YEAR.format(&date, &self.time) }; - Cell::paint(self.colours.date, &datestamp) + TextCell::paint(self.colours.date, datestamp) } - fn render_git_status(&self, git: f::Git) -> Cell { - Cell { - text: ANSIStrings(&[ self.render_git_char(git.staged), - self.render_git_char(git.unstaged) ]).to_string(), - length: 2, - } - } - - fn render_git_char(&self, status: f::GitStatus) -> ANSIString { - match status { + fn render_git_status(&self, git: f::Git) -> TextCell { + let git_char = |status| match status { f::GitStatus::NotModified => self.colours.punctuation.paint("-"), f::GitStatus::New => self.colours.git.new.paint("N"), f::GitStatus::Modified => self.colours.git.modified.paint("M"), f::GitStatus::Deleted => self.colours.git.deleted.paint("D"), f::GitStatus::Renamed => self.colours.git.renamed.paint("R"), f::GitStatus::TypeChange => self.colours.git.typechange.paint("T"), + }; + + TextCell { + length: 2, + contents: vec![ + git_char(git.staged), + git_char(git.unstaged) + ], } } - fn render_user(&mut self, user: f::User) -> Cell { + fn render_user(&mut self, user: f::User) -> TextCell { let user_name = match self.users.get_user_by_uid(user.0) { Some(user) => user.name, None => user.0.to_string(), }; let style = if self.users.get_current_uid() == user.0 { self.colours.users.user_you } - else { self.colours.users.user_someone_else }; - Cell::paint(style, &*user_name) + else { self.colours.users.user_someone_else }; + TextCell::paint(style, user_name) } - fn render_group(&mut self, group: f::Group) -> Cell { + fn render_group(&mut self, group: f::Group) -> TextCell { let mut style = self.colours.users.group_not_yours; - let group_name = match self.users.get_group_by_gid(group.0) { - Some(group) => { - let current_uid = self.users.get_current_uid(); - if let Some(current_user) = self.users.get_user_by_uid(current_uid) { - if current_user.primary_group == group.gid || group.members.contains(¤t_user.name) { - style = self.colours.users.group_yours; - } - } - group.name - }, - None => group.0.to_string(), + let group = match self.users.get_group_by_gid(group.0) { + Some(g) => g, + None => return TextCell::paint(style, group.0.to_string()), }; - Cell::paint(style, &*group_name) + let current_uid = self.users.get_current_uid(); + if let Some(current_user) = self.users.get_user_by_uid(current_uid) { + if current_user.primary_group == group.gid + || group.members.contains(¤t_user.name) { + style = self.colours.users.group_yours; + } + } + + TextCell::paint(style, group.name) } /// Render the table as a vector of Cells, to be displayed on standard output. - pub fn print_table(&self) -> Vec<Cell> { + pub fn print_table(self) -> Vec<TextCell> { let mut stack = Vec::new(); let mut cells = Vec::new(); @@ -664,14 +674,16 @@ impl<U> Table<U> where U: Users { let total_width: usize = self.columns.len() + column_widths.iter().fold(0, Add::add); - for row in self.rows.iter() { - let mut cell = Cell::empty(); + for row in self.rows { + let mut cell = TextCell::default(); + + if let Some(cells) = row.cells { + for (n, (this_cell, width)) in cells.into_iter().zip(column_widths.iter()).enumerate() { + let padding = width - this_cell.length; - if let Some(ref cells) = row.cells { - for (n, width) in column_widths.iter().enumerate() { match self.columns[n].alignment() { - Alignment::Left => { cell.append(&cells[n]); cell.add_spaces(width - cells[n].length); } - Alignment::Right => { cell.add_spaces(width - cells[n].length); cell.append(&cells[n]); } + Alignment::Left => { cell.append(this_cell); cell.add_spaces(padding); } + Alignment::Right => { cell.add_spaces(padding); cell.append(this_cell); } } cell.add_spaces(1); @@ -681,8 +693,7 @@ impl<U> Table<U> where U: Users { cell.add_spaces(total_width) } - let mut filename = String::new(); - let mut filename_length = 0; + let mut filename = TextCell::default(); // A stack tracks which tree characters should be printed. It's // necessary to maintain information about the previously-printed @@ -699,8 +710,7 @@ 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 { - filename.push_str(&*self.colours.punctuation.paint(stack[i].ascii_art()).to_string()); - filename_length += 4; + filename.push(self.colours.punctuation.paint(stack[i].ascii_art()), 4); } stack[row.depth] = if row.last { TreePart::Blank } else { TreePart::Line }; @@ -708,15 +718,13 @@ 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 { - filename.push(' '); - filename_length += 1; + filename.add_spaces(1); } // Print the name without worrying about padding. - filename.push_str(&*row.name.text); - filename_length += row.name.length; + filename.append(row.name); - cell.append(&Cell { text: filename, length: filename_length }); + cell.append(filename); cells.push(cell); } @@ -767,7 +775,8 @@ pub mod test { pub use super::Table; pub use file::File; pub use file::fields as f; - pub use output::column::{Cell, Column}; + pub use output::column::Column; + pub use output::cell::TextCell; pub use users::{User, Group, uid_t, gid_t}; pub use users::mock::MockUsers; @@ -806,7 +815,7 @@ pub mod test { table.users = users; let user = f::User(1000); - let expected = Cell::paint(Red.bold(), "enoch"); + let expected = TextCell::paint_str(Red.bold(), "enoch"); assert_eq!(expected, table.render_user(user)) } @@ -819,7 +828,7 @@ pub mod test { table.users = users; let user = f::User(1000); - let expected = Cell::paint(Cyan.bold(), "1000"); + let expected = TextCell::paint_str(Cyan.bold(), "1000"); assert_eq!(expected, table.render_user(user)); } @@ -830,7 +839,7 @@ pub mod test { table.users.add_user(newser(1000, "enoch", 100)); let user = f::User(1000); - let expected = Cell::paint(Green.bold(), "enoch"); + let expected = TextCell::paint_str(Green.bold(), "enoch"); assert_eq!(expected, table.render_user(user)); } @@ -840,7 +849,7 @@ pub mod test { table.colours.users.user_someone_else = Red.normal(); let user = f::User(1000); - let expected = Cell::paint(Red.normal(), "1000"); + let expected = TextCell::paint_str(Red.normal(), "1000"); assert_eq!(expected, table.render_user(user)); } @@ -850,7 +859,7 @@ pub mod test { table.colours.users.user_someone_else = Blue.underline(); let user = f::User(2_147_483_648); - let expected = Cell::paint(Blue.underline(), "2147483648"); + let expected = TextCell::paint_str(Blue.underline(), "2147483648"); assert_eq!(expected, table.render_user(user)); } } @@ -869,7 +878,7 @@ pub mod test { table.users = users; let group = f::Group(100); - let expected = Cell::paint(Fixed(101).normal(), "folk"); + let expected = TextCell::paint_str(Fixed(101).normal(), "folk"); assert_eq!(expected, table.render_group(group)) } @@ -882,7 +891,7 @@ pub mod test { table.users = users; let group = f::Group(100); - let expected = Cell::paint(Fixed(87).normal(), "100"); + let expected = TextCell::paint_str(Fixed(87).normal(), "100"); assert_eq!(expected, table.render_group(group)); } @@ -897,7 +906,7 @@ pub mod test { table.users = users; let group = f::Group(100); - let expected = Cell::paint(Fixed(64).normal(), "folk"); + let expected = TextCell::paint_str(Fixed(64).normal(), "folk"); assert_eq!(expected, table.render_group(group)) } @@ -912,7 +921,7 @@ pub mod test { table.users = users; let group = f::Group(100); - let expected = Cell::paint(Fixed(31).normal(), "folk"); + let expected = TextCell::paint_str(Fixed(31).normal(), "folk"); assert_eq!(expected, table.render_group(group)) } @@ -922,7 +931,7 @@ pub mod test { table.colours.users.group_not_yours = Blue.underline(); let group = f::Group(2_147_483_648); - let expected = Cell::paint(Blue.underline(), "2147483648"); + let expected = TextCell::paint_str(Blue.underline(), "2147483648"); assert_eq!(expected, table.render_group(group)); } } diff --git a/src/output/grid_details.rs b/src/output/grid_details.rs index e482464..332bfce 100644 --- a/src/output/grid_details.rs +++ b/src/output/grid_details.rs @@ -1,5 +1,6 @@ use std::iter::repeat; +use ansi_term::ANSIStrings; use users::OSUsers; use term_grid as grid; @@ -7,7 +8,8 @@ use dir::Dir; use feature::xattr::FileAttributes; use file::File; -use output::column::{Column, Cell}; +use output::cell::TextCell; +use output::column::Column; use output::details::{Details, Table}; use output::grid::Grid; @@ -25,19 +27,25 @@ fn file_has_xattrs(file: &File) -> bool { } impl GridDetails { - pub fn view(&self, dir: Option<&Dir>, files: &[File]) { + pub fn view(&self, dir: Option<&Dir>, files: Vec<File>) { let columns_for_dir = match self.details.columns { Some(cols) => cols.for_dir(dir), None => Vec::new(), }; 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, file_has_xattrs(file))).collect(); + let cells = files.iter() + .map(|file| first_table.cells_for_file(file, file_has_xattrs(file))) + .collect::<Vec<_>>(); - let mut last_working_table = self.make_grid(1, &*columns_for_dir, files, cells.clone()); + let file_names = files.into_iter() + .map(|file| first_table.filename_cell(file, false)) + .collect::<Vec<_>>(); + + let mut last_working_table = self.make_grid(1, &columns_for_dir, &file_names, cells.clone()); for column_count in 2.. { - let grid = self.make_grid(column_count, &*columns_for_dir, files, cells.clone()); + let grid = self.make_grid(column_count, &columns_for_dir, &file_names, cells.clone()); let the_grid_fits = { let d = grid.fit_into_columns(column_count); @@ -60,7 +68,7 @@ impl GridDetails { table } - fn make_grid(&self, column_count: usize, columns_for_dir: &[Column], files: &[File], cells: Vec<Vec<Cell>>) -> grid::Grid { + fn make_grid(&self, column_count: usize, columns_for_dir: &[Column], file_names: &[TextCell], cells: Vec<Vec<TextCell>>) -> grid::Grid { let mut tables: Vec<_> = repeat(()).map(|_| self.make_table(columns_for_dir)).take(column_count).collect(); let mut num_cells = cells.len(); @@ -71,7 +79,7 @@ impl GridDetails { 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() { + for (i, (file_name, row)) in file_names.iter().zip(cells.into_iter()).enumerate() { let index = if self.grid.across { i % column_count } @@ -79,10 +87,10 @@ impl GridDetails { i / original_height }; - tables[index].add_file_with_cells(row, file, 0, false, false); + tables[index].add_file_with_cells(row, file_name.clone(), 0, false); } - let columns: Vec<_> = tables.iter().map(|t| t.print_table()).collect(); + let columns: Vec<_> = tables.into_iter().map(|t| t.print_table()).collect(); let direction = if self.grid.across { grid::Direction::LeftToRight } else { grid::Direction::TopToBottom }; @@ -97,7 +105,7 @@ impl GridDetails { for column in columns.iter() { if row < column.len() { let cell = grid::Cell { - contents: column[row].text.clone(), + contents: ANSIStrings(&column[row].contents).to_string(), width: column[row].length, }; @@ -110,7 +118,7 @@ impl GridDetails { for column in columns.iter() { for cell in column.iter() { let cell = grid::Cell { - contents: cell.text.clone(), + contents: ANSIStrings(&cell.contents).to_string(), width: cell.length, }; diff --git a/src/output/lines.rs b/src/output/lines.rs index 7f19105..39ef2c3 100644 --- a/src/output/lines.rs +++ b/src/output/lines.rs @@ -1,6 +1,8 @@ use colours::Colours; use file::File; +use ansi_term::ANSIStrings; + use super::filename; @@ -11,9 +13,9 @@ pub struct Lines { /// The lines view literally just displays each file, line-by-line. impl Lines { - pub fn view(&self, files: &[File]) { + pub fn view(&self, files: Vec<File>) { for file in files { - println!("{}", filename(file |