diff --git a/yazi-core/src/manager/commands/create.rs b/yazi-core/src/manager/commands/create.rs index 55554b9c..351b40b5 100644 --- a/yazi-core/src/manager/commands/create.rs +++ b/yazi-core/src/manager/commands/create.rs @@ -4,7 +4,7 @@ use anyhow::Result; use tokio::fs; use yazi_config::popup::InputCfg; use yazi_proxy::{InputProxy, TabProxy, WATCHER}; -use yazi_shared::{event::Cmd, fs::{maybe_exists, ok_or_not_found, File, FilesOp, Url}}; +use yazi_shared::{event::Cmd, fs::{maybe_exists, ok_or_not_found, symlink_realpath, File, FilesOp, Url}}; use crate::manager::Manager; @@ -47,6 +47,10 @@ impl Manager { if dir { fs::create_dir_all(&new).await?; + } else if let Ok(real) = symlink_realpath(&new).await { + ok_or_not_found(fs::remove_file(&new).await)?; + FilesOp::Deleting(parent.clone(), vec![Url::from(real)]).emit(); + fs::File::create(&new).await?; } else { fs::create_dir_all(&parent).await.ok(); ok_or_not_found(fs::remove_file(&new).await)?; @@ -54,7 +58,7 @@ impl Manager { } if let Ok(f) = File::from(new.clone()).await { - FilesOp::Upserting(parent, HashMap::from_iter([(new.clone(), f)])).emit(); + FilesOp::Upserting(parent, HashMap::from_iter([(f.url(), f)])).emit(); TabProxy::reveal(&new) } Ok(()) diff --git a/yazi-core/src/manager/commands/rename.rs b/yazi-core/src/manager/commands/rename.rs index 70b9e371..6b13878d 100644 --- a/yazi-core/src/manager/commands/rename.rs +++ b/yazi-core/src/manager/commands/rename.rs @@ -5,7 +5,7 @@ use tokio::fs; use yazi_config::popup::InputCfg; use yazi_dds::Pubsub; use yazi_proxy::{InputProxy, TabProxy, WATCHER}; -use yazi_shared::{event::Cmd, fs::{maybe_exists, File, FilesOp, Url}}; +use yazi_shared::{event::Cmd, fs::{maybe_exists, ok_or_not_found, symlink_realpath, File, FilesOp, Url}}; use crate::manager::Manager; @@ -81,7 +81,13 @@ impl Manager { let Some(p_new) = new.parent_url() else { return Ok(()) }; let _permit = WATCHER.acquire().await.unwrap(); + let overwritten = symlink_realpath(&new).await; fs::rename(&old, &new).await?; + + if let Ok(p) = overwritten { + ok_or_not_found(fs::rename(&p, &new).await)?; + FilesOp::Deleting(p_new.clone(), vec![Url::from(p)]).emit(); + } Pubsub::pub_from_rename(tab, &old, &new); let file = File::from(new.clone()).await?; diff --git a/yazi-core/src/manager/watcher.rs b/yazi-core/src/manager/watcher.rs index 45cef5cb..7eab05a3 100644 --- a/yazi-core/src/manager/watcher.rs +++ b/yazi-core/src/manager/watcher.rs @@ -8,7 +8,7 @@ use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt}; use tracing::error; use yazi_plugin::isolate; use yazi_proxy::WATCHER; -use yazi_shared::{fs::{symlink_realpath, File, FilesOp, Url}, RoCell}; +use yazi_shared::{fs::{symlink_realpath_with, File, FilesOp, Url}, RoCell}; use super::Linked; use crate::folder::{Files, Folder}; @@ -111,7 +111,7 @@ impl Watcher { }; let realpath = if file.is_link() { - symlink_realpath(&url, &mut cached).await + symlink_realpath_with(&url, &mut cached).await } else { fs::canonicalize(&url).await.map(Cow::Owned) }; diff --git a/yazi-shared/src/fs/fns.rs b/yazi-shared/src/fs/fns.rs index ea727d0e..fa67bafa 100644 --- a/yazi-shared/src/fs/fns.rs +++ b/yazi-shared/src/fs/fns.rs @@ -24,23 +24,32 @@ pub fn ok_or_not_found(result: io::Result<()>) -> io::Result<()> { } } +#[inline] +pub async fn symlink_realpath(path: &Path) -> io::Result { + if fs::symlink_metadata(path).await?.is_symlink() { + symlink_realpath_with(path, &mut HashMap::new()).await.map(|p| p.into_owned()) + } else { + fs::canonicalize(path).await + } +} + // realpath(3) without resolving symlinks. This is useful for case-insensitive // filesystems. // // Make sure the file of the path exists and is a symlink. -pub async fn symlink_realpath<'a>( +pub async fn symlink_realpath_with<'a>( path: &'a Path, cached: &'a mut HashMap, ) -> io::Result> { - let Some(parent) = path.parent() else { - return Ok(Cow::Borrowed(path)); - }; - let lowercased: PathBuf = path.as_os_str().to_ascii_lowercase().into(); if lowercased == path { return Ok(Cow::Borrowed(path)); } + let Some(parent) = path.parent() else { + return Ok(Cow::Borrowed(path)); + }; + let case = parent.as_os_str().as_encoded_bytes().iter().any(|&b| b.is_ascii_uppercase()); if !cached.contains_key(parent) { let mut it = fs::read_dir(parent).await?;