diff --git a/src/app.rs b/src/app.rs index 3c63f29..6c0174c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -57,6 +57,13 @@ impl SearchState { self.selected = self.results.len().saturating_sub(1); } } + + pub fn toggle_all_selected(&mut self) { + let all_included = self.results.iter().all(|res| res.included); + self.results + .iter_mut() + .for_each(|res| res.included = !all_included); + } } #[derive(Debug, Eq, PartialEq)] @@ -539,6 +546,11 @@ impl App { .search_results_mut() .toggle_selected_inclusion(); } + (KeyCode::Char('a'), _) => { + self.current_screen + .search_results_mut() + .toggle_all_selected(); + } (KeyCode::Enter, _) => { if matches!(self.current_screen, Screen::SearchComplete(_)) { if let Screen::SearchComplete(search_state) = @@ -785,6 +797,97 @@ mod tests { rng.gen_range(1..10000) } + fn search_result(included: bool) -> SearchResult { + SearchResult { + path: Path::new("random/file").to_path_buf(), + line_number: random_num(), + line: "foo".to_owned(), + replacement: "bar".to_owned(), + included, + replace_result: None, + } + } + + #[test] + fn test_toggle_all_selected_when_all_selected() { + let mut search_state = SearchState { + results: vec![ + search_result(true), + search_result(true), + search_result(true), + ], + selected: 0, + }; + search_state.toggle_all_selected(); + assert_eq!( + search_state + .results + .iter() + .map(|res| res.included) + .collect::>(), + vec![false, false, false] + ); + } + + #[test] + fn test_toggle_all_selected_when_none_selected() { + let mut search_state = SearchState { + results: vec![ + search_result(false), + search_result(false), + search_result(false), + ], + selected: 0, + }; + search_state.toggle_all_selected(); + assert_eq!( + search_state + .results + .iter() + .map(|res| res.included) + .collect::>(), + vec![true, true, true] + ); + } + + #[test] + fn test_toggle_all_selected_when_some_selected() { + let mut search_state = SearchState { + results: vec![ + search_result(true), + search_result(false), + search_result(true), + ], + selected: 0, + }; + search_state.toggle_all_selected(); + assert_eq!( + search_state + .results + .iter() + .map(|res| res.included) + .collect::>(), + vec![true, true, true] + ); + } + + #[test] + fn test_toggle_all_selected_when_no_results() { + let mut search_state = SearchState { + results: vec![], + selected: 0, + }; + search_state.toggle_all_selected(); + assert_eq!( + search_state + .results + .iter() + .map(|res| res.included) + .collect::>(), + vec![] as Vec + ); + } + fn success_result() -> SearchResult { SearchResult { path: Path::new("random/file").to_path_buf(), diff --git a/src/ui.rs b/src/ui.rs index 62178c9..1e11369 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -449,13 +449,13 @@ pub fn render(app: &App, frame: &mut Frame<'_>) { } Screen::SearchProgressing(_) | Screen::SearchComplete(_) => { let mut keys = if let Screen::SearchComplete(_) = app.current_screen { - // TODO: actually prevent confirmation when search is in progress vec![" replace"] } else { vec![] }; keys.append(&mut vec![ " toggle", + " toggle all", " down", " up", " back",