summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBenjamin Sago <ogham@bsago.me>2015-12-22 12:36:32 +1100
committerBenjamin Sago <ogham@bsago.me>2015-12-22 12:36:32 +1100
commitfb22c7456de50a2dcd5c2c4b975ba5ce670cc042 (patch)
treec0398b21861dc6c6e3665937dfed5b4e54d8b05a
parent95c0d63045875a0c41a124b1dc6ed7f738163316 (diff)
parent2e15b812496f9f1cc92da22874cfab3239170ee5 (diff)
Merge branch 'cellular-regeneration' into develop
-rw-r--r--Cargo.lock32
-rw-r--r--Cargo.toml2
-rw-r--r--src/file.rs13
-rw-r--r--src/filetype.rs26
-rw-r--r--src/main.rs5
-rw-r--r--src/options.rs2
-rw-r--r--src/output/cell.rs205
-rw-r--r--src/output/colours.rs (renamed from src/colours.rs)0
-rw-r--r--src/output/column.rs38
-rw-r--r--src/output/details.rs286
-rw-r--r--src/output/grid.rs11
-rw-r--r--src/output/grid_details.rs34
-rw-r--r--src/output/lines.rs8
-rw-r--r--src/output/mod.rs68
14 files changed, 461 insertions, 269 deletions
diff --git a/Cargo.lock b/Cargo.lock
index ceafcad..4b22ccf 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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]]
diff --git a/Cargo.toml b/Cargo.toml
index 01ef139..f328769 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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));