Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial support for Windows #820

Merged
merged 27 commits into from
Feb 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7f717c3
checkpoint
zkat May 2, 2020
6a642d0
merge
skyline75489 Mar 26, 2021
0e8a458
Restore
skyline75489 Mar 26, 2021
e9d0af0
Restore more
skyline75489 Mar 26, 2021
aeb4a67
It actually works
skyline75489 Mar 26, 2021
3158369
Timestamps
skyline75489 Mar 26, 2021
5503e47
Clean
skyline75489 Mar 26, 2021
13b3635
Fix tests
skyline75489 Mar 26, 2021
33dd8fd
Clean
skyline75489 Mar 26, 2021
e874584
Try to fix CI
skyline75489 Mar 28, 2021
9d61301
Git works! Hooray!
skyline75489 Mar 28, 2021
78a3bc9
Merge branch 'master' into chesterliu/dev/win-support
skyline75489 Mar 30, 2021
00f97a9
Mimic 'Mode' in gci
skyline75489 Mar 30, 2021
0ea8f17
Clean
skyline75489 Mar 31, 2021
8ad46e2
Merge branch 'master' into chesterliu/dev/win-support
skyline75489 Apr 3, 2021
0adc5c7
Merge branch 'master' into chesterliu/dev/win-support
skyline75489 Apr 6, 2021
8f0e4cc
Merge branch 'master' into chesterliu/dev/win-support
skyline75489 Apr 13, 2021
777cd7e
Explicitly enable vt processing on Windows
skyline75489 Apr 17, 2021
d6d35bf
Hide _ prefix files
skyline75489 Apr 20, 2021
76e336c
Merge branch 'master' into chesterliu/dev/win-support
skyline75489 Apr 26, 2021
23a1c8a
Merge branch 'master' into chesterliu/dev/win-support
skyline75489 May 15, 2021
e3204a5
Use system path sep in symlink
skyline75489 May 15, 2021
9881d00
Fix build
skyline75489 May 20, 2021
99d653b
Merge branch 'master' into chesterliu/dev/win-support
skyline75489 Oct 29, 2021
d6732ae
Merge branch 'master' into chesterliu/dev/win-support
skyline75489 May 6, 2022
53cb75c
Fix build on Windows
skyline75489 May 6, 2022
6fb3740
Update src/fs/file.rs
skyline75489 May 6, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,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('_') {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is overdoing it. Python uses __init__.py for top-level modules, and this hides all such files. If you're running this build with msys/cygwin, most likely your dotfiles actually start with '.' anyway.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know any files, except for _vimrc on Windows that follows this convention either.

Copy link

@ntpeters ntpeters Oct 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're running this build with msys/cygwin, most likely your dotfiles actually start with '.' anyway

Even without msys/cygwin. I don't mess with those and only use pwsh, and just use the normal dot prefix for all my dotfiles (including .vimrc).

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