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() {
|
if !succeeded.is_empty() {
|
||||||
Pubsub::pub_from_bulk(succeeded.iter().map(|(u, f)| (u, f.url())).collect());
|
Pubsub::pub_from_bulk(succeeded.iter().map(|(u, f)| (u, f.url())).collect());
|
||||||
FilesOp::Upserting(cwd, succeeded).emit();
|
FilesOp::Upserting(cwd, succeeded).emit();
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,12 @@ impl Tab {
|
||||||
let Some(parent) = opt.target.parent_url() else {
|
let Some(parent) = opt.target.parent_url() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
let Ok(file) = File::from_dummy(opt.target.clone(), None) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
self.cd(parent.clone());
|
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);
|
ManagerProxy::hover(Some(opt.target), self.idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,11 +44,10 @@ impl Tab {
|
||||||
handle.abort();
|
handle.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut cwd = self.current.cwd.clone();
|
let cwd = self.current.cwd.to_search(&opt.subject);
|
||||||
let hidden = self.conf.show_hidden;
|
let hidden = self.conf.show_hidden;
|
||||||
|
|
||||||
self.search = Some(tokio::spawn(async move {
|
self.search = Some(tokio::spawn(async move {
|
||||||
cwd = cwd.into_search(opt.subject.clone());
|
|
||||||
let rx = if opt.via == SearchOptVia::Rg {
|
let rx = if opt.via == SearchOptVia::Rg {
|
||||||
external::rg(external::RgOpt {
|
external::rg(external::RgOpt {
|
||||||
cwd: cwd.clone(),
|
cwd: cwd.clone(),
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,8 @@ impl Deref for Files {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Files {
|
impl Files {
|
||||||
pub async fn from_dir(url: &Url) -> std::io::Result<UnboundedReceiver<File>> {
|
pub async fn from_dir(dir: &Url) -> std::io::Result<UnboundedReceiver<File>> {
|
||||||
let mut it = fs::read_dir(url).await?;
|
let mut it = fs::read_dir(dir).await?;
|
||||||
let (tx, rx) = mpsc::unbounded_channel();
|
let (tx, rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
|
|
@ -55,10 +55,13 @@ impl Files {
|
||||||
_ = tx.closed() => break,
|
_ = tx.closed() => break,
|
||||||
result = item.metadata() => {
|
result = item.metadata() => {
|
||||||
let url = Url::from(item.path());
|
let url = Url::from(item.path());
|
||||||
_ = tx.send(match result {
|
let file = match result {
|
||||||
Ok(meta) => File::from_meta(url, meta).await,
|
Ok(meta) => File::from_meta(url, meta).await,
|
||||||
Err(_) => File::from_dummy(url, item.file_type().await.ok())
|
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)
|
Ok(rx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn from_dir_bulk(url: &Url) -> std::io::Result<Vec<File>> {
|
pub async fn from_dir_bulk(dir: &Url) -> std::io::Result<Vec<File>> {
|
||||||
let mut it = fs::read_dir(url).await?;
|
let mut it = fs::read_dir(dir).await?;
|
||||||
let mut items = Vec::with_capacity(5000);
|
let mut entries = Vec::with_capacity(5000);
|
||||||
while let Ok(Some(item)) = it.next_entry().await {
|
while let Ok(Some(entry)) = it.next_entry().await {
|
||||||
items.push(item);
|
entries.push(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (first, rest) = items.split_at(items.len() / 3);
|
let (first, rest) = entries.split_at(entries.len() / 3);
|
||||||
let (second, third) = rest.split_at(items.len() / 3);
|
let (second, third) = rest.split_at(entries.len() / 3);
|
||||||
async fn go(entities: &[DirEntry]) -> Vec<File> {
|
async fn go(entries: &[DirEntry]) -> Vec<File> {
|
||||||
let mut files = Vec::with_capacity(entities.len() / 3 + 1);
|
let mut files = Vec::with_capacity(entries.len() / 3 + 1);
|
||||||
for entry in entities {
|
for entry in entries {
|
||||||
let url = Url::from(entry.path());
|
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,
|
Ok(meta) => File::from_meta(url, meta).await,
|
||||||
Err(_) => File::from_dummy(url, entry.file_type().await.ok()),
|
Err(_) => File::from_dummy(url, entry.file_type().await.ok()),
|
||||||
});
|
};
|
||||||
|
if let Ok(f) = file {
|
||||||
|
files.push(f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
files
|
files
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,10 @@ impl FilesSorter {
|
||||||
|
|
||||||
let by_alphabetical = |a: &File, b: &File| {
|
let by_alphabetical = |a: &File, b: &File| {
|
||||||
if self.sensitive {
|
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 {
|
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 {
|
tokio::spawn(async move {
|
||||||
while let Ok(Some(line)) = it.next_line().await {
|
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();
|
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 {
|
tokio::spawn(async move {
|
||||||
while let Ok(Some(line)) = it.next_line().await {
|
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();
|
tx.send(file).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ pub fn install(lua: &Lua) -> mlua::Result<()> {
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"read_dir",
|
"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") {
|
let glob = if let Ok(s) = options.raw_get::<_, mlua::String>("glob") {
|
||||||
Some(
|
Some(
|
||||||
GlobBuilder::new(s.to_str()?)
|
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 limit = options.raw_get("limit").unwrap_or(usize::MAX);
|
||||||
let resolve = options.raw_get("resolve").unwrap_or(false);
|
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,
|
Ok(it) => it,
|
||||||
Err(e) => return (Value::Nil, e.raw_os_error()).into_lua_multi(lua),
|
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())
|
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)?;
|
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 anyhow::Result;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
|
||||||
|
use super::Location;
|
||||||
use crate::{fs::{Cha, ChaKind, Url}, theme::IconCache};
|
use crate::{fs::{Cha, ChaKind, Url}, theme::IconCache};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct File {
|
pub struct File {
|
||||||
pub cha: Cha,
|
pub cha: Cha,
|
||||||
pub url: Url,
|
location: Location,
|
||||||
pub link_to: Option<Url>,
|
pub link_to: Option<Url>,
|
||||||
pub icon: Cell<IconCache>,
|
pub icon: Cell<IconCache>,
|
||||||
}
|
}
|
||||||
|
|
@ -29,16 +30,38 @@ impl File {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn from(url: Url) -> Result<Self> {
|
pub async fn from(url: Url) -> Result<Self> {
|
||||||
let meta = fs::symlink_metadata(&url).await?;
|
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 mut ck = ChaKind::empty();
|
||||||
let (is_link, mut link_to) = (meta.is_symlink(), None);
|
let (is_link, mut link_to) = (meta.is_symlink(), None);
|
||||||
|
|
||||||
if is_link {
|
if is_link {
|
||||||
meta = fs::metadata(&url).await.unwrap_or(meta);
|
meta = fs::metadata(loc.url()).await.unwrap_or(meta);
|
||||||
link_to = fs::read_link(&url).await.map(Url::from).ok();
|
link_to = fs::read_link(loc.url()).await.map(Url::from).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_link && meta.is_symlink() {
|
if is_link && meta.is_symlink() {
|
||||||
|
|
@ -48,7 +71,7 @@ impl File {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
if url.is_hidden() {
|
if loc.url().is_hidden() {
|
||||||
ck |= ChaKind::HIDDEN;
|
ck |= ChaKind::HIDDEN;
|
||||||
}
|
}
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
|
@ -59,26 +82,29 @@ impl File {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Self { cha: Cha::from(meta).with_kind(ck), url, link_to, icon: Default::default() }
|
Ok(Self {
|
||||||
}
|
cha: Cha::from(meta).with_kind(ck),
|
||||||
|
location: loc,
|
||||||
#[inline]
|
link_to,
|
||||||
pub fn from_dummy(url: Url, ft: Option<FileType>) -> Self {
|
icon: Default::default(),
|
||||||
Self { cha: ft.map_or_else(Cha::dummy, Cha::from), url: url.to_owned(), ..Default::default() }
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl File {
|
impl File {
|
||||||
// --- Url
|
// --- Location
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn url_owned(&self) -> Url { self.url.clone() }
|
pub fn url(&self) -> &Url { self.location.url() }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn name(&self) -> Option<&OsStr> { self.url.file_name() }
|
pub fn url_owned(&self) -> Url { self.url().clone() }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn stem(&self) -> Option<&OsStr> { self.url.file_stem() }
|
pub fn name(&self) -> &OsStr { self.location.name() }
|
||||||
|
|
||||||
#[inline]
|
#[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 cha;
|
||||||
mod file;
|
mod file;
|
||||||
mod fns;
|
mod fns;
|
||||||
|
mod location;
|
||||||
mod op;
|
mod op;
|
||||||
mod path;
|
mod path;
|
||||||
mod url;
|
mod url;
|
||||||
|
|
@ -8,6 +9,7 @@ mod url;
|
||||||
pub use cha::*;
|
pub use cha::*;
|
||||||
pub use file::*;
|
pub use file::*;
|
||||||
pub use fns::*;
|
pub use fns::*;
|
||||||
|
pub use location::*;
|
||||||
pub use op::*;
|
pub use op::*;
|
||||||
pub use path::*;
|
pub use path::*;
|
||||||
pub use url::*;
|
pub use url::*;
|
||||||
|
|
|
||||||
|
|
@ -179,12 +179,12 @@ impl Url {
|
||||||
pub fn is_search(&self) -> bool { self.scheme == UrlScheme::Search }
|
pub fn is_search(&self) -> bool { self.scheme == UrlScheme::Search }
|
||||||
|
|
||||||
#[inline]
|
#[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]
|
#[inline]
|
||||||
pub fn into_search(mut self, frag: String) -> Self {
|
pub fn into_search(mut self, frag: &str) -> Self {
|
||||||
self.scheme = UrlScheme::Search;
|
self.scheme = UrlScheme::Search;
|
||||||
self.frag = frag;
|
self.frag = frag.to_owned();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue