Compare commits

...

2 Commits

Author SHA1 Message Date
sxyazi
1714b23e8c
.. 2023-12-26 19:21:58 +08:00
sxyazi
73d5d34b88
.. 2023-12-26 18:07:27 +08:00
16 changed files with 208 additions and 87 deletions

View File

@ -1,4 +1,4 @@
use std::{env, path::Path, sync::Arc};
use std::{env, path::Path, sync::{atomic::Ordering, Arc}};
use anyhow::{anyhow, Result};
use ratatui::prelude::Rect;
@ -160,25 +160,13 @@ impl Adaptor {
pub(super) fn start(self) { Ueberzug::start(self); }
pub async fn image_show(self, path: &Path, rect: Rect) -> Result<(u32, u32)> {
let size = match self {
match self {
Self::Kitty => Kitty::image_show(path, rect).await,
Self::KittyOld => KittyOld::image_show(path, rect).await,
Self::Iterm2 => Iterm2::image_show(path, rect).await,
Self::Sixel => Sixel::image_show(path, rect).await,
_ => Ueberzug::image_show(path, rect).await,
}?;
let shown = Term::ratio()
.map(|(r1, r2)| Rect {
x: rect.x,
y: rect.y,
width: (size.0 as f64 / r1).ceil() as u16,
height: (size.1 as f64 / r2).ceil() as u16,
})
.unwrap_or(rect);
SHOWN.store(Some(Arc::new(shown)));
Ok(size)
}
}
pub fn image_hide(self) -> Result<()> {
@ -195,6 +183,20 @@ impl Adaptor {
}
}
#[inline]
pub(super) fn shown_store(rect: Rect, size: (u32, u32)) {
SHOWN.store(Some(Arc::new(
Term::ratio()
.map(|(r1, r2)| Rect {
x: rect.x,
y: rect.y,
width: (size.0 as f64 / r1).ceil() as u16,
height: (size.1 as f64 / r2).ceil() as u16,
})
.unwrap_or(rect),
)));
}
#[inline]
pub(super) fn needs_ueberzug(self) -> bool {
!matches!(self, Self::Kitty | Self::KittyOld | Self::Iterm2 | Self::Sixel)

View File

@ -18,6 +18,7 @@ impl Iterm2 {
let b = Self::encode(img).await?;
Adaptor::Iterm2.image_hide()?;
Adaptor::shown_store(rect, size);
Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| {
stdout.write_all(&b)?;
Ok(size)

View File

@ -318,6 +318,7 @@ impl Kitty {
let b = Self::encode(img).await?;
Adaptor::Kitty.image_hide()?;
Adaptor::shown_store(rect, size);
Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| {
stdout.write_all(&b)?;

View File

@ -18,6 +18,7 @@ impl KittyOld {
let b = Self::encode(img).await?;
Adaptor::KittyOld.image_hide()?;
Adaptor::shown_store(rect, size);
Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| {
stdout.write_all(&b)?;
Ok(size)

View File

@ -17,6 +17,7 @@ impl Sixel {
let b = Self::encode(img).await?;
Adaptor::Sixel.image_hide()?;
Adaptor::shown_store(rect, size);
Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| {
stdout.write_all(&b)?;
Ok(size)

View File

@ -44,6 +44,7 @@ impl Ueberzug {
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<(u32, u32)> {
if let Some(tx) = &*DEMON {
tx.send(Some((path.to_path_buf(), rect)))?;
Adaptor::shown_store(rect, (0, 0));
} else {
bail!("uninitialized ueberzug");
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -0,0 +1,44 @@
use std::{collections::BTreeMap, ops::{Deref, DerefMut}};
use yazi_shared::fs::Url;
#[derive(Default)]
pub struct Linked(BTreeMap<Url, Url> /* from ==> to */);
impl Deref for Linked {
type Target = BTreeMap<Url, Url>;
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<Url> {
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()
}
}

View File

@ -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::*;

View File

@ -34,8 +34,10 @@ impl Tabs {
return;
}
// We must reset it before changing the tab index.
self.active_mut().preview.reset_image();
// Reset the preview of the previous active tab
if let Some(active) = self.items.get_mut(self.idx) {
active.preview.reset_image();
}
self.idx = idx;
Manager::_refresh();

View File

@ -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<RwLock<BTreeSet<Url>>>,
pub linked: Arc<RwLock<BTreeMap<Url, Url>>>, // from ==> to
pub linked: Arc<RwLock<Linked>>,
}
impl Watcher {
@ -133,7 +134,7 @@ impl Watcher {
pin!(rx);
while let Some(urls) = rx.next().await {
let urls = urls.into_iter().collect::<BTreeSet<_>>();
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<Url> {
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
}
}

View File

@ -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<PreviewLock>,
pub skip: usize,
previewer_ct: Option<CancellationToken>,
previewer_ct: Option<CancellationToken>,
folder_handle: Option<JoinHandle<()>>,
}
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());

View File

@ -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<Url, String>) {
pub fn preload_affected(&self, affected: &[File], mimetype: &HashMap<Url, String>) {
{
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) {

View File

@ -45,25 +45,46 @@ impl FilesOp {
ticket
}
pub fn chroot(&self, new: Url) -> Self {
todo!()
// Full(Url, Vec<File>),
// Part(Url, u64, Vec<File>),
// Size(Url, BTreeMap<Url, u64>),
// 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<Url, File>),
// Deleting(Url, BTreeSet<Url>),
// Replacing(Url, BTreeMap<Url, File>),
// 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)),
}
}
}