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 14 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 @@ -36,9 +36,11 @@ scoped_threadpool = "0.1"
term_grid = "0.1"
term_size = "0.3"
unicode-width = "0.1"
users = "0.11"
zoneinfo_compiled = "0.5"

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

[dependencies.git2]
version = "0.13"
optional = true
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 @@ -273,6 +273,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 @@ -285,6 +286,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
62 changes: 62 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 @@ -330,12 +344,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 @@ -354,6 +379,11 @@ impl<'dir> File<'dir> {
}
}

#[cfg(windows)]
pub fn changed_time(&self) -> Option<SystemTime> {
return self.modified_time()
skyline75489 marked this conversation as resolved.
Show resolved Hide resolved
}

/// This file’s last accessed timestamp, if available on this platform.
pub fn accessed_time(&self) -> Option<SystemTime> {
self.metadata.accessed().ok()
Expand All @@ -369,6 +399,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 @@ -396,7 +427,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 @@ -420,6 +465,21 @@ 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;

f::Attributes {
skyline75489 marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -477,6 +537,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 @@ -554,6 +615,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 std::path::Path;

Expand Down Expand Up @@ -157,6 +158,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 @@ -250,6 +252,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
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
47 changes: 34 additions & 13 deletions src/options/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,6 @@ impl Args {
pub fn parse<'args, I>(&self, inputs: I, strictness: Strictness) -> Result<Matches<'args>, ParseError>
where I: IntoIterator<Item = &'args OsStr>
{
use std::os::unix::ffi::OsStrExt;

let mut parsing = true;

// The results that get built up.
Expand All @@ -159,7 +157,7 @@ impl Args {
// doesn’t have one in its string so it needs the next one.
let mut inputs = inputs.into_iter();
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 “--”.
// This allows a file named “--arg” to be specified by passing in
Expand All @@ -174,7 +172,7 @@ impl Args {

// If the string starts with *two* dashes then it’s a long argument.
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 there’s an equals in it, then the string before the
// equals will be the flag’s name, and the string after it
Expand Down Expand Up @@ -221,7 +219,7 @@ impl Args {
// If the string starts with *one* dash then it’s one or more
// short arguments.
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 there’s an equals in it, then the argument immediately
// before the equals was the one that has the value, with the
Expand All @@ -236,7 +234,7 @@ impl Args {
// it’s an error if any of the first set of arguments actually
// takes a value.
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...
for byte in other_args {
Expand Down Expand Up @@ -291,7 +289,7 @@ impl Args {
TakesValue::Optional(values) => {
if index < bytes.len() - 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;
}
else if let Some(next_arg) = inputs.next() {
Expand Down Expand Up @@ -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
/// either side. Returns `None` if there’s no equals or a string is missing.
fn split_on_equals(input: &OsStr) -> Option<(&OsStr, &OsStr)> {
use std::os::unix::ffi::OsStrExt;

if let Some(index) = input.as_bytes().iter().position(|elem| *elem == b'=') {
let (before, after) = input.as_bytes().split_at(index);
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);

// The after string contains the = that we need to remove.
if ! before.is_empty() && after.len() >= 2 {
return Some((OsStr::from_bytes(before),
OsStr::from_bytes(&after[1..])))
return Some((bytes_to_os_str(before),
bytes_to_os_str(&after[1..])))
}
}

Expand Down
Loading