From a72acf6fd36997dd2ba9057299498f58889c2f7a Mon Sep 17 00:00:00 2001 From: sxyazi Date: Sun, 9 Jul 2023 19:12:58 +0800 Subject: [PATCH] feat: add fd, rg external tools --- README.md | 14 +++++++------- src/core/external/fd.rs | 38 ++++++++++++++++++++++++++++++++++++++ src/core/external/mod.rs | 5 +++++ src/core/external/rg.rs | 36 ++++++++++++++++++++++++++++++++++++ src/core/mod.rs | 1 + src/misc/buffer.rs | 35 +++++++++++++++++++++++++++++++++++ src/misc/mod.rs | 2 ++ 7 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 src/core/external/fd.rs create mode 100644 src/core/external/mod.rs create mode 100644 src/core/external/rg.rs create mode 100644 src/misc/buffer.rs diff --git a/README.md b/README.md index f81786d6..4ee3e8dc 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ Before getting started, ensure that the following dependencies are installed on - jq (optional, for JSON preview) - ffmpegthumbnailer (optional, for video thumbnails) -- fzf (optional, for fuzzy search) -- rg (optional, for fuzzy search) +- fd (optional, for file searching) +- rg (optional, for content searching) - zoxide (optional, for directory jumping) Execute the following commands to clone the project and build Yazi: @@ -30,11 +30,11 @@ 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 fzf, rg for fuzzy file searching -- Support for Überzug++ for image previews with X11/wayland environment -- Batch renaming support +- [ ] 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 +- [ ] Support for Überzug++ for image previews with X11/wayland environment +- [ ] Batch renaming support ## License diff --git a/src/core/external/fd.rs b/src/core/external/fd.rs new file mode 100644 index 00000000..7e8f4d7f --- /dev/null +++ b/src/core/external/fd.rs @@ -0,0 +1,38 @@ +use std::{path::PathBuf, process::Stdio, time::Duration}; + +use anyhow::Result; +use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::UnboundedReceiver, task::JoinHandle}; + +use crate::misc::DelayedBuffer; + +pub struct FdOpt { + pub cwd: PathBuf, + pub hidden: bool, + pub regex: bool, + pub subject: String, +} + +pub fn fd(opt: FdOpt) -> Result<(JoinHandle<()>, UnboundedReceiver>)> { + let mut child = Command::new("fd") + .arg("--base-directory") + .arg(&opt.cwd) + .arg(if opt.hidden { "--hidden" } else { "--no-hidden" }) + .arg(if opt.regex { "--regex" } else { "--glob" }) + .arg(&opt.subject) + .kill_on_drop(true) + .stdout(Stdio::piped()) + .spawn()?; + + drop(child.stderr.take()); + + let mut it = BufReader::new(child.stdout.take().unwrap()).lines(); + let (mut buf, rx) = DelayedBuffer::new(Duration::from_millis(100)); + + let handle = tokio::spawn(async move { + while let Ok(Some(line)) = it.next_line().await { + buf.push(line); + } + child.wait().await.ok(); + }); + Ok((handle, rx)) +} diff --git a/src/core/external/mod.rs b/src/core/external/mod.rs new file mode 100644 index 00000000..7233145d --- /dev/null +++ b/src/core/external/mod.rs @@ -0,0 +1,5 @@ +mod fd; +mod rg; + +pub use fd::*; +pub use rg::*; diff --git a/src/core/external/rg.rs b/src/core/external/rg.rs new file mode 100644 index 00000000..2fc7459f --- /dev/null +++ b/src/core/external/rg.rs @@ -0,0 +1,36 @@ +use std::{path::PathBuf, process::Stdio, time::Duration}; + +use anyhow::Result; +use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::UnboundedReceiver, task::JoinHandle}; + +use crate::misc::DelayedBuffer; + +pub struct RgOpt { + pub cwd: PathBuf, + pub hidden: bool, + pub subject: String, +} + +pub async fn rg(opt: RgOpt) -> Result<(JoinHandle<()>, UnboundedReceiver>)> { + let mut child = Command::new("rg") + .current_dir(&opt.cwd) + .args(&["--color=never", "--files-with-matches", "--smart-case"]) + .arg(if opt.hidden { "--hidden" } else { "--no-hidden" }) + .arg(&opt.subject) + .kill_on_drop(true) + .stdout(Stdio::piped()) + .spawn()?; + + drop(child.stderr.take()); + + let mut it = BufReader::new(child.stdout.take().unwrap()).lines(); + let (mut buf, rx) = DelayedBuffer::new(Duration::from_millis(100)); + + let handle = tokio::spawn(async move { + while let Ok(Some(line)) = it.next_line().await { + buf.push(line); + } + child.wait().await.ok(); + }); + Ok((handle, rx)) +} diff --git a/src/core/mod.rs b/src/core/mod.rs index aeb27f44..dddee82c 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,5 +1,6 @@ mod adapter; mod event; +pub mod external; mod input; mod manager; mod tasks; diff --git a/src/misc/buffer.rs b/src/misc/buffer.rs new file mode 100644 index 00000000..d7ddae62 --- /dev/null +++ b/src/misc/buffer.rs @@ -0,0 +1,35 @@ +use std::time::Duration; + +use tokio::{sync::mpsc::{self, UnboundedReceiver, UnboundedSender}, time::Instant}; + +pub struct DelayedBuffer { + buf: Vec, + tx: UnboundedSender>, + last: Instant, + interval: Duration, +} + +impl DelayedBuffer { + pub fn new(interval: Duration) -> (Self, UnboundedReceiver>) { + let (tx, rx) = mpsc::unbounded_channel(); + (Self { buf: Vec::new(), tx, last: Instant::now() - interval, interval }, rx) + } + + pub fn push(&mut self, item: T) { + self.buf.push(item); + if self.last.elapsed() >= self.interval { + self.last = Instant::now(); + self.tx.send(self.buf.drain(..).collect()).ok(); + } + } + + pub fn flush(&mut self) { + if !self.buf.is_empty() { + self.tx.send(self.buf.drain(..).collect()).ok(); + } + } +} + +impl Drop for DelayedBuffer { + fn drop(&mut self) { self.flush(); } +} diff --git a/src/misc/mod.rs b/src/misc/mod.rs index 4398f9fb..b8b66626 100644 --- a/src/misc/mod.rs +++ b/src/misc/mod.rs @@ -1,7 +1,9 @@ +mod buffer; mod chars; mod defer; mod fns; +pub use buffer::*; pub use chars::*; pub use defer::*; pub use fns::*;