diff --git a/yazi-core/src/manager/commands/peek.rs b/yazi-core/src/manager/commands/peek.rs index 91559a92..dd337d21 100644 --- a/yazi-core/src/manager/commands/peek.rs +++ b/yazi-core/src/manager/commands/peek.rs @@ -40,14 +40,6 @@ impl Manager { return false; } - let mime = if hovered.is_dir() { - MIME_DIR.to_owned() - } else if let Some(s) = self.mimetype.get(&hovered.url) { - s.to_owned() - } else { - return self.active_mut().preview.reset(); - }; - let hovered = hovered.clone(); if !self.active().preview.same_url(&hovered.url) { self.active_mut().preview.skip = 0; @@ -63,10 +55,19 @@ impl Manager { } } - if opt.force { - self.active_mut().preview.force(hovered, mime); + 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); + } + return false; + } + + if let Some(s) = self.mimetype.get(&hovered.url).cloned() { + self.active_mut().preview.go(hovered, &s, opt.force); } else { - self.active_mut().preview.go(hovered, mime); + return self.active_mut().preview.reset(); } false } diff --git a/yazi-core/src/manager/commands/update_files.rs b/yazi-core/src/manager/commands/update_files.rs index 158ae30b..232891e5 100644 --- a/yazi-core/src/manager/commands/update_files.rs +++ b/yazi-core/src/manager/commands/update_files.rs @@ -25,7 +25,7 @@ impl Manager { } else if matches!(self.hovered(), Some(h) if h.url == url) { self.active_mut().history.entry(url.clone()).or_insert_with(|| Folder::from(&url)); self.active_mut().apply_files_attrs(true); - self.active_mut().history.get_mut(&url).unwrap().update(op) + self.active_mut().history.get_mut(&url).unwrap().update(op) | self.peek(true) } else { self.active_mut().history.entry(url.clone()).or_insert_with(|| Folder::from(&url)).update(op); false @@ -59,17 +59,24 @@ impl Manager { let Ok(opt) = opt.try_into() else { return false; }; - let calc = !matches!(opt.op, FilesOp::Size(..) | FilesOp::IOErr(_) | FilesOp::Deleting(..)); - let b = match opt.op { - FilesOp::IOErr(..) => self.handle_ioerr(opt.op), - _ => self.handle_read(opt.op), - }; + + let mut ops = vec![opt.op]; + for u in self.watcher.linked.read().from_dir(ops[0].url()) { + ops.push(ops[0].chroot(u)); + } + + let mut b = false; + for op in ops { + b |= match op { + FilesOp::IOErr(..) => self.handle_ioerr(op), + _ => self.handle_read(op), + }; + } if calc { tasks.preload_sorted(&self.current().files); } - b } } diff --git a/yazi-core/src/manager/commands/update_mimetype.rs b/yazi-core/src/manager/commands/update_mimetype.rs index 2911007c..ffe15949 100644 --- a/yazi-core/src/manager/commands/update_mimetype.rs +++ b/yazi-core/src/manager/commands/update_mimetype.rs @@ -21,23 +21,38 @@ impl Manager { return false; }; - let updates: HashMap<_, _> = opt + let linked = self.watcher.linked.read(); + let updates = opt .data .into_table_string() .into_iter() .map(|(url, mime)| (Url::from(url), mime)) .filter(|(url, mime)| self.mimetype.get(url) != Some(mime)) - .collect(); + .fold(HashMap::new(), |mut map, (u, m)| { + for u in linked.from_file(&u) { + map.insert(u, m.clone()); + } + map.insert(u, m); + map + }); + drop(linked); if updates.is_empty() { return false; } - let paged = self.current().paginate(self.current().page); - tasks.preload_affected(paged, &updates); + let affected: Vec<_> = self + .current() + .paginate(self.current().page) + .iter() + .filter(|&f| updates.contains_key(&f.url)) + .cloned() + .collect(); self.mimetype.extend(updates); self.peek(false); + + tasks.preload_affected(&affected, &self.mimetype); true } } diff --git a/yazi-core/src/manager/linked.rs b/yazi-core/src/manager/linked.rs new file mode 100644 index 00000000..9d688feb --- /dev/null +++ b/yazi-core/src/manager/linked.rs @@ -0,0 +1,44 @@ +use std::{collections::BTreeMap, ops::{Deref, DerefMut}}; + +use yazi_shared::fs::Url; + +#[derive(Default)] +pub struct Linked(BTreeMap /* from ==> to */); + +impl Deref for Linked { + type Target = BTreeMap; + + fn deref(&self) -> &Self::Target { &self.0 } +} + +impl DerefMut for Linked { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } +} + +impl Linked { + pub fn from_dir(&self, url: &Url) -> Vec<&Url> { + if let Some(to) = self.get(url) { + self.iter().filter(|(k, v)| *v == to && *k != url).map(|(k, _)| k).collect() + } else { + self.iter().filter(|(_, v)| *v == url).map(|(k, _)| k).collect() + } + } + + pub fn from_file(&self, url: &Url) -> Vec { + if self.is_empty() { + return Default::default(); + } + + let Some(p) = url.parent_url() else { + return Default::default(); + }; + + let relatives = self.from_dir(&p); + if relatives.is_empty() { + return Default::default(); + } + + let name = url.file_name().unwrap(); + relatives.into_iter().map(|u| u.join(name)).collect() + } +} diff --git a/yazi-core/src/manager/mod.rs b/yazi-core/src/manager/mod.rs index 8e9223e0..8016b0e4 100644 --- a/yazi-core/src/manager/mod.rs +++ b/yazi-core/src/manager/mod.rs @@ -1,8 +1,10 @@ mod commands; +mod linked; mod manager; mod tabs; mod watcher; +pub use linked::*; pub use manager::*; pub use tabs::*; pub use watcher::*; diff --git a/yazi-core/src/manager/watcher.rs b/yazi-core/src/manager/watcher.rs index ee83fbed..b24ad702 100644 --- a/yazi-core/src/manager/watcher.rs +++ b/yazi-core/src/manager/watcher.rs @@ -8,12 +8,13 @@ use tracing::error; use yazi_plugin::isolate; use yazi_shared::fs::{File, FilesOp, Url}; +use super::Linked; use crate::folder::Files; pub struct Watcher { watcher: RecommendedWatcher, watched: Arc>>, - pub linked: Arc>>, // from ==> to + pub linked: Arc>, } impl Watcher { @@ -133,7 +134,7 @@ impl Watcher { pin!(rx); while let Some(urls) = rx.next().await { - let urls = urls.into_iter().collect::>(); + let urls: BTreeSet<_> = urls.into_iter().collect(); let mut reload = Vec::with_capacity(urls.len()); for u in urls { @@ -161,16 +162,3 @@ impl Watcher { } } } - -impl Watcher { - pub fn relatives(&self, url: &Url) -> BTreeSet { - let mut map = BTreeSet::from_iter([url.clone()]); - - let linked = self.linked.read(); - if let Some(to) = linked.get(url) { - map.extend(linked.iter().filter(|(_, v)| *v == to).map(|(k, _)| k).cloned()); - } - - map - } -} diff --git a/yazi-core/src/tab/preview.rs b/yazi-core/src/tab/preview.rs index 90247f91..54456592 100644 --- a/yazi-core/src/tab/preview.rs +++ b/yazi-core/src/tab/preview.rs @@ -1,26 +1,31 @@ +use std::time::Duration; + +use tokio::{pin, task::JoinHandle}; +use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt}; use tokio_util::sync::CancellationToken; use yazi_adaptor::ADAPTOR; use yazi_config::PLUGIN; use yazi_plugin::{external::Highlighter, utils::PreviewLock}; -use yazi_shared::fs::{Cha, File, Url}; +use yazi_shared::{fs::{Cha, File, FilesOp, Url}, MIME_DIR}; + +use crate::folder::Files; #[derive(Default)] pub struct Preview { pub lock: Option, pub skip: usize, - previewer_ct: Option, + previewer_ct: Option, + folder_handle: Option>, } impl Preview { - pub fn go(&mut self, file: File, mime: String) { - if !self.content_unchanged(&file.url, &file.cha) { - self.force(file, mime); + pub fn go(&mut self, file: File, mime: &str, force: bool) { + if !force && self.content_unchanged(&file.url, &file.cha) { + return; } - } - pub fn force(&mut self, file: File, mime: String) { - let Some(previewer) = PLUGIN.previewer(&file.url, &mime) else { + let Some(previewer) = PLUGIN.previewer(&file.url, mime) else { self.reset(); return; }; @@ -33,6 +38,31 @@ impl Preview { } } + pub fn go_folder(&mut self, file: File, force: bool) { + if !force && self.content_unchanged(&file.url, &file.cha) { + return; + } + + self.go(file.clone(), 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 { + FilesOp::IOErr(file.url).emit(); + return; + }; + + let stream = + UnboundedReceiverStream::new(rx).chunks_timeout(10000, Duration::from_millis(350)); + pin!(stream); + + let ticket = FilesOp::prepare(&file.url); + while let Some(chunk) = stream.next().await { + FilesOp::Part(file.url.clone(), chunk, ticket).emit(); + } + })); + } + #[inline] pub fn abort(&mut self) { self.previewer_ct.take().map(|ct| ct.cancel()); diff --git a/yazi-core/src/tasks/tasks.rs b/yazi-core/src/tasks/tasks.rs index 8e669a26..40bcfb90 100644 --- a/yazi-core/src/tasks/tasks.rs +++ b/yazi-core/src/tasks/tasks.rs @@ -172,7 +172,7 @@ impl Tasks { }; for rule in PLUGIN.preloaders(&f.url, mime, factors) { - if loaded.get(&f.url).is_some_and(|x| x & 1 << rule.id != 0) { + if loaded.get(&f.url).is_some_and(|x| x & (1 << rule.id) != 0) { continue; } if rule.multi { @@ -188,7 +188,11 @@ impl Tasks { let mut go = |rule: &PluginRule, targets: Vec<&File>| { for &f in &targets { - *loaded.entry(f.url.clone()).or_default() |= 1 << rule.id; + if let Some(n) = loaded.get_mut(&f.url) { + *n |= 1 << rule.id; + } else { + loaded.insert(f.url.clone(), 1 << rule.id); + } } self.scheduler.preload_paged(rule, targets); }; @@ -203,15 +207,15 @@ impl Tasks { } } - pub fn preload_affected(&self, paged: &[File], mimetype: &HashMap) { + pub fn preload_affected(&self, affected: &[File], mimetype: &HashMap) { { let mut loaded = self.scheduler.preload.rule_loaded.write(); - for u in mimetype.keys() { - loaded.remove(u); + for f in affected { + loaded.remove(&f.url); } } - self.preload_paged(paged, mimetype); + self.preload_paged(affected, mimetype); } pub fn preload_sorted(&self, targets: &Files) { diff --git a/yazi-shared/src/fs/op.rs b/yazi-shared/src/fs/op.rs index 19015b2b..eb6a67a9 100644 --- a/yazi-shared/src/fs/op.rs +++ b/yazi-shared/src/fs/op.rs @@ -45,25 +45,46 @@ impl FilesOp { ticket } - pub fn chroot(&self, new: Url) -> Self { - todo!() - // Full(Url, Vec), - // Part(Url, u64, Vec), - // Size(Url, BTreeMap), - // IOErr(Url), + pub fn chroot(&self, new: &Url) -> Self { + let old = self.url(); + macro_rules! new { + ($url:expr) => {{ new.join($url.strip_prefix(old).unwrap()) }}; + } + macro_rules! files { + ($files:expr) => {{ + $files + .iter() + .map(|file| { + let mut f = file.clone(); + f.url = new!(f.url); + f + }) + .collect() + }}; + } + macro_rules! map { + ($map:expr) => {{ + $map + .iter() + .map(|(k, v)| { + let mut f = v.clone(); + f.url = new!(f.url); + (new!(k), f) + }) + .collect() + }}; + } - // Creating(Url, BTreeMap), - // Deleting(Url, BTreeSet), - // Replacing(Url, BTreeMap), - - // match self { - // FilesOp::Full(_, vec) => {} - // FilesOp::Part(_, _, vec) => todo!(), - // FilesOp::Size(_, map) => todo!(), - // FilesOp::IOErr(_) => todo!(), - // FilesOp::Creating(_, map) => todo!(), - // FilesOp::Deleting(_, set) => todo!(), - // FilesOp::Replacing(_, map) => todo!(), - // } + let u = new.clone(); + match self { + Self::Full(_, files) => Self::Full(u, files!(files)), + Self::Part(_, files, ticket) => Self::Part(u, files!(files), *ticket), + Self::Size(_, map) => Self::Size(u, map.iter().map(|(k, v)| (new!(k), *v)).collect()), + Self::IOErr(_) => Self::IOErr(u), + Self::Creating(_, files) => Self::Creating(u, files!(files)), + Self::Deleting(_, urls) => Self::Deleting(u, urls.iter().map(|u| new!(u)).collect()), + Self::Updating(_, map) => Self::Updating(u, map!(map)), + Self::Upserting(_, map) => Self::Upserting(u, map!(map)), + } } }