feat: search files with fd, rg

This commit is contained in:
sxyazi 2023-07-11 00:21:48 +08:00
parent ac6b077bd2
commit 774deae1ea
No known key found for this signature in database
8 changed files with 90 additions and 51 deletions

View file

@ -1,23 +1,23 @@
use std::{path::PathBuf, process::Stdio, time::Duration};
use anyhow::Result;
use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::UnboundedReceiver, task::JoinHandle};
use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::UnboundedReceiver};
use crate::misc::DelayedBuffer;
pub struct FdOpt {
pub cwd: PathBuf,
pub hidden: bool,
pub regex: bool,
pub glob: bool,
pub subject: String,
}
pub fn fd(opt: FdOpt) -> Result<(JoinHandle<()>, UnboundedReceiver<Vec<PathBuf>>)> {
pub fn fd(opt: FdOpt) -> Result<UnboundedReceiver<Vec<PathBuf>>> {
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(if opt.glob { "--glob" } else { "--regex" })
.arg(&opt.subject)
.kill_on_drop(true)
.stdout(Stdio::piped())
@ -28,14 +28,11 @@ pub fn fd(opt: FdOpt) -> Result<(JoinHandle<()>, UnboundedReceiver<Vec<PathBuf>>
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 {
tokio::spawn(async move {
while let Ok(Some(line)) = it.next_line().await {
let path = PathBuf::from(line);
if path.components().count() > 1 {
buf.push(path);
}
buf.push(opt.cwd.join(line));
}
child.wait().await.ok();
});
Ok((handle, rx))
Ok(rx)
}

View file

@ -1,7 +1,7 @@
use std::{path::PathBuf, process::Stdio, time::Duration};
use anyhow::Result;
use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::UnboundedReceiver, task::JoinHandle};
use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::UnboundedReceiver};
use crate::misc::DelayedBuffer;
@ -11,7 +11,7 @@ pub struct RgOpt {
pub subject: String,
}
pub fn rg(opt: RgOpt) -> Result<(JoinHandle<()>, UnboundedReceiver<Vec<PathBuf>>)> {
pub fn rg(opt: RgOpt) -> Result<UnboundedReceiver<Vec<PathBuf>>> {
let mut child = Command::new("rg")
.current_dir(&opt.cwd)
.args(&["--color=never", "--files-with-matches", "--smart-case"])
@ -26,14 +26,11 @@ pub fn rg(opt: RgOpt) -> Result<(JoinHandle<()>, UnboundedReceiver<Vec<PathBuf>>
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 {
tokio::spawn(async move {
while let Ok(Some(line)) = it.next_line().await {
let path = PathBuf::from(line);
if path.components().count() > 1 {
buf.push(path);
}
buf.push(opt.cwd.join(line));
}
child.wait().await.ok();
});
Ok((handle, rx))
Ok(rx)
}

View file

@ -38,7 +38,7 @@ impl Files {
items.insert(path, file);
}
}
emit!(Files(FilesOp::Update(path.to_path_buf(), items)));
emit!(Files(FilesOp::Read(path.to_path_buf(), items)));
}
pub fn sort(&mut self) {
@ -67,7 +67,7 @@ impl Files {
}
}
pub fn update(&mut self, mut items: IndexMap<PathBuf, File>) -> bool {
pub fn update_read(&mut self, mut items: IndexMap<PathBuf, File>) -> bool {
if !self.show_hidden {
items.retain(|_, item| !item.is_hidden);
}
@ -84,17 +84,19 @@ impl Files {
true
}
pub fn append(&mut self, items: IndexMap<PathBuf, File>) -> bool {
for (path, mut item) in items.into_iter() {
if let Some(old) = self.items.get(&path) {
item.length = old.length;
item.is_selected = old.is_selected;
}
self.items.insert(path, item);
pub fn update_search(&mut self, items: IndexMap<PathBuf, File>) -> bool {
if !items.is_empty() {
self.items.extend(items);
self.sort();
return true;
}
self.sort();
true
if !self.items.is_empty() {
self.items.clear();
return true;
}
false
}
}
@ -118,16 +120,16 @@ impl Default for FilesSort {
}
pub enum FilesOp {
Update(PathBuf, IndexMap<PathBuf, File>),
Append(PathBuf, IndexMap<PathBuf, File>),
Read(PathBuf, IndexMap<PathBuf, File>),
Search(PathBuf, IndexMap<PathBuf, File>),
}
impl FilesOp {
#[inline]
pub fn path(&self) -> PathBuf {
match self {
Self::Update(path, _) => path,
Self::Append(path, _) => path,
Self::Read(path, _) => path,
Self::Search(path, _) => path,
}
.clone()
}

View file

@ -1,4 +1,4 @@
use std::{path::{Path, PathBuf}, usize};
use std::path::{Path, PathBuf};
use indexmap::map::Slice;
use ratatui::layout::Rect;
@ -25,8 +25,8 @@ impl Folder {
pub fn update(&mut self, op: FilesOp) -> bool {
let b = match op {
FilesOp::Update(_, items) => self.files.update(items),
FilesOp::Append(_, items) => self.files.append(items),
FilesOp::Read(_, items) => self.files.update_read(items),
FilesOp::Search(_, items) => self.files.update_search(items),
};
if !b {
return false;

View file

@ -1,4 +1,4 @@
use std::{collections::{BTreeSet, HashMap, HashSet}, path::PathBuf};
use std::{collections::{BTreeSet, HashMap, HashSet}, mem, path::PathBuf};
use tokio::fs;
@ -166,20 +166,26 @@ impl Manager {
files.into_iter().map(|p| self.mimetype.get(p).cloned()).collect()
}
pub fn update_files(&mut self, op: FilesOp) -> bool {
pub fn update_read(&mut self, op: FilesOp) -> bool {
let path = op.path();
let cwd = self.current().cwd.clone();
let hovered = self.hovered().map(|h| h.path.clone());
let folder = if cwd == path && !self.current().in_search {
self.current_mut()
let mut b = if cwd == path && !self.current().in_search {
self.current_mut().update(op)
} else if matches!(self.parent(), Some(p) if p.cwd == path) {
self.active_mut().parent.as_mut().unwrap()
self.active_mut().parent.as_mut().unwrap().update(op)
} else {
self.active_mut().history.entry(path.to_path_buf()).or_insert_with(|| Folder::new(&path))
self
.active_mut()
.history
.entry(path.to_path_buf())
.or_insert_with(|| Folder::new(&path))
.update(op);
matches!(self.hovered(), Some(h) if h.path == path)
};
let mut b = folder.update(op) || matches!(self.hovered(), Some(h) if h.path == path);
b |= self.active_mut().parent.as_mut().map_or(false, |p| p.hover(&cwd));
b |= hovered.as_ref().map_or(false, |h| self.current_mut().hover(h));
@ -189,6 +195,18 @@ impl Manager {
b
}
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);
}
}
self.current_mut().update(op)
}
pub fn update_mimetype(&mut self, path: PathBuf, mimetype: String) -> bool {
if matches!(self.mimetype.get(&path), Some(m) if m == &mimetype) {
return false;

View file

@ -1,6 +1,7 @@
use std::{collections::BTreeMap, mem, path::{Path, PathBuf}};
use anyhow::Result;
use indexmap::IndexMap;
use tokio::task::JoinHandle;
use super::{Folder, Mode, Preview};
@ -146,21 +147,36 @@ impl Tab {
}))
.await?;
let (handle, mut buf) = if grep {
let mut rx = if grep {
external::rg(external::RgOpt { cwd: cwd.clone(), hidden, subject })?
} else {
external::fd(external::FdOpt { cwd: cwd.clone(), hidden, regex: false, subject })?
external::fd(external::FdOpt { cwd: cwd.clone(), hidden, glob: false, subject })?
};
while let Some(chunk) = buf.recv().await {
emit!(Files(FilesOp::Search(cwd.clone(), IndexMap::new())));
while let Some(chunk) = rx.recv().await {
if chunk.is_empty() {
break;
}
emit!(Files(FilesOp::Append(cwd.clone(), Files::from(chunk).await)));
emit!(Files(FilesOp::Search(cwd.clone(), Files::from(chunk).await)));
}
Ok(handle.await?)
Ok(())
}));
false
true
}
pub fn search_stop(&mut self) -> bool {
if let Some(handle) = self.search.take() {
handle.abort();
}
if self.current.in_search {
let cwd = self.current.cwd.clone();
let rep = self.history_new(&cwd);
drop(mem::replace(&mut self.current, rep));
}
emit!(Refresh);
true
}
pub fn select(&mut self, state: Option<bool>) -> bool {

View file

@ -3,7 +3,7 @@ use crossterm::event::KeyEvent;
use tokio::sync::oneshot::{self};
use super::{root::Root, Ctx, Executor, Logs, Signals, Term};
use crate::{config::keymap::Key, core::Event, emit};
use crate::{config::keymap::Key, core::{files::FilesOp, Event}, emit};
pub struct App {
cx: Ctx,
@ -75,7 +75,11 @@ impl App {
manager.refresh();
}
Event::Files(op) => {
if manager.update_files(op) {
let b = match op {
FilesOp::Read(..) => manager.update_read(op),
FilesOp::Search(..) => manager.update_search(op),
};
if b {
emit!(Render);
}
}

View file

@ -95,6 +95,11 @@ impl Executor {
Some("hide") => Some(false),
_ => None,
}),
"search" => match exec.args.get(0).map(|s| s.as_str()).unwrap_or("") {
"rg" => cx.manager.active_mut().search(true),
"fd" => cx.manager.active_mut().search(false),
_ => cx.manager.active_mut().search_stop(),
},
// Tabs
"tab_create" => {