mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: append the suffix to the end when generating unique filenames for directories, i.e., after not before the extension (#1784)
Co-authored-by: sxyazi <sxyazi@gmail.com>
This commit is contained in:
parent
097e5abb82
commit
17a1169ea2
8 changed files with 58 additions and 36 deletions
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
For breaking changes, see [Migrating to Yazi v0.4.0](https://github.com/sxyazi/yazi/issues/1772).
|
||||
|
||||
<br><br>
|
||||
|
||||
<div align="center">
|
||||
<img src="assets/logo.png" alt="Yazi logo" width="20%">
|
||||
</div>
|
||||
|
|
@ -56,7 +58,7 @@ https://github.com/sxyazi/yazi/assets/17523360/92ff23fa-0cd5-4f04-b387-894c12265
|
|||
| [VSCode](https://github.com/microsoft/vscode) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in |
|
||||
| [Tabby](https://github.com/Eugeny/tabby) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in |
|
||||
| [Hyper](https://github.com/vercel/hyper) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in |
|
||||
| [Rio](https://github.com/raphamorim/rio) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in |
|
||||
| [Rio](https://github.com/raphamorim/rio) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ❌ Rio doesn't correctly clear images (#1786) |
|
||||
| X11 / Wayland | Window system protocol | ☑️ [Überzug++](https://github.com/jstkdng/ueberzugpp) required |
|
||||
| Fallback | [ASCII art (Unicode block)](https://en.wikipedia.org/wiki/ASCII_art) | ☑️ [Chafa](https://hpjansson.org/chafa/) required |
|
||||
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ impl Watcher {
|
|||
};
|
||||
|
||||
let u = &file.url;
|
||||
let eq = (!file.is_linkable() && fs::canonicalize(u).await.is_ok_and(|p| p == ***u))
|
||||
let eq = (!file.is_link() && fs::canonicalize(u).await.is_ok_and(|p| p == ***u))
|
||||
|| realname_unchecked(u, &mut cached).await.is_ok_and(|s| urn.as_urn() == s);
|
||||
|
||||
if !eq {
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ pub fn install(lua: &Lua) -> mlua::Result<()> {
|
|||
(
|
||||
"unique_name",
|
||||
lua.create_async_function(|lua, url: UrlRef| async move {
|
||||
match yazi_shared::fs::unique_name(url.clone()).await {
|
||||
match yazi_shared::fs::unique_name(url.clone(), async { false }).await {
|
||||
Ok(u) => (Url::cast(lua, u)?, Value::Nil).into_lua_multi(lua),
|
||||
Err(e) => (Value::Nil, e.raw_os_error()).into_lua_multi(lua),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ impl File {
|
|||
let id = task.id;
|
||||
self.prog.send(TaskProg::New(id, cha.len))?;
|
||||
|
||||
if cha.is_linkable() {
|
||||
if cha.is_orphan() || (cha.is_link() && !task.follow) {
|
||||
self.queue(FileOp::Link(task.into()), NORMAL).await?;
|
||||
} else {
|
||||
self.queue(FileOp::Paste(task), LOW).await?;
|
||||
|
|
@ -208,7 +208,7 @@ impl File {
|
|||
let to = dest.join(from.file_name().unwrap());
|
||||
self.prog.send(TaskProg::New(task.id, cha.len))?;
|
||||
|
||||
if cha.is_linkable() {
|
||||
if cha.is_orphan() || (cha.is_link() && !task.follow) {
|
||||
self.queue(FileOp::Link(task.spawn(from, to, cha).into()), NORMAL).await?;
|
||||
} else {
|
||||
self.queue(FileOp::Paste(task.spawn(from, to, cha)), LOW).await?;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use tokio::{fs, select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}, task::
|
|||
use yazi_config::{TASKS, open::Opener, plugin::{Fetcher, Preloader}};
|
||||
use yazi_dds::Pump;
|
||||
use yazi_proxy::ManagerProxy;
|
||||
use yazi_shared::{Throttle, event::Data, fs::{Url, remove_dir_clean, unique_name}};
|
||||
use yazi_shared::{Throttle, event::Data, fs::{Url, must_be_dir, remove_dir_clean, unique_name}};
|
||||
|
||||
use super::{Ongoing, TaskProg, TaskStage};
|
||||
use crate::{HIGH, LOW, NORMAL, TaskKind, TaskOp, file::{File, FileOpDelete, FileOpHardlink, FileOpLink, FileOpPaste, FileOpTrash}, plugin::{Plugin, PluginOpEntry}, prework::{Prework, PreworkOpFetch, PreworkOpLoad, PreworkOpSize}, process::{Process, ProcessOpBg, ProcessOpBlock, ProcessOpOrphan}};
|
||||
|
|
@ -97,7 +97,7 @@ impl Scheduler {
|
|||
let file = self.file.clone();
|
||||
self.send_micro(id, LOW, async move {
|
||||
if !force {
|
||||
to = unique_name(to).await?;
|
||||
to = unique_name(to, must_be_dir(&from)).await?;
|
||||
}
|
||||
file.paste(FileOpPaste { id, from, to, cha: None, cut: true, follow: false, retry: 0 }).await
|
||||
});
|
||||
|
|
@ -114,7 +114,7 @@ impl Scheduler {
|
|||
let file = self.file.clone();
|
||||
self.send_micro(id, LOW, async move {
|
||||
if !force {
|
||||
to = unique_name(to).await?;
|
||||
to = unique_name(to, must_be_dir(&from)).await?;
|
||||
}
|
||||
file.paste(FileOpPaste { id, from, to, cha: None, cut: false, follow, retry: 0 }).await
|
||||
});
|
||||
|
|
@ -126,7 +126,7 @@ impl Scheduler {
|
|||
let file = self.file.clone();
|
||||
self.send_micro(id, LOW, async move {
|
||||
if !force {
|
||||
to = unique_name(to).await?;
|
||||
to = unique_name(to, must_be_dir(&from)).await?;
|
||||
}
|
||||
file
|
||||
.link(FileOpLink { id, from, to, cha: None, resolve: false, relative, delete: false })
|
||||
|
|
@ -145,7 +145,7 @@ impl Scheduler {
|
|||
let file = self.file.clone();
|
||||
self.send_micro(id, LOW, async move {
|
||||
if !force {
|
||||
to = unique_name(to).await?;
|
||||
to = unique_name(to, must_be_dir(&from)).await?;
|
||||
}
|
||||
file.hardlink(FileOpHardlink { id, from, to, cha: None, follow }).await
|
||||
});
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ impl From<Metadata> for Cha {
|
|||
let mut kind = ChaKind::empty();
|
||||
if m.is_dir() {
|
||||
kind |= ChaKind::DIR;
|
||||
} else if m.is_symlink() {
|
||||
kind |= ChaKind::LINK;
|
||||
}
|
||||
|
||||
Self {
|
||||
|
|
@ -91,7 +93,7 @@ impl From<FileType> for Cha {
|
|||
kind |= ChaKind::DIR;
|
||||
libc::S_IFDIR
|
||||
} else if t.is_symlink() {
|
||||
kind |= ChaKind::ORPHAN;
|
||||
kind |= ChaKind::LINK;
|
||||
libc::S_IFLNK
|
||||
} else if t.is_block_device() {
|
||||
libc::S_IFBLK
|
||||
|
|
@ -130,8 +132,11 @@ impl Cha {
|
|||
let mut attached = ChaKind::empty();
|
||||
|
||||
if meta.is_symlink() {
|
||||
attached |= ChaKind::LINK;
|
||||
meta = tokio::fs::metadata(path).await.unwrap_or(meta);
|
||||
attached |= if meta.is_symlink() { ChaKind::ORPHAN } else { ChaKind::LINK };
|
||||
}
|
||||
if meta.is_symlink() {
|
||||
attached |= ChaKind::ORPHAN;
|
||||
}
|
||||
|
||||
let mut cha = Self::new_nofollow(path, meta);
|
||||
|
|
@ -187,9 +192,6 @@ impl Cha {
|
|||
#[inline]
|
||||
pub const fn is_orphan(&self) -> bool { self.kind.contains(ChaKind::ORPHAN) }
|
||||
|
||||
#[inline]
|
||||
pub const fn is_linkable(&self) -> bool { self.is_link() || self.is_orphan() }
|
||||
|
||||
#[inline]
|
||||
pub const fn is_dummy(&self) -> bool { self.kind.contains(ChaKind::DUMMY) }
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,11 @@ pub async fn maybe_exists(p: impl AsRef<Path>) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn must_be_dir(p: impl AsRef<Path>) -> bool {
|
||||
fs::metadata(p).await.map_or(false, |m| m.is_dir())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn ok_or_not_found(result: io::Result<()>) -> io::Result<()> {
|
||||
match result {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{borrow::Cow, env, ffi::OsString, io, path::{Component, Path, PathBuf}};
|
||||
use std::{borrow::Cow, env, ffi::OsString, future::Future, io, path::{Component, Path, PathBuf}};
|
||||
|
||||
use tokio::fs;
|
||||
|
||||
|
|
@ -74,38 +74,51 @@ fn _expand_path(p: &Path) -> PathBuf {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn unique_name(mut u: Url) -> io::Result<Url> {
|
||||
pub async fn unique_name<F>(u: Url, append: F) -> io::Result<Url>
|
||||
where
|
||||
F: Future<Output = bool>,
|
||||
{
|
||||
match fs::symlink_metadata(&u).await {
|
||||
Ok(_) => _unique_name(u, append.await).await,
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(u),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
async fn _unique_name(mut u: Url, append: bool) -> io::Result<Url> {
|
||||
let Some(stem) = u.file_stem().map(|s| s.to_owned()) else {
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidInput, "empty file stem"));
|
||||
};
|
||||
|
||||
let ext = u
|
||||
.extension()
|
||||
.map(|s| {
|
||||
let mut n = OsString::with_capacity(s.len() + 1);
|
||||
n.push(".");
|
||||
n.push(s);
|
||||
n
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let dot_ext = u.extension().map_or_else(OsString::new, |e| {
|
||||
let mut s = OsString::with_capacity(e.len() + 1);
|
||||
s.push(".");
|
||||
s.push(e);
|
||||
s
|
||||
});
|
||||
|
||||
let mut i = 1u64;
|
||||
let mut p = u.to_path();
|
||||
loop {
|
||||
let mut name = OsString::with_capacity(stem.len() + dot_ext.len() + 5);
|
||||
name.push(&stem);
|
||||
|
||||
if append {
|
||||
name.push(&dot_ext);
|
||||
name.push("_");
|
||||
name.push(i.to_string());
|
||||
} else {
|
||||
name.push("_");
|
||||
name.push(i.to_string());
|
||||
name.push(&dot_ext);
|
||||
}
|
||||
|
||||
p.set_file_name(name);
|
||||
match fs::symlink_metadata(&p).await {
|
||||
Ok(_) => {}
|
||||
Ok(_) => i += 1,
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => break,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
|
||||
let mut name = OsString::with_capacity(stem.len() + ext.len() + 5);
|
||||
name.push(&stem);
|
||||
name.push("_");
|
||||
name.push(i.to_string());
|
||||
name.push(&ext);
|
||||
|
||||
p.set_file_name(name);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
u.set_loc(Loc::from(u.base(), p));
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue