From 774deae1ea5674eb370a8b64bc52e76d4466dccc Mon Sep 17 00:00:00 2001 From: sxyazi Date: Tue, 11 Jul 2023 00:21:48 +0800 Subject: [PATCH] feat: search files with fd, rg --- src/core/external/fd.rs | 17 +++++++---------- src/core/external/rg.rs | 13 +++++-------- src/core/files/files.rs | 32 +++++++++++++++++--------------- src/core/manager/folder.rs | 6 +++--- src/core/manager/manager.rs | 32 +++++++++++++++++++++++++------- src/core/manager/tab.rs | 28 ++++++++++++++++++++++------ src/ui/app.rs | 8 ++++++-- src/ui/dispatcher.rs | 5 +++++ 8 files changed, 90 insertions(+), 51 deletions(-) diff --git a/src/core/external/fd.rs b/src/core/external/fd.rs index 31cd0d36..a31c6fd3 100644 --- a/src/core/external/fd.rs +++ b/src/core/external/fd.rs @@ -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>)> { +pub fn fd(opt: FdOpt) -> Result>> { 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> 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) } diff --git a/src/core/external/rg.rs b/src/core/external/rg.rs index 50f2cee3..a71b1c64 100644 --- a/src/core/external/rg.rs +++ b/src/core/external/rg.rs @@ -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>)> { +pub fn rg(opt: RgOpt) -> Result>> { 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> 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) } diff --git a/src/core/files/files.rs b/src/core/files/files.rs index 8e836249..ca38c4ff 100644 --- a/src/core/files/files.rs +++ b/src/core/files/files.rs @@ -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) -> bool { + pub fn update_read(&mut self, mut items: IndexMap) -> 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) -> 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) -> 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), - Append(PathBuf, IndexMap), + Read(PathBuf, IndexMap), + Search(PathBuf, IndexMap), } 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() } diff --git a/src/core/manager/folder.rs b/src/core/manager/folder.rs index fb6f6dea..e65450fb 100644 --- a/src/core/manager/folder.rs +++ b/src/core/manager/folder.rs @@ -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; diff --git a/src/core/manager/manager.rs b/src/core/manager/manager.rs index bc28ea73..db3e991e 100644 --- a/src/core/manager/manager.rs +++ b/src/core/manager/manager.rs @@ -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; diff --git a/src/core/manager/tab.rs b/src/core/manager/tab.rs index b4298ab9..6f0d2a05 100644 --- a/src/core/manager/tab.rs +++ b/src/core/manager/tab.rs @@ -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 { diff --git a/src/ui/app.rs b/src/ui/app.rs index a6e3d6c0..80b55fe8 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -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); } } diff --git a/src/ui/dispatcher.rs b/src/ui/dispatcher.rs index fdb33571..1fe6815c 100644 --- a/src/ui/dispatcher.rs +++ b/src/ui/dispatcher.rs @@ -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" => {