From 4a5154c03e6c8845d7e679e2458ac9d8c8fb1998 Mon Sep 17 00:00:00 2001 From: sxyazi Date: Wed, 12 Jul 2023 19:33:23 +0800 Subject: [PATCH] feat: jump around with fzf and zoxide --- Cargo.lock | 66 ++++++++++++------------- README.md | 7 +-- src/core/blocker.rs | 4 ++ src/core/event.rs | 8 ++- src/core/external/fd.rs | 1 + src/core/external/fzf.rs | 23 +++++++++ src/core/external/mod.rs | 4 ++ src/core/external/rg.rs | 1 + src/core/external/zoxide.rs | 30 +++++++++++ src/core/files/file.rs | 6 +++ src/core/files/files.rs | 4 ++ src/core/manager/manager.rs | 16 +++--- src/core/manager/tab.rs | 61 ++++++++++++++++++++--- src/core/manager/watcher.rs | 99 ++++++++++++++++++++++++++++--------- src/core/mod.rs | 2 + src/core/tasks/process.rs | 10 ++-- src/ui/app.rs | 11 +++-- src/ui/dispatcher.rs | 5 ++ 18 files changed, 273 insertions(+), 85 deletions(-) create mode 100644 src/core/blocker.rs create mode 100644 src/core/external/fzf.rs create mode 100644 src/core/external/zoxide.rs diff --git a/Cargo.lock b/Cargo.lock index 2c11e345..473a3966 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,7 +77,7 @@ checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -382,9 +382,9 @@ checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "equivalent" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "event-listener" @@ -532,7 +532,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -1201,7 +1201,7 @@ checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -1224,9 +1224,9 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plist" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590" +checksum = "bdc0001cfea3db57a2e24bc0d818e9e20e554b5f97fabb9bc231dc240269ae06" dependencies = [ "base64 0.21.2", "indexmap 1.9.3", @@ -1257,9 +1257,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" dependencies = [ "unicode-ident", ] @@ -1307,9 +1307,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.28.2" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" +checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51" dependencies = [ "memchr", ] @@ -1415,7 +1415,7 @@ dependencies = [ "aho-corasick", "memchr", "regex-automata 0.3.2", - "regex-syntax 0.7.3", + "regex-syntax 0.7.4", ] [[package]] @@ -1435,7 +1435,7 @@ checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.3", + "regex-syntax 0.7.4", ] [[package]] @@ -1446,9 +1446,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "rustc-demangle" @@ -1491,29 +1491,29 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.167" +version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daf513456463b42aa1d94cff7e0c24d682b429f020b9afa4f5ba5c40a22b237" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.167" +version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69b106b68bc8054f0e974e70d19984040f8a5cf9215ca82626ea4853f82c4b9" +checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] name = "serde_json" -version = "1.0.100" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" +checksum = "b5062a995d481b2308b6064e9af76011f2921c35f97b0468811ed9f6cd91dfed" dependencies = [ "itoa", "ryu", @@ -1633,9 +1633,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.23" +version = "2.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" dependencies = [ "proc-macro2", "quote", @@ -1688,7 +1688,7 @@ checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -1714,9 +1714,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" +checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" dependencies = [ "itoa", "serde", @@ -1732,9 +1732,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" dependencies = [ "time-core", ] @@ -1793,7 +1793,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -1946,7 +1946,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -2106,7 +2106,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", "wasm-bindgen-shared", ] @@ -2128,7 +2128,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/README.md b/README.md index 4ee3e8dc..586ba0f6 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ Before getting started, ensure that the following dependencies are installed on - jq (optional, for JSON preview) - ffmpegthumbnailer (optional, for video thumbnails) - fd (optional, for file searching) -- rg (optional, for content searching) +- rg (optional, for file content searching) +- fzf (optional, for directory jumping) - zoxide (optional, for directory jumping) Execute the following commands to clone the project and build Yazi: @@ -31,8 +32,8 @@ cargo build --release ## TODO - [ ] Add example config for general usage, currently please see my [another repo](https://github.com/sxyazi/dotfiles/tree/main/yazi) instead -- [ ] Integration with zoxide for fast directory navigation -- [ ] Integration with fd, rg for fuzzy file searching +- [x] Integration with fzf, zoxide for fast directory navigation +- [x] Integration with fd, rg for fuzzy file searching - [ ] Support for Überzug++ for image previews with X11/wayland environment - [ ] Batch renaming support diff --git a/src/core/blocker.rs b/src/core/blocker.rs new file mode 100644 index 00000000..7b06e6d5 --- /dev/null +++ b/src/core/blocker.rs @@ -0,0 +1,4 @@ +use once_cell::sync::Lazy; +use tokio::sync::Semaphore; + +pub static BLOCKER: Lazy = Lazy::new(|| Semaphore::new(1)); diff --git a/src/core/event.rs b/src/core/event.rs index 15345422..a5103e0f 100644 --- a/src/core/event.rs +++ b/src/core/event.rs @@ -10,11 +10,12 @@ static mut TX: Option> = None; pub enum Event { Quit, - Stop(bool, oneshot::Sender<()>), + Stop(bool, Option>), Key(KeyEvent), Render(String), Resize(u16, u16), + Cd(PathBuf), Refresh, Files(FilesOp), Hover, @@ -54,7 +55,7 @@ impl Event { macro_rules! emit { (Stop($state:expr)) => {{ let (tx, rx) = tokio::sync::oneshot::channel(); - $crate::core::Event::Stop($state, tx).wait(rx) + $crate::core::Event::Stop($state, Some(tx)).wait(rx) }}; (Key($key:expr)) => { $crate::core::Event::Key($key).emit(); @@ -66,6 +67,9 @@ macro_rules! emit { $crate::core::Event::Resize($cols, $rows).emit(); }; + (Cd($op:expr)) => { + $crate::core::Event::Cd($op).emit(); + }; (Files($op:expr)) => { $crate::core::Event::Files($op).emit(); }; diff --git a/src/core/external/fd.rs b/src/core/external/fd.rs index a31c6fd3..349687e1 100644 --- a/src/core/external/fd.rs +++ b/src/core/external/fd.rs @@ -21,6 +21,7 @@ pub fn fd(opt: FdOpt) -> Result>> { .arg(&opt.subject) .kill_on_drop(true) .stdout(Stdio::piped()) + .stderr(Stdio::piped()) .spawn()?; drop(child.stderr.take()); diff --git a/src/core/external/fzf.rs b/src/core/external/fzf.rs new file mode 100644 index 00000000..f3bdca14 --- /dev/null +++ b/src/core/external/fzf.rs @@ -0,0 +1,23 @@ +use std::{path::PathBuf, process::Stdio}; + +use anyhow::Result; +use tokio::{process::Command, sync::oneshot::{self, Receiver}}; + +pub struct FzfOpt {} + +pub fn fzf(opt: FzfOpt) -> Result>> { + let child = Command::new("fzf").kill_on_drop(true).stdout(Stdio::piped()).spawn()?; + + let (tx, rx) = oneshot::channel(); + tokio::spawn(async move { + if let Ok(output) = child.wait_with_output().await { + let selected = String::from_utf8_lossy(&output.stdout).trim().to_string(); + if !selected.is_empty() { + tx.send(Ok(selected.into())).ok(); + return; + } + } + tx.send(Err(anyhow::anyhow!("No match"))).ok(); + }); + Ok(rx) +} diff --git a/src/core/external/mod.rs b/src/core/external/mod.rs index 7233145d..b6dd9a96 100644 --- a/src/core/external/mod.rs +++ b/src/core/external/mod.rs @@ -1,5 +1,9 @@ mod fd; +mod fzf; mod rg; +mod zoxide; pub use fd::*; +pub use fzf::*; pub use rg::*; +pub use zoxide::*; diff --git a/src/core/external/rg.rs b/src/core/external/rg.rs index a71b1c64..c8491326 100644 --- a/src/core/external/rg.rs +++ b/src/core/external/rg.rs @@ -19,6 +19,7 @@ pub fn rg(opt: RgOpt) -> Result>> { .arg(&opt.subject) .kill_on_drop(true) .stdout(Stdio::piped()) + .stderr(Stdio::piped()) .spawn()?; drop(child.stderr.take()); diff --git a/src/core/external/zoxide.rs b/src/core/external/zoxide.rs new file mode 100644 index 00000000..5bcb0657 --- /dev/null +++ b/src/core/external/zoxide.rs @@ -0,0 +1,30 @@ +use std::{path::PathBuf, process::Stdio}; + +use anyhow::Result; +use tokio::{process::Command, sync::oneshot::{self, Receiver}}; + +pub struct ZoxideOpt { + pub cwd: PathBuf, +} + +pub fn zoxide(opt: ZoxideOpt) -> Result>> { + let child = Command::new("zoxide") + .args(&["query", "-i", "--exclude"]) + .arg(opt.cwd) + .kill_on_drop(true) + .stdout(Stdio::piped()) + .spawn()?; + + let (tx, rx) = oneshot::channel(); + tokio::spawn(async move { + if let Ok(output) = child.wait_with_output().await { + let selected = String::from_utf8_lossy(&output.stdout).trim().to_string(); + if !selected.is_empty() { + tx.send(Ok(selected.into())).ok(); + return; + } + } + tx.send(Err(anyhow::anyhow!("No match"))).ok(); + }); + Ok(rx) +} diff --git a/src/core/files/file.rs b/src/core/files/file.rs index 213d3ed4..425b7295 100644 --- a/src/core/files/file.rs +++ b/src/core/files/file.rs @@ -30,4 +30,10 @@ impl File { let is_hidden = path.file_name().map(|s| s.to_string_lossy().starts_with('.')).unwrap_or(false); File { path: path.to_path_buf(), meta, length, is_link, is_hidden, is_selected: false } } + + #[inline] + pub fn set_path(mut self, path: &Path) -> Self { + self.path = path.to_path_buf(); + self + } } diff --git a/src/core/files/files.rs b/src/core/files/files.rs index fdae11cb..1a1ad487 100644 --- a/src/core/files/files.rs +++ b/src/core/files/files.rs @@ -116,6 +116,7 @@ impl Default for FilesSort { fn default() -> Self { Self { by: MANAGER.sort_by, reverse: MANAGER.sort_reverse } } } +#[derive(Debug)] pub enum FilesOp { Read(PathBuf, IndexMap), IOErr(PathBuf), @@ -135,4 +136,7 @@ impl FilesOp { #[inline] pub fn read_empty(path: &Path) -> Self { Self::Read(path.to_path_buf(), IndexMap::new()) } + + #[inline] + pub fn search_empty(path: &Path) -> Self { Self::Search(path.to_path_buf(), IndexMap::new()) } } diff --git a/src/core/manager/manager.rs b/src/core/manager/manager.rs index 67066f61..ddbaabe4 100644 --- a/src/core/manager/manager.rs +++ b/src/core/manager/manager.rs @@ -19,7 +19,7 @@ impl Manager { tabs: Tabs::new(), yanked: Default::default(), - watcher: Watcher::init(), + watcher: Watcher::start(), mimetype: Default::default(), } } @@ -215,14 +215,16 @@ impl Manager { pub fn update_search(&mut self, op: FilesOp) -> bool { let path = op.path(); - if !self.current().in_search || self.current().cwd != path { - let rep = mem::replace(self.current_mut(), Folder::new_search(&path)); - if !rep.in_search { - self.active_mut().history.insert(path, rep); - } + if self.current().in_search && self.current().cwd == path { + return self.current_mut().update(op); } - self.current_mut().update(op) + let rep = mem::replace(self.current_mut(), Folder::new_search(&path)); + if !rep.in_search { + self.active_mut().history.insert(path, rep); + } + self.current_mut().update(op); + true } pub fn update_mimetype(&mut self, path: PathBuf, mimetype: String) -> bool { diff --git a/src/core/manager/tab.rs b/src/core/manager/tab.rs index 9972f124..4619e903 100644 --- a/src/core/manager/tab.rs +++ b/src/core/manager/tab.rs @@ -1,11 +1,11 @@ use std::{collections::BTreeMap, mem, path::{Path, PathBuf}}; -use anyhow::Result; +use anyhow::{Error, Result}; use indexmap::IndexMap; use tokio::task::JoinHandle; use super::{Folder, Mode, Preview}; -use crate::{core::{external, files::{Files, FilesOp}, input::{Input, InputOpt}}, emit}; +use crate::{core::{external::{self, FzfOpt, ZoxideOpt}, files::{Files, FilesOp}, input::{Input, InputOpt}, Event, BLOCKER}, emit, misc::Defer}; pub struct Tab { pub(super) current: Folder, @@ -38,7 +38,11 @@ impl Tab { return true; } - self.select_all(Some(false)) + if self.select_all(Some(false)) { + return true; + } + + self.search_stop() } pub fn arrow(&mut self, step: isize) -> bool { @@ -69,6 +73,29 @@ impl Tab { true } + pub fn cd(&mut self, target: PathBuf) -> bool { + if self.current.cwd == target { + return false; + } + + if let Some(rep) = self.parent.take() { + self.history.insert(rep.cwd.clone(), rep); + } + + let rep = self.history_new(&target); + let rep = mem::replace(&mut self.current, rep); + if !rep.in_search { + self.history.insert(rep.cwd.clone(), rep); + } + + if let Some(parent) = target.parent() { + self.parent = Some(self.history_new(parent)); + } + + emit!(Refresh); + true + } + pub fn enter(&mut self) -> bool { let hovered = if let Some(h) = self.current.hovered() { h.clone() @@ -141,19 +168,19 @@ impl Tab { let pos = Input::top_position(); self.search = Some(tokio::spawn(async move { let subject = emit!(Input(InputOpt { - title: "Find:".to_string(), + title: "Search:".to_string(), value: "".to_string(), position: pos, })) .await?; let mut rx = if grep { - external::rg(external::RgOpt { cwd: cwd.clone(), hidden, subject })? + external::rg(external::RgOpt { cwd: cwd.clone(), hidden, subject }) } else { - external::fd(external::FdOpt { cwd: cwd.clone(), hidden, glob: false, subject })? - }; + external::fd(external::FdOpt { cwd: cwd.clone(), hidden, glob: false, subject }) + }?; - emit!(Files(FilesOp::Search(cwd.clone(), IndexMap::new()))); + emit!(Files(FilesOp::search_empty(&cwd))); while let Some(chunk) = rx.recv().await { if chunk.is_empty() { break; @@ -179,6 +206,24 @@ impl Tab { true } + pub fn jump(&self, global: bool) -> bool { + let cwd = self.current.cwd.clone(); + + tokio::spawn(async move { + let _guard = BLOCKER.acquire().await.unwrap(); + let _defer = Defer::new(|| Event::Stop(false, None).emit()); + emit!(Stop(true)).await; + + let rx = if global { external::fzf(FzfOpt {}) } else { external::zoxide(ZoxideOpt { cwd }) }?; + + if let Ok(target) = rx.await? { + emit!(Cd(target)); + } + Ok::<(), Error>(()) + }); + false + } + pub fn select(&mut self, state: Option) -> bool { let idx = Some(self.current.cursor()); self.current.select(idx, state) diff --git a/src/core/manager/watcher.rs b/src/core/manager/watcher.rs index 00267c87..e7fd131f 100644 --- a/src/core/manager/watcher.rs +++ b/src/core/manager/watcher.rs @@ -1,7 +1,9 @@ -use std::{collections::BTreeSet, path::{Path, PathBuf}}; +use std::{collections::BTreeSet, path::{Path, PathBuf}, sync::Arc}; -use notify::{event::{MetadataKind, ModifyKind}, EventKind, RecommendedWatcher, Watcher as _Watcher}; -use tokio::sync::mpsc::{self, Sender}; +use indexmap::IndexMap; +use notify::{event::{MetadataKind, ModifyKind}, EventKind, RecommendedWatcher, RecursiveMode, Watcher as _Watcher}; +use parking_lot::RwLock; +use tokio::{fs, sync::mpsc::{self, Receiver, Sender}}; use crate::{core::files::{Files, FilesOp}, emit}; @@ -9,19 +11,12 @@ pub struct Watcher { tx: Sender, watcher: RecommendedWatcher, - watched: BTreeSet, + watched: Arc>>>, } impl Watcher { - pub fn init() -> Self { - let (watcher, tx) = Self::start(); - - Self { tx, watcher, watched: Default::default() } - } - - fn start() -> (RecommendedWatcher, Sender) { - let (tx, mut rx) = mpsc::channel(50); - + pub(super) fn start() -> Self { + let (tx, rx) = mpsc::channel(50); let watcher = RecommendedWatcher::new( { let tx = tx.clone(); @@ -69,26 +64,84 @@ impl Watcher { ) .unwrap(); - tokio::spawn(async move { - while let Some(path) = rx.recv().await { - emit!(Files(match Files::read_dir(&path).await { + let instance = Self { tx, watcher, watched: Default::default() }; + tokio::spawn(Self::changed(rx, instance.watched.clone())); + instance + } + + async fn changed( + mut rx: Receiver, + watched: Arc>>>, + ) { + while let Some(path) = rx.recv().await { + let linked = watched + .read() + .iter() + .map_while(|(k, v)| v.as_ref().and_then(|v| path.strip_prefix(v).ok()).map(|v| k.join(v))) + .collect::>(); + + let result = Files::read_dir(&path).await; + if linked.is_empty() { + emit!(Files(match result { Ok(items) => FilesOp::Read(path, items), Err(_) => FilesOp::IOErr(path), })); + continue; } - }); - (watcher, tx) + for ori in linked { + emit!(Files(match &result { + Ok(items) => { + let files = IndexMap::from_iter(items.iter().map(|(p, f)| { + let p = ori.join(p.strip_prefix(&path).unwrap()); + let f = f.clone().set_path(&p); + (p, f) + })); + FilesOp::Read(ori, files) + } + Err(_) => FilesOp::IOErr(ori), + })); + } + } } pub(super) fn watch(&mut self, to_watch: BTreeSet) { - for p in to_watch.difference(&self.watched) { - self.watcher.watch(&p, notify::RecursiveMode::NonRecursive).ok(); - } - for p in self.watched.difference(&to_watch) { + let keys = self.watched.read().keys().cloned().collect::>(); + for p in keys.difference(&to_watch) { self.watcher.unwatch(p).ok(); } - self.watched = to_watch; + for p in to_watch.difference(&keys) { + self.watcher.watch(&p, RecursiveMode::NonRecursive).ok(); + } + + let mut todo = Vec::new(); + let mut watched = self.watched.write(); + *watched = IndexMap::from_iter(to_watch.into_iter().map(|k| { + if let Some(v) = watched.remove(&k) { + (k, v) + } else { + todo.push(k.clone()); + (k, None) + } + })); + watched.sort_unstable_by(|_, a, _, b| b.cmp(a)); + + let watched = self.watched.clone(); + tokio::spawn(async move { + let mut ext = IndexMap::new(); + for k in todo { + match fs::canonicalize(&k).await { + Ok(v) if v != k => { + ext.insert(k, Some(v)); + } + _ => {} + } + } + + let mut watched = watched.write(); + watched.extend(ext); + watched.sort_unstable_by(|_, a, _, b| b.cmp(a)); + }); } pub(super) fn trigger(&self, path: &Path) { diff --git a/src/core/mod.rs b/src/core/mod.rs index c43538ba..970de191 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,4 +1,5 @@ pub mod adapter; +mod blocker; mod event; pub mod external; pub mod files; @@ -6,4 +7,5 @@ pub mod input; pub mod manager; pub mod tasks; +pub use blocker::*; pub use event::*; diff --git a/src/core/tasks/process.rs b/src/core/tasks/process.rs index cf2ef74a..7873b2c1 100644 --- a/src/core/tasks/process.rs +++ b/src/core/tasks/process.rs @@ -1,17 +1,15 @@ use anyhow::Result; -use tokio::{process::Command, select, sync::{mpsc, oneshot, Semaphore}}; +use tokio::{process::Command, select, sync::{mpsc, oneshot}}; use tracing::trace; use super::TaskOp; -use crate::emit; +use crate::{core::BLOCKER, emit}; pub(super) struct Process { rx: async_channel::Receiver, tx: async_channel::Sender, sch: mpsc::UnboundedSender, - - blocker: Semaphore, } #[derive(Debug)] @@ -31,7 +29,7 @@ pub(super) struct ProcessOpOpen { impl Process { pub(super) fn new(sch: mpsc::UnboundedSender) -> Self { let (tx, rx) = async_channel::unbounded(); - Self { tx, rx, sch, blocker: Semaphore::new(1) } + Self { tx, rx, sch } } #[inline] @@ -55,7 +53,7 @@ impl Process { return Ok(self.sch.send(TaskOp::Adv(task.id, 1, 0))?); } - let _guard = self.blocker.acquire().await.unwrap(); + let _guard = BLOCKER.acquire().await.unwrap(); emit!(Stop(true)).await; match Command::new(&task.cmd).args(&task.args).kill_on_drop(true).spawn() { diff --git a/src/ui/app.rs b/src/ui/app.rs index 9eddf575..ffcc83a0 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -32,7 +32,7 @@ impl App { Ok(()) } - fn dispatch_stop(&mut self, state: bool, tx: oneshot::Sender<()>) { + fn dispatch_stop(&mut self, state: bool, tx: Option>) { if state { self.signals.stop_term(true); self.term = None; @@ -41,7 +41,9 @@ impl App { self.signals.stop_term(false); emit!(Render); } - tx.send(()).ok(); + if let Some(tx) = tx { + tx.send(()).ok(); + } } fn dispatch_key(&mut self, key: KeyEvent) { @@ -71,6 +73,9 @@ impl App { async fn dispatch_module(&mut self, event: Event) { let manager = &mut self.cx.manager; match event { + Event::Cd(path) => { + manager.active_mut().cd(path); + } Event::Refresh => { manager.refresh(); } @@ -105,7 +110,7 @@ impl App { } Event::Open(files) => { - let mime = self.cx.manager.mimetype(&files).await; + let mime = manager.mimetype(&files).await; let targets = files.into_iter().zip(mime).map_while(|(f, m)| m.map(|m| (f, m))).collect(); self.cx.tasks.file_open(targets); } diff --git a/src/ui/dispatcher.rs b/src/ui/dispatcher.rs index 1fe6815c..de922ee0 100644 --- a/src/ui/dispatcher.rs +++ b/src/ui/dispatcher.rs @@ -100,6 +100,11 @@ impl Executor { "fd" => cx.manager.active_mut().search(false), _ => cx.manager.active_mut().search_stop(), }, + "jump" => match exec.args.get(0).map(|s| s.as_str()).unwrap_or("") { + "fzf" => cx.manager.active_mut().jump(true), + "zoxide" => cx.manager.active_mut().jump(false), + _ => false, + }, // Tabs "tab_create" => {