Skip to content

Commit 86c53d8

Browse files
Merge branch 'main' into preview-word-wrap
2 parents c078fb8 + f5a7ace commit 86c53d8

File tree

3 files changed

+80
-40
lines changed

3 files changed

+80
-40
lines changed

cspell.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"language":"en","version":"0.2","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath"]}
1+
{"version":"0.2","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname"],"language":"en"}

yazi-core/src/manager/watcher.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{borrow::Cow, collections::{HashMap, HashSet}, time::{Duration, SystemTime}};
1+
use std::{collections::{HashMap, HashSet}, time::{Duration, SystemTime}};
22

33
use anyhow::Result;
44
use notify::{RecommendedWatcher, RecursiveMode, Watcher as _Watcher};
@@ -8,7 +8,7 @@ use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
88
use tracing::error;
99
use yazi_plugin::isolate;
1010
use yazi_proxy::WATCHER;
11-
use yazi_shared::{fs::{symlink_realpath_with, File, FilesOp, Url}, RoCell};
11+
use yazi_shared::{fs::{symlink_realname, File, FilesOp, Url}, RoCell};
1212

1313
use super::Linked;
1414
use crate::folder::{Files, Folder};
@@ -104,18 +104,18 @@ impl Watcher {
104104
let mut reload = Vec::with_capacity(urls.len());
105105

106106
for url in urls {
107+
let Some(name) = url.file_name() else { continue };
107108
let Some(parent) = url.parent_url() else { continue };
109+
108110
let Ok(file) = File::from(url.clone()).await else {
109111
FilesOp::Deleting(parent, vec![url]).emit();
110112
continue;
111113
};
112114

113-
let real = if file.is_link() {
114-
symlink_realpath_with(&url, &mut cached).await
115-
} else {
116-
fs::canonicalize(&url).await.map(Cow::Owned)
117-
};
118-
if !real.is_ok_and(|p| p == *url) {
115+
let eq = (!file.is_link() && fs::canonicalize(&url).await.is_ok_and(|p| p == *url))
116+
|| symlink_realname(&url, &mut cached).await.is_ok_and(|s| s == name);
117+
118+
if !eq {
119119
FilesOp::Deleting(parent, vec![url]).emit();
120120
continue;
121121
}

yazi-shared/src/fs/fns.rs

Lines changed: 71 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use std::{borrow::Cow, collections::{HashMap, VecDeque}, fs::Metadata, path::{Path, PathBuf}};
1+
use std::{borrow::Cow, collections::{HashMap, VecDeque}, ffi::{OsStr, OsString}, fs::Metadata, path::{Path, PathBuf}};
22

3-
use anyhow::Result;
3+
use anyhow::{bail, Result};
44
use tokio::{fs, io, select, sync::{mpsc, oneshot}, time};
55

66
#[inline]
@@ -23,50 +23,85 @@ pub fn ok_or_not_found(result: io::Result<()>) -> io::Result<()> {
2323
}
2424
}
2525

26-
#[inline]
27-
pub async fn symlink_realpath(path: &Path) -> io::Result<PathBuf> {
28-
if fs::symlink_metadata(path).await?.is_symlink() {
29-
symlink_realpath_with(path, &mut HashMap::new()).await.map(|p| p.into_owned())
30-
} else {
31-
fs::canonicalize(path).await
26+
pub async fn symlink_realpath(path: &Path) -> Result<PathBuf> {
27+
let p = fs::canonicalize(path).await?;
28+
if p == path {
29+
return Ok(p);
3230
}
31+
32+
let Some(parent) = path.parent() else { bail!("no parent") };
33+
symlink_realname(path, &mut HashMap::new()).await.map(|n| parent.join(n))
34+
}
35+
36+
#[cfg(unix)]
37+
#[tokio::test]
38+
async fn test_symlink_realpath() {
39+
fs::remove_dir_all("/tmp/issue-1173").await.ok();
40+
fs::create_dir_all("/tmp/issue-1173/real-dir").await.unwrap();
41+
fs::File::create("/tmp/issue-1173/A").await.unwrap();
42+
fs::File::create("/tmp/issue-1173/b").await.unwrap();
43+
fs::File::create("/tmp/issue-1173/real-dir/C").await.unwrap();
44+
fs::symlink("/tmp/issue-1173/b", "/tmp/issue-1173/D").await.unwrap();
45+
fs::symlink("real-dir", "/tmp/issue-1173/link-dir").await.unwrap();
46+
47+
async fn check(a: &str, b: &str) {
48+
let expected = if a == b || cfg!(windows) || cfg!(target_os = "macos") {
49+
Some(PathBuf::from(b))
50+
} else {
51+
None
52+
};
53+
assert_eq!(symlink_realpath(Path::new(a)).await.ok(), expected);
54+
}
55+
56+
check("/tmp/issue-1173/a", "/tmp/issue-1173/A").await;
57+
check("/tmp/issue-1173/A", "/tmp/issue-1173/A").await;
58+
59+
check("/tmp/issue-1173/b", "/tmp/issue-1173/b").await;
60+
check("/tmp/issue-1173/B", "/tmp/issue-1173/b").await;
61+
62+
check("/tmp/issue-1173/link-dir/c", "/tmp/issue-1173/link-dir/C").await;
63+
check("/tmp/issue-1173/link-dir/C", "/tmp/issue-1173/link-dir/C").await;
64+
65+
check("/tmp/issue-1173/d", "/tmp/issue-1173/D").await;
66+
check("/tmp/issue-1173/D", "/tmp/issue-1173/D").await;
3367
}
3468

3569
// realpath(3) without resolving symlinks. This is useful for case-insensitive
3670
// filesystems.
3771
//
38-
// Make sure the file of the path exists and is a symlink.
39-
pub async fn symlink_realpath_with<'a>(
72+
// Make sure the file of the path exists.
73+
pub async fn symlink_realname<'a>(
4074
path: &'a Path,
41-
cached: &'a mut HashMap<PathBuf, PathBuf>,
42-
) -> io::Result<Cow<'a, Path>> {
43-
let lowercased: PathBuf = path.as_os_str().to_ascii_lowercase().into();
44-
if lowercased == path {
45-
return Ok(Cow::Borrowed(path));
46-
}
47-
48-
let Some(parent) = path.parent() else {
49-
return Ok(Cow::Borrowed(path));
50-
};
75+
cached: &'a mut HashMap<PathBuf, HashMap<OsString, OsString>>,
76+
) -> Result<Cow<'a, OsStr>> {
77+
let Some(name) = path.file_name() else { bail!("no file name") };
78+
let Some(parent) = path.parent() else { return Ok(name.into()) };
5179

52-
let case = parent.as_os_str().as_encoded_bytes().iter().any(|&b| b.is_ascii_uppercase());
5380
if !cached.contains_key(parent) {
81+
let mut map = HashMap::new();
5482
let mut it = fs::read_dir(parent).await?;
5583
while let Some(entry) = it.next_entry().await? {
56-
let p = entry.path();
57-
if case || p.file_name().unwrap().as_encoded_bytes().iter().any(|&b| b.is_ascii_uppercase()) {
58-
cached.insert(p.as_os_str().to_ascii_lowercase().into(), p);
84+
let n = entry.file_name();
85+
if n.as_encoded_bytes().iter().all(|&b| b.is_ascii_lowercase()) {
86+
map.insert(n, OsString::new());
87+
} else {
88+
map.insert(n.to_ascii_lowercase(), n);
5989
}
6090
}
61-
cached.insert(parent.to_owned(), PathBuf::new());
91+
cached.insert(parent.to_owned(), map);
92+
}
93+
94+
let c = &cached[parent];
95+
if let Some(s) = c.get(name) {
96+
return if s.is_empty() { Ok(name.into()) } else { Ok(s.into()) };
6297
}
6398

64-
Ok(
65-
cached
66-
.get(&lowercased)
67-
.filter(|p| !p.as_os_str().is_empty())
68-
.map_or_else(|| Cow::Borrowed(path), |p| Cow::Borrowed(p)),
69-
)
99+
let lowercased = name.to_ascii_lowercase();
100+
if let Some(s) = c.get(&lowercased) {
101+
return if s.is_empty() { Ok(lowercased.into()) } else { Ok(s.into()) };
102+
}
103+
104+
Ok(name.into())
70105
}
71106

72107
pub async fn calculate_size(path: &Path) -> u64 {
@@ -112,6 +147,11 @@ pub fn copy_with_progress(
112147
use std::os::macos::fs::FileTimesExt;
113148
meta.created().map(|t| ft = ft.set_created(t)).ok();
114149
}
150+
#[cfg(windows)]
151+
{
152+
use std::os::windows::fs::FileTimesExt;
153+
meta.created().map(|t| ft = ft.set_created(t)).ok();
154+
}
115155

116156
async move {
117157
_ = match fs::copy(&from, &to).await {

0 commit comments

Comments
 (0)