Skip to content

Commit

Permalink
Merge pull request #820 from skyline75489/chesterliu/dev/win-support
Browse files Browse the repository at this point in the history
Initial support for Windows
  • Loading branch information
ariasuni committed Feb 20, 2023
2 parents e385cd5 + 6fb3740 commit f3ca1fe
Show file tree
Hide file tree
Showing 14 changed files with 279 additions and 17 deletions.
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ scoped_threadpool = "0.1"
term_grid = "0.2.0"
terminal_size = "0.1.16"
unicode-width = "0.1"
users = "0.11"
zoneinfo_compiled = "0.5.1"

[target.'cfg(unix)'.dependencies]
users = "0.11"

[dependencies.datetime]
version = "0.5.2"
default-features = false
Expand Down
7 changes: 7 additions & 0 deletions src/fs/dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ impl<'dir, 'ig> Files<'dir, 'ig> {
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 {
let git_status = self.git.map(|g| g.get(path, false)).unwrap_or_default();
if git_status.unstaged == GitStatus::Ignored {
Expand Down
9 changes: 9 additions & 0 deletions src/fs/feature/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ impl Git {
/// Paths need to be absolute for them to be compared properly, otherwise
/// you’d ask a repo about “./README.md” but it only knows about
/// “/vagrant/README.md”, prefixed by the workdir.
#[cfg(unix)]
fn reorient(path: &Path) -> PathBuf {
use std::env::current_dir;

Expand All @@ -308,6 +309,14 @@ fn reorient(path: &Path) -> PathBuf {
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.
fn working_tree_status(status: git2::Status) -> f::GitStatus {
match status {
Expand Down
14 changes: 14 additions & 0 deletions src/fs/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,27 @@ pub struct Permissions {
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 details view. These values are fused together to make the output a
/// little more compressed.
#[derive(Copy, Clone)]
pub struct PermissionsPlus {
pub file_type: Type,
#[cfg(unix)]
pub permissions: Permissions,
#[cfg(windows)]
pub attributes: Attributes,
pub xattrs: bool,
}

Expand Down
63 changes: 63 additions & 0 deletions src/fs/file.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
//! Files, and methods and fields to access their metadata.

use std::io;
#[cfg(unix)]
use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};
#[cfg(windows)]
use std::os::windows::fs::MetadataExt;
use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime, UNIX_EPOCH};

Expand Down Expand Up @@ -174,6 +177,7 @@ impl<'dir> File<'dir> {
/// Whether this file is both a regular file *and* executable for the
/// current user. An executable file has a different purpose from an
/// executable directory, so they should be highlighted differently.
#[cfg(unix)]
pub fn is_executable_file(&self) -> bool {
let bit = modes::USER_EXECUTE;
self.is_file() && (self.metadata.permissions().mode() & bit) == bit
Expand All @@ -185,21 +189,25 @@ impl<'dir> File<'dir> {
}

/// Whether this file is a named pipe on the filesystem.
#[cfg(unix)]
pub fn is_pipe(&self) -> bool {
self.metadata.file_type().is_fifo()
}

/// Whether this file is a char device on the filesystem.
#[cfg(unix)]
pub fn is_char_device(&self) -> bool {
self.metadata.file_type().is_char_device()
}

/// Whether this file is a block device on the filesystem.
#[cfg(unix)]
pub fn is_block_device(&self) -> bool {
self.metadata.file_type().is_block_device()
}

/// Whether this file is a socket on the filesystem.
#[cfg(unix)]
pub fn is_socket(&self) -> bool {
self.metadata.file_type().is_socket()
}
Expand Down Expand Up @@ -270,6 +278,7 @@ impl<'dir> File<'dir> {
/// is uncommon, while you come across directories and other types
/// with multiple links much more often. Thus, it should get highlighted
/// more attentively.
#[cfg(unix)]
pub fn links(&self) -> f::Links {
let count = self.metadata.nlink();

Expand All @@ -280,13 +289,15 @@ impl<'dir> File<'dir> {
}

/// This file’s inode.
#[cfg(unix)]
pub fn inode(&self) -> f::Inode {
f::Inode(self.metadata.ino())
}

/// This file’s number of filesystem blocks.
///
/// (Not the size of each block, which we don’t actually report on)
#[cfg(unix)]
pub fn blocks(&self) -> f::Blocks {
if self.is_file() || self.is_link() {
f::Blocks::Some(self.metadata.blocks())
Expand All @@ -297,11 +308,13 @@ impl<'dir> File<'dir> {
}

/// The ID of the user that own this file.
#[cfg(unix)]
pub fn user(&self) -> f::User {
f::User(self.metadata.uid())
}

/// The ID of the group that owns this file.
#[cfg(unix)]
pub fn group(&self) -> f::Group {
f::Group(self.metadata.gid())
}
Expand All @@ -314,6 +327,7 @@ impl<'dir> File<'dir> {
///
/// Block and character devices return their device IDs, because they
/// usually just have a file size of zero.
#[cfg(unix)]
pub fn size(&self) -> f::Size {
if self.is_directory() {
f::Size::None
Expand All @@ -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 file’s last modified timestamp, if available on this platform.
pub fn modified_time(&self) -> Option<SystemTime> {
self.metadata.modified().ok()
}

/// This file’s last changed timestamp, if available on this platform.
#[cfg(unix)]
pub fn changed_time(&self) -> Option<SystemTime> {
let (mut sec, mut nanosec) = (self.metadata.ctime(), self.metadata.ctime_nsec());

Expand All @@ -359,6 +384,11 @@ impl<'dir> File<'dir> {
}
}

#[cfg(windows)]
pub fn changed_time(&self) -> Option<SystemTime> {
return self.modified_time()
}

/// This file’s last accessed timestamp, if available on this platform.
pub fn accessed_time(&self) -> Option<SystemTime> {
self.metadata.accessed().ok()
Expand All @@ -374,6 +404,7 @@ impl<'dir> File<'dir> {
/// 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
/// ls puts this character there.
#[cfg(unix)]
pub fn type_char(&self) -> f::Type {
if self.is_file() {
f::Type::File
Expand Down Expand Up @@ -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 file’s permissions, with flags for each bit.
#[cfg(unix)]
pub fn permissions(&self) -> f::Permissions {
let bits = self.metadata.mode();
let has_bit = |bit| bits & bit == bit;
Expand All @@ -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 file’s extension is any of the strings that get passed in.
///
/// This will always return `false` if the file has no extension.
Expand Down Expand Up @@ -482,6 +543,7 @@ impl<'dir> FileTarget<'dir> {

/// More readable aliases for the permission bits exposed by libc.
#[allow(trivial_numeric_casts)]
#[cfg(unix)]
mod modes {

// The `libc::mode_t` type’s actual type varies, but the value returned
Expand Down Expand Up @@ -559,6 +621,7 @@ mod filename_test {
}

#[test]
#[cfg(unix)]
fn topmost() {
assert_eq!("/", File::filename(Path::new("/")))
}
Expand Down
3 changes: 3 additions & 0 deletions src/fs/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use std::cmp::Ordering;
use std::iter::FromIterator;
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;

use crate::fs::DotFilter;
Expand Down Expand Up @@ -130,6 +131,7 @@ pub enum SortField {

/// The file’s inode, which usually corresponds to the order in which
/// files were created on the filesystem, more or less.
#[cfg(unix)]
FileInode,

/// The time the file was modified (the “mtime”).
Expand Down Expand Up @@ -223,6 +225,7 @@ impl SortField {
Self::Name(AaBbCc) => natord::compare_ignore_case(&a.name, &b.name),

Self::Size => a.metadata.len().cmp(&b.metadata.len()),
#[cfg(unix)]
Self::FileInode => a.metadata.ino().cmp(&b.metadata.ino()),
Self::ModifiedDate => a.modified_time().cmp(&b.modified_time()),
Self::AccessedDate => a.accessed_time().cmp(&b.accessed_time()),
Expand Down
6 changes: 6 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,18 @@ mod theme;
fn main() {
use std::process::exit;

#[cfg(unix)]
unsafe {
libc::signal(libc::SIGPIPE, libc::SIG_DFL);
}

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();
match Options::parse(args.iter().map(|e| e.as_ref()), &LiveVars) {
OptionsResult::Ok(options, mut input_paths) => {
Expand Down
1 change: 1 addition & 0 deletions src/options/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ impl SortField {
"cr" | "created" => {
Self::CreatedDate
}
#[cfg(unix)]
"inode" => {
Self::FileInode
}
Expand Down
Loading

0 comments on commit f3ca1fe

Please sign in to comment.