Skip to content

Commit

Permalink
Allow to change search pattern without closing an app (#45)
Browse files Browse the repository at this point in the history
  • Loading branch information
konradsz authored Jul 22, 2023
1 parent a3e8a6c commit d7a1ab5
Show file tree
Hide file tree
Showing 8 changed files with 289 additions and 102 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ NOTE: `ig` respects `ripgrep`'s [configuration file](https://github.com/BurntSus
| `dw` | Filter out all matches in current file |
| `v` | Toggle vertical context viewer |
| `s` | Toggle horizontal context viewer |
| `F5` | Re-run search |
| `F5` | Open search pattern popup |

## Supported text editors
`igrep` supports Vim, Neovim, nano, VS Code (stable and insiders), Emacs, EmacsClient, Helix, SublimeText, Micro, Intellij, Goland and Pycharm. If your beloved editor is missing on this list and you still want to use `igrep` please file an issue.
Expand Down
12 changes: 6 additions & 6 deletions src/ig/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
ui::{editor::Editor, result_list::ResultList},
};
pub use search_config::SearchConfig;
use searcher::{Event, Searcher};
use searcher::Event;
use std::io;
use std::process::ExitStatus;
use std::sync::mpsc;
Expand All @@ -22,20 +22,20 @@ pub enum State {
}

pub struct Ig {
tx: mpsc::Sender<Event>,
rx: mpsc::Receiver<Event>,
state: State,
searcher: Searcher,
editor: Editor,
}

impl Ig {
pub fn new(config: SearchConfig, editor: Editor) -> Self {
pub fn new(editor: Editor) -> Self {
let (tx, rx) = mpsc::channel();

Self {
tx,
rx,
state: State::Idle,
searcher: Searcher::new(config, tx),
editor,
}
}
Expand Down Expand Up @@ -73,11 +73,11 @@ impl Ig {
None
}

pub fn search(&mut self, result_list: &mut ResultList) {
pub fn search(&mut self, search_config: SearchConfig, result_list: &mut ResultList) {
if self.state == State::Idle {
*result_list = ResultList::default();
self.state = State::Searching;
self.searcher.search();
searcher::search(search_config, self.tx.clone());
}
}

Expand Down
146 changes: 67 additions & 79 deletions src/ig/searcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,93 +14,81 @@ pub enum Event {
Error,
}

pub struct Searcher {
config: SearchConfig,
tx: mpsc::Sender<Event>,
}

impl Searcher {
pub fn new(config: SearchConfig, tx: mpsc::Sender<Event>) -> Self {
Self { config, tx }
}

pub fn search(&self) {
let tx = self.tx.clone();
let config = self.config.clone();
let paths = self.config.paths.clone();
std::thread::spawn(move || {
let path_searchers = paths
.into_iter()
.map(|path| {
let config = config.clone();
let tx = tx.clone();
std::thread::spawn(move || Self::run(&path, config, tx))
})
.collect::<Vec<_>>();
pub fn search(config: SearchConfig, tx: mpsc::Sender<Event>) {
std::thread::spawn(move || {
let path_searchers = config
.paths
.clone()
.into_iter()
.map(|path| {
let config = config.clone();
let tx = tx.clone();
std::thread::spawn(move || run(&path, config, tx))
})
.collect::<Vec<_>>();

for searcher in path_searchers {
if searcher.join().is_err() {
tx.send(Event::Error).ok();
return;
}
for searcher in path_searchers {
if searcher.join().is_err() {
tx.send(Event::Error).ok();
return;
}
}

tx.send(Event::SearchingFinished).ok();
});
}
tx.send(Event::SearchingFinished).ok();
});
}

fn run(path: &Path, config: SearchConfig, tx: mpsc::Sender<Event>) {
let grep_searcher = SearcherBuilder::new()
.binary_detection(BinaryDetection::quit(b'\x00'))
.line_terminator(LineTerminator::byte(b'\n'))
.line_number(true)
.multi_line(false)
.build();
fn run(path: &Path, config: SearchConfig, tx: mpsc::Sender<Event>) {
let grep_searcher = SearcherBuilder::new()
.binary_detection(BinaryDetection::quit(b'\x00'))
.line_terminator(LineTerminator::byte(b'\n'))
.line_number(true)
.multi_line(false)
.build();

let matcher = RegexMatcherBuilder::new()
.line_terminator(Some(b'\n'))
.case_insensitive(config.case_insensitive)
.case_smart(config.case_smart)
.build(&config.pattern)
.expect("Cannot build RegexMatcher");
let mut builder = WalkBuilder::new(path);
let matcher = RegexMatcherBuilder::new()
.line_terminator(Some(b'\n'))
.case_insensitive(config.case_insensitive)
.case_smart(config.case_smart)
.build(&config.pattern)
.expect("Cannot build RegexMatcher");
let mut builder = WalkBuilder::new(path);

let walk_parallel = builder
.overrides(config.overrides.clone())
.types(config.types.clone())
.hidden(!config.search_hidden)
.build_parallel();
walk_parallel.run(move || {
let tx = tx.clone();
let matcher = matcher.clone();
let mut grep_searcher = grep_searcher.clone();
let walk_parallel = builder
.overrides(config.overrides.clone())
.types(config.types.clone())
.hidden(!config.search_hidden)
.build_parallel();
walk_parallel.run(move || {
let tx = tx.clone();
let matcher = matcher.clone();
let mut grep_searcher = grep_searcher.clone();

Box::new(move |result| {
let dir_entry = match result {
Ok(entry) => {
if !entry.file_type().map_or(false, |ft| ft.is_file()) {
return ignore::WalkState::Continue;
}
entry
Box::new(move |result| {
let dir_entry = match result {
Ok(entry) => {
if !entry.file_type().map_or(false, |ft| ft.is_file()) {
return ignore::WalkState::Continue;
}
Err(_) => return ignore::WalkState::Continue,
};
let mut matches_in_entry = Vec::new();
let sr = MatchesSink::new(&matcher, &mut matches_in_entry);
grep_searcher
.search_path(&matcher, dir_entry.path(), sr)
.ok();

if !matches_in_entry.is_empty() {
tx.send(Event::NewEntry(FileEntry::new(
dir_entry.path().to_string_lossy().into_owned(),
matches_in_entry,
)))
.ok();
entry
}
Err(_) => return ignore::WalkState::Continue,
};
let mut matches_in_entry = Vec::new();
let sr = MatchesSink::new(&matcher, &mut matches_in_entry);
grep_searcher
.search_path(&matcher, dir_entry.path(), sr)
.ok();

ignore::WalkState::Continue
})
});
}
if !matches_in_entry.is_empty() {
tx.send(Event::NewEntry(FileEntry::new(
dir_entry.path().to_string_lossy().into_owned(),
matches_in_entry,
)))
.ok();
}

ignore::WalkState::Continue
})
});
}
38 changes: 34 additions & 4 deletions src/ui/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use super::{
input_handler::{InputHandler, InputState},
result_list::ResultList,
scroll_offset_list::{List, ListItem, ListState, ScrollOffset},
search_popup::SearchPopup,
theme::Theme,
};

Expand All @@ -29,27 +30,32 @@ use ratatui::{
use std::path::PathBuf;

pub struct App {
search_config: SearchConfig,
ig: Ig,
result_list: ResultList,
result_list_state: ListState,
context_viewer_state: ContextViewerState,
theme: Box<dyn Theme>,
search_popup: SearchPopup,
}

impl App {
pub fn new(config: SearchConfig, editor: Editor, theme: Box<dyn Theme>) -> Self {
pub fn new(search_config: SearchConfig, editor: Editor, theme: Box<dyn Theme>) -> Self {
Self {
ig: Ig::new(config, editor),
search_config,
ig: Ig::new(editor),
result_list: ResultList::default(),
result_list_state: ListState::default(),
context_viewer_state: ContextViewerState::default(),
theme,
search_popup: SearchPopup::default(),
}
}

pub fn run(&mut self) -> Result<()> {
let mut input_handler = InputHandler::default();
self.ig.search(&mut self.result_list);
self.ig
.search(self.search_config.clone(), &mut self.result_list);

loop {
let backend = CrosstermBackend::new(std::io::stdout());
Expand Down Expand Up @@ -133,11 +139,15 @@ impl App {
};

Self::draw_list(frame, list_area, app);

if let Some(cv_area) = cv_area {
Self::draw_context_viewer(frame, cv_area, app);
}

Self::draw_bottom_bar(frame, bottom_bar_area, app, input_handler);

app.search_popup
.draw(frame, app.theme.search_popup_border());
}

fn draw_list(frame: &mut Frame<CrosstermBackend<std::io::Stdout>>, area: Rect, app: &mut App) {
Expand Down Expand Up @@ -385,12 +395,29 @@ impl Application for App {
}

fn on_search(&mut self) {
self.ig.search(&mut self.result_list);
let pattern = self.search_popup.get_pattern();
self.search_config.pattern = pattern;
self.ig
.search(self.search_config.clone(), &mut self.result_list);
}

fn on_exit(&mut self) {
self.ig.exit();
}

fn on_toggle_popup(&mut self) {
self.search_popup
.set_pattern(self.search_config.pattern.clone());
self.search_popup.toggle();
}

fn on_char_inserted(&mut self, c: char) {
self.search_popup.insert_char(c);
}

fn on_char_removed(&mut self) {
self.search_popup.remove_char();
}
}

#[cfg_attr(test, mockall::automock)]
Expand All @@ -409,4 +436,7 @@ pub trait Application {
fn on_open_file(&mut self);
fn on_search(&mut self);
fn on_exit(&mut self);
fn on_toggle_popup(&mut self);
fn on_char_inserted(&mut self, c: char);
fn on_char_removed(&mut self);
}
Loading

0 comments on commit d7a1ab5

Please sign in to comment.