perf: introduce URN to speed up large directory locating

This commit is contained in:
sxyazi 2024-09-16 00:41:07 +08:00
parent 49639aa34c
commit e8bca4b78a
No known key found for this signature in database
21 changed files with 118 additions and 61 deletions

8
Cargo.lock generated
View file

@ -135,9 +135,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.87" version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356"
[[package]] [[package]]
name = "arbitrary" name = "arbitrary"
@ -1008,9 +1008,9 @@ checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
[[package]] [[package]]
name = "globset" name = "globset"
version = "0.4.14" version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"bstr", "bstr",

View file

@ -11,7 +11,7 @@ strip = true
[workspace.dependencies] [workspace.dependencies]
ansi-to-tui = "6.0.0" ansi-to-tui = "6.0.0"
anyhow = "1.0.86" anyhow = "1.0.88"
arc-swap = "1.7.1" arc-swap = "1.7.1"
base64 = "0.22.1" base64 = "0.22.1"
bitflags = "2.6.0" bitflags = "2.6.0"
@ -19,7 +19,7 @@ clap = { version = "4.5.17", features = [ "derive" ] }
crossterm = { version = "0.28.1", features = [ "event-stream" ] } crossterm = { version = "0.28.1", features = [ "event-stream" ] }
dirs = "5.0.1" dirs = "5.0.1"
futures = "0.3.30" futures = "0.3.30"
globset = "0.4.14" globset = "0.4.15"
libc = "0.2.158" libc = "0.2.158"
md-5 = "0.10.6" md-5 = "0.10.6"
mlua = { version = "0.9.9", features = [ "lua54", "serialize", "macros", "async" ] } 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" ] } ratatui = { version = "0.28.1", features = [ "unstable-rendered-line-info" ] }
regex = "1.10.6" regex = "1.10.6"
scopeguard = "1.2.0" scopeguard = "1.2.0"
serde = { version = "1.0.209", features = [ "derive" ] } serde = { version = "1.0.210", features = [ "derive" ] }
serde_json = "1.0.128" serde_json = "1.0.128"
shell-words = "1.1.0" shell-words = "1.1.0"
tokio = { version = "1.40.0", features = [ "full" ] } tokio = { version = "1.40.0", features = [ "full" ] }

View file

@ -16,7 +16,7 @@ impl Manager {
return AppProxy::notify_warn("Bulk rename", "No text opener found"); 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 old: Vec<_> = self.selected_or_hovered(true).collect();
let root = max_common_root(&old); let root = max_common_root(&old);

View file

@ -1,7 +1,7 @@
use std::collections::HashSet; use std::{collections::HashSet, path::PathBuf};
use yazi_dds::Pubsub; 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; use crate::manager::Manager;
@ -25,13 +25,8 @@ impl From<Option<Url>> for Opt {
impl Manager { impl Manager {
pub fn hover(&mut self, opt: impl Into<Opt>) { pub fn hover(&mut self, opt: impl Into<Opt>) {
let opt = opt.into() as Opt; let opt = opt.into() as Opt;
if let Some(u) = opt.url {
// Hover on the file self.hover_do(u, opt.tab);
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;
} }
// Repeek // Repeek
@ -40,7 +35,7 @@ impl Manager {
// Refresh watcher // Refresh watcher
let mut to_watch = HashSet::with_capacity(3 * self.tabs.len()); let mut to_watch = HashSet::with_capacity(3 * self.tabs.len());
for tab in self.tabs.iter() { for tab in self.tabs.iter() {
to_watch.insert(tab.cwd()); to_watch.insert(tab.cwd().url());
if let Some(ref p) = tab.parent { if let Some(ref p) = tab.parent {
to_watch.insert(&p.loc); to_watch.insert(&p.loc);
} }
@ -53,4 +48,18 @@ impl Manager {
// Publish through DDS // Publish through DDS
Pubsub::pub_from_hover(self.active().idx, self.hovered().map(|h| h.url())); Pubsub::pub_from_hover(self.active().idx, self.hovered().map(|h| h.url()));
} }
fn hover_do(&mut self, url: Url, tab: Option<usize>) {
// 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;
}
}
} }

View file

@ -9,7 +9,7 @@ use crate::{manager::Manager, tasks::Tasks};
impl Manager { impl Manager {
pub fn refresh(&mut self, _: Cmd, tasks: &Tasks) { pub fn refresh(&mut self, _: Cmd, tasks: &Tasks) {
env::set_current_dir(self.cwd()).ok(); 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() { if !MANAGER.title_format.is_empty() {
execute!(std::io::stderr(), SetTitle(self.title())).ok(); execute!(std::io::stderr(), SetTitle(self.title())).ok();

View file

@ -46,7 +46,7 @@ impl Tabs {
} else { } else {
tab.conf = self.active().conf.clone(); tab.conf = self.active().conf.clone();
tab.apply_files_attrs(); tab.apply_files_attrs();
tab.cd(self.active().cwd().clone()); tab.cd(self.active().cwd().url_owned());
} }
self.items.insert(self.cursor + 1, tab); self.items.insert(self.cursor + 1, tab);

View file

@ -47,7 +47,7 @@ impl Manager {
let url = op.url(); let url = op.url();
tab.selected.apply_op(&op); tab.selected.apply_op(&op);
if url == tab.cwd() { if url == tab.cwd().url() {
Self::update_current(tab, op, tasks); Self::update_current(tab, op, tasks);
} else if matches!(&tab.parent, Some(p) if url == &*p.loc) { } else if matches!(&tab.parent, Some(p) if url == &*p.loc) {
Self::update_parent(tab, op); Self::update_parent(tab, op);
@ -59,12 +59,15 @@ impl Manager {
} }
fn update_parent(tab: &mut Tab, op: Cow<FilesOp>) { fn update_parent(tab: &mut Tab, op: Cow<FilesOp>) {
let cwd = tab.cwd().clone(); let urn = tab.cwd().urn_owned();
let leave = matches!(*op, FilesOp::Deleting(_, ref urls) if urls.contains(&cwd)); // FIXME
let leave = false;
// let leave = matches!(*op, FilesOp::Deleting(_, ref urls) if
// urls.contains(&urn));
if let Some(f) = tab.parent.as_mut() { if let Some(f) = tab.parent.as_mut() {
render!(f.update(op.into_owned())); render!(f.update(op.into_owned()));
render!(f.hover(&cwd)); render!(f.hover(urn._deref()));
} }
if leave { if leave {
@ -73,7 +76,7 @@ impl Manager {
} }
fn update_current(tab: &mut Tab, op: Cow<FilesOp>, tasks: &Tasks) { fn update_current(tab: &mut Tab, op: Cow<FilesOp>, 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 calc = !matches!(*op, FilesOp::Size(..) | FilesOp::Deleting(..));
let foreign = matches!(op, Cow::Borrowed(_)); let foreign = matches!(op, Cow::Borrowed(_));
@ -81,7 +84,7 @@ impl Manager {
return; return;
} }
tab.current.repos(hovered); tab.current.repos(hovered.as_ref().map(|u| u._deref()));
if foreign { if foreign {
return; return;
} }
@ -113,9 +116,9 @@ impl Manager {
); );
let folder = tab.history.entry(op.url().clone()).or_insert_with(|| Folder::from(op.url())); 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 hovered = folder.hovered().filter(|_| folder.tracing).map(|h| h.urn_owned());
if folder.update(op.into_owned()) { if folder.update(op.into_owned()) {
folder.repos(hovered); folder.repos(hovered.as_ref().map(|u| u._deref()));
} }
if leave { if leave {

View file

@ -27,7 +27,7 @@ impl Manager {
return; return;
}; };
if opt.only_if.is_some_and(|u| u != *self.active().cwd()) { if opt.only_if.is_some_and(|u| u != *self.active().cwd().url()) {
return; return;
} }

View file

@ -2,7 +2,7 @@ use ratatui::layout::Rect;
use yazi_adapter::Dimension; use yazi_adapter::Dimension;
use yazi_config::popup::{Origin, Position}; use yazi_config::popup::{Origin, Position};
use yazi_fs::Folder; use yazi_fs::Folder;
use yazi_shared::fs::{File, Url}; use yazi_shared::fs::{File, Loc, Url};
use super::{Mimetype, Tabs, Watcher, Yanked}; use super::{Mimetype, Tabs, Watcher, Yanked};
use crate::tab::Tab; use crate::tab::Tab;
@ -39,7 +39,7 @@ impl Manager {
impl Manager { impl Manager {
#[inline] #[inline]
pub fn cwd(&self) -> &Url { &self.current().loc } pub fn cwd(&self) -> &Loc { self.active().cwd() }
#[inline] #[inline]
pub fn active(&self) -> &Tab { self.tabs.active() } pub fn active(&self) -> &Tab { self.tabs.active() }

View file

@ -39,7 +39,7 @@ impl Tab {
return self.cd_interactive(); return self.cd_interactive();
} }
if opt.target == *self.cwd() { if opt.target == *self.cwd().url() {
return; return;
} }

View file

@ -65,13 +65,13 @@ impl Tab {
ManagerProxy::update_paged(); // Update for paged files in next loop 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) { if !self.current.files.set_filter(filter) {
return; return;
} }
self.current.repos(hovered.as_ref()); self.current.repos(hovered.as_ref().map(|u| u._deref()));
if self.current.hovered().map(|f| f.url()) != hovered.as_ref() { if self.current.hovered().map(|f| f.urn()) != hovered.as_ref().map(|u| u._deref()) {
ManagerProxy::hover(None, self.idx); ManagerProxy::hover(None, self.idx);
} }

View file

@ -16,7 +16,7 @@ impl Tab {
.current .current
.hovered() .hovered()
.and_then(|h| h.parent()) .and_then(|h| h.parent())
.filter(|p| p != self.cwd()) .filter(|u| u != self.cwd().url())
.or_else(|| self.cwd().parent_url()) .or_else(|| self.cwd().parent_url())
.map(|u| self.cd(u)); .map(|u| self.cd(u));
} }

View file

@ -6,7 +6,7 @@ use tokio::task::JoinHandle;
use yazi_adapter::Dimension; use yazi_adapter::Dimension;
use yazi_config::{popup::{Origin, Position}, LAYOUT}; use yazi_config::{popup::{Origin, Position}, LAYOUT};
use yazi_fs::{Folder, FolderStage}; 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 super::{Backstack, Config, Finder, History, Mode, Preview};
use crate::tab::Selected; use crate::tab::Selected;
@ -39,10 +39,10 @@ impl Tab {
impl Tab { impl Tab {
// --- Current // --- Current
#[inline] #[inline]
pub fn cwd(&self) -> &Url { &self.current.loc } pub fn cwd(&self) -> &Loc { &self.current.loc }
pub fn hovered_rect(&self) -> Option<Rect> { pub fn hovered_rect(&self) -> Option<Rect> {
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; let mut rect = LAYOUT.load().current;
rect.y = rect.y.saturating_sub(1) + y as u16; rect.y = rect.y.saturating_sub(1) + y as u16;
@ -98,12 +98,12 @@ impl Tab {
return render!(); 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_show_hidden(self.conf.show_hidden);
f.files.set_sorter(self.conf.sorter()); f.files.set_sorter(self.conf.sorter());
render!(f.files.catchup_revision()); render!(f.files.catchup_revision());
render!(f.repos(hovered)); render!(f.repos(hovered.as_ref().map(|u| u._deref())));
}; };
apply(&mut self.current); apply(&mut self.current);
@ -112,7 +112,7 @@ impl Tab {
apply(parent); apply(parent);
// The parent should always track the CWD // 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); parent.tracing = parent.hovered().map(|h| h.url()) == Some(&self.current.loc);
} }

View file

@ -24,7 +24,7 @@ impl Tab {
pub(super) fn register(lua: &Lua) -> mlua::Result<()> { pub(super) fn register(lua: &Lua) -> mlua::Result<()> {
lua.register_userdata_type::<Self>(|reg| { lua.register_userdata_type::<Self>(|reg| {
reg.add_method("name", |lua, me, ()| { 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)); reg.add_field_method_get("mode", |_, me| Mode::make(&me.mode));

View file

@ -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 tokio::{fs::{self, DirEntry}, select, sync::mpsc::{self, UnboundedReceiver}};
use yazi_config::{manager::SortBy, MANAGER}; 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}; use super::{FilesSorter, Filter};
@ -345,8 +345,7 @@ impl Files {
impl Files { impl Files {
// --- Items // --- Items
#[inline] #[inline]
// TODO: use `name` instead of `url` pub fn position(&self, urn: &Urn) -> Option<usize> { self.iter().position(|f| urn == f.urn()) }
pub fn position(&self, url: &Url) -> Option<usize> { self.iter().position(|f| url == f.url()) }
// --- Ticket // --- Ticket
#[inline] #[inline]

View file

@ -2,7 +2,7 @@ use std::mem;
use yazi_config::{LAYOUT, MANAGER}; use yazi_config::{LAYOUT, MANAGER};
use yazi_proxy::ManagerProxy; 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 super::FolderStage;
use crate::{Files, Step}; use crate::{Files, Step};
@ -79,18 +79,18 @@ impl Folder {
b b
} }
pub fn hover(&mut self, url: &Url) -> bool { pub fn hover(&mut self, urn: &Urn) -> bool {
if self.hovered().map(|h| h.url()) == Some(url) { if self.hovered().map(|h| h.urn()) == Some(urn) {
return false; 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) self.arrow(new - self.cursor as isize)
} }
#[inline] #[inline]
pub fn repos(&mut self, url: Option<impl AsRef<Url>>) -> bool { pub fn repos(&mut self, url: Option<&Urn>) -> bool {
if let Some(u) = url { self.hover(u.as_ref()) } else { self.arrow(0) } if let Some(u) = url { self.hover(u) } else { self.arrow(0) }
} }
pub fn sync_page(&mut self, force: bool) { pub fn sync_page(&mut self, force: bool) {

View file

@ -24,9 +24,11 @@ impl File {
me.as_ref().link_to.clone().map(|u| Url::cast(lua, u)).transpose() me.as_ref().link_to.clone().map(|u| Url::cast(lua, u)).transpose()
}); });
// Extension
reg.add_field_method_get("name", |lua, me| { 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, ()| { reg.add_method("icon", |lua, me, ()| {

View file

@ -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 anyhow::Result;
use tokio::fs; use tokio::fs;
use super::Loc; use super::{Loc, Urn, UrnBuf};
use crate::{fs::{Cha, ChaKind, Url}, theme::IconCache}; use crate::{fs::{Cha, ChaKind, Url}, theme::IconCache};
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
@ -102,10 +102,13 @@ impl File {
pub fn url(&self) -> &Url { self.loc.url() } pub fn url(&self) -> &Url { self.loc.url() }
#[inline] #[inline]
pub fn url_owned(&self) -> Url { self.url().clone() } pub fn url_owned(&self) -> Url { self.loc.url_owned() }
#[inline] #[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] #[inline]
pub fn name(&self) -> &OsStr { self.loc.name() } pub fn name(&self) -> &OsStr { self.loc.name() }

View file

@ -1,6 +1,6 @@
use std::{ffi::OsStr, fmt::{self, Debug, Formatter}, ops::Deref, path::Path}; use std::{ffi::OsStr, fmt::{self, Debug, Formatter}, ops::Deref, path::Path};
use super::Url; use super::{Url, Urn, UrnBuf};
pub struct Loc { pub struct Loc {
url: Url, url: Url,
@ -73,7 +73,7 @@ impl Loc {
#[inline] #[inline]
fn twin_urn<'a>(&self, new: &'a Url) -> &'a OsStr { fn twin_urn<'a>(&self, new: &'a Url) -> &'a OsStr {
let total = new.components().count(); let total = new.components().count();
let take = self.urn().components().count(); let take = self.urn()._as_path().components().count();
let mut it = new.components(); let mut it = new.components();
for _ in 0..total - take { for _ in 0..total - take {
@ -89,7 +89,13 @@ impl Loc {
pub fn url(&self) -> &Url { &self.url } pub fn url(&self) -> &Url { &self.url }
#[inline] #[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] #[inline]
pub fn name(&self) -> &OsStr { unsafe { &*self.name } } pub fn name(&self) -> &OsStr { unsafe { &*self.name } }

View file

@ -5,6 +5,7 @@ mod loc;
mod op; mod op;
mod path; mod path;
mod url; mod url;
mod urn;
pub use cha::*; pub use cha::*;
pub use file::*; pub use file::*;
@ -13,3 +14,4 @@ pub use loc::*;
pub use op::*; pub use op::*;
pub use path::*; pub use path::*;
pub use url::*; pub use url::*;
pub use urn::*;

33
yazi-shared/src/fs/urn.rs Normal file
View file

@ -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<T: AsRef<Path> + ?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<Urn> for UrnBuf {
fn borrow(&self) -> &Urn { Urn::new(&self.0) }
}
impl UrnBuf {
// FIXME: remove this
pub fn _deref(&self) -> &Urn { Urn::new(&self.0) }
}