mirror of
https://github.com/Llewellynvdm/exa.git
synced 2025-02-04 11:38:24 +00:00
Merge branch 'exa-colors'
This branch added support for the EXA_COLORS environment variable, and defines a bunch of two-letter configuration settings that allows theming exa. The next step is to allow custom highlighting based on file names.
This commit is contained in:
commit
075fe802b4
29
Vagrantfile
vendored
29
Vagrantfile
vendored
@ -135,9 +135,9 @@ Vagrant.configure(2) do |config|
|
||||
echo -e "\033[32;1mt\033[0m or \033[32;1mtest-exa\033[0m to run \033[1mcargo test\033[0m" >> /etc/motd
|
||||
echo -e "\033[32;1mx\033[0m or \033[32;1mrun-xtests\033[0m to run \033[1m/vagrant/xtests/run.sh\033[0m" >> /etc/motd
|
||||
echo -e "\033[32;1mc\033[0m or \033[32;1mcompile-exa\033[0m to run all three" >> /etc/motd
|
||||
echo -e "\033[32;1mdebug on\033[0;32m|\033[1moff\033[0m to toggle printing logs" >> /etc/motd
|
||||
echo -e "\033[32;1mstrict on\033[0;32m|\033[1moff\033[0m to toggle strict mode" >> /etc/motd
|
||||
echo -e "\033[32;1mls-colors on\033[0;32m|\033[1moff\033[0m to toggle LS_COLORS\n" >> /etc/motd
|
||||
echo -e "\033[32;1mdebug\033[0m to toggle printing logs" >> /etc/motd
|
||||
echo -e "\033[32;1mstrict\033[0m to toggle strict mode" >> /etc/motd
|
||||
echo -e "\033[32;1mcolors\033[0m to toggle custom colours\n" >> /etc/motd
|
||||
|
||||
# help banner
|
||||
echo 'echo -e "\\033[4mVersions\\033[0m"' > /home/ubuntu/.bash_profile
|
||||
@ -150,7 +150,8 @@ Vagrant.configure(2) do |config|
|
||||
echo 'function debug_mode() { [ -n "$EXA_DEBUG" ] && echo "debug "; }' >> /home/ubuntu/.bash_profile
|
||||
echo 'function strict_mode() { [ -n "$EXA_STRICT" ] && echo "strict "; }' >> /home/ubuntu/.bash_profile
|
||||
echo 'function lsc_mode() { [ -n "$LS_COLORS" ] && echo "lsc "; }' >> /home/ubuntu/.bash_profile
|
||||
echo 'export PS1="\\[\\e[1;36m\\]\\h \\[\\e[32m\\]\\w \\[\\e[31m\\]\\`nonzero_return\\`\\[\\e[35m\\]\\`debug_mode\\`\\[\\e[32m\\]\\`lsc_mode\\`\\[\\e[33m\\]\\`strict_mode\\`\\[\\e[36m\\]\\\\$\\[\\e[0m\\] "' >> /home/ubuntu/.bash_profile
|
||||
echo 'function exac_mode() { [ -n "$EXA_COLORS" ] && echo "exac "; }' >> /home/ubuntu/.bash_profile
|
||||
echo 'export PS1="\\[\\e[1;36m\\]\\h \\[\\e[32m\\]\\w \\[\\e[31m\\]\\`nonzero_return\\`\\[\\e[35m\\]\\`debug_mode\\`\\[\\e[32m\\]\\`lsc_mode\\`\\[\\e[1;32m\\]\\`exac_mode\\`\\[\\e[33m\\]\\`strict_mode\\`\\[\\e[36m\\]\\\\$\\[\\e[0m\\] "' >> /home/ubuntu/.bash_profile
|
||||
|
||||
# environment setting
|
||||
echo 'function debug () {' >> /home/ubuntu/.bash_profile
|
||||
@ -165,13 +166,21 @@ Vagrant.configure(2) do |config|
|
||||
echo ' "") [ -n "$EXA_STRICT" ] && echo "strict on" || echo "strict off" ;;' >> /home/ubuntu/.bash_profile
|
||||
echo ' *) echo "Usage: strict on|off"; return 1 ;; esac; }' >> /home/ubuntu/.bash_profile
|
||||
|
||||
echo 'function ls-colors () {' >> /home/ubuntu/.bash_profile
|
||||
echo 'function colors () {' >> /home/ubuntu/.bash_profile
|
||||
echo ' case "$1" in ' >> /home/ubuntu/.bash_profile
|
||||
echo ' "on") export LS_COLORS="di=34:ln=35:so=32:pi=33:ex=31:bd=34;46:cd=34;43:su=30;41:sg=30;46:tw=30;42:ow=30;43" ;;' >> /home/ubuntu/.bash_profile
|
||||
echo ' "hacker") export LS_COLORS="di=32:ex=32:fi=32:pi=32:so=32:bd=32:cd=32:ln=32:or=32:mi=32" ;;' >> /home/ubuntu/.bash_profile
|
||||
echo ' "off") export LS_COLORS= ;;' >> /home/ubuntu/.bash_profile
|
||||
echo ' "") [ -n "$LS_COLORS" ] && echo "LS_COLORS=$LS_COLORS" || echo "ls-colors off" ;;' >> /home/ubuntu/.bash_profile
|
||||
echo ' *) echo "Usage: ls-colors on|off"; return 1 ;; esac; }' >> /home/ubuntu/.bash_profile
|
||||
echo ' "ls")' >> /home/ubuntu/.bash_profile
|
||||
echo ' export LS_COLORS="di=34:ln=35:so=32:pi=33:ex=31:bd=34;46:cd=34;43:su=30;41:sg=30;46:tw=30;42:ow=30;43"' >> /home/ubuntu/.bash_profile
|
||||
echo ' export EXA_COLORS="" ;;' >> /home/ubuntu/.bash_profile
|
||||
echo ' "hacker")' >> /home/ubuntu/.bash_profile
|
||||
echo ' export LS_COLORS="di=32:ex=32:fi=32:pi=32:so=32:bd=32:cd=32:ln=32:or=32:mi=32"' >> /home/ubuntu/.bash_profile
|
||||
echo ' export EXA_COLORS="ur=32:uw=32:ux=32:ue=32:gr=32:gw=32:gx=32:tr=32:tw=32:tx=32:su=32:sf=32:xa=32:sn=32:sb=32:df=32:ds=32:uu=32:un=32:gu=32:gn=32:lc=32:lm=32:ga=32:gm=32:gd=32:gv=32:gt=32:xx=32:da=32:in=32:bl=32:hd=32:lp=32:cc=32:" ;;' >> /home/ubuntu/.bash_profile
|
||||
echo ' "off")' >> /home/ubuntu/.bash_profile
|
||||
echo ' export LS_COLORS=' >> /home/ubuntu/.bash_profile
|
||||
echo ' export EXA_COLORS= ;;' >> /home/ubuntu/.bash_profile
|
||||
echo ' "")' >> /home/ubuntu/.bash_profile
|
||||
echo ' [ -n "$LS_COLORS" ] && echo "LS_COLORS=$LS_COLORS" || echo "ls-colors off"' >> /home/ubuntu/.bash_profile
|
||||
echo ' [ -n "$EXA_COLORS" ] && echo "EXA_COLORS=$EXA_COLORS" || echo "exa-colors off" ;;' >> /home/ubuntu/.bash_profile
|
||||
echo ' *) echo "Usage: ls-colors ls|hacker|off"; return 1 ;; esac; }' >> /home/ubuntu/.bash_profile
|
||||
|
||||
# Disable last login date in sshd
|
||||
sed -i '/PrintLastLog yes/c\PrintLastLog no' /etc/ssh/sshd_config
|
||||
|
@ -38,6 +38,7 @@ mod fs;
|
||||
mod info;
|
||||
mod options;
|
||||
mod output;
|
||||
mod style;
|
||||
|
||||
|
||||
/// The main program wrapper.
|
||||
|
@ -4,10 +4,13 @@
|
||||
//! those are the only metadata that we have access to without reading the
|
||||
//! file’s contents.
|
||||
|
||||
use ansi_term::Style;
|
||||
|
||||
use fs::File;
|
||||
use output::file_name::FileColours;
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct FileExtensions;
|
||||
|
||||
impl FileExtensions {
|
||||
@ -15,7 +18,7 @@ impl FileExtensions {
|
||||
/// An “immediate” file is something that can be run or activated somehow
|
||||
/// in order to kick off the build of a project. It’s usually only present
|
||||
/// in directories full of source code.
|
||||
pub fn is_immediate(&self, file: &File) -> bool {
|
||||
fn is_immediate(&self, file: &File) -> bool {
|
||||
file.name.starts_with("README") || file.name_is_one_of( &[
|
||||
"Makefile", "Cargo.toml", "SConstruct", "CMakeLists.txt",
|
||||
"build.gradle", "Rakefile", "Gruntfile.js",
|
||||
@ -23,7 +26,7 @@ impl FileExtensions {
|
||||
])
|
||||
}
|
||||
|
||||
pub fn is_image(&self, file: &File) -> bool {
|
||||
fn is_image(&self, file: &File) -> bool {
|
||||
file.extension_is_one_of( &[
|
||||
"png", "jpeg", "jpg", "gif", "bmp", "tiff", "tif",
|
||||
"ppm", "pgm", "pbm", "pnm", "webp", "raw", "arw",
|
||||
@ -32,7 +35,7 @@ impl FileExtensions {
|
||||
])
|
||||
}
|
||||
|
||||
pub fn is_video(&self, file: &File) -> bool {
|
||||
fn is_video(&self, file: &File) -> bool {
|
||||
file.extension_is_one_of( &[
|
||||
"avi", "flv", "m2v", "mkv", "mov", "mp4", "mpeg",
|
||||
"mpg", "ogm", "ogv", "vob", "wmv", "webm", "m2ts",
|
||||
@ -40,26 +43,26 @@ impl FileExtensions {
|
||||
])
|
||||
}
|
||||
|
||||
pub fn is_music(&self, file: &File) -> bool {
|
||||
fn is_music(&self, file: &File) -> bool {
|
||||
file.extension_is_one_of( &[
|
||||
"aac", "m4a", "mp3", "ogg", "wma", "mka", "opus",
|
||||
])
|
||||
}
|
||||
|
||||
// Lossless music, rather than any other kind of data...
|
||||
pub fn is_lossless(&self, file: &File) -> bool {
|
||||
fn is_lossless(&self, file: &File) -> bool {
|
||||
file.extension_is_one_of( &[
|
||||
"alac", "ape", "flac", "wav",
|
||||
])
|
||||
}
|
||||
|
||||
pub fn is_crypto(&self, file: &File) -> bool {
|
||||
fn is_crypto(&self, file: &File) -> bool {
|
||||
file.extension_is_one_of( &[
|
||||
"asc", "enc", "gpg", "pgp", "sig", "signature", "pfx", "p12",
|
||||
])
|
||||
}
|
||||
|
||||
pub fn is_document(&self, file: &File) -> bool {
|
||||
fn is_document(&self, file: &File) -> bool {
|
||||
file.extension_is_one_of( &[
|
||||
"djvu", "doc", "docx", "dvi", "eml", "eps", "fotd",
|
||||
"odp", "odt", "pdf", "ppt", "pptx", "rtf",
|
||||
@ -67,7 +70,7 @@ impl FileExtensions {
|
||||
])
|
||||
}
|
||||
|
||||
pub fn is_compressed(&self, file: &File) -> bool {
|
||||
fn is_compressed(&self, file: &File) -> bool {
|
||||
file.extension_is_one_of( &[
|
||||
"zip", "tar", "Z", "z", "gz", "bz2", "a", "ar", "7z",
|
||||
"iso", "dmg", "tc", "rar", "par", "tgz", "xz", "txz",
|
||||
@ -75,13 +78,13 @@ impl FileExtensions {
|
||||
])
|
||||
}
|
||||
|
||||
pub fn is_temp(&self, file: &File) -> bool {
|
||||
fn is_temp(&self, file: &File) -> bool {
|
||||
file.name.ends_with('~')
|
||||
|| (file.name.starts_with('#') && file.name.ends_with('#'))
|
||||
|| file.extension_is_one_of( &[ "tmp", "swp", "swo", "swn", "bak" ])
|
||||
}
|
||||
|
||||
pub fn is_compiled(&self, file: &File) -> bool {
|
||||
fn is_compiled(&self, file: &File) -> bool {
|
||||
if file.extension_is_one_of( &[ "class", "elc", "hi", "o", "pyc" ]) {
|
||||
true
|
||||
}
|
||||
@ -93,3 +96,23 @@ impl FileExtensions {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileColours for FileExtensions {
|
||||
fn colour_file(&self, file: &File) -> Option<Style> {
|
||||
use ansi_term::Colour::*;
|
||||
|
||||
Some(match file {
|
||||
f if self.is_immediate(f) => Yellow.bold().underline(),
|
||||
f if self.is_image(f) => Fixed(133).normal(),
|
||||
f if self.is_video(f) => Fixed(135).normal(),
|
||||
f if self.is_music(f) => Fixed(92).normal(),
|
||||
f if self.is_lossless(f) => Fixed(93).normal(),
|
||||
f if self.is_crypto(f) => Fixed(109).normal(),
|
||||
f if self.is_document(f) => Fixed(105).normal(),
|
||||
f if self.is_compressed(f) => Red.normal(),
|
||||
f if self.is_temp(f) => Fixed(244).normal(),
|
||||
f if self.is_compiled(f) => Fixed(137).normal(),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use output::Colours;
|
||||
use style::Colours;
|
||||
|
||||
use options::{flags, Vars, Misfire};
|
||||
use options::parser::MatchedFlags;
|
||||
@ -63,7 +63,8 @@ impl Colours {
|
||||
pub fn deduce<V, TW>(matches: &MatchedFlags, vars: &V, widther: TW) -> Result<Colours, Misfire>
|
||||
where TW: Fn() -> Option<usize>, V: Vars {
|
||||
use self::TerminalColours::*;
|
||||
use output::lsc::LSColors;
|
||||
use style::LSColors;
|
||||
use options::vars;
|
||||
|
||||
let tc = TerminalColours::deduce(matches)?;
|
||||
if tc == Never || (tc == Automatic && widther().is_none()) {
|
||||
@ -73,20 +74,14 @@ impl Colours {
|
||||
let scale = matches.has_where(|f| f.matches(&flags::COLOR_SCALE) || f.matches(&flags::COLOUR_SCALE))?;
|
||||
let mut colours = Colours::colourful(scale.is_some());
|
||||
|
||||
if let Some(lsc) = vars.get("LS_COLORS") {
|
||||
if let Some(lsc) = vars.get(vars::LS_COLORS) {
|
||||
let lsc = lsc.to_string_lossy();
|
||||
let lsc = LSColors::parse(lsc.as_ref());
|
||||
LSColors(lsc.as_ref()).each_pair(|pair| colours.set_ls(&pair));
|
||||
}
|
||||
|
||||
if let Some(c) = lsc.get("di") { colours.filekinds.directory = c; }
|
||||
if let Some(c) = lsc.get("ex") { colours.filekinds.executable = c; }
|
||||
if let Some(c) = lsc.get("fi") { colours.filekinds.normal = c; }
|
||||
if let Some(c) = lsc.get("pi") { colours.filekinds.pipe = c; }
|
||||
if let Some(c) = lsc.get("so") { colours.filekinds.socket = c; }
|
||||
if let Some(c) = lsc.get("bd") { colours.filekinds.block_device = c; }
|
||||
if let Some(c) = lsc.get("cd") { colours.filekinds.char_device = c; }
|
||||
if let Some(c) = lsc.get("ln") { colours.filekinds.symlink = c; }
|
||||
if let Some(c) = lsc.get("or") { colours.broken_arrow = c; }
|
||||
if let Some(c) = lsc.get("mi") { colours.broken_filename = c; }
|
||||
if let Some(exa) = vars.get(vars::EXA_COLORS) {
|
||||
let exa = exa.to_string_lossy();
|
||||
LSColors(exa.as_ref()).each_pair(|pair| colours.set_exa(&pair));
|
||||
}
|
||||
|
||||
Ok(colours)
|
||||
@ -251,7 +246,7 @@ mod customs_test {
|
||||
let vars = MockVars { ls: $ls, exa: $exa };
|
||||
|
||||
for result in parse_for_test(&[], &[], Both, |mf| Colours::deduce(mf, &vars, || Some(80))) {
|
||||
assert_eq!(result, Ok(c));
|
||||
assert_eq!(result.as_ref(), Ok(&c));
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -265,10 +260,12 @@ mod customs_test {
|
||||
// Test impl that just returns the value it has.
|
||||
impl Vars for MockVars {
|
||||
fn get(&self, name: &'static str) -> Option<OsString> {
|
||||
if name == "LS_COLORS" && !self.ls.is_empty() {
|
||||
use options::vars;
|
||||
|
||||
if name == vars::LS_COLORS && !self.ls.is_empty() {
|
||||
OsString::from(self.ls.clone()).into()
|
||||
}
|
||||
else if name == "EXA_COLORS" && !self.exa.is_empty() {
|
||||
else if name == vars::EXA_COLORS && !self.exa.is_empty() {
|
||||
OsString::from(self.exa.clone()).into()
|
||||
}
|
||||
else {
|
||||
|
@ -89,6 +89,9 @@ use self::version::VersionString;
|
||||
mod misfire;
|
||||
pub use self::misfire::Misfire;
|
||||
|
||||
pub mod vars;
|
||||
pub use self::vars::Vars;
|
||||
|
||||
mod parser;
|
||||
mod flags;
|
||||
use self::parser::MatchedFlags;
|
||||
@ -120,8 +123,9 @@ impl Options {
|
||||
where I: IntoIterator<Item=&'args OsString>,
|
||||
V: Vars {
|
||||
use options::parser::{Matches, Strictness};
|
||||
use options::vars;
|
||||
|
||||
let strictness = match vars.get("EXA_STRICT") {
|
||||
let strictness = match vars.get(vars::EXA_STRICT) {
|
||||
None => Strictness::UseLastArguments,
|
||||
Some(ref t) if t.is_empty() => Strictness::UseLastArguments,
|
||||
_ => Strictness::ComplainAboutRedundantArguments,
|
||||
@ -162,28 +166,13 @@ impl Options {
|
||||
}
|
||||
|
||||
|
||||
/// Mockable wrapper for `std::env::var_os`.
|
||||
pub trait Vars {
|
||||
fn get(&self, name: &'static str) -> Option<OsString>;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use super::{Options, Misfire, Vars, flags};
|
||||
use super::{Options, Misfire, flags};
|
||||
use options::parser::{Arg, MatchedFlags};
|
||||
use std::ffi::OsString;
|
||||
|
||||
// Test impl that just returns the value it has.
|
||||
impl Vars for Option<OsString> {
|
||||
fn get(&self, _name: &'static str) -> Option<OsString> {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum Strictnesses {
|
||||
Last,
|
||||
|
48
src/options/vars.rs
Normal file
48
src/options/vars.rs
Normal file
@ -0,0 +1,48 @@
|
||||
use std::ffi::OsString;
|
||||
|
||||
|
||||
// General variables
|
||||
|
||||
/// Environment variable used to colour files, both by their filesystem type
|
||||
/// (symlink, socket, directory) and their file name or extension (image,
|
||||
/// video, archive);
|
||||
pub static LS_COLORS: &str = "LS_COLORS";
|
||||
|
||||
/// Environment variable used to override the width of the terminal, in
|
||||
/// characters.
|
||||
pub static COLUMNS: &str = "COLUMNS";
|
||||
|
||||
|
||||
// exa-specific variables
|
||||
|
||||
/// Environment variable used to colour exa’s interface when colours are
|
||||
/// enabled. This includes all the colours that LS_COLORS would recognise,
|
||||
/// overriding them if necessary. It can also contain exa-specific codes.
|
||||
pub static EXA_COLORS: &str = "EXA_COLORS";
|
||||
|
||||
/// Environment variable used to switch on strict argument checking, such as
|
||||
/// complaining if an argument was specified twice, or if two conflict.
|
||||
/// This is meant to be so you don’t accidentally introduce the wrong
|
||||
/// behaviour in a script, rather than for general command-line use.
|
||||
pub static EXA_STRICT: &str = "EXA_STRICT";
|
||||
|
||||
/// Environment variable used to limit the grid-details view
|
||||
/// (`--grid --long`) so it’s only activated if there’s at least the given
|
||||
/// number of rows of output.
|
||||
pub static EXA_GRID_ROWS: &str = "EXA_GRID_ROWS";
|
||||
|
||||
|
||||
|
||||
/// Mockable wrapper for `std::env::var_os`.
|
||||
pub trait Vars {
|
||||
fn get(&self, name: &'static str) -> Option<OsString>;
|
||||
}
|
||||
|
||||
|
||||
// Test impl that just returns the value it has.
|
||||
#[cfg(test)]
|
||||
impl Vars for Option<OsString> {
|
||||
fn get(&self, _name: &'static str) -> Option<OsString> {
|
||||
self.clone()
|
||||
}
|
||||
}
|
@ -1,15 +1,15 @@
|
||||
use output::Colours;
|
||||
use style::Colours;
|
||||
|
||||
use output::{View, Mode, grid, details};
|
||||
use output::grid_details::{self, RowThreshold};
|
||||
use output::table::{TimeTypes, Environment, SizeFormat, Columns, Options as TableOptions};
|
||||
use output::file_name::{Classify, FileStyle};
|
||||
use output::file_name::{Classify, FileStyle, NoFileColours};
|
||||
use output::time::TimeFormat;
|
||||
|
||||
use options::{flags, Misfire, Vars};
|
||||
use options::parser::MatchedFlags;
|
||||
|
||||
use fs::feature::xattr;
|
||||
use info::filetype::FileExtensions;
|
||||
|
||||
|
||||
impl View {
|
||||
@ -18,7 +18,7 @@ impl View {
|
||||
pub fn deduce<V: Vars>(matches: &MatchedFlags, vars: &V) -> Result<View, Misfire> {
|
||||
let mode = Mode::deduce(matches, vars)?;
|
||||
let colours = Colours::deduce(matches, vars, || *TERM_WIDTH)?;
|
||||
let style = FileStyle::deduce(matches)?;
|
||||
let style = FileStyle::deduce(matches, &colours)?;
|
||||
Ok(View { mode, colours, style })
|
||||
}
|
||||
}
|
||||
@ -156,7 +156,9 @@ impl TerminalWidth {
|
||||
///
|
||||
/// Returns an error if a requested width doesn’t parse to an integer.
|
||||
fn deduce<V: Vars>(vars: &V) -> Result<TerminalWidth, Misfire> {
|
||||
if let Some(columns) = vars.get("COLUMNS").and_then(|s| s.into_string().ok()) {
|
||||
use options::vars;
|
||||
|
||||
if let Some(columns) = vars.get(vars::COLUMNS).and_then(|s| s.into_string().ok()) {
|
||||
match columns.parse() {
|
||||
Ok(width) => Ok(TerminalWidth::Set(width)),
|
||||
Err(e) => Err(Misfire::FailedParse(e)),
|
||||
@ -185,7 +187,9 @@ impl RowThreshold {
|
||||
/// Determine whether to use a row threshold based on the given
|
||||
/// environment variables.
|
||||
fn deduce<V: Vars>(vars: &V) -> Result<RowThreshold, Misfire> {
|
||||
if let Some(columns) = vars.get("EXA_GRID_ROWS").and_then(|s| s.into_string().ok()) {
|
||||
use options::vars;
|
||||
|
||||
if let Some(columns) = vars.get(vars::EXA_GRID_ROWS).and_then(|s| s.into_string().ok()) {
|
||||
match columns.parse() {
|
||||
Ok(rows) => Ok(RowThreshold::MinimumRows(rows)),
|
||||
Err(e) => Err(Misfire::FailedParse(e)),
|
||||
@ -332,9 +336,15 @@ impl TimeTypes {
|
||||
|
||||
|
||||
impl FileStyle {
|
||||
fn deduce(matches: &MatchedFlags) -> Result<FileStyle, Misfire> {
|
||||
|
||||
#[allow(trivial_casts)]
|
||||
fn deduce(matches: &MatchedFlags, colours: &Colours) -> Result<FileStyle, Misfire> {
|
||||
use info::filetype::FileExtensions;
|
||||
|
||||
let classify = Classify::deduce(matches)?;
|
||||
let exts = FileExtensions;
|
||||
let exts = if colours.colourful { Box::new(FileExtensions) as Box<_> }
|
||||
else { Box::new(NoFileColours) as Box<_> };
|
||||
|
||||
Ok(FileStyle { classify, exts })
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ use fs::{Dir, File};
|
||||
use fs::dir_action::RecurseOptions;
|
||||
use fs::filter::FileFilter;
|
||||
use fs::feature::xattr::{Attribute, FileAttributes};
|
||||
use output::colours::Colours;
|
||||
use style::Colours;
|
||||
use output::cell::TextCell;
|
||||
use output::tree::{TreeTrunk, TreeParams, TreeDepth};
|
||||
use output::file_name::FileStyle;
|
||||
|
@ -3,10 +3,9 @@ use std::path::Path;
|
||||
use ansi_term::{ANSIString, Style};
|
||||
|
||||
use fs::{File, FileTarget};
|
||||
use info::filetype::FileExtensions;
|
||||
use output::Colours;
|
||||
use output::escape;
|
||||
use output::cell::TextCellContents;
|
||||
use output::render::FiletypeColours;
|
||||
|
||||
|
||||
/// Basically a file name factory.
|
||||
@ -17,19 +16,19 @@ pub struct FileStyle {
|
||||
pub classify: Classify,
|
||||
|
||||
/// Mapping of file extensions to colours, to highlight regular files.
|
||||
pub exts: FileExtensions,
|
||||
pub exts: Box<FileColours>,
|
||||
}
|
||||
|
||||
impl FileStyle {
|
||||
|
||||
/// Create a new `FileName` that prints the given file’s name, painting it
|
||||
/// with the remaining arguments.
|
||||
pub fn for_file<'a, 'dir>(&'a self, file: &'a File<'dir>, colours: &'a Colours) -> FileName<'a, 'dir> {
|
||||
pub fn for_file<'a, 'dir, C: Colours>(&'a self, file: &'a File<'dir>, colours: &'a C) -> FileName<'a, 'dir, C> {
|
||||
FileName {
|
||||
file, colours,
|
||||
link_style: LinkStyle::JustFilenames,
|
||||
exts: &self.exts,
|
||||
classify: self.classify,
|
||||
exts: &*self.exts,
|
||||
target: if file.is_link() { Some(file.link_target()) }
|
||||
else { None }
|
||||
}
|
||||
@ -75,15 +74,15 @@ impl Default for Classify {
|
||||
|
||||
/// A **file name** holds all the information necessary to display the name
|
||||
/// of the given file. This is used in all of the views.
|
||||
pub struct FileName<'a, 'dir: 'a> {
|
||||
pub struct FileName<'a, 'dir: 'a, C: Colours+'a> {
|
||||
|
||||
/// A reference to the file that we're getting the name of.
|
||||
/// A reference to the file that we’re getting the name of.
|
||||
file: &'a File<'dir>,
|
||||
|
||||
/// The colours used to paint the file name and its surrounding text.
|
||||
colours: &'a Colours,
|
||||
colours: &'a C,
|
||||
|
||||
/// The file that this file points to if it's a link.
|
||||
/// The file that this file points to if it’s a link.
|
||||
target: Option<FileTarget<'dir>>,
|
||||
|
||||
/// How to handle displaying links.
|
||||
@ -93,11 +92,11 @@ pub struct FileName<'a, 'dir: 'a> {
|
||||
classify: Classify,
|
||||
|
||||
/// Mapping of file extensions to colours, to highlight regular files.
|
||||
exts: &'a FileExtensions,
|
||||
exts: &'a FileColours,
|
||||
}
|
||||
|
||||
|
||||
impl<'a, 'dir> FileName<'a, 'dir> {
|
||||
impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
||||
|
||||
/// Sets the flag on this file name to display link targets with an
|
||||
/// arrow followed by their path.
|
||||
@ -131,7 +130,7 @@ impl<'a, 'dir> FileName<'a, 'dir> {
|
||||
match *target {
|
||||
FileTarget::Ok(ref target) => {
|
||||
bits.push(Style::default().paint(" "));
|
||||
bits.push(self.colours.punctuation.paint("->"));
|
||||
bits.push(self.colours.normal_arrow().paint("->"));
|
||||
bits.push(Style::default().paint(" "));
|
||||
|
||||
if let Some(parent) = target.path.parent() {
|
||||
@ -156,9 +155,9 @@ impl<'a, 'dir> FileName<'a, 'dir> {
|
||||
|
||||
FileTarget::Broken(ref broken_path) => {
|
||||
bits.push(Style::default().paint(" "));
|
||||
bits.push(self.colours.broken_arrow.paint("->"));
|
||||
bits.push(self.colours.broken_arrow().paint("->"));
|
||||
bits.push(Style::default().paint(" "));
|
||||
escape(broken_path.display().to_string(), &mut bits, self.colours.broken_filename, self.colours.control_char.underline());
|
||||
escape(broken_path.display().to_string(), &mut bits, self.colours.broken_filename(), self.colours.control_char().underline());
|
||||
},
|
||||
|
||||
FileTarget::Err(_) => {
|
||||
@ -182,11 +181,11 @@ impl<'a, 'dir> FileName<'a, 'dir> {
|
||||
let coconut = parent.components().count();
|
||||
|
||||
if coconut == 1 && parent.has_root() {
|
||||
bits.push(self.colours.symlink_path.paint("/"));
|
||||
bits.push(self.colours.symlink_path().paint("/"));
|
||||
}
|
||||
else if coconut >= 1 {
|
||||
escape(parent.to_string_lossy().to_string(), bits, self.colours.symlink_path, self.colours.control_char);
|
||||
bits.push(self.colours.symlink_path.paint("/"));
|
||||
escape(parent.to_string_lossy().to_string(), bits, self.colours.symlink_path(), self.colours.control_char());
|
||||
bits.push(self.colours.symlink_path().paint("/"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,7 +222,7 @@ impl<'a, 'dir> FileName<'a, 'dir> {
|
||||
fn coloured_file_name<'unused>(&self) -> Vec<ANSIString<'unused>> {
|
||||
let file_style = self.style();
|
||||
let mut bits = Vec::new();
|
||||
escape(self.file.name.clone(), &mut bits, file_style, self.colours.control_char);
|
||||
escape(self.file.name.clone(), &mut bits, file_style, self.colours.control_char());
|
||||
bits
|
||||
}
|
||||
|
||||
@ -232,42 +231,57 @@ impl<'a, 'dir> FileName<'a, 'dir> {
|
||||
/// depending on which “type” of file it appears to be -- either from the
|
||||
/// class on the filesystem or from its name.
|
||||
pub fn style(&self) -> Style {
|
||||
|
||||
// Override the style with the “broken link” style when this file is
|
||||
// a link that we can’t follow for whatever reason. This is used when
|
||||
// there’s no other place to show that the link doesn’t work.
|
||||
if let LinkStyle::JustFilenames = self.link_style {
|
||||
if let Some(ref target) = self.target {
|
||||
if target.is_broken() {
|
||||
return self.colours.broken_arrow;
|
||||
return self.colours.broken_arrow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, just apply a bunch of rules in order. For example,
|
||||
// executable image files should be executable rather than images.
|
||||
match self.file {
|
||||
f if f.is_directory() => self.colours.filekinds.directory,
|
||||
f if f.is_executable_file() => self.colours.filekinds.executable,
|
||||
f if f.is_link() => self.colours.filekinds.symlink,
|
||||
f if f.is_pipe() => self.colours.filekinds.pipe,
|
||||
f if f.is_block_device() => self.colours.filekinds.block_device,
|
||||
f if f.is_char_device() => self.colours.filekinds.char_device,
|
||||
f if f.is_socket() => self.colours.filekinds.socket,
|
||||
f if !f.is_file() => self.colours.filekinds.special,
|
||||
self.kind_style()
|
||||
.or_else(|| self.exts.colour_file(self.file))
|
||||
.unwrap_or_else(|| self.colours.normal())
|
||||
}
|
||||
|
||||
f if self.exts.is_immediate(f) => self.colours.filetypes.immediate,
|
||||
f if self.exts.is_image(f) => self.colours.filetypes.image,
|
||||
f if self.exts.is_video(f) => self.colours.filetypes.video,
|
||||
f if self.exts.is_music(f) => self.colours.filetypes.music,
|
||||
f if self.exts.is_lossless(f) => self.colours.filetypes.lossless,
|
||||
f if self.exts.is_crypto(f) => self.colours.filetypes.crypto,
|
||||
f if self.exts.is_document(f) => self.colours.filetypes.document,
|
||||
f if self.exts.is_compressed(f) => self.colours.filetypes.compressed,
|
||||
f if self.exts.is_temp(f) => self.colours.filetypes.temp,
|
||||
f if self.exts.is_compiled(f) => self.colours.filetypes.compiled,
|
||||
fn kind_style(&self) -> Option<Style> {
|
||||
Some(match self.file {
|
||||
f if f.is_directory() => self.colours.directory(),
|
||||
f if f.is_executable_file() => self.colours.executable_file(),
|
||||
f if f.is_link() => self.colours.symlink(),
|
||||
f if f.is_pipe() => self.colours.pipe(),
|
||||
f if f.is_block_device() => self.colours.block_device(),
|
||||
f if f.is_char_device() => self.colours.char_device(),
|
||||
f if f.is_socket() => self.colours.socket(),
|
||||
f if !f.is_file() => self.colours.special(),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
_ => self.colours.filekinds.normal,
|
||||
pub trait Colours: FiletypeColours {
|
||||
fn broken_arrow(&self) -> Style;
|
||||
fn broken_filename(&self) -> Style;
|
||||
fn normal_arrow(&self) -> Style;
|
||||
fn control_char(&self) -> Style;
|
||||
fn symlink_path(&self) -> Style;
|
||||
fn executable_file(&self) -> Style;
|
||||
}
|
||||
|
||||
// needs Debug because FileStyle derives it
|
||||
use std::fmt::Debug;
|
||||
use std::marker::Sync;
|
||||
pub trait FileColours: Debug+Sync {
|
||||
fn colour_file(&self, file: &File) -> Option<Style>;
|
||||
}
|
||||
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct NoFileColours;
|
||||
impl FileColours for NoFileColours {
|
||||
fn colour_file(&self, _file: &File) -> Option<Style> { None }
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ use std::io::{Write, Result as IOResult};
|
||||
use term_grid as tg;
|
||||
|
||||
use fs::File;
|
||||
use output::colours::Colours;
|
||||
use style::Colours;
|
||||
use output::file_name::FileStyle;
|
||||
|
||||
|
||||
|
@ -9,8 +9,8 @@ use fs::{Dir, File};
|
||||
use fs::feature::xattr::FileAttributes;
|
||||
use fs::filter::FileFilter;
|
||||
|
||||
use style::Colours;
|
||||
use output::cell::TextCell;
|
||||
use output::colours::Colours;
|
||||
use output::details::{Options as DetailsOptions, Row as DetailsRow, Render as DetailsRender};
|
||||
use output::grid::Options as GridOptions;
|
||||
use output::file_name::FileStyle;
|
||||
|
@ -3,9 +3,8 @@ use std::io::{Write, Result as IOResult};
|
||||
use ansi_term::ANSIStrings;
|
||||
|
||||
use fs::File;
|
||||
|
||||
use output::file_name::{FileName, FileStyle};
|
||||
use super::colours::Colours;
|
||||
use style::Colours;
|
||||
|
||||
|
||||
/// The lines view literally just displays each file, line-by-line.
|
||||
@ -25,7 +24,7 @@ impl<'a> Render<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_file<'f>(&self, file: &'f File<'a>) -> FileName<'f, 'a> {
|
||||
fn render_file<'f>(&self, file: &'f File<'a>) -> FileName<'f, 'a, Colours> {
|
||||
self.style.for_file(file, self.colours).with_link_paths()
|
||||
}
|
||||
}
|
||||
|
@ -1,148 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use ansi_term::Style;
|
||||
use ansi_term::Colour::*;
|
||||
|
||||
|
||||
pub struct LSColors<'var> {
|
||||
contents: HashMap<&'var str, &'var str>
|
||||
}
|
||||
|
||||
impl<'var> LSColors<'var> {
|
||||
pub fn parse(input: &'var str) -> LSColors<'var> {
|
||||
let contents = input.split(":")
|
||||
.flat_map(|mapping| {
|
||||
|
||||
let bits = mapping.split("=")
|
||||
.take(3)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if bits.len() != 2 || bits[0].is_empty() || bits[1].is_empty() { None }
|
||||
else { Some( (bits[0], bits[1]) ) }
|
||||
}).collect();
|
||||
LSColors { contents }
|
||||
}
|
||||
|
||||
pub fn get(&self, facet_name: &str) -> Option<Style> {
|
||||
self.contents.get(facet_name).map(ansi_to_style)
|
||||
}
|
||||
}
|
||||
|
||||
fn ansi_to_style(ansi: &&str) -> Style {
|
||||
let mut style = Style::default();
|
||||
|
||||
for num in ansi.split(";") {
|
||||
match num {
|
||||
|
||||
// Bold and italic
|
||||
"1" => style = style.bold(),
|
||||
"4" => style = style.underline(),
|
||||
|
||||
// Foreground colours
|
||||
"30" => style = style.fg(Black),
|
||||
"31" => style = style.fg(Red),
|
||||
"32" => style = style.fg(Green),
|
||||
"33" => style = style.fg(Yellow),
|
||||
"34" => style = style.fg(Blue),
|
||||
"35" => style = style.fg(Purple),
|
||||
"36" => style = style.fg(Cyan),
|
||||
"37" => style = style.fg(White),
|
||||
|
||||
// Background colours
|
||||
"40" => style = style.on(Black),
|
||||
"41" => style = style.on(Red),
|
||||
"42" => style = style.on(Green),
|
||||
"43" => style = style.on(Yellow),
|
||||
"44" => style = style.on(Blue),
|
||||
"45" => style = style.on(Purple),
|
||||
"46" => style = style.on(Cyan),
|
||||
"47" => style = style.on(White),
|
||||
_ => {/* ignore the error and do nothing */},
|
||||
}
|
||||
}
|
||||
|
||||
style
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod ansi_test {
|
||||
use super::*;
|
||||
use ansi_term::Style;
|
||||
|
||||
macro_rules! test {
|
||||
($name:ident: $input:expr => $result:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
assert_eq!(ansi_to_style(&$input), $result);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Styles
|
||||
test!(bold: "1" => Style::default().bold());
|
||||
test!(under: "4" => Style::default().underline());
|
||||
test!(both: "1;4" => Style::default().bold().underline());
|
||||
test!(fg: "31" => Red.normal());
|
||||
test!(bg: "43" => Style::default().on(Yellow));
|
||||
test!(bfg: "31;43" => Red.on(Yellow));
|
||||
test!(all: "43;31;1;4" => Red.on(Yellow).bold().underline());
|
||||
test!(again: "1;1;1;1;1" => Style::default().bold());
|
||||
|
||||
// Failure cases
|
||||
test!(empty: "" => Style::default());
|
||||
test!(semis: ";;;;;;" => Style::default());
|
||||
test!(nines: "99999999" => Style::default());
|
||||
test!(word: "GREEN" => Style::default());
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
macro_rules! test {
|
||||
($name:ident: $input:expr, $facet:expr => $result:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let lsc = LSColors::parse($input);
|
||||
assert_eq!(lsc.get($facet), $result.into());
|
||||
assert_eq!(lsc.get(""), None);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Bad parses
|
||||
test!(empty: "", "di" => None);
|
||||
test!(jibber: "blah", "di" => None);
|
||||
|
||||
test!(equals: "=", "di" => None);
|
||||
test!(starts: "=di", "di" => None);
|
||||
test!(ends: "id=", "id" => None);
|
||||
|
||||
// Foreground colours
|
||||
test!(red: "di=31", "di" => Red.normal());
|
||||
test!(green: "cb=32", "cb" => Green.normal());
|
||||
test!(blue: "la=34", "la" => Blue.normal());
|
||||
|
||||
// Background colours
|
||||
test!(yellow: "do=43", "do" => Style::default().on(Yellow));
|
||||
test!(purple: "re=45", "re" => Style::default().on(Purple));
|
||||
test!(cyan: "mi=46", "mi" => Style::default().on(Cyan));
|
||||
|
||||
// Bold and underline
|
||||
test!(bold: "fa=1", "fa" => Style::default().bold());
|
||||
test!(under: "so=4", "so" => Style::default().underline());
|
||||
test!(both: "la=1;4", "la" => Style::default().bold().underline());
|
||||
|
||||
// More and many
|
||||
test!(more_1: "me=43;21;55;34:yu=1;4;1", "me" => Blue.on(Yellow));
|
||||
test!(more_2: "me=43;21;55;34:yu=1;4;1", "yu" => Style::default().bold().underline());
|
||||
|
||||
test!(many_1: "red=31:green=32:blue=34", "red" => Red.normal());
|
||||
test!(many_2: "red=31:green=32:blue=34", "green" => Green.normal());
|
||||
test!(many_3: "red=31:green=32:blue=34", "blue" => Blue.normal());
|
||||
}
|
@ -1,23 +1,20 @@
|
||||
use output::file_name::FileStyle;
|
||||
use style::Colours;
|
||||
|
||||
pub use self::cell::{TextCell, TextCellContents, DisplayWidth};
|
||||
pub use self::colours::Colours;
|
||||
pub use self::escape::escape;
|
||||
pub use self::lsc::LSColors;
|
||||
|
||||
pub mod details;
|
||||
pub mod file_name;
|
||||
pub mod grid_details;
|
||||
pub mod grid;
|
||||
pub mod lines;
|
||||
pub mod lsc;
|
||||
pub mod render;
|
||||
pub mod table;
|
||||
pub mod time;
|
||||
|
||||
mod cell;
|
||||
mod colours;
|
||||
mod escape;
|
||||
mod render;
|
||||
mod tree;
|
||||
|
||||
|
||||
|
@ -10,14 +10,12 @@ use locale;
|
||||
|
||||
use users::UsersCache;
|
||||
|
||||
use style::Colours;
|
||||
use output::cell::TextCell;
|
||||
use output::colours::Colours;
|
||||
use output::time::TimeFormat;
|
||||
|
||||
use fs::{File, Dir, fields as f};
|
||||
|
||||
|
||||
|
||||
/// Options for displaying a table.
|
||||
pub struct Options {
|
||||
pub env: Environment,
|
||||
|
@ -2,14 +2,17 @@ use ansi_term::Style;
|
||||
use ansi_term::Colour::{Red, Green, Yellow, Blue, Cyan, Purple, Fixed};
|
||||
|
||||
use output::render;
|
||||
use output::file_name::Colours as FileNameColours;
|
||||
|
||||
use style::lsc::Pair;
|
||||
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct Colours {
|
||||
pub colourful: bool,
|
||||
pub scale: bool,
|
||||
|
||||
pub filekinds: FileKinds,
|
||||
pub filetypes: FileTypes,
|
||||
pub perms: Permissions,
|
||||
pub size: Size,
|
||||
pub users: Users,
|
||||
@ -28,7 +31,6 @@ pub struct Colours {
|
||||
pub control_char: Style,
|
||||
}
|
||||
|
||||
// Colours for files depending on their filesystem type.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct FileKinds {
|
||||
pub normal: Style,
|
||||
@ -42,21 +44,6 @@ pub struct FileKinds {
|
||||
pub executable: Style,
|
||||
}
|
||||
|
||||
// Colours for files depending on their name or extension.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct FileTypes {
|
||||
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,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Permissions {
|
||||
pub user_read: Style,
|
||||
@ -123,6 +110,7 @@ impl Colours {
|
||||
|
||||
pub fn colourful(scale: bool) -> Colours {
|
||||
Colours {
|
||||
colourful: true,
|
||||
scale: scale,
|
||||
|
||||
filekinds: FileKinds {
|
||||
@ -137,19 +125,6 @@ impl Colours {
|
||||
executable: Green.bold(),
|
||||
},
|
||||
|
||||
filetypes: FileTypes {
|
||||
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: Fixed(244).normal(),
|
||||
immediate: Yellow.bold().underline(),
|
||||
compiled: Fixed(137).normal(),
|
||||
},
|
||||
|
||||
perms: Permissions {
|
||||
user_read: Yellow.bold(),
|
||||
user_write: Red.bold(),
|
||||
@ -219,6 +194,83 @@ impl Colours {
|
||||
}
|
||||
|
||||
|
||||
impl Colours {
|
||||
pub fn set_ls(&mut self, pair: &Pair) {
|
||||
match pair.key {
|
||||
"di" => self.filekinds.directory = pair.to_style(),
|
||||
"ex" => self.filekinds.executable = pair.to_style(),
|
||||
"fi" => self.filekinds.normal = pair.to_style(),
|
||||
"pi" => self.filekinds.pipe = pair.to_style(),
|
||||
"so" => self.filekinds.socket = pair.to_style(),
|
||||
"bd" => self.filekinds.block_device = pair.to_style(),
|
||||
"cd" => self.filekinds.char_device = pair.to_style(),
|
||||
"ln" => self.filekinds.symlink = pair.to_style(),
|
||||
"or" => self.broken_arrow = pair.to_style(),
|
||||
"mi" => self.broken_filename = pair.to_style(),
|
||||
_ => {/* don’t change anything */},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_exa(&mut self, pair: &Pair) {
|
||||
match pair.key {
|
||||
"di" => self.filekinds.directory = pair.to_style(),
|
||||
"ex" => self.filekinds.executable = pair.to_style(),
|
||||
"fi" => self.filekinds.normal = pair.to_style(),
|
||||
"pi" => self.filekinds.pipe = pair.to_style(),
|
||||
"so" => self.filekinds.socket = pair.to_style(),
|
||||
"bd" => self.filekinds.block_device = pair.to_style(),
|
||||
"cd" => self.filekinds.char_device = pair.to_style(),
|
||||
"ln" => self.filekinds.symlink = pair.to_style(),
|
||||
"or" => self.broken_arrow = pair.to_style(),
|
||||
"mi" => self.broken_filename = pair.to_style(),
|
||||
|
||||
"ur" => self.perms.user_read = pair.to_style(),
|
||||
"uw" => self.perms.user_write = pair.to_style(),
|
||||
"ux" => self.perms.user_execute_file = pair.to_style(),
|
||||
"ue" => self.perms.user_execute_other = pair.to_style(),
|
||||
"gr" => self.perms.group_read = pair.to_style(),
|
||||
"gw" => self.perms.group_write = pair.to_style(),
|
||||
"gx" => self.perms.group_execute = pair.to_style(),
|
||||
"tr" => self.perms.other_read = pair.to_style(),
|
||||
"tw" => self.perms.other_write = pair.to_style(),
|
||||
"tx" => self.perms.other_execute = pair.to_style(),
|
||||
"su" => self.perms.special_user_file = pair.to_style(),
|
||||
"sf" => self.perms.special_other = pair.to_style(),
|
||||
"xa" => self.perms.attribute = pair.to_style(),
|
||||
|
||||
"sn" => self.size.numbers = pair.to_style(),
|
||||
"sb" => self.size.unit = pair.to_style(),
|
||||
"df" => self.size.major = pair.to_style(),
|
||||
"ds" => self.size.minor = pair.to_style(),
|
||||
|
||||
"uu" => self.users.user_you = pair.to_style(),
|
||||
"un" => self.users.user_someone_else = pair.to_style(),
|
||||
"gu" => self.users.group_yours = pair.to_style(),
|
||||
"gn" => self.users.group_not_yours = pair.to_style(),
|
||||
|
||||
"lc" => self.links.normal = pair.to_style(),
|
||||
"lm" => self.links.multi_link_file = pair.to_style(),
|
||||
|
||||
"ga" => self.git.new = pair.to_style(),
|
||||
"gm" => self.git.modified = pair.to_style(),
|
||||
"gd" => self.git.deleted = pair.to_style(),
|
||||
"gv" => self.git.renamed = pair.to_style(),
|
||||
"gt" => self.git.typechange = pair.to_style(),
|
||||
|
||||
"xx" => self.punctuation = pair.to_style(),
|
||||
"da" => self.date = pair.to_style(),
|
||||
"in" => self.inode = pair.to_style(),
|
||||
"bl" => self.blocks = pair.to_style(),
|
||||
"hd" => self.header = pair.to_style(),
|
||||
"lp" => self.symlink_path = pair.to_style(),
|
||||
"cc" => self.control_char = pair.to_style(),
|
||||
|
||||
_ => {/* still don’t change anything */},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl render::BlocksColours for Colours {
|
||||
fn block_count(&self) -> Style { self.blocks }
|
||||
fn no_blocks(&self) -> Style { self.punctuation }
|
||||
@ -307,3 +359,12 @@ impl render::UserColours for Colours {
|
||||
fn someone_else(&self) -> Style { self.users.user_someone_else }
|
||||
}
|
||||
|
||||
impl FileNameColours for Colours {
|
||||
fn broken_arrow(&self) -> Style { self.broken_arrow }
|
||||
fn broken_filename(&self) -> Style { self.broken_filename }
|
||||
fn normal_arrow(&self) -> Style { self.punctuation }
|
||||
fn control_char(&self) -> Style { self.control_char }
|
||||
fn symlink_path(&self) -> Style { self.symlink_path }
|
||||
fn executable_file(&self) -> Style { self.filekinds.executable }
|
||||
}
|
||||
|
141
src/style/lsc.rs
Normal file
141
src/style/lsc.rs
Normal file
@ -0,0 +1,141 @@
|
||||
use std::ops::FnMut;
|
||||
|
||||
use ansi_term::Style;
|
||||
use ansi_term::Colour::*;
|
||||
|
||||
|
||||
pub struct LSColors<'var>(pub &'var str);
|
||||
|
||||
impl<'var> LSColors<'var> {
|
||||
pub fn each_pair<C>(&mut self, mut callback: C) where C: FnMut(Pair<'var>) -> () {
|
||||
for next in self.0.split(":") {
|
||||
let bits = next.split("=")
|
||||
.take(3)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if bits.len() == 2 && !bits[0].is_empty() && !bits[1].is_empty() {
|
||||
callback(Pair { key: bits[0], value: bits[1] });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Pair<'var> {
|
||||
pub key: &'var str,
|
||||
pub value: &'var str,
|
||||
}
|
||||
|
||||
impl<'var> Pair<'var> {
|
||||
pub fn to_style(&self) -> Style {
|
||||
let mut style = Style::default();
|
||||
|
||||
for num in self.value.split(";") {
|
||||
match num {
|
||||
|
||||
// Bold and italic
|
||||
"1" => style = style.bold(),
|
||||
"4" => style = style.underline(),
|
||||
|
||||
// Foreground colours
|
||||
"30" => style = style.fg(Black),
|
||||
"31" => style = style.fg(Red),
|
||||
"32" => style = style.fg(Green),
|
||||
"33" => style = style.fg(Yellow),
|
||||
"34" => style = style.fg(Blue),
|
||||
"35" => style = style.fg(Purple),
|
||||
"36" => style = style.fg(Cyan),
|
||||
"37" => style = style.fg(White),
|
||||
|
||||
// Background colours
|
||||
"40" => style = style.on(Black),
|
||||
"41" => style = style.on(Red),
|
||||
"42" => style = style.on(Green),
|
||||
"43" => style = style.on(Yellow),
|
||||
"44" => style = style.on(Blue),
|
||||
"45" => style = style.on(Purple),
|
||||
"46" => style = style.on(Cyan),
|
||||
"47" => style = style.on(White),
|
||||
_ => {/* ignore the error and do nothing */},
|
||||
}
|
||||
}
|
||||
|
||||
style
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod ansi_test {
|
||||
use super::*;
|
||||
use ansi_term::Style;
|
||||
|
||||
macro_rules! test {
|
||||
($name:ident: $input:expr => $result:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
assert_eq!(Pair { key: "", value: $input }.to_style(), $result);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Styles
|
||||
test!(bold: "1" => Style::default().bold());
|
||||
test!(under: "4" => Style::default().underline());
|
||||
test!(both: "1;4" => Style::default().bold().underline());
|
||||
test!(fg: "31" => Red.normal());
|
||||
test!(bg: "43" => Style::default().on(Yellow));
|
||||
test!(bfg: "31;43" => Red.on(Yellow));
|
||||
test!(all: "43;31;1;4" => Red.on(Yellow).bold().underline());
|
||||
test!(again: "1;1;1;1;1" => Style::default().bold());
|
||||
|
||||
// Failure cases
|
||||
test!(empty: "" => Style::default());
|
||||
test!(semis: ";;;;;;" => Style::default());
|
||||
test!(nines: "99999999" => Style::default());
|
||||
test!(word: "GREEN" => Style::default());
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
macro_rules! test {
|
||||
($name:ident: $input:expr => $result:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let mut lscs = Vec::new();
|
||||
LSColors($input).each_pair(|p| lscs.push( (p.key.clone(), p.to_style()) ));
|
||||
assert_eq!(lscs, $result.to_vec());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Bad parses
|
||||
test!(empty: "" => []);
|
||||
test!(jibber: "blah" => []);
|
||||
|
||||
test!(equals: "=" => []);
|
||||
test!(starts: "=di" => []);
|
||||
test!(ends: "id=" => []);
|
||||
|
||||
// Foreground colours
|
||||
test!(green: "cb=32" => [ ("cb", Green.normal()) ]);
|
||||
test!(red: "di=31" => [ ("di", Red.normal()) ]);
|
||||
test!(blue: "la=34" => [ ("la", Blue.normal()) ]);
|
||||
|
||||
// Background colours
|
||||
test!(yellow: "do=43" => [ ("do", Style::default().on(Yellow)) ]);
|
||||
test!(purple: "re=45" => [ ("re", Style::default().on(Purple)) ]);
|
||||
test!(cyan: "mi=46" => [ ("mi", Style::default().on(Cyan)) ]);
|
||||
|
||||
// Bold and underline
|
||||
test!(bold: "fa=1" => [ ("fa", Style::default().bold()) ]);
|
||||
test!(under: "so=4" => [ ("so", Style::default().underline()) ]);
|
||||
test!(both: "la=1;4" => [ ("la", Style::default().bold().underline()) ]);
|
||||
|
||||
// More and many
|
||||
test!(more: "me=43;21;55;34:yu=1;4;1" => [ ("me", Blue.on(Yellow)), ("yu", Style::default().bold().underline()) ]);
|
||||
test!(many: "red=31:green=32:blue=34" => [ ("red", Red.normal()), ("green", Green.normal()), ("blue", Blue.normal()) ]);
|
||||
}
|
5
src/style/mod.rs
Normal file
5
src/style/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod colours;
|
||||
pub use self::colours::Colours;
|
||||
|
||||
mod lsc;
|
||||
pub use self::lsc::LSColors;
|
6
xtests/file-names-exts-bw
Normal file
6
xtests/file-names-exts-bw
Normal file
@ -0,0 +1,6 @@
|
||||
#SAVEFILE# compressed.deb crypto.asc image.svg VIDEO.AVI
|
||||
backup~ compressed.tar.gz crypto.signature lossless.flac video.wmv
|
||||
compiled.class compressed.tar.xz document.pdf lossless.wav
|
||||
compiled.coffee compressed.tgz DOCUMENT.XLSX Makefile
|
||||
compiled.js compressed.txz file.tmp music.mp3
|
||||
compiled.o COMPRESSED.ZIP IMAGE.PNG MUSIC.OGG
|
6
xtests/file_names_bw
Normal file
6
xtests/file_names_bw
Normal file
@ -0,0 +1,6 @@
|
||||
ansi: [\u{1b}[34mblue\u{1b}[0m] form-feed: [\u{c}] new-line-dir: [\n]
|
||||
ascii: hello invalid-utf8-1: [<5B>] new-line: [\n]
|
||||
backspace: [\u{8}] invalid-utf8-2: [<5B>(] return: [\r]
|
||||
bell: [\u{7}] invalid-utf8-3: [<5B>(] tab: [\t]
|
||||
emoji: [🆒] invalid-utf8-4: [<5B>(<28>(] utf-8: pâté
|
||||
escape: [\u{1b}] links vertical-tab: [\u{b}]
|
@ -32,6 +32,10 @@ export EXA_STRICT="1"
|
||||
# We also don’t want to see reams and reams of debug output.
|
||||
export EXA_DEBUG=""
|
||||
|
||||
# And default colours by default
|
||||
export LS_COLORS=""
|
||||
export EXA_COLORS=""
|
||||
|
||||
|
||||
# Check that no files were created more than a year ago.
|
||||
# Files not from the current year use a different date format, meaning
|
||||
@ -178,6 +182,10 @@ COLUMNS=80 $exa_binary --colour=always $testcases/files -l | diff -q - $resul
|
||||
COLUMNS=80 $exa_binary --colour=never $testcases/files -l | diff -q - $results/files_l_bw || exit 1
|
||||
COLUMNS=80 $exa_binary --colour=automatic $testcases/files -l | diff -q - $results/files_l_bw || exit 1
|
||||
|
||||
# Switching colour off
|
||||
COLUMNS=80 $exa_binary --colour=never $testcases/file-names | diff -q - $results/file_names_bw || exit 1
|
||||
COLUMNS=80 $exa_binary --colour=never $testcases/file-names-exts | diff -q - $results/file-names-exts-bw || exit 1
|
||||
|
||||
|
||||
# Git
|
||||
$exa $testcases/git/additions -l --git 2>&1 | diff -q - $results/git_additions || exit 1
|
||||
|
Loading…
x
Reference in New Issue
Block a user