mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: search files with fd, rg
This commit is contained in:
parent
ac6b077bd2
commit
774deae1ea
8 changed files with 90 additions and 51 deletions
17
src/core/external/fd.rs
vendored
17
src/core/external/fd.rs
vendored
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
13
src/core/external/rg.rs
vendored
13
src/core/external/rg.rs
vendored
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue