-
Notifications
You must be signed in to change notification settings - Fork 595
feat: casefold #3114
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
feat: casefold #3114
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces casefold functionality to handle case-insensitive file operations on various filesystems. The changes refactor the existing case validation system to provide more robust case-insensitive filename handling.
- Replaces
valid_name_case
withcasefold
function that returns the actual cased filename - Adds platform-specific implementations for casefold operations across Unix-like systems and Windows
- Updates filesystem operations to use the new casefold functionality for proper case handling
Reviewed Changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
File | Description |
---|---|
yazi-watcher/src/watcher.rs | Updates file watcher to use new case matching function |
yazi-fs/src/provider/provider.rs | Adds public casefold function to provider interface |
yazi-fs/src/provider/local/local.rs | Updates parameter names for consistency |
yazi-fs/src/provider/local/casefold.rs | Replaces case validation with comprehensive casefold implementation |
yazi-fs/src/fns.rs | Removes old realname functionality and associated tests |
yazi-actor/src/mgr/rename.rs | Updates rename operations to use casefold for proper case handling |
yazi-actor/src/mgr/create.rs | Updates file creation to use casefold functionality |
cspell.json | Adds "inodes" to dictionary |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
use std::{ffi::{CStr, CString, OsString}, os::{fd::{AsRawFd, FromRawFd, OwnedFd}, unix::ffi::OsStringExt}}; | ||
|
||
use libc::{F_GETPATH, O_RDONLY, O_SYMLINK, PATH_MAX}; | ||
|
||
let cstr = CString::new(path.into_os_string().into_encoded_bytes())?; | ||
let Some(name) = Path::new(OsStr::from_bytes(cstr.as_bytes())).file_name() else { | ||
return Ok(true); | ||
}; | ||
|
||
let cstr = CString::new(path.into_os_string().into_vec())?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The into_vec()
conversion on line 26 and the subsequent vector operations could be inefficient for large paths. Consider using byte slice operations where possible to avoid unnecessary allocations.
Copilot uses AI. Check for mistakes.
let mut entries: Vec<_> = std::fs::read_dir(parent)? | ||
.filter_map(Result::ok) | ||
.filter_map(|e| e.metadata().ok().map(|m| (e, m))) | ||
.filter(|(_, m)| m.dev() == meta.dev() && m.ino() == meta.ino()) | ||
.map(|(e, _)| e.path()) | ||
.collect(); | ||
|
||
if entries.len() == 1 { | ||
// No hardlink that shares the same inode | ||
Ok(entries.remove(0)) | ||
} else if let Some(i) = entries.iter().position(|p| p == path) { | ||
// Exact match | ||
Ok(entries.swap_remove(i)) | ||
} else if let Some(i) = entries.iter().position(|p| p.as_os_str().eq_ignore_ascii_case(oss)) { | ||
// Case-insensitive match | ||
Ok(entries.swap_remove(i)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reading the entire directory and filtering by inode could be inefficient for large directories. This creates an O(n) operation for each casefold call. Consider caching directory contents or using a more efficient lookup strategy.
let mut entries: Vec<_> = std::fs::read_dir(parent)? | |
.filter_map(Result::ok) | |
.filter_map(|e| e.metadata().ok().map(|m| (e, m))) | |
.filter(|(_, m)| m.dev() == meta.dev() && m.ino() == meta.ino()) | |
.map(|(e, _)| e.path()) | |
.collect(); | |
if entries.len() == 1 { | |
// No hardlink that shares the same inode | |
Ok(entries.remove(0)) | |
} else if let Some(i) = entries.iter().position(|p| p == path) { | |
// Exact match | |
Ok(entries.swap_remove(i)) | |
} else if let Some(i) = entries.iter().position(|p| p.as_os_str().eq_ignore_ascii_case(oss)) { | |
// Case-insensitive match | |
Ok(entries.swap_remove(i)) | |
let mut inode_map: HashMap<(u64, u64), Vec<PathBuf>> = HashMap::new(); | |
for entry in std::fs::read_dir(parent)? { | |
if let Ok(e) = entry { | |
if let Ok(m) = e.metadata() { | |
let key = (m.dev(), m.ino()); | |
inode_map.entry(key).or_default().push(e.path()); | |
} | |
} | |
} | |
let key = (meta.dev(), meta.ino()); | |
let entries = inode_map.get_mut(&key); | |
if let Some(entries) = entries { | |
if entries.len() == 1 { | |
// No hardlink that shares the same inode | |
Ok(entries.remove(0)) | |
} else if let Some(i) = entries.iter().position(|p| p == path) { | |
// Exact match | |
Ok(entries.swap_remove(i)) | |
} else if let Some(i) = entries.iter().position(|p| p.as_os_str().eq_ignore_ascii_case(oss)) { | |
// Case-insensitive match | |
Ok(entries.swap_remove(i)) | |
} else { | |
Err(io::Error::new(io::ErrorKind::NotFound, "file not found")) | |
} |
Copilot uses AI. Check for mistakes.
if entries.len() == 1 { | ||
// No hardlink that shares the same inode | ||
Ok(entries.remove(0)) | ||
} else if let Some(i) = entries.iter().position(|p| p == path) { | ||
// Exact match | ||
Ok(entries.swap_remove(i)) | ||
} else if let Some(i) = entries.iter().position(|p| p.as_os_str().eq_ignore_ascii_case(oss)) { | ||
// Case-insensitive match | ||
Ok(entries.swap_remove(i)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The logic for handling different numbers of entries (1, exact match, case-insensitive match) could be simplified and made more readable by restructuring the conditional flow.
if entries.len() == 1 { | |
// No hardlink that shares the same inode | |
Ok(entries.remove(0)) | |
} else if let Some(i) = entries.iter().position(|p| p == path) { | |
// Exact match | |
Ok(entries.swap_remove(i)) | |
} else if let Some(i) = entries.iter().position(|p| p.as_os_str().eq_ignore_ascii_case(oss)) { | |
// Case-insensitive match | |
Ok(entries.swap_remove(i)) | |
// Prefer exact match, then case-insensitive match, then unique inode match | |
if let Some(i) = entries.iter().position(|p| p == path) { | |
// Exact match | |
Ok(entries[i].clone()) | |
} else if let Some(i) = entries.iter().position(|p| p.as_os_str().eq_ignore_ascii_case(oss)) { | |
// Case-insensitive match | |
Ok(entries[i].clone()) | |
} else if entries.len() == 1 { | |
// Only one entry with matching inode | |
Ok(entries[0].clone()) |
Copilot uses AI. Check for mistakes.
No description provided.