feat: jump around with fzf and zoxide

This commit is contained in:
sxyazi 2023-07-12 19:33:23 +08:00
parent 06cb708a80
commit 4a5154c03e
No known key found for this signature in database
18 changed files with 273 additions and 85 deletions

66
Cargo.lock generated
View file

@ -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",
]

View file

@ -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
View file

@ -0,0 +1,4 @@
use once_cell::sync::Lazy;
use tokio::sync::Semaphore;
pub static BLOCKER: Lazy<Semaphore> = Lazy::new(|| Semaphore::new(1));

View file

@ -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();
};

View file

@ -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
View 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)
}

View file

@ -1,5 +1,9 @@
mod fd;
mod fzf;
mod rg;
mod zoxide;
pub use fd::*;
pub use fzf::*;
pub use rg::*;
pub use zoxide::*;

View file

@ -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
View 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)
}

View file

@ -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
}
}

View file

@ -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()) }
}

View file

@ -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 {

View file

@ -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)

View file

@ -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) {

View file

@ -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::*;

View file

@ -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() {

View file

@ -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);
}

View file

@ -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" => {