mirror of
https://github.com/sxyazi/yazi.git
synced 2024-09-11 18:38:19 +03:00
Compare commits
2 Commits
b7d184abaf
...
1714b23e8c
Author | SHA1 | Date | |
---|---|---|---|
|
1714b23e8c | ||
|
73d5d34b88 |
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)?;
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
44
yazi-core/src/manager/linked.rs
Normal file
44
yazi-core/src/manager/linked.rs
Normal 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()
|
||||
}
|
||||
}
|
@ -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::*;
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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) {
|
||||
|
@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user