diff options
author | Benjamin Sago <ogham@bsago.me> | 2015-12-22 12:36:32 +1100 |
---|---|---|
committer | Benjamin Sago <ogham@bsago.me> | 2015-12-22 12:36:32 +1100 |
commit | fb22c7456de50a2dcd5c2c4b975ba5ce670cc042 (patch) | |
tree | c0398b21861dc6c6e3665937dfed5b4e54d8b05a | |
parent | 95c0d63045875a0c41a124b1dc6ed7f738163316 (diff) | |
parent | 2e15b812496f9f1cc92da22874cfab3239170ee5 (diff) |
Merge branch 'cellular-regeneration' into develop
-rw-r--r-- | Cargo.lock | 32 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/file.rs | 13 | ||||
-rw-r--r-- | src/filetype.rs | 26 | ||||
-rw-r--r-- | src/main.rs | 5 | ||||
-rw-r--r-- | src/options.rs | 2 | ||||
-rw-r--r-- | src/output/cell.rs | 205 | ||||
-rw-r--r-- | src/output/colours.rs (renamed from src/colours.rs) | 0 | ||||
-rw-r--r-- | src/output/column.rs | 38 | ||||
-rw-r--r-- | src/output/details.rs | 286 | ||||
-rw-r--r-- | src/output/grid.rs | 11 | ||||
-rw-r--r-- | src/output/grid_details.rs | 34 | ||||
-rw-r--r-- | src/output/lines.rs | 8 | ||||
-rw-r--r-- | src/output/mod.rs | 68 |
14 files changed, 461 insertions, 269 deletions
@@ -2,13 +2,13 @@ name = "exa" version = "0.4.0" dependencies = [ - "ansi_term 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ansi_term 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "datetime 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "git2 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "locale 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "natord 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", @@ -38,7 +38,7 @@ dependencies = [ [[package]] name = "ansi_term" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -92,7 +92,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "libgit2-sys 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "url 0.2.38 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -118,7 +118,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -127,10 +127,10 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cmake 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "libssh2-sys 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", "libz-sys 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -148,9 +148,9 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cmake 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "libz-sys 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -162,7 +162,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "gcc 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -185,7 +185,7 @@ name = "memchr" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -208,7 +208,7 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -222,10 +222,10 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "libressl-pnacl-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -257,7 +257,7 @@ version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "advapi32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -346,7 +346,7 @@ name = "users" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -7,7 +7,7 @@ authors = [ "ogham@bsago.me" ] name = "exa" [dependencies] -ansi_term = "0.7.0" +ansi_term = "0.7.1" bitflags = "0.1" datetime = "0.4.1" getopts = "0.2.14" diff --git a/src/file.rs b/src/file.rs index 08c45bb..53265d7 100644 --- a/src/file.rs +++ b/src/file.rs @@ -7,8 +7,6 @@ use std::io::Result as IOResult; use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::path::{Component, Path, PathBuf}; -use unicode_width::UnicodeWidthStr; - use dir::Dir; use self::fields as f; @@ -180,22 +178,13 @@ impl<'dir> File<'dir> { path_prefix } - /// The Unicode 'display width' of the filename. - /// - /// This is related to the number of graphemes in the string: most - /// characters are 1 columns wide, but in some contexts, certain - /// characters are actually 2 columns wide. - pub fn file_name_width(&self) -> usize { - UnicodeWidthStr::width(&self.name[..]) - } - /// Assuming the current file is a symlink, follows the link and /// returns a File object from the path the link points to. /// /// 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/filetype.rs b/src/filetype.rs index ed2f369..b8427d4 100644 --- a/src/filetype.rs +++ b/src/filetype.rs @@ -1,31 +1,7 @@ -use ansi_term::Style; - use file::File; -use colours::Colours; - - -pub fn file_colour(colours: &Colours, file: &File) -> Style { - match file { - f if f.is_directory() => colours.filetypes.directory, - f if f.is_executable_file() => colours.filetypes.executable, - f if f.is_link() => colours.filetypes.symlink, - f if !f.is_file() => colours.filetypes.special, - f if f.is_immediate() => colours.filetypes.immediate, - f if f.is_image() => colours.filetypes.image, - f if f.is_video() => colours.filetypes.video, - f if f.is_music() => colours.filetypes.music, - f if f.is_lossless() => colours.filetypes.lossless, - f if f.is_crypto() => colours.filetypes.crypto, - f if f.is_document() => colours.filetypes.document, - f if f.is_compressed() => colours.filetypes.compressed, - f if f.is_temp() => colours.filetypes.temp, - f if f.is_compiled() => colours.filetypes.compiled, - _ => colours.filetypes.normal, - } -} -trait FileTypes { +pub trait FileTypes { fn is_immediate(&self) -> bool; fn is_image(&self) -> bool; fn is_video(&self) -> bool; diff --git a/src/main.rs b/src/main.rs index 1e9f76c..e290853 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,7 +26,6 @@ use dir::Dir; use file::File; use options::{Options, View}; -mod colours; mod dir; mod feature; mod file; @@ -135,8 +134,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/options.rs b/src/options.rs index adffef1..c9baa2e 100644 --- a/src/options.rs +++ b/src/options.rs @@ -7,10 +7,10 @@ use std::os::unix::fs::MetadataExt; use getopts; use natord; -use colours::Colours; use feature::xattr; use file::File; use output::{Grid, Details, GridDetails, Lines}; +use output::Colours; use output::column::{Columns, TimeTypes, SizeFormat}; use term::dimensions; diff --git a/src/output/cell.rs b/src/output/cell.rs new file mode 100644 index 0000000..d900f6d --- /dev/null +++ b/src/output/cell.rs @@ -0,0 +1,205 @@ +//! The `TextCell` type for the details and lines views. + +use std::ops::{Deref, DerefMut}; + +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. + pub width: DisplayWidth, +} + +impl Deref for TextCell { + type Target = TextCellContents; + + fn deref<'a>(&'a self) -> &'a Self::Target { + &self.contents + } +} + +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 { + let width = DisplayWidth::from(&*text); + + TextCell { + contents: vec![ style.paint(text) ].into(), + width: width, + } + } + + /// 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 { + let width = DisplayWidth::from(text); + + TextCell { + contents: vec![ style.paint(text) ].into(), + width: width, + } + } + + /// 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 { + contents: vec![ style.paint("-") ].into(), + width: DisplayWidth::from(1), + } + } + + /// 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.width) += count; + + let spaces: String = repeat(' ').take(count).collect(); + self.contents.0.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>, extra_width: usize) { + self.contents.0.push(string); + (*self.width) += extra_width; + } + + /// Adds all the contents of another `TextCell` to the end of this cell. + pub fn append(&mut self, other: TextCell) { + (*self.width) += *other.width; + self.contents.0.extend(other.contents.0); + } +} + + +// 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. +#[derive(PartialEq, Debug, Clone, Default)] +pub struct TextCellContents(Vec<ANSIString<'static>>); + +impl From<Vec<ANSIString<'static>>> for TextCellContents { + fn from(strings: Vec<ANSIString<'static>>) -> TextCellContents { + TextCellContents(strings) + } +} + +impl Deref for TextCellContents { + type Target = [ANSIString<'static>]; + + fn deref<'a>(&'a self) -> &'a Self::Target { + &*self.0 + } +} + +// No DerefMut implementation here -- it would be publicly accessible, and as +// the contents only get changed in this module, the mutators in the struct +// above can just access the value directly. + +impl TextCellContents { + + /// 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.0) + } +} + + +/// The Unicode “display width” of a string. +/// +/// This is related to the number of *graphemes* of a string, rather than the +/// number of *characters*, or *bytes*: although most characters are one +/// column wide, a few can be two columns wide, and this is important to note +/// when calculating widths for displaying tables in a terminal. +/// +/// This type is used to ensure that the width, rather than the length, is +/// used when constructing a `TextCell` -- it's too easy to write something +/// like `file_name.len()` and assume it will work! +/// +/// It has `From` impls that convert an input string or fixed with to values +/// of this type, and will `Deref` to the contained `usize` value. +#[derive(PartialEq, Debug, Clone, Copy, Default)] +pub struct DisplayWidth(usize); + +impl<'a> From<&'a str> for DisplayWidth { + fn from(input: &'a str) -> DisplayWidth { + DisplayWidth(UnicodeWidthStr::width(input)) + } +} + +impl From<usize> for DisplayWidth { + fn from(width: usize) -> DisplayWidth { + DisplayWidth(width) + } +} + +impl Deref for DisplayWidth { + type Target = usize; + + fn deref<'a>(&'a self) -> &'a Self::Target { + &self.0 + } +} + +impl DerefMut for DisplayWidth { + fn deref_mut<'a>(&'a mut self) -> &'a mut Self::Target { + &mut self.0 + } +}
\ No newline at end of file diff --git a/src/colours.rs b/src/output/colours.rs index 97ccd87..97ccd87 100644 --- a/src/colours.rs +++ b/src/output/colours.rs 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..a31816e 100644 --- a/src/output/details.rs +++ b/src/output/details.rs @@ -113,23 +113,15 @@ use std::error::Error; use std::io; +use std::iter::repeat; +use std::ops::Add; use std::path::PathBuf; use std::string::ToString; -use std::ops::Add; -use std::iter::repeat; - -use colours::Colours; -use dir::Dir; -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 ansi_term::{ANSIString, ANSIStrings, Style}; +use ansi_term::Style; -use datetime::local::{LocalDateTime, DatePiece}; use datetime::format::DateFormat; +use datetime::local::{LocalDateTime, DatePiece}; use datetime::zoned::TimeZone; use locale; @@ -137,6 +129,14 @@ use locale; use users::{OSUsers, Users}; use users::mock::MockUsers; +use dir::Dir; +use feature::xattr::{Attribute, FileAttributes}; +use file::fields as f; +use file::File; +use options::{FileFilter, RecurseOptions}; +use output::colours::Colours; +use output::column::{Alignment, Column, Columns, SizeFormat}; +use output::cell::{TextCell, DisplayWidth}; use super::filename; @@ -198,7 +198,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 +213,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 +249,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 +261,6 @@ impl Details { let egg = Egg { cells: cells, - name: name, xattrs: xattrs, errors: errors, dir: dir, @@ -280,17 +272,23 @@ 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 width = DisplayWidth::from(&*egg.file.name); + + let name = TextCell { + contents: filename(egg.file, &self.colours, true), + width: width, + }; 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. @@ -366,7 +361,7 @@ impl Row { /// not, returns 0. fn column_width(&self, index: usize) -> usize { match self.cells { - Some(ref cells) => cells[index].length, + Some(ref cells) => *cells[index].width, None => 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,28 @@ 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 { + let width = DisplayWidth::from(&*file.name); + + TextCell { + contents: filename(file, &self.colours, links), + width: width, + } + } + + 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 +481,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 +505,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 +522,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 +536,80 @@ 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(), + // As these are all ASCII characters, we can guarantee that they’re + // all going to be one character wide, and don’t need to compute the + // cell’s display width. + let width = DisplayWidth::from(chars.len()); + + TextCell { + contents: chars.into(), + width: width, } } - 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) }; + + // The numbers and symbols are guaranteed to be written in ASCII, so + // we can skip the display width calculation. + let width = DisplayWidth::from(number.len() + symbol.len()); + + TextCell { + width: width, + contents: vec![ + self.colours.size.numbers.paint(number), + self.colours.size.unit.paint(symbol), + ].into(), } } #[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)); |