diff options
Diffstat (limited to 'default-plugins/strider/src/search/search_state.rs')
-rw-r--r-- | default-plugins/strider/src/search/search_state.rs | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/default-plugins/strider/src/search/search_state.rs b/default-plugins/strider/src/search/search_state.rs new file mode 100644 index 000000000..5766d1c29 --- /dev/null +++ b/default-plugins/strider/src/search/search_state.rs @@ -0,0 +1,241 @@ +use crate::search::search_results::SearchResult; +use crate::search::{MessageToSearch, ResultsOfSearch}; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use zellij_tile::prelude::{ + hide_self, open_file, open_file_floating, open_file_with_line, open_file_with_line_floating, + open_terminal, open_terminal_floating, post_message_to, Key, +}; + +pub const CURRENT_SEARCH_TERM: &str = "/data/current_search_term"; + +#[derive(Default)] +pub struct SearchState { + pub search_term: String, + pub file_name_search_results: Vec<SearchResult>, + pub file_contents_search_results: Vec<SearchResult>, + pub loading: bool, + pub loading_animation_offset: u8, + pub selected_search_result: usize, + pub should_open_floating: bool, + pub search_filter: SearchType, + pub display_rows: usize, + pub display_columns: usize, + pub displayed_search_results: (usize, Vec<SearchResult>), // usize is selected index +} + +impl SearchState { + pub fn handle_key(&mut self, key: Key) { + match key { + Key::Down => self.move_search_selection_down(), + Key::Up => self.move_search_selection_up(), + Key::Char('\n') => self.open_search_result_in_editor(), + Key::BackTab => self.open_search_result_in_terminal(), + Key::Ctrl('f') => { + self.should_open_floating = !self.should_open_floating; + }, + Key::Ctrl('r') => self.toggle_search_filter(), + Key::Esc => { + hide_self(); + self.clear_state(); + }, + _ => self.append_to_search_term(key), + } + } + pub fn update_file_name_search_results(&mut self, mut results_of_search: ResultsOfSearch) { + if self.search_term == results_of_search.search_term { + self.file_name_search_results = results_of_search.search_results.drain(..).collect(); + self.update_displayed_search_results(); + } + } + pub fn update_file_contents_search_results(&mut self, mut results_of_search: ResultsOfSearch) { + if self.search_term == results_of_search.search_term { + self.file_contents_search_results = + results_of_search.search_results.drain(..).collect(); + self.update_displayed_search_results(); + } + } + pub fn change_size(&mut self, rows: usize, cols: usize) { + self.display_rows = rows; + self.display_columns = cols; + } + pub fn progress_animation(&mut self) { + if self.loading_animation_offset == u8::MAX { + self.loading_animation_offset = 0; + } else { + self.loading_animation_offset = self.loading_animation_offset.saturating_add(1); + } + } + pub fn number_of_lines_in_displayed_search_results(&self) -> usize { + self.displayed_search_results + .1 + .iter() + .map(|l| l.rendered_height()) + .sum() + } + fn move_search_selection_down(&mut self) { + if self.displayed_search_results.0 < self.max_search_selection_index() { + self.displayed_search_results.0 += 1; + } + } + fn move_search_selection_up(&mut self) { + self.displayed_search_results.0 = self.displayed_search_results.0.saturating_sub(1); + } + fn open_search_result_in_editor(&mut self) { + match self.selected_search_result_entry() { + Some(SearchResult::File { path, .. }) => { + if self.should_open_floating { + open_file_floating(&PathBuf::from(path)) + } else { + open_file(&PathBuf::from(path)); + } + }, + Some(SearchResult::LineInFile { + path, line_number, .. + }) => { + if self.should_open_floating { + open_file_with_line_floating(&PathBuf::from(path), line_number); + } else { + open_file_with_line(&PathBuf::from(path), line_number); + } + }, + None => eprintln!("Search results not found"), + } + } + fn open_search_result_in_terminal(&mut self) { + let dir_path_of_result = |path: &str| -> PathBuf { + let file_path = PathBuf::from(path); + let mut dir_path = file_path.components(); + dir_path.next_back(); // remove file name to stay with just the folder + dir_path.as_path().into() + }; + let selected_search_result_entry = self.selected_search_result_entry(); + if let Some(SearchResult::File { path, .. }) | Some(SearchResult::LineInFile { path, .. }) = + selected_search_result_entry + { + let dir_path = dir_path_of_result(&path); + if self.should_open_floating { + open_terminal_floating(&dir_path); + } else { + open_terminal(&dir_path); + } + } + } + fn toggle_search_filter(&mut self) { + self.search_filter.progress(); + self.send_search_query(); + } + fn clear_state(&mut self) { + self.file_name_search_results.clear(); + self.file_contents_search_results.clear(); + self.displayed_search_results = (0, vec![]); + self.search_term.clear(); + } + fn append_to_search_term(&mut self, key: Key) { + match key { + Key::Char(character) => { + self.search_term.push(character); + }, + Key::Backspace => { + self.search_term.pop(); + if self.search_term.len() == 0 { + self.clear_state(); + } + }, + _ => {}, + } + self.send_search_query(); + } + fn send_search_query(&mut self) { + match std::fs::write(CURRENT_SEARCH_TERM, &self.search_term) { + Ok(_) => { + if !self.search_term.is_empty() { + post_message_to( + "file_name_search", + serde_json::to_string(&MessageToSearch::Search).unwrap(), + "".to_owned(), + ); + post_message_to( + "file_contents_search", + serde_json::to_string(&MessageToSearch::Search).unwrap(), + "".to_owned(), + ); + self.file_name_search_results.clear(); + self.file_contents_search_results.clear(); + } + }, + Err(e) => eprintln!("Failed to write search term to HD, aborting search: {}", e), + } + } + fn max_search_selection_index(&self) -> usize { + self.displayed_search_results.1.len().saturating_sub(1) + } + fn update_displayed_search_results(&mut self) { + if self.search_term.is_empty() { + self.clear_state(); + return; + } + let mut search_results_of_interest = match self.search_filter { + SearchType::NamesAndContents => { + let mut all_search_results = self.file_name_search_results.clone(); + all_search_results.append(&mut self.file_contents_search_results.clone()); + all_search_results.sort_by(|a, b| b.score().cmp(&a.score())); + all_search_results + }, + SearchType::Names => self.file_name_search_results.clone(), + SearchType::Contents => self.file_contents_search_results.clone(), + }; + let mut height_taken_up_by_results = 0; + let mut displayed_search_results = vec![]; + for search_result in search_results_of_interest.drain(..) { + if height_taken_up_by_results + search_result.rendered_height() + > self.rows_for_results() + { + break; + } + height_taken_up_by_results += search_result.rendered_height(); + displayed_search_results.push(search_result); + } + let new_index = self + .selected_search_result_entry() + .and_then(|currently_selected_search_result| { + displayed_search_results + .iter() + .position(|r| r.is_same_entry(¤tly_selected_search_result)) + }) + .unwrap_or(0); + self.displayed_search_results = (new_index, displayed_search_results); + } + fn selected_search_result_entry(&self) -> Option<SearchResult> { + self.displayed_search_results + .1 + .get(self.displayed_search_results.0) + .cloned() + } + pub fn rows_for_results(&self) -> usize { + self.display_rows.saturating_sub(3) // search line and 2 controls lines + } +} + +#[derive(Serialize, Deserialize)] +pub enum SearchType { + NamesAndContents, + Names, + Contents, +} + +impl SearchType { + pub fn progress(&mut self) { + match &self { + &SearchType::NamesAndContents => *self = SearchType::Names, + &SearchType::Names => *self = SearchType::Contents, + &SearchType::Contents => *self = SearchType::NamesAndContents, + } + } +} + +impl Default for SearchType { + fn default() -> Self { + SearchType::NamesAndContents + } +} |