From da49b80c3525db7f0a0aae0fe2ac3e0a9a574cd3 Mon Sep 17 00:00:00 2001 From: Ben S Date: Sat, 9 May 2015 16:10:26 +0100 Subject: [PATCH 1/7] Clean up filetype-detecting code --- src/file.rs | 21 +++-- src/filetype.rs | 207 ++++++++++++++++++++++++------------------------ 2 files changed, 117 insertions(+), 111 deletions(-) diff --git a/src/file.rs b/src/file.rs index 4069a73..bae78f6 100644 --- a/src/file.rs +++ b/src/file.rs @@ -25,7 +25,7 @@ use datetime::format::{DateFormat}; use column::{Column, Cell}; use column::Column::*; use dir::Dir; -use filetype::HasType; +use filetype::file_type; use options::{SizeFormat, TimeType}; use output::details::UserLocale; use feature::Attribute; @@ -112,12 +112,6 @@ impl<'a> File<'a> { self.name.starts_with(".") } - /// Whether this file is a temporary file or not. - pub fn is_tmpfile(&self) -> bool { - let name = &self.name; - name.ends_with("~") || (name.starts_with("#") && name.ends_with("#")) - } - /// Get the data for a column, formatted as a coloured string. pub fn display(&self, column: &Column, users_cache: &mut U, locale: &UserLocale) -> Cell { match *column { @@ -207,7 +201,7 @@ impl<'a> File<'a> { /// The `ansi_term::Style` that this file's name should be painted. pub fn file_colour(&self) -> Style { - self.get_type().style() + file_type(&self).style() } /// The Unicode 'display width' of the filename. @@ -472,6 +466,17 @@ impl<'a> File<'a> { } } + pub fn extension_is_one_of(&self, choices: &[&str]) -> bool { + match self.ext { + Some(ref ext) => choices.contains(&&ext[..]), + None => false, + } + } + + pub fn name_is_one_of(&self, choices: &[&str]) -> bool { + choices.contains(&&self.name[..]) + } + fn git_status(&self) -> Cell { let status = match self.dir { None => GREY.paint("--").to_string(), diff --git a/src/filetype.rs b/src/filetype.rs index cd6a230..a1b39c3 100644 --- a/src/filetype.rs +++ b/src/filetype.rs @@ -5,54 +5,13 @@ use ansi_term::Style; use ansi_term::Style::Plain; use ansi_term::Colour::{Red, Green, Yellow, Blue, Cyan, Fixed}; -#[derive(PartialEq, Debug)] +#[derive(Debug)] pub enum FileType { Normal, Directory, Executable, Immediate, Compiled, Symlink, Special, Image, Video, Music, Lossless, Compressed, Document, Temp, Crypto, } -static IMAGE_TYPES: &'static [&'static str] = &[ - "png", "jpeg", "jpg", "gif", "bmp", "tiff", "tif", - "ppm", "pgm", "pbm", "pnm", "webp", "raw", "arw", - "svg", "stl", "eps", "dvi", "ps", "cbr", - "cbz", "xpm", "ico" ]; - -static VIDEO_TYPES: &'static [&'static str] = &[ - "avi", "flv", "m2v", "mkv", "mov", "mp4", "mpeg", - "mpg", "ogm", "ogv", "vob", "wmv" ]; - -static MUSIC_TYPES: &'static [&'static str] = &[ - "aac", "m4a", "mp3", "ogg", "wma" ]; - -static MUSIC_LOSSLESS: &'static [&'static str] = &[ - "alac", "ape", "flac", "wav" ]; - -static COMPRESSED_TYPES: &'static [&'static str] = &[ - "zip", "tar", "Z", "gz", "bz2", "a", "ar", "7z", - "iso", "dmg", "tc", "rar", "par" ]; - -static DOCUMENT_TYPES: &'static [&'static str] = &[ - "djvu", "doc", "docx", "dvi", "eml", "eps", "fotd", - "odp", "odt", "pdf", "ppt", "pptx", "rtf", - "xls", "xlsx" ]; - -static TEMP_TYPES: &'static [&'static str] = &[ - "tmp", "swp", "swo", "swn", "bak" ]; - -static CRYPTO_TYPES: &'static [&'static str] = &[ - "asc", "gpg", "sig", "signature", "pgp" ]; - -static COMPILED_TYPES: &'static [&'static str] = &[ - "class", "elc", "hi", "o", "pyc" ]; - -static BUILD_TYPES: &'static [&'static str] = &[ - "Makefile", "Cargo.toml", "SConstruct", "CMakeLists.txt", - "build.gradle", "Rakefile", "Gruntfile.js", - "Gruntfile.coffee" ]; - impl FileType { - - /// Get the `ansi_term::Style` that a file of this type should use. pub fn style(&self) -> Style { match *self { Normal => Plain, @@ -74,72 +33,115 @@ impl FileType { } } -pub trait HasType { - /// For a given file, find out what type it has. - fn get_type(&self) -> FileType; +pub fn file_type(file: &File) -> FileType { + match file { + f if f.is_directory() => Directory, + f if f.is_executable_file() => Executable, + f if f.is_link() => Symlink, + f if !f.is_file() => Special, + f if f.is_immediate() => Immediate, + f if f.is_image() => Image, + f if f.is_video() => Video, + f if f.is_music() => Music, + f if f.is_lossless() => Lossless, + f if f.is_crypto() => Crypto, + f if f.is_document() => Document, + f if f.is_compressed() => Compressed, + f if f.is_temp() => Temp, + f if f.is_compiled() => Compiled, + _ => Normal, + } } -impl<'a> HasType for File<'a> { - fn get_type(&self) -> FileType { +trait FileTypes { + fn is_immediate(&self) -> bool; + fn is_image(&self) -> bool; + fn is_video(&self) -> bool; + fn is_music(&self) -> bool; + fn is_lossless(&self) -> bool; + fn is_crypto(&self) -> bool; + fn is_document(&self) -> bool; + fn is_compressed(&self) -> bool; + fn is_temp(&self) -> bool; + fn is_compiled(&self) -> bool; +} - if self.is_directory() { - return Directory; - } - else if self.is_executable_file() { - return Executable; - } - else if self.is_link() { - return Symlink; - } - else if !self.is_file() { - return Special; - } +impl<'_> FileTypes for File<'_> { + fn is_immediate(&self) -> bool { + self.name.starts_with("README") || self.name_is_one_of( &[ + "Makefile", "Cargo.toml", "SConstruct", "CMakeLists.txt", + "build.gradle", "Rakefile", "Gruntfile.js", + "Gruntfile.coffee", + ]) + } - if self.name.starts_with("README") || BUILD_TYPES.contains(&&self.name[..]) { - return Immediate; - } - else if let Some(ref ext) = self.ext { - if IMAGE_TYPES.contains(&&ext[..]) { - return Image; - } - else if VIDEO_TYPES.contains(&&ext[..]) { - return Video; - } - else if MUSIC_TYPES.contains(&&ext[..]) { - return Music; - } - else if MUSIC_LOSSLESS.contains(&&ext[..]) { - return Lossless; - } - else if CRYPTO_TYPES.contains(&&ext[..]) { - return Crypto; - } - else if DOCUMENT_TYPES.contains(&&ext[..]) { - return Document; - } - else if COMPRESSED_TYPES.contains(&&ext[..]) { - return Compressed; - } - else if self.is_tmpfile() || TEMP_TYPES.contains(&&ext[..]) { - return Temp; - } + fn is_image(&self) -> bool { + self.extension_is_one_of( &[ + "png", "jpeg", "jpg", "gif", "bmp", "tiff", "tif", + "ppm", "pgm", "pbm", "pnm", "webp", "raw", "arw", + "svg", "stl", "eps", "dvi", "ps", "cbr", + "cbz", "xpm", "ico", + ]) + } - let source_files = self.get_source_files(); - if source_files.is_empty() { - return Normal; - } - else if let Some(dir) = self.dir { - if source_files.iter().any(|path| dir.contains(path)) { - return Temp; - } - } - else if COMPILED_TYPES.contains(&&ext[..]) { - return Compiled; - } - } + fn is_video(&self) -> bool { + self.extension_is_one_of( &[ + "avi", "flv", "m2v", "mkv", "mov", "mp4", "mpeg", + "mpg", "ogm", "ogv", "vob", "wmv", + ]) + } - return Normal; // no filetype - } + fn is_music(&self) -> bool { + self.extension_is_one_of( &[ + "aac", "m4a", "mp3", "ogg", "wma", + ]) + } + + fn is_lossless(&self) -> bool { + self.extension_is_one_of( &[ + "alac", "ape", "flac", "wav", + ]) + } + + fn is_crypto(&self) -> bool { + self.extension_is_one_of( &[ + "zip", "tar", "Z", "gz", "bz2", "a", "ar", "7z", + "iso", "dmg", "tc", "rar", "par", + ]) + } + + fn is_document(&self) -> bool { + self.extension_is_one_of( &[ + "djvu", "doc", "docx", "dvi", "eml", "eps", "fotd", + "odp", "odt", "pdf", "ppt", "pptx", "rtf", + "xls", "xlsx", + ]) + } + + fn is_compressed(&self) -> bool { + self.extension_is_one_of( &[ + "zip", "tar", "Z", "gz", "bz2", "a", "ar", "7z", + "iso", "dmg", "tc", "rar", "par" + ]) + } + + fn is_temp(&self) -> bool { + self.name.ends_with("~") + || (self.name.starts_with("#") && self.name.ends_with("#")) + || self.extension_is_one_of( &[ "tmp", "swp", "swo", "swn", "bak" ]) + } + + fn is_compiled(&self) -> bool { + if self.extension_is_one_of( &[ "class", "elc", "hi", "o", "pyc" ]) { + true + } + else if let Some(dir) = self.dir { + self.get_source_files().iter().any(|path| dir.contains(path)) + } + else { + false + } + } } #[cfg(broken_test)] @@ -170,5 +172,4 @@ mod test { let file = new_file(dummy_stat(), "/cargo.toml"); assert_eq!(FileType::Normal, file.get_type()) } - } From 36116a142095d61ab7f601cb6efda03d2cb8d749 Mon Sep 17 00:00:00 2001 From: Ben S Date: Sat, 9 May 2015 23:57:18 +0100 Subject: [PATCH 2/7] Add colours module, and disable them sometimes Colours are now disabled when output is not to a terminal. Fixes #53! This required some internal restructuring - colours are now in their own object that gets passed around everywhere it's needed. --- Cargo.lock | 30 ++++++++++---------- src/colours.rs | 52 ++++++++++++++++++++++++++++++++++ src/file.rs | 20 ++++++------- src/filetype.rs | 66 ++++++++++++------------------------------- src/main.rs | 4 +-- src/options.rs | 47 +++++++++++++++++++----------- src/output/details.rs | 9 ++++-- src/output/grid.rs | 9 ++++-- src/output/lines.rs | 14 +++++++-- src/output/mod.rs | 2 +- 10 files changed, 149 insertions(+), 104 deletions(-) create mode 100644 src/colours.rs diff --git a/Cargo.lock b/Cargo.lock index 41683d0..31db78f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,14 +2,14 @@ name = "exa" version = "0.2.0" dependencies = [ - "ansi_term 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ansi_term 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "datetime 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "git2 0.2.9 (git+https://github.com/alexcrichton/git2-rs.git)", "locale 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "natord 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "number_prefix 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "pad 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -18,7 +18,7 @@ dependencies = [ [[package]] name = "ansi_term" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -57,14 +57,14 @@ version = "0.2.9" source = "git+https://github.com/alexcrichton/git2-rs.git#3a5f0b5698c5203fa48e33094158990a2c53c979" dependencies = [ "bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "libgit2-sys 0.2.12 (git+https://github.com/alexcrichton/git2-rs.git)", "url 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "libc" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -72,7 +72,7 @@ name = "libgit2-sys" version = "0.2.12" source = "git+https://github.com/alexcrichton/git2-rs.git#3a5f0b5698c5203fa48e33094158990a2c53c979" dependencies = [ - "libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "libssh2-sys 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", "libz-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -92,7 +92,7 @@ name = "libssh2-sys" version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "libz-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -103,7 +103,7 @@ name = "libz-sys" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -112,7 +112,7 @@ name = "locale" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "num 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -121,7 +121,7 @@ name = "log" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -145,10 +145,10 @@ dependencies = [ [[package]] name = "num_cpus" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -165,7 +165,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "gcc 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "libressl-pnacl-sys 2.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -196,7 +196,7 @@ name = "rand" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -244,6 +244,6 @@ name = "users" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/src/colours.rs b/src/colours.rs new file mode 100644 index 0000000..46397a0 --- /dev/null +++ b/src/colours.rs @@ -0,0 +1,52 @@ +use ansi_term::Style; +use ansi_term::Style::Plain; +use ansi_term::Colour::{Red, Green, Yellow, Blue, Cyan, Fixed}; + +use file::GREY; + +use std::default::Default; + +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct Colours { + pub normal: Style, + pub directory: Style, + pub symlink: Style, + pub special: Style, + pub executable: Style, + pub image: Style, + pub video: Style, + pub music: Style, + pub lossless: Style, + pub crypto: Style, + pub document: Style, + pub compressed: Style, + pub temp: Style, + pub immediate: Style, + pub compiled: Style, +} + +impl Colours { + pub fn plain() -> Colours { + Colours::default() + } + + pub fn colourful() -> Colours { + Colours { + normal: Plain, + directory: Blue.bold(), + symlink: Cyan.normal(), + special: Yellow.normal(), + executable: Green.bold(), + image: Fixed(133).normal(), + video: Fixed(135).normal(), + music: Fixed(92).normal(), + lossless: Fixed(93).normal(), + crypto: Fixed(109).normal(), + document: Fixed(105).normal(), + compressed: Red.normal(), + temp: GREY.normal(), + immediate: Yellow.bold().underline(), + compiled: Fixed(137).normal(), + } + } +} diff --git a/src/file.rs b/src/file.rs index bae78f6..8daffa3 100644 --- a/src/file.rs +++ b/src/file.rs @@ -22,10 +22,11 @@ use number_prefix::{binary_prefix, decimal_prefix, Prefixed, Standalone, PrefixN use datetime::local::{LocalDateTime, DatePiece}; use datetime::format::{DateFormat}; +use colours::Colours; use column::{Column, Cell}; use column::Column::*; use dir::Dir; -use filetype::file_type; +use filetype::file_colour; use options::{SizeFormat, TimeType}; use output::details::UserLocale; use feature::Attribute; @@ -132,12 +133,12 @@ impl<'a> File<'a> { /// /// It consists of the file name coloured in the appropriate style, /// with special formatting for a symlink. - pub fn file_name_view(&self) -> String { + pub fn file_name_view(&self, colours: &Colours) -> String { if self.is_link() { - self.symlink_file_name_view() + self.symlink_file_name_view(colours) } else { - self.file_colour().paint(&*self.name).to_string() + file_colour(colours, self).paint(&*self.name).to_string() } } @@ -149,9 +150,9 @@ impl<'a> File<'a> { /// an error, highlight the target and arrow in red. The error would /// be shown out of context, and it's almost always because the /// target doesn't exist. - fn symlink_file_name_view(&self) -> String { + fn symlink_file_name_view(&self, colours: &Colours) -> String { let name = &*self.name; - let style = self.file_colour(); + let style = file_colour(colours, self); if let Ok(path) = fs::read_link(&self.path) { let target_path = match self.dir { @@ -186,7 +187,7 @@ impl<'a> File<'a> { style.paint(name), GREY.paint("=>"), ANSIStrings(&[ Cyan.paint(&path_prefix), - file.file_colour().paint(&file.name) ])) + file_colour(colours, &file).paint(&file.name) ])) }, Err(filename) => format!("{} {} {}", style.paint(name), @@ -199,11 +200,6 @@ impl<'a> File<'a> { } } - /// The `ansi_term::Style` that this file's name should be painted. - pub fn file_colour(&self) -> Style { - file_type(&self).style() - } - /// The Unicode 'display width' of the filename. /// /// This is related to the number of graphemes in the string: most diff --git a/src/filetype.rs b/src/filetype.rs index a1b39c3..dadc415 100644 --- a/src/filetype.rs +++ b/src/filetype.rs @@ -1,55 +1,25 @@ -use file::{File, GREY}; -use self::FileType::*; - use ansi_term::Style; -use ansi_term::Style::Plain; -use ansi_term::Colour::{Red, Green, Yellow, Blue, Cyan, Fixed}; -#[derive(Debug)] -pub enum FileType { - Normal, Directory, Executable, Immediate, Compiled, Symlink, Special, - Image, Video, Music, Lossless, Compressed, Document, Temp, Crypto, -} +use file::File; +use colours::Colours; -impl FileType { - pub fn style(&self) -> Style { - match *self { - Normal => Plain, - Directory => Blue.bold(), - Symlink => Cyan.normal(), - Special => Yellow.normal(), - Executable => Green.bold(), - Image => Fixed(133).normal(), - Video => Fixed(135).normal(), - Music => Fixed(92).normal(), - Lossless => Fixed(93).normal(), - Crypto => Fixed(109).normal(), - Document => Fixed(105).normal(), - Compressed => Red.normal(), - Temp => GREY.normal(), - Immediate => Yellow.bold().underline(), - Compiled => Fixed(137).normal(), - } - } -} - -pub fn file_type(file: &File) -> FileType { +pub fn file_colour(colours: &Colours, file: &File) -> Style { match file { - f if f.is_directory() => Directory, - f if f.is_executable_file() => Executable, - f if f.is_link() => Symlink, - f if !f.is_file() => Special, - f if f.is_immediate() => Immediate, - f if f.is_image() => Image, - f if f.is_video() => Video, - f if f.is_music() => Music, - f if f.is_lossless() => Lossless, - f if f.is_crypto() => Crypto, - f if f.is_document() => Document, - f if f.is_compressed() => Compressed, - f if f.is_temp() => Temp, - f if f.is_compiled() => Compiled, - _ => Normal, + f if f.is_directory() => colours.directory, + f if f.is_executable_file() => colours.executable, + f if f.is_link() => colours.symlink, + f if !f.is_file() => colours.special, + f if f.is_immediate() => colours.immediate, + f if f.is_image() => colours.image, + f if f.is_video() => colours.video, + f if f.is_music() => colours.music, + f if f.is_lossless() => colours.lossless, + f if f.is_crypto() => colours.crypto, + f if f.is_document() => colours.document, + f if f.is_compressed() => colours.compressed, + f if f.is_temp() => colours.temp, + f if f.is_compiled() => colours.compiled, + _ => colours.normal, } } diff --git a/src/main.rs b/src/main.rs index e8ee7e3..7865457 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,8 +25,8 @@ use std::thread; use dir::Dir; use file::File; use options::{Options, View}; -use output::lines_view; +mod colours; mod column; mod dir; mod feature; @@ -189,7 +189,7 @@ impl<'a> Exa<'a> { match self.options.view { View::Grid(g) => g.view(files), View::Details(d) => d.view(dir, files), - View::Lines => lines_view(files), + View::Lines(l) => l.view(files), } } } diff --git a/src/options.rs b/src/options.rs index 8143e71..bff0d12 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,9 +1,10 @@ +use colours::Colours; use dir::Dir; use file::File; use column::Column; use column::Column::*; use feature::Attribute; -use output::{Grid, Details}; +use output::{Grid, Details, Lines}; use term::dimensions; use std::cmp::Ordering; @@ -38,7 +39,7 @@ pub struct FileFilter { #[derive(PartialEq, Debug, Copy, Clone)] pub enum View { Details(Details), - Lines, + Lines(Lines), Grid(Grid), } @@ -257,6 +258,7 @@ impl View { header: matches.opt_present("header"), recurse: dir_action.recurse_options().map(|o| (o, filter)), xattr: Attribute::feature_implemented() && matches.opt_present("extended"), + colours: if dimensions().is_some() { Colours::colourful() } else { Colours::plain() }, }; Ok(View::Details(details)) @@ -298,33 +300,44 @@ impl View { else if Attribute::feature_implemented() && matches.opt_present("extended") { Err(Misfire::Useless("extended", false, "long")) } - else if matches.opt_present("oneline") { - if matches.opt_present("across") { - Err(Misfire::Useless("across", true, "oneline")) + else if let Some((width, _)) = dimensions() { + if matches.opt_present("oneline") { + if matches.opt_present("across") { + Err(Misfire::Useless("across", true, "oneline")) + } + else { + let lines = Lines { + colours: Colours::colourful(), + }; + + Ok(View::Lines(lines)) + } } else { - Ok(View::Lines) - } - } - else { - if let Some((width, _)) = dimensions() { let grid = Grid { across: matches.opt_present("across"), - console_width: width + console_width: width, + colours: Colours::colourful(), }; Ok(View::Grid(grid)) } - else { - // If the terminal width couldn't be matched for some reason, such - // as the program's stdout being connected to a file, then - // fallback to the lines view. - Ok(View::Lines) - } + } + else { + // If the terminal width couldn't be matched for some reason, such + // as the program's stdout being connected to a file, then + // fallback to the lines view. + let lines = Lines { + colours: Colours::plain(), + }; + + Ok(View::Lines(lines)) } } } + + #[derive(PartialEq, Debug, Copy, Clone)] pub enum SizeFormat { DecimalBytes, diff --git a/src/output/details.rs b/src/output/details.rs index 5e69081..3870ace 100644 --- a/src/output/details.rs +++ b/src/output/details.rs @@ -1,3 +1,4 @@ +use colours::Colours; use column::{Alignment, Column, Cell}; use feature::Attribute; use dir::Dir; @@ -37,6 +38,8 @@ pub struct Details { /// Whether to show each file's extended attributes. pub xattr: bool, + + pub colours: Colours, } impl Details { @@ -55,7 +58,7 @@ impl Details { /// is present. fn add_files_to_table(&self, table: &mut Table, src: &[File], depth: usize) { for (index, file) in src.iter().enumerate() { - table.add_file(file, depth, index == src.len() - 1); + table.add_file(file, depth, index == src.len() - 1, &self.colours); // There are two types of recursion that exa supports: a tree // view, which is dealt with here, and multiple listings, which is @@ -150,11 +153,11 @@ impl Table { } /// Get the cells for the given file, and add the result to the table. - fn add_file(&mut self, file: &File, depth: usize, last: bool) { + fn add_file(&mut self, file: &File, depth: usize, last: bool, colours: &Colours) { let row = Row { depth: depth, cells: self.cells_for_file(file), - name: file.file_name_view(), + name: file.file_name_view(colours), last: last, attrs: file.xattrs.clone(), children: file.this.is_some(), diff --git a/src/output/grid.rs b/src/output/grid.rs index f87e916..0c2e950 100644 --- a/src/output/grid.rs +++ b/src/output/grid.rs @@ -1,6 +1,8 @@ +use colours::Colours; use column::Alignment::Left; use file::File; -use super::lines::lines_view; +use filetype::file_colour; +use super::lines::Lines; use std::cmp::max; use std::iter::repeat; @@ -9,6 +11,7 @@ use std::iter::repeat; pub struct Grid { pub across: bool, pub console_width: usize, + pub colours: Colours, } impl Grid { @@ -81,7 +84,7 @@ impl Grid { } let ref file = files[num]; - let styled_name = file.file_colour().paint(&file.name).to_string(); + let styled_name = file_colour(&self.colours, file).paint(&file.name).to_string(); if x == widths.len() - 1 { // The final column doesn't need to have trailing spaces print!("{}", styled_name); @@ -96,7 +99,7 @@ impl Grid { } else { // Drop down to lines view if the file names are too big for a grid - lines_view(files); + Lines { colours: self.colours }.view(files); } } } diff --git a/src/output/lines.rs b/src/output/lines.rs index 6649c29..b61494f 100644 --- a/src/output/lines.rs +++ b/src/output/lines.rs @@ -1,8 +1,16 @@ +use colours::Colours; use file::File; +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Lines { + pub colours: Colours, +} + /// The lines view literally just displays each file, line-by-line. -pub fn lines_view(files: &[File]) { - for file in files { - println!("{}", file.file_name_view()); +impl Lines { + pub fn view(&self, files: &[File]) { + for file in files { + println!("{}", file.file_name_view(&self.colours)); + } } } diff --git a/src/output/mod.rs b/src/output/mod.rs index 22b8726..8606d23 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -4,4 +4,4 @@ mod lines; pub use self::grid::Grid; pub use self::details::Details; -pub use self::lines::lines_view; +pub use self::lines::Lines; From 89dd5c12395230769100975b9ea582039b386dfd Mon Sep 17 00:00:00 2001 From: Ben S Date: Sun, 10 May 2015 00:13:00 +0100 Subject: [PATCH 3/7] Straighten quotes --- src/file.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/file.rs b/src/file.rs index 8daffa3..2f60a1d 100644 --- a/src/file.rs +++ b/src/file.rs @@ -382,8 +382,8 @@ impl<'a> File<'a> { /// Marker indicating that the file contains extended attributes /// - /// Returns "@" or " ” depending on wheter the file contains an extented - /// attribute or not. Also returns “ ” in case the attributes cannot be read + /// Returns "@" or " " depending on wheter the file contains an extented + /// attribute or not. Also returns " " in case the attributes cannot be read /// for some reason. fn attribute_marker(&self) -> ANSIString { if self.xattrs.len() > 0 { Plain.paint("@") } else { Plain.paint(" ") } From 627bca98ef62891da92302d6287cdf2a5e43a5b6 Mon Sep 17 00:00:00 2001 From: Ben S Date: Sun, 10 May 2015 00:22:58 +0100 Subject: [PATCH 4/7] Move file type colours to their own struct --- src/colours.rs | 37 ++++++++++++++++++++++--------------- src/filetype.rs | 30 +++++++++++++++--------------- 2 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/colours.rs b/src/colours.rs index 46397a0..b4dc1ac 100644 --- a/src/colours.rs +++ b/src/colours.rs @@ -8,6 +8,11 @@ use std::default::Default; #[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct Colours { + pub filetypes: FileTypes, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct FileTypes { pub normal: Style, pub directory: Style, pub symlink: Style, @@ -32,21 +37,23 @@ impl Colours { pub fn colourful() -> Colours { Colours { - normal: Plain, - directory: Blue.bold(), - symlink: Cyan.normal(), - special: Yellow.normal(), - executable: Green.bold(), - image: Fixed(133).normal(), - video: Fixed(135).normal(), - music: Fixed(92).normal(), - lossless: Fixed(93).normal(), - crypto: Fixed(109).normal(), - document: Fixed(105).normal(), - compressed: Red.normal(), - temp: GREY.normal(), - immediate: Yellow.bold().underline(), - compiled: Fixed(137).normal(), + filetypes: FileTypes { + normal: Plain, + directory: Blue.bold(), + symlink: Cyan.normal(), + special: Yellow.normal(), + executable: Green.bold(), + image: Fixed(133).normal(), + video: Fixed(135).normal(), + music: Fixed(92).normal(), + lossless: Fixed(93).normal(), + crypto: Fixed(109).normal(), + document: Fixed(105).normal(), + compressed: Red.normal(), + temp: GREY.normal(), + immediate: Yellow.bold().underline(), + compiled: Fixed(137).normal(), + } } } } diff --git a/src/filetype.rs b/src/filetype.rs index dadc415..a75f842 100644 --- a/src/filetype.rs +++ b/src/filetype.rs @@ -5,21 +5,21 @@ use colours::Colours; pub fn file_colour(colours: &Colours, file: &File) -> Style { match file { - f if f.is_directory() => colours.directory, - f if f.is_executable_file() => colours.executable, - f if f.is_link() => colours.symlink, - f if !f.is_file() => colours.special, - f if f.is_immediate() => colours.immediate, - f if f.is_image() => colours.image, - f if f.is_video() => colours.video, - f if f.is_music() => colours.music, - f if f.is_lossless() => colours.lossless, - f if f.is_crypto() => colours.crypto, - f if f.is_document() => colours.document, - f if f.is_compressed() => colours.compressed, - f if f.is_temp() => colours.temp, - f if f.is_compiled() => colours.compiled, - _ => colours.normal, + 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, } } From 5a37d1b6b16e72c0661d3a465ab1c56f458384d8 Mon Sep 17 00:00:00 2001 From: Ben S Date: Sun, 10 May 2015 12:50:20 +0100 Subject: [PATCH 5/7] Update most of the other columns to use colours --- src/colours.rs | 99 ++++++++++++++++++++++++++++++++++++-- src/file.rs | 108 +++++++++++++++++++++--------------------- src/output/details.rs | 19 ++++---- 3 files changed, 161 insertions(+), 65 deletions(-) diff --git a/src/colours.rs b/src/colours.rs index b4dc1ac..c0619b9 100644 --- a/src/colours.rs +++ b/src/colours.rs @@ -1,6 +1,6 @@ use ansi_term::Style; use ansi_term::Style::Plain; -use ansi_term::Colour::{Red, Green, Yellow, Blue, Cyan, Fixed}; +use ansi_term::Colour::{Red, Green, Yellow, Blue, Cyan, Purple, Fixed}; use file::GREY; @@ -8,7 +8,21 @@ use std::default::Default; #[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct Colours { - pub filetypes: FileTypes, + pub filetypes: FileTypes, + pub perms: Permissions, + pub size: Size, + pub users: Users, + pub links: Links, + + pub punctuation: Style, + pub date: Style, + pub inode: Style, + pub blocks: Style, + pub header: Style, + + pub symlink_path: Style, + pub broken_arrow: Style, + pub broken_filename: Style, } #[derive(Clone, Copy, Debug, Default, PartialEq)] @@ -30,6 +44,44 @@ pub struct FileTypes { pub compiled: Style, } +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct Permissions { + pub user_read: Style, + pub user_write: Style, + pub user_execute_file: Style, + pub user_execute_other: Style, + + pub group_read: Style, + pub group_write: Style, + pub group_execute: Style, + + pub other_read: Style, + pub other_write: Style, + pub other_execute: Style, + + pub attribute: Style, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct Size { + pub numbers: Style, + pub unit: Style, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct Users { + pub user_you: Style, + pub user_someone_else: Style, + pub group_yours: Style, + pub group_not_yours: Style, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct Links { + pub normal: Style, + pub multi_link_file: Style, +} + impl Colours { pub fn plain() -> Colours { Colours::default() @@ -53,7 +105,48 @@ impl Colours { temp: GREY.normal(), immediate: Yellow.bold().underline(), compiled: Fixed(137).normal(), - } + }, + + perms: Permissions { + user_read: Yellow.bold(), + user_write: Red.bold(), + user_execute_file: Green.bold().underline(), + user_execute_other: Green.bold(), + group_read: Yellow.normal(), + group_write: Red.normal(), + group_execute: Green.normal(), + other_read: Yellow.normal(), + other_write: Red.normal(), + other_execute: Green.normal(), + attribute: Plain, + }, + + size: Size { + numbers: Green.bold(), + unit: Green.normal(), + }, + + users: Users { + user_you: Yellow.bold(), + user_someone_else: Plain, + group_yours: Yellow.bold(), + group_not_yours: Plain, + }, + + links: Links { + normal: Red.bold(), + multi_link_file: Red.on(Yellow), + }, + + punctuation: Fixed(244).normal(), + date: Blue.normal(), + inode: Purple.normal(), + blocks: Cyan.normal(), + header: Plain.underline(), + + symlink_path: Cyan.normal(), + broken_arrow: Red.normal(), + broken_filename: Red.underline() } } } diff --git a/src/file.rs b/src/file.rs index 2f60a1d..d898707 100644 --- a/src/file.rs +++ b/src/file.rs @@ -9,7 +9,7 @@ use std::path::{Component, Path, PathBuf}; use ansi_term::{ANSIString, ANSIStrings, Colour, Style}; use ansi_term::Style::Plain; -use ansi_term::Colour::{Red, Green, Yellow, Blue, Purple, Cyan, Fixed}; +use ansi_term::Colour::Fixed; use users::Users; @@ -114,16 +114,16 @@ impl<'a> File<'a> { } /// Get the data for a column, formatted as a coloured string. - pub fn display(&self, column: &Column, users_cache: &mut U, locale: &UserLocale) -> Cell { + pub fn display(&self, column: &Column, colours: &Colours, users_cache: &mut U, locale: &UserLocale) -> Cell { match *column { - Permissions => self.permissions_string(), - FileSize(f) => self.file_size(f, &locale.numeric), - Timestamp(t, y) => self.timestamp(t, y, &locale.time), - HardLinks => self.hard_links(&locale.numeric), - Inode => self.inode(), - Blocks => self.blocks(&locale.numeric), - User => self.user(users_cache), - Group => self.group(users_cache), + Permissions => self.permissions_string(colours), + FileSize(f) => self.file_size(colours, f, &locale.numeric), + Timestamp(t, y) => self.timestamp(colours, t, y, &locale.time), + HardLinks => self.hard_links(colours, &locale.numeric), + Inode => self.inode(colours), + Blocks => self.blocks(colours, &locale.numeric), + User => self.user(colours, users_cache), + Group => self.group(colours, users_cache), GitStatus => self.git_status(), } } @@ -185,14 +185,14 @@ impl<'a> File<'a> { format!("{} {} {}", style.paint(name), - GREY.paint("=>"), - ANSIStrings(&[ Cyan.paint(&path_prefix), + colours.punctuation.paint("=>"), + ANSIStrings(&[ colours.symlink_path.paint(&path_prefix), file_colour(colours, &file).paint(&file.name) ])) }, Err(filename) => format!("{} {} {}", style.paint(name), - Red.paint("=>"), - Red.underline().paint(&filename)), + colours.broken_arrow.paint("=>"), + colours.broken_filename.paint(&filename)), } } else { @@ -236,8 +236,10 @@ impl<'a> File<'a> { } /// This file's number of hard links as a coloured string. - fn hard_links(&self, locale: &locale::Numeric) -> Cell { - let style = if self.has_multiple_links() { Red.on(Yellow) } else { Red.normal() }; + fn hard_links(&self, colours: &Colours, locale: &locale::Numeric) -> Cell { + let style = if self.has_multiple_links() { colours.links.multi_link_file } + else { colours.links.normal }; + Cell::paint(style, &locale.format_int(self.stat.as_raw().nlink())[..]) } @@ -251,18 +253,18 @@ impl<'a> File<'a> { } /// This file's inode as a coloured string. - fn inode(&self) -> Cell { + fn inode(&self, colours: &Colours) -> Cell { let inode = self.stat.as_raw().ino(); - Cell::paint(Purple.normal(), &inode.to_string()[..]) + Cell::paint(colours.inode, &inode.to_string()[..]) } /// This file's number of filesystem blocks (if available) as a coloured string. - fn blocks(&self, locale: &locale::Numeric) -> Cell { + fn blocks(&self, colours: &Colours, locale: &locale::Numeric) -> Cell { if self.is_file() || self.is_link() { - Cell::paint(Cyan.normal(), &locale.format_int(self.stat.as_raw().blocks())[..]) + Cell::paint(colours.blocks, &locale.format_int(self.stat.as_raw().blocks())[..]) } else { - Cell { text: GREY.paint("-").to_string(), length: 1 } + Cell { text: colours.punctuation.paint("-").to_string(), length: 1 } } } @@ -271,7 +273,7 @@ impl<'a> File<'a> { /// If the user is not present, then it formats the uid as a number /// instead. This usually happens when a user is deleted, but still owns /// files. - fn user(&self, users_cache: &mut U) -> Cell { + fn user(&self, colours: &Colours, users_cache: &mut U) -> Cell { let uid = self.stat.as_raw().uid(); let user_name = match users_cache.get_user_by_uid(uid) { @@ -279,23 +281,23 @@ impl<'a> File<'a> { None => uid.to_string(), }; - let style = if users_cache.get_current_uid() == uid { Yellow.bold() } else { Plain }; + let style = if users_cache.get_current_uid() == uid { colours.users.user_you } else { colours.users.user_someone_else }; Cell::paint(style, &*user_name) } /// This file's group name as a coloured string. /// /// As above, if not present, it formats the gid as a number instead. - fn group(&self, users_cache: &mut U) -> Cell { + fn group(&self, colours: &Colours, users_cache: &mut U) -> Cell { let gid = self.stat.as_raw().gid(); - let mut style = Plain; + let mut style = colours.users.group_not_yours; let group_name = match users_cache.get_group_by_gid(gid as u32) { Some(group) => { let current_uid = users_cache.get_current_uid(); if let Some(current_user) = users_cache.get_user_by_uid(current_uid) { if current_user.primary_group == group.gid || group.members.contains(¤t_user.name) { - style = Yellow.bold(); + style = colours.users.group_yours; } } group.name @@ -312,7 +314,7 @@ impl<'a> File<'a> { /// some filesystems, I've never looked at one of those numbers and gained /// any information from it, so by emitting "-" instead, the table is less /// cluttered with numbers. - fn file_size(&self, size_format: SizeFormat, locale: &locale::Numeric) -> Cell { + fn file_size(&self, colours: &Colours, size_format: SizeFormat, locale: &locale::Numeric) -> Cell { if self.is_directory() { Cell { text: GREY.paint("-").to_string(), length: 1 } } @@ -320,17 +322,17 @@ impl<'a> File<'a> { let result = match size_format { SizeFormat::DecimalBytes => decimal_prefix(self.stat.len() as f64), SizeFormat::BinaryBytes => binary_prefix(self.stat.len() as f64), - SizeFormat::JustBytes => return Cell::paint(Green.bold(), &locale.format_int(self.stat.len())[..]), + SizeFormat::JustBytes => return Cell::paint(colours.size.numbers, &locale.format_int(self.stat.len())[..]), }; match result { - Standalone(bytes) => Cell::paint(Green.bold(), &*bytes.to_string()), + Standalone(bytes) => Cell::paint(colours.size.numbers, &*bytes.to_string()), Prefixed(prefix, n) => { let number = if n < 10f64 { locale.format_float(n, 1) } else { locale.format_int(n as isize) }; let symbol = prefix.symbol(); Cell { - text: ANSIStrings( &[ Green.bold().paint(&number[..]), Green.paint(symbol) ]).to_string(), + text: ANSIStrings( &[ colours.size.unit.paint(&number[..]), colours.size.unit.paint(symbol) ]).to_string(), length: number.len() + symbol.len(), } } @@ -338,7 +340,7 @@ impl<'a> File<'a> { } } - fn timestamp(&self, time_type: TimeType, current_year: i64, locale: &locale::Time) -> Cell { + fn timestamp(&self, colours: &Colours, time_type: TimeType, current_year: i64, locale: &locale::Time) -> Cell { let time_in_seconds = match time_type { TimeType::FileAccessed => self.stat.as_raw().atime(), @@ -355,28 +357,28 @@ impl<'a> File<'a> { DateFormat::parse("{2>:D} {:M} {5>:Y}").unwrap() }; - Cell::paint(Blue.normal(), &format.format(date, locale)) + Cell::paint(colours.date, &format.format(date, locale)) } /// This file's type, represented by a coloured character. /// /// Although the file type can usually be guessed from the colour of the /// file, `ls` puts this character there, so people will expect it. - fn type_char(&self) -> ANSIString { + fn type_char(&self, colours: &Colours) -> ANSIString { if self.is_file() { - Plain.paint(".") + colours.filetypes.normal.paint(".") } else if self.is_directory() { - Blue.paint("d") + colours.filetypes.directory.paint("d") } else if self.is_pipe() { - Yellow.paint("|") + colours.filetypes.special.paint("|") } else if self.is_link() { - Cyan.paint("l") + colours.filetypes.symlink.paint("l") } else { - Purple.paint("?") + colours.filetypes.special.paint("?") } } @@ -385,8 +387,8 @@ impl<'a> File<'a> { /// Returns "@" or " " depending on wheter the file contains an extented /// attribute or not. Also returns " " in case the attributes cannot be read /// for some reason. - fn attribute_marker(&self) -> ANSIString { - if self.xattrs.len() > 0 { Plain.paint("@") } else { Plain.paint(" ") } + fn attribute_marker(&self, colours: &Colours) -> ANSIString { + if self.xattrs.len() > 0 { colours.perms.attribute.paint("@") } else { Plain.paint(" ") } } /// Generate the "rwxrwxrwx" permissions string, like how ls does it. @@ -394,24 +396,24 @@ impl<'a> File<'a> { /// Each character is given its own colour. The first three permission /// bits are bold because they're the ones used most often, and executable /// files are underlined to make them stand out more. - fn permissions_string(&self) -> Cell { + fn permissions_string(&self, colours: &Colours) -> Cell { let bits = self.stat.permissions().mode(); - let executable_colour = if self.is_file() { Green.bold().underline() } - else { Green.bold() }; + let executable_colour = if self.is_file() { colours.perms.user_execute_file } + else { colours.perms.user_execute_other }; let string = ANSIStrings(&[ - self.type_char(), - File::permission_bit(bits, unix::fs::USER_READ, "r", Yellow.bold()), - File::permission_bit(bits, unix::fs::USER_WRITE, "w", Red.bold()), + self.type_char(colours), + File::permission_bit(bits, unix::fs::USER_READ, "r", colours.perms.user_read), + File::permission_bit(bits, unix::fs::USER_WRITE, "w", colours.perms.user_write), File::permission_bit(bits, unix::fs::USER_EXECUTE, "x", executable_colour), - File::permission_bit(bits, unix::fs::GROUP_READ, "r", Yellow.normal()), - File::permission_bit(bits, unix::fs::GROUP_WRITE, "w", Red.normal()), - File::permission_bit(bits, unix::fs::GROUP_EXECUTE, "x", Green.normal()), - File::permission_bit(bits, unix::fs::OTHER_READ, "r", Yellow.normal()), - File::permission_bit(bits, unix::fs::OTHER_WRITE, "w", Red.normal()), - File::permission_bit(bits, unix::fs::OTHER_EXECUTE, "x", Green.normal()), - self.attribute_marker() + File::permission_bit(bits, unix::fs::GROUP_READ, "r", colours.perms.group_read), + File::permission_bit(bits, unix::fs::GROUP_WRITE, "w", colours.perms.group_write), + File::permission_bit(bits, unix::fs::GROUP_EXECUTE, "x", colours.perms.group_execute), + File::permission_bit(bits, unix::fs::OTHER_READ, "r", colours.perms.other_read), + File::permission_bit(bits, unix::fs::OTHER_WRITE, "w", colours.perms.other_write), + File::permission_bit(bits, unix::fs::OTHER_EXECUTE, "x", colours.perms.other_execute), + self.attribute_marker(colours) ]).to_string(); Cell { text: string, length: 11 } diff --git a/src/output/details.rs b/src/output/details.rs index 3870ace..6c0bbb1 100644 --- a/src/output/details.rs +++ b/src/output/details.rs @@ -7,7 +7,6 @@ use options::{Columns, FileFilter, RecurseOptions}; use users::OSUsers; use locale; -use ansi_term::Style::Plain; /// With the **Details** view, the output gets formatted into columns, with /// each `Column` object showing some piece of information about the file, @@ -46,7 +45,7 @@ impl Details { pub fn view(&self, dir: Option<&Dir>, files: &[File]) { // First, transform the Columns object into a vector of columns for // the current directory. - let mut table = Table::with_columns(self.columns.for_dir(dir)); + let mut table = Table::with_options(self.colours, self.columns.for_dir(dir)); if self.header { table.add_header() } // Then add files to the table and print it out. @@ -58,7 +57,7 @@ impl Details { /// is present. fn add_files_to_table(&self, table: &mut Table, src: &[File], depth: usize) { for (index, file) in src.iter().enumerate() { - table.add_file(file, depth, index == src.len() - 1, &self.colours); + table.add_file(file, depth, index == src.len() - 1); // There are two types of recursion that exa supports: a tree // view, which is dealt with here, and multiple listings, which is @@ -114,17 +113,19 @@ struct Table { users: OSUsers, locale: UserLocale, rows: Vec, + colours: Colours, } impl Table { /// Create a new, empty Table object, setting the caching fields to their /// empty states. - fn with_columns(columns: Vec) -> Table { + fn with_options(colours: Colours, columns: Vec) -> Table { Table { columns: columns, users: OSUsers::empty_cache(), locale: UserLocale::new(), rows: Vec::new(), + colours: colours, } } @@ -134,8 +135,8 @@ impl Table { fn add_header(&mut self) { let row = Row { depth: 0, - cells: self.columns.iter().map(|c| Cell::paint(Plain.underline(), c.header())).collect(), - name: Plain.underline().paint("Name").to_string(), + cells: self.columns.iter().map(|c| Cell::paint(self.colours.header, c.header())).collect(), + name: self.colours.header.paint("Name").to_string(), last: false, attrs: Vec::new(), children: false, @@ -148,16 +149,16 @@ impl Table { /// this file, per-column. fn cells_for_file(&mut self, file: &File) -> Vec { self.columns.clone().iter() - .map(|c| file.display(c, &mut self.users, &self.locale)) + .map(|c| file.display(c, &self.colours, &mut self.users, &self.locale)) .collect() } /// Get the cells for the given file, and add the result to the table. - fn add_file(&mut self, file: &File, depth: usize, last: bool, colours: &Colours) { + fn add_file(&mut self, file: &File, depth: usize, last: bool) { let row = Row { depth: depth, cells: self.cells_for_file(file), - name: file.file_name_view(colours), + name: file.file_name_view(&self.colours), last: last, attrs: file.xattrs.clone(), children: file.this.is_some(), From d9319c48b427881a197d96a15b941534646f42ac Mon Sep 17 00:00:00 2001 From: Ben S Date: Sun, 10 May 2015 17:57:21 +0100 Subject: [PATCH 6/7] Do the same for the Git column --- src/colours.rs | 18 ++++++++++++++++++ src/dir.rs | 7 ++++--- src/feature/git.rs | 34 +++++++++++++++++----------------- src/file.rs | 6 +++--- 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/colours.rs b/src/colours.rs index c0619b9..60465f7 100644 --- a/src/colours.rs +++ b/src/colours.rs @@ -13,6 +13,7 @@ pub struct Colours { pub size: Size, pub users: Users, pub links: Links, + pub git: Git, pub punctuation: Style, pub date: Style, @@ -82,6 +83,15 @@ pub struct Links { pub multi_link_file: Style, } +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct Git { + pub new: Style, + pub modified: Style, + pub deleted: Style, + pub renamed: Style, + pub typechange: Style, +} + impl Colours { pub fn plain() -> Colours { Colours::default() @@ -138,6 +148,14 @@ impl Colours { multi_link_file: Red.on(Yellow), }, + git: Git { + new: Green.normal(), + modified: Blue.normal(), + deleted: Red.normal(), + renamed: Yellow.normal(), + typechange: Purple.normal(), + }, + punctuation: Fixed(244).normal(), date: Blue.normal(), inode: Purple.normal(), diff --git a/src/dir.rs b/src/dir.rs index f8f517f..3685329 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -1,3 +1,4 @@ +use colours::Colours; use feature::Git; use file::{File, GREY}; @@ -64,10 +65,10 @@ impl Dir { } /// Get a string describing the Git status of the given file. - pub fn git_status(&self, path: &Path, prefix_lookup: bool) -> String { + pub fn git_status(&self, path: &Path, colours: &Colours, prefix_lookup: bool) -> String { match (&self.git, prefix_lookup) { - (&Some(ref git), false) => git.status(path), - (&Some(ref git), true) => git.dir_status(path), + (&Some(ref git), false) => git.status(colours, path), + (&Some(ref git), true) => git.dir_status(colours, path), (&None, _) => GREY.paint("--").to_string(), } } diff --git a/src/feature/git.rs b/src/feature/git.rs index 0d1fb23..03e6a5d 100644 --- a/src/feature/git.rs +++ b/src/feature/git.rs @@ -1,9 +1,9 @@ use std::path::{Path, PathBuf}; use ansi_term::{ANSIString, ANSIStrings}; -use ansi_term::Colour::*; use git2; +use colours::Colours; use file::GREY; /// Container of Git statuses for all the files in this folder's Git repository. @@ -30,11 +30,11 @@ impl Git { } /// Get the status for the file at the given path, if present. - pub fn status(&self, path: &Path) -> String { + pub fn status(&self, c: &Colours, path: &Path) -> String { let status = self.statuses.iter() .find(|p| p.0.as_path() == path); match status { - Some(&(_, s)) => ANSIStrings( &[Git::index_status(s), Git::working_tree_status(s) ]).to_string(), + Some(&(_, s)) => ANSIStrings( &[Git::index_status(c, s), Git::working_tree_status(c, s) ]).to_string(), None => GREY.paint("--").to_string(), } } @@ -42,35 +42,35 @@ impl Git { /// Get the combined status for all the files whose paths begin with the /// path that gets passed in. This is used for getting the status of /// directories, which don't really have an 'official' status. - pub fn dir_status(&self, dir: &Path) -> String { + pub fn dir_status(&self, c: &Colours, dir: &Path) -> String { let s = self.statuses.iter() .filter(|p| p.0.starts_with(dir)) .fold(git2::Status::empty(), |a, b| a | b.1); - ANSIStrings( &[Git::index_status(s), Git::working_tree_status(s)] ).to_string() + ANSIStrings( &[Git::index_status(c, s), Git::working_tree_status(c, s)] ).to_string() } /// The character to display if the file has been modified, but not staged. - fn working_tree_status(status: git2::Status) -> ANSIString<'static> { + fn working_tree_status(colours: &Colours, status: git2::Status) -> ANSIString<'static> { match status { - s if s.contains(git2::STATUS_WT_NEW) => Green.paint("A"), - s if s.contains(git2::STATUS_WT_MODIFIED) => Blue.paint("M"), - s if s.contains(git2::STATUS_WT_DELETED) => Red.paint("D"), - s if s.contains(git2::STATUS_WT_RENAMED) => Yellow.paint("R"), - s if s.contains(git2::STATUS_WT_TYPECHANGE) => Purple.paint("T"), + s if s.contains(git2::STATUS_WT_NEW) => colours.git.new.paint("A"), + s if s.contains(git2::STATUS_WT_MODIFIED) => colours.git.modified.paint("M"), + s if s.contains(git2::STATUS_WT_DELETED) => colours.git.deleted.paint("D"), + s if s.contains(git2::STATUS_WT_RENAMED) => colours.git.renamed.paint("R"), + s if s.contains(git2::STATUS_WT_TYPECHANGE) => colours.git.typechange.paint("T"), _ => GREY.paint("-"), } } /// The character to display if the file has been modified, and the change /// has been staged. - fn index_status(status: git2::Status) -> ANSIString<'static> { + fn index_status(colours: &Colours, status: git2::Status) -> ANSIString<'static> { match status { - s if s.contains(git2::STATUS_INDEX_NEW) => Green.paint("A"), - s if s.contains(git2::STATUS_INDEX_MODIFIED) => Blue.paint("M"), - s if s.contains(git2::STATUS_INDEX_DELETED) => Red.paint("D"), - s if s.contains(git2::STATUS_INDEX_RENAMED) => Yellow.paint("R"), - s if s.contains(git2::STATUS_INDEX_TYPECHANGE) => Purple.paint("T"), + s if s.contains(git2::STATUS_INDEX_NEW) => colours.git.new.paint("A"), + s if s.contains(git2::STATUS_INDEX_MODIFIED) => colours.git.modified.paint("M"), + s if s.contains(git2::STATUS_INDEX_DELETED) => colours.git.deleted.paint("D"), + s if s.contains(git2::STATUS_INDEX_RENAMED) => colours.git.renamed.paint("R"), + s if s.contains(git2::STATUS_INDEX_TYPECHANGE) => colours.git.typechange.paint("T"), _ => GREY.paint("-"), } } diff --git a/src/file.rs b/src/file.rs index d898707..07fd30a 100644 --- a/src/file.rs +++ b/src/file.rs @@ -124,7 +124,7 @@ impl<'a> File<'a> { Blocks => self.blocks(colours, &locale.numeric), User => self.user(colours, users_cache), Group => self.group(colours, users_cache), - GitStatus => self.git_status(), + GitStatus => self.git_status(colours), } } @@ -475,7 +475,7 @@ impl<'a> File<'a> { choices.contains(&&self.name[..]) } - fn git_status(&self) -> Cell { + fn git_status(&self, colours: &Colours) -> Cell { let status = match self.dir { None => GREY.paint("--").to_string(), Some(d) => { @@ -484,7 +484,7 @@ impl<'a> File<'a> { Ok(dir) => dir.join(&self.path), }; - d.git_status(&cwd, self.is_directory()) + d.git_status(&cwd, colours, self.is_directory()) }, }; From c6d8c21e805adee457d0fdce3e8b04e184f7a3f8 Mon Sep 17 00:00:00 2001 From: Ben S Date: Sun, 10 May 2015 18:03:15 +0100 Subject: [PATCH 7/7] Finally, do the same for the Git column. --- src/colours.rs | 4 +--- src/dir.rs | 4 ++-- src/feature/git.rs | 7 +++---- src/file.rs | 12 ++++-------- src/output/details.rs | 4 ++-- 5 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/colours.rs b/src/colours.rs index 60465f7..352b13c 100644 --- a/src/colours.rs +++ b/src/colours.rs @@ -2,8 +2,6 @@ use ansi_term::Style; use ansi_term::Style::Plain; use ansi_term::Colour::{Red, Green, Yellow, Blue, Cyan, Purple, Fixed}; -use file::GREY; - use std::default::Default; #[derive(Clone, Copy, Debug, Default, PartialEq)] @@ -112,7 +110,7 @@ impl Colours { crypto: Fixed(109).normal(), document: Fixed(105).normal(), compressed: Red.normal(), - temp: GREY.normal(), + temp: Fixed(244).normal(), immediate: Yellow.bold().underline(), compiled: Fixed(137).normal(), }, diff --git a/src/dir.rs b/src/dir.rs index 3685329..9433829 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -1,6 +1,6 @@ use colours::Colours; use feature::Git; -use file::{File, GREY}; +use file::File; use std::io; use std::fs; @@ -69,7 +69,7 @@ impl Dir { match (&self.git, prefix_lookup) { (&Some(ref git), false) => git.status(colours, path), (&Some(ref git), true) => git.dir_status(colours, path), - (&None, _) => GREY.paint("--").to_string(), + (&None, _) => colours.punctuation.paint("--").to_string(), } } } diff --git a/src/feature/git.rs b/src/feature/git.rs index 03e6a5d..db9cc0e 100644 --- a/src/feature/git.rs +++ b/src/feature/git.rs @@ -4,7 +4,6 @@ use ansi_term::{ANSIString, ANSIStrings}; use git2; use colours::Colours; -use file::GREY; /// Container of Git statuses for all the files in this folder's Git repository. pub struct Git { @@ -35,7 +34,7 @@ impl Git { .find(|p| p.0.as_path() == path); match status { Some(&(_, s)) => ANSIStrings( &[Git::index_status(c, s), Git::working_tree_status(c, s) ]).to_string(), - None => GREY.paint("--").to_string(), + None => c.punctuation.paint("--").to_string(), } } @@ -58,7 +57,7 @@ impl Git { s if s.contains(git2::STATUS_WT_DELETED) => colours.git.deleted.paint("D"), s if s.contains(git2::STATUS_WT_RENAMED) => colours.git.renamed.paint("R"), s if s.contains(git2::STATUS_WT_TYPECHANGE) => colours.git.typechange.paint("T"), - _ => GREY.paint("-"), + _ => colours.punctuation.paint("-"), } } @@ -71,7 +70,7 @@ impl Git { s if s.contains(git2::STATUS_INDEX_DELETED) => colours.git.deleted.paint("D"), s if s.contains(git2::STATUS_INDEX_RENAMED) => colours.git.renamed.paint("R"), s if s.contains(git2::STATUS_INDEX_TYPECHANGE) => colours.git.typechange.paint("T"), - _ => GREY.paint("-"), + _ => colours.punctuation.paint("-"), } } } diff --git a/src/file.rs b/src/file.rs index 07fd30a..b179e76 100644 --- a/src/file.rs +++ b/src/file.rs @@ -7,7 +7,7 @@ use std::os::unix::raw::mode_t; use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::path::{Component, Path, PathBuf}; -use ansi_term::{ANSIString, ANSIStrings, Colour, Style}; +use ansi_term::{ANSIString, ANSIStrings, Style}; use ansi_term::Style::Plain; use ansi_term::Colour::Fixed; @@ -31,10 +31,6 @@ use options::{SizeFormat, TimeType}; use output::details::UserLocale; use feature::Attribute; -/// This grey value is directly in between white and black, so it's guaranteed -/// to show up on either backgrounded terminal. -pub static GREY: Colour = Fixed(244); - /// A **File** is a wrapper around one of Rust's Path objects, along with /// associated data about the file. /// @@ -316,7 +312,7 @@ impl<'a> File<'a> { /// cluttered with numbers. fn file_size(&self, colours: &Colours, size_format: SizeFormat, locale: &locale::Numeric) -> Cell { if self.is_directory() { - Cell { text: GREY.paint("-").to_string(), length: 1 } + Cell { text: colours.punctuation.paint("-").to_string(), length: 1 } } else { let result = match size_format { @@ -425,7 +421,7 @@ impl<'a> File<'a> { style.paint(character) } else { - GREY.paint("-") + Fixed(244).paint("-") } } @@ -477,7 +473,7 @@ impl<'a> File<'a> { fn git_status(&self, colours: &Colours) -> Cell { let status = match self.dir { - None => GREY.paint("--").to_string(), + None => colours.punctuation.paint("--").to_string(), Some(d) => { let cwd = match current_dir() { Err(_) => Path::new(".").join(&self.path), diff --git a/src/output/details.rs b/src/output/details.rs index 6c0bbb1..ebe43ae 100644 --- a/src/output/details.rs +++ b/src/output/details.rs @@ -2,7 +2,7 @@ use colours::Colours; use column::{Alignment, Column, Cell}; use feature::Attribute; use dir::Dir; -use file::{File, GREY}; +use file::File; use options::{Columns, FileFilter, RecurseOptions}; use users::OSUsers; @@ -193,7 +193,7 @@ impl Table { stack[row.depth] = if row.last { TreePart::Corner } else { TreePart::Edge }; for i in 1 .. row.depth + 1 { - print!("{}", GREY.paint(stack[i].ascii_art())); + print!("{}", self.colours.punctuation.paint(stack[i].ascii_art())); } if row.children {