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 locating (#1648)
This commit is contained in:
parent
49639aa34c
commit
28083d805e
31 changed files with 196 additions and 129 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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" ] }
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,8 @@ keymap = [
|
||||||
{ on = "h", run = "leave", desc = "Go back to the parent directory" },
|
{ on = "h", run = "leave", desc = "Go back to the parent directory" },
|
||||||
{ on = "l", run = "enter", desc = "Enter the child directory" },
|
{ on = "l", run = "enter", desc = "Enter the child directory" },
|
||||||
|
|
||||||
{ on = "<Left>", run = "leave", desc = "Go back to the parent directory" },
|
{ on = "<Left>", run = "leave", desc = "Go back to the parent directory" },
|
||||||
{ on = "<Right>", run = "enter", desc = "Enter the child directory" },
|
{ on = "<Right>", run = "enter", desc = "Enter the child directory" },
|
||||||
|
|
||||||
{ on = "H", run = "back", desc = "Go back to the previous directory" },
|
{ on = "H", run = "back", desc = "Go back to the previous directory" },
|
||||||
{ on = "L", run = "forward", desc = "Go forward to the next 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", 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 = "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 = "x", run = [ "delete --cut", "move 1 --in-operating" ], desc = "Cut the current character" },
|
||||||
{ on = "y", run = "yank", desc = "Copy the selected characters" },
|
{ 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", desc = "Paste the copied characters after the cursor" },
|
||||||
{ on = "P", run = "paste --before", desc = "Paste the copied characters before the cursor" },
|
{ on = "P", run = "paste --before", desc = "Paste the copied characters before the cursor" },
|
||||||
|
|
||||||
# Undo/Redo
|
# Undo/Redo
|
||||||
{ on = "u", run = "undo", desc = "Undo the last operation" },
|
{ on = "u", run = "undo", desc = "Undo the last operation" },
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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,10 @@ 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()));
|
} else {
|
||||||
if opt.url.zip(self.current_or(opt.tab).hovered()).is_some_and(|(u, f)| &u == f.url()) {
|
self.current_or_mut(opt.tab).repos(None);
|
||||||
// `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 +37,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 +50,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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -110,8 +110,9 @@ impl Manager {
|
||||||
};
|
};
|
||||||
|
|
||||||
let find = |folder: Option<&Folder>| {
|
let find = |folder: Option<&Folder>| {
|
||||||
folder.is_some_and(|folder| {
|
folder.filter(|&f| p == *f.loc).is_some_and(|folder| {
|
||||||
p == *folder.loc && folder.files.iter().any(|f| f.is_dir() && url == f.url())
|
let loc = url.to_loc(&folder.loc);
|
||||||
|
folder.files.iter().any(|f| f.is_dir() && f.urn() == loc.urn())
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ impl Manager {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut ops = vec![opt.op];
|
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));
|
ops.push(ops[0].chroot(u));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,10 +44,10 @@ impl Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_tab(tab: &mut Tab, op: Cow<FilesOp>, tasks: &Tasks) {
|
fn update_tab(tab: &mut Tab, op: Cow<FilesOp>, tasks: &Tasks) {
|
||||||
let url = op.url();
|
let url = op.cwd();
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
@ -94,7 +97,7 @@ impl Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_hovered(tab: &mut Tab, op: Cow<FilesOp>) {
|
fn update_hovered(tab: &mut Tab, op: Cow<FilesOp>) {
|
||||||
let url = op.url();
|
let url = op.cwd();
|
||||||
let folder = tab.history.entry(url.clone()).or_insert_with(|| Folder::from(url));
|
let folder = tab.history.entry(url.clone()).or_insert_with(|| Folder::from(url));
|
||||||
|
|
||||||
let foreign = matches!(op, Cow::Borrowed(_));
|
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)),
|
|(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 folder = tab.history.entry(op.cwd().clone()).or_insert_with(|| Folder::from(op.cwd()));
|
||||||
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 {
|
||||||
|
|
|
||||||
|
|
@ -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.cwd().url()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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() }
|
||||||
|
|
|
||||||
|
|
@ -60,25 +60,22 @@ impl Watcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn trigger_dirs(&self, folders: &[&Folder]) {
|
pub(super) fn trigger_dirs(&self, folders: &[&Folder]) {
|
||||||
let todo: Vec<_> = folders
|
let todo: Vec<_> =
|
||||||
.iter()
|
folders.iter().filter(|&f| f.loc.is_regular()).map(|&f| (f.loc.url_owned(), f.cha)).collect();
|
||||||
.filter(|&f| f.loc.is_regular())
|
|
||||||
.map(|&f| (f.loc.url().clone(), f.cha))
|
|
||||||
.collect();
|
|
||||||
if todo.is_empty() {
|
if todo.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn go(url: Url, cha: Cha) {
|
async fn go(cwd: Url, cha: Cha) {
|
||||||
let Some(cha) = Files::assert_stale(&url, cha).await else { return };
|
let Some(cha) = Files::assert_stale(&cwd, cha).await else { return };
|
||||||
|
|
||||||
if let Ok(files) = Files::from_dir_bulk(&url).await {
|
if let Ok(files) = Files::from_dir_bulk(&cwd).await {
|
||||||
FilesOp::Full(url, files, cha).emit();
|
FilesOp::Full(cwd, files, cha).emit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tokio::spawn(async move {
|
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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,20 +39,20 @@ impl Tab {
|
||||||
return self.cd_interactive();
|
return self.cd_interactive();
|
||||||
}
|
}
|
||||||
|
|
||||||
if opt.target == *self.cwd() {
|
if opt.target == *self.cwd().url() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take parent to history
|
// Take parent to history
|
||||||
if let Some(rep) = self.parent.take() {
|
if let Some(rep) = self.parent.take() {
|
||||||
self.history.insert(rep.loc.url().clone(), rep);
|
self.history.insert(rep.loc.url_owned(), rep);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current
|
// Current
|
||||||
let rep = self.history.remove_or(&opt.target);
|
let rep = self.history.remove_or(&opt.target);
|
||||||
let rep = mem::replace(&mut self.current, rep);
|
let rep = mem::replace(&mut self.current, rep);
|
||||||
if rep.loc.is_regular() {
|
if rep.loc.is_regular() {
|
||||||
self.history.insert(rep.loc.url().clone(), rep);
|
self.history.insert(rep.loc.url_owned(), rep);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parent
|
// Parent
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,29 +38,29 @@ impl Preview {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn go_folder(&mut self, file: File, dir: Option<Cha>, force: bool) {
|
pub fn go_folder(&mut self, file: File, dir: Option<Cha>, force: bool) {
|
||||||
let (cha, url) = (file.cha, file.url_owned());
|
let (cha, cwd) = (file.cha, file.url_owned());
|
||||||
self.go(file, MIME_DIR, force);
|
self.go(file, MIME_DIR, force);
|
||||||
|
|
||||||
if self.content_unchanged(&url, cha) {
|
if self.content_unchanged(&cwd, cha) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.folder_loader.take().map(|h| h.abort());
|
self.folder_loader.take().map(|h| h.abort());
|
||||||
self.folder_loader = Some(tokio::spawn(async move {
|
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;
|
return;
|
||||||
};
|
};
|
||||||
let Ok(rx) = Files::from_dir(&url).await else { return };
|
let Ok(rx) = Files::from_dir(&cwd).await else { return };
|
||||||
|
|
||||||
let stream =
|
let stream =
|
||||||
UnboundedReceiverStream::new(rx).chunks_timeout(50000, Duration::from_millis(500));
|
UnboundedReceiverStream::new(rx).chunks_timeout(50000, Duration::from_millis(500));
|
||||||
pin!(stream);
|
pin!(stream);
|
||||||
|
|
||||||
let ticket = FilesOp::prepare(&url);
|
let ticket = FilesOp::prepare(&cwd);
|
||||||
while let Some(chunk) = stream.next().await {
|
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();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -112,10 +112,10 @@ impl Selected {
|
||||||
};
|
};
|
||||||
|
|
||||||
if !removal.is_empty() {
|
if !removal.is_empty() {
|
||||||
self.remove_many(&removal, !op.url().is_search());
|
self.remove_many(&removal, !op.cwd().is_search());
|
||||||
}
|
}
|
||||||
if !addition.is_empty() {
|
if !addition.is_empty() {
|
||||||
self.add_many(&addition, !op.url().is_search());
|
self.add_many(&addition, !op.cwd().is_search());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -86,7 +86,6 @@ impl Tab {
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- History
|
// --- History
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn hovered_folder(&self) -> Option<&Folder> {
|
pub fn hovered_folder(&self) -> Option<&Folder> {
|
||||||
self.current.hovered().filter(|&h| h.is_dir()).and_then(|h| self.history.get(h.url()))
|
self.current.hovered().filter(|&h| h.is_dir()).and_then(|h| self.history.get(h.url()))
|
||||||
|
|
@ -98,12 +97,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 +111,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ impl Folder {
|
||||||
|
|
||||||
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_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("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("stage", |lua, me| lua.create_any_userdata(me.stage));
|
||||||
reg.add_field_method_get("window", |_, me| Files::make(me.window.clone(), me, me.tab()));
|
reg.add_field_method_get("window", |_, me| Files::make(me.window.clone(), me, me.tab()));
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
|
|
||||||
|
|
@ -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};
|
||||||
|
|
||||||
|
|
@ -96,19 +96,19 @@ impl Files {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn assert_stale(url: &Url, cha: Cha) -> Option<Cha> {
|
pub async fn assert_stale(cwd: &Url, cha: Cha) -> Option<Cha> {
|
||||||
match fs::metadata(url).await.map(Cha::from) {
|
match fs::metadata(cwd).await.map(Cha::from) {
|
||||||
Ok(c) if !c.is_dir() => {
|
Ok(c) if !c.is_dir() => {
|
||||||
// FIXME: use `ErrorKind::NotADirectory` instead once it gets stabilized
|
// 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) if c.hits(cha) => {}
|
||||||
Ok(c) => return Some(c),
|
Ok(c) => return Some(c),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if maybe_exists(url).await {
|
if maybe_exists(cwd).await {
|
||||||
FilesOp::IOErr(url.clone(), e.kind()).emit();
|
FilesOp::IOErr(cwd.clone(), e.kind()).emit();
|
||||||
} else if let Some(p) = url.parent_url() {
|
} else if let Some(p) = cwd.parent_url() {
|
||||||
FilesOp::Deleting(p, vec![url.clone()]).emit();
|
FilesOp::Deleting(p, vec![cwd.clone()]).emit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -280,10 +280,9 @@ impl Files {
|
||||||
}
|
}
|
||||||
|
|
||||||
let (mut hidden, mut items) = if let Some(filter) = &self.filter {
|
let (mut hidden, mut items) = if let Some(filter) = &self.filter {
|
||||||
files.into_iter().partition(|(_, f)| {
|
files
|
||||||
(f.is_hidden() && !self.show_hidden)
|
.into_iter()
|
||||||
|| !f.url().file_name().is_some_and(|s| filter.matches(s))
|
.partition(|(_, f)| (f.is_hidden() && !self.show_hidden) || !filter.matches(f.name()))
|
||||||
})
|
|
||||||
} else if self.show_hidden {
|
} else if self.show_hidden {
|
||||||
(HashMap::new(), files)
|
(HashMap::new(), files)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -330,10 +329,9 @@ impl Files {
|
||||||
|
|
||||||
fn split_files(&self, files: impl IntoIterator<Item = File>) -> (Vec<File>, Vec<File>) {
|
fn split_files(&self, files: impl IntoIterator<Item = File>) -> (Vec<File>, Vec<File>) {
|
||||||
if let Some(filter) = &self.filter {
|
if let Some(filter) = &self.filter {
|
||||||
files.into_iter().partition(|f| {
|
files
|
||||||
(f.is_hidden() && !self.show_hidden)
|
.into_iter()
|
||||||
|| !f.url().file_name().is_some_and(|s| filter.matches(s))
|
.partition(|f| (f.is_hidden() && !self.show_hidden) || !filter.matches(f.name()))
|
||||||
})
|
|
||||||
} else if self.show_hidden {
|
} else if self.show_hidden {
|
||||||
(vec![], files.into_iter().collect())
|
(vec![], files.into_iter().collect())
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -345,8 +343,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]
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
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_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();
|
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_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();
|
tx.send(file).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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, ()| {
|
||||||
|
|
|
||||||
|
|
@ -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)]
|
||||||
|
|
@ -34,8 +34,8 @@ impl File {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn from_search(cwd: &Url, url: Url) -> Result<Self> {
|
pub async fn from_search_item(cwd: &Url, url: Url) -> Result<Self> {
|
||||||
let loc = Loc::from_search(cwd, url);
|
let loc = Loc::from_search_item(cwd, url);
|
||||||
let meta = fs::symlink_metadata(loc.url()).await?;
|
let meta = fs::symlink_metadata(loc.url()).await?;
|
||||||
Ok(Self::from_loc(loc, meta).await)
|
Ok(Self::from_loc(loc, meta).await)
|
||||||
}
|
}
|
||||||
|
|
@ -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() }
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
@ -36,7 +36,7 @@ impl Clone for Loc {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
let url = self.url.clone();
|
let url = self.url.clone();
|
||||||
let name = url.file_name().unwrap_or(OsStr::new("")) as *const OsStr;
|
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 }
|
Self { url, urn, name }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -57,7 +57,7 @@ impl Loc {
|
||||||
Self { url, urn, name: urn }
|
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 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;
|
let name = url.file_name().unwrap_or(OsStr::new("")) as *const OsStr;
|
||||||
Self { url, urn, name }
|
Self { url, urn, name }
|
||||||
|
|
@ -66,14 +66,14 @@ impl Loc {
|
||||||
pub fn rebase(&self, parent: &Url) -> Self {
|
pub fn rebase(&self, parent: &Url) -> Self {
|
||||||
let url = parent.join(self.name());
|
let url = parent.join(self.name());
|
||||||
let name = url.file_name().unwrap_or(OsStr::new("")) as *const OsStr;
|
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 }
|
Self { url, urn, name }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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 } }
|
||||||
|
|
|
||||||
|
|
@ -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::*;
|
||||||
|
|
|
||||||
|
|
@ -21,18 +21,18 @@ pub enum FilesOp {
|
||||||
|
|
||||||
impl FilesOp {
|
impl FilesOp {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn url(&self) -> &Url {
|
pub fn cwd(&self) -> &Url {
|
||||||
match self {
|
match self {
|
||||||
Self::Full(url, ..) => url,
|
Self::Full(u, ..) => u,
|
||||||
Self::Part(url, ..) => url,
|
Self::Part(u, ..) => u,
|
||||||
Self::Done(url, ..) => url,
|
Self::Done(u, ..) => u,
|
||||||
Self::Size(url, _) => url,
|
Self::Size(u, _) => u,
|
||||||
Self::IOErr(url, _) => url,
|
Self::IOErr(u, _) => u,
|
||||||
|
|
||||||
Self::Creating(url, _) => url,
|
Self::Creating(u, _) => u,
|
||||||
Self::Deleting(url, _) => url,
|
Self::Deleting(u, _) => u,
|
||||||
Self::Updating(url, _) => url,
|
Self::Updating(u, _) => u,
|
||||||
Self::Upserting(url, _) => url,
|
Self::Upserting(u, _) => u,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,9 +41,9 @@ impl FilesOp {
|
||||||
emit!(Call(Cmd::new("update_files").with_any("op", self), Layer::Manager));
|
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);
|
let ticket = FILES_TICKET.fetch_add(1, Ordering::Relaxed);
|
||||||
Self::Part(url.clone(), vec![], ticket).emit();
|
Self::Part(cwd.clone(), vec![], ticket).emit();
|
||||||
ticket
|
ticket
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 percent_encoding::{percent_decode_str, percent_encode, AsciiSet, CONTROLS};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::Loc;
|
||||||
|
|
||||||
const ENCODE_SET: &AsciiSet = &CONTROLS.add(b'#');
|
const ENCODE_SET: &AsciiSet = &CONTROLS.add(b'#');
|
||||||
|
|
||||||
#[derive(Clone, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
#[derive(Clone, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||||
|
|
@ -161,6 +163,14 @@ impl Url {
|
||||||
pub fn is_hidden(&self) -> bool {
|
pub fn is_hidden(&self) -> bool {
|
||||||
self.file_name().map_or(false, |s| s.as_encoded_bytes().starts_with(b"."))
|
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 {
|
impl Url {
|
||||||
|
|
@ -189,6 +199,9 @@ impl Url {
|
||||||
Self { scheme: UrlScheme::Search, path: self.path.clone(), frag: frag.to_owned() }
|
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]
|
#[inline]
|
||||||
pub fn into_search_item(mut self) -> Self {
|
pub fn into_search_item(mut self) -> Self {
|
||||||
self.scheme = UrlScheme::SearchItem;
|
self.scheme = UrlScheme::SearchItem;
|
||||||
|
|
|
||||||
33
yazi-shared/src/fs/urn.rs
Normal file
33
yazi-shared/src/fs/urn.rs
Normal 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) }
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue