mirror of
https://github.com/Llewellynvdm/exa.git
synced 2024-11-11 07:10:56 +00:00
Merge branch 'cellular-regeneration' into develop
This commit is contained in:
commit
fb22c7456d
32
Cargo.lock
generated
32
Cargo.lock
generated
@ -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"
|
||||
|
13
src/file.rs
13
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()),
|
||||
|
@ -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;
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
205
src/output/cell.rs
Normal file
205
src/output/cell.rs
Normal file
@ -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
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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::Style;
|
||||
|
||||
use ansi_term::{ANSIString, ANSIStrings, 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));
|
||||
|
||||
let datestamp = if date.year() == self.current_year {
|
||||
@ -598,60 +619,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 {
|
||||
width: DisplayWidth::from(2),
|
||||
contents: vec![
|
||||
git_char(git.staged),
|
||||
git_char(git.unstaged)
|
||||
].into(),
|
||||
}
|
||||
}
|
||||
|
||||
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 +685,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.width;
|
||||
|
||||
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 +704,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 +721,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 +729,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 +786,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 +826,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 +839,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 +850,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 +860,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 +870,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 +889,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 +902,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 +917,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 +932,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 +942,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));
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
use colours::Colours;
|
||||
use file::File;
|
||||
use filetype::file_colour;
|
||||
|
||||
use term_grid as grid;
|
||||
|
||||
use file::File;
|
||||
use output::DisplayWidth;
|
||||
use output::colours::Colours;
|
||||
use super::file_colour;
|
||||
|
||||
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
pub struct Grid {
|
||||
@ -27,7 +28,7 @@ impl Grid {
|
||||
for file in files.iter() {
|
||||
grid.add(grid::Cell {
|
||||
contents: file_colour(&self.colours, file).paint(&*file.name).to_string(),
|
||||
width: file.file_name_width(),
|
||||
width: *DisplayWidth::from(&*file.name),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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,8 +105,8 @@ impl GridDetails {
|
||||
for column in columns.iter() {
|
||||
if row < column.len() {
|
||||
let cell = grid::Cell {
|
||||
contents: column[row].text.clone(),
|
||||
width: column[row].length,
|
||||
contents: ANSIStrings(&column[row].contents).to_string(),
|
||||
width: *column[row].width,
|
||||
};
|
||||
|
||||
grid.add(cell);
|
||||
@ -110,8 +118,8 @@ impl GridDetails {
|
||||
for column in columns.iter() {
|
||||
for cell in column.iter() {
|
||||
let cell = grid::Cell {
|
||||
contents: cell.text.clone(),
|
||||
width: cell.length,
|
||||
contents: ANSIStrings(&cell.contents).to_string(),
|
||||
width: *cell.width,
|
||||
};
|
||||
|
||||
grid.add(cell);
|
||||
|
@ -1,7 +1,9 @@
|
||||
use colours::Colours;
|
||||
use ansi_term::ANSIStrings;
|
||||
|
||||
use file::File;
|
||||
|
||||
use super::filename;
|
||||
use super::colours::Colours;
|
||||
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
@ -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, &self.colours, true));
|
||||
println!("{}", ANSIStrings(&filename(file, &self.colours, true)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,42 +1,72 @@
|
||||
use ansi_term::ANSIStrings;
|
||||
use ansi_term::Style;
|
||||
|
||||
use colours::Colours;
|
||||
use file::File;
|
||||
use filetype::file_colour;
|
||||
|
||||
pub use self::cell::{TextCell, TextCellContents, DisplayWidth};
|
||||
pub use self::colours::Colours;
|
||||
pub use self::details::Details;
|
||||
pub use self::grid_details::GridDetails;
|
||||
pub use self::grid::Grid;
|
||||
pub use self::lines::Lines;
|
||||
pub use self::grid_details::GridDetails;
|
||||
|
||||
mod grid;
|
||||
pub mod details;
|
||||
mod lines;
|
||||
mod grid_details;
|
||||
pub mod column;
|
||||
mod cell;
|
||||
mod colours;
|
||||
|
||||
|
||||
pub fn filename(file: &File, colours: &Colours, links: bool) -> String {
|
||||
pub fn filename(file: File, colours: &Colours, links: bool) -> TextCellContents {
|
||||
if links && file.is_link() {
|
||||
symlink_filename(file, colours)
|
||||
}
|
||||
else {
|
||||
let style = file_colour(colours, file);
|
||||
style.paint(&*file.name).to_string()
|
||||
vec![
|
||||
file_colour(colours, &file).paint(file.name)
|
||||
].into()
|
||||
}
|
||||
}
|
||||
|
||||
fn symlink_filename(file: &File, colours: &Colours) -> String {
|
||||
fn symlink_filename(file: File, colours: &Colours) -> TextCellContents {
|
||||
match file.link_target() {
|
||||
Ok(target) => format!("{} {} {}",
|
||||
file_colour(colours, file).paint(&*file.name),
|
||||
colours.punctuation.paint("->"),
|
||||
ANSIStrings(&[ colours.symlink_path.paint(target.path_prefix()),
|
||||
file_colour(colours, &target).paint(target.name) ])),
|
||||
Ok(target) => vec![
|
||||
file_colour(colours, &file).paint(file.name),
|
||||
Style::default().paint(" "),
|
||||
colours.punctuation.paint("->"),
|
||||
Style::default().paint(" "),
|
||||
colours.symlink_path.paint(target.path_prefix()),
|
||||
file_colour(colours, &target).paint(target.name)
|
||||
].into(),
|
||||
|
||||
Err(filename) => format!("{} {} {}",
|
||||
file_colour(colours, file).paint(&*file.name),
|
||||
colours.broken_arrow.paint("->"),
|
||||
colours.broken_filename.paint(filename)),
|
||||
Err(filename) => vec![
|
||||
file_colour(colours, &file).paint(file.name),
|
||||
Style::default().paint(" "),
|
||||
colours.broken_arrow.paint("->"),
|
||||
Style::default().paint(" "),
|
||||
colours.broken_filename.paint(filename),
|
||||
].into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_colour(colours: &Colours, file: &File) -> Style {
|
||||
use filetype::FileTypes;
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user