mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: jump around with fzf and zoxide
This commit is contained in:
parent
06cb708a80
commit
4a5154c03e
18 changed files with 273 additions and 85 deletions
66
Cargo.lock
generated
66
Cargo.lock
generated
|
|
@ -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",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
4
src/core/blocker.rs
Normal file
4
src/core/blocker.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
use once_cell::sync::Lazy;
|
||||
use tokio::sync::Semaphore;
|
||||
|
||||
pub static BLOCKER: Lazy<Semaphore> = Lazy::new(|| Semaphore::new(1));
|
||||
|
|
@ -10,11 +10,12 @@ static mut TX: Option<Sender<Event>> = None;
|
|||
|
||||
pub enum Event {
|
||||
Quit,
|
||||
Stop(bool, oneshot::Sender<()>),
|
||||
Stop(bool, Option<oneshot::Sender<()>>),
|
||||
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();
|
||||
};
|
||||
|
|
|
|||
1
src/core/external/fd.rs
vendored
1
src/core/external/fd.rs
vendored
|
|
@ -21,6 +21,7 @@ pub fn fd(opt: FdOpt) -> Result<UnboundedReceiver<Vec<PathBuf>>> {
|
|||
.arg(&opt.subject)
|
||||
.kill_on_drop(true)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
drop(child.stderr.take());
|
||||
|
|
|
|||
23
src/core/external/fzf.rs
vendored
Normal file
23
src/core/external/fzf.rs
vendored
Normal file
|
|
@ -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<Receiver<Result<PathBuf>>> {
|
||||
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)
|
||||
}
|
||||
4
src/core/external/mod.rs
vendored
4
src/core/external/mod.rs
vendored
|
|
@ -1,5 +1,9 @@
|
|||
mod fd;
|
||||
mod fzf;
|
||||
mod rg;
|
||||
mod zoxide;
|
||||
|
||||
pub use fd::*;
|
||||
pub use fzf::*;
|
||||
pub use rg::*;
|
||||
pub use zoxide::*;
|
||||
|
|
|
|||
1
src/core/external/rg.rs
vendored
1
src/core/external/rg.rs
vendored
|
|
@ -19,6 +19,7 @@ pub fn rg(opt: RgOpt) -> Result<UnboundedReceiver<Vec<PathBuf>>> {
|
|||
.arg(&opt.subject)
|
||||
.kill_on_drop(true)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
drop(child.stderr.take());
|
||||
|
|
|
|||
30
src/core/external/zoxide.rs
vendored
Normal file
30
src/core/external/zoxide.rs
vendored
Normal file
|
|
@ -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<Receiver<Result<PathBuf>>> {
|
||||
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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<PathBuf, File>),
|
||||
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()) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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>) -> bool {
|
||||
let idx = Some(self.current.cursor());
|
||||
self.current.select(idx, state)
|
||||
|
|
|
|||
|
|
@ -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<PathBuf>,
|
||||
|
||||
watcher: RecommendedWatcher,
|
||||
watched: BTreeSet<PathBuf>,
|
||||
watched: Arc<RwLock<IndexMap<PathBuf, Option<PathBuf>>>>,
|
||||
}
|
||||
|
||||
impl Watcher {
|
||||
pub fn init() -> Self {
|
||||
let (watcher, tx) = Self::start();
|
||||
|
||||
Self { tx, watcher, watched: Default::default() }
|
||||
}
|
||||
|
||||
fn start() -> (RecommendedWatcher, Sender<PathBuf>) {
|
||||
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<PathBuf>,
|
||||
watched: Arc<RwLock<IndexMap<PathBuf, Option<PathBuf>>>>,
|
||||
) {
|
||||
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::<Vec<_>>();
|
||||
|
||||
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<PathBuf>) {
|
||||
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::<BTreeSet<_>>();
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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::*;
|
||||
|
|
|
|||
|
|
@ -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<ProcessOp>,
|
||||
tx: async_channel::Sender<ProcessOp>,
|
||||
|
||||
sch: mpsc::UnboundedSender<TaskOp>,
|
||||
|
||||
blocker: Semaphore,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -31,7 +29,7 @@ pub(super) struct ProcessOpOpen {
|
|||
impl Process {
|
||||
pub(super) fn new(sch: mpsc::UnboundedSender<TaskOp>) -> 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() {
|
||||
|
|
|
|||
|
|
@ -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<oneshot::Sender<()>>) {
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue