use std::time::Duration; use tokio::{pin, task::JoinHandle}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use yazi_adapter::ADAPTOR; use yazi_config::{LAYOUT, YAZI}; use yazi_fs::{Entries, FilesOp, cha::Cha, file::File}; use yazi_macro::render; use yazi_runner::{RUNNER, previewer::{PeekError, PeekJob}}; use yazi_shared::{pool::Symbol, url::UrlBuf}; use yazi_vfs::{VfsEntries, VfsFilesOp}; use crate::{AppProxy, Highlighter, MgrProxy, tab::PreviewLock}; #[derive(Default)] pub struct Preview { pub lock: Option, pub skip: usize, handle: Option>, pub folder_lock: Option, folder_loader: Option>, } impl Preview { pub fn go(&mut self, file: File, mime: Symbol, force: bool) { if mime.is_empty() { return; // Wait till mimetype is resolved to avoid flickering } else if !force && self.same_lock(&file, &mime) { return; } let Some(previewer) = YAZI.plugin.previewers.matches(&file, &mime) else { return self.reset(); }; self.abort(); let job = PeekJob { previewer, file, mime, skip: self.skip }; self.handle = Some(tokio::spawn(async move { let mut rx = RUNNER.peek(&job).await; match rx.recv().await.unwrap_or(Err(PeekError::Cancelled)) { Ok(()) | Err(PeekError::Cancelled) => {} Err(PeekError::ShouldSync) => AppProxy::plugin_peek(job), Err(e) => MgrProxy::update_peeked_error(job, e.to_string()), } })); } pub fn go_folder(&mut self, file: File, dir: Option, mime: Symbol, force: bool) { if self.folder_lock.as_ref() == Some(&file.url) { return self.go(file, mime, force); } let wd = file.url_owned(); self.go(file, mime, force); self.folder_lock = Some(wd.clone()); self.folder_loader.take().map(|h| h.abort()); self.folder_loader = Some(tokio::spawn(async move { let Some(new) = Entries::assert_stale(&wd, dir.unwrap_or_default()).await else { return }; let rx = match Entries::from_dir(&wd).await { Ok(rx) => rx, Err(e) => return FilesOp::issue_error(&wd, e).await, }; let stream = UnboundedReceiverStream::new(rx).chunks_timeout(50000, Duration::from_millis(500)); pin!(stream); let ticket = FilesOp::prepare(&wd); while let Some(chunk) = stream.next().await { FilesOp::Part(wd.clone(), chunk, ticket).emit(); } FilesOp::Done(wd, new, ticket).emit(); })); } pub fn abort(&mut self) { self.handle.take().map(|ct| ct.abort()); Highlighter::abort(); } pub fn reset(&mut self) { self.abort(); ADAPTOR.image_hide().ok(); render!(self.lock.take().is_some()) } pub fn reset_image(&mut self) { self.abort(); ADAPTOR.image_hide().ok(); } pub fn same_url(&self, url: &UrlBuf) -> bool { matches!(&self.lock, Some(l) if l.url == *url) } pub fn same_file(&self, file: &File, mime: &str) -> bool { self.same_url(&file.url) && matches!(&self.lock , Some(l) if l.cha.hits(file.cha) && l.mime == mime && *l.area == LAYOUT.get().preview) } pub fn same_lock(&self, file: &File, mime: &str) -> bool { self.same_file(file, mime) && matches!(&self.lock, Some(l) if l.skip == self.skip) } pub fn same_folder(&self, url: &UrlBuf) -> bool { self.folder_lock.as_ref() == Some(url) } }