mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41: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 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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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 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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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" => {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue