mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
perf: detection of changes in the directory (#476)
This commit is contained in:
parent
63f81f6431
commit
43878fd92e
12 changed files with 152 additions and 83 deletions
|
|
@ -131,14 +131,12 @@ impl Files {
|
|||
}
|
||||
|
||||
pub fn update_full(&mut self, files: Vec<File>) {
|
||||
if files.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.ticket = FILES_TICKET.fetch_add(1, Ordering::Relaxed);
|
||||
self.revision += 1;
|
||||
|
||||
(self.hidden, self.items) = self.split_files(files);
|
||||
if !self.items.is_empty() {
|
||||
self.revision += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_part(&mut self, files: Vec<File>, ticket: u64) {
|
||||
|
|
@ -147,8 +145,11 @@ impl Files {
|
|||
return;
|
||||
}
|
||||
|
||||
self.revision += 1;
|
||||
let (hidden, items) = self.split_files(files);
|
||||
if !items.is_empty() {
|
||||
self.revision += 1;
|
||||
}
|
||||
|
||||
self.hidden.extend(hidden);
|
||||
self.items.extend(items);
|
||||
return;
|
||||
|
|
@ -340,6 +341,10 @@ impl Files {
|
|||
#[inline]
|
||||
pub fn position(&self, url: &Url) -> Option<usize> { self.iter().position(|f| &f.url == url) }
|
||||
|
||||
// --- Ticket
|
||||
#[inline]
|
||||
pub fn ticket(&self) -> u64 { self.ticket }
|
||||
|
||||
// --- Selected
|
||||
pub fn selected(&self, pending: &BTreeSet<usize>, unset: bool) -> Vec<&File> {
|
||||
if self.selected.is_empty() && (unset || pending.is_empty()) {
|
||||
|
|
@ -435,8 +440,10 @@ impl Files {
|
|||
if self.show_hidden { mem::take(&mut self.hidden) } else { mem::take(&mut self.items) };
|
||||
let (hidden, items) = self.split_files(take);
|
||||
|
||||
self.revision += 1;
|
||||
self.hidden.extend(hidden);
|
||||
self.items.extend(items);
|
||||
if !items.is_empty() {
|
||||
self.revision += 1;
|
||||
self.items.extend(items);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use std::time::SystemTime;
|
||||
|
||||
use ratatui::layout::Rect;
|
||||
use yazi_config::LAYOUT;
|
||||
use yazi_shared::{emit, fs::{File, FilesOp, Url}};
|
||||
|
|
@ -7,6 +9,7 @@ use crate::{folder::Files, Step};
|
|||
#[derive(Default)]
|
||||
pub struct Folder {
|
||||
pub cwd: Url,
|
||||
pub mtime: Option<SystemTime>,
|
||||
pub files: Files,
|
||||
|
||||
pub offset: usize,
|
||||
|
|
@ -25,9 +28,17 @@ impl From<&Url> for Folder {
|
|||
|
||||
impl Folder {
|
||||
pub fn update(&mut self, op: FilesOp) -> bool {
|
||||
let revision = self.files.revision;
|
||||
match op {
|
||||
FilesOp::Full(_, files) => self.files.update_full(files),
|
||||
FilesOp::Full(_, _, mtime) => self.mtime = mtime,
|
||||
FilesOp::Done(_, mtime, ticket) if ticket == self.files.ticket() => self.mtime = mtime,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match op {
|
||||
FilesOp::Full(_, files, _) => self.files.update_full(files),
|
||||
FilesOp::Part(_, files, ticket) => self.files.update_part(files, ticket),
|
||||
FilesOp::Done(..) => {}
|
||||
FilesOp::Size(_, sizes) => self.files.update_size(sizes),
|
||||
|
||||
FilesOp::Creating(_, files) => self.files.update_creating(files),
|
||||
|
|
@ -35,10 +46,8 @@ impl Folder {
|
|||
FilesOp::Updating(_, files) => _ = self.files.update_updating(files),
|
||||
FilesOp::Upserting(_, files) => self.files.update_upserting(files),
|
||||
}
|
||||
if !self.files.catchup_revision() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: use a better way to detect if the page is changed
|
||||
let old = self.page;
|
||||
self.prev(Default::default());
|
||||
|
||||
|
|
@ -46,7 +55,7 @@ impl Folder {
|
|||
self.set_page(true); // Force update
|
||||
}
|
||||
|
||||
true
|
||||
self.files.revision != revision
|
||||
}
|
||||
|
||||
pub fn set_page(&mut self, force: bool) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use yazi_shared::{emit, event::Exec, fs::Url, render, Layer, MIME_DIR};
|
||||
use yazi_shared::{emit, event::Exec, fs::Url, render, Layer};
|
||||
|
||||
use crate::manager::Manager;
|
||||
|
||||
|
|
@ -56,16 +56,13 @@ impl Manager {
|
|||
}
|
||||
|
||||
if hovered.is_dir() {
|
||||
if self.active().history.contains_key(&hovered.url) {
|
||||
self.active_mut().preview.go(hovered, MIME_DIR, opt.force);
|
||||
} else {
|
||||
self.active_mut().preview.go_folder(hovered, opt.force);
|
||||
}
|
||||
let mtime = self.active().history.get(&hovered.url).and_then(|f| f.mtime);
|
||||
self.active_mut().preview.go_folder(hovered, mtime, opt.force);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(s) = self.mimetype.get(&hovered.url).cloned() {
|
||||
self.active_mut().preview.go(hovered, &s, opt.force);
|
||||
if let Some(m) = self.mimetype.get(&hovered.url).cloned() {
|
||||
self.active_mut().preview.go(hovered, &m, opt.force);
|
||||
} else {
|
||||
render!(self.active_mut().preview.reset());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ impl Manager {
|
|||
self.active_mut().apply_files_attrs();
|
||||
|
||||
if let Some(p) = self.parent() {
|
||||
self.watcher.trigger_dirs(&[self.cwd(), &p.cwd]);
|
||||
self.watcher.trigger_dirs(&[self.current(), p]);
|
||||
} else {
|
||||
self.watcher.trigger_dirs(&[self.cwd()]);
|
||||
self.watcher.trigger_dirs(&[self.current()]);
|
||||
}
|
||||
|
||||
self.hover(None);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ impl Manager {
|
|||
|
||||
if let Some(p) = self.active_mut().parent.as_mut() {
|
||||
render!(p.update(op));
|
||||
render!(p.files.catchup_revision());
|
||||
render!(p.hover(&cwd));
|
||||
}
|
||||
|
||||
|
|
@ -32,6 +33,7 @@ impl Manager {
|
|||
let calc = !matches!(op, FilesOp::Size(..) | FilesOp::Deleting(..));
|
||||
|
||||
render!(self.current_mut().update(op));
|
||||
render!(self.current_mut().files.catchup_revision());
|
||||
render!(hovered.as_ref().is_some_and(|h| self.current_mut().hover(h)));
|
||||
|
||||
if hovered.as_ref() != self.hovered().map(|h| &h.url) {
|
||||
|
|
@ -44,9 +46,20 @@ impl Manager {
|
|||
|
||||
fn update_hovered(&mut self, op: FilesOp) {
|
||||
let url = op.url();
|
||||
self.active_mut().history.entry(url.clone()).or_insert_with(|| Folder::from(url)).update(op);
|
||||
let folder = match self.active_mut().history.get_mut(url) {
|
||||
Some(f) => f,
|
||||
None => {
|
||||
let f = Folder::from(url);
|
||||
self.active_mut().history.insert(url.clone(), f);
|
||||
self.active_mut().apply_files_attrs();
|
||||
self.active_mut().history.get_mut(url).unwrap()
|
||||
}
|
||||
};
|
||||
|
||||
self.peek(true);
|
||||
if folder.update(op) {
|
||||
folder.files.catchup_revision();
|
||||
self.peek(true);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_history(&mut self, op: FilesOp) {
|
||||
|
|
@ -84,7 +97,5 @@ impl Manager {
|
|||
self.update_history(op);
|
||||
}
|
||||
}
|
||||
|
||||
self.active_mut().apply_files_attrs();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use yazi_plugin::isolate;
|
|||
use yazi_shared::fs::{File, FilesOp, Url};
|
||||
|
||||
use super::Linked;
|
||||
use crate::folder::Files;
|
||||
use crate::folder::{Files, Folder};
|
||||
|
||||
pub struct Watcher {
|
||||
watcher: RecommendedWatcher,
|
||||
|
|
@ -83,19 +83,31 @@ impl Watcher {
|
|||
self.sync_linked();
|
||||
}
|
||||
|
||||
pub(super) fn trigger_dirs(&self, dirs: &[&Url]) {
|
||||
let urls: Vec<_> = dirs.iter().filter(|&u| u.is_regular()).map(|&u| u.clone()).collect();
|
||||
if urls.is_empty() {
|
||||
pub(super) fn trigger_dirs(&self, folders: &[&Folder]) {
|
||||
let todo: Vec<_> =
|
||||
folders.iter().filter(|&f| f.cwd.is_regular()).map(|&f| (f.cwd.clone(), f.mtime)).collect();
|
||||
if todo.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
for u in urls {
|
||||
if let Ok(rx) = Files::from_dir(&u).await {
|
||||
for (url, mtime) in todo {
|
||||
let Ok(meta) = fs::metadata(&url).await else {
|
||||
if let Ok(m) = fs::symlink_metadata(&url).await {
|
||||
FilesOp::Full(url, vec![], m.modified().ok()).emit();
|
||||
} else if let Some(p) = url.parent_url() {
|
||||
FilesOp::Deleting(p, vec![url]).emit();
|
||||
}
|
||||
continue;
|
||||
};
|
||||
|
||||
if meta.modified().ok() == mtime {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok(rx) = Files::from_dir(&url).await {
|
||||
let files: Vec<_> = UnboundedReceiverStream::new(rx).collect().await;
|
||||
FilesOp::Full(u, files).emit();
|
||||
} else if let Some(p) = u.parent_url() {
|
||||
FilesOp::Deleting(p, vec![u]).emit();
|
||||
FilesOp::Full(url, files, meta.modified().ok()).emit();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -129,7 +141,7 @@ impl Watcher {
|
|||
|
||||
async fn on_changed(rx: UnboundedReceiver<Url>) {
|
||||
// TODO: revert this once a new notification is implemented
|
||||
let rx = UnboundedReceiverStream::new(rx).chunks_timeout(10, Duration::from_millis(20));
|
||||
let rx = UnboundedReceiverStream::new(rx).chunks_timeout(50, Duration::from_millis(20));
|
||||
pin!(rx);
|
||||
|
||||
while let Some(urls) = rx.next().await {
|
||||
|
|
|
|||
|
|
@ -59,15 +59,14 @@ impl Tab {
|
|||
let rx = UnboundedReceiverStream::new(rx).chunks_timeout(1000, Duration::from_millis(300));
|
||||
pin!(rx);
|
||||
|
||||
let ticket = FilesOp::prepare(&cwd);
|
||||
let mut first = true;
|
||||
let (mut first, ticket) = (true, FilesOp::prepare(&cwd));
|
||||
while let Some(chunk) = rx.next().await {
|
||||
if first {
|
||||
if mem::replace(&mut first, false) {
|
||||
Tab::_cd(&cwd);
|
||||
first = false;
|
||||
}
|
||||
FilesOp::Part(cwd.clone(), chunk, ticket).emit();
|
||||
}
|
||||
FilesOp::Done(cwd, None, ticket).emit();
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::time::Duration;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use tokio::{pin, task::JoinHandle};
|
||||
use tokio::{fs, pin, task::JoinHandle};
|
||||
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use yazi_adaptor::ADAPTOR;
|
||||
|
|
@ -16,7 +16,7 @@ pub struct Preview {
|
|||
pub skip: usize,
|
||||
|
||||
previewer_ct: Option<CancellationToken>,
|
||||
folder_handle: Option<JoinHandle<()>>,
|
||||
folder_loader: Option<(Url, JoinHandle<()>)>,
|
||||
}
|
||||
|
||||
impl Preview {
|
||||
|
|
@ -38,29 +38,51 @@ impl Preview {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn go_folder(&mut self, file: File, force: bool) {
|
||||
pub fn go_folder(&mut self, file: File, mtime: Option<SystemTime>, force: bool) {
|
||||
if !force && self.content_unchanged(&file.url, &file.cha) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.go(file.clone(), MIME_DIR, force);
|
||||
let url = file.url();
|
||||
self.go(file, MIME_DIR, force);
|
||||
|
||||
self.folder_handle.take().map(|h| h.abort());
|
||||
self.folder_handle = Some(tokio::spawn(async move {
|
||||
let Ok(rx) = Files::from_dir(&file.url).await else {
|
||||
file.url.parent_url().map(|p| FilesOp::Deleting(p, vec![file.url]).emit());
|
||||
return;
|
||||
};
|
||||
if matches!(&self.folder_loader, Some((u, _)) if *u == url) {
|
||||
return;
|
||||
}
|
||||
|
||||
let stream =
|
||||
UnboundedReceiverStream::new(rx).chunks_timeout(10000, Duration::from_millis(350));
|
||||
pin!(stream);
|
||||
self.folder_loader.take().map(|(_, h)| h.abort());
|
||||
self.folder_loader = Some((
|
||||
url.clone(),
|
||||
tokio::spawn(async move {
|
||||
let Ok(meta) = fs::metadata(&url).await else {
|
||||
if let Ok(m) = fs::symlink_metadata(&url).await {
|
||||
FilesOp::Full(url, vec![], m.modified().ok()).emit();
|
||||
} else if let Some(p) = url.parent_url() {
|
||||
FilesOp::Deleting(p, vec![url]).emit();
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
let ticket = FilesOp::prepare(&file.url);
|
||||
while let Some(chunk) = stream.next().await {
|
||||
FilesOp::Part(file.url.clone(), chunk, ticket).emit();
|
||||
}
|
||||
}));
|
||||
if meta.modified().ok() == mtime {
|
||||
return;
|
||||
}
|
||||
|
||||
let Ok(rx) = Files::from_dir(&url).await else {
|
||||
FilesOp::Full(url, vec![], meta.modified().ok()).emit();
|
||||
return;
|
||||
};
|
||||
|
||||
let stream =
|
||||
UnboundedReceiverStream::new(rx).chunks_timeout(10000, Duration::from_millis(350));
|
||||
pin!(stream);
|
||||
|
||||
let ticket = FilesOp::prepare(&url);
|
||||
while let Some(chunk) = stream.next().await {
|
||||
FilesOp::Part(url.clone(), chunk, ticket).emit();
|
||||
}
|
||||
FilesOp::Done(url, meta.modified().ok(), ticket).emit();
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
|||
|
|
@ -82,15 +82,15 @@ impl Tab {
|
|||
render!(f.repos(hovered));
|
||||
};
|
||||
|
||||
if let Some(f) =
|
||||
self.current.hovered().filter(|h| h.is_dir()).and_then(|h| self.history.get_mut(&h.url))
|
||||
{
|
||||
apply(f);
|
||||
}
|
||||
|
||||
apply(&mut self.current);
|
||||
if let Some(parent) = self.parent.as_mut() {
|
||||
apply(parent);
|
||||
}
|
||||
|
||||
self
|
||||
.current
|
||||
.hovered()
|
||||
.filter(|h| h.is_dir())
|
||||
.and_then(|h| self.history.get_mut(&h.url))
|
||||
.map(apply);
|
||||
|
||||
self.parent.as_mut().map(apply);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use anyhow::{Ok, Result};
|
|||
use crossterm::event::KeyEvent;
|
||||
use yazi_config::keymap::Key;
|
||||
use yazi_core::input::InputMode;
|
||||
use yazi_shared::{event::{Event, Exec, NEED_RENDER}, term::Term, Layer};
|
||||
use yazi_shared::{emit, event::{Event, Exec, NEED_RENDER}, term::Term, Layer};
|
||||
|
||||
use crate::{lives::Lives, Ctx, Executor, Logs, Panic, Signals};
|
||||
|
||||
|
|
@ -26,24 +26,31 @@ impl App {
|
|||
app.render()?;
|
||||
|
||||
let mut events = Vec::with_capacity(10);
|
||||
let mut render_in_place = false;
|
||||
while app.signals.rx.recv_many(&mut events, 10).await > 0 {
|
||||
for event in events.drain(..) {
|
||||
match event {
|
||||
Event::Call(exec, layer) => app.dispatch_call(exec, layer),
|
||||
Event::Render => render_in_place = true,
|
||||
Event::Key(key) => app.dispatch_key(key),
|
||||
Event::Resize(cols, rows) => app.dispatch_resize(cols, rows)?,
|
||||
Event::Paste(str) => app.dispatch_paste(str),
|
||||
Event::Quit(no_cwd_file) => {
|
||||
app.quit(no_cwd_file)?;
|
||||
break;
|
||||
}
|
||||
Event::Key(key) => app.dispatch_key(key),
|
||||
Event::Paste(str) => app.dispatch_paste(str),
|
||||
Event::Resize(cols, rows) => app.dispatch_resize(cols, rows)?,
|
||||
Event::Call(exec, layer) => app.dispatch_call(exec, layer),
|
||||
event => app.dispatch_module(event),
|
||||
}
|
||||
}
|
||||
|
||||
if NEED_RENDER.swap(false, Ordering::Relaxed) {
|
||||
if render_in_place {
|
||||
render_in_place = false;
|
||||
app.render()?;
|
||||
}
|
||||
|
||||
if NEED_RENDER.swap(false, Ordering::Relaxed) {
|
||||
emit!(Render);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,14 @@ use crate::{term::Term, Layer, RoCell};
|
|||
|
||||
static TX: RoCell<UnboundedSender<Event>> = RoCell::new();
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Event {
|
||||
Quit(bool), // no-cwd-file
|
||||
Key(KeyEvent),
|
||||
Paste(String),
|
||||
Resize(u16, u16),
|
||||
Call(Vec<Exec>, Layer),
|
||||
Render,
|
||||
Key(KeyEvent),
|
||||
Resize(u16, u16),
|
||||
Paste(String),
|
||||
Quit(bool), // no-cwd-file
|
||||
|
||||
// Manager
|
||||
Pages(usize),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{collections::BTreeMap, sync::atomic::{AtomicU64, Ordering}};
|
||||
use std::{collections::BTreeMap, sync::atomic::{AtomicU64, Ordering}, time::SystemTime};
|
||||
|
||||
use super::File;
|
||||
use crate::{emit, event::Exec, fs::Url, Layer};
|
||||
|
|
@ -7,8 +7,9 @@ pub static FILES_TICKET: AtomicU64 = AtomicU64::new(0);
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum FilesOp {
|
||||
Full(Url, Vec<File>),
|
||||
Full(Url, Vec<File>, Option<SystemTime>),
|
||||
Part(Url, Vec<File>, u64),
|
||||
Done(Url, Option<SystemTime>, u64),
|
||||
Size(Url, BTreeMap<Url, u64>),
|
||||
|
||||
Creating(Url, Vec<File>),
|
||||
|
|
@ -21,8 +22,9 @@ impl FilesOp {
|
|||
#[inline]
|
||||
pub fn url(&self) -> &Url {
|
||||
match self {
|
||||
Self::Full(url, _) => url,
|
||||
Self::Full(url, ..) => url,
|
||||
Self::Part(url, ..) => url,
|
||||
Self::Done(url, ..) => url,
|
||||
Self::Size(url, _) => url,
|
||||
|
||||
Self::Creating(url, _) => url,
|
||||
|
|
@ -75,8 +77,9 @@ impl FilesOp {
|
|||
|
||||
let u = new.clone();
|
||||
match self {
|
||||
Self::Full(_, files) => Self::Full(u, files!(files)),
|
||||
Self::Full(_, files, mtime) => Self::Full(u, files!(files), *mtime),
|
||||
Self::Part(_, files, ticket) => Self::Part(u, files!(files), *ticket),
|
||||
Self::Done(_, mtime, ticket) => Self::Done(u, *mtime, *ticket),
|
||||
Self::Size(_, map) => Self::Size(u, map.iter().map(|(k, v)| (new!(k), *v)).collect()),
|
||||
|
||||
Self::Creating(_, files) => Self::Creating(u, files!(files)),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue