mirror of
https://github.com/Llewellynvdm/exa.git
synced 2024-11-25 05:17:34 +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
|
/// - https://crates.io/crates/vergen
|
||||||
|
|
||||||
extern crate datetime;
|
extern crate datetime;
|
||||||
use std::io::Result as IOResult;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::io::Result as IOResult;
|
||||||
|
|
||||||
|
|
||||||
fn git_hash() -> String {
|
fn git_hash() -> String {
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
@ -52,7 +53,8 @@ fn write_statics() -> IOResult<()> {
|
|||||||
|
|
||||||
let ver = if is_development_version() {
|
let ver = if is_development_version() {
|
||||||
format!("exa v{} ({} built on {})", cargo_version(), git_hash(), build_date())
|
format!("exa v{} ({} built on {})", cargo_version(), git_hash(), build_date())
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
format!("exa v{}", cargo_version())
|
format!("exa v{}", cargo_version())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ use log::*;
|
|||||||
use crate::fs::File;
|
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.
|
/// being listed.
|
||||||
///
|
///
|
||||||
/// This object gets passed to the Files themselves, in order for them to
|
/// This object gets passed to the Files themselves, in order for them to
|
||||||
@ -40,7 +40,7 @@ impl Dir {
|
|||||||
|
|
||||||
let contents = fs::read_dir(&path)?
|
let contents = fs::read_dir(&path)?
|
||||||
.map(|result| result.map(|entry| entry.path()))
|
.map(|result| result.map(|entry| entry.path()))
|
||||||
.collect::<Result<_,_>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
Ok(Self { contents, path })
|
Ok(Self { contents, path })
|
||||||
}
|
}
|
||||||
@ -53,7 +53,8 @@ impl Dir {
|
|||||||
dir: self,
|
dir: self,
|
||||||
dotfiles: dots.shows_dotfiles(),
|
dotfiles: dots.shows_dotfiles(),
|
||||||
dots: dots.dots(),
|
dots: dots.dots(),
|
||||||
git, git_ignoring,
|
git,
|
||||||
|
git_ignoring,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +107,9 @@ impl<'dir, 'ig> Files<'dir, 'ig> {
|
|||||||
loop {
|
loop {
|
||||||
if let Some(path) = self.inner.next() {
|
if let Some(path) = self.inner.next() {
|
||||||
let filename = File::filename(path);
|
let filename = File::filename(path);
|
||||||
if !self.dotfiles && filename.starts_with('.') { continue }
|
if ! self.dotfiles && filename.starts_with('.') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if self.git_ignoring {
|
if self.git_ignoring {
|
||||||
let git_status = self.git.map(|g| g.get(path, false)).unwrap_or_default();
|
let git_status = self.git.map(|g| g.get(path, false)).unwrap_or_default();
|
||||||
@ -139,7 +142,6 @@ enum DotsNext {
|
|||||||
Files,
|
Files,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl<'dir, 'ig> Iterator for Files<'dir, 'ig> {
|
impl<'dir, 'ig> Iterator for Files<'dir, 'ig> {
|
||||||
type Item = Result<File<'dir>, (PathBuf, io::Error)>;
|
type Item = Result<File<'dir>, (PathBuf, io::Error)>;
|
||||||
|
|
||||||
@ -149,22 +151,24 @@ impl<'dir, 'ig> Iterator for Files<'dir, 'ig> {
|
|||||||
self.dots = DotsNext::DotDot;
|
self.dots = DotsNext::DotDot;
|
||||||
Some(File::new_aa_current(self.dir)
|
Some(File::new_aa_current(self.dir)
|
||||||
.map_err(|e| (Path::new(".").to_path_buf(), e)))
|
.map_err(|e| (Path::new(".").to_path_buf(), e)))
|
||||||
},
|
}
|
||||||
|
|
||||||
DotsNext::DotDot => {
|
DotsNext::DotDot => {
|
||||||
self.dots = DotsNext::Files;
|
self.dots = DotsNext::Files;
|
||||||
Some(File::new_aa_parent(self.parent(), self.dir)
|
Some(File::new_aa_parent(self.parent(), self.dir)
|
||||||
.map_err(|e| (self.parent(), e)))
|
.map_err(|e| (self.parent(), e)))
|
||||||
},
|
}
|
||||||
|
|
||||||
DotsNext::Files => {
|
DotsNext::Files => {
|
||||||
self.next_visible_file()
|
self.next_visible_file()
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Usually files in Unix use a leading dot to be hidden or visible, but two
|
/// 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.
|
/// visible after an extra `-a` option.
|
||||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||||
pub enum DotFilter {
|
pub enum DotFilter {
|
||||||
|
@ -36,7 +36,9 @@ impl GitCache {
|
|||||||
|
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
impl FromIterator<PathBuf> for GitCache {
|
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 iter = iter.into_iter();
|
||||||
let mut git = Self {
|
let mut git = Self {
|
||||||
repos: Vec::with_capacity(iter.size_hint().0),
|
repos: Vec::with_capacity(iter.size_hint().0),
|
||||||
@ -61,8 +63,10 @@ impl FromIterator<PathBuf> for GitCache {
|
|||||||
|
|
||||||
debug!("Discovered new Git repo");
|
debug!("Discovered new Git repo");
|
||||||
git.repos.push(r);
|
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.
|
/// A **Git repository** is one we’ve discovered somewhere on the filesystem.
|
||||||
pub struct GitRepo {
|
pub struct GitRepo {
|
||||||
|
|
||||||
@ -99,7 +101,9 @@ pub struct GitRepo {
|
|||||||
enum GitContents {
|
enum GitContents {
|
||||||
|
|
||||||
/// All the interesting Git stuff goes through this.
|
/// 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
|
/// Temporary value used in `repo_to_statuses` so we can move the
|
||||||
/// repository out of the `Before` variant.
|
/// 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
|
/// The data we’ve extracted from the repository, but only after we’ve
|
||||||
/// actually done so.
|
/// actually done so.
|
||||||
After { statuses: Git }
|
After {
|
||||||
|
statuses: Git,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GitRepo {
|
impl GitRepo {
|
||||||
@ -116,7 +122,7 @@ impl GitRepo {
|
|||||||
/// depending on the prefix-lookup flag) and returns its Git status.
|
/// depending on the prefix-lookup flag) and returns its Git status.
|
||||||
///
|
///
|
||||||
/// Actually querying the `git2` repository for the mapping of paths to
|
/// 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.
|
/// re-query the entire repository the times after that.
|
||||||
///
|
///
|
||||||
/// The temporary `Processing` enum variant is used after the `git2`
|
/// The temporary `Processing` enum variant is used after the `git2`
|
||||||
@ -166,7 +172,7 @@ impl GitRepo {
|
|||||||
let workdir = workdir.to_path_buf();
|
let workdir = workdir.to_path_buf();
|
||||||
let contents = Mutex::new(GitContents::Before { repo });
|
let contents = Mutex::new(GitContents::Before { repo });
|
||||||
Ok(Self { contents, workdir, original_path: path, extra_paths: Vec::new() })
|
Ok(Self { contents, workdir, original_path: path, extra_paths: Vec::new() })
|
||||||
},
|
}
|
||||||
None => {
|
None => {
|
||||||
warn!("Repository has no workdir?");
|
warn!("Repository has no workdir?");
|
||||||
Err(path)
|
Err(path)
|
||||||
@ -205,8 +211,10 @@ fn repo_to_statuses(repo: &git2::Repository, workdir: &Path) -> Git {
|
|||||||
let elem = (path, e.status());
|
let elem = (path, e.status());
|
||||||
statuses.push(elem);
|
statuses.push(elem);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(e) => error!("Error looking up Git statuses: {:?}", e),
|
Err(e) => {
|
||||||
|
error!("Error looking up Git statuses: {:?}", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Git { statuses }
|
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.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"
|
// 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.
|
// look any faster.
|
||||||
|
|
||||||
|
|
||||||
@ -239,6 +247,7 @@ impl Git {
|
|||||||
/// Get the status for the file at the given path.
|
/// Get the status for the file at the given path.
|
||||||
fn file_status(&self, file: &Path) -> f::Git {
|
fn file_status(&self, file: &Path) -> f::Git {
|
||||||
let path = reorient(file);
|
let path = reorient(file);
|
||||||
|
|
||||||
self.statuses.iter()
|
self.statuses.iter()
|
||||||
.find(|p| p.0.as_path() == path)
|
.find(|p| p.0.as_path() == path)
|
||||||
.map(|&(_, s)| f::Git { staged: index_status(s), unstaged: working_tree_status(s) })
|
.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.
|
/// directories, which don’t really have an ‘official’ status.
|
||||||
fn dir_status(&self, dir: &Path) -> f::Git {
|
fn dir_status(&self, dir: &Path) -> f::Git {
|
||||||
let path = reorient(dir);
|
let path = reorient(dir);
|
||||||
|
|
||||||
let s = self.statuses.iter()
|
let s = self.statuses.iter()
|
||||||
.filter(|p| p.0.starts_with(&path))
|
.filter(|p| p.0.starts_with(&path))
|
||||||
.fold(git2::Status::empty(), |a, b| a | b.1);
|
.fold(git2::Status::empty(), |a, b| a | b.1);
|
||||||
|
|
||||||
f::Git { staged: index_status(s), unstaged: working_tree_status(s) }
|
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.
|
/// Converts a path to an absolute path based on the current directory.
|
||||||
/// Paths need to be absolute for them to be compared properly, otherwise
|
/// 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
|
/// you’d ask a repo about “./README.md” but it only knows about
|
||||||
/// “/vagrant/README.md”, prefixed by the workdir.
|
/// “/vagrant/README.md”, prefixed by the workdir.
|
||||||
fn reorient(path: &Path) -> PathBuf {
|
fn reorient(path: &Path) -> PathBuf {
|
||||||
use std::env::current_dir;
|
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() {
|
let path = match current_dir() {
|
||||||
Err(_) => Path::new(".").join(&path),
|
Err(_) => Path::new(".").join(&path),
|
||||||
Ok(dir) => dir.join(&path),
|
Ok(dir) => dir.join(&path),
|
||||||
};
|
};
|
||||||
|
|
||||||
path.canonicalize().unwrap_or(path)
|
path.canonicalize().unwrap_or(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
pub mod xattr;
|
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 {
|
pub mod git {
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@ -13,8 +14,10 @@ pub mod git {
|
|||||||
pub struct GitCache;
|
pub struct GitCache;
|
||||||
|
|
||||||
impl FromIterator<PathBuf> for GitCache {
|
impl FromIterator<PathBuf> for GitCache {
|
||||||
fn from_iter<I: IntoIterator<Item=PathBuf>>(_iter: I) -> Self {
|
fn from_iter<I>(_iter: I) -> Self
|
||||||
GitCache
|
where I: IntoIterator<Item=PathBuf>
|
||||||
|
{
|
||||||
|
Self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
//! Extended attribute support for Darwin and Linux systems.
|
//! Extended attribute support for Darwin and Linux systems.
|
||||||
|
|
||||||
#![allow(trivial_casts)] // for ARM
|
#![allow(trivial_casts)] // for ARM
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
|
|
||||||
@ -6,7 +7,11 @@ use std::cmp::Ordering;
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::path::Path;
|
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 {
|
pub trait FileAttributes {
|
||||||
fn attributes(&self) -> io::Result<Vec<Attribute>>;
|
fn attributes(&self) -> io::Result<Vec<Attribute>>;
|
||||||
@ -27,20 +32,21 @@ impl FileAttributes for Path {
|
|||||||
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
|
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
|
||||||
impl FileAttributes for Path {
|
impl FileAttributes for Path {
|
||||||
fn attributes(&self) -> io::Result<Vec<Attribute>> {
|
fn attributes(&self) -> io::Result<Vec<Attribute>> {
|
||||||
Ok(vec![])
|
Ok(Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn symlink_attributes(&self) -> io::Result<Vec<Attribute>> {
|
fn symlink_attributes(&self) -> io::Result<Vec<Attribute>> {
|
||||||
Ok(vec![])
|
Ok(Vec::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Attributes which can be passed to `Attribute::list_with_flags`
|
/// Attributes which can be passed to `Attribute::list_with_flags`
|
||||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub enum FollowSymlinks {
|
pub enum FollowSymlinks {
|
||||||
Yes,
|
Yes,
|
||||||
No
|
No,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extended attribute
|
/// Extended attribute
|
||||||
@ -50,13 +56,16 @@ pub struct Attribute {
|
|||||||
pub size: usize,
|
pub size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||||
pub fn list_attrs(lister: &lister::Lister, path: &Path) -> io::Result<Vec<Attribute>> {
|
pub fn list_attrs(lister: &lister::Lister, path: &Path) -> io::Result<Vec<Attribute>> {
|
||||||
use std::ffi::CString;
|
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,
|
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);
|
let bufsize = lister.listxattr_first(&c_path);
|
||||||
@ -91,33 +100,40 @@ pub fn list_attrs(lister: &lister::Lister, path: &Path) -> io::Result<Vec<Attrib
|
|||||||
if size > 0 {
|
if size > 0 {
|
||||||
names.push(Attribute {
|
names.push(Attribute {
|
||||||
name: lister.translate_attribute_name(&buf[start..end]),
|
name: lister.translate_attribute_name(&buf[start..end]),
|
||||||
size: size as usize
|
size: size as usize,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
start = c_end;
|
start = c_end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(names)
|
Ok(names)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
mod lister {
|
mod lister {
|
||||||
use std::ffi::CString;
|
|
||||||
use libc::{c_int, size_t, ssize_t, c_char, c_void};
|
|
||||||
use super::FollowSymlinks;
|
use super::FollowSymlinks;
|
||||||
|
use libc::{c_int, size_t, ssize_t, c_char, c_void};
|
||||||
|
use std::ffi::CString;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn listxattr(
|
fn listxattr(
|
||||||
path: *const c_char, namebuf: *mut c_char,
|
path: *const c_char,
|
||||||
size: size_t, options: c_int
|
namebuf: *mut c_char,
|
||||||
|
size: size_t,
|
||||||
|
options: c_int,
|
||||||
) -> ssize_t;
|
) -> ssize_t;
|
||||||
|
|
||||||
fn getxattr(
|
fn getxattr(
|
||||||
path: *const c_char, name: *const c_char,
|
path: *const c_char,
|
||||||
value: *mut c_void, size: size_t, position: u32,
|
name: *const c_char,
|
||||||
options: c_int
|
value: *mut c_void,
|
||||||
|
size: size_t,
|
||||||
|
position: u32,
|
||||||
|
options: c_int,
|
||||||
) -> ssize_t;
|
) -> ssize_t;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,16 +152,17 @@ mod lister {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn translate_attribute_name(&self, input: &[u8]) -> String {
|
pub fn translate_attribute_name(&self, input: &[u8]) -> String {
|
||||||
use std::str::from_utf8_unchecked;
|
unsafe { std::str::from_utf8_unchecked(input).into() }
|
||||||
|
|
||||||
unsafe {
|
|
||||||
from_utf8_unchecked(input).into()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn listxattr_first(&self, c_path: &CString) -> ssize_t {
|
pub fn listxattr_first(&self, c_path: &CString) -> ssize_t {
|
||||||
unsafe {
|
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(
|
listxattr(
|
||||||
c_path.as_ptr(),
|
c_path.as_ptr(),
|
||||||
buf.as_mut_ptr() as *mut c_char,
|
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(
|
getxattr(
|
||||||
c_path.as_ptr(),
|
c_path.as_ptr(),
|
||||||
buf.as_ptr() as *const c_char,
|
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")]
|
#[cfg(target_os = "linux")]
|
||||||
mod lister {
|
mod lister {
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
@ -180,21 +202,29 @@ mod lister {
|
|||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn listxattr(
|
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;
|
) -> ssize_t;
|
||||||
|
|
||||||
fn llistxattr(
|
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;
|
) -> ssize_t;
|
||||||
|
|
||||||
fn getxattr(
|
fn getxattr(
|
||||||
path: *const c_char, name: *const c_char,
|
path: *const c_char,
|
||||||
value: *mut c_void, size: size_t
|
name: *const c_char,
|
||||||
|
value: *mut c_void,
|
||||||
|
size: size_t,
|
||||||
) -> ssize_t;
|
) -> ssize_t;
|
||||||
|
|
||||||
fn lgetxattr(
|
fn lgetxattr(
|
||||||
path: *const c_char, name: *const c_char,
|
path: *const c_char,
|
||||||
value: *mut c_void, size: size_t
|
name: *const c_char,
|
||||||
|
value: *mut c_void,
|
||||||
|
size: size_t,
|
||||||
) -> ssize_t;
|
) -> ssize_t;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,7 +248,11 @@ mod lister {
|
|||||||
};
|
};
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
listxattr(c_path.as_ptr() as *const _, ptr::null_mut(), 0)
|
listxattr(
|
||||||
|
c_path.as_ptr() as *const _,
|
||||||
|
ptr::null_mut(),
|
||||||
|
0,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,7 +266,7 @@ mod lister {
|
|||||||
listxattr(
|
listxattr(
|
||||||
c_path.as_ptr() as *const _,
|
c_path.as_ptr() as *const _,
|
||||||
buf.as_mut_ptr() as *mut c_char,
|
buf.as_mut_ptr() as *mut c_char,
|
||||||
bufsize as size_t
|
bufsize as size_t,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -247,7 +281,8 @@ mod lister {
|
|||||||
getxattr(
|
getxattr(
|
||||||
c_path.as_ptr() as *const _,
|
c_path.as_ptr() as *const _,
|
||||||
buf.as_ptr() as *const c_char,
|
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.
|
//! Wrapper types for the values returned from `File`s.
|
||||||
//!
|
//!
|
||||||
//! The methods of `File` that return information about the entry on the
|
//! 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.
|
/// Its ordering is used when sorting by type.
|
||||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
|
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
|
||||||
pub enum Type {
|
pub enum Type {
|
||||||
Directory, File, Link, Pipe, Socket, CharDevice, BlockDevice, Special,
|
Directory,
|
||||||
|
File,
|
||||||
|
Link,
|
||||||
|
Pipe,
|
||||||
|
Socket,
|
||||||
|
CharDevice,
|
||||||
|
BlockDevice,
|
||||||
|
Special,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Type {
|
impl Type {
|
||||||
@ -152,7 +158,7 @@ pub enum Size {
|
|||||||
/// have a file size. For example, a directory will just contain a list of
|
/// 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
|
/// 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
|
/// 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.
|
/// learnt something. So we discard it and just output “-” instead.
|
||||||
///
|
///
|
||||||
/// See this answer for more: http://unix.stackexchange.com/a/68266
|
/// 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)
|
/// A file that’s ignored (that matches a line in .gitignore)
|
||||||
Ignored,
|
Ignored,
|
||||||
|
|
||||||
/// A file that's updated but unmerged.
|
/// A file that’s updated but unmerged.
|
||||||
Conflicted,
|
Conflicted,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// A file’s complete Git status. It’s possible to make changes to a file, add
|
/// 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
|
/// it to the staging area, then make *more* changes, so we need to list each
|
||||||
/// file’s status for both of these.
|
/// file’s status for both of these.
|
||||||
@ -227,11 +234,13 @@ pub struct Git {
|
|||||||
pub unstaged: GitStatus,
|
pub unstaged: GitStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::default::Default;
|
|
||||||
impl Default for Git {
|
impl Default for Git {
|
||||||
|
|
||||||
/// Create a Git status for a file with nothing done to it.
|
/// Create a Git status for a file with nothing done to it.
|
||||||
fn default() -> Self {
|
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::Error as IOError;
|
||||||
use std::io::Result as IOResult;
|
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::path::{Path, PathBuf};
|
||||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
@ -11,7 +11,8 @@ use log::*;
|
|||||||
use crate::fs::dir::Dir;
|
use crate::fs::dir::Dir;
|
||||||
use crate::fs::fields as f;
|
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.
|
/// associated data about the file.
|
||||||
///
|
///
|
||||||
/// Each file is definitely going to have its filename displayed at least
|
/// 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
|
/// 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 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,
|
pub metadata: std::fs::Metadata,
|
||||||
|
|
||||||
/// A reference to the directory that contains this file, if any.
|
/// 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 `..`.
|
/// Whether this is one of the two `--all all` directories, `.` and `..`.
|
||||||
///
|
///
|
||||||
/// Unlike all other entries, these are not returned as part of the
|
/// 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.
|
/// means that they should be skipped when recursing.
|
||||||
pub is_all_all: bool,
|
pub is_all_all: bool,
|
||||||
}
|
}
|
||||||
@ -88,8 +89,9 @@ impl<'dir> File<'dir> {
|
|||||||
debug!("Statting file {:?}", &path);
|
debug!("Statting file {:?}", &path);
|
||||||
let metadata = std::fs::symlink_metadata(&path)?;
|
let metadata = std::fs::symlink_metadata(&path)?;
|
||||||
let is_all_all = true;
|
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>> {
|
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);
|
debug!("Statting file {:?}", &path);
|
||||||
let metadata = std::fs::symlink_metadata(&path)?;
|
let metadata = std::fs::symlink_metadata(&path)?;
|
||||||
let is_all_all = true;
|
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
|
/// 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> {
|
fn ext(path: &Path) -> Option<String> {
|
||||||
let name = path.file_name().map(|f| f.to_string_lossy().to_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.
|
/// Whether this file is a directory on the filesystem.
|
||||||
@ -249,7 +254,8 @@ impl<'dir> File<'dir> {
|
|||||||
Ok(metadata) => {
|
Ok(metadata) => {
|
||||||
let ext = File::ext(&path);
|
let ext = File::ext(&path);
|
||||||
let name = File::filename(&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) => {
|
Err(e) => {
|
||||||
error!("Error following link {:?}: {:#?}", &path, 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 {
|
pub fn inode(&self) -> f::Inode {
|
||||||
f::Inode(self.metadata.ino())
|
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 {
|
pub fn blocks(&self) -> f::Blocks {
|
||||||
if self.is_file() || self.is_link() {
|
if self.is_file() || self.is_link() {
|
||||||
f::Blocks::Some(self.metadata.blocks())
|
f::Blocks::Some(self.metadata.blocks())
|
||||||
@ -334,17 +340,19 @@ impl<'dir> File<'dir> {
|
|||||||
pub fn changed_time(&self) -> Option<SystemTime> {
|
pub fn changed_time(&self) -> Option<SystemTime> {
|
||||||
let (mut sec, mut nsec) = (self.metadata.ctime(), self.metadata.ctime_nsec());
|
let (mut sec, mut nsec) = (self.metadata.ctime(), self.metadata.ctime_nsec());
|
||||||
|
|
||||||
Some(
|
|
||||||
if sec < 0 {
|
if sec < 0 {
|
||||||
if nsec > 0 {
|
if nsec > 0 {
|
||||||
sec += 1;
|
sec += 1;
|
||||||
nsec -= 1_000_000_000;
|
nsec -= 1_000_000_000;
|
||||||
}
|
}
|
||||||
UNIX_EPOCH - Duration::new(sec.abs() as u64, nsec.abs() as u32)
|
|
||||||
} else {
|
let duration = Duration::new(sec.abs() as u64, nsec.abs() as u32);
|
||||||
UNIX_EPOCH + Duration::new(sec as u64, nsec 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.
|
/// 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.
|
/// This file’s permissions, with flags for each bit.
|
||||||
pub fn permissions(&self) -> f::Permissions {
|
pub fn permissions(&self) -> f::Permissions {
|
||||||
let bits = self.metadata.mode();
|
let bits = self.metadata.mode();
|
||||||
let has_bit = |bit| { bits & bit == bit };
|
let has_bit = |bit| bits & bit == bit;
|
||||||
|
|
||||||
f::Permissions {
|
f::Permissions {
|
||||||
user_read: has_bit(modes::USER_READ),
|
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.
|
/// that get passed in.
|
||||||
pub fn name_is_one_of(&self, choices: &[&str]) -> bool {
|
pub fn name_is_one_of(&self, choices: &[&str]) -> bool {
|
||||||
choices.contains(&&self.name[..])
|
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
|
// 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
|
// `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> {
|
impl<'dir> FileTarget<'dir> {
|
||||||
@ -471,9 +479,10 @@ impl<'dir> FileTarget<'dir> {
|
|||||||
/// More readable aliases for the permission bits exposed by libc.
|
/// More readable aliases for the permission bits exposed by libc.
|
||||||
#[allow(trivial_numeric_casts)]
|
#[allow(trivial_numeric_casts)]
|
||||||
mod modes {
|
mod modes {
|
||||||
pub type Mode = u32;
|
|
||||||
// The `libc::mode_t` type’s actual type varies, but the value returned
|
// The `libc::mode_t` type’s actual type varies, but the value returned
|
||||||
// from `metadata.permissions().mode()` is always `u32`.
|
// from `metadata.permissions().mode()` is always `u32`.
|
||||||
|
pub type Mode = u32;
|
||||||
|
|
||||||
pub const USER_READ: Mode = libc::S_IRUSR as Mode;
|
pub const USER_READ: Mode = libc::S_IRUSR as Mode;
|
||||||
pub const USER_WRITE: Mode = libc::S_IWUSR 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::os::unix::fs::MetadataExt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::fs::File;
|
|
||||||
use crate::fs::DotFilter;
|
use crate::fs::DotFilter;
|
||||||
|
use crate::fs::File;
|
||||||
|
|
||||||
|
|
||||||
/// The **file filter** processes a list of files before displaying them to
|
/// The **file filter** processes a list of files before displaying them to
|
||||||
@ -88,12 +88,11 @@ pub struct FileFilter {
|
|||||||
pub git_ignore: GitIgnore,
|
pub git_ignore: GitIgnore,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl FileFilter {
|
impl FileFilter {
|
||||||
/// Remove every file in the given vector that does *not* pass the
|
/// Remove every file in the given vector that does *not* pass the
|
||||||
/// filter predicate for files found inside a directory.
|
/// filter predicate for files found inside a directory.
|
||||||
pub fn filter_child_files(&self, files: &mut Vec<File>) {
|
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 {
|
if self.only_dirs {
|
||||||
files.retain(File::is_directory);
|
files.retain(File::is_directory);
|
||||||
@ -110,14 +109,18 @@ impl FileFilter {
|
|||||||
/// `exa -I='*.ogg' music/*` should filter out the ogg files obtained
|
/// `exa -I='*.ogg' music/*` should filter out the ogg files obtained
|
||||||
/// from the glob, even though the globbing is done by the shell!
|
/// from the glob, even though the globbing is done by the shell!
|
||||||
pub fn filter_argument_files(&self, files: &mut Vec<File>) {
|
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.
|
/// Sort the files in the given vector based on the sort field option.
|
||||||
pub fn sort_files<'a, F>(&self, files: &mut Vec<F>)
|
pub fn sort_files<'a, F>(&self, files: &mut Vec<F>)
|
||||||
where F: AsRef<File<'a>> {
|
where F: AsRef<File<'a>>
|
||||||
|
{
|
||||||
files.sort_by(|a, b| self.sort_field.compare_files(a.as_ref(), b.as_ref()));
|
files.sort_by(|a, b| {
|
||||||
|
self.sort_field.compare_files(a.as_ref(), b.as_ref())
|
||||||
|
});
|
||||||
|
|
||||||
if self.reverse {
|
if self.reverse {
|
||||||
files.reverse();
|
files.reverse();
|
||||||
@ -126,7 +129,8 @@ impl FileFilter {
|
|||||||
if self.list_dirs_first {
|
if self.list_dirs_first {
|
||||||
// This relies on the fact that `sort_by` is *stable*: it will keep
|
// This relies on the fact that `sort_by` is *stable*: it will keep
|
||||||
// adjacent elements next to each other.
|
// adjacent elements next to each other.
|
||||||
files.sort_by(|a, b| {b.as_ref().points_to_directory()
|
files.sort_by(|a, b| {
|
||||||
|
b.as_ref().points_to_directory()
|
||||||
.cmp(&a.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”).
|
/// The time the file was changed (the “ctime”).
|
||||||
///
|
///
|
||||||
/// This field is used to mark the time when a file’s metadata
|
/// 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.
|
/// In original Unix, this was, however, meant as creation time.
|
||||||
/// https://www.bell-labs.com/usr/dmr/www/cacm.html
|
/// https://www.bell-labs.com/usr/dmr/www/cacm.html
|
||||||
ChangedDate,
|
ChangedDate,
|
||||||
|
|
||||||
/// The time the file was created (the "btime" or "birthtime").
|
/// The time the file was created (the “btime” or “birthtime”).
|
||||||
CreatedDate,
|
CreatedDate,
|
||||||
|
|
||||||
/// The type of the file: directories, links, pipes, regular, files, etc.
|
/// The type of the file: directories, links, pipes, regular, files, etc.
|
||||||
@ -280,11 +284,8 @@ impl SortField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn strip_dot(n: &str) -> &str {
|
fn strip_dot(n: &str) -> &str {
|
||||||
if n.starts_with('.') {
|
if n.starts_with('.') { &n[1..] }
|
||||||
&n[1..]
|
else { n }
|
||||||
} else {
|
|
||||||
n
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,8 +299,12 @@ pub struct IgnorePatterns {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FromIterator<glob::Pattern> for 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.
|
// > usually found in $XDG_CONFIG_HOME/git/ignore.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_ignores {
|
mod test_ignores {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -4,7 +4,7 @@ pub use self::dir::{Dir, DotFilter};
|
|||||||
mod file;
|
mod file;
|
||||||
pub use self::file::{File, FileTarget};
|
pub use self::file::{File, FileTarget};
|
||||||
|
|
||||||
|
pub mod dir_action;
|
||||||
pub mod feature;
|
pub mod feature;
|
||||||
pub mod fields;
|
pub mod fields;
|
||||||
pub mod filter;
|
pub mod filter;
|
||||||
pub mod dir_action;
|
|
||||||
|
@ -124,11 +124,17 @@ impl FileIcon for FileExtensions {
|
|||||||
fn icon_file(&self, file: &File) -> Option<char> {
|
fn icon_file(&self, file: &File) -> Option<char> {
|
||||||
use crate::output::icons::Icons;
|
use crate::output::icons::Icons;
|
||||||
|
|
||||||
Some(match file {
|
if self.is_music(file) || self.is_lossless(file) {
|
||||||
f if self.is_music(f) || self.is_lossless(f) => Icons::Audio.value(),
|
Some(Icons::Audio.value())
|
||||||
f if self.is_image(f) => Icons::Image.value(),
|
}
|
||||||
f if self.is_video(f) => Icons::Video.value(),
|
else if self.is_image(file) {
|
||||||
_ => return None,
|
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
|
/// The point of this is to highlight compiled files such as `foo.js` when
|
||||||
/// their source file `foo.coffee` exists in the same directory.
|
/// their source file `foo.coffee` exists in the same directory.
|
||||||
/// For example, `foo.js` is perfectly valid without `foo.coffee`, so we
|
/// 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`)
|
/// (See also `FileExtensions#is_compiled`)
|
||||||
pub fn get_source_files(&self) -> Vec<PathBuf> {
|
pub fn get_source_files(&self) -> Vec<PathBuf> {
|
||||||
if let Some(ext) = &self.ext {
|
if let Some(ext) = &self.ext {
|
||||||
@ -34,7 +34,7 @@ impl<'a> File<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
vec![] // No source files if there's no extension, either!
|
vec![] // No source files if there’s no extension, either!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
49
src/main.rs
49
src/main.rs
@ -11,11 +11,10 @@ use ansi_term::{ANSIStrings, Style};
|
|||||||
use log::*;
|
use log::*;
|
||||||
|
|
||||||
use crate::fs::{Dir, File};
|
use crate::fs::{Dir, File};
|
||||||
use crate::fs::filter::GitIgnore;
|
|
||||||
use crate::fs::feature::git::GitCache;
|
use crate::fs::feature::git::GitCache;
|
||||||
|
use crate::fs::filter::GitIgnore;
|
||||||
use crate::options::{Options, Vars};
|
use crate::options::{Options, Vars};
|
||||||
pub use crate::options::vars;
|
pub use crate::options::{Misfire, vars};
|
||||||
pub use crate::options::Misfire;
|
|
||||||
use crate::output::{escape, lines, grid, grid_details, details, View, Mode};
|
use crate::output::{escape, lines, grid, grid_details, details, View, Mode};
|
||||||
|
|
||||||
mod fs;
|
mod fs;
|
||||||
@ -35,18 +34,24 @@ fn main() {
|
|||||||
match Exa::from_args(args.iter(), stdout()) {
|
match Exa::from_args(args.iter(), stdout()) {
|
||||||
Ok(mut exa) => {
|
Ok(mut exa) => {
|
||||||
match exa.run() {
|
match exa.run() {
|
||||||
Ok(exit_status) => exit(exit_status),
|
Ok(exit_status) => {
|
||||||
|
exit(exit_status)
|
||||||
|
}
|
||||||
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
match e.kind() {
|
match e.kind() {
|
||||||
ErrorKind::BrokenPipe => exit(exits::SUCCESS),
|
ErrorKind::BrokenPipe => {
|
||||||
|
exit(exits::SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
eprintln!("{}", e);
|
eprintln!("{}", e);
|
||||||
exit(exits::RUNTIME_ERROR);
|
exit(exits::RUNTIME_ERROR);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
|
|
||||||
Err(ref e) if e.is_error() => {
|
Err(ref e) if e.is_error() => {
|
||||||
let mut stderr = stderr();
|
let mut stderr = stderr();
|
||||||
@ -57,13 +62,13 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
exit(exits::OPTIONS_ERROR);
|
exit(exits::OPTIONS_ERROR);
|
||||||
},
|
}
|
||||||
|
|
||||||
Err(ref e) => {
|
Err(ref e) => {
|
||||||
println!("{}", e);
|
println!("{}", e);
|
||||||
exit(exits::SUCCESS);
|
exit(exits::SUCCESS);
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -109,7 +114,7 @@ fn git_options(options: &Options, args: &[&OsStr]) -> Option<GitCache> {
|
|||||||
|
|
||||||
impl<'args> Exa<'args> {
|
impl<'args> Exa<'args> {
|
||||||
pub fn from_args<I>(args: I, writer: Stdout) -> Result<Exa<'args>, Misfire>
|
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)?;
|
let (options, mut args) = Options::parse(args, &LiveVars)?;
|
||||||
debug!("Dir action from arguments: {:#?}", options.dir_action);
|
debug!("Dir action from arguments: {:#?}", options.dir_action);
|
||||||
@ -136,9 +141,10 @@ impl<'args> Exa<'args> {
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
exit_status = 2;
|
exit_status = 2;
|
||||||
writeln!(stderr(), "{:?}: {}", file_path, e)?;
|
writeln!(stderr(), "{:?}: {}", file_path, e)?;
|
||||||
},
|
}
|
||||||
|
|
||||||
Ok(f) => {
|
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() {
|
match f.to_dir() {
|
||||||
Ok(d) => dirs.push(d),
|
Ok(d) => dirs.push(d),
|
||||||
Err(e) => writeln!(stderr(), "{:?}: {}", file_path, e)?,
|
Err(e) => writeln!(stderr(), "{:?}: {}", file_path, e)?,
|
||||||
@ -147,7 +153,7 @@ impl<'args> Exa<'args> {
|
|||||||
else {
|
else {
|
||||||
files.push(f);
|
files.push(f);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +182,7 @@ impl<'args> Exa<'args> {
|
|||||||
writeln!(&mut self.writer)?;
|
writeln!(&mut self.writer)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !is_only_dir {
|
if ! is_only_dir {
|
||||||
let mut bits = Vec::new();
|
let mut bits = Vec::new();
|
||||||
escape(dir.path.display().to_string(), &mut bits, Style::default(), Style::default());
|
escape(dir.path.display().to_string(), &mut bits, Style::default(), Style::default());
|
||||||
writeln!(&mut self.writer, "{}:", ANSIStrings(&bits))?;
|
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() {
|
if let Some(recurse_opts) = self.options.dir_action.recurse_options() {
|
||||||
let depth = dir.path.components().filter(|&c| c != Component::CurDir).count() + 1;
|
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();
|
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() {
|
match child_dir.to_dir() {
|
||||||
Ok(d) => child_dirs.push(d),
|
Ok(d) => child_dirs.push(d),
|
||||||
Err(e) => writeln!(stderr(), "{}: {}", child_dir.path.display(), e)?,
|
Err(e) => writeln!(stderr(), "{}: {}", child_dir.path.display(), e)?,
|
||||||
@ -225,7 +231,10 @@ impl<'args> Exa<'args> {
|
|||||||
/// For various annoying logistical reasons, each one handles
|
/// For various annoying logistical reasons, each one handles
|
||||||
/// printing differently...
|
/// printing differently...
|
||||||
fn print_files(&mut self, dir: Option<&Dir>, files: Vec<File>) -> IOResult<()> {
|
fn print_files(&mut self, dir: Option<&Dir>, files: Vec<File>) -> IOResult<()> {
|
||||||
if !files.is_empty() {
|
if files.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let View { ref mode, ref colours, ref style } = self.options.view;
|
let View { ref mode, ref colours, ref style } = self.options.view;
|
||||||
|
|
||||||
match mode {
|
match mode {
|
||||||
@ -260,10 +269,6 @@ impl<'args> Exa<'args> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ impl DirAction {
|
|||||||
|
|
||||||
if matches.is_strict() {
|
if matches.is_strict() {
|
||||||
// Early check for --level when it wouldn’t do anything
|
// 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));
|
return Err(Misfire::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE));
|
||||||
}
|
}
|
||||||
else if recurse && as_file {
|
else if recurse && as_file {
|
||||||
|
@ -42,29 +42,64 @@ impl SortField {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let field = match word {
|
let field = match word {
|
||||||
"name" | "filename" => Self::Name(SortCase::AaBbCc),
|
"name" | "filename" => {
|
||||||
"Name" | "Filename" => Self::Name(SortCase::ABCabc),
|
Self::Name(SortCase::AaBbCc)
|
||||||
".name" | ".filename" => Self::NameMixHidden(SortCase::AaBbCc),
|
}
|
||||||
".Name" | ".Filename" => Self::NameMixHidden(SortCase::ABCabc),
|
"Name" | "Filename" => {
|
||||||
"size" | "filesize" => Self::Size,
|
Self::Name(SortCase::ABCabc)
|
||||||
"ext" | "extension" => Self::Extension(SortCase::AaBbCc),
|
}
|
||||||
"Ext" | "Extension" => Self::Extension(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”
|
// “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
|
// 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
|
// is the right way round to do this: “size” puts the smallest at
|
||||||
// the top and the largest at the bottom, doesn’t it?
|
// 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
|
// Similarly, “age” means that files with the least age (the
|
||||||
// newest files) get sorted at the top, and files with the most
|
// newest files) get sorted at the top, and files with the most
|
||||||
// age (the oldest) at the bottom.
|
// age (the oldest) at the bottom.
|
||||||
"age" | "old" | "oldest" => Self::ModifiedAge,
|
"age" | "old" | "oldest" => {
|
||||||
"ch" | "changed" => Self::ChangedDate,
|
Self::ModifiedAge
|
||||||
"acc" | "accessed" => Self::AccessedDate,
|
}
|
||||||
"cr" | "created" => Self::CreatedDate,
|
|
||||||
"inode" => Self::FileInode,
|
"ch" | "changed" => {
|
||||||
"type" => Self::FileType,
|
Self::ChangedDate
|
||||||
"none" => Self::Unsorted,
|
}
|
||||||
_ => return Err(Misfire::BadArgument(&flags::SORT, word.into()))
|
"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)
|
Ok(field)
|
||||||
@ -103,7 +138,6 @@ impl SortField {
|
|||||||
// “apps” first, then “Documents”.
|
// “apps” first, then “Documents”.
|
||||||
//
|
//
|
||||||
// You can get the old behaviour back by sorting with `--sort=Name`.
|
// You can get the old behaviour back by sorting with `--sort=Name`.
|
||||||
|
|
||||||
impl Default for SortField {
|
impl Default for SortField {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Name(SortCase::AaBbCc)
|
Self::Name(SortCase::AaBbCc)
|
||||||
@ -151,8 +185,8 @@ impl IgnorePatterns {
|
|||||||
// If there are no inputs, we return a set of patterns that doesn’t
|
// If there are no inputs, we return a set of patterns that doesn’t
|
||||||
// match anything, rather than, say, `None`.
|
// match anything, rather than, say, `None`.
|
||||||
let inputs = match matches.get(&flags::IGNORE_GLOB)? {
|
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
|
// Awkwardly, though, a glob pattern can be invalid, and we need to
|
||||||
@ -171,13 +205,16 @@ impl IgnorePatterns {
|
|||||||
|
|
||||||
impl GitIgnore {
|
impl GitIgnore {
|
||||||
pub fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
|
pub fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
|
||||||
Ok(if matches.has(&flags::GIT_IGNORE)? { Self::CheckAndIgnore }
|
if matches.has(&flags::GIT_IGNORE)? {
|
||||||
else { Self::Off })
|
Ok(Self::CheckAndIgnore)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Ok(Self::Off)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::options::parser::{Arg, Args, Values, TakesValue};
|
use crate::options::parser::{Arg, Args, TakesValue, Values};
|
||||||
|
|
||||||
|
|
||||||
// exa options
|
// exa options
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::fs::feature::xattr;
|
||||||
use crate::options::flags;
|
use crate::options::flags;
|
||||||
use crate::options::parser::MatchedFlags;
|
use crate::options::parser::MatchedFlags;
|
||||||
use crate::fs::feature::xattr;
|
|
||||||
|
|
||||||
|
|
||||||
static OPTIONS: &str = r##"
|
static OPTIONS: &str = r##"
|
||||||
@ -106,7 +106,7 @@ impl fmt::Display for HelpString {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
writeln!(f, "Usage:\n exa [options] [files...]")?;
|
writeln!(f, "Usage:\n exa [options] [files...]")?;
|
||||||
|
|
||||||
if !self.only_long {
|
if ! self.only_long {
|
||||||
write!(f, "{}", OPTIONS)?;
|
write!(f, "{}", OPTIONS)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,7 +127,6 @@ impl fmt::Display for HelpString {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::options::Options;
|
use crate::options::Options;
|
||||||
|
@ -6,7 +6,7 @@ use crate::options::{flags, HelpString, VersionString};
|
|||||||
use crate::options::parser::{Arg, Flag, ParseError};
|
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.
|
/// catch-all for anything outside the program’s normal execution.
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub enum Misfire {
|
pub enum Misfire {
|
||||||
@ -34,7 +34,7 @@ pub enum Misfire {
|
|||||||
Conflict(&'static Arg, &'static Arg),
|
Conflict(&'static Arg, &'static Arg),
|
||||||
|
|
||||||
/// An option was given that does nothing when another one either is or
|
/// 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),
|
Useless(&'static Arg, bool, &'static Arg),
|
||||||
|
|
||||||
/// An option was given that does nothing when either of two other options
|
/// An option was given that does nothing when either of two other options
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
//!
|
//!
|
||||||
//! `--sort=size` should override `--sort=Name` because it’s closer to the end
|
//! `--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
|
//! 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!
|
//! if no aliases are being used!
|
||||||
//!
|
//!
|
||||||
//! Finally, this isn’t just useful when options could override each other.
|
//! Finally, this isn’t just useful when options could override each other.
|
||||||
@ -72,12 +72,13 @@
|
|||||||
use std::ffi::{OsStr, OsString};
|
use std::ffi::{OsStr, OsString};
|
||||||
|
|
||||||
use crate::fs::dir_action::DirAction;
|
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};
|
use crate::output::{View, Mode, details, grid_details};
|
||||||
|
|
||||||
mod style;
|
|
||||||
mod dir_action;
|
mod dir_action;
|
||||||
mod filter;
|
mod filter;
|
||||||
|
mod flags;
|
||||||
|
mod style;
|
||||||
mod view;
|
mod view;
|
||||||
|
|
||||||
mod help;
|
mod help;
|
||||||
@ -93,7 +94,6 @@ pub mod vars;
|
|||||||
pub use self::vars::Vars;
|
pub use self::vars::Vars;
|
||||||
|
|
||||||
mod parser;
|
mod parser;
|
||||||
mod flags;
|
|
||||||
use self::parser::MatchedFlags;
|
use self::parser::MatchedFlags;
|
||||||
|
|
||||||
|
|
||||||
@ -120,8 +120,9 @@ impl Options {
|
|||||||
/// for extra options.
|
/// for extra options.
|
||||||
#[allow(unused_results)]
|
#[allow(unused_results)]
|
||||||
pub fn parse<'args, I, V>(args: I, vars: &V) -> Result<(Self, Vec<&'args OsStr>), Misfire>
|
pub fn parse<'args, I, V>(args: I, vars: &V) -> Result<(Self, Vec<&'args OsStr>), Misfire>
|
||||||
where I: IntoIterator<Item=&'args OsString>,
|
where I: IntoIterator<Item = &'args OsString>,
|
||||||
V: Vars {
|
V: Vars
|
||||||
|
{
|
||||||
use crate::options::parser::{Matches, Strictness};
|
use crate::options::parser::{Matches, Strictness};
|
||||||
|
|
||||||
let strictness = match vars.get(vars::EXA_STRICT) {
|
let strictness = match vars.get(vars::EXA_STRICT) {
|
||||||
@ -169,7 +170,6 @@ impl Options {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod test {
|
pub mod test {
|
||||||
use super::{Options, Misfire, flags};
|
use super::{Options, Misfire, flags};
|
||||||
|
@ -144,7 +144,8 @@ impl Args {
|
|||||||
/// Iterates over the given list of command-line arguments and parses
|
/// Iterates over the given list of command-line arguments and parses
|
||||||
/// them into a list of matched flags and free strings.
|
/// them into a list of matched flags and free strings.
|
||||||
pub fn parse<'args, I>(&self, inputs: I, strictness: Strictness) -> Result<Matches<'args>, ParseError>
|
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;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
|
||||||
let mut parsing = true;
|
let mut parsing = true;
|
||||||
@ -164,7 +165,7 @@ impl Args {
|
|||||||
// This allows a file named “--arg” to be specified by passing in
|
// This allows a file named “--arg” to be specified by passing in
|
||||||
// the pair “-- --arg”, without it getting matched as a flag that
|
// the pair “-- --arg”, without it getting matched as a flag that
|
||||||
// doesn’t exist.
|
// doesn’t exist.
|
||||||
if !parsing {
|
if ! parsing {
|
||||||
frees.push(arg)
|
frees.push(arg)
|
||||||
}
|
}
|
||||||
else if arg == "--" {
|
else if arg == "--" {
|
||||||
@ -348,7 +349,7 @@ pub struct Matches<'args> {
|
|||||||
pub flags: MatchedFlags<'args>,
|
pub flags: MatchedFlags<'args>,
|
||||||
|
|
||||||
/// All the strings that weren’t matched as arguments, as well as anything
|
/// 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>,
|
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
|
/// Returns `true` if it was, `false` if it wasn’t, and an error in
|
||||||
/// strict mode if it was specified more than once.
|
/// strict mode if it was specified more than once.
|
||||||
pub fn has(&self, arg: &'static Arg) -> Result<bool, Misfire> {
|
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
|
/// 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);
|
let (before, after) = input.as_bytes().split_at(index);
|
||||||
|
|
||||||
// The after string contains the = that we need to remove.
|
// 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),
|
return Some((OsStr::from_bytes(before),
|
||||||
OsStr::from_bytes(&after[1..])))
|
OsStr::from_bytes(&after[1..])))
|
||||||
}
|
}
|
||||||
@ -569,7 +571,9 @@ mod parse_test {
|
|||||||
|
|
||||||
let strictness = Strictness::UseLastArguments; // this isn’t even used
|
let strictness = Strictness::UseLastArguments; // this isn’t even used
|
||||||
let got = Args(TEST_ARGS).parse(inputs.iter(), strictness);
|
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);
|
assert_eq!(got, expected);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -580,9 +584,11 @@ mod parse_test {
|
|||||||
use self::ParseError::*;
|
use self::ParseError::*;
|
||||||
|
|
||||||
let strictness = Strictness::UseLastArguments; // this isn’t even used
|
let strictness = Strictness::UseLastArguments; // this isn’t even used
|
||||||
let bits = $inputs.as_ref().into_iter().map(|&o| os(o)).collect::<Vec<OsString>>();
|
let bits = $inputs.as_ref().into_iter()
|
||||||
let got = Args(TEST_ARGS).parse(bits.iter(), strictness);
|
.map(|&o| os(o))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let got = Args(TEST_ARGS).parse(bits.iter(), strictness);
|
||||||
assert_eq!(got, Err($error));
|
assert_eq!(got, Err($error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -719,6 +725,6 @@ mod matches_test {
|
|||||||
fn no_count() {
|
fn no_count() {
|
||||||
let flags = MatchedFlags { flags: Vec::new(), strictness: Strictness::UseLastArguments };
|
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::fs::File;
|
||||||
use crate::options::{flags, Vars, Misfire};
|
use crate::options::{flags, Vars, Misfire};
|
||||||
use crate::options::parser::MatchedFlags;
|
use crate::options::parser::MatchedFlags;
|
||||||
use crate::output::file_name::{FileStyle, Classify};
|
use crate::output::file_name::{Classify, FileStyle};
|
||||||
use crate::style::Colours;
|
use crate::style::Colours;
|
||||||
|
|
||||||
|
|
||||||
@ -38,7 +38,6 @@ impl TerminalColours {
|
|||||||
|
|
||||||
/// Determine which terminal colour conditions to use.
|
/// Determine which terminal colour conditions to use.
|
||||||
fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
|
fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
|
||||||
|
|
||||||
let word = match matches.get_where(|f| f.matches(&flags::COLOR) || f.matches(&flags::COLOUR))? {
|
let word = match matches.get_where(|f| f.matches(&flags::COLOR) || f.matches(&flags::COLOUR))? {
|
||||||
Some(w) => w,
|
Some(w) => w,
|
||||||
None => return Ok(Self::default()),
|
None => return Ok(Self::default()),
|
||||||
@ -77,7 +76,7 @@ pub struct Styles {
|
|||||||
|
|
||||||
impl 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>
|
pub fn deduce<V, TW>(matches: &MatchedFlags, vars: &V, widther: TW) -> Result<Self, Misfire>
|
||||||
where TW: Fn() -> Option<usize>, V: Vars {
|
where TW: Fn() -> Option<usize>, V: Vars {
|
||||||
use crate::info::filetype::FileExtensions;
|
use crate::info::filetype::FileExtensions;
|
||||||
@ -89,12 +88,12 @@ impl Styles {
|
|||||||
// custom colours at all
|
// custom colours at all
|
||||||
let tc = TerminalColours::deduce(matches)?;
|
let tc = TerminalColours::deduce(matches)?;
|
||||||
|
|
||||||
if tc == TerminalColours::Never
|
if tc == TerminalColours::Never || (tc == TerminalColours::Automatic && widther().is_none()) {
|
||||||
|| (tc == TerminalColours::Automatic && widther().is_none())
|
let exts = Box::new(NoFileColours);
|
||||||
{
|
|
||||||
return Ok(Self {
|
return Ok(Self {
|
||||||
colours: Colours::plain(),
|
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) {
|
if let Some(lsc) = vars.get(vars::LS_COLORS) {
|
||||||
let lsc = lsc.to_string_lossy();
|
let lsc = lsc.to_string_lossy();
|
||||||
|
|
||||||
LSColors(lsc.as_ref()).each_pair(|pair| {
|
LSColors(lsc.as_ref()).each_pair(|pair| {
|
||||||
if !colours.set_ls(&pair) {
|
if ! colours.set_ls(&pair) {
|
||||||
match glob::Pattern::new(pair.key) {
|
match glob::Pattern::new(pair.key) {
|
||||||
Ok(pat) => exts.add(pat, pair.to_style()),
|
Ok(pat) => {
|
||||||
Err(e) => warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e),
|
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| {
|
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) {
|
match glob::Pattern::new(pair.key) {
|
||||||
Ok(pat) => exts.add(pat, pair.to_style()),
|
Ok(pat) => {
|
||||||
Err(e) => warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e),
|
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)]
|
#[derive(PartialEq, Debug, Default)]
|
||||||
struct ExtensionMappings {
|
struct ExtensionMappings {
|
||||||
mappings: Vec<(glob::Pattern, Style)>
|
mappings: Vec<(glob::Pattern, Style)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop through backwards so that colours specified later in the list override
|
// Loop through backwards so that colours specified later in the list override
|
||||||
@ -178,9 +186,7 @@ struct ExtensionMappings {
|
|||||||
use crate::output::file_name::FileColours;
|
use crate::output::file_name::FileColours;
|
||||||
impl FileColours for ExtensionMappings {
|
impl FileColours for ExtensionMappings {
|
||||||
fn colour_file(&self, file: &File) -> Option<Style> {
|
fn colour_file(&self, file: &File) -> Option<Style> {
|
||||||
self.mappings
|
self.mappings.iter().rev()
|
||||||
.iter()
|
|
||||||
.rev()
|
|
||||||
.find(|t| t.0.matches(&file.name))
|
.find(|t| t.0.matches(&file.name))
|
||||||
.map (|t| t.1)
|
.map (|t| t.1)
|
||||||
}
|
}
|
||||||
@ -188,7 +194,7 @@ impl FileColours for ExtensionMappings {
|
|||||||
|
|
||||||
impl ExtensionMappings {
|
impl ExtensionMappings {
|
||||||
fn is_non_empty(&self) -> bool {
|
fn is_non_empty(&self) -> bool {
|
||||||
!self.mappings.is_empty()
|
! self.mappings.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add(&mut self, pattern: glob::Pattern, style: Style) {
|
fn add(&mut self, pattern: glob::Pattern, style: Style) {
|
||||||
@ -197,18 +203,16 @@ impl ExtensionMappings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
impl Classify {
|
impl Classify {
|
||||||
fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
|
fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
|
||||||
let flagged = matches.has(&flags::CLASSIFY)?;
|
let flagged = matches.has(&flags::CLASSIFY)?;
|
||||||
|
|
||||||
Ok(if flagged { Self::AddFileIndicators }
|
if flagged { Ok(Self::AddFileIndicators) }
|
||||||
else { Self::JustFilenames })
|
else { Ok(Self::JustFilenames) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod terminal_test {
|
mod terminal_test {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -338,7 +342,6 @@ mod colour_test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod customs_test {
|
mod customs_test {
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
@ -410,11 +413,11 @@ mod customs_test {
|
|||||||
fn get(&self, name: &'static str) -> Option<OsString> {
|
fn get(&self, name: &'static str) -> Option<OsString> {
|
||||||
use crate::options::vars;
|
use crate::options::vars;
|
||||||
|
|
||||||
if name == vars::LS_COLORS && !self.ls.is_empty() {
|
if name == vars::LS_COLORS && ! self.ls.is_empty() {
|
||||||
OsString::from(self.ls.clone()).into()
|
Some(OsString::from(self.ls.clone()))
|
||||||
}
|
}
|
||||||
else if name == vars::EXA_COLORS && !self.exa.is_empty() {
|
else if name == vars::EXA_COLORS && ! self.exa.is_empty() {
|
||||||
OsString::from(self.exa.clone()).into()
|
Some(OsString::from(self.exa.clone()))
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
None
|
None
|
||||||
|
@ -15,6 +15,7 @@ pub static COLUMNS: &str = "COLUMNS";
|
|||||||
/// Environment variable used to datetime format.
|
/// Environment variable used to datetime format.
|
||||||
pub static TIME_STYLE: &str = "TIME_STYLE";
|
pub static TIME_STYLE: &str = "TIME_STYLE";
|
||||||
|
|
||||||
|
|
||||||
// exa-specific variables
|
// exa-specific variables
|
||||||
|
|
||||||
/// Environment variable used to colour exa’s interface when colours are
|
/// 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";
|
pub static EXA_GRID_ROWS: &str = "EXA_GRID_ROWS";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// Mockable wrapper for `std::env::var_os`.
|
/// Mockable wrapper for `std::env::var_os`.
|
||||||
pub trait Vars {
|
pub trait Vars {
|
||||||
fn get(&self, name: &'static str) -> Option<OsString>;
|
fn get(&self, name: &'static str) -> Option<OsString>;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
use crate::fs::feature::xattr;
|
||||||
use crate::options::{flags, Misfire, Vars};
|
use crate::options::{flags, Misfire, Vars};
|
||||||
use crate::options::parser::MatchedFlags;
|
use crate::options::parser::MatchedFlags;
|
||||||
use crate::output::{View, Mode, grid, details, lines};
|
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::table::{TimeTypes, Environment, SizeFormat, Columns, Options as TableOptions};
|
||||||
use crate::output::time::TimeFormat;
|
use crate::output::time::TimeFormat;
|
||||||
|
|
||||||
use crate::fs::feature::xattr;
|
|
||||||
|
|
||||||
|
|
||||||
impl View {
|
impl View {
|
||||||
|
|
||||||
@ -28,7 +27,7 @@ impl Mode {
|
|||||||
/// Determine the mode from the command-line arguments.
|
/// Determine the mode from the command-line arguments.
|
||||||
pub fn deduce<V: Vars>(matches: &MatchedFlags, vars: &V) -> Result<Self, Misfire> {
|
pub fn deduce<V: Vars>(matches: &MatchedFlags, vars: &V) -> Result<Self, Misfire> {
|
||||||
let long = || {
|
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))
|
Err(Misfire::Useless(&flags::ACROSS, true, &flags::LONG))
|
||||||
}
|
}
|
||||||
else if matches.has(&flags::ONE_LINE)? {
|
else if matches.has(&flags::ONE_LINE)? {
|
||||||
@ -104,7 +103,8 @@ impl Mode {
|
|||||||
let other_options_mode = other_options_scan()?;
|
let other_options_mode = other_options_scan()?;
|
||||||
if let Self::Grid(grid) = other_options_mode {
|
if let Self::Grid(grid) = other_options_mode {
|
||||||
let row_threshold = RowThreshold::deduce(vars)?;
|
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 {
|
else {
|
||||||
return Ok(other_options_mode);
|
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));
|
return Err(Misfire::Useless(&flags::GIT, false, &flags::LONG));
|
||||||
}
|
}
|
||||||
else if matches.has(&flags::LEVEL)? && !matches.has(&flags::RECURSE)? && !matches.has(&flags::TREE)? {
|
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.
|
// TODO: I’m not sure if the code even gets this far.
|
||||||
// There is an identical check in dir_action
|
// There is an identical check in dir_action
|
||||||
return Err(Misfire::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE));
|
return Err(Misfire::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE));
|
||||||
}
|
}
|
||||||
@ -220,7 +220,7 @@ impl TableOptions {
|
|||||||
impl Columns {
|
impl Columns {
|
||||||
fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
|
fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
|
||||||
let time_types = TimeTypes::deduce(matches)?;
|
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 blocks = matches.has(&flags::BLOCKS)?;
|
||||||
let group = matches.has(&flags::GROUP)?;
|
let group = matches.has(&flags::GROUP)?;
|
||||||
@ -228,9 +228,9 @@ impl Columns {
|
|||||||
let links = matches.has(&flags::LINKS)?;
|
let links = matches.has(&flags::LINKS)?;
|
||||||
let octal = matches.has(&flags::OCTAL)?;
|
let octal = matches.has(&flags::OCTAL)?;
|
||||||
|
|
||||||
let permissions = !matches.has(&flags::NO_PERMISSIONS)?;
|
let permissions = ! matches.has(&flags::NO_PERMISSIONS)?;
|
||||||
let filesize = !matches.has(&flags::NO_FILESIZE)?;
|
let filesize = ! matches.has(&flags::NO_FILESIZE)?;
|
||||||
let user = !matches.has(&flags::NO_USER)?;
|
let user = ! matches.has(&flags::NO_USER)?;
|
||||||
|
|
||||||
Ok(Self { time_types, git, octal, blocks, group, inode, links, permissions, filesize, user })
|
Ok(Self { time_types, git, octal, blocks, group, inode, links, permissions, filesize, user })
|
||||||
}
|
}
|
||||||
@ -266,11 +266,13 @@ impl TimeFormat {
|
|||||||
pub use crate::output::time::{DefaultFormat, ISOFormat};
|
pub use crate::output::time::{DefaultFormat, ISOFormat};
|
||||||
|
|
||||||
let word = match matches.get(&flags::TIME_STYLE)? {
|
let word = match matches.get(&flags::TIME_STYLE)? {
|
||||||
Some(w) => w.to_os_string(),
|
Some(w) => {
|
||||||
|
w.to_os_string()
|
||||||
|
}
|
||||||
None => {
|
None => {
|
||||||
use crate::options::vars;
|
use crate::options::vars;
|
||||||
match vars.get(vars::TIME_STYLE) {
|
match vars.get(vars::TIME_STYLE) {
|
||||||
Some(ref t) if !t.is_empty() => t.clone(),
|
Some(ref t) if ! t.is_empty() => t.clone(),
|
||||||
_ => return Ok(Self::DefaultFormat(DefaultFormat::load()))
|
_ => return Ok(Self::DefaultFormat(DefaultFormat::load()))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -373,7 +375,6 @@ lazy_static! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -600,7 +601,7 @@ mod test {
|
|||||||
test!(just_binary: Mode <- ["--binary"], None; Last => like Ok(Mode::Grid(_)));
|
test!(just_binary: Mode <- ["--binary"], None; Last => like Ok(Mode::Grid(_)));
|
||||||
test!(just_bytes: Mode <- ["--bytes"], 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_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));
|
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_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));
|
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));
|
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
|
// the contents only get changed in this module, the mutators in the struct
|
||||||
// above can just access the value directly.
|
// above can just access the value directly.
|
||||||
|
|
||||||
@ -188,7 +188,7 @@ impl TextCellContents {
|
|||||||
/// when calculating widths for displaying tables in a terminal.
|
/// when calculating widths for displaying tables in a terminal.
|
||||||
///
|
///
|
||||||
/// This type is used to ensure that the width, rather than the length, is
|
/// 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!
|
/// like `file_name.len()` and assume it will work!
|
||||||
///
|
///
|
||||||
/// It has `From` impls that convert an input string or fixed with to values
|
/// 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 {
|
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)
|
iter.fold(Self(0), Add::add)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
//! The **Details** output view displays each file as a row in a table.
|
//! 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
|
//! - 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;
|
//! 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 std::vec::IntoIter as VecIntoIter;
|
||||||
|
|
||||||
use ansi_term::{ANSIGenericString, Style};
|
use ansi_term::{ANSIGenericString, Style};
|
||||||
|
use scoped_threadpool::Pool;
|
||||||
|
|
||||||
use crate::fs::{Dir, File};
|
use crate::fs::{Dir, File};
|
||||||
use crate::fs::dir_action::RecurseOptions;
|
use crate::fs::dir_action::RecurseOptions;
|
||||||
use crate::fs::filter::FileFilter;
|
|
||||||
use crate::fs::feature::git::GitCache;
|
use crate::fs::feature::git::GitCache;
|
||||||
use crate::fs::feature::xattr::{Attribute, FileAttributes};
|
use crate::fs::feature::xattr::{Attribute, FileAttributes};
|
||||||
|
use crate::fs::filter::FileFilter;
|
||||||
use crate::style::Colours;
|
use crate::style::Colours;
|
||||||
use crate::output::cell::TextCell;
|
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::file_name::FileStyle;
|
||||||
use crate::output::table::{Table, Options as TableOptions, Row as TableRow};
|
use crate::output::table::{Table, Options as TableOptions, Row as TableRow};
|
||||||
use crate::output::icons::painted_icon;
|
use crate::output::tree::{TreeTrunk, TreeParams, TreeDepth};
|
||||||
|
|
||||||
use scoped_threadpool::Pool;
|
|
||||||
|
|
||||||
|
|
||||||
/// With the **Details** view, the output gets formatted into columns, with
|
/// 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.
|
/// Whether to show a header line or not.
|
||||||
pub header: bool,
|
pub header: bool,
|
||||||
|
|
||||||
/// Whether to show each file's extended attributes.
|
/// Whether to show each file’s extended attributes.
|
||||||
pub xattr: bool,
|
pub xattr: bool,
|
||||||
|
|
||||||
/// Enables --icons mode
|
/// Whether icons mode is enabled.
|
||||||
pub icons: bool,
|
pub icons: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub struct Render<'a> {
|
pub struct Render<'a> {
|
||||||
pub dir: Option<&'a Dir>,
|
pub dir: Option<&'a Dir>,
|
||||||
pub files: Vec<File<'a>>,
|
pub files: Vec<File<'a>>,
|
||||||
@ -157,8 +155,8 @@ impl<'a> Render<'a> {
|
|||||||
|
|
||||||
if let Some(ref table) = self.opts.table {
|
if let Some(ref table) = self.opts.table {
|
||||||
match (git, self.dir) {
|
match (git, self.dir) {
|
||||||
(Some(g), Some(d)) => if !g.has_anything_for(&d.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 },
|
(Some(g), None) => if ! self.files.iter().any(|f| g.has_anything_for(&f.path)) { git = None },
|
||||||
(None, _) => {/* Keep Git how it is */},
|
(None, _) => {/* Keep Git how it is */},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,16 +246,17 @@ 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();
|
xattrs.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut dir = None;
|
let mut dir = None;
|
||||||
|
|
||||||
if let Some(r) = self.recurse {
|
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() {
|
match file.to_dir() {
|
||||||
Ok(d) => { dir = Some(d); },
|
Ok(d) => { dir = Some(d); },
|
||||||
Err(e) => { errors.push((e, None)) },
|
Err(e) => { errors.push((e, None)) },
|
||||||
@ -265,9 +264,8 @@ impl<'a> Render<'a> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let icon = if self.opts.icons {
|
let icon = if self.opts.icons { Some(painted_icon(file, self.style)) }
|
||||||
Some(painted_icon(file, self.style))
|
else { None };
|
||||||
} else { None };
|
|
||||||
|
|
||||||
let egg = Egg { table_row, xattrs, errors, dir, file, icon };
|
let egg = Egg { table_row, xattrs, errors, dir, file, icon };
|
||||||
unsafe { std::ptr::write(file_eggs.lock().unwrap()[idx].as_mut_ptr(), egg) }
|
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 {
|
if let Some(icon) = egg.icon {
|
||||||
name_cell.push(ANSIGenericString::from(icon), 2)
|
name_cell.push(ANSIGenericString::from(icon), 2)
|
||||||
}
|
}
|
||||||
name_cell.append(self.style.for_file(egg.file, self.colours)
|
|
||||||
|
let style = self.style.for_file(egg.file, self.colours)
|
||||||
.with_link_paths()
|
.with_link_paths()
|
||||||
.paint()
|
.paint()
|
||||||
.promote());
|
.promote();
|
||||||
|
name_cell.append(style);
|
||||||
|
|
||||||
|
|
||||||
let row = Row {
|
let row = Row {
|
||||||
@ -308,14 +308,18 @@ impl<'a> Render<'a> {
|
|||||||
if let Some(ref dir) = egg.dir {
|
if let Some(ref dir) = egg.dir {
|
||||||
for file_to_add in dir.files(self.filter.dot_filter, git, self.git_ignoring) {
|
for file_to_add in dir.files(self.filter.dot_filter, git, self.git_ignoring) {
|
||||||
match file_to_add {
|
match file_to_add {
|
||||||
Ok(f) => files.push(f),
|
Ok(f) => {
|
||||||
Err((path, e)) => errors.push((e, Some(path)))
|
files.push(f);
|
||||||
|
}
|
||||||
|
Err((path, e)) => {
|
||||||
|
errors.push((e, Some(path)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.filter.filter_child_files(&mut files);
|
self.filter.filter_child_files(&mut files);
|
||||||
|
|
||||||
if !files.is_empty() {
|
if ! files.is_empty() {
|
||||||
for xattr in egg.xattrs {
|
for xattr in egg.xattrs {
|
||||||
rows.push(self.render_xattr(&xattr, TreeParams::new(depth.deeper(), false)));
|
rows.push(self.render_xattr(&xattr, TreeParams::new(depth.deeper(), false)));
|
||||||
}
|
}
|
||||||
@ -331,12 +335,16 @@ impl<'a> Render<'a> {
|
|||||||
|
|
||||||
let count = egg.xattrs.len();
|
let count = egg.xattrs.len();
|
||||||
for (index, xattr) in egg.xattrs.into_iter().enumerate() {
|
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();
|
let count = errors.len();
|
||||||
for (index, (error, path)) in errors.into_iter().enumerate() {
|
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.
|
/// 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
|
/// 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
|
/// `None` for a row displaying an attribute or error, neither of which
|
||||||
/// have cells.
|
/// have cells.
|
||||||
pub cells: Option<TableRow>,
|
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.
|
/// from the other cells, as it never requires padding.
|
||||||
pub name: TextCell,
|
pub name: TextCell,
|
||||||
|
|
||||||
@ -441,7 +449,7 @@ impl<'a> Iterator for TableIter<'a> {
|
|||||||
|
|
||||||
// If any tree characters have been printed, then add an extra
|
// If any tree characters have been printed, then add an extra
|
||||||
// space, which makes the output look much better.
|
// space, which makes the output look much better.
|
||||||
if !row.tree.is_at_root() {
|
if ! row.tree.is_at_root() {
|
||||||
cell.add_spaces(1);
|
cell.add_spaces(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -471,7 +479,7 @@ impl Iterator for Iter {
|
|||||||
|
|
||||||
// If any tree characters have been printed, then add an extra
|
// If any tree characters have been printed, then add an extra
|
||||||
// space, which makes the output look much better.
|
// space, which makes the output look much better.
|
||||||
if !row.tree.is_at_root() {
|
if ! row.tree.is_at_root() {
|
||||||
cell.add_spaces(1);
|
cell.add_spaces(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,8 +4,9 @@ use ansi_term::{ANSIString, Style};
|
|||||||
pub fn escape<'a>(string: String, bits: &mut Vec<ANSIString<'a>>, good: Style, bad: 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) {
|
if string.chars().all(|c| c >= 0x20 as char && c != 0x7f as char) {
|
||||||
bits.push(good.paint(string));
|
bits.push(good.paint(string));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
for c in string.chars() {
|
for c in string.chars() {
|
||||||
// The `escape_default` method on `char` is *almost* what we want here, but
|
// 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.
|
// it still escapes non-ASCII UTF-8 characters, which are still printable.
|
||||||
@ -16,10 +17,10 @@ pub fn escape<'a>(string: String, bits: &mut Vec<ANSIString<'a>>, good: Style, b
|
|||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
s.push(c);
|
s.push(c);
|
||||||
bits.push(good.paint(s));
|
bits.push(good.paint(s));
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
let s = c.escape_default().collect::<String>();
|
let s = c.escape_default().collect::<String>();
|
||||||
bits.push(bad.paint(s));
|
bits.push(bad.paint(s));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
|
use std::fmt::Debug;
|
||||||
|
use std::marker::Sync;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use ansi_term::{ANSIString, Style};
|
use ansi_term::{ANSIString, Style};
|
||||||
|
|
||||||
use crate::fs::{File, FileTarget};
|
use crate::fs::{File, FileTarget};
|
||||||
use crate::output::escape;
|
|
||||||
use crate::output::cell::TextCellContents;
|
use crate::output::cell::TextCellContents;
|
||||||
|
use crate::output::escape;
|
||||||
use crate::output::render::FiletypeColours;
|
use crate::output::render::FiletypeColours;
|
||||||
|
|
||||||
|
|
||||||
@ -25,7 +27,8 @@ impl FileStyle {
|
|||||||
/// with the remaining arguments.
|
/// 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> {
|
pub fn for_file<'a, 'dir, C: Colours>(&'a self, file: &'a File<'dir>, colours: &'a C) -> FileName<'a, 'dir, C> {
|
||||||
FileName {
|
FileName {
|
||||||
file, colours,
|
file,
|
||||||
|
colours,
|
||||||
link_style: LinkStyle::JustFilenames,
|
link_style: LinkStyle::JustFilenames,
|
||||||
classify: self.classify,
|
classify: self.classify,
|
||||||
exts: &*self.exts,
|
exts: &*self.exts,
|
||||||
@ -71,7 +74,6 @@ impl Default for Classify {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// A **file name** holds all the information necessary to display the name
|
/// A **file name** holds all the information necessary to display the name
|
||||||
/// of the given file. This is used in all of the views.
|
/// of the given file. This is used in all of the views.
|
||||||
pub struct FileName<'a, 'dir: 'a, C: Colours+'a> {
|
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,
|
// 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
|
// link targets, the filename has to have a different style to
|
||||||
// indicate this fact. But when showing targets, we can just
|
// indicate this fact. But when showing targets, we can just
|
||||||
// colour the path instead (see below), and leave the broken
|
// 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);
|
self.add_parent_bits(&mut bits, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !target.name.is_empty() {
|
if ! target.name.is_empty() {
|
||||||
let target = FileName {
|
let target = FileName {
|
||||||
file: target,
|
file: target,
|
||||||
colours: self.colours,
|
colours: self.colours,
|
||||||
@ -157,18 +159,24 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
|||||||
bits.push(bit);
|
bits.push(bit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
FileTarget::Broken(broken_path) => {
|
FileTarget::Broken(broken_path) => {
|
||||||
bits.push(Style::default().paint(" "));
|
bits.push(Style::default().paint(" "));
|
||||||
bits.push(self.colours.broken_symlink().paint("->"));
|
bits.push(self.colours.broken_symlink().paint("->"));
|
||||||
bits.push(Style::default().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(_) => {
|
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 {
|
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("/"));
|
bits.push(self.colours.symlink_path().paint("/"));
|
||||||
}
|
}
|
||||||
else if coconut >= 1 {
|
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("/"));
|
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> {
|
fn classify_char(&self) -> Option<&'static str> {
|
||||||
if self.file.is_executable_file() {
|
if self.file.is_executable_file() {
|
||||||
Some("*")
|
Some("*")
|
||||||
} else if self.file.is_directory() {
|
}
|
||||||
|
else if self.file.is_directory() {
|
||||||
Some("/")
|
Some("/")
|
||||||
} else if self.file.is_pipe() {
|
}
|
||||||
|
else if self.file.is_pipe() {
|
||||||
Some("|")
|
Some("|")
|
||||||
} else if self.file.is_link() {
|
}
|
||||||
|
else if self.file.is_link() {
|
||||||
Some("@")
|
Some("@")
|
||||||
} else if self.file.is_socket() {
|
}
|
||||||
|
else if self.file.is_socket() {
|
||||||
Some("=")
|
Some("=")
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -228,13 +246,20 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
|||||||
fn coloured_file_name<'unused>(&self) -> Vec<ANSIString<'unused>> {
|
fn coloured_file_name<'unused>(&self) -> Vec<ANSIString<'unused>> {
|
||||||
let file_style = self.style();
|
let file_style = self.style();
|
||||||
let mut bits = Vec::new();
|
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
|
bits
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Figures out which colour to paint the filename part of the output,
|
/// 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,
|
/// class on the filesystem or from its name. (Or the broken link colour,
|
||||||
/// if there’s nowhere else for that fact to be shown.)
|
/// if there’s nowhere else for that fact to be shown.)
|
||||||
pub fn style(&self) -> Style {
|
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_block_device() => self.colours.block_device(),
|
||||||
f if f.is_char_device() => self.colours.char_device(),
|
f if f.is_char_device() => self.colours.char_device(),
|
||||||
f if f.is_socket() => self.colours.socket(),
|
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,
|
_ => return None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -298,9 +323,7 @@ pub trait Colours: FiletypeColours {
|
|||||||
|
|
||||||
|
|
||||||
// needs Debug because FileStyle derives it
|
// needs Debug because FileStyle derives it
|
||||||
use std::fmt::Debug;
|
pub trait FileColours: Debug + Sync {
|
||||||
use std::marker::Sync;
|
|
||||||
pub trait FileColours: Debug+Sync {
|
|
||||||
fn colour_file(&self, file: &File) -> Option<Style>;
|
fn colour_file(&self, file: &File) -> Option<Style>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,7 +331,9 @@ pub trait FileColours: Debug+Sync {
|
|||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub struct NoFileColours;
|
pub struct NoFileColours;
|
||||||
impl FileColours for 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
|
// 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
|
// file type associations, while falling back to the default set if not set
|
||||||
// explicitly.
|
// explicitly.
|
||||||
impl<A, B> FileColours for (A, B)
|
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> {
|
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 term_grid as tg;
|
||||||
|
|
||||||
use crate::fs::File;
|
use crate::fs::File;
|
||||||
use crate::style::Colours;
|
use crate::output::cell::DisplayWidth;
|
||||||
use crate::output::file_name::FileStyle;
|
use crate::output::file_name::FileStyle;
|
||||||
use crate::output::icons::painted_icon;
|
use crate::output::icons::painted_icon;
|
||||||
use crate::output::cell::DisplayWidth;
|
use crate::style::Colours;
|
||||||
|
|
||||||
|
|
||||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||||
@ -41,16 +41,16 @@ impl<'a> Render<'a> {
|
|||||||
grid.reserve(self.files.len());
|
grid.reserve(self.files.len());
|
||||||
|
|
||||||
for file in &self.files {
|
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 filename = self.style.for_file(file, self.colours).paint();
|
||||||
let width = if self.opts.icons {
|
|
||||||
DisplayWidth::from(2) + filename.width()
|
let width = if self.opts.icons { DisplayWidth::from(2) + filename.width() }
|
||||||
} else {
|
else { filename.width() };
|
||||||
filename.width()
|
|
||||||
};
|
|
||||||
|
|
||||||
grid.add(tg::Cell {
|
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,
|
width: *width,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -66,9 +66,11 @@ impl<'a> Render<'a> {
|
|||||||
if self.opts.icons {
|
if self.opts.icons {
|
||||||
write!(w, "{}", painted_icon(file, self.style))?;
|
write!(w, "{}", painted_icon(file, self.style))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let name_cell = self.style.for_file(file, self.colours).paint();
|
let name_cell = self.style.for_file(file, self.colours).paint();
|
||||||
writeln!(w, "{}", name_cell.strings())?;
|
writeln!(w, "{}", name_cell.strings())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,15 +9,14 @@ use crate::fs::{Dir, File};
|
|||||||
use crate::fs::feature::git::GitCache;
|
use crate::fs::feature::git::GitCache;
|
||||||
use crate::fs::feature::xattr::FileAttributes;
|
use crate::fs::feature::xattr::FileAttributes;
|
||||||
use crate::fs::filter::FileFilter;
|
use crate::fs::filter::FileFilter;
|
||||||
|
|
||||||
use crate::style::Colours;
|
|
||||||
use crate::output::cell::TextCell;
|
use crate::output::cell::TextCell;
|
||||||
use crate::output::details::{Options as DetailsOptions, Row as DetailsRow, Render as DetailsRender};
|
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::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::table::{Table, Row as TableRow, Options as TableOptions};
|
||||||
use crate::output::tree::{TreeParams, TreeDepth};
|
use crate::output::tree::{TreeParams, TreeDepth};
|
||||||
use crate::output::icons::painted_icon;
|
use crate::style::Colours;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -84,7 +83,7 @@ pub struct Render<'a> {
|
|||||||
impl<'a> Render<'a> {
|
impl<'a> Render<'a> {
|
||||||
|
|
||||||
/// Create a temporary Details render that gets used for the columns of
|
/// 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
|
/// 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
|
/// the table in *this* file, not in details: we only want to insert every
|
||||||
@ -138,7 +137,7 @@ impl<'a> Render<'a> {
|
|||||||
|
|
||||||
let rows = self.files.iter()
|
let rows = self.files.iter()
|
||||||
.map(|file| first_table.row_for_file(file, file_has_xattrs(file)))
|
.map(|file| first_table.row_for_file(file, file_has_xattrs(file)))
|
||||||
.collect::<Vec<TableRow>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let file_names = self.files.iter()
|
let file_names = self.files.iter()
|
||||||
.map(|file| {
|
.map(|file| {
|
||||||
@ -148,11 +147,12 @@ impl<'a> Render<'a> {
|
|||||||
let file_cell = self.style.for_file(file, self.colours).paint().promote();
|
let file_cell = self.style.for_file(file, self.colours).paint().promote();
|
||||||
icon_cell.append(file_cell);
|
icon_cell.append(file_cell);
|
||||||
icon_cell
|
icon_cell
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
self.style.for_file(file, self.colours).paint().promote()
|
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);
|
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>) {
|
fn make_table(&'a self, options: &'a TableOptions, mut git: Option<&'a GitCache>, drender: &DetailsRender) -> (Table<'a>, Vec<DetailsRow>) {
|
||||||
match (git, self.dir) {
|
match (git, self.dir) {
|
||||||
(Some(g), Some(d)) => if !g.has_anything_for(&d.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 },
|
(Some(g), None) => if ! self.files.iter().any(|f| g.has_anything_for(&f.path)) { git = None },
|
||||||
(None, _) => {/* Keep Git how it is */},
|
(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 {
|
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();
|
let mut tables = Vec::new();
|
||||||
for _ in 0 .. column_count {
|
for _ in 0 .. column_count {
|
||||||
tables.push(self.make_table(options, git, drender));
|
tables.push(self.make_table(options, git, drender));
|
||||||
@ -234,17 +233,19 @@ impl<'a> Render<'a> {
|
|||||||
rows.push(details_row);
|
rows.push(details_row);
|
||||||
}
|
}
|
||||||
|
|
||||||
let columns: Vec<_> = tables.into_iter().map(|(table, details_rows)| {
|
let columns = tables
|
||||||
drender.iterate_with_table(table, details_rows).collect::<Vec<_>>()
|
.into_iter()
|
||||||
}).collect();
|
.map(|(table, details_rows)| {
|
||||||
|
drender.iterate_with_table(table, details_rows)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let direction = if self.grid.across { grid::Direction::LeftToRight }
|
let direction = if self.grid.across { grid::Direction::LeftToRight }
|
||||||
else { grid::Direction::TopToBottom };
|
else { grid::Direction::TopToBottom };
|
||||||
|
|
||||||
let mut grid = grid::Grid::new(grid::GridOptions {
|
let filling = grid::Filling::Spaces(4);
|
||||||
direction,
|
let mut grid = grid::Grid::new(grid::GridOptions { direction, filling });
|
||||||
filling: grid::Filling::Spaces(4),
|
|
||||||
});
|
|
||||||
|
|
||||||
if self.grid.across {
|
if self.grid.across {
|
||||||
for row in 0 .. height {
|
for row in 0 .. height {
|
||||||
@ -280,14 +281,18 @@ impl<'a> Render<'a> {
|
|||||||
|
|
||||||
fn divide_rounding_up(a: usize, b: usize) -> usize {
|
fn divide_rounding_up(a: usize, b: usize) -> usize {
|
||||||
let mut result = a / b;
|
let mut result = a / b;
|
||||||
if a % b != 0 { result += 1; }
|
|
||||||
|
if a % b != 0 {
|
||||||
|
result += 1;
|
||||||
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn file_has_xattrs(file: &File) -> bool {
|
fn file_has_xattrs(file: &File) -> bool {
|
||||||
match file.path.attributes() {
|
match file.path.attributes() {
|
||||||
Ok(attrs) => !attrs.is_empty(),
|
Ok(attrs) => ! attrs.is_empty(),
|
||||||
Err(_) => false,
|
Err(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,12 @@ use crate::fs::File;
|
|||||||
use crate::info::filetype::FileExtensions;
|
use crate::info::filetype::FileExtensions;
|
||||||
use crate::output::file_name::FileStyle;
|
use crate::output::file_name::FileStyle;
|
||||||
|
|
||||||
|
|
||||||
pub trait FileIcon {
|
pub trait FileIcon {
|
||||||
fn icon_file(&self, file: &File) -> Option<char>;
|
fn icon_file(&self, file: &File) -> Option<char>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub enum Icons {
|
pub enum Icons {
|
||||||
Audio,
|
Audio,
|
||||||
@ -25,6 +27,7 @@ impl Icons {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn painted_icon(file: &File, style: &FileStyle) -> String {
|
pub fn painted_icon(file: &File, style: &FileStyle) -> String {
|
||||||
let file_icon = icon(file).to_string();
|
let file_icon = icon(file).to_string();
|
||||||
let painted = style.exts
|
let painted = style.exts
|
||||||
@ -33,20 +36,32 @@ pub fn painted_icon(file: &File, style: &FileStyle) -> String {
|
|||||||
// Remove underline from icon
|
// Remove underline from icon
|
||||||
if c.is_underline {
|
if c.is_underline {
|
||||||
match c.foreground {
|
match c.foreground {
|
||||||
Some(color) => Style::from(color).paint(file_icon).to_string(),
|
Some(color) => {
|
||||||
None => Style::default().paint(file_icon).to_string(),
|
Style::from(color).paint(file_icon).to_string()
|
||||||
}
|
}
|
||||||
} else {
|
None => {
|
||||||
|
Style::default().paint(file_icon).to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
c.paint(file_icon).to_string()
|
c.paint(file_icon).to_string()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
format!("{} ", painted)
|
format!("{} ", painted)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn icon(file: &File) -> char {
|
fn icon(file: &File) -> char {
|
||||||
let extensions = Box::new(FileExtensions);
|
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() {
|
else if let Some(ext) = file.ext.as_ref() {
|
||||||
match ext.as_str() {
|
match ext.as_str() {
|
||||||
"ai" => '\u{e7b4}',
|
"ai" => '\u{e7b4}',
|
||||||
@ -189,7 +204,8 @@ fn icon(file: &File) -> char {
|
|||||||
"nix" => '\u{f313}',
|
"nix" => '\u{f313}',
|
||||||
_ => '\u{f016}'
|
_ => '\u{f016}'
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
'\u{f016}'
|
'\u{f016}'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,14 +3,15 @@ use std::io::{Write, Result as IOResult};
|
|||||||
use ansi_term::{ANSIStrings, ANSIGenericString};
|
use ansi_term::{ANSIStrings, ANSIGenericString};
|
||||||
|
|
||||||
use crate::fs::File;
|
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::cell::TextCell;
|
||||||
|
use crate::output::file_name::{FileName, FileStyle};
|
||||||
|
use crate::output::icons::painted_icon;
|
||||||
|
use crate::style::Colours;
|
||||||
|
|
||||||
|
|
||||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
pub icons: bool
|
pub icons: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The lines view literally just displays each file, line-by-line.
|
/// 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.push(ANSIGenericString::from(icon), 2);
|
||||||
cell.append(name_cell.promote());
|
cell.append(name_cell.promote());
|
||||||
writeln!(w, "{}", ANSIStrings(&cell))?;
|
writeln!(w, "{}", ANSIStrings(&cell))?;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
writeln!(w, "{}", ANSIStrings(&name_cell))?;
|
writeln!(w, "{}", ANSIStrings(&name_cell))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,8 @@ pub use self::escape::escape;
|
|||||||
|
|
||||||
pub mod details;
|
pub mod details;
|
||||||
pub mod file_name;
|
pub mod file_name;
|
||||||
pub mod grid_details;
|
|
||||||
pub mod grid;
|
pub mod grid;
|
||||||
|
pub mod grid_details;
|
||||||
pub mod icons;
|
pub mod icons;
|
||||||
pub mod lines;
|
pub mod lines;
|
||||||
pub mod render;
|
pub mod render;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use ansi_term::Style;
|
use ansi_term::Style;
|
||||||
|
|
||||||
use crate::output::cell::TextCell;
|
|
||||||
use crate::fs::fields as f;
|
use crate::fs::fields as f;
|
||||||
|
use crate::output::cell::TextCell;
|
||||||
|
|
||||||
|
|
||||||
impl f::Blocks {
|
impl f::Blocks {
|
||||||
|
@ -65,7 +65,7 @@ pub mod test {
|
|||||||
fn renamed(&self) -> Style { Fixed(94).normal() }
|
fn renamed(&self) -> Style { Fixed(94).normal() }
|
||||||
fn type_change(&self) -> Style { Fixed(95).normal() }
|
fn type_change(&self) -> Style { Fixed(95).normal() }
|
||||||
fn ignored(&self) -> Style { Fixed(96).normal() }
|
fn ignored(&self) -> Style { Fixed(96).normal() }
|
||||||
fn conflicted(&self) -> Style { Fixed(93).normal() }
|
fn conflicted(&self) -> Style { Fixed(97).normal() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,8 +18,10 @@ impl f::Group {
|
|||||||
|
|
||||||
let current_uid = users.get_current_uid();
|
let current_uid = users.get_current_uid();
|
||||||
if let Some(current_user) = users.get_user_by_uid(current_uid) {
|
if let Some(current_user) = users.get_user_by_uid(current_uid) {
|
||||||
|
|
||||||
if current_user.primary_group_id() == group.gid()
|
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();
|
style = colours.yours();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use ansi_term::Style;
|
use ansi_term::Style;
|
||||||
|
|
||||||
use crate::output::cell::TextCell;
|
|
||||||
use crate::fs::fields as f;
|
use crate::fs::fields as f;
|
||||||
|
use crate::output::cell::TextCell;
|
||||||
|
|
||||||
|
|
||||||
impl f::Inode {
|
impl f::Inode {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use ansi_term::Style;
|
use ansi_term::Style;
|
||||||
use locale::Numeric as NumericLocale;
|
use locale::Numeric as NumericLocale;
|
||||||
|
|
||||||
use crate::output::cell::TextCell;
|
|
||||||
use crate::fs::fields as f;
|
use crate::fs::fields as f;
|
||||||
|
use crate::output::cell::TextCell;
|
||||||
|
|
||||||
|
|
||||||
impl f::Links {
|
impl f::Links {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use ansi_term::Style;
|
use ansi_term::Style;
|
||||||
|
|
||||||
use crate::output::cell::TextCell;
|
|
||||||
use crate::fs::fields as f;
|
use crate::fs::fields as f;
|
||||||
|
use crate::output::cell::TextCell;
|
||||||
|
|
||||||
|
|
||||||
impl f::OctalPermissions {
|
impl f::OctalPermissions {
|
||||||
fn bits_to_octal(r: bool, w: bool, x: bool) -> u8 {
|
fn bits_to_octal(r: bool, w: bool, x: bool) -> u8 {
|
||||||
@ -9,7 +10,6 @@ impl f::OctalPermissions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&self, style: Style) -> TextCell {
|
pub fn render(&self, style: Style) -> TextCell {
|
||||||
|
|
||||||
let perm = &self.permissions;
|
let perm = &self.permissions;
|
||||||
let octal_sticky = Self::bits_to_octal(perm.setuid, perm.setgid, perm.sticky);
|
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_owner = Self::bits_to_octal(perm.user_read, perm.user_write, perm.user_execute);
|
||||||
@ -18,9 +18,9 @@ impl f::OctalPermissions {
|
|||||||
|
|
||||||
TextCell::paint(style, format!("{}{}{}{}", octal_sticky, octal_owner, octal_group, octal_other))
|
TextCell::paint(style, format!("{}{}{}{}", octal_sticky, octal_owner, octal_group, octal_other))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod test {
|
pub mod test {
|
||||||
use crate::output::cell::TextCell;
|
use crate::output::cell::TextCell;
|
||||||
|
@ -25,12 +25,12 @@ impl f::PermissionsPlus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
impl f::Permissions {
|
impl f::Permissions {
|
||||||
pub fn render<C: Colours>(&self, colours: &C, is_regular_file: bool) -> Vec<ANSIString<'static>> {
|
pub fn render<C: Colours>(&self, colours: &C, is_regular_file: bool) -> Vec<ANSIString<'static>> {
|
||||||
|
|
||||||
let bit = |bit, chr: &'static str, style: Style| {
|
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![
|
vec![
|
||||||
|
@ -21,20 +21,23 @@ impl f::Size {
|
|||||||
SizeFormat::DecimalBytes => NumberPrefix::decimal(size as f64),
|
SizeFormat::DecimalBytes => NumberPrefix::decimal(size as f64),
|
||||||
SizeFormat::BinaryBytes => NumberPrefix::binary(size as f64),
|
SizeFormat::BinaryBytes => NumberPrefix::binary(size as f64),
|
||||||
SizeFormat::JustBytes => {
|
SizeFormat::JustBytes => {
|
||||||
|
|
||||||
// Use the binary prefix to select a style.
|
// Use the binary prefix to select a style.
|
||||||
let prefix = match NumberPrefix::binary(size as f64) {
|
let prefix = match NumberPrefix::binary(size as f64) {
|
||||||
NumberPrefix::Standalone(_) => None,
|
NumberPrefix::Standalone(_) => None,
|
||||||
NumberPrefix::Prefixed(p, _) => Some(p),
|
NumberPrefix::Prefixed(p, _) => Some(p),
|
||||||
};
|
};
|
||||||
|
|
||||||
// But format the number directly using the locale.
|
// But format the number directly using the locale.
|
||||||
let string = numerics.format_int(size);
|
let string = numerics.format_int(size);
|
||||||
|
|
||||||
return TextCell::paint(colours.size(prefix), string);
|
return TextCell::paint(colours.size(prefix), string);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (prefix, n) = match result {
|
let (prefix, n) = match result {
|
||||||
NumberPrefix::Standalone(b) => return TextCell::paint(colours.size(None), b.to_string()),
|
NumberPrefix::Standalone(b) => return TextCell::paint(colours.size(None), b.to_string()),
|
||||||
NumberPrefix::Prefixed(p, n) => (p, n)
|
NumberPrefix::Prefixed(p, n) => (p, n),
|
||||||
};
|
};
|
||||||
|
|
||||||
let symbol = prefix.symbol();
|
let symbol = prefix.symbol();
|
||||||
@ -72,6 +75,7 @@ impl f::DeviceIDs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub trait Colours {
|
pub trait Colours {
|
||||||
fn size(&self, prefix: Option<Prefix>) -> Style;
|
fn size(&self, prefix: Option<Prefix>) -> Style;
|
||||||
fn unit(&self, prefix: Option<Prefix>) -> Style;
|
fn unit(&self, prefix: Option<Prefix>) -> Style;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use datetime::TimeZone;
|
use datetime::TimeZone;
|
||||||
use ansi_term::Style;
|
use ansi_term::Style;
|
||||||
|
|
||||||
@ -6,23 +8,20 @@ use crate::output::time::TimeFormat;
|
|||||||
|
|
||||||
|
|
||||||
pub trait Render {
|
pub trait Render {
|
||||||
fn render(self, style: Style,
|
fn render(self, style: Style, tz: &Option<TimeZone>, format: &TimeFormat) -> TextCell;
|
||||||
tz: &Option<TimeZone>,
|
|
||||||
format: &TimeFormat) -> TextCell;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for Option<std::time::SystemTime> {
|
impl Render for Option<SystemTime> {
|
||||||
fn render(self, style: Style,
|
fn render(self, style: Style, tz: &Option<TimeZone>, format: &TimeFormat) -> TextCell {
|
||||||
tz: &Option<TimeZone>,
|
|
||||||
format: &TimeFormat) -> TextCell {
|
|
||||||
|
|
||||||
let datestamp = if let Some(time) = self {
|
let datestamp = if let Some(time) = self {
|
||||||
if let Some(ref tz) = tz {
|
if let Some(ref tz) = tz {
|
||||||
format.format_zoned(time, tz)
|
format.format_zoned(time, tz)
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
format.format_local(time)
|
format.format_local(time)
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
String::from("-")
|
String::from("-")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ use crate::fs::fields as f;
|
|||||||
use crate::output::cell::TextCell;
|
use crate::output::cell::TextCell;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
impl f::User {
|
impl f::User {
|
||||||
pub fn render<C: Colours, U: Users>(self, colours: &C, users: &U) -> TextCell {
|
pub fn render<C: Colours, U: Users>(self, colours: &C, users: &U) -> TextCell {
|
||||||
let user_name = match users.get_user_by_uid(self.0) {
|
let user_name = match users.get_user_by_uid(self.0) {
|
||||||
|
@ -11,12 +11,12 @@ use log::*;
|
|||||||
|
|
||||||
use users::UsersCache;
|
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::cell::TextCell;
|
||||||
use crate::output::render::TimeRender;
|
use crate::output::render::TimeRender;
|
||||||
use crate::output::time::TimeFormat;
|
use crate::output::time::TimeFormat;
|
||||||
use crate::fs::{File, fields as f};
|
use crate::style::Colours;
|
||||||
use crate::fs::feature::git::GitCache;
|
|
||||||
|
|
||||||
|
|
||||||
/// Options for displaying a table.
|
/// Options for displaying a table.
|
||||||
@ -108,7 +108,7 @@ impl Columns {
|
|||||||
columns.push(Column::Timestamp(TimeType::Accessed));
|
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);
|
columns.push(Column::GitStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +136,8 @@ pub enum Column {
|
|||||||
/// right-aligned, and text is left-aligned.
|
/// right-aligned, and text is left-aligned.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub enum Alignment {
|
pub enum Alignment {
|
||||||
Left, Right,
|
Left,
|
||||||
|
Right,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Column {
|
impl Column {
|
||||||
@ -199,6 +200,7 @@ impl Default for SizeFormat {
|
|||||||
/// across most (all?) operating systems.
|
/// across most (all?) operating systems.
|
||||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||||
pub enum TimeType {
|
pub enum TimeType {
|
||||||
|
|
||||||
/// The file’s modified time (`st_mtime`).
|
/// The file’s modified time (`st_mtime`).
|
||||||
Modified,
|
Modified,
|
||||||
|
|
||||||
@ -229,7 +231,7 @@ impl TimeType {
|
|||||||
/// Fields for which of a file’s time fields should be displayed in the
|
/// Fields for which of a file’s time fields should be displayed in the
|
||||||
/// columns output.
|
/// 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).
|
/// the time columns entirely (yet).
|
||||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||||
pub struct TimeTypes {
|
pub struct TimeTypes {
|
||||||
@ -244,15 +246,18 @@ impl Default for TimeTypes {
|
|||||||
/// By default, display just the ‘modified’ time. This is the most
|
/// By default, display just the ‘modified’ time. This is the most
|
||||||
/// common option, which is why it has this shorthand.
|
/// common option, which is why it has this shorthand.
|
||||||
fn default() -> Self {
|
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
|
/// 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.
|
/// Any environment field should be able to be mocked up for test runs.
|
||||||
pub struct Environment {
|
pub struct Environment {
|
||||||
@ -260,8 +265,8 @@ pub struct Environment {
|
|||||||
/// Localisation rules for formatting numbers.
|
/// Localisation rules for formatting numbers.
|
||||||
numeric: locale::Numeric,
|
numeric: locale::Numeric,
|
||||||
|
|
||||||
/// The computer's current time zone. This gets used to determine how to
|
/// The computer’s current time zone. This gets used to determine how to
|
||||||
/// offset files' timestamps.
|
/// offset files’ timestamps.
|
||||||
tz: Option<TimeZone>,
|
tz: Option<TimeZone>,
|
||||||
|
|
||||||
/// Mapping cache of user IDs to usernames.
|
/// Mapping cache of user IDs to usernames.
|
||||||
@ -275,7 +280,9 @@ impl Environment {
|
|||||||
|
|
||||||
pub fn load_all() -> Self {
|
pub fn load_all() -> Self {
|
||||||
let tz = match determine_time_zone() {
|
let tz = match determine_time_zone() {
|
||||||
Ok(t) => Some(t),
|
Ok(t) => {
|
||||||
|
Some(t)
|
||||||
|
}
|
||||||
Err(ref e) => {
|
Err(ref e) => {
|
||||||
println!("Unable to determine time zone: {}", e);
|
println!("Unable to determine time zone: {}", e);
|
||||||
None
|
None
|
||||||
@ -294,7 +301,8 @@ impl Environment {
|
|||||||
fn determine_time_zone() -> TZResult<TimeZone> {
|
fn determine_time_zone() -> TZResult<TimeZone> {
|
||||||
if let Ok(file) = env::var("TZ") {
|
if let Ok(file) = env::var("TZ") {
|
||||||
TimeZone::from_file(format!("/usr/share/zoneinfo/{}", file))
|
TimeZone::from_file(format!("/usr/share/zoneinfo/{}", file))
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
TimeZone::from_file("/etc/localtime")
|
TimeZone::from_file("/etc/localtime")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -321,7 +329,10 @@ impl<'a, 'f> Table<'a> {
|
|||||||
let widths = TableWidths::zero(columns.len());
|
let widths = TableWidths::zero(columns.len());
|
||||||
|
|
||||||
Table {
|
Table {
|
||||||
colours, widths, columns, git,
|
colours,
|
||||||
|
widths,
|
||||||
|
columns,
|
||||||
|
git,
|
||||||
env: &options.env,
|
env: &options.env,
|
||||||
time_format: &options.time_format,
|
time_format: &options.time_format,
|
||||||
size_format: options.size_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 {
|
fn display(&self, file: &File, column: Column, xattrs: bool) -> TextCell {
|
||||||
match column {
|
match column {
|
||||||
Column::Permissions => self.permissions_plus(file, xattrs).render(self.colours),
|
Column::Permissions => {
|
||||||
Column::FileSize => file.size().render(self.colours, self.size_format, &self.env.numeric),
|
self.permissions_plus(file, xattrs).render(self.colours)
|
||||||
Column::HardLinks => file.links().render(self.colours, &self.env.numeric),
|
}
|
||||||
Column::Inode => file.inode().render(self.colours.inode),
|
Column::FileSize => {
|
||||||
Column::Blocks => file.blocks().render(self.colours),
|
file.size().render(self.colours, self.size_format, &self.env.numeric)
|
||||||
Column::User => file.user().render(self.colours, &*self.env.lock_users()),
|
}
|
||||||
Column::Group => file.group().render(self.colours, &*self.env.lock_users()),
|
Column::HardLinks => {
|
||||||
Column::GitStatus => self.git_status(file).render(self.colours),
|
file.links().render(self.colours, &self.env.numeric)
|
||||||
Column::Octal => self.octal_permissions(file).render(self.colours.octal),
|
}
|
||||||
|
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::Modified) => {
|
||||||
Column::Timestamp(TimeType::Changed) => file.changed_time() .render(self.colours.date, &self.env.tz, self.time_format),
|
file.modified_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::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 {
|
fn git_status(&self, file: &File) -> f::Git {
|
||||||
debug!("Getting Git status for file {:?}", file.path);
|
debug!("Getting Git status for file {:?}", file.path);
|
||||||
|
|
||||||
self.git
|
self.git
|
||||||
.map(|g| g.get(&file.path, file.is_directory()))
|
.map(|g| g.get(&file.path, file.is_directory()))
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
@ -395,12 +433,22 @@ impl<'a, 'f> Table<'a> {
|
|||||||
pub fn render(&self, row: Row) -> TextCell {
|
pub fn render(&self, row: Row) -> TextCell {
|
||||||
let mut cell = TextCell::default();
|
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;
|
let padding = width - *this_cell.width;
|
||||||
|
|
||||||
match self.columns[n].alignment() {
|
match self.columns[n].alignment() {
|
||||||
Alignment::Left => { cell.append(this_cell); cell.add_spaces(padding); }
|
Alignment::Left => {
|
||||||
Alignment::Right => { cell.add_spaces(padding); cell.append(this_cell); }
|
cell.append(this_cell);
|
||||||
|
cell.add_spaces(padding);
|
||||||
|
}
|
||||||
|
Alignment::Right => {
|
||||||
|
cell.add_spaces(padding);
|
||||||
|
cell.append(this_cell);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cell.add_spaces(1);
|
cell.add_spaces(1);
|
||||||
@ -411,7 +459,6 @@ impl<'a, 'f> Table<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub struct TableWidths(Vec<usize>);
|
pub struct TableWidths(Vec<usize>);
|
||||||
|
|
||||||
impl Deref for TableWidths {
|
impl Deref for TableWidths {
|
||||||
@ -424,7 +471,7 @@ impl Deref for TableWidths {
|
|||||||
|
|
||||||
impl TableWidths {
|
impl TableWidths {
|
||||||
pub fn zero(count: usize) -> Self {
|
pub fn zero(count: usize) -> Self {
|
||||||
Self(vec![ 0; count ])
|
Self(vec![0; count])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_widths(&mut self, row: &Row) {
|
pub fn add_widths(&mut self, row: &Row) {
|
||||||
|
@ -119,10 +119,7 @@ impl DefaultFormat {
|
|||||||
|
|
||||||
Self { current_year, locale, date_and_time, date_and_year }
|
Self { current_year, locale, date_and_time, date_and_year }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl DefaultFormat {
|
|
||||||
fn is_recent(&self, date: LocalDateTime) -> bool {
|
fn is_recent(&self, date: LocalDateTime) -> bool {
|
||||||
date.year() == self.current_year
|
date.year() == self.current_year
|
||||||
}
|
}
|
||||||
@ -217,7 +214,6 @@ fn long_zoned(time: SystemTime, zone: &TimeZone) -> String {
|
|||||||
date.hour(), date.minute())
|
date.hour(), date.minute())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[allow(trivial_numeric_casts)]
|
#[allow(trivial_numeric_casts)]
|
||||||
fn full_local(time: SystemTime) -> String {
|
fn full_local(time: SystemTime) -> String {
|
||||||
let date = LocalDateTime::at(systemtime_epoch(time));
|
let date = LocalDateTime::at(systemtime_epoch(time));
|
||||||
@ -240,7 +236,6 @@ fn full_zoned(time: SystemTime, zone: &TimeZone) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct ISOFormat {
|
pub struct ISOFormat {
|
||||||
|
|
||||||
|
@ -111,18 +111,21 @@ impl TreeTrunk {
|
|||||||
// If this isn’t our first iteration, then update the tree parts thus
|
// If this isn’t our first iteration, then update the tree parts thus
|
||||||
// far to account for there being another row after it.
|
// far to account for there being another row after it.
|
||||||
if let Some(last) = self.last_params {
|
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
|
// Make sure the stack has enough space, then add or modify another
|
||||||
// part into it.
|
// part into it.
|
||||||
self.stack.resize(params.depth.0 + 1, TreePart::Edge);
|
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);
|
self.last_params = Some(params);
|
||||||
|
|
||||||
// Return the tree parts as a slice of the stack.
|
// 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
|
// appearing before the very first directory. This level would
|
||||||
// join unrelated directories without connecting to anything:
|
// 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
|
/// Creates an iterator that, as well as yielding each value, yields a
|
||||||
/// `TreeParams` with the current depth and last flag filled in.
|
/// `TreeParams` with the current depth and last flag filled in.
|
||||||
pub fn iterate_over<I, T>(self, inner: I) -> Iter<I>
|
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 }
|
Iter { current_depth: self, inner }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -171,14 +175,16 @@ pub struct Iter<I> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<I, T> Iterator for 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);
|
type Item = (TreeParams, T);
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
self.inner.next().map(|t| {
|
let t = self.inner.next()?;
|
||||||
// use exact_size_is_empty API soon
|
|
||||||
(TreeParams::new(self.current_depth, self.inner.len() == 0), t)
|
// 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]
|
#[test]
|
||||||
fn empty_at_first() {
|
fn empty_at_first() {
|
||||||
let mut tt = TreeTrunk::default();
|
let mut tt = TreeTrunk::default();
|
||||||
assert_eq!(tt.new_row(params(0, true)), &[]);
|
assert_eq!(tt.new_row(params(0, true)), &[ ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn one_child() {
|
fn one_child() {
|
||||||
let mut tt = TreeTrunk::default();
|
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, true)), &[ TreePart::Corner ]);
|
assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn two_children() {
|
fn two_children() {
|
||||||
let mut tt = TreeTrunk::default();
|
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, false)), &[ TreePart::Edge ]);
|
||||||
assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]);
|
assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]);
|
||||||
}
|
}
|
||||||
@ -215,11 +221,11 @@ mod trunk_test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn two_times_two_children() {
|
fn two_times_two_children() {
|
||||||
let mut tt = TreeTrunk::default();
|
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, false)), &[ TreePart::Edge ]);
|
||||||
assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]);
|
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, false)), &[ TreePart::Edge ]);
|
||||||
assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]);
|
assert_eq!(tt.new_row(params(1, true)), &[ TreePart::Corner ]);
|
||||||
}
|
}
|
||||||
@ -227,7 +233,7 @@ mod trunk_test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn two_times_two_nested_children() {
|
fn two_times_two_nested_children() {
|
||||||
let mut tt = TreeTrunk::default();
|
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, false)), &[ TreePart::Edge ]);
|
||||||
assert_eq!(tt.new_row(params(2, false)), &[ TreePart::Line, TreePart::Edge ]);
|
assert_eq!(tt.new_row(params(2, false)), &[ TreePart::Line, TreePart::Edge ]);
|
||||||
@ -240,7 +246,6 @@ mod trunk_test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod iter_test {
|
mod iter_test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
use ansi_term::Style;
|
|
||||||
use ansi_term::Colour::{Red, Green, Yellow, Blue, Cyan, Purple, Fixed};
|
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::file_name::Colours as FileNameColours;
|
||||||
|
use crate::output::render;
|
||||||
use crate::style::lsc::Pair;
|
use crate::style::lsc::Pair;
|
||||||
|
|
||||||
|
|
||||||
@ -190,11 +189,8 @@ impl Colours {
|
|||||||
|
|
||||||
impl Size {
|
impl Size {
|
||||||
pub fn colourful(scale: bool) -> Self {
|
pub fn colourful(scale: bool) -> Self {
|
||||||
if scale {
|
if scale { Self::colourful_scale() }
|
||||||
Self::colourful_scale()
|
else { Self::colourful_plain() }
|
||||||
} else {
|
|
||||||
Self::colourful_plain()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn colourful_plain() -> Self {
|
fn colourful_plain() -> Self {
|
||||||
@ -233,7 +229,6 @@ impl Size {
|
|||||||
unit_giga: Green.normal(),
|
unit_giga: Green.normal(),
|
||||||
unit_huge: Green.normal(),
|
unit_huge: Green.normal(),
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,6 +347,7 @@ impl Colours {
|
|||||||
|
|
||||||
_ => return false,
|
_ => return false,
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use std::iter::Peekable;
|
||||||
use std::ops::FnMut;
|
use std::ops::FnMut;
|
||||||
|
|
||||||
use ansi_term::{Colour, Style};
|
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
|
// 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.
|
// of the LS_COLORS string without having to parse them.
|
||||||
|
|
||||||
|
|
||||||
pub struct LSColors<'var>(pub &'var str);
|
pub struct LSColors<'var>(pub &'var str);
|
||||||
|
|
||||||
impl<'var> LSColors<'var> {
|
impl<'var> LSColors<'var> {
|
||||||
@ -33,21 +33,17 @@ impl<'var> LSColors<'var> {
|
|||||||
.take(3)
|
.take(3)
|
||||||
.collect::<Vec<_>>();
|
.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] });
|
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>
|
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() {
|
match iter.peek() {
|
||||||
Some(&"5") => {
|
Some(&"5") => {
|
||||||
let _ = iter.next();
|
let _ = iter.next();
|
||||||
@ -57,11 +53,12 @@ where I: Iterator<Item=&'a str> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(&"2") => {
|
Some(&"2") => {
|
||||||
let _ = iter.next();
|
let _ = iter.next();
|
||||||
if let Some(hexes) = iter.next() {
|
if let Some(hexes) = iter.next() {
|
||||||
// Some terminals support R:G:B instead of R;G;B
|
// 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(':') {
|
/*if hexes.contains(':') {
|
||||||
let rgb = hexes.splitn(3, ':').collect::<Vec<_>>();
|
let rgb = hexes.splitn(3, ':').collect::<Vec<_>>();
|
||||||
if rgb.len() != 3 {
|
if rgb.len() != 3 {
|
||||||
@ -79,11 +76,19 @@ where I: Iterator<Item=&'a str> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct Pair<'var> {
|
||||||
|
pub key: &'var str,
|
||||||
|
pub value: &'var str,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'var> Pair<'var> {
|
impl<'var> Pair<'var> {
|
||||||
pub fn to_style(&self) -> Style {
|
pub fn to_style(&self) -> Style {
|
||||||
let mut style = Style::default();
|
let mut style = Style::default();
|
||||||
@ -185,7 +190,6 @@ mod ansi_test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
Loading…
Reference in New Issue
Block a user