feat: check and refresh the file list when the terminal gains focus (#3561)

This commit is contained in:
三咲雅 misaki masa 2026-01-13 16:53:00 +08:00 committed by GitHub
parent 41e5717930
commit 0c8c973e6c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 95 additions and 69 deletions

View file

@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Tree view for the preset archive previewer ([#3525]) - Tree view for the preset archive previewer ([#3525])
- Support compressed tarballs (`.tar.gz`, `.tar.bz2`, etc.) in the preset archive previewer ([#3518]) - Support compressed tarballs (`.tar.gz`, `.tar.bz2`, etc.) in the preset archive previewer ([#3518])
- Check and refresh the file list when the terminal gains focus ([#3561])
- New `Path.os()` API creates an OS-native `Path` ([#3541]) - New `Path.os()` API creates an OS-native `Path` ([#3541])
### Fixed ### Fixed
@ -1601,3 +1602,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
[#3532]: https://github.com/sxyazi/yazi/pull/3532 [#3532]: https://github.com/sxyazi/yazi/pull/3532
[#3540]: https://github.com/sxyazi/yazi/pull/3540 [#3540]: https://github.com/sxyazi/yazi/pull/3540
[#3541]: https://github.com/sxyazi/yazi/pull/3541 [#3541]: https://github.com/sxyazi/yazi/pull/3541
[#3561]: https://github.com/sxyazi/yazi/pull/3561

View file

@ -21,11 +21,12 @@ impl Actions {
writeln!(s, " Version: {}", Self::process_output("ya", "--version"))?; writeln!(s, " Version: {}", Self::process_output("ya", "--version"))?;
writeln!(s, "\nConfig")?; writeln!(s, "\nConfig")?;
writeln!(s, " Yazi : {}", Self::config_state("yazi"))?; writeln!(s, " Init : {}", Self::config_state("init.lua"))?;
writeln!(s, " Keymap : {}", Self::config_state("keymap"))?; writeln!(s, " Yazi : {}", Self::config_state("yazi.toml"))?;
writeln!(s, " Theme : {}", Self::config_state("theme"))?; writeln!(s, " Keymap : {}", Self::config_state("keymap.toml"))?;
writeln!(s, " VFS : {}", Self::config_state("vfs"))?; writeln!(s, " Theme : {}", Self::config_state("theme.toml"))?;
writeln!(s, " Package : {}", Self::config_state("package"))?; writeln!(s, " VFS : {}", Self::config_state("vfs.toml"))?;
writeln!(s, " Package : {}", Self::config_state("package.toml"))?;
writeln!(s, " Dark/light flavor: {:?} / {:?}", THEME.flavor.dark, THEME.flavor.light)?; writeln!(s, " Dark/light flavor: {:?} / {:?}", THEME.flavor.dark, THEME.flavor.light)?;
writeln!(s, "\nEmulator")?; writeln!(s, "\nEmulator")?;
@ -125,7 +126,7 @@ impl Actions {
} }
fn config_state(name: &str) -> String { fn config_state(name: &str) -> String {
let p = Xdg::config_dir().join(format!("{name}.toml")); let p = Xdg::config_dir().join(name);
match std::fs::read_to_string(&p) { match std::fs::read_to_string(&p) {
Ok(s) if s.is_empty() => format!("{} (empty)", p.display()), Ok(s) if s.is_empty() => format!("{} (empty)", p.display()),
Ok(s) if s.trim().is_empty() => format!("{} (whitespaces)", p.display()), Ok(s) if s.trim().is_empty() => format!("{} (whitespaces)", p.display()),

View file

@ -0,0 +1,15 @@
use anyhow::Result;
use yazi_actor::Ctx;
use yazi_macro::act;
use yazi_parser::VoidOpt;
use yazi_shared::data::Data;
use crate::app::App;
impl App {
pub fn focus(&mut self, _: VoidOpt) -> Result<Data> {
let cx = &mut Ctx::active(&mut self.core);
act!(mgr:refresh, cx)
}
}

View file

@ -2,6 +2,7 @@ yazi_macro::mod_flat!(
accept_payload accept_payload
bootstrap bootstrap
deprecate deprecate
focus
mouse mouse
notify notify
plugin plugin

View file

@ -28,6 +28,7 @@ impl<'a> Dispatcher<'a> {
Event::Key(key) => self.dispatch_key(key), Event::Key(key) => self.dispatch_key(key),
Event::Mouse(mouse) => act!(mouse, self.app, mouse), Event::Mouse(mouse) => act!(mouse, self.app, mouse),
Event::Resize => act!(resize, self.app), Event::Resize => act!(resize, self.app),
Event::Focus => act!(focus, self.app),
Event::Paste(str) => self.dispatch_paste(str), Event::Paste(str) => self.dispatch_paste(str),
Event::Quit(opt) => act!(quit, self.app, opt), Event::Quit(opt) => act!(quit, self.app, opt),
}; };

View file

@ -64,8 +64,9 @@ impl Signals {
Event::Mouse(mouse).emit(); Event::Mouse(mouse).emit();
} }
} }
CrosstermEvent::Paste(str) => Event::Paste(str).emit(),
CrosstermEvent::Resize(..) => Event::Resize.emit(), CrosstermEvent::Resize(..) => Event::Resize.emit(),
CrosstermEvent::FocusGained => Event::Focus.emit(),
CrosstermEvent::Paste(str) => Event::Paste(str).emit(),
_ => {} _ => {}
} }
} }

View file

@ -1,7 +1,7 @@
use std::{io, ops::{Deref, DerefMut}, sync::atomic::{AtomicBool, Ordering}}; use std::{io, ops::{Deref, DerefMut}, sync::atomic::{AtomicBool, Ordering}};
use anyhow::Result; use anyhow::Result;
use crossterm::{event::{DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, execute, queue, style::Print, terminal::{EnterAlternateScreen, LeaveAlternateScreen, SetTitle, disable_raw_mode, enable_raw_mode}}; use crossterm::{event::{DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, EnableFocusChange, EnableMouseCapture, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, execute, queue, style::Print, terminal::{EnterAlternateScreen, LeaveAlternateScreen, SetTitle, disable_raw_mode, enable_raw_mode}};
use ratatui::{CompletedFrame, Frame, Terminal, backend::CrosstermBackend, buffer::Buffer, layout::Rect}; use ratatui::{CompletedFrame, Frame, Terminal, backend::CrosstermBackend, buffer::Buffer, layout::Rect};
use yazi_adapter::{Emulator, Mux, TMUX}; use yazi_adapter::{Emulator, Mux, TMUX};
use yazi_config::{THEME, YAZI}; use yazi_config::{THEME, YAZI};
@ -40,6 +40,7 @@ impl Term {
yazi_term::If(TMUX.get(), EnterAlternateScreen), yazi_term::If(TMUX.get(), EnterAlternateScreen),
yazi_term::SetBackground(true, THEME.app.bg_color()), // Set app background yazi_term::SetBackground(true, THEME.app.bg_color()), // Set app background
EnableBracketedPaste, EnableBracketedPaste,
EnableFocusChange,
yazi_term::If(!YAZI.mgr.mouse_events.get().is_empty(), EnableMouseCapture), yazi_term::If(!YAZI.mgr.mouse_events.get().is_empty(), EnableMouseCapture),
)?; )?;
@ -81,6 +82,7 @@ impl Term {
TTY.writer(), TTY.writer(),
yazi_term::If(!YAZI.mgr.mouse_events.get().is_empty(), DisableMouseCapture), yazi_term::If(!YAZI.mgr.mouse_events.get().is_empty(), DisableMouseCapture),
yazi_term::RestoreCursor, yazi_term::RestoreCursor,
DisableFocusChange,
DisableBracketedPaste, DisableBracketedPaste,
LeaveAlternateScreen, LeaveAlternateScreen,
)?; )?;
@ -103,6 +105,7 @@ impl Term {
yazi_term::If(!YAZI.mgr.mouse_events.get().is_empty(), DisableMouseCapture), yazi_term::If(!YAZI.mgr.mouse_events.get().is_empty(), DisableMouseCapture),
yazi_term::SetBackground(false, THEME.app.bg_color()), yazi_term::SetBackground(false, THEME.app.bg_color()),
yazi_term::RestoreCursor, yazi_term::RestoreCursor,
DisableFocusChange,
DisableBracketedPaste, DisableBracketedPaste,
LeaveAlternateScreen, LeaveAlternateScreen,
crossterm::cursor::Show crossterm::cursor::Show

View file

@ -455,6 +455,7 @@ pub(crate) enum FileOutUpload {
Deform(String), Deform(String),
Succ, Succ,
Fail(String), Fail(String),
Clean,
} }
impl From<anyhow::Error> for FileOutUpload { impl From<anyhow::Error> for FileOutUpload {
@ -481,6 +482,9 @@ impl FileOutUpload {
prog.collected = Some(false); prog.collected = Some(false);
task.log(reason); task.log(reason);
} }
Self::Clean => {
prog.cleaned = Some(true);
}
} }
} }
} }

View file

@ -321,6 +321,7 @@ pub struct FileProgUpload {
pub total_bytes: u64, pub total_bytes: u64,
pub processed_bytes: u64, pub processed_bytes: u64,
pub collected: Option<bool>, pub collected: Option<bool>,
pub cleaned: Option<bool>,
} }
impl From<FileProgUpload> for TaskSummary { impl From<FileProgUpload> for TaskSummary {
@ -340,14 +341,16 @@ impl FileProgUpload {
} }
pub fn running(self) -> bool { pub fn running(self) -> bool {
self.collected.is_none() || self.success_files + self.failed_files != self.total_files self.collected.is_none()
|| self.success_files + self.failed_files != self.total_files
|| (self.cleaned.is_none() && self.cooked())
} }
pub fn success(self) -> bool { self.cooked() } pub fn success(self) -> bool { self.cleaned == Some(true) && self.cooked() }
pub fn failed(self) -> bool { self.collected == Some(false) } pub fn failed(self) -> bool { self.cleaned == Some(false) || self.collected == Some(false) }
pub fn cleaned(self) -> Option<bool> { None } pub fn cleaned(self) -> Option<bool> { self.cleaned }
pub fn percent(self) -> Option<f32> { pub fn percent(self) -> Option<f32> {
Some(if self.success() { Some(if self.success() {

View file

@ -7,7 +7,7 @@ use yazi_fs::ok_or_not_found;
use yazi_proxy::TasksProxy; use yazi_proxy::TasksProxy;
use yazi_vfs::provider; use yazi_vfs::provider;
use crate::{Ongoing, TaskOp, TaskOps, file::{FileOutCopy, FileOutCut, FileOutDelete, FileOutDownload, FileOutTrash}, hook::{HookInOutCopy, HookInOutCut, HookInOutDelete, HookInOutDownload, HookInOutTrash}}; use crate::{Ongoing, TaskOp, TaskOps, file::{FileOutCopy, FileOutCut, FileOutDelete, FileOutDownload, FileOutTrash, FileOutUpload}, hook::{HookInDelete, HookInDownload, HookInOutCopy, HookInOutCut, HookInTrash, HookInUpload}};
pub(crate) struct Hook { pub(crate) struct Hook {
ops: TaskOps, ops: TaskOps,
@ -41,7 +41,7 @@ impl Hook {
self.ops.out(task.id, FileOutCopy::Clean); self.ops.out(task.id, FileOutCopy::Clean);
} }
pub(crate) async fn delete(&self, task: HookInOutDelete) { pub(crate) async fn delete(&self, task: HookInDelete) {
if !self.ongoing.lock().intact(task.id) { if !self.ongoing.lock().intact(task.id) {
return self.ops.out(task.id, FileOutDelete::Clean(Ok(()))); return self.ops.out(task.id, FileOutDelete::Clean(Ok(())));
} }
@ -53,7 +53,7 @@ impl Hook {
self.ops.out(task.id, FileOutDelete::Clean(result)); self.ops.out(task.id, FileOutDelete::Clean(result));
} }
pub(crate) async fn trash(&self, task: HookInOutTrash) { pub(crate) async fn trash(&self, task: HookInTrash) {
let intact = self.ongoing.lock().intact(task.id); let intact = self.ongoing.lock().intact(task.id);
if intact { if intact {
TasksProxy::update_succeed([&task.target]); TasksProxy::update_succeed([&task.target]);
@ -62,7 +62,15 @@ impl Hook {
self.ops.out(task.id, FileOutTrash::Clean); self.ops.out(task.id, FileOutTrash::Clean);
} }
pub(crate) async fn download(&self, task: HookInOutDownload) { pub(crate) async fn download(&self, task: HookInDownload) {
self.ops.out(task.id, FileOutDownload::Clean); self.ops.out(task.id, FileOutDownload::Clean);
} }
pub(crate) async fn upload(&self, task: HookInUpload) {
let intact = self.ongoing.lock().intact(task.id);
if intact {
TasksProxy::update_succeed([task.target]);
}
self.ops.out(task.id, FileOutUpload::Clean);
}
} }

View file

@ -48,44 +48,27 @@ impl HookInOutCut {
// --- Delete // --- Delete
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct HookInOutDelete { pub(crate) struct HookInDelete {
pub(crate) id: Id, pub(crate) id: Id,
pub(crate) target: UrlBuf, pub(crate) target: UrlBuf,
} }
impl HookInOutDelete {
pub(crate) fn reduce(self, task: &mut Task) {
if let TaskProg::FileDelete(_) = &task.prog {
task.hook = Some(self.into());
}
}
}
// --- Trash // --- Trash
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct HookInOutTrash { pub(crate) struct HookInTrash {
pub(crate) id: Id, pub(crate) id: Id,
pub(crate) target: UrlBuf, pub(crate) target: UrlBuf,
} }
impl HookInOutTrash {
pub(crate) fn reduce(self, task: &mut Task) {
if let TaskProg::FileTrash(_) = &task.prog {
task.hook = Some(self.into());
}
}
}
// --- Download // --- Download
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct HookInOutDownload { pub(crate) struct HookInDownload {
pub(crate) id: Id, pub(crate) id: Id,
} }
impl HookInOutDownload { // --- Upload
pub(crate) fn reduce(self, task: &mut Task) { #[derive(Debug)]
if let TaskProg::FileDownload(_) = &task.prog { pub(crate) struct HookInUpload {
task.hook = Some(self.into()); pub(crate) id: Id,
} pub(crate) target: UrlBuf,
}
} }

View file

@ -1,6 +1,6 @@
use yazi_shared::Id; use yazi_shared::Id;
use crate::{file::{FileInCopy, FileInCut, FileInDelete, FileInDownload, FileInHardlink, FileInLink, FileInTrash, FileInUpload}, hook::{HookInOutCopy, HookInOutCut, HookInOutDelete, HookInOutDownload, HookInOutTrash}, impl_from_in, plugin::PluginInEntry, prework::{PreworkInFetch, PreworkInLoad, PreworkInSize}, process::{ProcessInBg, ProcessInBlock, ProcessInOrphan}}; use crate::{file::{FileInCopy, FileInCut, FileInDelete, FileInDownload, FileInHardlink, FileInLink, FileInTrash, FileInUpload}, hook::{HookInDelete, HookInDownload, HookInOutCopy, HookInOutCut, HookInTrash, HookInUpload}, impl_from_in, plugin::PluginInEntry, prework::{PreworkInFetch, PreworkInLoad, PreworkInSize}, process::{ProcessInBg, ProcessInBlock, ProcessInOrphan}};
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum TaskIn { pub(crate) enum TaskIn {
@ -26,9 +26,10 @@ pub(crate) enum TaskIn {
// Hook // Hook
HookCopy(HookInOutCopy), HookCopy(HookInOutCopy),
HookCut(HookInOutCut), HookCut(HookInOutCut),
HookDelete(HookInOutDelete), HookDelete(HookInDelete),
HookTrash(HookInOutTrash), HookTrash(HookInTrash),
HookDownload(HookInOutDownload), HookDownload(HookInDownload),
HookUpload(HookInUpload),
} }
impl_from_in! { impl_from_in! {
@ -41,7 +42,7 @@ impl_from_in! {
// Process // Process
ProcessBlock(ProcessInBlock), ProcessOrphan(ProcessInOrphan), ProcessBg(ProcessInBg), ProcessBlock(ProcessInBlock), ProcessOrphan(ProcessInOrphan), ProcessBg(ProcessInBg),
// Hook // Hook
HookCopy(HookInOutCopy), HookCut(HookInOutCut), HookDelete(HookInOutDelete), HookTrash(HookInOutTrash), HookDownload(HookInOutDownload), HookCopy(HookInOutCopy), HookCut(HookInOutCut), HookDelete(HookInDelete), HookTrash(HookInTrash), HookDownload(HookInDownload), HookUpload(HookInUpload),
} }
impl TaskIn { impl TaskIn {
@ -72,6 +73,7 @@ impl TaskIn {
Self::HookDelete(r#in) => r#in.id, Self::HookDelete(r#in) => r#in.id,
Self::HookTrash(r#in) => r#in.id, Self::HookTrash(r#in) => r#in.id,
Self::HookDownload(r#in) => r#in.id, Self::HookDownload(r#in) => r#in.id,
Self::HookUpload(r#in) => r#in.id,
} }
} }
@ -102,6 +104,7 @@ impl TaskIn {
Self::HookDelete(_) => true, Self::HookDelete(_) => true,
Self::HookTrash(_) => true, Self::HookTrash(_) => true,
Self::HookDownload(_) => true, Self::HookDownload(_) => true,
Self::HookUpload(_) => true,
} }
} }
} }

View file

@ -1,4 +1,4 @@
use crate::{Task, file::{FileOutCopy, FileOutCopyDo, FileOutCut, FileOutCutDo, FileOutDelete, FileOutDeleteDo, FileOutDownload, FileOutDownloadDo, FileOutHardlink, FileOutHardlinkDo, FileOutLink, FileOutTrash, FileOutUpload, FileOutUploadDo}, hook::{HookInOutCopy, HookInOutCut, HookInOutDelete, HookInOutDownload, HookInOutTrash}, impl_from_out, plugin::PluginOutEntry, prework::{PreworkOutFetch, PreworkOutLoad, PreworkOutSize}, process::{ProcessOutBg, ProcessOutBlock, ProcessOutOrphan}}; use crate::{Task, file::{FileOutCopy, FileOutCopyDo, FileOutCut, FileOutCutDo, FileOutDelete, FileOutDeleteDo, FileOutDownload, FileOutDownloadDo, FileOutHardlink, FileOutHardlinkDo, FileOutLink, FileOutTrash, FileOutUpload, FileOutUploadDo}, hook::{HookInOutCopy, HookInOutCut}, impl_from_out, plugin::PluginOutEntry, prework::{PreworkOutFetch, PreworkOutLoad, PreworkOutSize}, process::{ProcessOutBg, ProcessOutBlock, ProcessOutOrphan}};
#[derive(Debug)] #[derive(Debug)]
pub(super) enum TaskOut { pub(super) enum TaskOut {
@ -30,9 +30,6 @@ pub(super) enum TaskOut {
// Hook // Hook
HookCopy(HookInOutCopy), HookCopy(HookInOutCopy),
HookCut(HookInOutCut), HookCut(HookInOutCut),
HookDelete(HookInOutDelete),
HookTrash(HookInOutTrash),
HookDownload(HookInOutDownload),
} }
impl_from_out! { impl_from_out! {
@ -45,7 +42,7 @@ impl_from_out! {
// Process // Process
ProcessBlock(ProcessOutBlock), ProcessOrphan(ProcessOutOrphan), ProcessBg(ProcessOutBg), ProcessBlock(ProcessOutBlock), ProcessOrphan(ProcessOutOrphan), ProcessBg(ProcessOutBg),
// Hook // Hook
HookCopy(HookInOutCopy), HookCut(HookInOutCut), HookDelete(HookInOutDelete), HookTrash(HookInOutTrash), HookDownload(HookInOutDownload), HookCopy(HookInOutCopy), HookCut(HookInOutCut),
} }
impl TaskOut { impl TaskOut {
@ -79,9 +76,6 @@ impl TaskOut {
// Hook // Hook
Self::HookCopy(out) => out.reduce(task), Self::HookCopy(out) => out.reduce(task),
Self::HookCut(out) => out.reduce(task), Self::HookCut(out) => out.reduce(task),
Self::HookDelete(out) => out.reduce(task),
Self::HookTrash(out) => out.reduce(task),
Self::HookDownload(out) => out.reduce(task),
} }
} }
} }

View file

@ -39,6 +39,7 @@ impl Runner {
TaskIn::HookDelete(r#in) => Ok(self.hook.delete(r#in).await), TaskIn::HookDelete(r#in) => Ok(self.hook.delete(r#in).await),
TaskIn::HookTrash(r#in) => Ok(self.hook.trash(r#in).await), TaskIn::HookTrash(r#in) => Ok(self.hook.trash(r#in).await),
TaskIn::HookDownload(r#in) => Ok(self.hook.download(r#in).await), TaskIn::HookDownload(r#in) => Ok(self.hook.download(r#in).await),
TaskIn::HookUpload(r#in) => Ok(self.hook.upload(r#in).await),
} }
} }
@ -69,6 +70,7 @@ impl Runner {
TaskIn::HookDelete(_in) => unreachable!(), TaskIn::HookDelete(_in) => unreachable!(),
TaskIn::HookTrash(_in) => unreachable!(), TaskIn::HookTrash(_in) => unreachable!(),
TaskIn::HookDownload(_in) => unreachable!(), TaskIn::HookDownload(_in) => unreachable!(),
TaskIn::HookUpload(_in) => unreachable!(),
} }
} }
} }

View file

@ -7,7 +7,7 @@ use yazi_parser::{app::PluginOpt, tasks::ProcessOpenOpt};
use yazi_shared::{CompletionToken, Id, Throttle, url::{UrlBuf, UrlLike}}; use yazi_shared::{CompletionToken, Id, Throttle, url::{UrlBuf, UrlLike}};
use super::{Ongoing, TaskOp}; use super::{Ongoing, TaskOp};
use crate::{HIGH, LOW, NORMAL, Runner, TaskIn, TaskOps, file::{File, FileInCopy, FileInCut, FileInDelete, FileInDownload, FileInHardlink, FileInLink, FileInTrash, FileInUpload, FileOutCopy, FileOutCut, FileOutDownload, FileOutHardlink, FileOutUpload, FileProgCopy, FileProgCut, FileProgDelete, FileProgDownload, FileProgHardlink, FileProgLink, FileProgTrash, FileProgUpload}, hook::{Hook, HookInOutDelete, HookInOutDownload, HookInOutTrash}, plugin::{Plugin, PluginInEntry, PluginProgEntry}, prework::{Prework, PreworkInFetch, PreworkInLoad, PreworkInSize, PreworkProgFetch, PreworkProgLoad, PreworkProgSize}, process::{Process, ProcessInBg, ProcessInBlock, ProcessInOrphan, ProcessProgBg, ProcessProgBlock, ProcessProgOrphan}}; use crate::{HIGH, LOW, NORMAL, Runner, TaskIn, TaskOps, file::{File, FileInCopy, FileInCut, FileInDelete, FileInDownload, FileInHardlink, FileInLink, FileInTrash, FileInUpload, FileOutCopy, FileOutCut, FileOutDownload, FileOutHardlink, FileOutUpload, FileProgCopy, FileProgCut, FileProgDelete, FileProgDownload, FileProgHardlink, FileProgLink, FileProgTrash, FileProgUpload}, hook::{Hook, HookInDelete, HookInDownload, HookInTrash, HookInUpload}, plugin::{Plugin, PluginInEntry, PluginProgEntry}, prework::{Prework, PreworkInFetch, PreworkInLoad, PreworkInSize, PreworkProgFetch, PreworkProgLoad, PreworkProgSize}, process::{Process, ProcessInBg, ProcessInBlock, ProcessInOrphan, ProcessProgBg, ProcessProgBlock, ProcessProgOrphan}};
pub struct Scheduler { pub struct Scheduler {
ops: TaskOps, ops: TaskOps,
@ -163,7 +163,7 @@ impl Scheduler {
let mut ongoing = self.ongoing.lock(); let mut ongoing = self.ongoing.lock();
let task = ongoing.add::<FileProgDelete>(format!("Delete {}", target.display())); let task = ongoing.add::<FileProgDelete>(format!("Delete {}", target.display()));
task.set_hook(HookInOutDelete { id: task.id, target: target.clone() }); task.set_hook(HookInDelete { id: task.id, target: target.clone() });
self.queue(FileInDelete { id: task.id, target, cha: None }, LOW); self.queue(FileInDelete { id: task.id, target, cha: None }, LOW);
} }
@ -171,7 +171,7 @@ impl Scheduler {
let mut ongoing = self.ongoing.lock(); let mut ongoing = self.ongoing.lock();
let task = ongoing.add::<FileProgTrash>(format!("Trash {}", target.display())); let task = ongoing.add::<FileProgTrash>(format!("Trash {}", target.display()));
task.set_hook(HookInOutTrash { id: task.id, target: target.clone() }); task.set_hook(HookInTrash { id: task.id, target: target.clone() });
self.queue(FileInTrash { id: task.id, target }, LOW); self.queue(FileInTrash { id: task.id, target }, LOW);
} }
@ -179,8 +179,8 @@ impl Scheduler {
let mut ongoing = self.ongoing.lock(); let mut ongoing = self.ongoing.lock();
let task = ongoing.add::<FileProgDownload>(format!("Download {}", url.display())); let task = ongoing.add::<FileProgDownload>(format!("Download {}", url.display()));
task.set_hook(HookInOutDownload { id: task.id });
if url.kind().is_remote() { if url.kind().is_remote() {
task.set_hook(HookInDownload { id: task.id });
self.queue( self.queue(
FileInDownload { id: task.id, url, cha: None, retry: 0, done: task.done.clone() }, FileInDownload { id: task.id, url, cha: None, retry: 0, done: task.done.clone() },
LOW, LOW,
@ -202,6 +202,7 @@ impl Scheduler {
.out(task.id, FileOutUpload::Fail("Cannot upload non-remote file".to_owned())); .out(task.id, FileOutUpload::Fail("Cannot upload non-remote file".to_owned()));
}; };
task.set_hook(HookInUpload { id: task.id, target: url.clone() });
self.queue( self.queue(
FileInUpload { id: task.id, url, cha: None, cache: None, done: task.done.clone() }, FileInUpload { id: task.id, url, cha: None, cache: None, done: task.done.clone() },
LOW, LOW,

View file

@ -15,6 +15,7 @@ pub enum Event {
Key(KeyEvent), Key(KeyEvent),
Mouse(MouseEvent), Mouse(MouseEvent),
Resize, Resize,
Focus,
Paste(String), Paste(String),
Quit(EventQuit), Quit(EventQuit),
} }

View file

@ -1,7 +1,7 @@
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use anyhow::Result; use anyhow::Result;
use hashbrown::HashSet; use hashbrown::HashMap;
use tokio::{pin, sync::mpsc::UnboundedReceiver}; use tokio::{pin, sync::mpsc::UnboundedReceiver};
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_fs::{File, FilesOp}; use yazi_fs::{File, FilesOp};
@ -14,7 +14,7 @@ use crate::{Reporter, WATCHER};
pub(crate) struct Remote; pub(crate) struct Remote;
impl Remote { impl Remote {
pub(crate) fn serve(rx: UnboundedReceiver<UrlBuf>, _reporter: Reporter) -> Self { pub(crate) fn serve(rx: UnboundedReceiver<(UrlBuf, bool)>, _reporter: Reporter) -> Self {
tokio::spawn(Self::changed(rx)); tokio::spawn(Self::changed(rx));
Self Self
@ -24,19 +24,22 @@ impl Remote {
pub(crate) fn unwatch(&mut self, _url: Url) -> Result<()> { Ok(()) } pub(crate) fn unwatch(&mut self, _url: Url) -> Result<()> { Ok(()) }
async fn changed(rx: UnboundedReceiver<UrlBuf>) { async fn changed(rx: UnboundedReceiver<(UrlBuf, bool)>) {
let rx = UnboundedReceiverStream::new(rx).chunks_timeout(1000, Duration::from_millis(250)); let rx = UnboundedReceiverStream::new(rx).chunks_timeout(1000, Duration::from_millis(250));
pin!(rx); pin!(rx);
while let Some(chunk) = rx.next().await { while let Some(chunk) = rx.next().await {
let urls: HashSet<_> = chunk.into_iter().collect(); let mut urls = HashMap::with_capacity(chunk.len());
for (u, upload) in chunk {
urls.entry(u).and_modify(|b| *b |= upload).or_insert(upload);
}
let _permit = WATCHER.acquire().await.unwrap(); let _permit = WATCHER.acquire().await.unwrap();
let mut ops = Vec::with_capacity(urls.len()); let mut ops = Vec::with_capacity(urls.len());
let mut ups = Vec::with_capacity(urls.len()); let mut ups = Vec::with_capacity(urls.len());
for u in urls { for (u, upload) in urls {
let Some((parent, urn)) = u.pair() else { continue }; let Some((parent, urn)) = u.pair() else { continue };
let Ok(mut file) = File::new(&u).await else { let Ok(mut file) = File::new(&u).await else {
ops.push(FilesOp::Deleting(parent.into(), [urn.into()].into())); ops.push(FilesOp::Deleting(parent.into(), [urn.into()].into()));
@ -47,7 +50,7 @@ impl Remote {
file.cha.ctime = Some(SystemTime::now()); file.cha.ctime = Some(SystemTime::now());
ops.push(FilesOp::Upserting(parent.into(), [(urn.into(), file)].into())); ops.push(FilesOp::Upserting(parent.into(), [(urn.into(), file)].into()));
if is_file { if upload && is_file {
ups.push(u); ups.push(u);
} }
} }

View file

@ -9,7 +9,7 @@ use crate::{WATCHED, local::LINKED};
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct Reporter { pub(crate) struct Reporter {
pub(super) local_tx: mpsc::UnboundedSender<UrlBuf>, pub(super) local_tx: mpsc::UnboundedSender<UrlBuf>,
pub(super) remote_tx: mpsc::UnboundedSender<UrlBuf>, pub(super) remote_tx: mpsc::UnboundedSender<(UrlBuf, bool)>,
} }
impl Reporter { impl Reporter {
@ -49,9 +49,9 @@ impl Reporter {
let Some(dir) = watched.find_by_cache(parent.loc()) else { continue }; let Some(dir) = watched.find_by_cache(parent.loc()) else { continue };
let Some(name) = url.name() else { continue }; let Some(name) = url.name() else { continue };
if let Ok(u) = dir.try_join(Cow::from(percent_decode(name.encoded_bytes()))) { if let Ok(u) = dir.try_join(Cow::from(percent_decode(name.encoded_bytes()))) {
self.remote_tx.send(u).ok(); self.remote_tx.send((u, true)).ok();
} }
self.remote_tx.send(dir).ok(); self.remote_tx.send((dir, false)).ok();
} }
} }
@ -61,7 +61,7 @@ impl Reporter {
return; return;
} }
self.remote_tx.send(parent.to_owned()).ok(); self.remote_tx.send((parent.to_owned(), false)).ok();
self.remote_tx.send(url.into_owned()).ok(); self.remote_tx.send((url.into_owned(), false)).ok();
} }
} }