From 3ee1209d3c6e07d1c81dc6ae4a27dc745180979f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Tue, 10 Sep 2024 00:41:33 +0800 Subject: [PATCH] perf: introduce URN to speed up large directory file sorting (#1622) --- yazi-adapter/src/ueberzug.rs | 1 - yazi-core/src/manager/commands/bulk_rename.rs | 1 + yazi-core/src/manager/commands/hover.rs | 4 +- yazi-core/src/manager/commands/open.rs | 2 +- yazi-core/src/manager/commands/tab_create.rs | 2 +- .../src/manager/commands/update_files.rs | 8 +- .../src/manager/commands/update_paged.rs | 2 +- yazi-core/src/manager/manager.rs | 2 +- yazi-core/src/manager/watcher.rs | 7 +- yazi-core/src/tab/commands/cd.rs | 14 +-- yazi-core/src/tab/commands/escape.rs | 4 +- yazi-core/src/tab/commands/leave.rs | 4 +- yazi-core/src/tab/commands/search.rs | 7 +- yazi-core/src/tab/commands/select_all.rs | 2 +- yazi-core/src/tab/history.rs | 26 +++++ yazi-core/src/tab/mod.rs | 2 + yazi-core/src/tab/tab.rs | 17 ++-- yazi-fm/src/lives/file.rs | 14 +-- yazi-fm/src/lives/folder.rs | 2 +- yazi-fm/src/lives/tab.rs | 10 +- yazi-fs/src/files.rs | 26 ++--- yazi-fs/src/folder.rs | 8 +- yazi-fs/src/sorter.rs | 6 +- yazi-plugin/preset/compat.lua | 47 +-------- yazi-plugin/preset/components/root.lua | 1 - yazi-plugin/src/external/fd.rs | 2 +- yazi-plugin/src/external/rg.rs | 2 +- yazi-plugin/src/file/file.rs | 14 ++- yazi-plugin/src/fs/fs.rs | 5 +- yazi-shared/src/fs/file.rs | 68 +++++++++---- yazi-shared/src/fs/loc.rs | 96 +++++++++++++++++++ yazi-shared/src/fs/mod.rs | 2 + yazi-shared/src/fs/op.rs | 49 +++------- yazi-shared/src/fs/url.rs | 21 ++-- 34 files changed, 283 insertions(+), 195 deletions(-) create mode 100644 yazi-core/src/tab/history.rs create mode 100644 yazi-shared/src/fs/loc.rs diff --git a/yazi-adapter/src/ueberzug.rs b/yazi-adapter/src/ueberzug.rs index 79155c39d..f235bd438 100644 --- a/yazi-adapter/src/ueberzug.rs +++ b/yazi-adapter/src/ueberzug.rs @@ -82,7 +82,6 @@ impl Ueberzug { } fn create_demon(adapter: Adapter) -> Result { - // TODO: demon let result = Command::new("ueberzugpp") .args(["layer", "-so", &adapter.to_string()]) .env("SPDLOG_LEVEL", if cfg!(debug_assertions) { "debug" } else { "" }) diff --git a/yazi-core/src/manager/commands/bulk_rename.rs b/yazi-core/src/manager/commands/bulk_rename.rs index ce25e5d34..a6c61ceed 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/manager/commands/hover.rs b/yazi-core/src/manager/commands/hover.rs index 215218e4f..691ce24ba 100644 --- a/yazi-core/src/manager/commands/hover.rs +++ b/yazi-core/src/manager/commands/hover.rs @@ -40,9 +40,9 @@ impl Manager { // Refresh watcher let mut to_watch = HashSet::with_capacity(3 * self.tabs.len()); for tab in self.tabs.iter() { - to_watch.insert(&tab.current.cwd); + to_watch.insert(tab.cwd()); if let Some(ref p) = tab.parent { - to_watch.insert(&p.cwd); + to_watch.insert(&p.loc); } if let Some(h) = tab.current.hovered().filter(|&h| h.is_dir()) { to_watch.insert(h.url()); diff --git a/yazi-core/src/manager/commands/open.rs b/yazi-core/src/manager/commands/open.rs index 2594773a9..76ed32050 100644 --- a/yazi-core/src/manager/commands/open.rs +++ b/yazi-core/src/manager/commands/open.rs @@ -111,7 +111,7 @@ impl Manager { let find = |folder: Option<&Folder>| { folder.is_some_and(|folder| { - folder.cwd == p && folder.files.iter().any(|f| f.is_dir() && url == f.url()) + p == *folder.loc && folder.files.iter().any(|f| f.is_dir() && url == f.url()) }) }; diff --git a/yazi-core/src/manager/commands/tab_create.rs b/yazi-core/src/manager/commands/tab_create.rs index d1ca7ba1e..d19526a76 100644 --- a/yazi-core/src/manager/commands/tab_create.rs +++ b/yazi-core/src/manager/commands/tab_create.rs @@ -46,7 +46,7 @@ impl Tabs { } else { tab.conf = self.active().conf.clone(); tab.apply_files_attrs(); - tab.cd(self.active().current.cwd.clone()); + tab.cd(self.active().cwd().clone()); } self.items.insert(self.cursor + 1, tab); diff --git a/yazi-core/src/manager/commands/update_files.rs b/yazi-core/src/manager/commands/update_files.rs index aabb6b0c2..b0cf29a89 100644 --- a/yazi-core/src/manager/commands/update_files.rs +++ b/yazi-core/src/manager/commands/update_files.rs @@ -47,9 +47,9 @@ impl Manager { let url = op.url(); tab.selected.apply_op(&op); - if tab.current.cwd == *url { + if url == tab.cwd() { Self::update_current(tab, op, tasks); - } else if matches!(&tab.parent, Some(p) if p.cwd == *url) { + } else if matches!(&tab.parent, Some(p) if url == &*p.loc) { Self::update_parent(tab, op); } else if matches!(tab.current.hovered(), Some(h) if url == h.url()) { Self::update_hovered(tab, op); @@ -59,7 +59,7 @@ impl Manager { } fn update_parent(tab: &mut Tab, op: Cow) { - let cwd = tab.current.cwd.clone(); + let cwd = tab.cwd().clone(); let leave = matches!(*op, FilesOp::Deleting(_, ref urls) if urls.contains(&cwd)); if let Some(f) = tab.parent.as_mut() { @@ -108,7 +108,7 @@ impl Manager { } fn update_history(tab: &mut Tab, op: Cow) { - let leave = tab.parent.as_ref().and_then(|f| f.cwd.parent_url().map(|p| (&f.cwd, p))).is_some_and( + let leave = tab.parent.as_ref().and_then(|f| f.loc.parent_url().map(|p| (&f.loc, p))).is_some_and( |(p, pp)| matches!(*op, FilesOp::Deleting(ref parent, ref urls) if *parent == pp && urls.contains(p)), ); diff --git a/yazi-core/src/manager/commands/update_paged.rs b/yazi-core/src/manager/commands/update_paged.rs index 6c28791b9..b6763b1df 100644 --- a/yazi-core/src/manager/commands/update_paged.rs +++ b/yazi-core/src/manager/commands/update_paged.rs @@ -27,7 +27,7 @@ impl Manager { return; }; - if opt.only_if.is_some_and(|u| u != self.current().cwd) { + if opt.only_if.is_some_and(|u| u != *self.active().cwd()) { return; } diff --git a/yazi-core/src/manager/manager.rs b/yazi-core/src/manager/manager.rs index 0b5545f8d..d7c9f98ec 100644 --- a/yazi-core/src/manager/manager.rs +++ b/yazi-core/src/manager/manager.rs @@ -41,7 +41,7 @@ impl Manager { impl Manager { #[inline] - pub fn cwd(&self) -> &Url { &self.current().cwd } + pub fn cwd(&self) -> &Url { &self.current().loc } #[inline] pub fn active(&self) -> &Tab { self.tabs.active() } diff --git a/yazi-core/src/manager/watcher.rs b/yazi-core/src/manager/watcher.rs index e01159a83..9be552fd8 100644 --- a/yazi-core/src/manager/watcher.rs +++ b/yazi-core/src/manager/watcher.rs @@ -60,8 +60,11 @@ impl Watcher { } pub(super) fn trigger_dirs(&self, folders: &[&Folder]) { - let todo: Vec<_> = - folders.iter().filter(|&f| f.cwd.is_regular()).map(|&f| (f.cwd.clone(), f.cha)).collect(); + let todo: Vec<_> = folders + .iter() + .filter(|&f| f.loc.is_regular()) + .map(|&f| (f.loc.url().clone(), f.cha)) + .collect(); if todo.is_empty() { return; } diff --git a/yazi-core/src/tab/commands/cd.rs b/yazi-core/src/tab/commands/cd.rs index dfa148af4..5cebaf096 100644 --- a/yazi-core/src/tab/commands/cd.rs +++ b/yazi-core/src/tab/commands/cd.rs @@ -39,25 +39,25 @@ impl Tab { return self.cd_interactive(); } - if self.current.cwd == opt.target { + if opt.target == *self.cwd() { return; } // Take parent to history if let Some(rep) = self.parent.take() { - self.history.insert(rep.cwd.clone(), rep); + self.history.insert(rep.loc.url().clone(), rep); } // Current - let rep = self.history_new(&opt.target); + let rep = self.history.remove_or(&opt.target); let rep = mem::replace(&mut self.current, rep); - if rep.cwd.is_regular() { - self.history.insert(rep.cwd.clone(), rep); + if rep.loc.is_regular() { + self.history.insert(rep.loc.url().clone(), rep); } // Parent if let Some(parent) = opt.target.parent_url() { - self.parent = Some(self.history_new(&parent)); + self.parent = Some(self.history.remove_or(&parent)); } // Backstack @@ -65,7 +65,7 @@ impl Tab { self.backstack.push(opt.target.clone()); } - Pubsub::pub_from_cd(self.idx, &self.current.cwd); + Pubsub::pub_from_cd(self.idx, self.cwd()); ManagerProxy::refresh(); render!(); } diff --git a/yazi-core/src/tab/commands/escape.rs b/yazi-core/src/tab/commands/escape.rs index a363c4bc7..4f61306c9 100644 --- a/yazi-core/src/tab/commands/escape.rs +++ b/yazi-core/src/tab/commands/escape.rs @@ -92,7 +92,7 @@ impl Tab { } pub fn escape_search(&mut self) -> bool { - let b = self.current.cwd.is_search(); + let b = self.cwd().is_search(); self.search_stop(); render_and!(b) @@ -108,7 +108,7 @@ impl Tab { let urls: Vec<_> = indices.into_iter().filter_map(|i| self.current.files.get(i)).map(|f| f.url()).collect(); - let same = !self.current.cwd.is_search(); + let same = !self.cwd().is_search(); if !select { self.selected.remove_many(&urls, same); } else if self.selected.add_many(&urls, same) != urls.len() { diff --git a/yazi-core/src/tab/commands/leave.rs b/yazi-core/src/tab/commands/leave.rs index 85d96fe65..440614f1c 100644 --- a/yazi-core/src/tab/commands/leave.rs +++ b/yazi-core/src/tab/commands/leave.rs @@ -16,8 +16,8 @@ impl Tab { .current .hovered() .and_then(|h| h.parent()) - .filter(|p| *p != self.current.cwd) - .or_else(|| self.current.cwd.parent_url()) + .filter(|p| p != self.cwd()) + .or_else(|| self.cwd().parent_url()) .map(|u| self.cd(u)); } } diff --git a/yazi-core/src/tab/commands/search.rs b/yazi-core/src/tab/commands/search.rs index b516baf39..b7a73c1f4 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.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(), @@ -82,8 +81,8 @@ impl Tab { if let Some(handle) = self.search.take() { handle.abort(); } - if self.current.cwd.is_search() { - let rep = self.history_new(&self.current.cwd.to_regular()); + if self.cwd().is_search() { + let rep = self.history.remove_or(&self.cwd().to_regular()); drop(mem::replace(&mut self.current, rep)); ManagerProxy::refresh(); } diff --git a/yazi-core/src/tab/commands/select_all.rs b/yazi-core/src/tab/commands/select_all.rs index e01391bb8..4a60a646c 100644 --- a/yazi-core/src/tab/commands/select_all.rs +++ b/yazi-core/src/tab/commands/select_all.rs @@ -31,7 +31,7 @@ impl Tab { None => iter.partition(|&u| self.selected.contains_key(u)), }; - let same = !self.current.cwd.is_search(); + let same = !self.cwd().is_search(); render!(self.selected.remove_many(&removal, same) > 0); let added = self.selected.add_many(&addition, same); diff --git a/yazi-core/src/tab/history.rs b/yazi-core/src/tab/history.rs new file mode 100644 index 000000000..39fba485c --- /dev/null +++ b/yazi-core/src/tab/history.rs @@ -0,0 +1,26 @@ +use std::{collections::HashMap, ops::{Deref, DerefMut}}; + +use yazi_fs::Folder; +use yazi_shared::fs::Url; + +#[derive(Default)] +pub struct History(HashMap); + +impl Deref for History { + type Target = HashMap; + + #[inline] + fn deref(&self) -> &Self::Target { &self.0 } +} + +impl DerefMut for History { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } +} + +impl History { + #[inline] + pub fn remove_or(&mut self, url: &Url) -> Folder { + self.0.remove(url).unwrap_or_else(|| Folder::from(url)) + } +} diff --git a/yazi-core/src/tab/mod.rs b/yazi-core/src/tab/mod.rs index 08d7a6163..0e7d4e69e 100644 --- a/yazi-core/src/tab/mod.rs +++ b/yazi-core/src/tab/mod.rs @@ -2,6 +2,7 @@ mod backstack; mod commands; mod config; mod finder; +mod history; mod mode; mod preview; mod selected; @@ -10,6 +11,7 @@ mod tab; pub use backstack::*; pub use config::*; pub use finder::*; +pub use history::*; pub use mode::*; pub use preview::*; pub use selected::*; diff --git a/yazi-core/src/tab/tab.rs b/yazi-core/src/tab/tab.rs index fc987a127..1b4430293 100644 --- a/yazi-core/src/tab/tab.rs +++ b/yazi-core/src/tab/tab.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, iter}; +use std::iter; use anyhow::Result; use ratatui::layout::Rect; @@ -8,7 +8,7 @@ use yazi_config::{popup::{Origin, Position}, LAYOUT}; use yazi_fs::{Folder, FolderStage}; use yazi_shared::{fs::Url, render}; -use super::{Backstack, Config, Finder, Mode, Preview}; +use super::{Backstack, Config, Finder, History, Mode, Preview}; use crate::tab::Selected; #[derive(Default)] @@ -20,7 +20,7 @@ pub struct Tab { pub parent: Option, pub backstack: Backstack, - pub history: HashMap, + pub history: History, pub selected: Selected, pub preview: Preview, @@ -38,6 +38,9 @@ impl Tab { impl Tab { // --- Current + #[inline] + pub fn cwd(&self) -> &Url { &self.current.loc } + pub fn hovered_rect(&self) -> Option { let y = self.current.files.position(self.current.hovered()?.url())? - self.current.offset; @@ -83,10 +86,6 @@ impl Tab { } // --- History - #[inline] - pub fn history_new(&mut self, url: &Url) -> Folder { - self.history.remove(url).unwrap_or_else(|| Folder::from(url)) - } #[inline] pub fn hovered_folder(&self) -> Option<&Folder> { @@ -113,8 +112,8 @@ impl Tab { apply(parent); // The parent should always track the CWD - parent.hover(&self.current.cwd); - parent.tracing = parent.hovered().map(|h| h.url()) == Some(&self.current.cwd); + parent.hover(&self.current.loc); + parent.tracing = parent.hovered().map(|h| h.url()) == Some(&self.current.loc); } self diff --git a/yazi-fm/src/lives/file.rs b/yazi-fm/src/lives/file.rs index 5beba0571..c8fafa4ff 100644 --- a/yazi-fm/src/lives/file.rs +++ b/yazi-fm/src/lives/file.rs @@ -46,11 +46,11 @@ impl File { Ok(cx.manager.mimetype.get(me.url()).cloned()) }); reg.add_method("prefix", |lua, me, ()| { - if !me.folder().cwd.is_search() { + if !me.folder().loc.is_search() { return Ok(None); } - let mut p = me.url().strip_prefix(&me.folder().cwd).unwrap_or(me.url()).components(); + let mut p = me.url().strip_prefix(&me.folder().loc).unwrap_or(me.url()).components(); p.next_back(); Some(lua.create_string(p.as_path().as_os_str().as_encoded_bytes())).transpose() }); @@ -77,7 +77,7 @@ impl File { }); reg.add_method("is_marked", |_, me, ()| { use yazi_core::tab::Mode::*; - if !me.tab().mode.is_visual() || me.folder().cwd != me.tab().current.cwd { + if !me.tab().mode.is_visual() || me.folder().loc != me.tab().current.loc { return Ok(0u8); } @@ -89,11 +89,11 @@ impl File { }); reg.add_method("is_selected", |_, me, ()| Ok(me.tab().selected.contains_key(me.url()))); reg.add_method("in_parent", |_, me, ()| { - Ok(me.tab().parent.as_ref().is_some_and(|f| me.folder().cwd == f.cwd)) + Ok(me.tab().parent.as_ref().is_some_and(|f| me.folder().loc == f.loc)) }); - reg.add_method("in_current", |_, me, ()| Ok(me.folder().cwd == me.tab().current.cwd)); + reg.add_method("in_current", |_, me, ()| Ok(me.folder().loc == me.tab().current.loc)); reg.add_method("in_preview", |_, me, ()| { - Ok(me.tab().current.hovered().is_some_and(|f| me.folder().cwd == *f.url())) + Ok(me.tab().current.hovered().is_some_and(|f| f.url() == &*me.folder().loc)) }); reg.add_method("found", |lua, me, ()| { let cx = lua.named_registry_value::("cx")?; @@ -113,7 +113,7 @@ impl File { let Some(finder) = &cx.manager.active().finder else { return Ok(None); }; - if me.folder().cwd != me.tab().current.cwd { + if me.folder().loc != me.tab().current.loc { return Ok(None); } let Some(h) = finder.filter.highlighted(me.name()) else { diff --git a/yazi-fm/src/lives/folder.rs b/yazi-fm/src/lives/folder.rs index bbe3f8f5b..8d1e06e37 100644 --- a/yazi-fm/src/lives/folder.rs +++ b/yazi-fm/src/lives/folder.rs @@ -38,7 +38,7 @@ impl Folder { pub(super) fn register(lua: &Lua) -> mlua::Result<()> { lua.register_userdata_type::(|reg| { - reg.add_field_method_get("cwd", |lua, me| Url::cast(lua, me.cwd.clone())); + reg.add_field_method_get("cwd", |lua, me| Url::cast(lua, me.loc.url().clone())); reg.add_field_method_get("files", |_, me| Files::make(0..me.files.len(), me, me.tab())); reg.add_field_method_get("stage", |lua, me| lua.create_any_userdata(me.stage)); reg.add_field_method_get("window", |_, me| Files::make(me.window.clone(), me, me.tab())); diff --git a/yazi-fm/src/lives/tab.rs b/yazi-fm/src/lives/tab.rs index df622d012..ebdf56f18 100644 --- a/yazi-fm/src/lives/tab.rs +++ b/yazi-fm/src/lives/tab.rs @@ -24,15 +24,7 @@ impl Tab { pub(super) fn register(lua: &Lua) -> mlua::Result<()> { lua.register_userdata_type::(|reg| { reg.add_method("name", |lua, me, ()| { - Some( - lua.create_string( - me.current - .cwd - .file_name() - .map_or(me.current.cwd.as_os_str().as_encoded_bytes(), |n| n.as_encoded_bytes()), - ), - ) - .transpose() + Some(lua.create_string(me.current.loc.name().as_encoded_bytes())).transpose() }); reg.add_field_method_get("mode", |_, me| Mode::make(&me.mode)); diff --git a/yazi-fs/src/files.rs b/yazi-fs/src/files.rs index 95c9f76e2..583de41ac 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 { @@ -66,18 +66,18 @@ 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 { Ok(meta) => File::from_meta(url, meta).await, @@ -238,7 +238,7 @@ impl Files { ($dist:expr, $src:expr, $inc:literal) => { let len = $dist.len(); - $dist.retain(|f| !$src.remove(&f.url)); + $dist.retain(|f| !$src.remove(f.url())); if $dist.len() != len { self.revision += $inc; } diff --git a/yazi-fs/src/folder.rs b/yazi-fs/src/folder.rs index f521e179b..8ddaf272f 100644 --- a/yazi-fs/src/folder.rs +++ b/yazi-fs/src/folder.rs @@ -2,14 +2,14 @@ use std::mem; use yazi_config::{LAYOUT, MANAGER}; use yazi_proxy::ManagerProxy; -use yazi_shared::fs::{Cha, File, FilesOp, Url}; +use yazi_shared::fs::{Cha, File, FilesOp, Loc, Url}; use super::FolderStage; use crate::{Files, Step}; #[derive(Default)] pub struct Folder { - pub cwd: Url, + pub loc: Loc, pub cha: Cha, pub files: Files, pub stage: FolderStage, @@ -22,7 +22,7 @@ pub struct Folder { } impl From<&Url> for Folder { - fn from(cwd: &Url) -> Self { Self { cwd: cwd.clone(), ..Default::default() } } + fn from(cwd: &Url) -> Self { Self { loc: Loc::from(cwd.clone()), ..Default::default() } } } impl Folder { @@ -101,7 +101,7 @@ impl Folder { let new = self.cursor / limit; if mem::replace(&mut self.page, new) != new || force { - ManagerProxy::update_paged_by(new, &self.cwd); + ManagerProxy::update_paged_by(new, &self.loc); } } diff --git a/yazi-fs/src/sorter.rs b/yazi-fs/src/sorter.rs index 9c9451479..e50cdb7b5 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/preset/compat.lua b/yazi-plugin/preset/compat.lua index bc76fcc98..4ba280517 100644 --- a/yazi-plugin/preset/compat.lua +++ b/yazi-plugin/preset/compat.lua @@ -1,46 +1 @@ --- TODO: remove this after 0.3.0 release - -Manager = {} -Folder = {} -File = {} - -local function warn(name) - ya.notify { - title = "Deprecated API", - content = string.format( - [[The `%s` global variable has been removed in Yazi v0.3, please remove it from your `init.lua`. - -See https://github.com/sxyazi/yazi/pull/1257 for details.]], - name - ), - timeout = 20, - level = "warn", - } -end - -local b1, b2, b3 = false, false, false -function __yazi_check_and_warn_deprecated_api() - if not b1 then - for _ in pairs(Manager) do - b1 = true - warn("Manager") - break - end - end - - if not b2 then - for _ in pairs(Folder) do - b2 = true - warn("Folder") - break - end - end - - if not b3 then - for _ in pairs(File) do - b3 = true - warn("File") - break - end - end -end +-- diff --git a/yazi-plugin/preset/components/root.lua b/yazi-plugin/preset/components/root.lua index 555bb2b7d..61fc6cd0e 100644 --- a/yazi-plugin/preset/components/root.lua +++ b/yazi-plugin/preset/components/root.lua @@ -34,7 +34,6 @@ function Root:render() for _, child in ipairs(self._children) do children = ya.list_merge(children, ya.render_with(child)) end - __yazi_check_and_warn_deprecated_api() -- TODO: remove this after 0.3.0 release return children end diff --git a/yazi-plugin/src/external/fd.rs b/yazi-plugin/src/external/fd.rs index 74aaca151..b26ae6aac 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 7ff71f2c5..72e0d3bb4 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/file/file.rs b/yazi-plugin/src/file/file.rs index 877aab3f2..ae71b755a 100644 --- a/yazi-plugin/src/file/file.rs +++ b/yazi-plugin/src/file/file.rs @@ -1,5 +1,6 @@ use mlua::{AnyUserData, Lua, Table, UserDataFields, UserDataMethods, UserDataRef, UserDataRegistry}; use yazi_config::THEME; +use yazi_shared::fs::Loc; use crate::{bindings::{Cast, Icon}, cha::Cha, url::Url}; @@ -48,14 +49,11 @@ impl File { lua.globals().raw_set( "File", lua.create_function(|lua, t: Table| { - // FIXME - todo!(); - Ok(()) - // Self::cast(lua, yazi_shared::fs::File { - // cha: t.raw_get::<_, AnyUserData>("cha")?.take()?, - // url: t.raw_get::<_, AnyUserData>("url")?.take()?, - // ..Default::default() - // }) + Self::cast(lua, yazi_shared::fs::File { + loc: Loc::from(t.raw_get::<_, AnyUserData>("url")?.take()?), + cha: t.raw_get::<_, AnyUserData>("cha")?.take()?, + ..Default::default() + }) })?, ) } diff --git a/yazi-plugin/src/fs/fs.rs b/yazi-plugin/src/fs/fs.rs index 23919cc01..13e5fb3d5 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), }; @@ -97,7 +97,6 @@ pub fn install(lua: &Lua) -> mlua::Result<()> { } else { yazi_shared::fs::File::from_dummy(url, next.file_type().await.ok()) }; - files.push(File::cast(lua, file)?); } diff --git a/yazi-shared/src/fs/file.rs b/yazi-shared/src/fs/file.rs index 79153ba4b..e8780f5c0 100644 --- a/yazi-shared/src/fs/file.rs +++ b/yazi-shared/src/fs/file.rs @@ -1,14 +1,15 @@ -use std::{cell::Cell, ffi::OsStr, fs::{FileType, Metadata}, ops::Deref}; +use std::{cell::Cell, ffi::OsStr, fs::{FileType, Metadata}, ops::Deref, path::Path}; use anyhow::Result; use tokio::fs; +use super::Loc; use crate::{fs::{Cha, ChaKind, Url}, theme::IconCache}; #[derive(Clone, Debug, Default)] pub struct File { + pub loc: Loc, pub cha: Cha, - pub url: Url, pub link_to: Option, pub icon: Cell, } @@ -32,13 +33,45 @@ impl File { Ok(Self::from_meta(url, meta).await) } - pub async fn from_meta(url: Url, mut meta: Metadata) -> Self { + #[inline] + pub async fn from_search(cwd: &Url, url: Url) -> Result { + let loc = Loc::from_search(cwd, url); + let meta = fs::symlink_metadata(loc.url()).await?; + Ok(Self::from_loc(loc, meta).await) + } + + #[inline] + pub async fn from_meta(url: Url, meta: Metadata) -> Self { + Self::from_loc(Loc::from(url), meta).await + } + + #[inline] + pub fn from_dummy(url: Url, ft: Option) -> Self { + Self { + loc: Loc::from(url), + cha: ft.map_or_else(Cha::dummy, Cha::from), + link_to: None, + icon: Default::default(), + } + } + + #[inline] + pub fn rebase(&self, parent: &Url) -> Self { + Self { + loc: self.loc.rebase(parent), + cha: self.cha, + link_to: self.link_to.clone(), + icon: Default::default(), + } + } + + async fn from_loc(loc: Loc, mut meta: Metadata) -> Self { 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 +81,7 @@ impl File { } #[cfg(unix)] - if url.is_hidden() { + if loc.url().is_hidden() { ck |= ChaKind::HIDDEN; } #[cfg(windows)] @@ -59,26 +92,27 @@ 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() } + Self { loc, cha: Cha::from(meta).with_kind(ck), link_to, icon: Default::default() } } } impl File { - // --- Url + // --- Loc + #[inline] + pub fn url(&self) -> &Url { self.loc.url() } + + #[inline] + pub fn url_owned(&self) -> Url { self.url().clone() } + #[inline] - pub fn url_owned(&self) -> Url { self.url.clone() } + pub fn urn(&self) -> &Path { self.loc.urn() } #[inline] - pub fn name(&self) -> Option<&OsStr> { self.url.file_name() } + pub fn name(&self) -> &OsStr { self.loc.name() } #[inline] - pub fn stem(&self) -> Option<&OsStr> { self.url.file_stem() } + pub fn stem(&self) -> Option<&OsStr> { self.url().file_stem() } #[inline] - pub fn parent(&self) -> Option { self.url.parent_url() } + pub fn parent(&self) -> Option { self.url().parent_url() } } diff --git a/yazi-shared/src/fs/loc.rs b/yazi-shared/src/fs/loc.rs new file mode 100644 index 000000000..9e9e36e14 --- /dev/null +++ b/yazi-shared/src/fs/loc.rs @@ -0,0 +1,96 @@ +use std::{ffi::OsStr, fmt::{self, Debug, Formatter}, ops::Deref, path::Path}; + +use super::Url; + +pub struct Loc { + url: Url, + urn: *const OsStr, + name: *const OsStr, +} + +unsafe impl Send for Loc {} + +impl Default for Loc { + fn default() -> Self { Self { url: Url::default(), urn: OsStr::new(""), name: OsStr::new("") } } +} + +impl Deref for Loc { + type Target = Url; + + fn deref(&self) -> &Self::Target { &self.url } +} + +impl AsRef for Loc { + fn as_ref(&self) -> &Path { self.url() } +} + +impl Eq for Loc {} + +impl PartialEq for Loc { + fn eq(&self, other: &Self) -> bool { + self.url == other.url && self.urn() == other.urn() && self.name() == other.name() + } +} + +impl Clone for Loc { + fn clone(&self) -> Self { + let url = self.url.clone(); + let name = url.file_name().unwrap_or(OsStr::new("")) as *const OsStr; + let urn = if url.is_search() { self.twin_urn(&url) } else { name }; + Self { url, urn, name } + } +} + +impl Debug for Loc { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.debug_struct("Loc") + .field("url", &self.url) + .field("urn", &self.urn()) + .field("name", &self.name()) + .finish() + } +} + +impl Loc { + pub fn from(url: Url) -> Self { + let urn = url.file_name().unwrap_or(OsStr::new("")) as *const OsStr; + Self { url, urn, name: urn } + } + + pub fn from_search(cwd: &Url, url: Url) -> Self { + let urn = url.strip_prefix(cwd).unwrap_or(&url).as_os_str() as *const OsStr; + let name = url.file_name().unwrap_or(OsStr::new("")) as *const OsStr; + Self { url, urn, name } + } + + pub fn rebase(&self, parent: &Url) -> Self { + let url = parent.join(self.name()); + let name = url.file_name().unwrap_or(OsStr::new("")) as *const OsStr; + let urn = if url.is_search() { self.twin_urn(&url) } else { name }; + Self { url, urn, name } + } + + #[inline] + fn twin_urn<'a>(&self, new: &'a Url) -> &'a OsStr { + let total = new.components().count(); + let take = self.urn().components().count(); + + let mut it = new.components(); + for _ in 0..total - take { + it.next().unwrap(); + } + + it.as_path().as_os_str() + } +} + +impl Loc { + #[inline] + pub fn url(&self) -> &Url { &self.url } + + #[inline] + pub fn urn(&self) -> &Path { Path::new(unsafe { &*self.urn }) } + + #[inline] + pub fn name(&self) -> &OsStr { unsafe { &*self.name } } +} diff --git a/yazi-shared/src/fs/mod.rs b/yazi-shared/src/fs/mod.rs index 9cd7865e8..059abbb89 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 loc; mod op; mod path; mod url; @@ -8,6 +9,7 @@ mod url; pub use cha::*; pub use file::*; pub use fns::*; +pub use loc::*; pub use op::*; pub use path::*; pub use url::*; diff --git a/yazi-shared/src/fs/op.rs b/yazi-shared/src/fs/op.rs index 3a0d98032..32338506c 100644 --- a/yazi-shared/src/fs/op.rs +++ b/yazi-shared/src/fs/op.rs @@ -48,51 +48,28 @@ impl FilesOp { } pub fn chroot(&self, new: &Url) -> Self { - let old = self.url(); macro_rules! new { - ($url:expr) => {{ new.join($url.strip_prefix(old).unwrap()) }}; + ($url:expr) => {{ new.join($url.file_name().unwrap()) }}; } macro_rules! files { - ($files:expr) => {{ - $files - .iter() - .map(|file| { - let mut f = file.clone(); - // FIXME - todo!(); - // f.url = new!(f.url); - f - }) - .collect() - }}; + ($files:expr) => {{ $files.iter().map(|f| f.rebase(new)).collect() }}; } macro_rules! map { - ($map:expr) => {{ - $map - .iter() - .map(|(k, v)| { - let mut f = v.clone(); - // FIXME - todo!(); - // f.url = new!(f.url); - (new!(k), f) - }) - .collect() - }}; + ($map:expr) => {{ $map.iter().map(|(u, f)| (new!(u), f.rebase(new))).collect() }}; } - let u = new.clone(); + let n = new.clone(); match self { - Self::Full(_, files, mtime) => Self::Full(u, files!(files), *mtime), - Self::Part(_, files, ticket) => Self::Part(u, files!(files), *ticket), - Self::Done(_, mtime, ticket) => Self::Done(u, *mtime, *ticket), - Self::Size(_, map) => Self::Size(u, map.iter().map(|(k, v)| (new!(k), *v)).collect()), - Self::IOErr(_, err) => Self::IOErr(u, *err), + Self::Full(_, files, mtime) => Self::Full(n, files!(files), *mtime), + Self::Part(_, files, ticket) => Self::Part(n, files!(files), *ticket), + Self::Done(_, mtime, ticket) => Self::Done(n, *mtime, *ticket), + Self::Size(_, map) => Self::Size(n, map.iter().map(|(u, &s)| (new!(u), s)).collect()), + Self::IOErr(_, err) => Self::IOErr(n, *err), - Self::Creating(_, files) => Self::Creating(u, files!(files)), - Self::Deleting(_, urls) => Self::Deleting(u, urls.iter().map(|u| new!(u)).collect()), - Self::Updating(_, map) => Self::Updating(u, map!(map)), - Self::Upserting(_, map) => Self::Upserting(u, map!(map)), + Self::Creating(_, files) => Self::Creating(n, files!(files)), + Self::Deleting(_, urls) => Self::Deleting(n, urls.iter().map(|u| new!(u)).collect()), + Self::Updating(_, map) => Self::Updating(n, map!(map)), + Self::Upserting(_, map) => Self::Upserting(n, map!(map)), } } } diff --git a/yazi-shared/src/fs/url.rs b/yazi-shared/src/fs/url.rs index 274931f2b..16574c211 100644 --- a/yazi-shared/src/fs/url.rs +++ b/yazi-shared/src/fs/url.rs @@ -128,7 +128,7 @@ impl Url { let url = Self::from(self.path.join(path)); match self.scheme { UrlScheme::Regular => url, - UrlScheme::Search => url, + UrlScheme::Search => url.into_search(), UrlScheme::Archive => url.into_archive(), } } @@ -161,12 +161,14 @@ impl Url { } impl Url { - // --- Scheme + // --- Regular #[inline] pub fn is_regular(&self) -> bool { self.scheme == UrlScheme::Regular } #[inline] - pub fn to_regular(&self) -> Self { self.clone().into_regular() } + pub fn to_regular(&self) -> Self { + Self { scheme: UrlScheme::Regular, path: self.path.clone(), frag: String::new() } + } #[inline] pub fn into_regular(mut self) -> Self { @@ -175,16 +177,19 @@ impl Url { self } + // --- Search #[inline] 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 { scheme: UrlScheme::Search, path: self.path.clone(), frag: frag.to_owned() } + } #[inline] - pub fn into_search(mut self, frag: String) -> Self { + pub fn into_search(mut self) -> Self { self.scheme = UrlScheme::Search; - self.frag = frag; + self.frag = String::new(); self } @@ -192,7 +197,9 @@ impl Url { pub fn is_archive(&self) -> bool { self.scheme == UrlScheme::Archive } #[inline] - pub fn to_archive(&self) -> Self { self.clone().into_archive() } + pub fn to_archive(&self) -> Self { + Self { scheme: UrlScheme::Archive, path: self.path.clone(), frag: String::new() } + } #[inline] pub fn into_archive(mut self) -> Self {