diff --git a/yazi-core/src/manager/commands/bulk_rename.rs b/yazi-core/src/manager/commands/bulk_rename.rs index ce25e5d3..a6c61cee 100644 --- a/yazi-core/src/manager/commands/bulk_rename.rs +++ b/yazi-core/src/manager/commands/bulk_rename.rs @@ -95,6 +95,7 @@ impl Manager { } } + // FIXME: consider old and new in the different directories if !succeeded.is_empty() { Pubsub::pub_from_bulk(succeeded.iter().map(|(u, f)| (u, f.url())).collect()); FilesOp::Upserting(cwd, succeeded).emit(); diff --git a/yazi-core/src/tab/commands/reveal.rs b/yazi-core/src/tab/commands/reveal.rs index 46df9fb8..652fa4af 100644 --- a/yazi-core/src/tab/commands/reveal.rs +++ b/yazi-core/src/tab/commands/reveal.rs @@ -28,9 +28,12 @@ impl Tab { let Some(parent) = opt.target.parent_url() else { return; }; + let Ok(file) = File::from_dummy(opt.target.clone(), None) else { + return; + }; self.cd(parent.clone()); - FilesOp::Creating(parent, vec![File::from_dummy(opt.target.clone(), None)]).emit(); + FilesOp::Creating(parent, vec![file]).emit(); ManagerProxy::hover(Some(opt.target), self.idx); } } diff --git a/yazi-core/src/tab/commands/search.rs b/yazi-core/src/tab/commands/search.rs index b516baf3..5d8655ad 100644 --- a/yazi-core/src/tab/commands/search.rs +++ b/yazi-core/src/tab/commands/search.rs @@ -44,11 +44,10 @@ impl Tab { handle.abort(); } - let mut cwd = self.current.cwd.clone(); + let cwd = self.current.cwd.to_search(&opt.subject); let hidden = self.conf.show_hidden; self.search = Some(tokio::spawn(async move { - cwd = cwd.into_search(opt.subject.clone()); let rx = if opt.via == SearchOptVia::Rg { external::rg(external::RgOpt { cwd: cwd.clone(), diff --git a/yazi-fs/src/files.rs b/yazi-fs/src/files.rs index 95c9f76e..066ff7d7 100644 --- a/yazi-fs/src/files.rs +++ b/yazi-fs/src/files.rs @@ -45,8 +45,8 @@ impl Deref for Files { } impl Files { - pub async fn from_dir(url: &Url) -> std::io::Result> { - let mut it = fs::read_dir(url).await?; + pub async fn from_dir(dir: &Url) -> std::io::Result> { + let mut it = fs::read_dir(dir).await?; let (tx, rx) = mpsc::unbounded_channel(); tokio::spawn(async move { @@ -55,10 +55,13 @@ impl Files { _ = tx.closed() => break, result = item.metadata() => { let url = Url::from(item.path()); - _ = tx.send(match result { + let file = match result { Ok(meta) => File::from_meta(url, meta).await, Err(_) => File::from_dummy(url, item.file_type().await.ok()) - }); + }; + if let Ok(f) = file { + _ = tx.send(f); + } } } } @@ -66,23 +69,26 @@ impl Files { Ok(rx) } - pub async fn from_dir_bulk(url: &Url) -> std::io::Result> { - let mut it = fs::read_dir(url).await?; - let mut items = Vec::with_capacity(5000); - while let Ok(Some(item)) = it.next_entry().await { - items.push(item); + pub async fn from_dir_bulk(dir: &Url) -> std::io::Result> { + let mut it = fs::read_dir(dir).await?; + let mut entries = Vec::with_capacity(5000); + while let Ok(Some(entry)) = it.next_entry().await { + entries.push(entry); } - let (first, rest) = items.split_at(items.len() / 3); - let (second, third) = rest.split_at(items.len() / 3); - async fn go(entities: &[DirEntry]) -> Vec { - let mut files = Vec::with_capacity(entities.len() / 3 + 1); - for entry in entities { + let (first, rest) = entries.split_at(entries.len() / 3); + let (second, third) = rest.split_at(entries.len() / 3); + async fn go(entries: &[DirEntry]) -> Vec { + let mut files = Vec::with_capacity(entries.len() / 3 + 1); + for entry in entries { let url = Url::from(entry.path()); - files.push(match entry.metadata().await { + let file = match entry.metadata().await { Ok(meta) => File::from_meta(url, meta).await, Err(_) => File::from_dummy(url, entry.file_type().await.ok()), - }); + }; + if let Ok(f) = file { + files.push(f); + } } files } diff --git a/yazi-fs/src/sorter.rs b/yazi-fs/src/sorter.rs index 9c945147..e50cdb7b 100644 --- a/yazi-fs/src/sorter.rs +++ b/yazi-fs/src/sorter.rs @@ -20,10 +20,10 @@ impl FilesSorter { let by_alphabetical = |a: &File, b: &File| { if self.sensitive { - return self.cmp(a.name(), b.name(), self.promote(a, b)); + self.cmp(a.name(), b.name(), self.promote(a, b)) + } else { + self.cmp(a.name().to_ascii_uppercase(), b.name().to_ascii_uppercase(), self.promote(a, b)) } - - self.cmp(a.name().to_ascii_uppercase(), b.name().to_ascii_uppercase(), self.promote(a, b)) }; match self.by { diff --git a/yazi-plugin/src/external/fd.rs b/yazi-plugin/src/external/fd.rs index 74aaca15..b26ae6aa 100644 --- a/yazi-plugin/src/external/fd.rs +++ b/yazi-plugin/src/external/fd.rs @@ -29,7 +29,7 @@ pub fn fd(opt: FdOpt) -> Result> { tokio::spawn(async move { while let Ok(Some(line)) = it.next_line().await { - if let Ok(file) = File::from(opt.cwd.join(line)).await { + if let Ok(file) = File::from_search(&opt.cwd, opt.cwd.join(line)).await { tx.send(file).ok(); } } diff --git a/yazi-plugin/src/external/rg.rs b/yazi-plugin/src/external/rg.rs index 7ff71f2c..72e0d3bb 100644 --- a/yazi-plugin/src/external/rg.rs +++ b/yazi-plugin/src/external/rg.rs @@ -28,7 +28,7 @@ pub fn rg(opt: RgOpt) -> Result> { tokio::spawn(async move { while let Ok(Some(line)) = it.next_line().await { - if let Ok(file) = File::from(opt.cwd.join(line)).await { + if let Ok(file) = File::from_search(&opt.cwd, opt.cwd.join(line)).await { tx.send(file).ok(); } } diff --git a/yazi-plugin/src/fs/fs.rs b/yazi-plugin/src/fs/fs.rs index 23919cc0..5f9dd036 100644 --- a/yazi-plugin/src/fs/fs.rs +++ b/yazi-plugin/src/fs/fs.rs @@ -54,7 +54,7 @@ pub fn install(lua: &Lua) -> mlua::Result<()> { ), ( "read_dir", - lua.create_async_function(|lua, (url, options): (UrlRef, Table)| async move { + lua.create_async_function(|lua, (dir, options): (UrlRef, Table)| async move { let glob = if let Ok(s) = options.raw_get::<_, mlua::String>("glob") { Some( GlobBuilder::new(s.to_str()?) @@ -73,7 +73,7 @@ pub fn install(lua: &Lua) -> mlua::Result<()> { let limit = options.raw_get("limit").unwrap_or(usize::MAX); let resolve = options.raw_get("resolve").unwrap_or(false); - let mut it = match fs::read_dir(&*url).await { + let mut it = match fs::read_dir(&*dir).await { Ok(it) => it, Err(e) => return (Value::Nil, e.raw_os_error()).into_lua_multi(lua), }; @@ -98,7 +98,9 @@ pub fn install(lua: &Lua) -> mlua::Result<()> { yazi_shared::fs::File::from_dummy(url, next.file_type().await.ok()) }; - files.push(File::cast(lua, file)?); + if let Ok(f) = file { + files.push(File::cast(lua, f)?); + } } let tbl = lua.create_table_with_capacity(files.len(), 0)?; diff --git a/yazi-shared/src/fs/file.rs b/yazi-shared/src/fs/file.rs index 79153ba4..6ed06d33 100644 --- a/yazi-shared/src/fs/file.rs +++ b/yazi-shared/src/fs/file.rs @@ -3,12 +3,13 @@ use std::{cell::Cell, ffi::OsStr, fs::{FileType, Metadata}, ops::Deref}; use anyhow::Result; use tokio::fs; +use super::Location; use crate::{fs::{Cha, ChaKind, Url}, theme::IconCache}; #[derive(Clone, Debug, Default)] pub struct File { pub cha: Cha, - pub url: Url, + location: Location, pub link_to: Option, pub icon: Cell, } @@ -29,16 +30,38 @@ impl File { #[inline] pub async fn from(url: Url) -> Result { let meta = fs::symlink_metadata(&url).await?; - Ok(Self::from_meta(url, meta).await) + Self::from_meta(url, meta).await } - pub async fn from_meta(url: Url, mut meta: Metadata) -> Self { + #[inline] + pub async fn from_meta(url: Url, meta: Metadata) -> Result { + Self::from_loc(Location::from(url)?, meta).await + } + + #[inline] + pub fn from_dummy(url: Url, ft: Option) -> Result { + Ok(Self { + cha: ft.map_or_else(Cha::dummy, Cha::from), + location: Location::from(url)?, + link_to: None, + icon: Default::default(), + }) + } + + #[inline] + pub async fn from_search(cwd: &Url, url: Url) -> Result { + let loc = Location::from_search(cwd, url)?; + let meta = fs::symlink_metadata(loc.url()).await?; + Self::from_loc(loc, meta).await + } + + async fn from_loc(loc: Location, mut meta: Metadata) -> Result { let mut ck = ChaKind::empty(); let (is_link, mut link_to) = (meta.is_symlink(), None); if is_link { - meta = fs::metadata(&url).await.unwrap_or(meta); - link_to = fs::read_link(&url).await.map(Url::from).ok(); + meta = fs::metadata(loc.url()).await.unwrap_or(meta); + link_to = fs::read_link(loc.url()).await.map(Url::from).ok(); } if is_link && meta.is_symlink() { @@ -48,7 +71,7 @@ impl File { } #[cfg(unix)] - if url.is_hidden() { + if loc.url().is_hidden() { ck |= ChaKind::HIDDEN; } #[cfg(windows)] @@ -59,26 +82,29 @@ impl File { } } - Self { cha: Cha::from(meta).with_kind(ck), url, link_to, icon: Default::default() } - } - - #[inline] - pub fn from_dummy(url: Url, ft: Option) -> Self { - Self { cha: ft.map_or_else(Cha::dummy, Cha::from), url: url.to_owned(), ..Default::default() } + Ok(Self { + cha: Cha::from(meta).with_kind(ck), + location: loc, + link_to, + icon: Default::default(), + }) } } impl File { - // --- Url + // --- Location #[inline] - pub fn url_owned(&self) -> Url { self.url.clone() } + pub fn url(&self) -> &Url { self.location.url() } #[inline] - pub fn name(&self) -> Option<&OsStr> { self.url.file_name() } + pub fn url_owned(&self) -> Url { self.url().clone() } #[inline] - pub fn stem(&self) -> Option<&OsStr> { self.url.file_stem() } + pub fn name(&self) -> &OsStr { self.location.name() } #[inline] - pub fn parent(&self) -> Option { self.url.parent_url() } + pub fn stem(&self) -> Option<&OsStr> { self.url().file_stem() } + + #[inline] + pub fn parent(&self) -> Option { self.url().parent_url() } } diff --git a/yazi-shared/src/fs/location.rs b/yazi-shared/src/fs/location.rs new file mode 100644 index 00000000..804afcc1 --- /dev/null +++ b/yazi-shared/src/fs/location.rs @@ -0,0 +1,62 @@ +use std::{ffi::OsStr, path::Path}; + +use anyhow::{bail, Result}; + +use super::Url; + +#[derive(Clone, Debug)] +pub(super) struct Location { + url: Url, + urn: *const OsStr, + name: *const OsStr, +} + +unsafe impl Send for Location {} + +impl Default for Location { + fn default() -> Self { + let url = Url::default(); + let urn = url.as_os_str() as *const OsStr; + let name = url.as_os_str() as *const OsStr; + Self { url, urn, name } + } +} + +impl Location { + pub(super) fn from(url: Url) -> Result { + if url.is_search() { + bail!("url is from search results: {url:?}"); + } + let Some(name) = url.file_name() else { + bail!("url has no filename: {url:?}"); + }; + + let urn = name as *const OsStr; + let name = name as *const OsStr; + Ok(Self { url, urn, name }) + } + + pub(super) fn from_search(cwd: &Url, url: Url) -> Result { + if !url.is_search() { + bail!("url is not from search results: {url:?}"); + } + let Some(name) = url.file_name() else { + bail!("url has no filename: {url:?}"); + }; + + let urn = url.strip_prefix(cwd).unwrap_or(&url).as_os_str() as *const OsStr; + let name = name as *const OsStr; + Ok(Self { url, urn, name }) + } +} + +impl Location { + #[inline] + pub(super) fn url(&self) -> &Url { &self.url } + + #[inline] + pub(super) fn urn(&self) -> &Path { Path::new(unsafe { &*self.urn }) } + + #[inline] + pub(super) fn name(&self) -> &OsStr { unsafe { &*self.name } } +} diff --git a/yazi-shared/src/fs/mod.rs b/yazi-shared/src/fs/mod.rs index 9cd7865e..022ca043 100644 --- a/yazi-shared/src/fs/mod.rs +++ b/yazi-shared/src/fs/mod.rs @@ -1,6 +1,7 @@ mod cha; mod file; mod fns; +mod location; mod op; mod path; mod url; @@ -8,6 +9,7 @@ mod url; pub use cha::*; pub use file::*; pub use fns::*; +pub use location::*; pub use op::*; pub use path::*; pub use url::*; diff --git a/yazi-shared/src/fs/url.rs b/yazi-shared/src/fs/url.rs index 274931f2..2d8c506a 100644 --- a/yazi-shared/src/fs/url.rs +++ b/yazi-shared/src/fs/url.rs @@ -179,12 +179,12 @@ impl Url { pub fn is_search(&self) -> bool { self.scheme == UrlScheme::Search } #[inline] - pub fn to_search(&self, frag: String) -> Self { self.clone().into_search(frag) } + pub fn to_search(&self, frag: &str) -> Self { self.clone().into_search(frag) } #[inline] - pub fn into_search(mut self, frag: String) -> Self { + pub fn into_search(mut self, frag: &str) -> Self { self.scheme = UrlScheme::Search; - self.frag = frag; + self.frag = frag.to_owned(); self }