From 28083d805e04de71c6c3b0d9896e2559b828e9b7 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: Mon, 16 Sep 2024 18:55:03 +0800 Subject: [PATCH] perf: introduce URN to speed up large directory locating (#1648) --- Cargo.lock | 8 ++--- Cargo.toml | 6 ++-- yazi-config/preset/keymap.toml | 10 +++--- yazi-core/src/manager/commands/bulk_rename.rs | 2 +- yazi-core/src/manager/commands/hover.rs | 31 +++++++++++------ yazi-core/src/manager/commands/open.rs | 5 +-- yazi-core/src/manager/commands/refresh.rs | 2 +- yazi-core/src/manager/commands/tab_create.rs | 2 +- .../src/manager/commands/update_files.rs | 27 ++++++++------- .../src/manager/commands/update_paged.rs | 2 +- yazi-core/src/manager/manager.rs | 4 +-- yazi-core/src/manager/watcher.rs | 17 ++++------ yazi-core/src/tab/commands/cd.rs | 6 ++-- yazi-core/src/tab/commands/filter.rs | 6 ++-- yazi-core/src/tab/commands/leave.rs | 2 +- yazi-core/src/tab/preview.rs | 14 ++++---- yazi-core/src/tab/selected.rs | 4 +-- yazi-core/src/tab/tab.rs | 13 ++++---- yazi-fm/src/lives/folder.rs | 2 +- yazi-fm/src/lives/tab.rs | 2 +- yazi-fs/src/files.rs | 33 +++++++++---------- yazi-fs/src/folder.rs | 12 +++---- yazi-plugin/src/external/fd.rs | 2 +- yazi-plugin/src/external/rg.rs | 2 +- yazi-plugin/src/file/file.rs | 6 ++-- yazi-shared/src/fs/file.rs | 15 +++++---- yazi-shared/src/fs/loc.rs | 18 ++++++---- yazi-shared/src/fs/mod.rs | 2 ++ yazi-shared/src/fs/op.rs | 24 +++++++------- yazi-shared/src/fs/url.rs | 13 ++++++++ yazi-shared/src/fs/urn.rs | 33 +++++++++++++++++++ 31 files changed, 196 insertions(+), 129 deletions(-) create mode 100644 yazi-shared/src/fs/urn.rs diff --git a/Cargo.lock b/Cargo.lock index dd30387b..0ce0eac5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,9 +135,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.87" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" +checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356" [[package]] name = "arbitrary" @@ -1008,9 +1008,9 @@ checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "globset" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" dependencies = [ "aho-corasick", "bstr", diff --git a/Cargo.toml b/Cargo.toml index 87bf0b7e..3c9322de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ strip = true [workspace.dependencies] ansi-to-tui = "6.0.0" -anyhow = "1.0.86" +anyhow = "1.0.88" arc-swap = "1.7.1" base64 = "0.22.1" bitflags = "2.6.0" @@ -19,7 +19,7 @@ clap = { version = "4.5.17", features = [ "derive" ] } crossterm = { version = "0.28.1", features = [ "event-stream" ] } dirs = "5.0.1" futures = "0.3.30" -globset = "0.4.14" +globset = "0.4.15" libc = "0.2.158" md-5 = "0.10.6" mlua = { version = "0.9.9", features = [ "lua54", "serialize", "macros", "async" ] } @@ -27,7 +27,7 @@ parking_lot = "0.12.3" ratatui = { version = "0.28.1", features = [ "unstable-rendered-line-info" ] } regex = "1.10.6" scopeguard = "1.2.0" -serde = { version = "1.0.209", features = [ "derive" ] } +serde = { version = "1.0.210", features = [ "derive" ] } serde_json = "1.0.128" shell-words = "1.1.0" tokio = { version = "1.40.0", features = [ "full" ] } diff --git a/yazi-config/preset/keymap.toml b/yazi-config/preset/keymap.toml index 7c08299e..adf82fc2 100644 --- a/yazi-config/preset/keymap.toml +++ b/yazi-config/preset/keymap.toml @@ -36,8 +36,8 @@ keymap = [ { on = "h", run = "leave", desc = "Go back to the parent directory" }, { on = "l", run = "enter", desc = "Enter the child directory" }, - { on = "", run = "leave", desc = "Go back to the parent directory" }, - { on = "", run = "enter", desc = "Enter the child directory" }, + { on = "", run = "leave", desc = "Go back to the parent directory" }, + { on = "", run = "enter", desc = "Enter the child directory" }, { on = "H", run = "back", desc = "Go back to the previous directory" }, { on = "L", run = "forward", desc = "Go forward to the next directory" }, @@ -249,9 +249,9 @@ keymap = [ { on = "c", run = "delete --cut --insert", desc = "Cut the selected characters, and enter insert mode" }, { on = "C", run = [ "delete --cut --insert", "move 999" ], desc = "Cut until the EOL, and enter insert mode" }, { on = "x", run = [ "delete --cut", "move 1 --in-operating" ], desc = "Cut the current character" }, - { on = "y", run = "yank", desc = "Copy the selected characters" }, - { on = "p", run = "paste", desc = "Paste the copied characters after the cursor" }, - { on = "P", run = "paste --before", desc = "Paste the copied characters before the cursor" }, + { on = "y", run = "yank", desc = "Copy the selected characters" }, + { on = "p", run = "paste", desc = "Paste the copied characters after the cursor" }, + { on = "P", run = "paste --before", desc = "Paste the copied characters before the cursor" }, # Undo/Redo { on = "u", run = "undo", desc = "Undo the last operation" }, diff --git a/yazi-core/src/manager/commands/bulk_rename.rs b/yazi-core/src/manager/commands/bulk_rename.rs index a6c61cee..2aaafd4c 100644 --- a/yazi-core/src/manager/commands/bulk_rename.rs +++ b/yazi-core/src/manager/commands/bulk_rename.rs @@ -16,7 +16,7 @@ impl Manager { return AppProxy::notify_warn("Bulk rename", "No text opener found"); }; - let cwd = self.cwd().clone(); + let cwd = self.cwd().url_owned(); let old: Vec<_> = self.selected_or_hovered(true).collect(); let root = max_common_root(&old); diff --git a/yazi-core/src/manager/commands/hover.rs b/yazi-core/src/manager/commands/hover.rs index 691ce24b..a3d046a1 100644 --- a/yazi-core/src/manager/commands/hover.rs +++ b/yazi-core/src/manager/commands/hover.rs @@ -1,7 +1,7 @@ -use std::collections::HashSet; +use std::{collections::HashSet, path::PathBuf}; use yazi_dds::Pubsub; -use yazi_shared::{event::{Cmd, Data}, fs::Url, render}; +use yazi_shared::{event::{Cmd, Data}, fs::{Url, Urn}, render}; use crate::manager::Manager; @@ -25,13 +25,10 @@ impl From> for Opt { impl Manager { pub fn hover(&mut self, opt: impl Into) { let opt = opt.into() as Opt; - - // Hover on the file - render!(self.current_or_mut(opt.tab).repos(opt.url.as_ref())); - if opt.url.zip(self.current_or(opt.tab).hovered()).is_some_and(|(u, f)| &u == f.url()) { - // `hover(Some)` occurs after user actions, such as create, rename, reveal, etc. - // At this point, it's intuitive to track the location of this file regardless. - self.current_or_mut(opt.tab).tracing = true; + if let Some(u) = opt.url { + self.hover_do(u, opt.tab); + } else { + self.current_or_mut(opt.tab).repos(None); } // Repeek @@ -40,7 +37,7 @@ 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.cwd()); + to_watch.insert(tab.cwd().url()); if let Some(ref p) = tab.parent { to_watch.insert(&p.loc); } @@ -53,4 +50,18 @@ impl Manager { // Publish through DDS Pubsub::pub_from_hover(self.active().idx, self.hovered().map(|h| h.url())); } + + fn hover_do(&mut self, url: Url, tab: Option) { + // Hover on the file + if let Some(p) = url.strip_prefix(&self.current_or(tab).loc).map(PathBuf::from) { + render!(self.current_or_mut(tab).repos(Some(Urn::new(&p)))); + } + + // Turn on tracing + if self.current_or(tab).hovered().is_some_and(|f| url == *f.url()) { + // `hover(Some)` occurs after user actions, such as create, rename, reveal, etc. + // At this point, it's intuitive to track the location of this file regardless. + self.current_or_mut(tab).tracing = true; + } + } } diff --git a/yazi-core/src/manager/commands/open.rs b/yazi-core/src/manager/commands/open.rs index b0af78bf..5fe8b9ac 100644 --- a/yazi-core/src/manager/commands/open.rs +++ b/yazi-core/src/manager/commands/open.rs @@ -110,8 +110,9 @@ impl Manager { }; let find = |folder: Option<&Folder>| { - folder.is_some_and(|folder| { - p == *folder.loc && folder.files.iter().any(|f| f.is_dir() && url == f.url()) + folder.filter(|&f| p == *f.loc).is_some_and(|folder| { + let loc = url.to_loc(&folder.loc); + folder.files.iter().any(|f| f.is_dir() && f.urn() == loc.urn()) }) }; diff --git a/yazi-core/src/manager/commands/refresh.rs b/yazi-core/src/manager/commands/refresh.rs index 346cd182..6595a015 100644 --- a/yazi-core/src/manager/commands/refresh.rs +++ b/yazi-core/src/manager/commands/refresh.rs @@ -9,7 +9,7 @@ use crate::{manager::Manager, tasks::Tasks}; impl Manager { pub fn refresh(&mut self, _: Cmd, tasks: &Tasks) { env::set_current_dir(self.cwd()).ok(); - env::set_var("PWD", self.cwd()); + env::set_var("PWD", self.cwd().url()); if !MANAGER.title_format.is_empty() { execute!(std::io::stderr(), SetTitle(self.title())).ok(); diff --git a/yazi-core/src/manager/commands/tab_create.rs b/yazi-core/src/manager/commands/tab_create.rs index d19526a7..545f7aac 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().cwd().clone()); + tab.cd(self.active().cwd().url_owned()); } 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 b0cf29a8..8f98b22a 100644 --- a/yazi-core/src/manager/commands/update_files.rs +++ b/yazi-core/src/manager/commands/update_files.rs @@ -25,7 +25,7 @@ impl Manager { }; let mut ops = vec![opt.op]; - for u in LINKED.read().from_dir(ops[0].url()) { + for u in LINKED.read().from_dir(ops[0].cwd()) { ops.push(ops[0].chroot(u)); } @@ -44,10 +44,10 @@ impl Manager { } fn update_tab(tab: &mut Tab, op: Cow, tasks: &Tasks) { - let url = op.url(); + let url = op.cwd(); tab.selected.apply_op(&op); - if url == tab.cwd() { + if url == tab.cwd().url() { Self::update_current(tab, op, tasks); } else if matches!(&tab.parent, Some(p) if url == &*p.loc) { Self::update_parent(tab, op); @@ -59,12 +59,15 @@ impl Manager { } fn update_parent(tab: &mut Tab, op: Cow) { - let cwd = tab.cwd().clone(); - let leave = matches!(*op, FilesOp::Deleting(_, ref urls) if urls.contains(&cwd)); + let urn = tab.cwd().urn_owned(); + // FIXME + let leave = false; + // let leave = matches!(*op, FilesOp::Deleting(_, ref urls) if + // urls.contains(&urn)); if let Some(f) = tab.parent.as_mut() { render!(f.update(op.into_owned())); - render!(f.hover(&cwd)); + render!(f.hover(urn._deref())); } if leave { @@ -73,7 +76,7 @@ impl Manager { } fn update_current(tab: &mut Tab, op: Cow, tasks: &Tasks) { - let hovered = tab.current.hovered().filter(|_| tab.current.tracing).map(|h| h.url_owned()); + let hovered = tab.current.hovered().filter(|_| tab.current.tracing).map(|h| h.urn_owned()); let calc = !matches!(*op, FilesOp::Size(..) | FilesOp::Deleting(..)); let foreign = matches!(op, Cow::Borrowed(_)); @@ -81,7 +84,7 @@ impl Manager { return; } - tab.current.repos(hovered); + tab.current.repos(hovered.as_ref().map(|u| u._deref())); if foreign { return; } @@ -94,7 +97,7 @@ impl Manager { } fn update_hovered(tab: &mut Tab, op: Cow) { - let url = op.url(); + let url = op.cwd(); let folder = tab.history.entry(url.clone()).or_insert_with(|| Folder::from(url)); let foreign = matches!(op, Cow::Borrowed(_)); @@ -112,10 +115,10 @@ impl Manager { |(p, pp)| matches!(*op, FilesOp::Deleting(ref parent, ref urls) if *parent == pp && urls.contains(p)), ); - let folder = tab.history.entry(op.url().clone()).or_insert_with(|| Folder::from(op.url())); - let hovered = folder.hovered().filter(|_| folder.tracing).map(|h| h.url_owned()); + let folder = tab.history.entry(op.cwd().clone()).or_insert_with(|| Folder::from(op.cwd())); + let hovered = folder.hovered().filter(|_| folder.tracing).map(|h| h.urn_owned()); if folder.update(op.into_owned()) { - folder.repos(hovered); + folder.repos(hovered.as_ref().map(|u| u._deref())); } if leave { diff --git a/yazi-core/src/manager/commands/update_paged.rs b/yazi-core/src/manager/commands/update_paged.rs index b6763b1d..60330440 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.active().cwd()) { + if opt.only_if.is_some_and(|u| u != *self.cwd().url()) { return; } diff --git a/yazi-core/src/manager/manager.rs b/yazi-core/src/manager/manager.rs index 246d51bf..5f5afca2 100644 --- a/yazi-core/src/manager/manager.rs +++ b/yazi-core/src/manager/manager.rs @@ -2,7 +2,7 @@ use ratatui::layout::Rect; use yazi_adapter::Dimension; use yazi_config::popup::{Origin, Position}; use yazi_fs::Folder; -use yazi_shared::fs::{File, Url}; +use yazi_shared::fs::{File, Loc, Url}; use super::{Mimetype, Tabs, Watcher, Yanked}; use crate::tab::Tab; @@ -39,7 +39,7 @@ impl Manager { impl Manager { #[inline] - pub fn cwd(&self) -> &Url { &self.current().loc } + pub fn cwd(&self) -> &Loc { self.active().cwd() } #[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 9be552fd..6756361e 100644 --- a/yazi-core/src/manager/watcher.rs +++ b/yazi-core/src/manager/watcher.rs @@ -60,25 +60,22 @@ impl Watcher { } pub(super) fn trigger_dirs(&self, folders: &[&Folder]) { - let todo: Vec<_> = folders - .iter() - .filter(|&f| f.loc.is_regular()) - .map(|&f| (f.loc.url().clone(), f.cha)) - .collect(); + let todo: Vec<_> = + folders.iter().filter(|&f| f.loc.is_regular()).map(|&f| (f.loc.url_owned(), f.cha)).collect(); if todo.is_empty() { return; } - async fn go(url: Url, cha: Cha) { - let Some(cha) = Files::assert_stale(&url, cha).await else { return }; + async fn go(cwd: Url, cha: Cha) { + let Some(cha) = Files::assert_stale(&cwd, cha).await else { return }; - if let Ok(files) = Files::from_dir_bulk(&url).await { - FilesOp::Full(url, files, cha).emit(); + if let Ok(files) = Files::from_dir_bulk(&cwd).await { + FilesOp::Full(cwd, files, cha).emit(); } } tokio::spawn(async move { - futures::future::join_all(todo.into_iter().map(|(url, cha)| go(url, cha))).await; + futures::future::join_all(todo.into_iter().map(|(cwd, cha)| go(cwd, cha))).await; }); } diff --git a/yazi-core/src/tab/commands/cd.rs b/yazi-core/src/tab/commands/cd.rs index 5cebaf09..70c2daa6 100644 --- a/yazi-core/src/tab/commands/cd.rs +++ b/yazi-core/src/tab/commands/cd.rs @@ -39,20 +39,20 @@ impl Tab { return self.cd_interactive(); } - if opt.target == *self.cwd() { + if opt.target == *self.cwd().url() { return; } // Take parent to history if let Some(rep) = self.parent.take() { - self.history.insert(rep.loc.url().clone(), rep); + self.history.insert(rep.loc.url_owned(), rep); } // Current let rep = self.history.remove_or(&opt.target); let rep = mem::replace(&mut self.current, rep); if rep.loc.is_regular() { - self.history.insert(rep.loc.url().clone(), rep); + self.history.insert(rep.loc.url_owned(), rep); } // Parent diff --git a/yazi-core/src/tab/commands/filter.rs b/yazi-core/src/tab/commands/filter.rs index 89e2831c..ed8a112f 100644 --- a/yazi-core/src/tab/commands/filter.rs +++ b/yazi-core/src/tab/commands/filter.rs @@ -65,13 +65,13 @@ impl Tab { ManagerProxy::update_paged(); // Update for paged files in next loop } - let hovered = self.current.hovered().map(|f| f.url_owned()); + let hovered = self.current.hovered().map(|f| f.urn_owned()); if !self.current.files.set_filter(filter) { return; } - self.current.repos(hovered.as_ref()); - if self.current.hovered().map(|f| f.url()) != hovered.as_ref() { + self.current.repos(hovered.as_ref().map(|u| u._deref())); + if self.current.hovered().map(|f| f.urn()) != hovered.as_ref().map(|u| u._deref()) { ManagerProxy::hover(None, self.idx); } diff --git a/yazi-core/src/tab/commands/leave.rs b/yazi-core/src/tab/commands/leave.rs index 440614f1..5f82b80d 100644 --- a/yazi-core/src/tab/commands/leave.rs +++ b/yazi-core/src/tab/commands/leave.rs @@ -16,7 +16,7 @@ impl Tab { .current .hovered() .and_then(|h| h.parent()) - .filter(|p| p != self.cwd()) + .filter(|u| u != self.cwd().url()) .or_else(|| self.cwd().parent_url()) .map(|u| self.cd(u)); } diff --git a/yazi-core/src/tab/preview.rs b/yazi-core/src/tab/preview.rs index 2ae1881a..9e29e43b 100644 --- a/yazi-core/src/tab/preview.rs +++ b/yazi-core/src/tab/preview.rs @@ -38,29 +38,29 @@ impl Preview { } pub fn go_folder(&mut self, file: File, dir: Option, force: bool) { - let (cha, url) = (file.cha, file.url_owned()); + let (cha, cwd) = (file.cha, file.url_owned()); self.go(file, MIME_DIR, force); - if self.content_unchanged(&url, cha) { + if self.content_unchanged(&cwd, cha) { return; } self.folder_loader.take().map(|h| h.abort()); self.folder_loader = Some(tokio::spawn(async move { - let Some(new) = Files::assert_stale(&url, dir.unwrap_or(Cha::dummy())).await else { + let Some(new) = Files::assert_stale(&cwd, dir.unwrap_or(Cha::dummy())).await else { return; }; - let Ok(rx) = Files::from_dir(&url).await else { return }; + let Ok(rx) = Files::from_dir(&cwd).await else { return }; let stream = UnboundedReceiverStream::new(rx).chunks_timeout(50000, Duration::from_millis(500)); pin!(stream); - let ticket = FilesOp::prepare(&url); + let ticket = FilesOp::prepare(&cwd); while let Some(chunk) = stream.next().await { - FilesOp::Part(url.clone(), chunk, ticket).emit(); + FilesOp::Part(cwd.clone(), chunk, ticket).emit(); } - FilesOp::Done(url, new, ticket).emit(); + FilesOp::Done(cwd, new, ticket).emit(); })); } diff --git a/yazi-core/src/tab/selected.rs b/yazi-core/src/tab/selected.rs index d98e47a7..75600396 100644 --- a/yazi-core/src/tab/selected.rs +++ b/yazi-core/src/tab/selected.rs @@ -112,10 +112,10 @@ impl Selected { }; if !removal.is_empty() { - self.remove_many(&removal, !op.url().is_search()); + self.remove_many(&removal, !op.cwd().is_search()); } if !addition.is_empty() { - self.add_many(&addition, !op.url().is_search()); + self.add_many(&addition, !op.cwd().is_search()); } } } diff --git a/yazi-core/src/tab/tab.rs b/yazi-core/src/tab/tab.rs index 1b443029..12cbfb15 100644 --- a/yazi-core/src/tab/tab.rs +++ b/yazi-core/src/tab/tab.rs @@ -6,7 +6,7 @@ use tokio::task::JoinHandle; use yazi_adapter::Dimension; use yazi_config::{popup::{Origin, Position}, LAYOUT}; use yazi_fs::{Folder, FolderStage}; -use yazi_shared::{fs::Url, render}; +use yazi_shared::{fs::{Loc, Url}, render}; use super::{Backstack, Config, Finder, History, Mode, Preview}; use crate::tab::Selected; @@ -39,10 +39,10 @@ impl Tab { impl Tab { // --- Current #[inline] - pub fn cwd(&self) -> &Url { &self.current.loc } + pub fn cwd(&self) -> &Loc { &self.current.loc } pub fn hovered_rect(&self) -> Option { - let y = self.current.files.position(self.current.hovered()?.url())? - self.current.offset; + let y = self.current.files.position(self.current.hovered()?.urn())? - self.current.offset; let mut rect = LAYOUT.load().current; rect.y = rect.y.saturating_sub(1) + y as u16; @@ -86,7 +86,6 @@ impl Tab { } // --- History - #[inline] pub fn hovered_folder(&self) -> Option<&Folder> { self.current.hovered().filter(|&h| h.is_dir()).and_then(|h| self.history.get(h.url())) @@ -98,12 +97,12 @@ impl Tab { return render!(); } - let hovered = f.hovered().filter(|_| f.tracing).map(|h| h.url_owned()); + let hovered = f.hovered().filter(|_| f.tracing).map(|h| h.urn_owned()); f.files.set_show_hidden(self.conf.show_hidden); f.files.set_sorter(self.conf.sorter()); render!(f.files.catchup_revision()); - render!(f.repos(hovered)); + render!(f.repos(hovered.as_ref().map(|u| u._deref()))); }; apply(&mut self.current); @@ -112,7 +111,7 @@ impl Tab { apply(parent); // The parent should always track the CWD - parent.hover(&self.current.loc); + parent.hover(self.current.loc.urn()); parent.tracing = parent.hovered().map(|h| h.url()) == Some(&self.current.loc); } diff --git a/yazi-fm/src/lives/folder.rs b/yazi-fm/src/lives/folder.rs index 8d1e06e3..2d0e854b 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.loc.url().clone())); + reg.add_field_method_get("cwd", |lua, me| Url::cast(lua, me.loc.url_owned())); 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 ebdf56f1..b29cd9d5 100644 --- a/yazi-fm/src/lives/tab.rs +++ b/yazi-fm/src/lives/tab.rs @@ -24,7 +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.loc.name().as_encoded_bytes())).transpose() + lua.create_string(me.current.loc.name().as_encoded_bytes()) }); 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 583de41a..c3fce4d9 100644 --- a/yazi-fs/src/files.rs +++ b/yazi-fs/src/files.rs @@ -2,7 +2,7 @@ use std::{collections::{HashMap, HashSet}, mem, ops::Deref, sync::atomic::Orderi use tokio::{fs::{self, DirEntry}, select, sync::mpsc::{self, UnboundedReceiver}}; use yazi_config::{manager::SortBy, MANAGER}; -use yazi_shared::fs::{maybe_exists, Cha, File, FilesOp, Url, FILES_TICKET}; +use yazi_shared::fs::{maybe_exists, Cha, File, FilesOp, Url, Urn, FILES_TICKET}; use super::{FilesSorter, Filter}; @@ -96,19 +96,19 @@ impl Files { ) } - pub async fn assert_stale(url: &Url, cha: Cha) -> Option { - match fs::metadata(url).await.map(Cha::from) { + pub async fn assert_stale(cwd: &Url, cha: Cha) -> Option { + match fs::metadata(cwd).await.map(Cha::from) { Ok(c) if !c.is_dir() => { // FIXME: use `ErrorKind::NotADirectory` instead once it gets stabilized - FilesOp::IOErr(url.clone(), std::io::ErrorKind::AlreadyExists).emit(); + FilesOp::IOErr(cwd.clone(), std::io::ErrorKind::AlreadyExists).emit(); } Ok(c) if c.hits(cha) => {} Ok(c) => return Some(c), Err(e) => { - if maybe_exists(url).await { - FilesOp::IOErr(url.clone(), e.kind()).emit(); - } else if let Some(p) = url.parent_url() { - FilesOp::Deleting(p, vec![url.clone()]).emit(); + if maybe_exists(cwd).await { + FilesOp::IOErr(cwd.clone(), e.kind()).emit(); + } else if let Some(p) = cwd.parent_url() { + FilesOp::Deleting(p, vec![cwd.clone()]).emit(); } } } @@ -280,10 +280,9 @@ impl Files { } let (mut hidden, mut items) = if let Some(filter) = &self.filter { - files.into_iter().partition(|(_, f)| { - (f.is_hidden() && !self.show_hidden) - || !f.url().file_name().is_some_and(|s| filter.matches(s)) - }) + files + .into_iter() + .partition(|(_, f)| (f.is_hidden() && !self.show_hidden) || !filter.matches(f.name())) } else if self.show_hidden { (HashMap::new(), files) } else { @@ -330,10 +329,9 @@ impl Files { fn split_files(&self, files: impl IntoIterator) -> (Vec, Vec) { if let Some(filter) = &self.filter { - files.into_iter().partition(|f| { - (f.is_hidden() && !self.show_hidden) - || !f.url().file_name().is_some_and(|s| filter.matches(s)) - }) + files + .into_iter() + .partition(|f| (f.is_hidden() && !self.show_hidden) || !filter.matches(f.name())) } else if self.show_hidden { (vec![], files.into_iter().collect()) } else { @@ -345,8 +343,7 @@ impl Files { impl Files { // --- Items #[inline] - // TODO: use `name` instead of `url` - pub fn position(&self, url: &Url) -> Option { self.iter().position(|f| url == f.url()) } + pub fn position(&self, urn: &Urn) -> Option { self.iter().position(|f| urn == f.urn()) } // --- Ticket #[inline] diff --git a/yazi-fs/src/folder.rs b/yazi-fs/src/folder.rs index 8ddaf272..384c0623 100644 --- a/yazi-fs/src/folder.rs +++ b/yazi-fs/src/folder.rs @@ -2,7 +2,7 @@ use std::mem; use yazi_config::{LAYOUT, MANAGER}; use yazi_proxy::ManagerProxy; -use yazi_shared::fs::{Cha, File, FilesOp, Loc, Url}; +use yazi_shared::fs::{Cha, File, FilesOp, Loc, Url, Urn}; use super::FolderStage; use crate::{Files, Step}; @@ -79,18 +79,18 @@ impl Folder { b } - pub fn hover(&mut self, url: &Url) -> bool { - if self.hovered().map(|h| h.url()) == Some(url) { + pub fn hover(&mut self, urn: &Urn) -> bool { + if self.hovered().map(|h| h.urn()) == Some(urn) { return false; } - let new = self.files.position(url).unwrap_or(self.cursor) as isize; + let new = self.files.position(urn).unwrap_or(self.cursor) as isize; self.arrow(new - self.cursor as isize) } #[inline] - pub fn repos(&mut self, url: Option>) -> bool { - if let Some(u) = url { self.hover(u.as_ref()) } else { self.arrow(0) } + pub fn repos(&mut self, url: Option<&Urn>) -> bool { + if let Some(u) = url { self.hover(u) } else { self.arrow(0) } } pub fn sync_page(&mut self, force: bool) { diff --git a/yazi-plugin/src/external/fd.rs b/yazi-plugin/src/external/fd.rs index b26ae6aa..40de3262 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_search(&opt.cwd, opt.cwd.join(line)).await { + if let Ok(file) = File::from_search_item(&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 72e0d3bb..17440e2b 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_search(&opt.cwd, opt.cwd.join(line)).await { + if let Ok(file) = File::from_search_item(&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 ae71b755..d2e101d0 100644 --- a/yazi-plugin/src/file/file.rs +++ b/yazi-plugin/src/file/file.rs @@ -24,9 +24,11 @@ impl File { me.as_ref().link_to.clone().map(|u| Url::cast(lua, u)).transpose() }); - // Extension reg.add_field_method_get("name", |lua, me| { - me.as_ref().url().file_name().map(|n| lua.create_string(n.as_encoded_bytes())).transpose() + Some(me.as_ref().name()) + .filter(|s| !s.is_empty()) + .map(|s| lua.create_string(s.as_encoded_bytes())) + .transpose() }); reg.add_method("icon", |lua, me, ()| { diff --git a/yazi-shared/src/fs/file.rs b/yazi-shared/src/fs/file.rs index e8780f5c..e8c40e72 100644 --- a/yazi-shared/src/fs/file.rs +++ b/yazi-shared/src/fs/file.rs @@ -1,9 +1,9 @@ -use std::{cell::Cell, ffi::OsStr, fs::{FileType, Metadata}, ops::Deref, path::Path}; +use std::{cell::Cell, ffi::OsStr, fs::{FileType, Metadata}, ops::Deref}; use anyhow::Result; use tokio::fs; -use super::Loc; +use super::{Loc, Urn, UrnBuf}; use crate::{fs::{Cha, ChaKind, Url}, theme::IconCache}; #[derive(Clone, Debug, Default)] @@ -34,8 +34,8 @@ impl File { } #[inline] - pub async fn from_search(cwd: &Url, url: Url) -> Result { - let loc = Loc::from_search(cwd, url); + pub async fn from_search_item(cwd: &Url, url: Url) -> Result { + let loc = Loc::from_search_item(cwd, url); let meta = fs::symlink_metadata(loc.url()).await?; Ok(Self::from_loc(loc, meta).await) } @@ -102,10 +102,13 @@ impl File { pub fn url(&self) -> &Url { self.loc.url() } #[inline] - pub fn url_owned(&self) -> Url { self.url().clone() } + pub fn url_owned(&self) -> Url { self.loc.url_owned() } #[inline] - pub fn urn(&self) -> &Path { self.loc.urn() } + pub fn urn(&self) -> &Urn { self.loc.urn() } + + #[inline] + pub fn urn_owned(&self) -> UrnBuf { self.loc.urn_owned() } #[inline] pub fn name(&self) -> &OsStr { self.loc.name() } diff --git a/yazi-shared/src/fs/loc.rs b/yazi-shared/src/fs/loc.rs index 9e9e36e1..33562e79 100644 --- a/yazi-shared/src/fs/loc.rs +++ b/yazi-shared/src/fs/loc.rs @@ -1,6 +1,6 @@ use std::{ffi::OsStr, fmt::{self, Debug, Formatter}, ops::Deref, path::Path}; -use super::Url; +use super::{Url, Urn, UrnBuf}; pub struct Loc { url: Url, @@ -36,7 +36,7 @@ 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 }; + let urn = if url.is_search_item() { self.twin_urn(&url) } else { name }; Self { url, urn, name } } } @@ -57,7 +57,7 @@ impl Loc { Self { url, urn, name: urn } } - pub fn from_search(cwd: &Url, url: Url) -> Self { + pub fn from_search_item(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 } @@ -66,14 +66,14 @@ impl Loc { 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 }; + let urn = if url.is_search_item() { 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 take = self.urn()._as_path().components().count(); let mut it = new.components(); for _ in 0..total - take { @@ -89,7 +89,13 @@ impl Loc { pub fn url(&self) -> &Url { &self.url } #[inline] - pub fn urn(&self) -> &Path { Path::new(unsafe { &*self.urn }) } + pub fn url_owned(&self) -> Url { self.url.to_owned() } + + #[inline] + pub fn urn(&self) -> &Urn { Urn::new(unsafe { &*self.urn }) } + + #[inline] + pub fn urn_owned(&self) -> UrnBuf { self.urn().to_owned() } #[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 059abbb8..443aa0b1 100644 --- a/yazi-shared/src/fs/mod.rs +++ b/yazi-shared/src/fs/mod.rs @@ -5,6 +5,7 @@ mod loc; mod op; mod path; mod url; +mod urn; pub use cha::*; pub use file::*; @@ -13,3 +14,4 @@ pub use loc::*; pub use op::*; pub use path::*; pub use url::*; +pub use urn::*; diff --git a/yazi-shared/src/fs/op.rs b/yazi-shared/src/fs/op.rs index 32338506..a15f3eb5 100644 --- a/yazi-shared/src/fs/op.rs +++ b/yazi-shared/src/fs/op.rs @@ -21,18 +21,18 @@ pub enum FilesOp { impl FilesOp { #[inline] - pub fn url(&self) -> &Url { + pub fn cwd(&self) -> &Url { match self { - Self::Full(url, ..) => url, - Self::Part(url, ..) => url, - Self::Done(url, ..) => url, - Self::Size(url, _) => url, - Self::IOErr(url, _) => url, + Self::Full(u, ..) => u, + Self::Part(u, ..) => u, + Self::Done(u, ..) => u, + Self::Size(u, _) => u, + Self::IOErr(u, _) => u, - Self::Creating(url, _) => url, - Self::Deleting(url, _) => url, - Self::Updating(url, _) => url, - Self::Upserting(url, _) => url, + Self::Creating(u, _) => u, + Self::Deleting(u, _) => u, + Self::Updating(u, _) => u, + Self::Upserting(u, _) => u, } } @@ -41,9 +41,9 @@ impl FilesOp { emit!(Call(Cmd::new("update_files").with_any("op", self), Layer::Manager)); } - pub fn prepare(url: &Url) -> u64 { + pub fn prepare(cwd: &Url) -> u64 { let ticket = FILES_TICKET.fetch_add(1, Ordering::Relaxed); - Self::Part(url.clone(), vec![], ticket).emit(); + Self::Part(cwd.clone(), vec![], ticket).emit(); ticket } diff --git a/yazi-shared/src/fs/url.rs b/yazi-shared/src/fs/url.rs index 7d1fc90a..a1f1b525 100644 --- a/yazi-shared/src/fs/url.rs +++ b/yazi-shared/src/fs/url.rs @@ -3,6 +3,8 @@ use std::{ffi::{OsStr, OsString}, fmt::{Debug, Display, Formatter}, ops::{Deref, use percent_encoding::{percent_decode_str, percent_encode, AsciiSet, CONTROLS}; use serde::{Deserialize, Serialize}; +use super::Loc; + const ENCODE_SET: &AsciiSet = &CONTROLS.add(b'#'); #[derive(Clone, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] @@ -161,6 +163,14 @@ impl Url { pub fn is_hidden(&self) -> bool { self.file_name().map_or(false, |s| s.as_encoded_bytes().starts_with(b".")) } + + #[inline] + pub fn to_loc(&self, cwd: &Url) -> Loc { self.clone().into_loc(cwd) } + + #[inline] + pub fn into_loc(self, cwd: &Url) -> Loc { + if self.is_search_item() { Loc::from_search_item(cwd, self) } else { Loc::from(self) } + } } impl Url { @@ -189,6 +199,9 @@ impl Url { Self { scheme: UrlScheme::Search, path: self.path.clone(), frag: frag.to_owned() } } + #[inline] + pub fn is_search_item(&self) -> bool { self.scheme == UrlScheme::SearchItem } + #[inline] pub fn into_search_item(mut self) -> Self { self.scheme = UrlScheme::SearchItem; diff --git a/yazi-shared/src/fs/urn.rs b/yazi-shared/src/fs/urn.rs new file mode 100644 index 00000000..086ca8a6 --- /dev/null +++ b/yazi-shared/src/fs/urn.rs @@ -0,0 +1,33 @@ +use std::{borrow::Borrow, path::{Path, PathBuf}}; + +#[derive(Debug, Eq, PartialEq)] +#[repr(transparent)] +pub struct Urn(Path); + +impl Urn { + // TODO: clean this up + pub fn new + ?Sized>(p: &T) -> &Self { + unsafe { &*(p.as_ref() as *const Path as *const Self) } + } + + // FIXME: remove this + pub fn _as_path(&self) -> &Path { &self.0 } +} + +impl ToOwned for Urn { + type Owned = UrnBuf; + + fn to_owned(&self) -> Self::Owned { UrnBuf(self.0.to_owned()) } +} + +// --- UrnBuf +pub struct UrnBuf(PathBuf); + +impl Borrow for UrnBuf { + fn borrow(&self) -> &Urn { Urn::new(&self.0) } +} + +impl UrnBuf { + // FIXME: remove this + pub fn _deref(&self) -> &Urn { Urn::new(&self.0) } +}