fix: directories are recreated after deletion did not update the watcher tracking properly (#381)

This commit is contained in:
三咲雅 · Misaki Masa 2023-11-21 00:50:26 +08:00 committed by GitHub
parent a6f76400f8
commit ac039c628e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 181 additions and 118 deletions

29
Cargo.lock generated
View file

@ -557,9 +557,9 @@ dependencies = [
[[package]]
name = "errno"
version = "0.3.6"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e"
checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8"
dependencies = [
"libc",
"windows-sys 0.48.0",
@ -1576,9 +1576,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.38.21"
version = "0.38.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e"
dependencies = [
"bitflags 2.4.1",
"errno",
@ -2017,11 +2017,12 @@ dependencies = [
[[package]]
name = "tracing-appender"
version = "0.2.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e"
checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf"
dependencies = [
"crossbeam-channel",
"thiserror",
"time",
"tracing-subscriber",
]
@ -2049,9 +2050,9 @@ dependencies = [
[[package]]
name = "tracing-log"
version = "0.1.4"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
@ -2060,9 +2061,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.3.17"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
dependencies = [
"nu-ansi-term",
"sharded-slab",
@ -2608,18 +2609,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.7.25"
version = "0.7.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cd369a67c0edfef15010f980c3cbe45d7f651deac2cd67ce097cd801de16557"
checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.25"
version = "0.7.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b"
checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f"
dependencies = [
"proc-macro2",
"quote",

View file

@ -1,6 +1,6 @@
use yazi_config::keymap::{Exec, KeymapLayer};
use crate::{completion::Completion, emit};
use crate::{completion::Completion, emit, input::Input};
pub struct Opt {
submit: bool,
@ -11,13 +11,15 @@ impl From<&Exec> for Opt {
}
impl Completion {
#[inline]
pub fn _close() {
emit!(Call(Exec::call("close", vec![]).vec(), KeymapLayer::Completion));
}
pub fn close(&mut self, opt: impl Into<Opt>) -> bool {
let opt = opt.into() as Opt;
if opt.submit {
emit!(Call(
Exec::call("complete", vec![self.selected().into()]).with("ticket", self.ticket).vec(),
KeymapLayer::Input
));
Input::_complete(self.selected(), self.ticket);
}
self.caches.clear();

View file

@ -6,14 +6,14 @@ use yazi_config::keymap::{Exec, KeymapLayer};
use crate::{completion::Completion, emit};
pub struct Opt<'a> {
before: &'a str,
word: &'a str,
ticket: usize,
}
impl<'a> From<&'a Exec> for Opt<'a> {
fn from(e: &'a Exec) -> Self {
Self {
before: e.named.get("before").map(|s| s.as_str()).unwrap_or_default(),
word: e.args.first().map(|s| s.as_str()).unwrap_or_default(),
ticket: e.named.get("ticket").and_then(|s| s.parse().ok()).unwrap_or(0),
}
}
@ -21,11 +21,11 @@ impl<'a> From<&'a Exec> for Opt<'a> {
impl Completion {
#[inline]
fn split_path(s: &str) -> (String, String) {
match s.rsplit_once(MAIN_SEPARATOR) {
Some((p, c)) => (format!("{p}{}", MAIN_SEPARATOR), c.to_owned()),
None => (".".to_owned(), s.to_owned()),
}
pub fn _trigger(word: &str, ticket: usize) {
emit!(Call(
Exec::call("trigger", vec![word.to_owned()]).with("ticket", ticket).vec(),
KeymapLayer::Completion
));
}
pub fn trigger<'a>(&mut self, opt: impl Into<Opt<'a>>) -> bool {
@ -35,7 +35,7 @@ impl Completion {
}
self.ticket = opt.ticket;
let (parent, child) = Self::split_path(opt.before);
let (parent, child) = Self::split_path(opt.word);
if self.caches.contains_key(&parent) {
return self.show(
@ -78,6 +78,14 @@ impl Completion {
mem::replace(&mut self.visible, false)
}
#[inline]
fn split_path(s: &str) -> (String, String) {
match s.rsplit_once(MAIN_SEPARATOR) {
Some((p, c)) => (format!("{p}{}", MAIN_SEPARATOR), c.to_owned()),
None => (".".to_owned(), s.to_owned()),
}
}
}
#[cfg(test)]

View file

@ -21,11 +21,9 @@ pub enum Event {
Call(Vec<Exec>, KeymapLayer),
// Manager
Refresh,
Files(FilesOp),
Pages(usize),
Mimetype(BTreeMap<Url, String>),
Hover(Option<Url>),
Peek(Option<(usize, Url)>),
Preview(PreviewLock),
@ -82,12 +80,6 @@ macro_rules! emit {
(Mimetype($mimes:expr)) => {
$crate::Event::Mimetype($mimes).emit();
};
(Hover) => {
$crate::Event::Hover(None).emit();
};
(Hover($file:expr)) => {
$crate::Event::Hover(Some($file)).emit();
};
(Peek) => {
$crate::Event::Peek(None).emit();
};

View file

@ -1,7 +1,7 @@
use yazi_config::keymap::{Exec, KeymapLayer};
use yazi_config::keymap::Exec;
use yazi_shared::InputError;
use crate::{emit, input::Input};
use crate::{completion::Completion, input::Input};
pub struct Opt {
submit: bool,
@ -19,7 +19,7 @@ impl Input {
let opt = opt.into() as Opt;
if self.completion {
emit!(Call(Exec::call("close", vec![]).vec(), KeymapLayer::Completion));
Completion::_close();
}
if let Some(cb) = self.callback.take() {

View file

@ -1,8 +1,8 @@
use std::path::MAIN_SEPARATOR;
use yazi_config::keymap::Exec;
use yazi_config::keymap::{Exec, KeymapLayer};
use crate::input::Input;
use crate::{emit, input::Input};
pub struct Opt<'a> {
word: &'a str,
@ -19,6 +19,14 @@ impl<'a> From<&'a Exec> for Opt<'a> {
}
impl Input {
#[inline]
pub fn _complete(word: &str, ticket: usize) {
emit!(Call(
Exec::call("complete", vec![word.to_owned()]).with("ticket", ticket).vec(),
KeymapLayer::Input
));
}
pub fn complete<'a>(&mut self, opt: impl Into<Opt<'a>>) -> bool {
let opt = opt.into() as Opt;
if self.ticket != opt.ticket {

View file

@ -1,6 +1,6 @@
use yazi_config::keymap::{Exec, KeymapLayer};
use yazi_config::keymap::Exec;
use crate::{emit, input::{op::InputOp, Input, InputMode}};
use crate::{completion::Completion, input::{op::InputOp, Input, InputMode}};
pub struct Opt;
@ -26,7 +26,7 @@ impl Input {
self.move_(-1);
if self.completion {
emit!(Call(Exec::call("close", vec![]).vec(), KeymapLayer::Completion));
Completion::_close();
}
}
}

View file

@ -43,8 +43,8 @@ impl Manager {
Url::from(path.components().take(cwd.components().count() + 1).collect::<PathBuf>());
if let Ok(f) = File::from(child.clone()).await {
emit!(Files(FilesOp::Creating(cwd, f.into_map())));
emit!(Hover(child));
emit!(Refresh);
Manager::_hover(Some(child));
Manager::_refresh();
}
Ok::<(), anyhow::Error>(())
});

View file

@ -0,0 +1,47 @@
use std::collections::BTreeSet;
use yazi_config::keymap::{Exec, KeymapLayer};
use yazi_shared::Url;
use crate::{emit, manager::Manager};
pub struct Opt {
url: Option<Url>,
}
impl From<&Exec> for Opt {
fn from(e: &Exec) -> Self { Self { url: e.args.first().map(Url::from) } }
}
impl Manager {
#[inline]
pub fn _hover(url: Option<Url>) {
emit!(Call(
Exec::call("hover", url.map_or_else(Vec::new, |u| vec![u.to_string()])).vec(),
KeymapLayer::Manager
));
}
pub fn hover(&mut self, opt: impl Into<Opt>) -> bool {
// Refresh watcher
let mut to_watch = BTreeSet::new();
for tab in self.tabs.iter() {
to_watch.insert(&tab.current.cwd);
match tab.current.hovered() {
Some(h) if h.is_dir() => _ = to_watch.insert(&h.url),
_ => {}
}
if let Some(ref p) = tab.parent {
to_watch.insert(&p.cwd);
}
}
self.watcher.watch(to_watch);
// Trigger peek
emit!(Peek);
// Hover
let opt = opt.into() as Opt;
self.current_mut().repos(opt.url)
}
}

View file

@ -1,5 +1,6 @@
mod close;
mod create;
mod hover;
mod link;
mod open;
mod paste;

View file

@ -1,10 +1,24 @@
use std::{collections::BTreeSet, env};
use std::env;
use yazi_config::keymap::{Exec, KeymapLayer};
use crate::{emit, manager::Manager};
pub struct Opt;
impl From<&Exec> for Opt {
fn from(_: &Exec) -> Self { Self }
}
impl Manager {
pub fn refresh(&mut self) {
#[inline]
pub fn _refresh() {
emit!(Call(Exec::call("refresh", vec![]).vec(), KeymapLayer::Manager));
}
pub fn refresh(&mut self, _: impl Into<Opt>) -> bool {
env::set_current_dir(self.cwd()).ok();
env::set_var("PWD", self.cwd());
self.active_mut().apply_files_attrs(false);
@ -13,19 +27,8 @@ impl Manager {
} else {
self.watcher.trigger_dirs(&[self.cwd()]);
}
emit!(Hover);
let mut to_watch = BTreeSet::new();
for tab in self.tabs.iter() {
to_watch.insert(&tab.current.cwd);
match tab.current.hovered() {
Some(h) if h.is_dir() => _ = to_watch.insert(&h.url),
_ => {}
}
if let Some(ref p) = tab.parent {
to_watch.insert(&p.cwd);
}
}
self.watcher.watch(to_watch);
Self::_hover(None);
false
}
}

View file

@ -27,7 +27,7 @@ impl Manager {
let file = File::from(new.clone()).await?;
emit!(Files(FilesOp::Creating(file.parent().unwrap(), file.into_map())));
emit!(Hover(new));
Self::_hover(Some(new));
Ok(())
}

View file

@ -3,7 +3,7 @@ use std::collections::{BTreeMap, HashMap, HashSet};
use yazi_shared::Url;
use super::{Tabs, Watcher};
use crate::{emit, files::{File, FilesOp}, tab::{Folder, Tab}, tasks::Tasks};
use crate::{files::{File, FilesOp}, tab::{Folder, Tab}, tasks::Tasks};
pub struct Manager {
pub tabs: Tabs,
@ -46,7 +46,7 @@ impl Manager {
b |= hovered.as_ref().is_some_and(|h| self.current_mut().hover(h));
if hovered.as_ref() != self.hovered().map(|h| &h.url) {
emit!(Hover);
Self::_hover(None);
}
b
}

View file

@ -1,7 +1,7 @@
use yazi_config::BOOT;
use yazi_shared::Url;
use crate::{emit, tab::Tab};
use crate::{manager::Manager, tab::Tab};
pub struct Tabs {
pub idx: usize,
@ -32,7 +32,7 @@ impl Tabs {
pub(super) fn set_idx(&mut self, idx: usize) {
self.idx = idx;
self.active_mut().preview.reset(|l| l.is_image());
emit!(Refresh);
Manager::_refresh();
}
}

View file

@ -91,8 +91,8 @@ impl Watcher {
*guard = watched
.into_iter()
.map(|k| {
if let Some((k, v)) = guard.remove_entry(k) {
(k, v)
if let Some(old) = guard.remove_entry(k) {
old
} else {
to_resolve.push(k.clone());
(k.clone(), None)
@ -102,17 +102,14 @@ impl Watcher {
let lock = self.watched.clone();
tokio::spawn(async move {
let mut ext = IndexMap::new();
for k in to_resolve {
match fs::canonicalize(&k).await {
Ok(v) if v != *k => {
ext.insert(k, Some(Url::from(v)));
lock.write().insert(k, Some(Url::from(v)));
}
_ => {}
}
}
lock.write().extend(ext);
});
}

View file

@ -1,6 +1,6 @@
use yazi_config::keymap::Exec;
use crate::{emit, tab::Tab, Step};
use crate::{manager::Manager, tab::Tab, Step};
pub struct Opt {
step: Step,
@ -41,7 +41,7 @@ impl Tab {
}
}
emit!(Hover);
Manager::_hover(None);
true
}
}

View file

@ -5,7 +5,7 @@ use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
use yazi_config::{keymap::{Exec, KeymapLayer}, popup::InputOpt};
use yazi_shared::{expand_path, Debounce, InputError, Url};
use crate::{emit, tab::Tab};
use crate::{completion::Completion, emit, manager::Manager, tab::Tab};
pub struct Opt {
target: Url,
@ -27,6 +27,11 @@ impl From<Url> for Opt {
}
impl Tab {
#[inline]
pub fn _cd(target: &Url) {
emit!(Call(Exec::call("cd", vec![target.to_string()]).vec(), KeymapLayer::Manager));
}
pub fn cd(&mut self, opt: impl Into<Opt>) -> bool {
let opt = opt.into() as Opt;
if opt.interactive {
@ -59,7 +64,7 @@ impl Tab {
self.backstack.push(opt.target.clone());
}
emit!(Refresh);
Manager::_refresh();
true
}
@ -75,24 +80,19 @@ impl Tab {
while let Some(result) = rx.next().await {
match result {
Ok(s) => {
let p = expand_path(s);
let Ok(meta) = fs::metadata(&p).await else {
let u = Url::from(expand_path(s));
let Ok(meta) = fs::metadata(&u).await else {
return;
};
emit!(Call(
Exec::call(if meta.is_dir() { "cd" } else { "reveal" }, vec![
p.to_string_lossy().to_string()
])
.vec(),
KeymapLayer::Manager
));
if meta.is_dir() {
Tab::_cd(&u);
} else {
Tab::_reveal(&u);
}
}
Err(InputError::Completed(before, ticket)) => {
emit!(Call(
Exec::call("complete", vec![]).with("before", before).with("ticket", ticket).vec(),
KeymapLayer::Input
));
Completion::_trigger(&before, ticket);
}
_ => break,
}

View file

@ -2,7 +2,7 @@ use std::mem;
use yazi_config::keymap::Exec;
use crate::{emit, tab::Tab};
use crate::{manager::Manager, tab::Tab};
pub struct Opt;
impl From<()> for Opt {
@ -34,7 +34,7 @@ impl Tab {
// Backstack
self.backstack.push(hovered);
emit!(Refresh);
Manager::_refresh();
true
}
}

View file

@ -1,4 +1,4 @@
use yazi_config::keymap::{Exec, KeymapLayer};
use yazi_config::keymap::Exec;
use yazi_shared::{ends_with_slash, Defer};
use crate::{emit, external::{self, FzfOpt, ZoxideOpt}, tab::Tab, Event, BLOCKER};
@ -39,15 +39,21 @@ impl Tab {
let _defer = Defer::new(|| Event::Stop(false, None).emit());
emit!(Stop(true)).await;
let url = if opt.type_ == OptType::Fzf {
let result = if opt.type_ == OptType::Fzf {
external::fzf(FzfOpt { cwd }).await
} else {
external::zoxide(ZoxideOpt { cwd }).await
}?;
};
let op = if opt.type_ == OptType::Fzf && !ends_with_slash(&url) { "reveal" } else { "cd" };
emit!(Call(Exec::call(op, vec![url.to_string()]).vec(), KeymapLayer::Manager));
Ok::<(), anyhow::Error>(())
let Ok(url) = result else {
return;
};
if opt.type_ == OptType::Fzf && !ends_with_slash(&url) {
Tab::_reveal(&url)
} else {
Tab::_cd(&url)
}
});
false
}

View file

@ -2,7 +2,7 @@ use std::mem;
use yazi_config::keymap::Exec;
use crate::{emit, tab::Tab};
use crate::{manager::Manager, tab::Tab};
pub struct Opt;
impl From<()> for Opt {
@ -43,7 +43,7 @@ impl Tab {
// Backstack
self.backstack.push(current);
emit!(Refresh);
Manager::_refresh();
true
}
}

View file

@ -1,7 +1,7 @@
use yazi_config::keymap::Exec;
use yazi_config::keymap::{Exec, KeymapLayer};
use yazi_shared::{expand_path, Url};
use crate::{emit, files::{File, FilesOp}, tab::Tab};
use crate::{emit, files::{File, FilesOp}, manager::Manager, tab::Tab};
pub struct Opt {
target: Url,
@ -22,6 +22,11 @@ impl From<Url> for Opt {
}
impl Tab {
#[inline]
pub fn _reveal(target: &Url) {
emit!(Call(Exec::call("reveal", vec![target.to_string()]).vec(), KeymapLayer::Manager));
}
pub fn reveal(&mut self, opt: impl Into<Opt>) -> bool {
let opt = opt.into() as Opt;
@ -34,7 +39,7 @@ impl Tab {
parent.clone(),
File::from_dummy(opt.target.clone()).into_map()
)));
emit!(Hover(opt.target));
Manager::_hover(Some(opt.target));
b
}
}

View file

@ -3,9 +3,9 @@ use std::{mem, time::Duration};
use anyhow::bail;
use tokio::pin;
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
use yazi_config::{keymap::{Exec, KeymapLayer}, popup::InputOpt};
use yazi_config::{keymap::Exec, popup::InputOpt};
use crate::{emit, external, files::FilesOp, tab::Tab};
use crate::{emit, external, files::FilesOp, manager::Manager, tab::Tab};
pub struct Opt {
pub type_: OptType,
@ -61,7 +61,7 @@ impl Tab {
let mut first = true;
while let Some(chunk) = rx.next().await {
if first {
emit!(Call(Exec::call("cd", vec![cwd.clone().to_string()]).vec(), KeymapLayer::Manager));
Tab::_cd(&cwd);
first = false;
}
emit!(Files(FilesOp::Part(cwd.clone(), ticket, chunk)));
@ -80,7 +80,7 @@ impl Tab {
let rep = self.history_new(&self.current.cwd.to_regular());
drop(mem::replace(&mut self.current, rep));
emit!(Refresh);
Manager::_refresh();
}
false
}

View file

@ -5,7 +5,7 @@ use crossterm::event::KeyEvent;
use ratatui::prelude::Rect;
use tokio::sync::oneshot;
use yazi_config::{keymap::{Exec, Key, KeymapLayer}, BOOT};
use yazi_core::{emit, files::FilesOp, input::InputMode, Ctx, Event};
use yazi_core::{emit, files::FilesOp, input::InputMode, manager::Manager, Ctx, Event};
use yazi_shared::Term;
use crate::{Executor, Logs, Panic, Root, Signals};
@ -110,7 +110,7 @@ impl App {
self.term = Some(Term::start().unwrap());
self.signals.stop_term(false);
emit!(Render);
emit!(Hover);
Manager::_hover(None);
}
if let Some(tx) = tx {
tx.send(()).ok();
@ -128,9 +128,6 @@ impl App {
let manager = &mut self.cx.manager;
let tasks = &mut self.cx.tasks;
match event {
Event::Refresh => {
manager.refresh();
}
Event::Files(op) => {
let calc = !matches!(op, FilesOp::Size(..) | FilesOp::IOErr(_));
let b = match op {
@ -154,12 +151,6 @@ impl App {
emit!(Peek);
}
}
Event::Hover(url) => {
if manager.current_mut().repos(url) {
emit!(Render);
}
emit!(Peek);
}
Event::Peek(sequent) => {
if let Some((max, url)) = sequent {
manager.active_mut().update_peek(max, url);

View file

@ -88,10 +88,12 @@ impl<'a> Executor<'a> {
};
}
on!(ACTIVE, escape);
on!(MANAGER, hover);
on!(MANAGER, refresh);
on!(MANAGER, quit, &self.cx.tasks);
on!(MANAGER, close, &self.cx.tasks);
on!(MANAGER, suspend);
on!(ACTIVE, escape);
// Navigation
on!(ACTIVE, arrow);
@ -212,7 +214,7 @@ impl<'a> Executor<'a> {
on!(move_, "move");
if exec.cmd.as_str() == "complete" {
return if exec.args.is_empty() {
return if exec.named.contains_key("trigger") {
self.cx.completion.trigger(exec)
} else {
self.cx.input.complete(exec)