mirror of
https://github.com/Llewellynvdm/exa.git
synced 2024-11-21 19:45:11 +00:00
Batch source formatting
I read through every file and applied a couple of rustfmt suggestions. The brace placement and alignment of items on similar lines has been made consistent, even if neither are rustfmt's default style (a file has been put in place to enforce this). Other changes are: • Alphabetical imports and modules • Comma placement at the end of match blocks • Use newlines and indentation judiciously • Spaces around associated types • Spaces after negations (it makes it more clear imho) • Comment formatting • Use early-returns and Optional `?` where appropriate
This commit is contained in:
parent
c3c39fee0a
commit
f8df02dae7
1
.rustfmt.toml
Normal file
1
.rustfmt.toml
Normal file
@ -0,0 +1 @@
|
||||
disable_all_formatting = true
|
6
build.rs
6
build.rs
@ -11,8 +11,9 @@
|
||||
/// - https://crates.io/crates/vergen
|
||||
|
||||
extern crate datetime;
|
||||
use std::io::Result as IOResult;
|
||||
use std::env;
|
||||
use std::io::Result as IOResult;
|
||||
|
||||
|
||||
fn git_hash() -> String {
|
||||
use std::process::Command;
|
||||
@ -52,7 +53,8 @@ fn write_statics() -> IOResult<()> {
|
||||
|
||||
let ver = if is_development_version() {
|
||||
format!("exa v{} ({} built on {})", cargo_version(), git_hash(), build_date())
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
format!("exa v{}", cargo_version())
|
||||
};
|
||||
|
||||
|
@ -10,7 +10,7 @@ use log::*;
|
||||
use crate::fs::File;
|
||||
|
||||
|
||||
/// A **Dir** provides a cached list of the file paths in a directory that's
|
||||
/// A **Dir** provides a cached list of the file paths in a directory that’s
|
||||
/// being listed.
|
||||
///
|
||||
/// This object gets passed to the Files themselves, in order for them to
|
||||
@ -39,8 +39,8 @@ impl Dir {
|
||||
info!("Reading directory {:?}", &path);
|
||||
|
||||
let contents = fs::read_dir(&path)?
|
||||
.map(|result| result.map(|entry| entry.path()))
|
||||
.collect::<Result<_,_>>()?;
|
||||
.map(|result| result.map(|entry| entry.path()))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
Ok(Self { contents, path })
|
||||
}
|
||||
@ -53,7 +53,8 @@ impl Dir {
|
||||
dir: self,
|
||||
dotfiles: dots.shows_dotfiles(),
|
||||
dots: dots.dots(),
|
||||
git, git_ignoring,
|
||||
git,
|
||||
git_ignoring,
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,7 +107,9 @@ impl<'dir, 'ig> Files<'dir, 'ig> {
|
||||
loop {
|
||||
if let Some(path) = self.inner.next() {
|
||||
let filename = File::filename(path);
|
||||
if !self.dotfiles && filename.starts_with('.') { continue }
|
||||
if ! self.dotfiles && filename.starts_with('.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.git_ignoring {
|
||||
let git_status = self.git.map(|g| g.get(path, false)).unwrap_or_default();
|
||||
@ -139,7 +142,6 @@ enum DotsNext {
|
||||
Files,
|
||||
}
|
||||
|
||||
|
||||
impl<'dir, 'ig> Iterator for Files<'dir, 'ig> {
|
||||
type Item = Result<File<'dir>, (PathBuf, io::Error)>;
|
||||
|
||||
@ -149,22 +151,24 @@ impl<'dir, 'ig> Iterator for Files<'dir, 'ig> {
|
||||
self.dots = DotsNext::DotDot;
|
||||
Some(File::new_aa_current(self.dir)
|
||||
.map_err(|e| (Path::new(".").to_path_buf(), e)))
|
||||
},
|
||||
}
|
||||
|
||||
DotsNext::DotDot => {
|
||||
self.dots = DotsNext::Files;
|
||||
Some(File::new_aa_parent(self.parent(), self.dir)
|
||||
.map_err(|e| (self.parent(), e)))
|
||||
},
|
||||
}
|
||||
|
||||
DotsNext::Files => {
|
||||
self.next_visible_file()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Usually files in Unix use a leading dot to be hidden or visible, but two
|
||||
/// entries in particular are "extra-hidden": `.` and `..`, which only become
|
||||
/// entries in particular are “extra-hidden”: `.` and `..`, which only become
|
||||
/// visible after an extra `-a` option.
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
pub enum DotFilter {
|
||||
@ -199,9 +203,9 @@ impl DotFilter {
|
||||
/// Whether this filter should add dot directories to a listing.
|
||||
fn dots(self) -> DotsNext {
|
||||
match self {
|
||||
Self::JustFiles => DotsNext::Files,
|
||||
Self::Dotfiles => DotsNext::Files,
|
||||
Self::DotfilesAndDots => DotsNext::Dot,
|
||||
Self::JustFiles => DotsNext::Files,
|
||||
Self::Dotfiles => DotsNext::Files,
|
||||
Self::DotfilesAndDots => DotsNext::Dot,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,9 @@ impl GitCache {
|
||||
|
||||
use std::iter::FromIterator;
|
||||
impl FromIterator<PathBuf> for GitCache {
|
||||
fn from_iter<I: IntoIterator<Item=PathBuf>>(iter: I) -> Self {
|
||||
fn from_iter<I>(iter: I) -> Self
|
||||
where I: IntoIterator<Item=PathBuf>
|
||||
{
|
||||
let iter = iter.into_iter();
|
||||
let mut git = Self {
|
||||
repos: Vec::with_capacity(iter.size_hint().0),
|
||||
@ -61,8 +63,10 @@ impl FromIterator<PathBuf> for GitCache {
|
||||
|
||||
debug!("Discovered new Git repo");
|
||||
git.repos.push(r);
|
||||
},
|
||||
Err(miss) => git.misses.push(miss),
|
||||
}
|
||||
Err(miss) => {
|
||||
git.misses.push(miss)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -72,8 +76,6 @@ impl FromIterator<PathBuf> for GitCache {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// A **Git repository** is one we’ve discovered somewhere on the filesystem.
|
||||
pub struct GitRepo {
|
||||
|
||||
@ -99,7 +101,9 @@ pub struct GitRepo {
|
||||
enum GitContents {
|
||||
|
||||
/// All the interesting Git stuff goes through this.
|
||||
Before { repo: git2::Repository },
|
||||
Before {
|
||||
repo: git2::Repository,
|
||||
},
|
||||
|
||||
/// Temporary value used in `repo_to_statuses` so we can move the
|
||||
/// repository out of the `Before` variant.
|
||||
@ -107,7 +111,9 @@ enum GitContents {
|
||||
|
||||
/// The data we’ve extracted from the repository, but only after we’ve
|
||||
/// actually done so.
|
||||
After { statuses: Git }
|
||||
After {
|
||||
statuses: Git,
|
||||
},
|
||||
}
|
||||
|
||||
impl GitRepo {
|
||||
@ -116,7 +122,7 @@ impl GitRepo {
|
||||
/// depending on the prefix-lookup flag) and returns its Git status.
|
||||
///
|
||||
/// Actually querying the `git2` repository for the mapping of paths to
|
||||
/// Git statuses is only done once, and gets cached so we don't need to
|
||||
/// Git statuses is only done once, and gets cached so we don’t need to
|
||||
/// re-query the entire repository the times after that.
|
||||
///
|
||||
/// The temporary `Processing` enum variant is used after the `git2`
|
||||
@ -166,7 +172,7 @@ impl GitRepo {
|
||||
let workdir = workdir.to_path_buf();
|
||||
let contents = Mutex::new(GitContents::Before { repo });
|
||||
Ok(Self { contents, workdir, original_path: path, extra_paths: Vec::new() })
|
||||
},
|
||||
}
|
||||
None => {
|
||||
warn!("Repository has no workdir?");
|
||||
Err(path)
|
||||
@ -205,8 +211,10 @@ fn repo_to_statuses(repo: &git2::Repository, workdir: &Path) -> Git {
|
||||
let elem = (path, e.status());
|
||||
statuses.push(elem);
|
||||
}
|
||||
},
|
||||
Err(e) => error!("Error looking up Git statuses: {:?}", e),
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error looking up Git statuses: {:?}", e)
|
||||
}
|
||||
}
|
||||
|
||||
Git { statuses }
|
||||
@ -217,7 +225,7 @@ fn repo_to_statuses(repo: &git2::Repository, workdir: &Path) -> Git {
|
||||
// 20.311276 INFO:exa::fs::feature::git: Getting Git statuses for repo with workdir "/vagrant/"
|
||||
// 20.799610 DEBUG:exa::output::table: Getting Git status for file "./Cargo.toml"
|
||||
//
|
||||
// Even inserting another logging line immediately afterwards doesn't make it
|
||||
// Even inserting another logging line immediately afterwards doesn’t make it
|
||||
// look any faster.
|
||||
|
||||
|
||||
@ -239,6 +247,7 @@ impl Git {
|
||||
/// Get the status for the file at the given path.
|
||||
fn file_status(&self, file: &Path) -> f::Git {
|
||||
let path = reorient(file);
|
||||
|
||||
self.statuses.iter()
|
||||
.find(|p| p.0.as_path() == path)
|
||||
.map(|&(_, s)| f::Git { staged: index_status(s), unstaged: working_tree_status(s) })
|
||||
@ -250,25 +259,31 @@ impl Git {
|
||||
/// directories, which don’t really have an ‘official’ status.
|
||||
fn dir_status(&self, dir: &Path) -> f::Git {
|
||||
let path = reorient(dir);
|
||||
let s = self.statuses.iter()
|
||||
.filter(|p| p.0.starts_with(&path))
|
||||
.fold(git2::Status::empty(), |a, b| a | b.1);
|
||||
|
||||
f::Git { staged: index_status(s), unstaged: working_tree_status(s) }
|
||||
let s = self.statuses.iter()
|
||||
.filter(|p| p.0.starts_with(&path))
|
||||
.fold(git2::Status::empty(), |a, b| a | b.1);
|
||||
|
||||
let staged = index_status(s);
|
||||
let unstaged = working_tree_status(s);
|
||||
f::Git { staged, unstaged }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Converts a path to an absolute path based on the current directory.
|
||||
/// Paths need to be absolute for them to be compared properly, otherwise
|
||||
/// you’d ask a repo about “./README.md” but it only knows about
|
||||
/// “/vagrant/README.md”, prefixed by the workdir.
|
||||
fn reorient(path: &Path) -> PathBuf {
|
||||
use std::env::current_dir;
|
||||
// I’m not 100% on this func tbh
|
||||
|
||||
// TODO: I’m not 100% on this func tbh
|
||||
let path = match current_dir() {
|
||||
Err(_) => Path::new(".").join(&path),
|
||||
Ok(dir) => dir.join(&path),
|
||||
Err(_) => Path::new(".").join(&path),
|
||||
Ok(dir) => dir.join(&path),
|
||||
};
|
||||
|
||||
path.canonicalize().unwrap_or(path)
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
pub mod xattr;
|
||||
|
||||
#[cfg(feature="git")] pub mod git;
|
||||
#[cfg(feature = "git")]
|
||||
pub mod git;
|
||||
|
||||
#[cfg(not(feature="git"))]
|
||||
#[cfg(not(feature = "git"))]
|
||||
pub mod git {
|
||||
use std::iter::FromIterator;
|
||||
use std::path::{Path, PathBuf};
|
||||
@ -13,8 +14,10 @@ pub mod git {
|
||||
pub struct GitCache;
|
||||
|
||||
impl FromIterator<PathBuf> for GitCache {
|
||||
fn from_iter<I: IntoIterator<Item=PathBuf>>(_iter: I) -> Self {
|
||||
GitCache
|
||||
fn from_iter<I>(_iter: I) -> Self
|
||||
where I: IntoIterator<Item=PathBuf>
|
||||
{
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
//! Extended attribute support for Darwin and Linux systems.
|
||||
|
||||
#![allow(trivial_casts)] // for ARM
|
||||
extern crate libc;
|
||||
|
||||
@ -6,7 +7,11 @@ use std::cmp::Ordering;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
pub const ENABLED: bool = cfg!(feature="git") && cfg!(any(target_os="macos", target_os="linux"));
|
||||
|
||||
pub const ENABLED: bool =
|
||||
cfg!(feature="git") &&
|
||||
cfg!(any(target_os = "macos", target_os = "linux"));
|
||||
|
||||
|
||||
pub trait FileAttributes {
|
||||
fn attributes(&self) -> io::Result<Vec<Attribute>>;
|
||||
@ -27,20 +32,21 @@ impl FileAttributes for Path {
|
||||
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
|
||||
impl FileAttributes for Path {
|
||||
fn attributes(&self) -> io::Result<Vec<Attribute>> {
|
||||
Ok(vec![])
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
fn symlink_attributes(&self) -> io::Result<Vec<Attribute>> {
|
||||
Ok(vec![])
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Attributes which can be passed to `Attribute::list_with_flags`
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum FollowSymlinks {
|
||||
Yes,
|
||||
No
|
||||
No,
|
||||
}
|
||||
|
||||
/// Extended attribute
|
||||
@ -50,29 +56,32 @@ pub struct Attribute {
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
pub fn list_attrs(lister: &lister::Lister, path: &Path) -> io::Result<Vec<Attribute>> {
|
||||
use std::ffi::CString;
|
||||
|
||||
let c_path = match path.to_str().and_then(|s| { CString::new(s).ok() }) {
|
||||
let c_path = match path.to_str().and_then(|s| CString::new(s).ok()) {
|
||||
Some(cstring) => cstring,
|
||||
None => return Err(io::Error::new(io::ErrorKind::Other, "Error: path somehow contained a NUL?")),
|
||||
None => {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "Error: path somehow contained a NUL?"));
|
||||
}
|
||||
};
|
||||
|
||||
let bufsize = lister.listxattr_first(&c_path);
|
||||
match bufsize.cmp(&0) {
|
||||
Ordering::Less => return Err(io::Error::last_os_error()),
|
||||
Ordering::Equal => return Ok(Vec::new()),
|
||||
Ordering::Greater => {},
|
||||
Ordering::Less => return Err(io::Error::last_os_error()),
|
||||
Ordering::Equal => return Ok(Vec::new()),
|
||||
Ordering::Greater => {},
|
||||
}
|
||||
|
||||
let mut buf = vec![0_u8; bufsize as usize];
|
||||
let err = lister.listxattr_second(&c_path, &mut buf, bufsize);
|
||||
|
||||
match err.cmp(&0) {
|
||||
Ordering::Less => return Err(io::Error::last_os_error()),
|
||||
Ordering::Equal => return Ok(Vec::new()),
|
||||
Ordering::Greater => {},
|
||||
Ordering::Less => return Err(io::Error::last_os_error()),
|
||||
Ordering::Equal => return Ok(Vec::new()),
|
||||
Ordering::Greater => {},
|
||||
}
|
||||
|
||||
let mut names = Vec::new();
|
||||
@ -91,33 +100,40 @@ pub fn list_attrs(lister: &lister::Lister, path: &Path) -> io::Result<Vec<Attrib
|
||||
if size > 0 {
|
||||
names.push(Attribute {
|
||||
name: lister.translate_attribute_name(&buf[start..end]),
|
||||
size: size as usize
|
||||
size: size as usize,
|
||||
});
|
||||
}
|
||||
|
||||
start = c_end;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(names)
|
||||
}
|
||||
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod lister {
|
||||
use std::ffi::CString;
|
||||
use libc::{c_int, size_t, ssize_t, c_char, c_void};
|
||||
use super::FollowSymlinks;
|
||||
use libc::{c_int, size_t, ssize_t, c_char, c_void};
|
||||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
extern "C" {
|
||||
fn listxattr(
|
||||
path: *const c_char, namebuf: *mut c_char,
|
||||
size: size_t, options: c_int
|
||||
path: *const c_char,
|
||||
namebuf: *mut c_char,
|
||||
size: size_t,
|
||||
options: c_int,
|
||||
) -> ssize_t;
|
||||
|
||||
fn getxattr(
|
||||
path: *const c_char, name: *const c_char,
|
||||
value: *mut c_void, size: size_t, position: u32,
|
||||
options: c_int
|
||||
path: *const c_char,
|
||||
name: *const c_char,
|
||||
value: *mut c_void,
|
||||
size: size_t,
|
||||
position: u32,
|
||||
options: c_int,
|
||||
) -> ssize_t;
|
||||
}
|
||||
|
||||
@ -128,24 +144,25 @@ mod lister {
|
||||
impl Lister {
|
||||
pub fn new(do_follow: FollowSymlinks) -> Self {
|
||||
let c_flags: c_int = match do_follow {
|
||||
FollowSymlinks::Yes => 0x0001,
|
||||
FollowSymlinks::No => 0x0000,
|
||||
FollowSymlinks::Yes => 0x0001,
|
||||
FollowSymlinks::No => 0x0000,
|
||||
};
|
||||
|
||||
Self { c_flags }
|
||||
}
|
||||
|
||||
pub fn translate_attribute_name(&self, input: &[u8]) -> String {
|
||||
use std::str::from_utf8_unchecked;
|
||||
|
||||
unsafe {
|
||||
from_utf8_unchecked(input).into()
|
||||
}
|
||||
unsafe { std::str::from_utf8_unchecked(input).into() }
|
||||
}
|
||||
|
||||
pub fn listxattr_first(&self, c_path: &CString) -> ssize_t {
|
||||
unsafe {
|
||||
listxattr(c_path.as_ptr(), ptr::null_mut(), 0, self.c_flags)
|
||||
listxattr(
|
||||
c_path.as_ptr(),
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
self.c_flags,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,7 +171,8 @@ mod lister {
|
||||
listxattr(
|
||||
c_path.as_ptr(),
|
||||
buf.as_mut_ptr() as *mut c_char,
|
||||
bufsize as size_t, self.c_flags
|
||||
bufsize as size_t,
|
||||
self.c_flags,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -164,13 +182,17 @@ mod lister {
|
||||
getxattr(
|
||||
c_path.as_ptr(),
|
||||
buf.as_ptr() as *const c_char,
|
||||
ptr::null_mut(), 0, 0, self.c_flags
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
0,
|
||||
self.c_flags,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod lister {
|
||||
use std::ffi::CString;
|
||||
@ -180,21 +202,29 @@ mod lister {
|
||||
|
||||
extern "C" {
|
||||
fn listxattr(
|
||||
path: *const c_char, list: *mut c_char, size: size_t
|
||||
path: *const c_char,
|
||||
list: *mut c_char,
|
||||
size: size_t,
|
||||
) -> ssize_t;
|
||||
|
||||
fn llistxattr(
|
||||
path: *const c_char, list: *mut c_char, size: size_t
|
||||
path: *const c_char,
|
||||
list: *mut c_char,
|
||||
size: size_t,
|
||||
) -> ssize_t;
|
||||
|
||||
fn getxattr(
|
||||
path: *const c_char, name: *const c_char,
|
||||
value: *mut c_void, size: size_t
|
||||
path: *const c_char,
|
||||
name: *const c_char,
|
||||
value: *mut c_void,
|
||||
size: size_t,
|
||||
) -> ssize_t;
|
||||
|
||||
fn lgetxattr(
|
||||
path: *const c_char, name: *const c_char,
|
||||
value: *mut c_void, size: size_t
|
||||
path: *const c_char,
|
||||
name: *const c_char,
|
||||
value: *mut c_void,
|
||||
size: size_t,
|
||||
) -> ssize_t;
|
||||
}
|
||||
|
||||
@ -213,41 +243,46 @@ mod lister {
|
||||
|
||||
pub fn listxattr_first(&self, c_path: &CString) -> ssize_t {
|
||||
let listxattr = match self.follow_symlinks {
|
||||
FollowSymlinks::Yes => listxattr,
|
||||
FollowSymlinks::No => llistxattr,
|
||||
FollowSymlinks::Yes => listxattr,
|
||||
FollowSymlinks::No => llistxattr,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
listxattr(c_path.as_ptr() as *const _, ptr::null_mut(), 0)
|
||||
listxattr(
|
||||
c_path.as_ptr() as *const _,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listxattr_second(&self, c_path: &CString, buf: &mut Vec<u8>, bufsize: ssize_t) -> ssize_t {
|
||||
let listxattr = match self.follow_symlinks {
|
||||
FollowSymlinks::Yes => listxattr,
|
||||
FollowSymlinks::No => llistxattr,
|
||||
FollowSymlinks::Yes => listxattr,
|
||||
FollowSymlinks::No => llistxattr,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
listxattr(
|
||||
c_path.as_ptr() as *const _,
|
||||
buf.as_mut_ptr() as *mut c_char,
|
||||
bufsize as size_t
|
||||
bufsize as size_t,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getxattr(&self, c_path: &CString, buf: &[u8]) -> ssize_t {
|
||||
let getxattr = match self.follow_symlinks {
|
||||
FollowSymlinks::Yes => getxattr,
|
||||
FollowSymlinks::No => lgetxattr,
|
||||
FollowSymlinks::Yes => getxattr,
|
||||
FollowSymlinks::No => lgetxattr,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
getxattr(
|
||||
c_path.as_ptr() as *const _,
|
||||
buf.as_ptr() as *const c_char,
|
||||
ptr::null_mut(), 0
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
//! Wrapper types for the values returned from `File`s.
|
||||
//!
|
||||
//! The methods of `File` that return information about the entry on the
|
||||
@ -45,7 +44,14 @@ pub type uid_t = u32;
|
||||
/// Its ordering is used when sorting by type.
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
|
||||
pub enum Type {
|
||||
Directory, File, Link, Pipe, Socket, CharDevice, BlockDevice, Special,
|
||||
Directory,
|
||||
File,
|
||||
Link,
|
||||
Pipe,
|
||||
Socket,
|
||||
CharDevice,
|
||||
BlockDevice,
|
||||
Special,
|
||||
}
|
||||
|
||||
impl Type {
|
||||
@ -152,7 +158,7 @@ pub enum Size {
|
||||
/// have a file size. For example, a directory will just contain a list of
|
||||
/// its files as its “contents” and will be specially flagged as being a
|
||||
/// directory, rather than a file. However, seeing the “file size” of this
|
||||
/// data is rarely useful -- I can’t think of a time when I’ve seen it and
|
||||
/// data is rarely useful — I can’t think of a time when I’ve seen it and
|
||||
/// learnt something. So we discard it and just output “-” instead.
|
||||
///
|
||||
/// See this answer for more: http://unix.stackexchange.com/a/68266
|
||||
@ -214,10 +220,11 @@ pub enum GitStatus {
|
||||
/// A file that’s ignored (that matches a line in .gitignore)
|
||||
Ignored,
|
||||
|
||||
/// A file that's updated but unmerged.
|
||||
/// A file that’s updated but unmerged.
|
||||
Conflicted,
|
||||
}
|
||||
|
||||
|
||||
/// A file’s complete Git status. It’s possible to make changes to a file, add
|
||||
/// it to the staging area, then make *more* changes, so we need to list each
|
||||
/// file’s status for both of these.
|
||||
@ -227,11 +234,13 @@ pub struct Git {
|
||||
pub unstaged: GitStatus,
|
||||
}
|
||||
|
||||
use std::default::Default;
|
||||
impl Default for Git {
|
||||
|
||||
/// Create a Git status for a file with nothing done to it.
|
||||
fn default() -> Self {
|
||||
Self { staged: GitStatus::NotModified, unstaged: GitStatus::NotModified }
|
||||
Self {
|
||||
staged: GitStatus::NotModified,
|
||||
unstaged: GitStatus::NotModified,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
use std::io::Error as IOError;
|
||||
use std::io::Result as IOResult;
|
||||
use std::os::unix::fs::{MetadataExt, PermissionsExt, FileTypeExt};
|
||||
use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
@ -11,7 +11,8 @@ use log::*;
|
||||
use crate::fs::dir::Dir;
|
||||
use crate::fs::fields as f;
|
||||
|
||||
/// A **File** is a wrapper around one of Rust's Path objects, along with
|
||||
|
||||
/// A **File** is a wrapper around one of Rust’s `PathBuf` values, along with
|
||||
/// associated data about the file.
|
||||
///
|
||||
/// Each file is definitely going to have its filename displayed at least
|
||||
@ -44,7 +45,7 @@ pub struct File<'dir> {
|
||||
///
|
||||
/// This too is queried multiple times, and is *not* cached by the OS, as
|
||||
/// it could easily change between invocations — but exa is so short-lived
|
||||
/// it's better to just cache it.
|
||||
/// it’s better to just cache it.
|
||||
pub metadata: std::fs::Metadata,
|
||||
|
||||
/// A reference to the directory that contains this file, if any.
|
||||
@ -60,7 +61,7 @@ pub struct File<'dir> {
|
||||
/// Whether this is one of the two `--all all` directories, `.` and `..`.
|
||||
///
|
||||
/// Unlike all other entries, these are not returned as part of the
|
||||
/// directory's children, and are in fact added specifically by exa; this
|
||||
/// directory’s children, and are in fact added specifically by exa; this
|
||||
/// means that they should be skipped when recursing.
|
||||
pub is_all_all: bool,
|
||||
}
|
||||
@ -88,8 +89,9 @@ impl<'dir> File<'dir> {
|
||||
debug!("Statting file {:?}", &path);
|
||||
let metadata = std::fs::symlink_metadata(&path)?;
|
||||
let is_all_all = true;
|
||||
let parent_dir = Some(parent_dir);
|
||||
|
||||
Ok(File { path, parent_dir: Some(parent_dir), metadata, ext, name: ".".to_string(), is_all_all })
|
||||
Ok(File { path, parent_dir, metadata, ext, name: ".".into(), is_all_all })
|
||||
}
|
||||
|
||||
pub fn new_aa_parent(path: PathBuf, parent_dir: &'dir Dir) -> IOResult<File<'dir>> {
|
||||
@ -98,8 +100,9 @@ impl<'dir> File<'dir> {
|
||||
debug!("Statting file {:?}", &path);
|
||||
let metadata = std::fs::symlink_metadata(&path)?;
|
||||
let is_all_all = true;
|
||||
let parent_dir = Some(parent_dir);
|
||||
|
||||
Ok(File { path, parent_dir: Some(parent_dir), metadata, ext, name: "..".to_string(), is_all_all })
|
||||
Ok(File { path, parent_dir, metadata, ext, name: "..".into(), is_all_all })
|
||||
}
|
||||
|
||||
/// A file’s name is derived from its string. This needs to handle directories
|
||||
@ -127,7 +130,9 @@ impl<'dir> File<'dir> {
|
||||
fn ext(path: &Path) -> Option<String> {
|
||||
let name = path.file_name().map(|f| f.to_string_lossy().to_string())?;
|
||||
|
||||
name.rfind('.').map(|p| name[p+1..].to_ascii_lowercase())
|
||||
name.rfind('.')
|
||||
.map(|p| name[p + 1 ..]
|
||||
.to_ascii_lowercase())
|
||||
}
|
||||
|
||||
/// Whether this file is a directory on the filesystem.
|
||||
@ -249,7 +254,8 @@ impl<'dir> File<'dir> {
|
||||
Ok(metadata) => {
|
||||
let ext = File::ext(&path);
|
||||
let name = File::filename(&path);
|
||||
FileTarget::Ok(Box::new(File { parent_dir: None, path, ext, metadata, name, is_all_all: false }))
|
||||
let file = File { parent_dir: None, path, ext, metadata, name, is_all_all: false };
|
||||
FileTarget::Ok(Box::new(file))
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error following link {:?}: {:#?}", &path, e);
|
||||
@ -274,14 +280,14 @@ impl<'dir> File<'dir> {
|
||||
}
|
||||
}
|
||||
|
||||
/// This file's inode.
|
||||
/// This file’s inode.
|
||||
pub fn inode(&self) -> f::Inode {
|
||||
f::Inode(self.metadata.ino())
|
||||
}
|
||||
|
||||
/// This file's number of filesystem blocks.
|
||||
/// This file’s number of filesystem blocks.
|
||||
///
|
||||
/// (Not the size of each block, which we don't actually report on)
|
||||
/// (Not the size of each block, which we don’t actually report on)
|
||||
pub fn blocks(&self) -> f::Blocks {
|
||||
if self.is_file() || self.is_link() {
|
||||
f::Blocks::Some(self.metadata.blocks())
|
||||
@ -334,17 +340,19 @@ impl<'dir> File<'dir> {
|
||||
pub fn changed_time(&self) -> Option<SystemTime> {
|
||||
let (mut sec, mut nsec) = (self.metadata.ctime(), self.metadata.ctime_nsec());
|
||||
|
||||
Some(
|
||||
if sec < 0 {
|
||||
if nsec > 0 {
|
||||
sec += 1;
|
||||
nsec -= 1_000_000_000;
|
||||
}
|
||||
UNIX_EPOCH - Duration::new(sec.abs() as u64, nsec.abs() as u32)
|
||||
} else {
|
||||
UNIX_EPOCH + Duration::new(sec as u64, nsec as u32)
|
||||
}
|
||||
)
|
||||
if sec < 0 {
|
||||
if nsec > 0 {
|
||||
sec += 1;
|
||||
nsec -= 1_000_000_000;
|
||||
}
|
||||
|
||||
let duration = Duration::new(sec.abs() as u64, nsec.abs() as u32);
|
||||
Some(UNIX_EPOCH - duration)
|
||||
}
|
||||
else {
|
||||
let duration = Duration::new(sec as u64, nsec as u32);
|
||||
Some(UNIX_EPOCH + duration)
|
||||
}
|
||||
}
|
||||
|
||||
/// This file’s last accessed timestamp, if available on this platform.
|
||||
@ -392,7 +400,7 @@ impl<'dir> File<'dir> {
|
||||
/// This file’s permissions, with flags for each bit.
|
||||
pub fn permissions(&self) -> f::Permissions {
|
||||
let bits = self.metadata.mode();
|
||||
let has_bit = |bit| { bits & bit == bit };
|
||||
let has_bit = |bit| bits & bit == bit;
|
||||
|
||||
f::Permissions {
|
||||
user_read: has_bit(modes::USER_READ),
|
||||
@ -423,7 +431,7 @@ impl<'dir> File<'dir> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this file's name, including extension, is any of the strings
|
||||
/// Whether this file’s name, including extension, is any of the strings
|
||||
/// that get passed in.
|
||||
pub fn name_is_one_of(&self, choices: &[&str]) -> bool {
|
||||
choices.contains(&&self.name[..])
|
||||
@ -455,7 +463,7 @@ pub enum FileTarget<'dir> {
|
||||
|
||||
// Err is its own variant, instead of having the whole thing be inside an
|
||||
// `IOResult`, because being unable to follow a symlink is not a serious
|
||||
// error -- we just display the error message and move on.
|
||||
// error — we just display the error message and move on.
|
||||
}
|
||||
|
||||
impl<'dir> FileTarget<'dir> {
|
||||
@ -471,9 +479,10 @@ impl<'dir> FileTarget<'dir> {
|
||||
/// More readable aliases for the permission bits exposed by libc.
|
||||
#[allow(trivial_numeric_casts)]
|
||||
mod modes {
|
||||
pub type Mode = u32;
|
||||
|
||||
// The `libc::mode_t` type’s actual type varies, but the value returned
|
||||
// from `metadata.permissions().mode()` is always `u32`.
|
||||
pub type Mode = u32;
|
||||
|
||||
pub const USER_READ: Mode = libc::S_IRUSR as Mode;
|
||||
pub const USER_WRITE: Mode = libc::S_IWUSR as Mode;
|
||||
|
@ -5,8 +5,8 @@ use std::iter::FromIterator;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::fs::File;
|
||||
use crate::fs::DotFilter;
|
||||
use crate::fs::File;
|
||||
|
||||
|
||||
/// The **file filter** processes a list of files before displaying them to
|
||||
@ -88,12 +88,11 @@ pub struct FileFilter {
|
||||
pub git_ignore: GitIgnore,
|
||||
}
|
||||
|
||||
|
||||
impl FileFilter {
|
||||
/// Remove every file in the given vector that does *not* pass the
|
||||
/// filter predicate for files found inside a directory.
|
||||
pub fn filter_child_files(&self, files: &mut Vec<File>) {
|
||||
files.retain(|f| !self.ignore_patterns.is_ignored(&f.name));
|
||||
files.retain(|f| ! self.ignore_patterns.is_ignored(&f.name));
|
||||
|
||||
if self.only_dirs {
|
||||
files.retain(File::is_directory);
|
||||
@ -110,14 +109,18 @@ impl FileFilter {
|
||||
/// `exa -I='*.ogg' music/*` should filter out the ogg files obtained
|
||||
/// from the glob, even though the globbing is done by the shell!
|
||||
pub fn filter_argument_files(&self, files: &mut Vec<File>) {
|
||||
files.retain(|f| !self.ignore_patterns.is_ignored(&f.name));
|
||||
files.retain(|f| {
|
||||
! self.ignore_patterns.is_ignored(&f.name)
|
||||
});
|
||||
}
|
||||
|
||||
/// Sort the files in the given vector based on the sort field option.
|
||||
pub fn sort_files<'a, F>(&self, files: &mut Vec<F>)
|
||||
where F: AsRef<File<'a>> {
|
||||
|
||||
files.sort_by(|a, b| self.sort_field.compare_files(a.as_ref(), b.as_ref()));
|
||||
where F: AsRef<File<'a>>
|
||||
{
|
||||
files.sort_by(|a, b| {
|
||||
self.sort_field.compare_files(a.as_ref(), b.as_ref())
|
||||
});
|
||||
|
||||
if self.reverse {
|
||||
files.reverse();
|
||||
@ -126,8 +129,9 @@ impl FileFilter {
|
||||
if self.list_dirs_first {
|
||||
// This relies on the fact that `sort_by` is *stable*: it will keep
|
||||
// adjacent elements next to each other.
|
||||
files.sort_by(|a, b| {b.as_ref().points_to_directory()
|
||||
.cmp(&a.as_ref().points_to_directory())
|
||||
files.sort_by(|a, b| {
|
||||
b.as_ref().points_to_directory()
|
||||
.cmp(&a.as_ref().points_to_directory())
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -175,13 +179,13 @@ pub enum SortField {
|
||||
/// The time the file was changed (the “ctime”).
|
||||
///
|
||||
/// This field is used to mark the time when a file’s metadata
|
||||
/// changed -- its permissions, owners, or link count.
|
||||
/// changed — its permissions, owners, or link count.
|
||||
///
|
||||
/// In original Unix, this was, however, meant as creation time.
|
||||
/// https://www.bell-labs.com/usr/dmr/www/cacm.html
|
||||
ChangedDate,
|
||||
|
||||
/// The time the file was created (the "btime" or "birthtime").
|
||||
/// The time the file was created (the “btime” or “birthtime”).
|
||||
CreatedDate,
|
||||
|
||||
/// The type of the file: directories, links, pipes, regular, files, etc.
|
||||
@ -280,11 +284,8 @@ impl SortField {
|
||||
}
|
||||
|
||||
fn strip_dot(n: &str) -> &str {
|
||||
if n.starts_with('.') {
|
||||
&n[1..]
|
||||
} else {
|
||||
n
|
||||
}
|
||||
if n.starts_with('.') { &n[1..] }
|
||||
else { n }
|
||||
}
|
||||
}
|
||||
|
||||
@ -298,8 +299,12 @@ pub struct IgnorePatterns {
|
||||
}
|
||||
|
||||
impl FromIterator<glob::Pattern> for IgnorePatterns {
|
||||
fn from_iter<I: IntoIterator<Item = glob::Pattern>>(iter: I) -> Self {
|
||||
Self { patterns: iter.into_iter().collect() }
|
||||
|
||||
fn from_iter<I>(iter: I) -> Self
|
||||
where I: IntoIterator<Item = glob::Pattern>
|
||||
{
|
||||
let patterns = iter.into_iter().collect();
|
||||
Self { patterns }
|
||||
}
|
||||
}
|
||||
|
||||
@ -371,7 +376,6 @@ pub enum GitIgnore {
|
||||
// > usually found in $XDG_CONFIG_HOME/git/ignore.
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_ignores {
|
||||
use super::*;
|
||||
|
@ -4,7 +4,7 @@ pub use self::dir::{Dir, DotFilter};
|
||||
mod file;
|
||||
pub use self::file::{File, FileTarget};
|
||||
|
||||
pub mod dir_action;
|
||||
pub mod feature;
|
||||
pub mod fields;
|
||||
pub mod filter;
|
||||
pub mod dir_action;
|
||||
|
@ -124,11 +124,17 @@ impl FileIcon for FileExtensions {
|
||||
fn icon_file(&self, file: &File) -> Option<char> {
|
||||
use crate::output::icons::Icons;
|
||||
|
||||
Some(match file {
|
||||
f if self.is_music(f) || self.is_lossless(f) => Icons::Audio.value(),
|
||||
f if self.is_image(f) => Icons::Image.value(),
|
||||
f if self.is_video(f) => Icons::Video.value(),
|
||||
_ => return None,
|
||||
})
|
||||
if self.is_music(file) || self.is_lossless(file) {
|
||||
Some(Icons::Audio.value())
|
||||
}
|
||||
else if self.is_image(file) {
|
||||
Some(Icons::Image.value())
|
||||
}
|
||||
else if self.is_video(file) {
|
||||
Some(Icons::Video.value())
|
||||
}
|
||||
else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ impl<'a> File<'a> {
|
||||
/// The point of this is to highlight compiled files such as `foo.js` when
|
||||
/// their source file `foo.coffee` exists in the same directory.
|
||||
/// For example, `foo.js` is perfectly valid without `foo.coffee`, so we
|
||||
/// don't want to always blindly highlight `*.js` as compiled.
|
||||
/// don’t want to always blindly highlight `*.js` as compiled.
|
||||
/// (See also `FileExtensions#is_compiled`)
|
||||
pub fn get_source_files(&self) -> Vec<PathBuf> {
|
||||
if let Some(ext) = &self.ext {
|
||||
@ -34,7 +34,7 @@ impl<'a> File<'a> {
|
||||
}
|
||||
}
|
||||
else {
|
||||
vec![] // No source files if there's no extension, either!
|
||||
vec![] // No source files if there’s no extension, either!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,10 +57,10 @@ impl log::Log for Logger {
|
||||
|
||||
fn level(level: log::Level) -> ANSIString<'static> {
|
||||
match level {
|
||||
log::Level::Error => Colour::Red.paint("ERROR"),
|
||||
log::Level::Warn => Colour::Yellow.paint("WARN"),
|
||||
log::Level::Info => Colour::Cyan.paint("INFO"),
|
||||
log::Level::Debug => Colour::Blue.paint("DEBUG"),
|
||||
log::Level::Trace => Colour::Fixed(245).paint("TRACE"),
|
||||
log::Level::Error => Colour::Red.paint("ERROR"),
|
||||
log::Level::Warn => Colour::Yellow.paint("WARN"),
|
||||
log::Level::Info => Colour::Cyan.paint("INFO"),
|
||||
log::Level::Debug => Colour::Blue.paint("DEBUG"),
|
||||
log::Level::Trace => Colour::Fixed(245).paint("TRACE"),
|
||||
}
|
||||
}
|
||||
|
115
src/main.rs
115
src/main.rs
@ -11,11 +11,10 @@ use ansi_term::{ANSIStrings, Style};
|
||||
use log::*;
|
||||
|
||||
use crate::fs::{Dir, File};
|
||||
use crate::fs::filter::GitIgnore;
|
||||
use crate::fs::feature::git::GitCache;
|
||||
use crate::fs::filter::GitIgnore;
|
||||
use crate::options::{Options, Vars};
|
||||
pub use crate::options::vars;
|
||||
pub use crate::options::Misfire;
|
||||
pub use crate::options::{Misfire, vars};
|
||||
use crate::output::{escape, lines, grid, grid_details, details, View, Mode};
|
||||
|
||||
mod fs;
|
||||
@ -35,18 +34,24 @@ fn main() {
|
||||
match Exa::from_args(args.iter(), stdout()) {
|
||||
Ok(mut exa) => {
|
||||
match exa.run() {
|
||||
Ok(exit_status) => exit(exit_status),
|
||||
Ok(exit_status) => {
|
||||
exit(exit_status)
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
match e.kind() {
|
||||
ErrorKind::BrokenPipe => exit(exits::SUCCESS),
|
||||
ErrorKind::BrokenPipe => {
|
||||
exit(exits::SUCCESS);
|
||||
}
|
||||
|
||||
_ => {
|
||||
eprintln!("{}", e);
|
||||
exit(exits::RUNTIME_ERROR);
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
Err(ref e) if e.is_error() => {
|
||||
let mut stderr = stderr();
|
||||
@ -57,13 +62,13 @@ fn main() {
|
||||
}
|
||||
|
||||
exit(exits::OPTIONS_ERROR);
|
||||
},
|
||||
}
|
||||
|
||||
Err(ref e) => {
|
||||
println!("{}", e);
|
||||
exit(exits::SUCCESS);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -109,7 +114,7 @@ fn git_options(options: &Options, args: &[&OsStr]) -> Option<GitCache> {
|
||||
|
||||
impl<'args> Exa<'args> {
|
||||
pub fn from_args<I>(args: I, writer: Stdout) -> Result<Exa<'args>, Misfire>
|
||||
where I: Iterator<Item=&'args OsString>
|
||||
where I: Iterator<Item = &'args OsString>
|
||||
{
|
||||
let (options, mut args) = Options::parse(args, &LiveVars)?;
|
||||
debug!("Dir action from arguments: {:#?}", options.dir_action);
|
||||
@ -136,18 +141,19 @@ impl<'args> Exa<'args> {
|
||||
Err(e) => {
|
||||
exit_status = 2;
|
||||
writeln!(stderr(), "{:?}: {}", file_path, e)?;
|
||||
},
|
||||
}
|
||||
|
||||
Ok(f) => {
|
||||
if f.points_to_directory() && !self.options.dir_action.treat_dirs_as_files() {
|
||||
if f.points_to_directory() && ! self.options.dir_action.treat_dirs_as_files() {
|
||||
match f.to_dir() {
|
||||
Ok(d) => dirs.push(d),
|
||||
Err(e) => writeln!(stderr(), "{:?}: {}", file_path, e)?,
|
||||
Ok(d) => dirs.push(d),
|
||||
Err(e) => writeln!(stderr(), "{:?}: {}", file_path, e)?,
|
||||
}
|
||||
}
|
||||
else {
|
||||
files.push(f);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,7 +182,7 @@ impl<'args> Exa<'args> {
|
||||
writeln!(&mut self.writer)?;
|
||||
}
|
||||
|
||||
if !is_only_dir {
|
||||
if ! is_only_dir {
|
||||
let mut bits = Vec::new();
|
||||
escape(dir.path.display().to_string(), &mut bits, Style::default(), Style::default());
|
||||
writeln!(&mut self.writer, "{}:", ANSIStrings(&bits))?;
|
||||
@ -196,10 +202,10 @@ impl<'args> Exa<'args> {
|
||||
|
||||
if let Some(recurse_opts) = self.options.dir_action.recurse_options() {
|
||||
let depth = dir.path.components().filter(|&c| c != Component::CurDir).count() + 1;
|
||||
if !recurse_opts.tree && !recurse_opts.is_too_deep(depth) {
|
||||
if ! recurse_opts.tree && ! recurse_opts.is_too_deep(depth) {
|
||||
|
||||
let mut child_dirs = Vec::new();
|
||||
for child_dir in children.iter().filter(|f| f.is_directory() && !f.is_all_all) {
|
||||
for child_dir in children.iter().filter(|f| f.is_directory() && ! f.is_all_all) {
|
||||
match child_dir.to_dir() {
|
||||
Ok(d) => child_dirs.push(d),
|
||||
Err(e) => writeln!(stderr(), "{}: {}", child_dir.path.display(), e)?,
|
||||
@ -225,43 +231,42 @@ impl<'args> Exa<'args> {
|
||||
/// For various annoying logistical reasons, each one handles
|
||||
/// printing differently...
|
||||
fn print_files(&mut self, dir: Option<&Dir>, files: Vec<File>) -> IOResult<()> {
|
||||
if !files.is_empty() {
|
||||
let View { ref mode, ref colours, ref style } = self.options.view;
|
||||
|
||||
match mode {
|
||||
Mode::Lines(ref opts) => {
|
||||
let r = lines::Render { files, colours, style, opts };
|
||||
r.render(&mut self.writer)
|
||||
}
|
||||
|
||||
Mode::Grid(ref opts) => {
|
||||
let r = grid::Render { files, colours, style, opts };
|
||||
r.render(&mut self.writer)
|
||||
}
|
||||
|
||||
Mode::Details(ref opts) => {
|
||||
let filter = &self.options.filter;
|
||||
let recurse = self.options.dir_action.recurse_options();
|
||||
|
||||
let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
|
||||
let r = details::Render { dir, files, colours, style, opts, filter, recurse, git_ignoring };
|
||||
r.render(self.git.as_ref(), &mut self.writer)
|
||||
}
|
||||
|
||||
Mode::GridDetails(ref opts) => {
|
||||
let grid = &opts.grid;
|
||||
let filter = &self.options.filter;
|
||||
let details = &opts.details;
|
||||
let row_threshold = opts.row_threshold;
|
||||
|
||||
let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
|
||||
let r = grid_details::Render { dir, files, colours, style, grid, details, filter, row_threshold, git_ignoring };
|
||||
r.render(self.git.as_ref(), &mut self.writer)
|
||||
}
|
||||
}
|
||||
if files.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
else {
|
||||
Ok(())
|
||||
|
||||
let View { ref mode, ref colours, ref style } = self.options.view;
|
||||
|
||||
match mode {
|
||||
Mode::Lines(ref opts) => {
|
||||
let r = lines::Render { files, colours, style, opts };
|
||||
r.render(&mut self.writer)
|
||||
}
|
||||
|
||||
Mode::Grid(ref opts) => {
|
||||
let r = grid::Render { files, colours, style, opts };
|
||||
r.render(&mut self.writer)
|
||||
}
|
||||
|
||||
Mode::Details(ref opts) => {
|
||||
let filter = &self.options.filter;
|
||||
let recurse = self.options.dir_action.recurse_options();
|
||||
|
||||
let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
|
||||
let r = details::Render { dir, files, colours, style, opts, filter, recurse, git_ignoring };
|
||||
r.render(self.git.as_ref(), &mut self.writer)
|
||||
}
|
||||
|
||||
Mode::GridDetails(ref opts) => {
|
||||
let grid = &opts.grid;
|
||||
let filter = &self.options.filter;
|
||||
let details = &opts.details;
|
||||
let row_threshold = opts.row_threshold;
|
||||
|
||||
let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
|
||||
let r = grid_details::Render { dir, files, colours, style, grid, details, filter, row_threshold, git_ignoring };
|
||||
r.render(self.git.as_ref(), &mut self.writer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ impl DirAction {
|
||||
|
||||
if matches.is_strict() {
|
||||
// Early check for --level when it wouldn’t do anything
|
||||
if !recurse && !tree && matches.count(&flags::LEVEL) > 0 {
|
||||
if ! recurse && ! tree && matches.count(&flags::LEVEL) > 0 {
|
||||
return Err(Misfire::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE));
|
||||
}
|
||||
else if recurse && as_file {
|
||||
|
@ -12,13 +12,13 @@ impl FileFilter {
|
||||
/// Determines which of all the file filter options to use.
|
||||
pub fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
|
||||
Ok(Self {
|
||||
list_dirs_first: matches.has(&flags::DIRS_FIRST)?,
|
||||
reverse: matches.has(&flags::REVERSE)?,
|
||||
only_dirs: matches.has(&flags::ONLY_DIRS)?,
|
||||
sort_field: SortField::deduce(matches)?,
|
||||
dot_filter: DotFilter::deduce(matches)?,
|
||||
ignore_patterns: IgnorePatterns::deduce(matches)?,
|
||||
git_ignore: GitIgnore::deduce(matches)?,
|
||||
list_dirs_first: matches.has(&flags::DIRS_FIRST)?,
|
||||
reverse: matches.has(&flags::REVERSE)?,
|
||||
only_dirs: matches.has(&flags::ONLY_DIRS)?,
|
||||
sort_field: SortField::deduce(matches)?,
|
||||
dot_filter: DotFilter::deduce(matches)?,
|
||||
ignore_patterns: IgnorePatterns::deduce(matches)?,
|
||||
git_ignore: GitIgnore::deduce(matches)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -42,29 +42,64 @@ impl SortField {
|
||||
};
|
||||
|
||||
let field = match word {
|
||||
"name" | "filename" => Self::Name(SortCase::AaBbCc),
|
||||
"Name" | "Filename" => Self::Name(SortCase::ABCabc),
|
||||
".name" | ".filename" => Self::NameMixHidden(SortCase::AaBbCc),
|
||||
".Name" | ".Filename" => Self::NameMixHidden(SortCase::ABCabc),
|
||||
"size" | "filesize" => Self::Size,
|
||||
"ext" | "extension" => Self::Extension(SortCase::AaBbCc),
|
||||
"Ext" | "Extension" => Self::Extension(SortCase::ABCabc),
|
||||
"name" | "filename" => {
|
||||
Self::Name(SortCase::AaBbCc)
|
||||
}
|
||||
"Name" | "Filename" => {
|
||||
Self::Name(SortCase::ABCabc)
|
||||
}
|
||||
".name" | ".filename" => {
|
||||
Self::NameMixHidden(SortCase::AaBbCc)
|
||||
}
|
||||
".Name" | ".Filename" => {
|
||||
Self::NameMixHidden(SortCase::ABCabc)
|
||||
}
|
||||
"size" | "filesize" => {
|
||||
Self::Size
|
||||
}
|
||||
"ext" | "extension" => {
|
||||
Self::Extension(SortCase::AaBbCc)
|
||||
}
|
||||
"Ext" | "Extension" => {
|
||||
Self::Extension(SortCase::ABCabc)
|
||||
}
|
||||
|
||||
// “new” sorts oldest at the top and newest at the bottom; “old”
|
||||
// sorts newest at the top and oldest at the bottom. I think this
|
||||
// is the right way round to do this: “size” puts the smallest at
|
||||
// the top and the largest at the bottom, doesn’t it?
|
||||
"date" | "time" | "mod" | "modified" | "new" | "newest" => Self::ModifiedDate,
|
||||
"date" | "time" | "mod" | "modified" | "new" | "newest" => {
|
||||
Self::ModifiedDate
|
||||
}
|
||||
|
||||
// Similarly, “age” means that files with the least age (the
|
||||
// newest files) get sorted at the top, and files with the most
|
||||
// age (the oldest) at the bottom.
|
||||
"age" | "old" | "oldest" => Self::ModifiedAge,
|
||||
"ch" | "changed" => Self::ChangedDate,
|
||||
"acc" | "accessed" => Self::AccessedDate,
|
||||
"cr" | "created" => Self::CreatedDate,
|
||||
"inode" => Self::FileInode,
|
||||
"type" => Self::FileType,
|
||||
"none" => Self::Unsorted,
|
||||
_ => return Err(Misfire::BadArgument(&flags::SORT, word.into()))
|
||||
"age" | "old" | "oldest" => {
|
||||
Self::ModifiedAge
|
||||
}
|
||||
|
||||
"ch" | "changed" => {
|
||||
Self::ChangedDate
|
||||
}
|
||||
"acc" | "accessed" => {
|
||||
Self::AccessedDate
|
||||
}
|
||||
"cr" | "created" => {
|
||||
Self::CreatedDate
|
||||
}
|
||||
"inode" => {
|
||||
Self::FileInode
|
||||
}
|
||||
"type" => {
|
||||
Self::FileType
|
||||
}
|
||||
"none" => {
|
||||
Self::Unsorted
|
||||
}
|
||||
_ => {
|
||||
return Err(Misfire::BadArgument(&flags::SORT, word.into()));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(field)
|
||||
@ -103,7 +138,6 @@ impl SortField {
|
||||
// “apps” first, then “Documents”.
|
||||
//
|
||||
// You can get the old behaviour back by sorting with `--sort=Name`.
|
||||
|
||||
impl Default for SortField {
|
||||
fn default() -> Self {
|
||||
Self::Name(SortCase::AaBbCc)
|
||||
@ -151,8 +185,8 @@ impl IgnorePatterns {
|
||||
// If there are no inputs, we return a set of patterns that doesn’t
|
||||
// match anything, rather than, say, `None`.
|
||||
let inputs = match matches.get(&flags::IGNORE_GLOB)? {
|
||||
None => return Ok(Self::empty()),
|
||||
Some(is) => is,
|
||||
Some(is) => is,
|
||||
None => return Ok(Self::empty()),
|
||||
};
|
||||
|
||||
// Awkwardly, though, a glob pattern can be invalid, and we need to
|
||||
@ -162,8 +196,8 @@ impl IgnorePatterns {
|
||||
// It can actually return more than one glob error,
|
||||
// but we only use one. (TODO)
|
||||
match errors.pop() {
|
||||
Some(e) => Err(e.into()),
|
||||
None => Ok(patterns),
|
||||
Some(e) => Err(e.into()),
|
||||
None => Ok(patterns),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -171,13 +205,16 @@ impl IgnorePatterns {
|
||||
|
||||
impl GitIgnore {
|
||||
pub fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
|
||||
Ok(if matches.has(&flags::GIT_IGNORE)? { Self::CheckAndIgnore }
|
||||
else { Self::Off })
|
||||
if matches.has(&flags::GIT_IGNORE)? {
|
||||
Ok(Self::CheckAndIgnore)
|
||||
}
|
||||
else {
|
||||
Ok(Self::Off)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::options::parser::{Arg, Args, Values, TakesValue};
|
||||
use crate::options::parser::{Arg, Args, TakesValue, Values};
|
||||
|
||||
|
||||
// exa options
|
||||
@ -32,8 +32,8 @@ pub static GIT_IGNORE: Arg = Arg { short: None, long: "git-ignore", t
|
||||
pub static DIRS_FIRST: Arg = Arg { short: None, long: "group-directories-first", takes_value: TakesValue::Forbidden };
|
||||
pub static ONLY_DIRS: Arg = Arg { short: Some(b'D'), long: "only-dirs", takes_value: TakesValue::Forbidden };
|
||||
const SORTS: Values = &[ "name", "Name", "size", "extension",
|
||||
"Extension", "modified", "changed", "accessed",
|
||||
"created", "inode", "type", "none" ];
|
||||
"Extension", "modified", "changed", "accessed",
|
||||
"created", "inode", "type", "none" ];
|
||||
|
||||
// display options
|
||||
pub static BINARY: Arg = Arg { short: Some(b'b'), long: "binary", takes_value: TakesValue::Forbidden };
|
||||
|
@ -1,8 +1,8 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::fs::feature::xattr;
|
||||
use crate::options::flags;
|
||||
use crate::options::parser::MatchedFlags;
|
||||
use crate::fs::feature::xattr;
|
||||
|
||||
|
||||
static OPTIONS: &str = r##"
|
||||
@ -106,7 +106,7 @@ impl fmt::Display for HelpString {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
writeln!(f, "Usage:\n exa [options] [files...]")?;
|
||||
|
||||
if !self.only_long {
|
||||
if ! self.only_long {
|
||||
write!(f, "{}", OPTIONS)?;
|
||||
}
|
||||
|
||||
@ -127,7 +127,6 @@ impl fmt::Display for HelpString {
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::options::Options;
|
||||
|
@ -6,7 +6,7 @@ use crate::options::{flags, HelpString, VersionString};
|
||||
use crate::options::parser::{Arg, Flag, ParseError};
|
||||
|
||||
|
||||
/// A **misfire** is a thing that can happen instead of listing files -- a
|
||||
/// A **misfire** is a thing that can happen instead of listing files — a
|
||||
/// catch-all for anything outside the program’s normal execution.
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum Misfire {
|
||||
@ -34,7 +34,7 @@ pub enum Misfire {
|
||||
Conflict(&'static Arg, &'static Arg),
|
||||
|
||||
/// An option was given that does nothing when another one either is or
|
||||
/// isn't present.
|
||||
/// isn’t present.
|
||||
Useless(&'static Arg, bool, &'static Arg),
|
||||
|
||||
/// An option was given that does nothing when either of two other options
|
||||
@ -78,19 +78,19 @@ impl fmt::Display for Misfire {
|
||||
write!(f, "Option {} has no {:?} setting", arg, attempt)
|
||||
}
|
||||
},
|
||||
Self::InvalidOptions(e) => write!(f, "{}", e),
|
||||
Self::Unsupported(e) => write!(f, "{}", e),
|
||||
Self::Help(text) => write!(f, "{}", text),
|
||||
Self::Version(version) => write!(f, "{}", version),
|
||||
Self::Conflict(a, b) => write!(f, "Option {} conflicts with option {}", a, b),
|
||||
Self::Duplicate(a, b) if a == b => write!(f, "Flag {} was given twice", a),
|
||||
Self::Duplicate(a, b) => write!(f, "Flag {} conflicts with flag {}", a, b),
|
||||
Self::Useless(a, false, b) => write!(f, "Option {} is useless without option {}", a, b),
|
||||
Self::Useless(a, true, b) => write!(f, "Option {} is useless given option {}", a, b),
|
||||
Self::Useless2(a, b1, b2) => write!(f, "Option {} is useless without options {} or {}", a, b1, b2),
|
||||
Self::TreeAllAll => write!(f, "Option --tree is useless given --all --all"),
|
||||
Self::FailedParse(ref e) => write!(f, "Failed to parse number: {}", e),
|
||||
Self::FailedGlobPattern(ref e) => write!(f, "Failed to parse glob pattern: {}", e),
|
||||
Self::InvalidOptions(e) => write!(f, "{}", e),
|
||||
Self::Unsupported(e) => write!(f, "{}", e),
|
||||
Self::Help(text) => write!(f, "{}", text),
|
||||
Self::Version(version) => write!(f, "{}", version),
|
||||
Self::Conflict(a, b) => write!(f, "Option {} conflicts with option {}", a, b),
|
||||
Self::Duplicate(a, b) if a == b => write!(f, "Flag {} was given twice", a),
|
||||
Self::Duplicate(a, b) => write!(f, "Flag {} conflicts with flag {}", a, b),
|
||||
Self::Useless(a, false, b) => write!(f, "Option {} is useless without option {}", a, b),
|
||||
Self::Useless(a, true, b) => write!(f, "Option {} is useless given option {}", a, b),
|
||||
Self::Useless2(a, b1, b2) => write!(f, "Option {} is useless without options {} or {}", a, b1, b2),
|
||||
Self::TreeAllAll => write!(f, "Option --tree is useless given --all --all"),
|
||||
Self::FailedParse(ref e) => write!(f, "Failed to parse number: {}", e),
|
||||
Self::FailedGlobPattern(ref e) => write!(f, "Failed to parse glob pattern: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +60,7 @@
|
||||
//!
|
||||
//! `--sort=size` should override `--sort=Name` because it’s closer to the end
|
||||
//! of the arguments array. In fact, because there’s no way to tell where the
|
||||
//! arguments came from -- it’s just a heuristic -- this will still work even
|
||||
//! arguments came from — it’s just a heuristic — this will still work even
|
||||
//! if no aliases are being used!
|
||||
//!
|
||||
//! Finally, this isn’t just useful when options could override each other.
|
||||
@ -72,12 +72,13 @@
|
||||
use std::ffi::{OsStr, OsString};
|
||||
|
||||
use crate::fs::dir_action::DirAction;
|
||||
use crate::fs::filter::{FileFilter,GitIgnore};
|
||||
use crate::fs::filter::{FileFilter, GitIgnore};
|
||||
use crate::output::{View, Mode, details, grid_details};
|
||||
|
||||
mod style;
|
||||
mod dir_action;
|
||||
mod filter;
|
||||
mod flags;
|
||||
mod style;
|
||||
mod view;
|
||||
|
||||
mod help;
|
||||
@ -93,7 +94,6 @@ pub mod vars;
|
||||
pub use self::vars::Vars;
|
||||
|
||||
mod parser;
|
||||
mod flags;
|
||||
use self::parser::MatchedFlags;
|
||||
|
||||
|
||||
@ -120,8 +120,9 @@ impl Options {
|
||||
/// for extra options.
|
||||
#[allow(unused_results)]
|
||||
pub fn parse<'args, I, V>(args: I, vars: &V) -> Result<(Self, Vec<&'args OsStr>), Misfire>
|
||||
where I: IntoIterator<Item=&'args OsString>,
|
||||
V: Vars {
|
||||
where I: IntoIterator<Item = &'args OsString>,
|
||||
V: Vars
|
||||
{
|
||||
use crate::options::parser::{Matches, Strictness};
|
||||
|
||||
let strictness = match vars.get(vars::EXA_STRICT) {
|
||||
@ -169,7 +170,6 @@ impl Options {
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use super::{Options, Misfire, flags};
|
||||
|
@ -70,8 +70,8 @@ impl Flag {
|
||||
impl fmt::Display for Flag {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
match self {
|
||||
Self::Short(short) => write!(f, "-{}", *short as char),
|
||||
Self::Long(long) => write!(f, "--{}", long),
|
||||
Self::Short(short) => write!(f, "-{}", *short as char),
|
||||
Self::Long(long) => write!(f, "--{}", long),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -144,7 +144,8 @@ impl Args {
|
||||
/// Iterates over the given list of command-line arguments and parses
|
||||
/// them into a list of matched flags and free strings.
|
||||
pub fn parse<'args, I>(&self, inputs: I, strictness: Strictness) -> Result<Matches<'args>, ParseError>
|
||||
where I: IntoIterator<Item=&'args OsString> {
|
||||
where I: IntoIterator<Item = &'args OsString>
|
||||
{
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let mut parsing = true;
|
||||
@ -164,7 +165,7 @@ impl Args {
|
||||
// This allows a file named “--arg” to be specified by passing in
|
||||
// the pair “-- --arg”, without it getting matched as a flag that
|
||||
// doesn’t exist.
|
||||
if !parsing {
|
||||
if ! parsing {
|
||||
frees.push(arg)
|
||||
}
|
||||
else if arg == "--" {
|
||||
@ -348,7 +349,7 @@ pub struct Matches<'args> {
|
||||
pub flags: MatchedFlags<'args>,
|
||||
|
||||
/// All the strings that weren’t matched as arguments, as well as anything
|
||||
/// after the special "--" string.
|
||||
/// after the special “--” string.
|
||||
pub frees: Vec<&'args OsStr>,
|
||||
}
|
||||
|
||||
@ -373,7 +374,8 @@ impl<'a> MatchedFlags<'a> {
|
||||
/// Returns `true` if it was, `false` if it wasn’t, and an error in
|
||||
/// strict mode if it was specified more than once.
|
||||
pub fn has(&self, arg: &'static Arg) -> Result<bool, Misfire> {
|
||||
self.has_where(|flag| flag.matches(arg)).map(|flag| flag.is_some())
|
||||
self.has_where(|flag| flag.matches(arg))
|
||||
.map(|flag| flag.is_some())
|
||||
}
|
||||
|
||||
/// Returns the first found argument that satisfies the predicate, or
|
||||
@ -488,7 +490,7 @@ fn split_on_equals(input: &OsStr) -> Option<(&OsStr, &OsStr)> {
|
||||
let (before, after) = input.as_bytes().split_at(index);
|
||||
|
||||
// The after string contains the = that we need to remove.
|
||||
if !before.is_empty() && after.len() >= 2 {
|
||||
if ! before.is_empty() && after.len() >= 2 {
|
||||
return Some((OsStr::from_bytes(before),
|
||||
OsStr::from_bytes(&after[1..])))
|
||||
}
|
||||
@ -569,7 +571,9 @@ mod parse_test {
|
||||
|
||||
let strictness = Strictness::UseLastArguments; // this isn’t even used
|
||||
let got = Args(TEST_ARGS).parse(inputs.iter(), strictness);
|
||||
let expected = Ok(Matches { frees, flags: MatchedFlags { flags, strictness } });
|
||||
let flags = MatchedFlags { flags, strictness };
|
||||
|
||||
let expected = Ok(Matches { frees, flags });
|
||||
assert_eq!(got, expected);
|
||||
}
|
||||
};
|
||||
@ -580,9 +584,11 @@ mod parse_test {
|
||||
use self::ParseError::*;
|
||||
|
||||
let strictness = Strictness::UseLastArguments; // this isn’t even used
|
||||
let bits = $inputs.as_ref().into_iter().map(|&o| os(o)).collect::<Vec<OsString>>();
|
||||
let got = Args(TEST_ARGS).parse(bits.iter(), strictness);
|
||||
let bits = $inputs.as_ref().into_iter()
|
||||
.map(|&o| os(o))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let got = Args(TEST_ARGS).parse(bits.iter(), strictness);
|
||||
assert_eq!(got, Err($error));
|
||||
}
|
||||
};
|
||||
@ -719,6 +725,6 @@ mod matches_test {
|
||||
fn no_count() {
|
||||
let flags = MatchedFlags { flags: Vec::new(), strictness: Strictness::UseLastArguments };
|
||||
|
||||
assert!(!flags.has(&COUNT).unwrap());
|
||||
assert_eq!(flags.has(&COUNT).unwrap(), false);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use ansi_term::Style;
|
||||
use crate::fs::File;
|
||||
use crate::options::{flags, Vars, Misfire};
|
||||
use crate::options::parser::MatchedFlags;
|
||||
use crate::output::file_name::{FileStyle, Classify};
|
||||
use crate::output::file_name::{Classify, FileStyle};
|
||||
use crate::style::Colours;
|
||||
|
||||
|
||||
@ -38,10 +38,9 @@ impl TerminalColours {
|
||||
|
||||
/// Determine which terminal colour conditions to use.
|
||||
fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
|
||||
|
||||
let word = match matches.get_where(|f| f.matches(&flags::COLOR) || f.matches(&flags::COLOUR))? {
|
||||
Some(w) => w,
|
||||
None => return Ok(Self::default()),
|
||||
Some(w) => w,
|
||||
None => return Ok(Self::default()),
|
||||
};
|
||||
|
||||
if word == "always" {
|
||||
@ -77,7 +76,7 @@ pub struct Styles {
|
||||
|
||||
impl Styles {
|
||||
|
||||
#[allow(trivial_casts)] // the "as Box<_>" stuff below warns about this for some reason
|
||||
#[allow(trivial_casts)] // the `as Box<_>` stuff below warns about this for some reason
|
||||
pub fn deduce<V, TW>(matches: &MatchedFlags, vars: &V, widther: TW) -> Result<Self, Misfire>
|
||||
where TW: Fn() -> Option<usize>, V: Vars {
|
||||
use crate::info::filetype::FileExtensions;
|
||||
@ -89,12 +88,12 @@ impl Styles {
|
||||
// custom colours at all
|
||||
let tc = TerminalColours::deduce(matches)?;
|
||||
|
||||
if tc == TerminalColours::Never
|
||||
|| (tc == TerminalColours::Automatic && widther().is_none())
|
||||
{
|
||||
if tc == TerminalColours::Never || (tc == TerminalColours::Automatic && widther().is_none()) {
|
||||
let exts = Box::new(NoFileColours);
|
||||
|
||||
return Ok(Self {
|
||||
colours: Colours::plain(),
|
||||
style: FileStyle { classify, exts: Box::new(NoFileColours) },
|
||||
style: FileStyle { classify, exts },
|
||||
});
|
||||
}
|
||||
|
||||
@ -133,11 +132,16 @@ fn parse_color_vars<V: Vars>(vars: &V, colours: &mut Colours) -> (ExtensionMappi
|
||||
|
||||
if let Some(lsc) = vars.get(vars::LS_COLORS) {
|
||||
let lsc = lsc.to_string_lossy();
|
||||
|
||||
LSColors(lsc.as_ref()).each_pair(|pair| {
|
||||
if !colours.set_ls(&pair) {
|
||||
if ! colours.set_ls(&pair) {
|
||||
match glob::Pattern::new(pair.key) {
|
||||
Ok(pat) => exts.add(pat, pair.to_style()),
|
||||
Err(e) => warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e),
|
||||
Ok(pat) => {
|
||||
exts.add(pat, pair.to_style());
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -154,10 +158,14 @@ fn parse_color_vars<V: Vars>(vars: &V, colours: &mut Colours) -> (ExtensionMappi
|
||||
}
|
||||
|
||||
LSColors(exa.as_ref()).each_pair(|pair| {
|
||||
if !colours.set_ls(&pair) && !colours.set_exa(&pair) {
|
||||
if ! colours.set_ls(&pair) && ! colours.set_exa(&pair) {
|
||||
match glob::Pattern::new(pair.key) {
|
||||
Ok(pat) => exts.add(pat, pair.to_style()),
|
||||
Err(e) => warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e),
|
||||
Ok(pat) => {
|
||||
exts.add(pat, pair.to_style());
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
@ -169,7 +177,7 @@ fn parse_color_vars<V: Vars>(vars: &V, colours: &mut Colours) -> (ExtensionMappi
|
||||
|
||||
#[derive(PartialEq, Debug, Default)]
|
||||
struct ExtensionMappings {
|
||||
mappings: Vec<(glob::Pattern, Style)>
|
||||
mappings: Vec<(glob::Pattern, Style)>,
|
||||
}
|
||||
|
||||
// Loop through backwards so that colours specified later in the list override
|
||||
@ -178,9 +186,7 @@ struct ExtensionMappings {
|
||||
use crate::output::file_name::FileColours;
|
||||
impl FileColours for ExtensionMappings {
|
||||
fn colour_file(&self, file: &File) -> Option<Style> {
|
||||
self.mappings
|
||||
.iter()
|
||||
.rev()
|
||||
self.mappings.iter().rev()
|
||||
.find(|t| t.0.matches(&file.name))
|
||||
.map (|t| t.1)
|
||||
}
|
||||
@ -188,7 +194,7 @@ impl FileColours for ExtensionMappings {
|
||||
|
||||
impl ExtensionMappings {
|
||||
fn is_non_empty(&self) -> bool {
|
||||
!self.mappings.is_empty()
|
||||
! self.mappings.is_empty()
|
||||
}
|
||||
|
||||
fn add(&mut self, pattern: glob::Pattern, style: Style) {
|
||||
@ -197,18 +203,16 @@ impl ExtensionMappings {
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl Classify {
|
||||
fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
|
||||
let flagged = matches.has(&flags::CLASSIFY)?;
|
||||
|
||||
Ok(if flagged { Self::AddFileIndicators }
|
||||
else { Self::JustFilenames })
|
||||
if flagged { Ok(Self::AddFileIndicators) }
|
||||
else { Ok(Self::JustFilenames) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod terminal_test {
|
||||
use super::*;
|
||||
@ -338,7 +342,6 @@ mod colour_test {
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod customs_test {
|
||||
use std::ffi::OsString;
|
||||
@ -410,11 +413,11 @@ mod customs_test {
|
||||
fn get(&self, name: &'static str) -> Option<OsString> {
|
||||
use crate::options::vars;
|
||||
|
||||
if name == vars::LS_COLORS && !self.ls.is_empty() {
|
||||
OsString::from(self.ls.clone()).into()
|
||||
if name == vars::LS_COLORS && ! self.ls.is_empty() {
|
||||
Some(OsString::from(self.ls.clone()))
|
||||
}
|
||||
else if name == vars::EXA_COLORS && !self.exa.is_empty() {
|
||||
OsString::from(self.exa.clone()).into()
|
||||
else if name == vars::EXA_COLORS && ! self.exa.is_empty() {
|
||||
Some(OsString::from(self.exa.clone()))
|
||||
}
|
||||
else {
|
||||
None
|
||||
|
@ -15,6 +15,7 @@ pub static COLUMNS: &str = "COLUMNS";
|
||||
/// Environment variable used to datetime format.
|
||||
pub static TIME_STYLE: &str = "TIME_STYLE";
|
||||
|
||||
|
||||
// exa-specific variables
|
||||
|
||||
/// Environment variable used to colour exa’s interface when colours are
|
||||
@ -39,7 +40,6 @@ pub static EXA_DEBUG: &str = "EXA_DEBUG";
|
||||
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>;
|
||||
|
@ -1,5 +1,6 @@
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use crate::fs::feature::xattr;
|
||||
use crate::options::{flags, Misfire, Vars};
|
||||
use crate::options::parser::MatchedFlags;
|
||||
use crate::output::{View, Mode, grid, details, lines};
|
||||
@ -7,8 +8,6 @@ use crate::output::grid_details::{self, RowThreshold};
|
||||
use crate::output::table::{TimeTypes, Environment, SizeFormat, Columns, Options as TableOptions};
|
||||
use crate::output::time::TimeFormat;
|
||||
|
||||
use crate::fs::feature::xattr;
|
||||
|
||||
|
||||
impl View {
|
||||
|
||||
@ -28,7 +27,7 @@ impl Mode {
|
||||
/// Determine the mode from the command-line arguments.
|
||||
pub fn deduce<V: Vars>(matches: &MatchedFlags, vars: &V) -> Result<Self, Misfire> {
|
||||
let long = || {
|
||||
if matches.has(&flags::ACROSS)? && !matches.has(&flags::GRID)? {
|
||||
if matches.has(&flags::ACROSS)? && ! matches.has(&flags::GRID)? {
|
||||
Err(Misfire::Useless(&flags::ACROSS, true, &flags::LONG))
|
||||
}
|
||||
else if matches.has(&flags::ONE_LINE)? {
|
||||
@ -104,7 +103,8 @@ impl Mode {
|
||||
let other_options_mode = other_options_scan()?;
|
||||
if let Self::Grid(grid) = other_options_mode {
|
||||
let row_threshold = RowThreshold::deduce(vars)?;
|
||||
return Ok(Self::GridDetails(grid_details::Options { grid, details, row_threshold }));
|
||||
let opts = grid_details::Options { grid, details, row_threshold };
|
||||
return Ok(Self::GridDetails(opts));
|
||||
}
|
||||
else {
|
||||
return Ok(other_options_mode);
|
||||
@ -125,11 +125,11 @@ impl Mode {
|
||||
}
|
||||
}
|
||||
|
||||
if cfg!(feature="git") && matches.has(&flags::GIT)? {
|
||||
if cfg!(feature = "git") && matches.has(&flags::GIT)? {
|
||||
return Err(Misfire::Useless(&flags::GIT, false, &flags::LONG));
|
||||
}
|
||||
else if matches.has(&flags::LEVEL)? && !matches.has(&flags::RECURSE)? && !matches.has(&flags::TREE)? {
|
||||
// TODO: I'm not sure if the code even gets this far.
|
||||
else if matches.has(&flags::LEVEL)? && ! matches.has(&flags::RECURSE)? && ! matches.has(&flags::TREE)? {
|
||||
// TODO: I’m not sure if the code even gets this far.
|
||||
// There is an identical check in dir_action
|
||||
return Err(Misfire::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE));
|
||||
}
|
||||
@ -220,7 +220,7 @@ impl TableOptions {
|
||||
impl Columns {
|
||||
fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
|
||||
let time_types = TimeTypes::deduce(matches)?;
|
||||
let git = cfg!(feature="git") && matches.has(&flags::GIT)?;
|
||||
let git = cfg!(feature = "git") && matches.has(&flags::GIT)?;
|
||||
|
||||
let blocks = matches.has(&flags::BLOCKS)?;
|
||||
let group = matches.has(&flags::GROUP)?;
|
||||
@ -228,9 +228,9 @@ impl Columns {
|
||||
let links = matches.has(&flags::LINKS)?;
|
||||
let octal = matches.has(&flags::OCTAL)?;
|
||||
|
||||
let permissions = !matches.has(&flags::NO_PERMISSIONS)?;
|
||||
let filesize = !matches.has(&flags::NO_FILESIZE)?;
|
||||
let user = !matches.has(&flags::NO_USER)?;
|
||||
let permissions = ! matches.has(&flags::NO_PERMISSIONS)?;
|
||||
let filesize = ! matches.has(&flags::NO_FILESIZE)?;
|
||||
let user = ! matches.has(&flags::NO_USER)?;
|
||||
|
||||
Ok(Self { time_types, git, octal, blocks, group, inode, links, permissions, filesize, user })
|
||||
}
|
||||
@ -266,12 +266,14 @@ impl TimeFormat {
|
||||
pub use crate::output::time::{DefaultFormat, ISOFormat};
|
||||
|
||||
let word = match matches.get(&flags::TIME_STYLE)? {
|
||||
Some(w) => w.to_os_string(),
|
||||
None => {
|
||||
Some(w) => {
|
||||
w.to_os_string()
|
||||
}
|
||||
None => {
|
||||
use crate::options::vars;
|
||||
match vars.get(vars::TIME_STYLE) {
|
||||
Some(ref t) if !t.is_empty() => t.clone(),
|
||||
_ => return Ok(Self::DefaultFormat(DefaultFormat::load()))
|
||||
Some(ref t) if ! t.is_empty() => t.clone(),
|
||||
_ => return Ok(Self::DefaultFormat(DefaultFormat::load()))
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -373,7 +375,6 @@ lazy_static! {
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
@ -600,7 +601,7 @@ mod test {
|
||||
test!(just_binary: Mode <- ["--binary"], None; Last => like Ok(Mode::Grid(_)));
|
||||
test!(just_bytes: Mode <- ["--bytes"], None; Last => like Ok(Mode::Grid(_)));
|
||||
|
||||
#[cfg(feature="git")]
|
||||
#[cfg(feature = "git")]
|
||||
test!(just_git: Mode <- ["--git"], None; Last => like Ok(Mode::Grid(_)));
|
||||
|
||||
test!(just_header_2: Mode <- ["--header"], None; Complain => err Misfire::Useless(&flags::HEADER, false, &flags::LONG));
|
||||
@ -611,7 +612,7 @@ mod test {
|
||||
test!(just_binary_2: Mode <- ["--binary"], None; Complain => err Misfire::Useless(&flags::BINARY, false, &flags::LONG));
|
||||
test!(just_bytes_2: Mode <- ["--bytes"], None; Complain => err Misfire::Useless(&flags::BYTES, false, &flags::LONG));
|
||||
|
||||
#[cfg(feature="git")]
|
||||
#[cfg(feature = "git")]
|
||||
test!(just_git_2: Mode <- ["--git"], None; Complain => err Misfire::Useless(&flags::GIT, false, &flags::LONG));
|
||||
}
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ impl Deref for TextCellContents {
|
||||
}
|
||||
}
|
||||
|
||||
// No DerefMut implementation here -- it would be publicly accessible, and as
|
||||
// No DerefMut implementation here — it would be publicly accessible, and as
|
||||
// the contents only get changed in this module, the mutators in the struct
|
||||
// above can just access the value directly.
|
||||
|
||||
@ -188,7 +188,7 @@ impl TextCellContents {
|
||||
/// when calculating widths for displaying tables in a terminal.
|
||||
///
|
||||
/// This type is used to ensure that the width, rather than the length, is
|
||||
/// used when constructing a `TextCell` -- it's too easy to write something
|
||||
/// used when constructing a `TextCell` — it’s too easy to write something
|
||||
/// like `file_name.len()` and assume it will work!
|
||||
///
|
||||
/// It has `From` impls that convert an input string or fixed with to values
|
||||
@ -239,7 +239,9 @@ impl Add<usize> for DisplayWidth {
|
||||
}
|
||||
|
||||
impl Sum for DisplayWidth {
|
||||
fn sum<I>(iter: I) -> Self where I: Iterator<Item=Self> {
|
||||
fn sum<I>(iter: I) -> Self
|
||||
where I: Iterator<Item = Self>
|
||||
{
|
||||
iter.fold(Self(0), Add::add)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! The **Details** output view displays each file as a row in a table.
|
||||
//!
|
||||
//! It's used in the following situations:
|
||||
//! It’s used in the following situations:
|
||||
//!
|
||||
//! - Most commonly, when using the `--long` command-line argument to display the
|
||||
//! details of each file, which requires using a table view to hold all the data;
|
||||
@ -66,20 +66,19 @@ use std::path::PathBuf;
|
||||
use std::vec::IntoIter as VecIntoIter;
|
||||
|
||||
use ansi_term::{ANSIGenericString, Style};
|
||||
use scoped_threadpool::Pool;
|
||||
|
||||
use crate::fs::{Dir, File};
|
||||
use crate::fs::dir_action::RecurseOptions;
|
||||
use crate::fs::filter::FileFilter;
|
||||
use crate::fs::feature::git::GitCache;
|
||||
use crate::fs::feature::xattr::{Attribute, FileAttributes};
|
||||
use crate::fs::filter::FileFilter;
|
||||
use crate::style::Colours;
|
||||
use crate::output::cell::TextCell;
|
||||
use crate::output::tree::{TreeTrunk, TreeParams, TreeDepth};
|
||||
use crate::output::icons::painted_icon;
|
||||
use crate::output::file_name::FileStyle;
|
||||
use crate::output::table::{Table, Options as TableOptions, Row as TableRow};
|
||||
use crate::output::icons::painted_icon;
|
||||
|
||||
use scoped_threadpool::Pool;
|
||||
use crate::output::tree::{TreeTrunk, TreeParams, TreeDepth};
|
||||
|
||||
|
||||
/// With the **Details** view, the output gets formatted into columns, with
|
||||
@ -105,15 +104,14 @@ pub struct Options {
|
||||
/// Whether to show a header line or not.
|
||||
pub header: bool,
|
||||
|
||||
/// Whether to show each file's extended attributes.
|
||||
/// Whether to show each file’s extended attributes.
|
||||
pub xattr: bool,
|
||||
|
||||
/// Enables --icons mode
|
||||
/// Whether icons mode is enabled.
|
||||
pub icons: bool,
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub struct Render<'a> {
|
||||
pub dir: Option<&'a Dir>,
|
||||
pub files: Vec<File<'a>>,
|
||||
@ -157,8 +155,8 @@ impl<'a> Render<'a> {
|
||||
|
||||
if let Some(ref table) = self.opts.table {
|
||||
match (git, self.dir) {
|
||||
(Some(g), Some(d)) => if !g.has_anything_for(&d.path) { git = None },
|
||||
(Some(g), None) => if !self.files.iter().any(|f| g.has_anything_for(&f.path)) { git = None },
|
||||
(Some(g), Some(d)) => if ! g.has_anything_for(&d.path) { git = None },
|
||||
(Some(g), None) => if ! self.files.iter().any(|f| g.has_anything_for(&f.path)) { git = None },
|
||||
(None, _) => {/* Keep Git how it is */},
|
||||
}
|
||||
|
||||
@ -248,26 +246,26 @@ impl<'a> Render<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
let table_row = table.as_ref().map(|t| t.row_for_file(file, !xattrs.is_empty()));
|
||||
let table_row = table.as_ref()
|
||||
.map(|t| t.row_for_file(file, ! xattrs.is_empty()));
|
||||
|
||||
if !self.opts.xattr {
|
||||
if ! self.opts.xattr {
|
||||
xattrs.clear();
|
||||
}
|
||||
|
||||
let mut dir = None;
|
||||
|
||||
if let Some(r) = self.recurse {
|
||||
if file.is_directory() && r.tree && !r.is_too_deep(depth.0) {
|
||||
if file.is_directory() && r.tree && ! r.is_too_deep(depth.0) {
|
||||
match file.to_dir() {
|
||||
Ok(d) => { dir = Some(d); },
|
||||
Err(e) => { errors.push((e, None)) },
|
||||
Ok(d) => { dir = Some(d); },
|
||||
Err(e) => { errors.push((e, None)) },
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let icon = if self.opts.icons {
|
||||
Some(painted_icon(file, self.style))
|
||||
} else { None };
|
||||
let icon = if self.opts.icons { Some(painted_icon(file, self.style)) }
|
||||
else { None };
|
||||
|
||||
let egg = Egg { table_row, xattrs, errors, dir, file, icon };
|
||||
unsafe { std::ptr::write(file_eggs.lock().unwrap()[idx].as_mut_ptr(), egg) }
|
||||
@ -291,10 +289,12 @@ impl<'a> Render<'a> {
|
||||
if let Some(icon) = egg.icon {
|
||||
name_cell.push(ANSIGenericString::from(icon), 2)
|
||||
}
|
||||
name_cell.append(self.style.for_file(egg.file, self.colours)
|
||||
.with_link_paths()
|
||||
.paint()
|
||||
.promote());
|
||||
|
||||
let style = self.style.for_file(egg.file, self.colours)
|
||||
.with_link_paths()
|
||||
.paint()
|
||||
.promote();
|
||||
name_cell.append(style);
|
||||
|
||||
|
||||
let row = Row {
|
||||
@ -308,14 +308,18 @@ impl<'a> Render<'a> {
|
||||
if let Some(ref dir) = egg.dir {
|
||||
for file_to_add in dir.files(self.filter.dot_filter, git, self.git_ignoring) {
|
||||
match file_to_add {
|
||||
Ok(f) => files.push(f),
|
||||
Err((path, e)) => errors.push((e, Some(path)))
|
||||
Ok(f) => {
|
||||
files.push(f);
|
||||
}
|
||||
Err((path, e)) => {
|
||||
errors.push((e, Some(path)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.filter.filter_child_files(&mut files);
|
||||
|
||||
if !files.is_empty() {
|
||||
if ! files.is_empty() {
|
||||
for xattr in egg.xattrs {
|
||||
rows.push(self.render_xattr(&xattr, TreeParams::new(depth.deeper(), false)));
|
||||
}
|
||||
@ -331,12 +335,16 @@ impl<'a> Render<'a> {
|
||||
|
||||
let count = egg.xattrs.len();
|
||||
for (index, xattr) in egg.xattrs.into_iter().enumerate() {
|
||||
rows.push(self.render_xattr(&xattr, TreeParams::new(depth.deeper(), errors.is_empty() && index == count - 1)));
|
||||
let params = TreeParams::new(depth.deeper(), errors.is_empty() && index == count - 1);
|
||||
let r = self.render_xattr(&xattr, params);
|
||||
rows.push(r);
|
||||
}
|
||||
|
||||
let count = errors.len();
|
||||
for (index, (error, path)) in errors.into_iter().enumerate() {
|
||||
rows.push(self.render_error(&error, TreeParams::new(depth.deeper(), index == count - 1), path));
|
||||
let params = TreeParams::new(depth.deeper(), index == count - 1);
|
||||
let r = self.render_error(&error, params, path);
|
||||
rows.push(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -396,13 +404,13 @@ pub struct Row {
|
||||
|
||||
/// Vector of cells to display.
|
||||
///
|
||||
/// Most of the rows will be used to display files' metadata, so this will
|
||||
/// Most of the rows will be used to display files’ metadata, so this will
|
||||
/// almost always be `Some`, containing a vector of cells. It will only be
|
||||
/// `None` for a row displaying an attribute or error, neither of which
|
||||
/// have cells.
|
||||
pub cells: Option<TableRow>,
|
||||
|
||||
/// This file's name, in coloured output. The name is treated separately
|
||||
/// This file’s name, in coloured output. The name is treated separately
|
||||
/// from the other cells, as it never requires padding.
|
||||
pub name: TextCell,
|
||||
|
||||
@ -441,7 +449,7 @@ impl<'a> Iterator for TableIter<'a> {
|
||||
|
||||
// If any tree characters have been printed, then add an extra
|
||||
// space, which makes the output look much better.
|
||||
if !row.tree.is_at_root() {
|
||||
if ! row.tree.is_at_root() {
|
||||
cell.add_spaces(1);
|
||||
}
|
||||
|
||||
@ -471,7 +479,7 @@ impl Iterator for Iter {
|
||||
|
||||
// If any tree characters have been printed, then add an extra
|
||||
// space, which makes the output look much better.
|
||||
if !row.tree.is_at_root() {
|
||||
if ! row.tree.is_at_root() {
|
||||
cell.add_spaces(1);
|
||||
}
|
||||
|
||||
|
@ -4,22 +4,23 @@ use ansi_term::{ANSIString, Style};
|
||||
pub fn escape<'a>(string: String, bits: &mut Vec<ANSIString<'a>>, good: Style, bad: Style) {
|
||||
if string.chars().all(|c| c >= 0x20 as char && c != 0x7f as char) {
|
||||
bits.push(good.paint(string));
|
||||
return;
|
||||
}
|
||||
else {
|
||||
for c in string.chars() {
|
||||
// The `escape_default` method on `char` is *almost* what we want here, but
|
||||
// it still escapes non-ASCII UTF-8 characters, which are still printable.
|
||||
|
||||
if c >= 0x20 as char && c != 0x7f as char {
|
||||
// TODO: This allocates way too much,
|
||||
// hence the `all` check above.
|
||||
let mut s = String::new();
|
||||
s.push(c);
|
||||
bits.push(good.paint(s));
|
||||
} else {
|
||||
let s = c.escape_default().collect::<String>();
|
||||
bits.push(bad.paint(s));
|
||||
}
|
||||
for c in string.chars() {
|
||||
// The `escape_default` method on `char` is *almost* what we want here, but
|
||||
// it still escapes non-ASCII UTF-8 characters, which are still printable.
|
||||
|
||||
if c >= 0x20 as char && c != 0x7f as char {
|
||||
// TODO: This allocates way too much,
|
||||
// hence the `all` check above.
|
||||
let mut s = String::new();
|
||||
s.push(c);
|
||||
bits.push(good.paint(s));
|
||||
}
|
||||
else {
|
||||
let s = c.escape_default().collect::<String>();
|
||||
bits.push(bad.paint(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
use std::fmt::Debug;
|
||||
use std::marker::Sync;
|
||||
use std::path::Path;
|
||||
|
||||
use ansi_term::{ANSIString, Style};
|
||||
|
||||
use crate::fs::{File, FileTarget};
|
||||
use crate::output::escape;
|
||||
use crate::output::cell::TextCellContents;
|
||||
use crate::output::escape;
|
||||
use crate::output::render::FiletypeColours;
|
||||
|
||||
|
||||
@ -25,7 +27,8 @@ impl FileStyle {
|
||||
/// with the remaining arguments.
|
||||
pub fn for_file<'a, 'dir, C: Colours>(&'a self, file: &'a File<'dir>, colours: &'a C) -> FileName<'a, 'dir, C> {
|
||||
FileName {
|
||||
file, colours,
|
||||
file,
|
||||
colours,
|
||||
link_style: LinkStyle::JustFilenames,
|
||||
classify: self.classify,
|
||||
exts: &*self.exts,
|
||||
@ -71,7 +74,6 @@ 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, C: Colours+'a> {
|
||||
@ -120,9 +122,9 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
||||
}
|
||||
}
|
||||
|
||||
if !self.file.name.is_empty() {
|
||||
if ! self.file.name.is_empty() {
|
||||
// The “missing file” colour seems like it should be used here,
|
||||
// but it’s not! In a grid view, where there's no space to display
|
||||
// but it’s not! In a grid view, where there’s no space to display
|
||||
// link targets, the filename has to have a different style to
|
||||
// indicate this fact. But when showing targets, we can just
|
||||
// colour the path instead (see below), and leave the broken
|
||||
@ -143,7 +145,7 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
||||
self.add_parent_bits(&mut bits, parent);
|
||||
}
|
||||
|
||||
if !target.name.is_empty() {
|
||||
if ! target.name.is_empty() {
|
||||
let target = FileName {
|
||||
file: target,
|
||||
colours: self.colours,
|
||||
@ -157,18 +159,24 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
||||
bits.push(bit);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
FileTarget::Broken(broken_path) => {
|
||||
bits.push(Style::default().paint(" "));
|
||||
bits.push(self.colours.broken_symlink().paint("->"));
|
||||
bits.push(Style::default().paint(" "));
|
||||
escape(broken_path.display().to_string(), &mut bits, self.colours.broken_filename(), self.colours.broken_control_char());
|
||||
},
|
||||
|
||||
escape(
|
||||
broken_path.display().to_string(),
|
||||
&mut bits,
|
||||
self.colours.broken_filename(),
|
||||
self.colours.broken_control_char(),
|
||||
);
|
||||
}
|
||||
|
||||
FileTarget::Err(_) => {
|
||||
// Do nothing -- the error gets displayed on the next line
|
||||
},
|
||||
// Do nothing — the error gets displayed on the next line
|
||||
}
|
||||
}
|
||||
}
|
||||
else if let Classify::AddFileIndicators = self.classify {
|
||||
@ -190,7 +198,12 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
||||
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());
|
||||
escape(
|
||||
parent.to_string_lossy().to_string(),
|
||||
bits,
|
||||
self.colours.symlink_path(),
|
||||
self.colours.control_char(),
|
||||
);
|
||||
bits.push(self.colours.symlink_path().paint("/"));
|
||||
}
|
||||
}
|
||||
@ -201,15 +214,20 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
||||
fn classify_char(&self) -> Option<&'static str> {
|
||||
if self.file.is_executable_file() {
|
||||
Some("*")
|
||||
} else if self.file.is_directory() {
|
||||
}
|
||||
else if self.file.is_directory() {
|
||||
Some("/")
|
||||
} else if self.file.is_pipe() {
|
||||
}
|
||||
else if self.file.is_pipe() {
|
||||
Some("|")
|
||||
} else if self.file.is_link() {
|
||||
}
|
||||
else if self.file.is_link() {
|
||||
Some("@")
|
||||
} else if self.file.is_socket() {
|
||||
}
|
||||
else if self.file.is_socket() {
|
||||
Some("=")
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
None
|
||||
}
|
||||
}
|
||||
@ -228,13 +246,20 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
/// Figures out which colour to paint the filename part of the output,
|
||||
/// depending on which “type” of file it appears to be -- either from the
|
||||
/// depending on which “type” of file it appears to be — either from the
|
||||
/// class on the filesystem or from its name. (Or the broken link colour,
|
||||
/// if there’s nowhere else for that fact to be shown.)
|
||||
pub fn style(&self) -> Style {
|
||||
@ -260,7 +285,7 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
||||
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(),
|
||||
f if ! f.is_file() => self.colours.special(),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
@ -298,9 +323,7 @@ pub trait Colours: FiletypeColours {
|
||||
|
||||
|
||||
// needs Debug because FileStyle derives it
|
||||
use std::fmt::Debug;
|
||||
use std::marker::Sync;
|
||||
pub trait FileColours: Debug+Sync {
|
||||
pub trait FileColours: Debug + Sync {
|
||||
fn colour_file(&self, file: &File) -> Option<Style>;
|
||||
}
|
||||
|
||||
@ -308,7 +331,9 @@ pub trait FileColours: Debug+Sync {
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct NoFileColours;
|
||||
impl FileColours for NoFileColours {
|
||||
fn colour_file(&self, _file: &File) -> Option<Style> { None }
|
||||
fn colour_file(&self, _file: &File) -> Option<Style> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// When getting the colour of a file from a *pair* of colourisers, try the
|
||||
@ -316,8 +341,11 @@ impl FileColours for NoFileColours {
|
||||
// file type associations, while falling back to the default set if not set
|
||||
// explicitly.
|
||||
impl<A, B> FileColours for (A, B)
|
||||
where A: FileColours, B: FileColours {
|
||||
where A: FileColours,
|
||||
B: FileColours,
|
||||
{
|
||||
fn colour_file(&self, file: &File) -> Option<Style> {
|
||||
self.0.colour_file(file).or_else(|| self.1.colour_file(file))
|
||||
self.0.colour_file(file)
|
||||
.or_else(|| self.1.colour_file(file))
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,10 @@ use std::io::{Write, Result as IOResult};
|
||||
use term_grid as tg;
|
||||
|
||||
use crate::fs::File;
|
||||
use crate::style::Colours;
|
||||
use crate::output::cell::DisplayWidth;
|
||||
use crate::output::file_name::FileStyle;
|
||||
use crate::output::icons::painted_icon;
|
||||
use crate::output::cell::DisplayWidth;
|
||||
use crate::style::Colours;
|
||||
|
||||
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
@ -41,16 +41,16 @@ impl<'a> Render<'a> {
|
||||
grid.reserve(self.files.len());
|
||||
|
||||
for file in &self.files {
|
||||
let icon = if self.opts.icons { Some(painted_icon(file, self.style)) } else { None };
|
||||
let icon = if self.opts.icons { Some(painted_icon(file, self.style)) }
|
||||
else { None };
|
||||
|
||||
let filename = self.style.for_file(file, self.colours).paint();
|
||||
let width = if self.opts.icons {
|
||||
DisplayWidth::from(2) + filename.width()
|
||||
} else {
|
||||
filename.width()
|
||||
};
|
||||
|
||||
let width = if self.opts.icons { DisplayWidth::from(2) + filename.width() }
|
||||
else { filename.width() };
|
||||
|
||||
grid.add(tg::Cell {
|
||||
contents: format!("{icon}{filename}", icon=&icon.unwrap_or_default(), filename=filename.strings().to_string()),
|
||||
contents: format!("{}{}", &icon.unwrap_or_default(), filename.strings()),
|
||||
width: *width,
|
||||
});
|
||||
}
|
||||
@ -66,9 +66,11 @@ impl<'a> Render<'a> {
|
||||
if self.opts.icons {
|
||||
write!(w, "{}", painted_icon(file, self.style))?;
|
||||
}
|
||||
|
||||
let name_cell = self.style.for_file(file, self.colours).paint();
|
||||
writeln!(w, "{}", name_cell.strings())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -9,15 +9,14 @@ use crate::fs::{Dir, File};
|
||||
use crate::fs::feature::git::GitCache;
|
||||
use crate::fs::feature::xattr::FileAttributes;
|
||||
use crate::fs::filter::FileFilter;
|
||||
|
||||
use crate::style::Colours;
|
||||
use crate::output::cell::TextCell;
|
||||
use crate::output::details::{Options as DetailsOptions, Row as DetailsRow, Render as DetailsRender};
|
||||
use crate::output::grid::Options as GridOptions;
|
||||
use crate::output::file_name::FileStyle;
|
||||
use crate::output::grid::Options as GridOptions;
|
||||
use crate::output::icons::painted_icon;
|
||||
use crate::output::table::{Table, Row as TableRow, Options as TableOptions};
|
||||
use crate::output::tree::{TreeParams, TreeDepth};
|
||||
use crate::output::icons::painted_icon;
|
||||
use crate::style::Colours;
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -84,21 +83,21 @@ pub struct Render<'a> {
|
||||
impl<'a> Render<'a> {
|
||||
|
||||
/// Create a temporary Details render that gets used for the columns of
|
||||
/// the grid-details render that's being generated.
|
||||
/// the grid-details render that’s being generated.
|
||||
///
|
||||
/// This includes an empty files vector because the files get added to
|
||||
/// the table in *this* file, not in details: we only want to insert every
|
||||
/// *n* files into each column’s table, not all of them.
|
||||
pub fn details(&self) -> DetailsRender<'a> {
|
||||
DetailsRender {
|
||||
dir: self.dir,
|
||||
files: Vec::new(),
|
||||
colours: self.colours,
|
||||
style: self.style,
|
||||
opts: self.details,
|
||||
recurse: None,
|
||||
filter: self.filter,
|
||||
git_ignoring: self.git_ignoring,
|
||||
dir: self.dir,
|
||||
files: Vec::new(),
|
||||
colours: self.colours,
|
||||
style: self.style,
|
||||
opts: self.details,
|
||||
recurse: None,
|
||||
filter: self.filter,
|
||||
git_ignoring: self.git_ignoring,
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,14 +105,14 @@ impl<'a> Render<'a> {
|
||||
/// in the terminal (or something has gone wrong) and we have given up.
|
||||
pub fn give_up(self) -> DetailsRender<'a> {
|
||||
DetailsRender {
|
||||
dir: self.dir,
|
||||
files: self.files,
|
||||
colours: self.colours,
|
||||
style: self.style,
|
||||
opts: self.details,
|
||||
recurse: None,
|
||||
filter: self.filter,
|
||||
git_ignoring: self.git_ignoring,
|
||||
dir: self.dir,
|
||||
files: self.files,
|
||||
colours: self.colours,
|
||||
style: self.style,
|
||||
opts: self.details,
|
||||
recurse: None,
|
||||
filter: self.filter,
|
||||
git_ignoring: self.git_ignoring,
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,7 +137,7 @@ impl<'a> Render<'a> {
|
||||
|
||||
let rows = self.files.iter()
|
||||
.map(|file| first_table.row_for_file(file, file_has_xattrs(file)))
|
||||
.collect::<Vec<TableRow>>();
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let file_names = self.files.iter()
|
||||
.map(|file| {
|
||||
@ -148,11 +147,12 @@ impl<'a> Render<'a> {
|
||||
let file_cell = self.style.for_file(file, self.colours).paint().promote();
|
||||
icon_cell.append(file_cell);
|
||||
icon_cell
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
self.style.for_file(file, self.colours).paint().promote()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<TextCell>>();
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut last_working_table = self.make_grid(1, options, git, &file_names, rows.clone(), &drender);
|
||||
|
||||
@ -188,8 +188,8 @@ impl<'a> Render<'a> {
|
||||
|
||||
fn make_table(&'a self, options: &'a TableOptions, mut git: Option<&'a GitCache>, drender: &DetailsRender) -> (Table<'a>, Vec<DetailsRow>) {
|
||||
match (git, self.dir) {
|
||||
(Some(g), Some(d)) => if !g.has_anything_for(&d.path) { git = None },
|
||||
(Some(g), None) => if !self.files.iter().any(|f| g.has_anything_for(&f.path)) { git = None },
|
||||
(Some(g), Some(d)) => if ! g.has_anything_for(&d.path) { git = None },
|
||||
(Some(g), None) => if ! self.files.iter().any(|f| g.has_anything_for(&f.path)) { git = None },
|
||||
(None, _) => {/* Keep Git how it is */},
|
||||
}
|
||||
|
||||
@ -206,7 +206,6 @@ impl<'a> Render<'a> {
|
||||
}
|
||||
|
||||
fn make_grid(&'a self, column_count: usize, options: &'a TableOptions, git: Option<&GitCache>, file_names: &[TextCell], rows: Vec<TableRow>, drender: &DetailsRender) -> grid::Grid {
|
||||
|
||||
let mut tables = Vec::new();
|
||||
for _ in 0 .. column_count {
|
||||
tables.push(self.make_table(options, git, drender));
|
||||
@ -234,17 +233,19 @@ impl<'a> Render<'a> {
|
||||
rows.push(details_row);
|
||||
}
|
||||
|
||||
let columns: Vec<_> = tables.into_iter().map(|(table, details_rows)| {
|
||||
drender.iterate_with_table(table, details_rows).collect::<Vec<_>>()
|
||||
}).collect();
|
||||
let columns = tables
|
||||
.into_iter()
|
||||
.map(|(table, details_rows)| {
|
||||
drender.iterate_with_table(table, details_rows)
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let direction = if self.grid.across { grid::Direction::LeftToRight }
|
||||
else { grid::Direction::TopToBottom };
|
||||
|
||||
let mut grid = grid::Grid::new(grid::GridOptions {
|
||||
direction,
|
||||
filling: grid::Filling::Spaces(4),
|
||||
});
|
||||
let filling = grid::Filling::Spaces(4);
|
||||
let mut grid = grid::Grid::new(grid::GridOptions { direction, filling });
|
||||
|
||||
if self.grid.across {
|
||||
for row in 0 .. height {
|
||||
@ -280,14 +281,18 @@ impl<'a> Render<'a> {
|
||||
|
||||
fn divide_rounding_up(a: usize, b: usize) -> usize {
|
||||
let mut result = a / b;
|
||||
if a % b != 0 { result += 1; }
|
||||
|
||||
if a % b != 0 {
|
||||
result += 1;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
fn file_has_xattrs(file: &File) -> bool {
|
||||
match file.path.attributes() {
|
||||
Ok(attrs) => !attrs.is_empty(),
|
||||
Err(_) => false,
|
||||
Ok(attrs) => ! attrs.is_empty(),
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
@ -4,10 +4,12 @@ use crate::fs::File;
|
||||
use crate::info::filetype::FileExtensions;
|
||||
use crate::output::file_name::FileStyle;
|
||||
|
||||
|
||||
pub trait FileIcon {
|
||||
fn icon_file(&self, file: &File) -> Option<char>;
|
||||
}
|
||||
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Icons {
|
||||
Audio,
|
||||
@ -18,35 +20,48 @@ pub enum Icons {
|
||||
impl Icons {
|
||||
pub fn value(self) -> char {
|
||||
match self {
|
||||
Self::Audio => '\u{f001}',
|
||||
Self::Image => '\u{f1c5}',
|
||||
Self::Video => '\u{f03d}',
|
||||
Self::Audio => '\u{f001}',
|
||||
Self::Image => '\u{f1c5}',
|
||||
Self::Video => '\u{f03d}',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn painted_icon(file: &File, style: &FileStyle) -> String {
|
||||
let file_icon = icon(file).to_string();
|
||||
let painted = style.exts
|
||||
.colour_file(file)
|
||||
.map_or(file_icon.to_string(), |c| {
|
||||
// Remove underline from icon
|
||||
if c.is_underline {
|
||||
match c.foreground {
|
||||
Some(color) => Style::from(color).paint(file_icon).to_string(),
|
||||
None => Style::default().paint(file_icon).to_string(),
|
||||
.colour_file(file)
|
||||
.map_or(file_icon.to_string(), |c| {
|
||||
// Remove underline from icon
|
||||
if c.is_underline {
|
||||
match c.foreground {
|
||||
Some(color) => {
|
||||
Style::from(color).paint(file_icon).to_string()
|
||||
}
|
||||
None => {
|
||||
Style::default().paint(file_icon).to_string()
|
||||
}
|
||||
} else {
|
||||
c.paint(file_icon).to_string()
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
c.paint(file_icon).to_string()
|
||||
}
|
||||
});
|
||||
|
||||
format!("{} ", painted)
|
||||
}
|
||||
|
||||
|
||||
fn icon(file: &File) -> char {
|
||||
let extensions = Box::new(FileExtensions);
|
||||
if file.points_to_directory() { '\u{f115}' }
|
||||
else if let Some(icon) = extensions.icon_file(file) { icon }
|
||||
|
||||
if file.points_to_directory() {
|
||||
'\u{f115}'
|
||||
}
|
||||
else if let Some(icon) = extensions.icon_file(file) {
|
||||
icon
|
||||
}
|
||||
else if let Some(ext) = file.ext.as_ref() {
|
||||
match ext.as_str() {
|
||||
"ai" => '\u{e7b4}',
|
||||
@ -189,7 +204,8 @@ fn icon(file: &File) -> char {
|
||||
"nix" => '\u{f313}',
|
||||
_ => '\u{f016}'
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
'\u{f016}'
|
||||
}
|
||||
}
|
||||
|
@ -3,14 +3,15 @@ use std::io::{Write, Result as IOResult};
|
||||
use ansi_term::{ANSIStrings, ANSIGenericString};
|
||||
|
||||
use crate::fs::File;
|
||||
use crate::output::file_name::{FileName, FileStyle};
|
||||
use crate::style::Colours;
|
||||
use crate::output::icons::painted_icon;
|
||||
use crate::output::cell::TextCell;
|
||||
use crate::output::file_name::{FileName, FileStyle};
|
||||
use crate::output::icons::painted_icon;
|
||||
use crate::style::Colours;
|
||||
|
||||
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
pub struct Options {
|
||||
pub icons: bool
|
||||
pub icons: bool,
|
||||
}
|
||||
|
||||
/// The lines view literally just displays each file, line-by-line.
|
||||
@ -32,7 +33,8 @@ impl<'a> Render<'a> {
|
||||
cell.push(ANSIGenericString::from(icon), 2);
|
||||
cell.append(name_cell.promote());
|
||||
writeln!(w, "{}", ANSIStrings(&cell))?;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
writeln!(w, "{}", ANSIStrings(&name_cell))?;
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,8 @@ pub use self::escape::escape;
|
||||
|
||||
pub mod details;
|
||||
pub mod file_name;
|
||||
pub mod grid_details;
|
||||
pub mod grid;
|
||||
pub mod grid_details;
|
||||
pub mod icons;
|
||||
pub mod lines;
|
||||
pub mod render;
|
||||
|
@ -1,7 +1,7 @@
|
||||
use ansi_term::Style;
|
||||
|
||||
use crate::output::cell::TextCell;
|
||||
use crate::fs::fields as f;
|
||||
use crate::output::cell::TextCell;
|
||||
|
||||
|
||||
impl f::Blocks {
|
||||
|
@ -6,14 +6,14 @@ use crate::fs::fields as f;
|
||||
impl f::Type {
|
||||
pub fn render<C: Colours>(self, colours: &C) -> ANSIString<'static> {
|
||||
match self {
|
||||
Self::File => colours.normal().paint("."),
|
||||
Self::Directory => colours.directory().paint("d"),
|
||||
Self::Pipe => colours.pipe().paint("|"),
|
||||
Self::Link => colours.symlink().paint("l"),
|
||||
Self::BlockDevice => colours.block_device().paint("b"),
|
||||
Self::CharDevice => colours.char_device().paint("c"),
|
||||
Self::Socket => colours.socket().paint("s"),
|
||||
Self::Special => colours.special().paint("?"),
|
||||
Self::File => colours.normal().paint("."),
|
||||
Self::Directory => colours.directory().paint("d"),
|
||||
Self::Pipe => colours.pipe().paint("|"),
|
||||
Self::Link => colours.symlink().paint("l"),
|
||||
Self::BlockDevice => colours.block_device().paint("b"),
|
||||
Self::CharDevice => colours.char_device().paint("c"),
|
||||
Self::Socket => colours.socket().paint("s"),
|
||||
Self::Special => colours.special().paint("?"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ pub mod test {
|
||||
fn renamed(&self) -> Style { Fixed(94).normal() }
|
||||
fn type_change(&self) -> Style { Fixed(95).normal() }
|
||||
fn ignored(&self) -> Style { Fixed(96).normal() }
|
||||
fn conflicted(&self) -> Style { Fixed(93).normal() }
|
||||
fn conflicted(&self) -> Style { Fixed(97).normal() }
|
||||
}
|
||||
|
||||
|
||||
|
@ -12,14 +12,16 @@ impl f::Group {
|
||||
let mut style = colours.not_yours();
|
||||
|
||||
let group = match users.get_group_by_gid(self.0) {
|
||||
Some(g) => (*g).clone(),
|
||||
None => return TextCell::paint(style, self.0.to_string()),
|
||||
Some(g) => (*g).clone(),
|
||||
None => return TextCell::paint(style, self.0.to_string()),
|
||||
};
|
||||
|
||||
let current_uid = users.get_current_uid();
|
||||
if let Some(current_user) = users.get_user_by_uid(current_uid) {
|
||||
|
||||
if current_user.primary_group_id() == group.gid()
|
||||
|| group.members().iter().any(|u| u == current_user.name()) {
|
||||
|| group.members().iter().any(|u| u == current_user.name())
|
||||
{
|
||||
style = colours.yours();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use ansi_term::Style;
|
||||
|
||||
use crate::output::cell::TextCell;
|
||||
use crate::fs::fields as f;
|
||||
use crate::output::cell::TextCell;
|
||||
|
||||
|
||||
impl f::Inode {
|
||||
|
@ -1,8 +1,8 @@
|
||||
use ansi_term::Style;
|
||||
use locale::Numeric as NumericLocale;
|
||||
|
||||
use crate::output::cell::TextCell;
|
||||
use crate::fs::fields as f;
|
||||
use crate::output::cell::TextCell;
|
||||
|
||||
|
||||
impl f::Links {
|
||||
|
@ -1,26 +1,26 @@
|
||||
use ansi_term::Style;
|
||||
|
||||
use crate::output::cell::TextCell;
|
||||
use crate::fs::fields as f;
|
||||
use crate::output::cell::TextCell;
|
||||
|
||||
|
||||
impl f::OctalPermissions {
|
||||
fn bits_to_octal(r: bool, w: bool, x: bool) -> u8 {
|
||||
(r as u8) * 4 + (w as u8) * 2 + (x as u8)
|
||||
(r as u8) * 4 + (w as u8) * 2 + (x as u8)
|
||||
}
|
||||
|
||||
pub fn render(&self, style: Style) -> TextCell {
|
||||
|
||||
let perm = &self.permissions;
|
||||
let octal_sticky = Self::bits_to_octal(perm.setuid, perm.setgid, perm.sticky);
|
||||
let octal_owner = Self::bits_to_octal(perm.user_read, perm.user_write, perm.user_execute);
|
||||
let octal_group = Self::bits_to_octal(perm.group_read, perm.group_write, perm.group_execute);
|
||||
let octal_other = Self::bits_to_octal(perm.other_read, perm.other_write, perm.other_execute);
|
||||
let octal_owner = Self::bits_to_octal(perm.user_read, perm.user_write, perm.user_execute);
|
||||
let octal_group = Self::bits_to_octal(perm.group_read, perm.group_write, perm.group_execute);
|
||||
let octal_other = Self::bits_to_octal(perm.other_read, perm.other_write, perm.other_execute);
|
||||
|
||||
TextCell::paint(style, format!("{}{}{}{}", octal_sticky, octal_owner, octal_group, octal_other))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use crate::output::cell::TextCell;
|
||||
|
@ -25,23 +25,23 @@ impl f::PermissionsPlus {
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl f::Permissions {
|
||||
pub fn render<C: Colours>(&self, colours: &C, is_regular_file: bool) -> Vec<ANSIString<'static>> {
|
||||
|
||||
let bit = |bit, chr: &'static str, style: Style| {
|
||||
if bit { style.paint(chr) } else { colours.dash().paint("-") }
|
||||
if bit { style.paint(chr) }
|
||||
else { colours.dash().paint("-") }
|
||||
};
|
||||
|
||||
vec![
|
||||
bit(self.user_read, "r", colours.user_read()),
|
||||
bit(self.user_write, "w", colours.user_write()),
|
||||
bit(self.user_read, "r", colours.user_read()),
|
||||
bit(self.user_write, "w", colours.user_write()),
|
||||
self.user_execute_bit(colours, is_regular_file),
|
||||
bit(self.group_read, "r", colours.group_read()),
|
||||
bit(self.group_write, "w", colours.group_write()),
|
||||
bit(self.group_read, "r", colours.group_read()),
|
||||
bit(self.group_write, "w", colours.group_write()),
|
||||
self.group_execute_bit(colours),
|
||||
bit(self.other_read, "r", colours.other_read()),
|
||||
bit(self.other_write, "w", colours.other_write()),
|
||||
bit(self.other_read, "r", colours.other_read()),
|
||||
bit(self.other_write, "w", colours.other_write()),
|
||||
self.other_execute_bit(colours)
|
||||
]
|
||||
}
|
||||
|
@ -21,20 +21,23 @@ impl f::Size {
|
||||
SizeFormat::DecimalBytes => NumberPrefix::decimal(size as f64),
|
||||
SizeFormat::BinaryBytes => NumberPrefix::binary(size as f64),
|
||||
SizeFormat::JustBytes => {
|
||||
|
||||
// Use the binary prefix to select a style.
|
||||
let prefix = match NumberPrefix::binary(size as f64) {
|
||||
NumberPrefix::Standalone(_) => None,
|
||||
NumberPrefix::Prefixed(p, _) => Some(p),
|
||||
NumberPrefix::Standalone(_) => None,
|
||||
NumberPrefix::Prefixed(p, _) => Some(p),
|
||||
};
|
||||
|
||||
// But format the number directly using the locale.
|
||||
let string = numerics.format_int(size);
|
||||
|
||||
return TextCell::paint(colours.size(prefix), string);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let (prefix, n) = match result {
|
||||
NumberPrefix::Standalone(b) => return TextCell::paint(colours.size(None), b.to_string()),
|
||||
NumberPrefix::Prefixed(p, n) => (p, n)
|
||||
NumberPrefix::Standalone(b) => return TextCell::paint(colours.size(None), b.to_string()),
|
||||
NumberPrefix::Prefixed(p, n) => (p, n),
|
||||
};
|
||||
|
||||
let symbol = prefix.symbol();
|
||||
@ -72,6 +75,7 @@ impl f::DeviceIDs {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub trait Colours {
|
||||
fn size(&self, prefix: Option<Prefix>) -> Style;
|
||||
fn unit(&self, prefix: Option<Prefix>) -> Style;
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::time::SystemTime;
|
||||
|
||||
use datetime::TimeZone;
|
||||
use ansi_term::Style;
|
||||
|
||||
@ -6,23 +8,20 @@ use crate::output::time::TimeFormat;
|
||||
|
||||
|
||||
pub trait Render {
|
||||
fn render(self, style: Style,
|
||||
tz: &Option<TimeZone>,
|
||||
format: &TimeFormat) -> TextCell;
|
||||
fn render(self, style: Style, tz: &Option<TimeZone>, format: &TimeFormat) -> TextCell;
|
||||
}
|
||||
|
||||
impl Render for Option<std::time::SystemTime> {
|
||||
fn render(self, style: Style,
|
||||
tz: &Option<TimeZone>,
|
||||
format: &TimeFormat) -> TextCell {
|
||||
|
||||
impl Render for Option<SystemTime> {
|
||||
fn render(self, style: Style, tz: &Option<TimeZone>, format: &TimeFormat) -> TextCell {
|
||||
let datestamp = if let Some(time) = self {
|
||||
if let Some(ref tz) = tz {
|
||||
format.format_zoned(time, tz)
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
format.format_local(time)
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
String::from("-")
|
||||
};
|
||||
|
||||
|
@ -5,7 +5,6 @@ use crate::fs::fields as f;
|
||||
use crate::output::cell::TextCell;
|
||||
|
||||
|
||||
|
||||
impl f::User {
|
||||
pub fn render<C: Colours, U: Users>(self, colours: &C, users: &U) -> TextCell {
|
||||
let user_name = match users.get_user_by_uid(self.0) {
|
||||
@ -13,8 +12,8 @@ impl f::User {
|
||||
None => self.0.to_string(),
|
||||
};
|
||||
|
||||
let style = if users.get_current_uid() == self.0 { colours.you() }
|
||||
else { colours.someone_else() };
|
||||
let style = if users.get_current_uid() == self.0 { colours.you() }
|
||||
else { colours.someone_else() };
|
||||
TextCell::paint(style, user_name)
|
||||
}
|
||||
}
|
||||
|
@ -11,12 +11,12 @@ use log::*;
|
||||
|
||||
use users::UsersCache;
|
||||
|
||||
use crate::style::Colours;
|
||||
use crate::fs::{File, fields as f};
|
||||
use crate::fs::feature::git::GitCache;
|
||||
use crate::output::cell::TextCell;
|
||||
use crate::output::render::TimeRender;
|
||||
use crate::output::time::TimeFormat;
|
||||
use crate::fs::{File, fields as f};
|
||||
use crate::fs::feature::git::GitCache;
|
||||
use crate::style::Colours;
|
||||
|
||||
|
||||
/// Options for displaying a table.
|
||||
@ -108,7 +108,7 @@ impl Columns {
|
||||
columns.push(Column::Timestamp(TimeType::Accessed));
|
||||
}
|
||||
|
||||
if cfg!(feature="git") && self.git && actually_enable_git {
|
||||
if cfg!(feature = "git") && self.git && actually_enable_git {
|
||||
columns.push(Column::GitStatus);
|
||||
}
|
||||
|
||||
@ -136,7 +136,8 @@ pub enum Column {
|
||||
/// right-aligned, and text is left-aligned.
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Alignment {
|
||||
Left, Right,
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
impl Column {
|
||||
@ -144,12 +145,12 @@ impl Column {
|
||||
/// Get the alignment this column should use.
|
||||
pub fn alignment(self) -> Alignment {
|
||||
match self {
|
||||
Self::FileSize |
|
||||
Self::HardLinks |
|
||||
Self::Inode |
|
||||
Self::Blocks |
|
||||
Self::GitStatus => Alignment::Right,
|
||||
_ => Alignment::Left,
|
||||
Self::FileSize |
|
||||
Self::HardLinks |
|
||||
Self::Inode |
|
||||
Self::Blocks |
|
||||
Self::GitStatus => Alignment::Right,
|
||||
_ => Alignment::Left,
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,6 +200,7 @@ impl Default for SizeFormat {
|
||||
/// across most (all?) operating systems.
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
pub enum TimeType {
|
||||
|
||||
/// The file’s modified time (`st_mtime`).
|
||||
Modified,
|
||||
|
||||
@ -229,7 +231,7 @@ impl TimeType {
|
||||
/// Fields for which of a file’s time fields should be displayed in the
|
||||
/// columns output.
|
||||
///
|
||||
/// There should always be at least one of these--there's no way to disable
|
||||
/// There should always be at least one of these — there’s no way to disable
|
||||
/// the time columns entirely (yet).
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
pub struct TimeTypes {
|
||||
@ -244,15 +246,18 @@ impl Default for TimeTypes {
|
||||
/// By default, display just the ‘modified’ time. This is the most
|
||||
/// common option, which is why it has this shorthand.
|
||||
fn default() -> Self {
|
||||
Self { modified: true, changed: false, accessed: false, created: false }
|
||||
Self {
|
||||
modified: true,
|
||||
changed: false,
|
||||
accessed: false,
|
||||
created: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// The **environment** struct contains any data that could change between
|
||||
/// running instances of exa, depending on the user's computer's configuration.
|
||||
/// running instances of exa, depending on the user’s computer’s configuration.
|
||||
///
|
||||
/// Any environment field should be able to be mocked up for test runs.
|
||||
pub struct Environment {
|
||||
@ -260,8 +265,8 @@ pub struct Environment {
|
||||
/// Localisation rules for formatting numbers.
|
||||
numeric: locale::Numeric,
|
||||
|
||||
/// The computer's current time zone. This gets used to determine how to
|
||||
/// offset files' timestamps.
|
||||
/// The computer’s current time zone. This gets used to determine how to
|
||||
/// offset files’ timestamps.
|
||||
tz: Option<TimeZone>,
|
||||
|
||||
/// Mapping cache of user IDs to usernames.
|
||||
@ -275,7 +280,9 @@ impl Environment {
|
||||
|
||||
pub fn load_all() -> Self {
|
||||
let tz = match determine_time_zone() {
|
||||
Ok(t) => Some(t),
|
||||
Ok(t) => {
|
||||
Some(t)
|
||||
}
|
||||
Err(ref e) => {
|
||||
println!("Unable to determine time zone: {}", e);
|
||||
None
|
||||
@ -283,7 +290,7 @@ impl Environment {
|
||||
};
|
||||
|
||||
let numeric = locale::Numeric::load_user_locale()
|
||||
.unwrap_or_else(|_| locale::Numeric::english());
|
||||
.unwrap_or_else(|_| locale::Numeric::english());
|
||||
|
||||
let users = Mutex::new(UsersCache::new());
|
||||
|
||||
@ -294,7 +301,8 @@ impl Environment {
|
||||
fn determine_time_zone() -> TZResult<TimeZone> {
|
||||
if let Ok(file) = env::var("TZ") {
|
||||
TimeZone::from_file(format!("/usr/share/zoneinfo/{}", file))
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
TimeZone::from_file("/etc/localtime")
|
||||
}
|
||||
}
|
||||
@ -321,7 +329,10 @@ impl<'a, 'f> Table<'a> {
|
||||
let widths = TableWidths::zero(columns.len());
|
||||
|
||||
Table {
|
||||
colours, widths, columns, git,
|
||||
colours,
|
||||
widths,
|
||||
columns,
|
||||
git,
|
||||
env: &options.env,
|
||||
time_format: &options.time_format,
|
||||
size_format: options.size_format,
|
||||
@ -368,25 +379,52 @@ impl<'a, 'f> Table<'a> {
|
||||
|
||||
fn display(&self, file: &File, column: Column, xattrs: bool) -> TextCell {
|
||||
match column {
|
||||
Column::Permissions => self.permissions_plus(file, xattrs).render(self.colours),
|
||||
Column::FileSize => file.size().render(self.colours, self.size_format, &self.env.numeric),
|
||||
Column::HardLinks => file.links().render(self.colours, &self.env.numeric),
|
||||
Column::Inode => file.inode().render(self.colours.inode),
|
||||
Column::Blocks => file.blocks().render(self.colours),
|
||||
Column::User => file.user().render(self.colours, &*self.env.lock_users()),
|
||||
Column::Group => file.group().render(self.colours, &*self.env.lock_users()),
|
||||
Column::GitStatus => self.git_status(file).render(self.colours),
|
||||
Column::Octal => self.octal_permissions(file).render(self.colours.octal),
|
||||
Column::Permissions => {
|
||||
self.permissions_plus(file, xattrs).render(self.colours)
|
||||
}
|
||||
Column::FileSize => {
|
||||
file.size().render(self.colours, self.size_format, &self.env.numeric)
|
||||
}
|
||||
Column::HardLinks => {
|
||||
file.links().render(self.colours, &self.env.numeric)
|
||||
}
|
||||
Column::Inode => {
|
||||
file.inode().render(self.colours.inode)
|
||||
}
|
||||
Column::Blocks => {
|
||||
file.blocks().render(self.colours)
|
||||
}
|
||||
Column::User => {
|
||||
file.user().render(self.colours, &*self.env.lock_users())
|
||||
}
|
||||
Column::Group => {
|
||||
file.group().render(self.colours, &*self.env.lock_users())
|
||||
}
|
||||
Column::GitStatus => {
|
||||
self.git_status(file).render(self.colours)
|
||||
}
|
||||
Column::Octal => {
|
||||
self.octal_permissions(file).render(self.colours.octal)
|
||||
}
|
||||
|
||||
Column::Timestamp(TimeType::Modified) => file.modified_time().render(self.colours.date, &self.env.tz, self.time_format),
|
||||
Column::Timestamp(TimeType::Changed) => file.changed_time() .render(self.colours.date, &self.env.tz, self.time_format),
|
||||
Column::Timestamp(TimeType::Created) => file.created_time() .render(self.colours.date, &self.env.tz, self.time_format),
|
||||
Column::Timestamp(TimeType::Accessed) => file.accessed_time().render(self.colours.date, &self.env.tz, self.time_format),
|
||||
Column::Timestamp(TimeType::Modified) => {
|
||||
file.modified_time().render(self.colours.date, &self.env.tz, self.time_format)
|
||||
}
|
||||
Column::Timestamp(TimeType::Changed) => {
|
||||
file.changed_time().render(self.colours.date, &self.env.tz, self.time_format)
|
||||
}
|
||||
Column::Timestamp(TimeType::Created) => {
|
||||
file.created_time().render(self.colours.date, &self.env.tz, self.time_format)
|
||||
}
|
||||
Column::Timestamp(TimeType::Accessed) => {
|
||||
file.accessed_time().render(self.colours.date, &self.env.tz, self.time_format)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn git_status(&self, file: &File) -> f::Git {
|
||||
debug!("Getting Git status for file {:?}", file.path);
|
||||
|
||||
self.git
|
||||
.map(|g| g.get(&file.path, file.is_directory()))
|
||||
.unwrap_or_default()
|
||||
@ -395,12 +433,22 @@ impl<'a, 'f> Table<'a> {
|
||||
pub fn render(&self, row: Row) -> TextCell {
|
||||
let mut cell = TextCell::default();
|
||||
|
||||
for (n, (this_cell, width)) in row.cells.into_iter().zip(self.widths.iter()).enumerate() {
|
||||
let iter = row.cells.into_iter()
|
||||
.zip(self.widths.iter())
|
||||
.enumerate();
|
||||
|
||||
for (n, (this_cell, width)) in iter {
|
||||
let padding = width - *this_cell.width;
|
||||
|
||||
match self.columns[n].alignment() {
|
||||
Alignment::Left => { cell.append(this_cell); cell.add_spaces(padding); }
|
||||
Alignment::Right => { cell.add_spaces(padding); cell.append(this_cell); }
|
||||
Alignment::Left => {
|
||||
cell.append(this_cell);
|
||||
cell.add_spaces(padding);
|
||||
}
|
||||
Alignment::Right => {
|
||||
cell.add_spaces(padding);
|
||||
cell.append(this_cell);
|
||||
}
|
||||
}
|
||||
|
||||
cell.add_spaces(1);
|
||||
@ -411,7 +459,6 @@ impl<'a, 'f> Table<'a> {
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub struct TableWidths(Vec<usize>);
|
||||
|
||||
impl Deref for TableWidths {
|
||||
@ -424,7 +471,7 @@ impl Deref for TableWidths {
|
||||
|
||||
impl TableWidths {
|
||||
pub fn zero(count: usize) -> Self {
|
||||
Self(vec![ 0; count ])
|
||||
Self(vec![0; count])
|
||||
}
|
||||
|
||||
pub fn add_widths(&mut self, row: &Row) {
|
||||
|
@ -119,28 +119,25 @@ impl DefaultFormat {
|
||||
|
||||
Self { current_year, locale, date_and_time, date_and_year }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl DefaultFormat {
|
||||
fn is_recent(&self, date: LocalDateTime) -> bool {
|
||||
date.year() == self.current_year
|
||||
}
|
||||
|
||||
fn month_to_abbrev(month: datetime::Month) -> &'static str {
|
||||
match month {
|
||||
datetime::Month::January => "Jan",
|
||||
datetime::Month::February => "Feb",
|
||||
datetime::Month::March => "Mar",
|
||||
datetime::Month::April => "Apr",
|
||||
datetime::Month::May => "May",
|
||||
datetime::Month::June => "Jun",
|
||||
datetime::Month::July => "Jul",
|
||||
datetime::Month::August => "Aug",
|
||||
datetime::Month::September => "Sep",
|
||||
datetime::Month::October => "Oct",
|
||||
datetime::Month::November => "Nov",
|
||||
datetime::Month::December => "Dec",
|
||||
datetime::Month::January => "Jan",
|
||||
datetime::Month::February => "Feb",
|
||||
datetime::Month::March => "Mar",
|
||||
datetime::Month::April => "Apr",
|
||||
datetime::Month::May => "May",
|
||||
datetime::Month::June => "Jun",
|
||||
datetime::Month::July => "Jul",
|
||||
datetime::Month::August => "Aug",
|
||||
datetime::Month::September => "Sep",
|
||||
datetime::Month::October => "Oct",
|
||||
datetime::Month::November => "Nov",
|
||||
datetime::Month::December => "Dec",
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,7 +214,6 @@ fn long_zoned(time: SystemTime, zone: &TimeZone) -> String {
|
||||
date.hour(), date.minute())
|
||||
}
|
||||
|
||||
|
||||
#[allow(trivial_numeric_casts)]
|
||||
fn full_local(time: SystemTime) -> String {
|
||||
let date = LocalDateTime::at(systemtime_epoch(time));
|
||||
@ -240,7 +236,6 @@ fn full_zoned(time: SystemTime, zone: &TimeZone) -> String {
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ISOFormat {
|
||||
|
||||
|
@ -111,18 +111,21 @@ impl TreeTrunk {
|
||||
// If this isn’t our first iteration, then update the tree parts thus
|
||||
// far to account for there being another row after it.
|
||||
if let Some(last) = self.last_params {
|
||||
self.stack[last.depth.0] = if last.last { TreePart::Blank } else { TreePart::Line };
|
||||
self.stack[last.depth.0] = if last.last { TreePart::Blank }
|
||||
else { TreePart::Line };
|
||||
}
|
||||
|
||||
// Make sure the stack has enough space, then add or modify another
|
||||
// part into it.
|
||||
self.stack.resize(params.depth.0 + 1, TreePart::Edge);
|
||||
self.stack[params.depth.0] = if params.last { TreePart::Corner } else { TreePart::Edge };
|
||||
self.stack[params.depth.0] = if params.last { TreePart::Corner }
|
||||
else { TreePart::Edge };
|
||||
|
||||
self.last_params = Some(params);
|
||||
|
||||
// Return the tree parts as a slice of the stack.
|
||||
//
|
||||
// Ignore the first element here to prevent a 'zeroth level' from
|
||||
// Ignore the first element here to prevent a ‘zeroth level’ from
|
||||
// appearing before the very first directory. This level would
|
||||
// join unrelated directories without connecting to anything:
|
||||
//
|
||||
@ -159,7 +162,8 @@ impl TreeDepth {
|
||||
/// Creates an iterator that, as well as yielding each value, yields a
|
||||
/// `TreeParams` with the current depth and last flag filled in.
|
||||
pub fn iterate_over<I, T>(self, inner: I) -> Iter<I>
|
||||
where I: ExactSizeIterator+Iterator<Item=T> {
|
||||
where I: ExactSizeIterator + Iterator<Item = T>
|
||||
{
|
||||
Iter { current_depth: self, inner }
|
||||
}
|
||||
}
|
||||
@ -171,14 +175,16 @@ pub struct Iter<I> {
|
||||
}
|
||||
|
||||
impl<I, T> Iterator for Iter<I>
|
||||
where I: ExactSizeIterator+Iterator<Item=T> {
|
||||
where I: ExactSizeIterator + Iterator<Item = T>
|
||||
{
|
||||
type Item = (TreeParams, T);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next().map(|t| {
|
||||
// use exact_size_is_empty API soon
|
||||
(TreeParams::new(self.current_depth, self.inner.len() == 0), t)
|
||||
})
|
||||
let t = self.inner.next()?;
|
||||
|
||||
// TODO: use exact_size_is_empty API soon
|
||||
let params = TreeParams::new(self.current_depth, self.inner.len() == 0);
|
||||
Some((params, t))
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,20 +200,20 @@ mod trunk_test {
|
||||
#[test]
|
||||
fn empty_at_first() {
|
||||
let mut tt = TreeTrunk::default();
|
||||
assert_eq!(tt.new_row(params(0, true)), &[]);
|
||||
assert_eq!(tt.new_row(params(0, true)), &[ ]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_child() {
|
||||
let mut tt = TreeTrunk::default();
|
||||
assert_eq!(tt.new_row(params(0, true)), &[]);
|
||||
assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]);
|
||||
assert_eq!(tt.new_row(params(0, true)), &[ ]);
|
||||
assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_children() {
|
||||
let mut tt = TreeTrunk::default();
|
||||
assert_eq!(tt.new_row(params(0, true)), &[]);
|
||||
assert_eq!(tt.new_row(params(0, true)), &[ ]);
|
||||
assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]);
|
||||
assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]);
|
||||
}
|
||||
@ -215,11 +221,11 @@ mod trunk_test {
|
||||
#[test]
|
||||
fn two_times_two_children() {
|
||||
let mut tt = TreeTrunk::default();
|
||||
assert_eq!(tt.new_row(params(0, false)), &[]);
|
||||
assert_eq!(tt.new_row(params(0, false)), &[ ]);
|
||||
assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]);
|
||||
assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]);
|
||||
|
||||
assert_eq!(tt.new_row(params(0, true)), &[]);
|
||||
assert_eq!(tt.new_row(params(0, true)), &[ ]);
|
||||
assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]);
|
||||
assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]);
|
||||
}
|
||||
@ -227,7 +233,7 @@ mod trunk_test {
|
||||
#[test]
|
||||
fn two_times_two_nested_children() {
|
||||
let mut tt = TreeTrunk::default();
|
||||
assert_eq!(tt.new_row(params(0, true)), &[]);
|
||||
assert_eq!(tt.new_row(params(0, true)), &[ ]);
|
||||
|
||||
assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]);
|
||||
assert_eq!(tt.new_row(params(2, false)), &[ TreePart::Line, TreePart::Edge ]);
|
||||
@ -240,7 +246,6 @@ mod trunk_test {
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod iter_test {
|
||||
use super::*;
|
||||
|
@ -1,9 +1,8 @@
|
||||
use ansi_term::Style;
|
||||
use ansi_term::Colour::{Red, Green, Yellow, Blue, Cyan, Purple, Fixed};
|
||||
use ansi_term::Style;
|
||||
|
||||
use crate::output::render;
|
||||
use crate::output::file_name::Colours as FileNameColours;
|
||||
|
||||
use crate::output::render;
|
||||
use crate::style::lsc::Pair;
|
||||
|
||||
|
||||
@ -190,11 +189,8 @@ impl Colours {
|
||||
|
||||
impl Size {
|
||||
pub fn colourful(scale: bool) -> Self {
|
||||
if scale {
|
||||
Self::colourful_scale()
|
||||
} else {
|
||||
Self::colourful_plain()
|
||||
}
|
||||
if scale { Self::colourful_scale() }
|
||||
else { Self::colourful_plain() }
|
||||
}
|
||||
|
||||
fn colourful_plain() -> Self {
|
||||
@ -233,7 +229,6 @@ impl Size {
|
||||
unit_giga: Green.normal(),
|
||||
unit_huge: Green.normal(),
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -352,6 +347,7 @@ impl Colours {
|
||||
|
||||
_ => return false,
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
use std::iter::Peekable;
|
||||
use std::ops::FnMut;
|
||||
|
||||
use ansi_term::{Colour, Style};
|
||||
@ -21,7 +22,6 @@ use ansi_term::Colour::*;
|
||||
// just not worth doing, and there should really be a way to just use slices
|
||||
// of the LS_COLORS string without having to parse them.
|
||||
|
||||
|
||||
pub struct LSColors<'var>(pub &'var str);
|
||||
|
||||
impl<'var> LSColors<'var> {
|
||||
@ -33,21 +33,17 @@ impl<'var> LSColors<'var> {
|
||||
.take(3)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if bits.len() == 2 && !bits[0].is_empty() && !bits[1].is_empty() {
|
||||
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,
|
||||
}
|
||||
|
||||
use std::iter::Peekable;
|
||||
fn parse_into_high_colour<'a, I>(iter: &mut Peekable<I>) -> Option<Colour>
|
||||
where I: Iterator<Item=&'a str> {
|
||||
where I: Iterator<Item = &'a str>
|
||||
{
|
||||
match iter.peek() {
|
||||
Some(&"5") => {
|
||||
let _ = iter.next();
|
||||
@ -57,11 +53,12 @@ where I: Iterator<Item=&'a str> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(&"2") => {
|
||||
let _ = iter.next();
|
||||
if let Some(hexes) = iter.next() {
|
||||
// Some terminals support R:G:B instead of R;G;B
|
||||
// but this clashes with splitting on ':' in each_pair above.
|
||||
// but this clashes with splitting on ‘:’ in each_pair above.
|
||||
/*if hexes.contains(':') {
|
||||
let rgb = hexes.splitn(3, ':').collect::<Vec<_>>();
|
||||
if rgb.len() != 3 {
|
||||
@ -79,11 +76,19 @@ where I: Iterator<Item=&'a str> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => {},
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
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();
|
||||
@ -125,7 +130,7 @@ impl<'var> Pair<'var> {
|
||||
"47" => style = style.on(White),
|
||||
"48" => if let Some(c) = parse_into_high_colour(&mut iter) { style = style.on(c) },
|
||||
|
||||
_ => {/* ignore the error and do nothing */},
|
||||
_ => {/* ignore the error and do nothing */},
|
||||
}
|
||||
}
|
||||
|
||||
@ -185,7 +190,6 @@ mod ansi_test {
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
Loading…
Reference in New Issue
Block a user