Skip to content

Commit

Permalink
perf: introduce URN to speed up large directory locating (#1648)
Browse files Browse the repository at this point in the history
  • Loading branch information
sxyazi authored Sep 16, 2024
1 parent 49639aa commit 28083d8
Show file tree
Hide file tree
Showing 31 changed files with 196 additions and 129 deletions.
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,23 @@ strip = true

[workspace.dependencies]
ansi-to-tui = "6.0.0"
anyhow = "1.0.86"
anyhow = "1.0.88"
arc-swap = "1.7.1"
base64 = "0.22.1"
bitflags = "2.6.0"
clap = { version = "4.5.17", features = [ "derive" ] }
crossterm = { version = "0.28.1", features = [ "event-stream" ] }
dirs = "5.0.1"
futures = "0.3.30"
globset = "0.4.14"
globset = "0.4.15"
libc = "0.2.158"
md-5 = "0.10.6"
mlua = { version = "0.9.9", features = [ "lua54", "serialize", "macros", "async" ] }
parking_lot = "0.12.3"
ratatui = { version = "0.28.1", features = [ "unstable-rendered-line-info" ] }
regex = "1.10.6"
scopeguard = "1.2.0"
serde = { version = "1.0.209", features = [ "derive" ] }
serde = { version = "1.0.210", features = [ "derive" ] }
serde_json = "1.0.128"
shell-words = "1.1.0"
tokio = { version = "1.40.0", features = [ "full" ] }
Expand Down
10 changes: 5 additions & 5 deletions yazi-config/preset/keymap.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ keymap = [
{ on = "h", run = "leave", desc = "Go back to the parent directory" },
{ on = "l", run = "enter", desc = "Enter the child directory" },

{ on = "<Left>", run = "leave", desc = "Go back to the parent directory" },
{ on = "<Right>", run = "enter", desc = "Enter the child directory" },
{ on = "<Left>", run = "leave", desc = "Go back to the parent directory" },
{ on = "<Right>", run = "enter", desc = "Enter the child directory" },

{ on = "H", run = "back", desc = "Go back to the previous directory" },
{ on = "L", run = "forward", desc = "Go forward to the next directory" },
Expand Down Expand Up @@ -249,9 +249,9 @@ keymap = [
{ on = "c", run = "delete --cut --insert", desc = "Cut the selected characters, and enter insert mode" },
{ on = "C", run = [ "delete --cut --insert", "move 999" ], desc = "Cut until the EOL, and enter insert mode" },
{ on = "x", run = [ "delete --cut", "move 1 --in-operating" ], desc = "Cut the current character" },
{ on = "y", run = "yank", desc = "Copy the selected characters" },
{ on = "p", run = "paste", desc = "Paste the copied characters after the cursor" },
{ on = "P", run = "paste --before", desc = "Paste the copied characters before the cursor" },
{ on = "y", run = "yank", desc = "Copy the selected characters" },
{ on = "p", run = "paste", desc = "Paste the copied characters after the cursor" },
{ on = "P", run = "paste --before", desc = "Paste the copied characters before the cursor" },

# Undo/Redo
{ on = "u", run = "undo", desc = "Undo the last operation" },
Expand Down
2 changes: 1 addition & 1 deletion yazi-core/src/manager/commands/bulk_rename.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ impl Manager {
return AppProxy::notify_warn("Bulk rename", "No text opener found");
};

let cwd = self.cwd().clone();
let cwd = self.cwd().url_owned();
let old: Vec<_> = self.selected_or_hovered(true).collect();

let root = max_common_root(&old);
Expand Down
31 changes: 21 additions & 10 deletions yazi-core/src/manager/commands/hover.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::HashSet;
use std::{collections::HashSet, path::PathBuf};

use yazi_dds::Pubsub;
use yazi_shared::{event::{Cmd, Data}, fs::Url, render};
use yazi_shared::{event::{Cmd, Data}, fs::{Url, Urn}, render};

use crate::manager::Manager;

Expand All @@ -25,13 +25,10 @@ impl From<Option<Url>> for Opt {
impl Manager {
pub fn hover(&mut self, opt: impl Into<Opt>) {
let opt = opt.into() as Opt;

// Hover on the file
render!(self.current_or_mut(opt.tab).repos(opt.url.as_ref()));
if opt.url.zip(self.current_or(opt.tab).hovered()).is_some_and(|(u, f)| &u == f.url()) {
// `hover(Some)` occurs after user actions, such as create, rename, reveal, etc.
// At this point, it's intuitive to track the location of this file regardless.
self.current_or_mut(opt.tab).tracing = true;
if let Some(u) = opt.url {
self.hover_do(u, opt.tab);
} else {
self.current_or_mut(opt.tab).repos(None);
}

// Repeek
Expand All @@ -40,7 +37,7 @@ impl Manager {
// Refresh watcher
let mut to_watch = HashSet::with_capacity(3 * self.tabs.len());
for tab in self.tabs.iter() {
to_watch.insert(tab.cwd());
to_watch.insert(tab.cwd().url());
if let Some(ref p) = tab.parent {
to_watch.insert(&p.loc);
}
Expand All @@ -53,4 +50,18 @@ impl Manager {
// Publish through DDS
Pubsub::pub_from_hover(self.active().idx, self.hovered().map(|h| h.url()));
}

fn hover_do(&mut self, url: Url, tab: Option<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;
}
}
}
5 changes: 3 additions & 2 deletions yazi-core/src/manager/commands/open.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,9 @@ impl Manager {
};

let find = |folder: Option<&Folder>| {
folder.is_some_and(|folder| {
p == *folder.loc && folder.files.iter().any(|f| f.is_dir() && url == f.url())
folder.filter(|&f| p == *f.loc).is_some_and(|folder| {
let loc = url.to_loc(&folder.loc);
folder.files.iter().any(|f| f.is_dir() && f.urn() == loc.urn())
})
};

Expand Down
2 changes: 1 addition & 1 deletion yazi-core/src/manager/commands/refresh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{manager::Manager, tasks::Tasks};
impl Manager {
pub fn refresh(&mut self, _: Cmd, tasks: &Tasks) {
env::set_current_dir(self.cwd()).ok();
env::set_var("PWD", self.cwd());
env::set_var("PWD", self.cwd().url());

if !MANAGER.title_format.is_empty() {
execute!(std::io::stderr(), SetTitle(self.title())).ok();
Expand Down
2 changes: 1 addition & 1 deletion yazi-core/src/manager/commands/tab_create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl Tabs {
} else {
tab.conf = self.active().conf.clone();
tab.apply_files_attrs();
tab.cd(self.active().cwd().clone());
tab.cd(self.active().cwd().url_owned());
}

self.items.insert(self.cursor + 1, tab);
Expand Down
27 changes: 15 additions & 12 deletions yazi-core/src/manager/commands/update_files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ impl Manager {
};

let mut ops = vec![opt.op];
for u in LINKED.read().from_dir(ops[0].url()) {
for u in LINKED.read().from_dir(ops[0].cwd()) {
ops.push(ops[0].chroot(u));
}

Expand All @@ -44,10 +44,10 @@ impl Manager {
}

fn update_tab(tab: &mut Tab, op: Cow<FilesOp>, tasks: &Tasks) {
let url = op.url();
let url = op.cwd();
tab.selected.apply_op(&op);

if url == tab.cwd() {
if url == tab.cwd().url() {
Self::update_current(tab, op, tasks);
} else if matches!(&tab.parent, Some(p) if url == &*p.loc) {
Self::update_parent(tab, op);
Expand All @@ -59,12 +59,15 @@ impl Manager {
}

fn update_parent(tab: &mut Tab, op: Cow<FilesOp>) {
let cwd = tab.cwd().clone();
let leave = matches!(*op, FilesOp::Deleting(_, ref urls) if urls.contains(&cwd));
let urn = tab.cwd().urn_owned();
// FIXME
let leave = false;
// let leave = matches!(*op, FilesOp::Deleting(_, ref urls) if
// urls.contains(&urn));

if let Some(f) = tab.parent.as_mut() {
render!(f.update(op.into_owned()));
render!(f.hover(&cwd));
render!(f.hover(urn._deref()));
}

if leave {
Expand All @@ -73,15 +76,15 @@ impl Manager {
}

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 foreign = matches!(op, Cow::Borrowed(_));
if !tab.current.update(op.into_owned()) {
return;
}

tab.current.repos(hovered);
tab.current.repos(hovered.as_ref().map(|u| u._deref()));
if foreign {
return;
}
Expand All @@ -94,7 +97,7 @@ impl Manager {
}

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 foreign = matches!(op, Cow::Borrowed(_));
Expand All @@ -112,10 +115,10 @@ impl Manager {
|(p, pp)| matches!(*op, FilesOp::Deleting(ref parent, ref urls) if *parent == pp && urls.contains(p)),
);

let folder = tab.history.entry(op.url().clone()).or_insert_with(|| Folder::from(op.url()));
let hovered = folder.hovered().filter(|_| folder.tracing).map(|h| h.url_owned());
let folder = tab.history.entry(op.cwd().clone()).or_insert_with(|| Folder::from(op.cwd()));
let hovered = folder.hovered().filter(|_| folder.tracing).map(|h| h.urn_owned());
if folder.update(op.into_owned()) {
folder.repos(hovered);
folder.repos(hovered.as_ref().map(|u| u._deref()));
}

if leave {
Expand Down
2 changes: 1 addition & 1 deletion yazi-core/src/manager/commands/update_paged.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ impl Manager {
return;
};

if opt.only_if.is_some_and(|u| u != *self.active().cwd()) {
if opt.only_if.is_some_and(|u| u != *self.cwd().url()) {
return;
}

Expand Down
4 changes: 2 additions & 2 deletions yazi-core/src/manager/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use ratatui::layout::Rect;
use yazi_adapter::Dimension;
use yazi_config::popup::{Origin, Position};
use yazi_fs::Folder;
use yazi_shared::fs::{File, Url};
use yazi_shared::fs::{File, Loc, Url};

use super::{Mimetype, Tabs, Watcher, Yanked};
use crate::tab::Tab;
Expand Down Expand Up @@ -39,7 +39,7 @@ impl Manager {

impl Manager {
#[inline]
pub fn cwd(&self) -> &Url { &self.current().loc }
pub fn cwd(&self) -> &Loc { self.active().cwd() }

#[inline]
pub fn active(&self) -> &Tab { self.tabs.active() }
Expand Down
17 changes: 7 additions & 10 deletions yazi-core/src/manager/watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,25 +60,22 @@ impl Watcher {
}

pub(super) fn trigger_dirs(&self, folders: &[&Folder]) {
let todo: Vec<_> = folders
.iter()
.filter(|&f| f.loc.is_regular())
.map(|&f| (f.loc.url().clone(), f.cha))
.collect();
let todo: Vec<_> =
folders.iter().filter(|&f| f.loc.is_regular()).map(|&f| (f.loc.url_owned(), f.cha)).collect();
if todo.is_empty() {
return;
}

async fn go(url: Url, cha: Cha) {
let Some(cha) = Files::assert_stale(&url, cha).await else { return };
async fn go(cwd: Url, cha: Cha) {
let Some(cha) = Files::assert_stale(&cwd, cha).await else { return };

if let Ok(files) = Files::from_dir_bulk(&url).await {
FilesOp::Full(url, files, cha).emit();
if let Ok(files) = Files::from_dir_bulk(&cwd).await {
FilesOp::Full(cwd, files, cha).emit();
}
}

tokio::spawn(async move {
futures::future::join_all(todo.into_iter().map(|(url, cha)| go(url, cha))).await;
futures::future::join_all(todo.into_iter().map(|(cwd, cha)| go(cwd, cha))).await;
});
}

Expand Down
6 changes: 3 additions & 3 deletions yazi-core/src/tab/commands/cd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,20 @@ impl Tab {
return self.cd_interactive();
}

if opt.target == *self.cwd() {
if opt.target == *self.cwd().url() {
return;
}

// Take parent to history
if let Some(rep) = self.parent.take() {
self.history.insert(rep.loc.url().clone(), rep);
self.history.insert(rep.loc.url_owned(), rep);
}

// Current
let rep = self.history.remove_or(&opt.target);
let rep = mem::replace(&mut self.current, rep);
if rep.loc.is_regular() {
self.history.insert(rep.loc.url().clone(), rep);
self.history.insert(rep.loc.url_owned(), rep);
}

// Parent
Expand Down
6 changes: 3 additions & 3 deletions yazi-core/src/tab/commands/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,13 @@ impl Tab {
ManagerProxy::update_paged(); // Update for paged files in next loop
}

let hovered = self.current.hovered().map(|f| f.url_owned());
let hovered = self.current.hovered().map(|f| f.urn_owned());
if !self.current.files.set_filter(filter) {
return;
}

self.current.repos(hovered.as_ref());
if self.current.hovered().map(|f| f.url()) != hovered.as_ref() {
self.current.repos(hovered.as_ref().map(|u| u._deref()));
if self.current.hovered().map(|f| f.urn()) != hovered.as_ref().map(|u| u._deref()) {
ManagerProxy::hover(None, self.idx);
}

Expand Down
2 changes: 1 addition & 1 deletion yazi-core/src/tab/commands/leave.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ impl Tab {
.current
.hovered()
.and_then(|h| h.parent())
.filter(|p| p != self.cwd())
.filter(|u| u != self.cwd().url())
.or_else(|| self.cwd().parent_url())
.map(|u| self.cd(u));
}
Expand Down
14 changes: 7 additions & 7 deletions yazi-core/src/tab/preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,29 +38,29 @@ impl Preview {
}

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);

if self.content_unchanged(&url, cha) {
if self.content_unchanged(&cwd, cha) {
return;
}

self.folder_loader.take().map(|h| h.abort());
self.folder_loader = Some(tokio::spawn(async move {
let Some(new) = Files::assert_stale(&url, dir.unwrap_or(Cha::dummy())).await else {
let Some(new) = Files::assert_stale(&cwd, dir.unwrap_or(Cha::dummy())).await else {
return;
};
let Ok(rx) = Files::from_dir(&url).await else { return };
let Ok(rx) = Files::from_dir(&cwd).await else { return };

let stream =
UnboundedReceiverStream::new(rx).chunks_timeout(50000, Duration::from_millis(500));
pin!(stream);

let ticket = FilesOp::prepare(&url);
let ticket = FilesOp::prepare(&cwd);
while let Some(chunk) = stream.next().await {
FilesOp::Part(url.clone(), chunk, ticket).emit();
FilesOp::Part(cwd.clone(), chunk, ticket).emit();
}
FilesOp::Done(url, new, ticket).emit();
FilesOp::Done(cwd, new, ticket).emit();
}));
}

Expand Down
Loading

0 comments on commit 28083d8

Please sign in to comment.