mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
perf: introduce URN to speed up large directory file updates, sorting and locating
This commit is contained in:
parent
d20b3d8408
commit
fe2af1025d
12 changed files with 148 additions and 47 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -45,8 +45,8 @@ impl Deref for Files {
|
|||
}
|
||||
|
||||
impl Files {
|
||||
pub async fn from_dir(url: &Url) -> std::io::Result<UnboundedReceiver<File>> {
|
||||
let mut it = fs::read_dir(url).await?;
|
||||
pub async fn from_dir(dir: &Url) -> std::io::Result<UnboundedReceiver<File>> {
|
||||
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<Vec<File>> {
|
||||
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<Vec<File>> {
|
||||
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<File> {
|
||||
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<File> {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
2
yazi-plugin/src/external/fd.rs
vendored
2
yazi-plugin/src/external/fd.rs
vendored
|
|
@ -29,7 +29,7 @@ pub fn fd(opt: FdOpt) -> Result<UnboundedReceiver<File>> {
|
|||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
yazi-plugin/src/external/rg.rs
vendored
2
yazi-plugin/src/external/rg.rs
vendored
|
|
@ -28,7 +28,7 @@ pub fn rg(opt: RgOpt) -> Result<UnboundedReceiver<File>> {
|
|||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)?;
|
||||
|
|
|
|||
|
|
@ -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<Url>,
|
||||
pub icon: Cell<IconCache>,
|
||||
}
|
||||
|
|
@ -29,16 +30,38 @@ impl File {
|
|||
#[inline]
|
||||
pub async fn from(url: Url) -> Result<Self> {
|
||||
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> {
|
||||
Self::from_loc(Location::from(url)?, meta).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_dummy(url: Url, ft: Option<FileType>) -> Result<Self> {
|
||||
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<Self> {
|
||||
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<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 +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<FileType>) -> 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<Url> { self.url.parent_url() }
|
||||
pub fn stem(&self) -> Option<&OsStr> { self.url().file_stem() }
|
||||
|
||||
#[inline]
|
||||
pub fn parent(&self) -> Option<Url> { self.url().parent_url() }
|
||||
}
|
||||
|
|
|
|||
62
yazi-shared/src/fs/location.rs
Normal file
62
yazi-shared/src/fs/location.rs
Normal file
|
|
@ -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<Self> {
|
||||
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<Self> {
|
||||
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 } }
|
||||
}
|
||||
|
|
@ -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::*;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue