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 std::{path::PathBuf, process::Stdio, time::Duration};
use anyhow::Result; 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; use crate::misc::DelayedBuffer;
pub struct FdOpt { pub struct FdOpt {
pub cwd: PathBuf, pub cwd: PathBuf,
pub hidden: bool, pub hidden: bool,
pub regex: bool, pub glob: bool,
pub subject: String, 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") let mut child = Command::new("fd")
.arg("--base-directory") .arg("--base-directory")
.arg(&opt.cwd) .arg(&opt.cwd)
.arg(if opt.hidden { "--hidden" } else { "--no-hidden" }) .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) .arg(&opt.subject)
.kill_on_drop(true) .kill_on_drop(true)
.stdout(Stdio::piped()) .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 it = BufReader::new(child.stdout.take().unwrap()).lines();
let (mut buf, rx) = DelayedBuffer::new(Duration::from_millis(100)); 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 { while let Ok(Some(line)) = it.next_line().await {
let path = PathBuf::from(line); buf.push(opt.cwd.join(line));
if path.components().count() > 1 {
buf.push(path);
}
} }
child.wait().await.ok(); child.wait().await.ok();
}); });
Ok((handle, rx)) Ok(rx)
} }

View file

@ -1,7 +1,7 @@
use std::{path::PathBuf, process::Stdio, time::Duration}; use std::{path::PathBuf, process::Stdio, time::Duration};
use anyhow::Result; 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; use crate::misc::DelayedBuffer;
@ -11,7 +11,7 @@ pub struct RgOpt {
pub subject: String, 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") let mut child = Command::new("rg")
.current_dir(&opt.cwd) .current_dir(&opt.cwd)
.args(&["--color=never", "--files-with-matches", "--smart-case"]) .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 it = BufReader::new(child.stdout.take().unwrap()).lines();
let (mut buf, rx) = DelayedBuffer::new(Duration::from_millis(100)); 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 { while let Ok(Some(line)) = it.next_line().await {
let path = PathBuf::from(line); buf.push(opt.cwd.join(line));
if path.components().count() > 1 {
buf.push(path);
}
} }
child.wait().await.ok(); child.wait().await.ok();
}); });
Ok((handle, rx)) Ok(rx)
} }

View file

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

View file

@ -1,4 +1,4 @@
use std::{path::{Path, PathBuf}, usize}; use std::path::{Path, PathBuf};
use indexmap::map::Slice; use indexmap::map::Slice;
use ratatui::layout::Rect; use ratatui::layout::Rect;
@ -25,8 +25,8 @@ impl Folder {
pub fn update(&mut self, op: FilesOp) -> bool { pub fn update(&mut self, op: FilesOp) -> bool {
let b = match op { let b = match op {
FilesOp::Update(_, items) => self.files.update(items), FilesOp::Read(_, items) => self.files.update_read(items),
FilesOp::Append(_, items) => self.files.append(items), FilesOp::Search(_, items) => self.files.update_search(items),
}; };
if !b { if !b {
return false; 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; use tokio::fs;
@ -166,20 +166,26 @@ impl Manager {
files.into_iter().map(|p| self.mimetype.get(p).cloned()).collect() 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 path = op.path();
let cwd = self.current().cwd.clone(); let cwd = self.current().cwd.clone();
let hovered = self.hovered().map(|h| h.path.clone()); let hovered = self.hovered().map(|h| h.path.clone());
let folder = if cwd == path && !self.current().in_search { let mut b = if cwd == path && !self.current().in_search {
self.current_mut() self.current_mut().update(op)
} else if matches!(self.parent(), Some(p) if p.cwd == path) { } 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 { } 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 |= 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)); b |= hovered.as_ref().map_or(false, |h| self.current_mut().hover(h));
@ -189,6 +195,18 @@ impl Manager {
b 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 { pub fn update_mimetype(&mut self, path: PathBuf, mimetype: String) -> bool {
if matches!(self.mimetype.get(&path), Some(m) if m == &mimetype) { if matches!(self.mimetype.get(&path), Some(m) if m == &mimetype) {
return false; return false;

View file

@ -1,6 +1,7 @@
use std::{collections::BTreeMap, mem, path::{Path, PathBuf}}; use std::{collections::BTreeMap, mem, path::{Path, PathBuf}};
use anyhow::Result; use anyhow::Result;
use indexmap::IndexMap;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use super::{Folder, Mode, Preview}; use super::{Folder, Mode, Preview};
@ -146,21 +147,36 @@ impl Tab {
})) }))
.await?; .await?;
let (handle, mut buf) = if grep { let mut rx = if grep {
external::rg(external::RgOpt { cwd: cwd.clone(), hidden, subject })? external::rg(external::RgOpt { cwd: cwd.clone(), hidden, subject })?
} else { } 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() { if chunk.is_empty() {
break; 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 { 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 tokio::sync::oneshot::{self};
use super::{root::Root, Ctx, Executor, Logs, Signals, Term}; 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 { pub struct App {
cx: Ctx, cx: Ctx,
@ -75,7 +75,11 @@ impl App {
manager.refresh(); manager.refresh();
} }
Event::Files(op) => { 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); emit!(Render);
} }
} }

View file

@ -95,6 +95,11 @@ impl Executor {
Some("hide") => Some(false), Some("hide") => Some(false),
_ => None, _ => 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 // Tabs
"tab_create" => { "tab_create" => {