Merge pull request #820 from skyline75489/chesterliu/dev/win-support

Initial support for Windows
This commit is contained in:
Mélanie Chauvel 2023-02-20 14:19:47 +01:00 committed by GitHub
commit f3ca1fe6f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 279 additions and 17 deletions

View File

@ -31,9 +31,11 @@ scoped_threadpool = "0.1"
term_grid = "0.2.0" term_grid = "0.2.0"
terminal_size = "0.1.16" terminal_size = "0.1.16"
unicode-width = "0.1" unicode-width = "0.1"
users = "0.11"
zoneinfo_compiled = "0.5.1" zoneinfo_compiled = "0.5.1"
[target.'cfg(unix)'.dependencies]
users = "0.11"
[dependencies.datetime] [dependencies.datetime]
version = "0.5.2" version = "0.5.2"
default-features = false default-features = false

View File

@ -111,6 +111,13 @@ impl<'dir, 'ig> Files<'dir, 'ig> {
continue; continue;
} }
// Also hide _prefix files on Windows because it's used by old applications
// as an alternative to dot-prefix files.
#[cfg(windows)]
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();
if git_status.unstaged == GitStatus::Ignored { if git_status.unstaged == GitStatus::Ignored {

View File

@ -296,6 +296,7 @@ impl Git {
/// Paths need to be absolute for them to be compared properly, otherwise /// Paths need to be absolute for them to be compared properly, otherwise
/// youd ask a repo about “./README.md” but it only knows about /// youd 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.
#[cfg(unix)]
fn reorient(path: &Path) -> PathBuf { fn reorient(path: &Path) -> PathBuf {
use std::env::current_dir; use std::env::current_dir;
@ -308,6 +309,14 @@ fn reorient(path: &Path) -> PathBuf {
path.canonicalize().unwrap_or(path) path.canonicalize().unwrap_or(path)
} }
#[cfg(windows)]
fn reorient(path: &Path) -> PathBuf {
let unc_path = path.canonicalize().unwrap();
// On Windows UNC path is returned. We need to strip the prefix for it to work.
let normal_path = unc_path.as_os_str().to_str().unwrap().trim_left_matches("\\\\?\\");
return PathBuf::from(normal_path);
}
/// The character to display if the file has been modified, but not staged. /// The character to display if the file has been modified, but not staged.
fn working_tree_status(status: git2::Status) -> f::GitStatus { fn working_tree_status(status: git2::Status) -> f::GitStatus {
match status { match status {

View File

@ -82,13 +82,27 @@ pub struct Permissions {
pub setuid: bool, pub setuid: bool,
} }
/// The file's FileAttributes field, available only on Windows.
#[derive(Copy, Clone)]
pub struct Attributes {
pub archive: bool,
pub directory: bool,
pub readonly: bool,
pub hidden: bool,
pub system: bool,
pub reparse_point: bool,
}
/// The three pieces of information that are displayed as a single column in /// The three pieces of information that are displayed as a single column in
/// the details view. These values are fused together to make the output a /// the details view. These values are fused together to make the output a
/// little more compressed. /// little more compressed.
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct PermissionsPlus { pub struct PermissionsPlus {
pub file_type: Type, pub file_type: Type,
#[cfg(unix)]
pub permissions: Permissions, pub permissions: Permissions,
#[cfg(windows)]
pub attributes: Attributes,
pub xattrs: bool, pub xattrs: bool,
} }

View File

@ -1,7 +1,10 @@
//! Files, and methods and fields to access their metadata. //! Files, and methods and fields to access their metadata.
use std::io; use std::io;
#[cfg(unix)]
use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt}; use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};
#[cfg(windows)]
use std::os::windows::fs::MetadataExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::time::{Duration, SystemTime, UNIX_EPOCH};
@ -174,6 +177,7 @@ impl<'dir> File<'dir> {
/// Whether this file is both a regular file *and* executable for the /// Whether this file is both a regular file *and* executable for the
/// current user. An executable file has a different purpose from an /// current user. An executable file has a different purpose from an
/// executable directory, so they should be highlighted differently. /// executable directory, so they should be highlighted differently.
#[cfg(unix)]
pub fn is_executable_file(&self) -> bool { pub fn is_executable_file(&self) -> bool {
let bit = modes::USER_EXECUTE; let bit = modes::USER_EXECUTE;
self.is_file() && (self.metadata.permissions().mode() & bit) == bit self.is_file() && (self.metadata.permissions().mode() & bit) == bit
@ -185,21 +189,25 @@ impl<'dir> File<'dir> {
} }
/// Whether this file is a named pipe on the filesystem. /// Whether this file is a named pipe on the filesystem.
#[cfg(unix)]
pub fn is_pipe(&self) -> bool { pub fn is_pipe(&self) -> bool {
self.metadata.file_type().is_fifo() self.metadata.file_type().is_fifo()
} }
/// Whether this file is a char device on the filesystem. /// Whether this file is a char device on the filesystem.
#[cfg(unix)]
pub fn is_char_device(&self) -> bool { pub fn is_char_device(&self) -> bool {
self.metadata.file_type().is_char_device() self.metadata.file_type().is_char_device()
} }
/// Whether this file is a block device on the filesystem. /// Whether this file is a block device on the filesystem.
#[cfg(unix)]
pub fn is_block_device(&self) -> bool { pub fn is_block_device(&self) -> bool {
self.metadata.file_type().is_block_device() self.metadata.file_type().is_block_device()
} }
/// Whether this file is a socket on the filesystem. /// Whether this file is a socket on the filesystem.
#[cfg(unix)]
pub fn is_socket(&self) -> bool { pub fn is_socket(&self) -> bool {
self.metadata.file_type().is_socket() self.metadata.file_type().is_socket()
} }
@ -270,6 +278,7 @@ impl<'dir> File<'dir> {
/// is uncommon, while you come across directories and other types /// is uncommon, while you come across directories and other types
/// with multiple links much more often. Thus, it should get highlighted /// with multiple links much more often. Thus, it should get highlighted
/// more attentively. /// more attentively.
#[cfg(unix)]
pub fn links(&self) -> f::Links { pub fn links(&self) -> f::Links {
let count = self.metadata.nlink(); let count = self.metadata.nlink();
@ -280,6 +289,7 @@ impl<'dir> File<'dir> {
} }
/// This files inode. /// This files inode.
#[cfg(unix)]
pub fn inode(&self) -> f::Inode { pub fn inode(&self) -> f::Inode {
f::Inode(self.metadata.ino()) f::Inode(self.metadata.ino())
} }
@ -287,6 +297,7 @@ impl<'dir> File<'dir> {
/// This files number of filesystem blocks. /// This files number of filesystem blocks.
/// ///
/// (Not the size of each block, which we dont actually report on) /// (Not the size of each block, which we dont actually report on)
#[cfg(unix)]
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())
@ -297,11 +308,13 @@ impl<'dir> File<'dir> {
} }
/// The ID of the user that own this file. /// The ID of the user that own this file.
#[cfg(unix)]
pub fn user(&self) -> f::User { pub fn user(&self) -> f::User {
f::User(self.metadata.uid()) f::User(self.metadata.uid())
} }
/// The ID of the group that owns this file. /// The ID of the group that owns this file.
#[cfg(unix)]
pub fn group(&self) -> f::Group { pub fn group(&self) -> f::Group {
f::Group(self.metadata.gid()) f::Group(self.metadata.gid())
} }
@ -314,6 +327,7 @@ impl<'dir> File<'dir> {
/// ///
/// Block and character devices return their device IDs, because they /// Block and character devices return their device IDs, because they
/// usually just have a file size of zero. /// usually just have a file size of zero.
#[cfg(unix)]
pub fn size(&self) -> f::Size { pub fn size(&self) -> f::Size {
if self.is_directory() { if self.is_directory() {
f::Size::None f::Size::None
@ -335,12 +349,23 @@ impl<'dir> File<'dir> {
} }
} }
#[cfg(windows)]
pub fn size(&self) -> f::Size {
if self.is_directory() {
f::Size::None
}
else {
f::Size::Some(self.metadata.len())
}
}
/// This files last modified timestamp, if available on this platform. /// This files last modified timestamp, if available on this platform.
pub fn modified_time(&self) -> Option<SystemTime> { pub fn modified_time(&self) -> Option<SystemTime> {
self.metadata.modified().ok() self.metadata.modified().ok()
} }
/// This files last changed timestamp, if available on this platform. /// This files last changed timestamp, if available on this platform.
#[cfg(unix)]
pub fn changed_time(&self) -> Option<SystemTime> { pub fn changed_time(&self) -> Option<SystemTime> {
let (mut sec, mut nanosec) = (self.metadata.ctime(), self.metadata.ctime_nsec()); let (mut sec, mut nanosec) = (self.metadata.ctime(), self.metadata.ctime_nsec());
@ -359,6 +384,11 @@ impl<'dir> File<'dir> {
} }
} }
#[cfg(windows)]
pub fn changed_time(&self) -> Option<SystemTime> {
return self.modified_time()
}
/// This files last accessed timestamp, if available on this platform. /// This files last accessed timestamp, if available on this platform.
pub fn accessed_time(&self) -> Option<SystemTime> { pub fn accessed_time(&self) -> Option<SystemTime> {
self.metadata.accessed().ok() self.metadata.accessed().ok()
@ -374,6 +404,7 @@ impl<'dir> File<'dir> {
/// This is used a the leftmost character of the permissions column. /// This is used a the leftmost character of the permissions column.
/// The file type can usually be guessed from the colour of the file, but /// The file type can usually be guessed from the colour of the file, but
/// ls puts this character there. /// ls puts this character there.
#[cfg(unix)]
pub fn type_char(&self) -> f::Type { pub fn type_char(&self) -> f::Type {
if self.is_file() { if self.is_file() {
f::Type::File f::Type::File
@ -401,7 +432,21 @@ impl<'dir> File<'dir> {
} }
} }
#[cfg(windows)]
pub fn type_char(&self) -> f::Type {
if self.is_file() {
f::Type::File
}
else if self.is_directory() {
f::Type::Directory
}
else {
f::Type::Special
}
}
/// This files permissions, with flags for each bit. /// This files permissions, with flags for each bit.
#[cfg(unix)]
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;
@ -425,6 +470,22 @@ impl<'dir> File<'dir> {
} }
} }
#[cfg(windows)]
pub fn attributes(&self) -> f::Attributes {
let bits = self.metadata.file_attributes();
let has_bit = |bit| bits & bit == bit;
// https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
f::Attributes {
directory: has_bit(0x10),
archive: has_bit(0x20),
readonly: has_bit(0x1),
hidden: has_bit(0x2),
system: has_bit(0x4),
reparse_point: has_bit(0x400),
}
}
/// Whether this files extension is any of the strings that get passed in. /// Whether this files extension is any of the strings that get passed in.
/// ///
/// This will always return `false` if the file has no extension. /// This will always return `false` if the file has no extension.
@ -482,6 +543,7 @@ 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)]
#[cfg(unix)]
mod modes { mod modes {
// The `libc::mode_t` types actual type varies, but the value returned // The `libc::mode_t` types actual type varies, but the value returned
@ -559,6 +621,7 @@ mod filename_test {
} }
#[test] #[test]
#[cfg(unix)]
fn topmost() { fn topmost() {
assert_eq!("/", File::filename(Path::new("/"))) assert_eq!("/", File::filename(Path::new("/")))
} }

View File

@ -2,6 +2,7 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use std::iter::FromIterator; use std::iter::FromIterator;
#[cfg(unix)]
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use crate::fs::DotFilter; use crate::fs::DotFilter;
@ -130,6 +131,7 @@ pub enum SortField {
/// The files inode, which usually corresponds to the order in which /// The files inode, which usually corresponds to the order in which
/// files were created on the filesystem, more or less. /// files were created on the filesystem, more or less.
#[cfg(unix)]
FileInode, FileInode,
/// The time the file was modified (the “mtime”). /// The time the file was modified (the “mtime”).
@ -223,6 +225,7 @@ impl SortField {
Self::Name(AaBbCc) => natord::compare_ignore_case(&a.name, &b.name), Self::Name(AaBbCc) => natord::compare_ignore_case(&a.name, &b.name),
Self::Size => a.metadata.len().cmp(&b.metadata.len()), Self::Size => a.metadata.len().cmp(&b.metadata.len()),
#[cfg(unix)]
Self::FileInode => a.metadata.ino().cmp(&b.metadata.ino()), Self::FileInode => a.metadata.ino().cmp(&b.metadata.ino()),
Self::ModifiedDate => a.modified_time().cmp(&b.modified_time()), Self::ModifiedDate => a.modified_time().cmp(&b.modified_time()),
Self::AccessedDate => a.accessed_time().cmp(&b.accessed_time()), Self::AccessedDate => a.accessed_time().cmp(&b.accessed_time()),

View File

@ -49,12 +49,18 @@ mod theme;
fn main() { fn main() {
use std::process::exit; use std::process::exit;
#[cfg(unix)]
unsafe { unsafe {
libc::signal(libc::SIGPIPE, libc::SIG_DFL); libc::signal(libc::SIGPIPE, libc::SIG_DFL);
} }
logger::configure(env::var_os(vars::EXA_DEBUG)); logger::configure(env::var_os(vars::EXA_DEBUG));
#[cfg(windows)]
if let Err(e) = ansi_term::enable_ansi_support() {
warn!("Failed to enable ANSI support: {}", e);
}
let args: Vec<_> = env::args_os().skip(1).collect(); let args: Vec<_> = env::args_os().skip(1).collect();
match Options::parse(args.iter().map(|e| e.as_ref()), &LiveVars) { match Options::parse(args.iter().map(|e| e.as_ref()), &LiveVars) {
OptionsResult::Ok(options, mut input_paths) => { OptionsResult::Ok(options, mut input_paths) => {

View File

@ -88,6 +88,7 @@ impl SortField {
"cr" | "created" => { "cr" | "created" => {
Self::CreatedDate Self::CreatedDate
} }
#[cfg(unix)]
"inode" => { "inode" => {
Self::FileInode Self::FileInode
} }

View File

@ -146,8 +146,6 @@ impl Args {
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 OsStr> where I: IntoIterator<Item = &'args OsStr>
{ {
use std::os::unix::ffi::OsStrExt;
let mut parsing = true; let mut parsing = true;
// The results that get built up. // The results that get built up.
@ -159,7 +157,7 @@ impl Args {
// doesnt have one in its string so it needs the next one. // doesnt have one in its string so it needs the next one.
let mut inputs = inputs.into_iter(); let mut inputs = inputs.into_iter();
while let Some(arg) = inputs.next() { while let Some(arg) = inputs.next() {
let bytes = arg.as_bytes(); let bytes = os_str_to_bytes(arg);
// Stop parsing if one of the arguments is the literal string “--”. // Stop parsing if one of the arguments is the literal string “--”.
// This allows a file named “--arg” to be specified by passing in // This allows a file named “--arg” to be specified by passing in
@ -174,7 +172,7 @@ impl Args {
// If the string starts with *two* dashes then its a long argument. // If the string starts with *two* dashes then its a long argument.
else if bytes.starts_with(b"--") { else if bytes.starts_with(b"--") {
let long_arg_name = OsStr::from_bytes(&bytes[2..]); let long_arg_name = bytes_to_os_str(&bytes[2..]);
// If theres an equals in it, then the string before the // If theres an equals in it, then the string before the
// equals will be the flags name, and the string after it // equals will be the flags name, and the string after it
@ -221,7 +219,7 @@ impl Args {
// If the string starts with *one* dash then its one or more // If the string starts with *one* dash then its one or more
// short arguments. // short arguments.
else if bytes.starts_with(b"-") && arg != "-" { else if bytes.starts_with(b"-") && arg != "-" {
let short_arg = OsStr::from_bytes(&bytes[1..]); let short_arg = bytes_to_os_str(&bytes[1..]);
// If theres an equals in it, then the argument immediately // If theres an equals in it, then the argument immediately
// before the equals was the one that has the value, with the // before the equals was the one that has the value, with the
@ -236,7 +234,7 @@ impl Args {
// its an error if any of the first set of arguments actually // its an error if any of the first set of arguments actually
// takes a value. // takes a value.
if let Some((before, after)) = split_on_equals(short_arg) { if let Some((before, after)) = split_on_equals(short_arg) {
let (arg_with_value, other_args) = before.as_bytes().split_last().unwrap(); let (arg_with_value, other_args) = os_str_to_bytes(before).split_last().unwrap();
// Process the characters immediately following the dash... // Process the characters immediately following the dash...
for byte in other_args { for byte in other_args {
@ -291,7 +289,7 @@ impl Args {
TakesValue::Optional(values) => { TakesValue::Optional(values) => {
if index < bytes.len() - 1 { if index < bytes.len() - 1 {
let remnants = &bytes[index+1 ..]; let remnants = &bytes[index+1 ..];
result_flags.push((flag, Some(OsStr::from_bytes(remnants)))); result_flags.push((flag, Some(bytes_to_os_str(remnants))));
break; break;
} }
else if let Some(next_arg) = inputs.next() { else if let Some(next_arg) = inputs.next() {
@ -495,19 +493,42 @@ impl fmt::Display for ParseError {
} }
} }
#[cfg(unix)]
fn os_str_to_bytes<'b>(s: &'b OsStr) -> &'b [u8]{
use std::os::unix::ffi::OsStrExt;
return s.as_bytes()
}
#[cfg(unix)]
fn bytes_to_os_str<'b>(b: &'b [u8]) -> &'b OsStr{
use std::os::unix::ffi::OsStrExt;
return OsStr::from_bytes(b);
}
#[cfg(windows)]
fn os_str_to_bytes<'b>(s: &'b OsStr) -> &'b [u8]{
return s.to_str().unwrap().as_bytes()
}
#[cfg(windows)]
fn bytes_to_os_str<'b>(b: &'b [u8]) -> &'b OsStr{
use std::str;
return OsStr::new(str::from_utf8(b).unwrap());
}
/// Splits a string on its `=` character, returning the two substrings on /// Splits a string on its `=` character, returning the two substrings on
/// either side. Returns `None` if theres no equals or a string is missing. /// either side. Returns `None` if theres no equals or a string is missing.
fn split_on_equals(input: &OsStr) -> Option<(&OsStr, &OsStr)> { fn split_on_equals(input: &OsStr) -> Option<(&OsStr, &OsStr)> {
use std::os::unix::ffi::OsStrExt; if let Some(index) = os_str_to_bytes(input).iter().position(|elem| *elem == b'=') {
let (before, after) = os_str_to_bytes(input).split_at(index);
if let Some(index) = input.as_bytes().iter().position(|elem| *elem == b'=') {
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((bytes_to_os_str(before),
OsStr::from_bytes(&after[1..]))) bytes_to_os_str(&after[1..])))
} }
} }

View File

@ -226,7 +226,7 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
let coconut = parent.components().count(); let coconut = parent.components().count();
if coconut == 1 && parent.has_root() { if coconut == 1 && parent.has_root() {
bits.push(self.colours.symlink_path().paint("/")); bits.push(self.colours.symlink_path().paint(std::path::MAIN_SEPARATOR.to_string()));
} }
else if coconut >= 1 { else if coconut >= 1 {
escape( escape(
@ -235,12 +235,13 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
self.colours.symlink_path(), self.colours.symlink_path(),
self.colours.control_char(), self.colours.control_char(),
); );
bits.push(self.colours.symlink_path().paint("/")); bits.push(self.colours.symlink_path().paint(std::path::MAIN_SEPARATOR.to_string()));
} }
} }
/// The character to be displayed after a file when classifying is on, if /// The character to be displayed after a file when classifying is on, if
/// the files type has one associated with it. /// the files type has one associated with it.
#[cfg(unix)]
fn classify_char(&self, file: &File<'_>) -> Option<&'static str> { fn classify_char(&self, file: &File<'_>) -> Option<&'static str> {
if file.is_executable_file() { if file.is_executable_file() {
Some("*") Some("*")
@ -262,6 +263,19 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
} }
} }
#[cfg(windows)]
fn classify_char(&self, file: &File<'_>) -> Option<&'static str> {
if file.is_directory() {
Some("/")
}
else if file.is_link() {
Some("@")
}
else {
None
}
}
/// Returns at least one ANSI-highlighted string representing this files /// Returns at least one ANSI-highlighted string representing this files
/// name using the given set of colours. /// name using the given set of colours.
/// ///
@ -301,11 +315,16 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
match self.file { match self.file {
f if f.is_directory() => self.colours.directory(), f if f.is_directory() => self.colours.directory(),
#[cfg(unix)]
f if f.is_executable_file() => self.colours.executable_file(), f if f.is_executable_file() => self.colours.executable_file(),
f if f.is_link() => self.colours.symlink(), f if f.is_link() => self.colours.symlink(),
#[cfg(unix)]
f if f.is_pipe() => self.colours.pipe(), f if f.is_pipe() => self.colours.pipe(),
#[cfg(unix)]
f if f.is_block_device() => self.colours.block_device(), f if f.is_block_device() => self.colours.block_device(),
#[cfg(unix)]
f if f.is_char_device() => self.colours.char_device(), f if f.is_char_device() => self.colours.char_device(),
#[cfg(unix)]
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(),
_ => self.colours.colour_file(self.file), _ => self.colours.colour_file(self.file),

View File

@ -7,7 +7,9 @@ pub use self::filetype::Colours as FiletypeColours;
mod git; mod git;
pub use self::git::Colours as GitColours; pub use self::git::Colours as GitColours;
#[cfg(unix)]
mod groups; mod groups;
#[cfg(unix)]
pub use self::groups::Colours as GroupColours; pub use self::groups::Colours as GroupColours;
mod inode; mod inode;
@ -26,7 +28,9 @@ mod times;
pub use self::times::Render as TimeRender; pub use self::times::Render as TimeRender;
// times does too // times does too
#[cfg(unix)]
mod users; mod users;
#[cfg(unix)]
pub use self::users::Colours as UserColours; pub use self::users::Colours as UserColours;
mod octal; mod octal;

View File

@ -6,6 +6,7 @@ use crate::output::render::FiletypeColours;
impl f::PermissionsPlus { impl f::PermissionsPlus {
#[cfg(unix)]
pub fn render<C: Colours+FiletypeColours>(&self, colours: &C) -> TextCell { pub fn render<C: Colours+FiletypeColours>(&self, colours: &C) -> TextCell {
let mut chars = vec![ self.file_type.render(colours) ]; let mut chars = vec![ self.file_type.render(colours) ];
chars.extend(self.permissions.render(colours, self.file_type.is_regular_file())); chars.extend(self.permissions.render(colours, self.file_type.is_regular_file()));
@ -22,6 +23,17 @@ impl f::PermissionsPlus {
contents: chars.into(), contents: chars.into(),
} }
} }
#[cfg(windows)]
pub fn render<C: Colours+FiletypeColours>(&self, colours: &C) -> TextCell {
let mut chars = vec![ self.attributes.render_type(colours) ];
chars.extend(self.attributes.render(colours));
TextCell {
width: DisplayWidth::from(chars.len()),
contents: chars.into(),
}
}
} }
@ -76,6 +88,33 @@ impl f::Permissions {
} }
} }
impl f::Attributes {
pub fn render<C: Colours+FiletypeColours>(&self, colours: &C) -> Vec<ANSIString<'static>> {
let bit = |bit, chr: &'static str, style: Style| {
if bit { style.paint(chr) }
else { colours.dash().paint("-") }
};
vec![
bit(self.archive, "a", colours.normal()),
bit(self.readonly, "r", colours.user_read()),
bit(self.hidden, "h", colours.special_user_file()),
bit(self.system, "s", colours.special_other()),
]
}
pub fn render_type<C: Colours+FiletypeColours>(&self, colours: &C) -> ANSIString<'static> {
if self.reparse_point {
return colours.pipe().paint("l")
}
else if self.directory {
return colours.directory().paint("d")
}
else {
return colours.dash().paint("-")
}
}
}
pub trait Colours { pub trait Colours {
fn dash(&self) -> Style; fn dash(&self) -> Style;

View File

@ -1,6 +1,7 @@
use std::cmp::max; use std::cmp::max;
use std::env; use std::env;
use std::ops::Deref; use std::ops::Deref;
#[cfg(unix)]
use std::sync::{Mutex, MutexGuard}; use std::sync::{Mutex, MutexGuard};
use datetime::TimeZone; use datetime::TimeZone;
@ -8,6 +9,7 @@ use zoneinfo_compiled::{CompiledData, Result as TZResult};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use log::*; use log::*;
#[cfg(unix)]
use users::UsersCache; use users::UsersCache;
use crate::fs::{File, fields as f}; use crate::fs::{File, fields as f};
@ -54,10 +56,12 @@ impl Columns {
let mut columns = Vec::with_capacity(4); let mut columns = Vec::with_capacity(4);
if self.inode { if self.inode {
#[cfg(unix)]
columns.push(Column::Inode); columns.push(Column::Inode);
} }
if self.octal { if self.octal {
#[cfg(unix)]
columns.push(Column::Octal); columns.push(Column::Octal);
} }
@ -66,6 +70,7 @@ impl Columns {
} }
if self.links { if self.links {
#[cfg(unix)]
columns.push(Column::HardLinks); columns.push(Column::HardLinks);
} }
@ -74,14 +79,17 @@ impl Columns {
} }
if self.blocks { if self.blocks {
#[cfg(unix)]
columns.push(Column::Blocks); columns.push(Column::Blocks);
} }
if self.user { if self.user {
#[cfg(unix)]
columns.push(Column::User); columns.push(Column::User);
} }
if self.group { if self.group {
#[cfg(unix)]
columns.push(Column::Group); columns.push(Column::Group);
} }
@ -116,12 +124,18 @@ pub enum Column {
Permissions, Permissions,
FileSize, FileSize,
Timestamp(TimeType), Timestamp(TimeType),
#[cfg(unix)]
Blocks, Blocks,
#[cfg(unix)]
User, User,
#[cfg(unix)]
Group, Group,
#[cfg(unix)]
HardLinks, HardLinks,
#[cfg(unix)]
Inode, Inode,
GitStatus, GitStatus,
#[cfg(unix)]
Octal, Octal,
} }
@ -136,6 +150,7 @@ pub enum Alignment {
impl Column { impl Column {
/// Get the alignment this column should use. /// Get the alignment this column should use.
#[cfg(unix)]
pub fn alignment(self) -> Alignment { pub fn alignment(self) -> Alignment {
match self { match self {
Self::FileSize | Self::FileSize |
@ -147,19 +162,37 @@ impl Column {
} }
} }
#[cfg(windows)]
pub fn alignment(&self) -> Alignment {
match self {
Self::FileSize |
Self::GitStatus => Alignment::Right,
_ => Alignment::Left,
}
}
/// Get the text that should be printed at the top, when the user elects /// Get the text that should be printed at the top, when the user elects
/// to have a header row printed. /// to have a header row printed.
pub fn header(self) -> &'static str { pub fn header(self) -> &'static str {
match self { match self {
#[cfg(unix)]
Self::Permissions => "Permissions", Self::Permissions => "Permissions",
#[cfg(windows)]
Self::Permissions => "Mode",
Self::FileSize => "Size", Self::FileSize => "Size",
Self::Timestamp(t) => t.header(), Self::Timestamp(t) => t.header(),
#[cfg(unix)]
Self::Blocks => "Blocks", Self::Blocks => "Blocks",
#[cfg(unix)]
Self::User => "User", Self::User => "User",
#[cfg(unix)]
Self::Group => "Group", Self::Group => "Group",
#[cfg(unix)]
Self::HardLinks => "Links", Self::HardLinks => "Links",
#[cfg(unix)]
Self::Inode => "inode", Self::Inode => "inode",
Self::GitStatus => "Git", Self::GitStatus => "Git",
#[cfg(unix)]
Self::Octal => "Octal", Self::Octal => "Octal",
} }
} }
@ -274,10 +307,12 @@ pub struct Environment {
tz: Option<TimeZone>, tz: Option<TimeZone>,
/// Mapping cache of user IDs to usernames. /// Mapping cache of user IDs to usernames.
#[cfg(unix)]
users: Mutex<UsersCache>, users: Mutex<UsersCache>,
} }
impl Environment { impl Environment {
#[cfg(unix)]
pub fn lock_users(&self) -> MutexGuard<'_, UsersCache> { pub fn lock_users(&self) -> MutexGuard<'_, UsersCache> {
self.users.lock().unwrap() self.users.lock().unwrap()
} }
@ -296,12 +331,14 @@ impl Environment {
let numeric = locale::Numeric::load_user_locale() let numeric = locale::Numeric::load_user_locale()
.unwrap_or_else(|_| locale::Numeric::english()); .unwrap_or_else(|_| locale::Numeric::english());
#[cfg(unix)]
let users = Mutex::new(UsersCache::new()); let users = Mutex::new(UsersCache::new());
Self { numeric, tz, users } Self { numeric, tz, #[cfg(unix)] users }
} }
} }
#[cfg(unix)]
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({ TimeZone::from_file({
@ -322,6 +359,31 @@ fn determine_time_zone() -> TZResult<TimeZone> {
} }
} }
#[cfg(windows)]
fn determine_time_zone() -> TZResult<TimeZone> {
use datetime::zone::{FixedTimespan, FixedTimespanSet, StaticTimeZone, TimeZoneSource};
use std::borrow::Cow;
Ok(TimeZone(TimeZoneSource::Static(&StaticTimeZone {
name: "Unsupported",
fixed_timespans: FixedTimespanSet {
first: FixedTimespan {
offset: 0,
is_dst: false,
name: Cow::Borrowed("ZONE_A"),
},
rest: &[(
1206838800,
FixedTimespan {
offset: 3600,
is_dst: false,
name: Cow::Borrowed("ZONE_B"),
},
)],
},
})))
}
lazy_static! { lazy_static! {
static ref ENVIRONMENT: Environment = Environment::load_all(); static ref ENVIRONMENT: Environment = Environment::load_all();
} }
@ -388,11 +450,15 @@ impl<'a, 'f> Table<'a> {
fn permissions_plus(&self, file: &File<'_>, xattrs: bool) -> f::PermissionsPlus { fn permissions_plus(&self, file: &File<'_>, xattrs: bool) -> f::PermissionsPlus {
f::PermissionsPlus { f::PermissionsPlus {
file_type: file.type_char(), file_type: file.type_char(),
#[cfg(unix)]
permissions: file.permissions(), permissions: file.permissions(),
#[cfg(windows)]
attributes: file.attributes(),
xattrs, xattrs,
} }
} }
#[cfg(unix)]
fn octal_permissions(&self, file: &File<'_>) -> f::OctalPermissions { fn octal_permissions(&self, file: &File<'_>) -> f::OctalPermissions {
f::OctalPermissions { f::OctalPermissions {
permissions: file.permissions(), permissions: file.permissions(),
@ -407,24 +473,30 @@ impl<'a, 'f> Table<'a> {
Column::FileSize => { Column::FileSize => {
file.size().render(self.theme, self.size_format, &self.env.numeric) file.size().render(self.theme, self.size_format, &self.env.numeric)
} }
#[cfg(unix)]
Column::HardLinks => { Column::HardLinks => {
file.links().render(self.theme, &self.env.numeric) file.links().render(self.theme, &self.env.numeric)
} }
#[cfg(unix)]
Column::Inode => { Column::Inode => {
file.inode().render(self.theme.ui.inode) file.inode().render(self.theme.ui.inode)
} }
#[cfg(unix)]
Column::Blocks => { Column::Blocks => {
file.blocks().render(self.theme) file.blocks().render(self.theme)
} }
#[cfg(unix)]
Column::User => { Column::User => {
file.user().render(self.theme, &*self.env.lock_users(), self.user_format) file.user().render(self.theme, &*self.env.lock_users(), self.user_format)
} }
#[cfg(unix)]
Column::Group => { Column::Group => {
file.group().render(self.theme, &*self.env.lock_users(), self.user_format) file.group().render(self.theme, &*self.env.lock_users(), self.user_format)
} }
Column::GitStatus => { Column::GitStatus => {
self.git_status(file).render(self.theme) self.git_status(file).render(self.theme)
} }
#[cfg(unix)]
Column::Octal => { Column::Octal => {
self.octal_permissions(file).render(self.theme.ui.octal) self.octal_permissions(file).render(self.theme.ui.octal)
} }

View File

@ -229,6 +229,7 @@ impl render::GitColours for Theme {
fn conflicted(&self) -> Style { self.ui.git.conflicted } fn conflicted(&self) -> Style { self.ui.git.conflicted }
} }
#[cfg(unix)]
impl render::GroupColours for Theme { impl render::GroupColours for Theme {
fn yours(&self) -> Style { self.ui.users.group_yours } fn yours(&self) -> Style { self.ui.users.group_yours }
fn not_yours(&self) -> Style { self.ui.users.group_not_yours } fn not_yours(&self) -> Style { self.ui.users.group_not_yours }
@ -287,6 +288,7 @@ impl render::SizeColours for Theme {
fn minor(&self) -> Style { self.ui.size.minor } fn minor(&self) -> Style { self.ui.size.minor }
} }
#[cfg(unix)]
impl render::UserColours for Theme { impl render::UserColours for Theme {
fn you(&self) -> Style { self.ui.users.user_you } fn you(&self) -> Style { self.ui.users.user_you }
fn someone_else(&self) -> Style { self.ui.users.user_someone_else } fn someone_else(&self) -> Style { self.ui.users.user_someone_else }