From c4fd29541b049f38a9a898974b0b098f6bffe777 Mon Sep 17 00:00:00 2001 From: Adhyan Date: Sun, 1 Sep 2024 18:52:26 -0600 Subject: [PATCH] added a way to search through list, ref #2093 --- src/exercise.rs | 1 + src/list.rs | 55 +++++++++++++++++++++++++++++++++++++--- src/list/scroll_state.rs | 2 +- src/list/state.rs | 37 +++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 4 deletions(-) diff --git a/src/exercise.rs b/src/exercise.rs index 11eea638..68068076 100644 --- a/src/exercise.rs +++ b/src/exercise.rs @@ -55,6 +55,7 @@ fn run_bin( } /// See `info_file::ExerciseInfo` +#[derive(Debug)] pub struct Exercise { pub dir: Option<&'static str>, pub name: &'static str, diff --git a/src/list.rs b/src/list.rs index 481fb2f4..8364da7d 100644 --- a/src/list.rs +++ b/src/list.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, Result}; +use anyhow::{Context, Ok, Result}; use crossterm::{ cursor, event::{ @@ -21,6 +21,7 @@ mod state; fn handle_list(app_state: &mut AppState, stdout: &mut StdoutLock) -> Result<()> { let mut list_state = ListState::new(app_state, stdout)?; + let mut is_searching = false; loop { match event::read().context("Failed to read terminal event")? { @@ -31,9 +32,50 @@ fn handle_list(app_state: &mut AppState, stdout: &mut StdoutLock) -> Result<()> } list_state.message.clear(); + + let curr_key = key.code; + + if is_searching { + match curr_key { + KeyCode::Esc | KeyCode::Enter => { + is_searching = false; // not sure why rust analyzer thinks this is unused + list_state.search_query.clear(); + return Ok(()); + } + KeyCode::Char(k) => { + eprintln!("pressed while searching {:?}", curr_key); + + list_state.search_query.push(k); + list_state.message.push_str("search:"); + list_state.message.push_str(&list_state.search_query); + list_state.message.push_str("|"); + + list_state.select_if_matches_search_query(); + + list_state.draw(stdout)?; + continue; + } + KeyCode::Backspace => { + list_state.search_query.pop(); + list_state.message.push_str("search:"); + list_state.message.push_str(&list_state.search_query); + list_state.message.push_str("|"); + + list_state.select_if_matches_search_query(); + + list_state.draw(stdout)?; + continue; + } + _ => { + continue; + } + } + } match key.code { - KeyCode::Char('q') => return Ok(()), + KeyCode::Char('q') => { + return Ok(()); + } KeyCode::Down | KeyCode::Char('j') => list_state.select_next(), KeyCode::Up | KeyCode::Char('k') => list_state.select_previous(), KeyCode::Home | KeyCode::Char('g') => list_state.select_first(), @@ -66,9 +108,16 @@ fn handle_list(app_state: &mut AppState, stdout: &mut StdoutLock) -> Result<()> return Ok(()); } } + KeyCode::Char('s') | KeyCode::Char('/') => { + eprintln!("starting search"); + list_state.message.push_str("search:|"); + is_searching = true; + } // Redraw to remove the message. KeyCode::Esc => (), - _ => continue, + _ => { + continue; + } } } Event::Mouse(event) => match event.kind { diff --git a/src/list/scroll_state.rs b/src/list/scroll_state.rs index 25a73736..2c02ed4f 100644 --- a/src/list/scroll_state.rs +++ b/src/list/scroll_state.rs @@ -46,7 +46,7 @@ impl ScrollState { self.selected } - fn set_selected(&mut self, selected: usize) { + pub fn set_selected(&mut self, selected: usize) { self.selected = Some(selected); self.update_offset(); } diff --git a/src/list/state.rs b/src/list/state.rs index 49b6d5d8..117740cf 100644 --- a/src/list/state.rs +++ b/src/list/state.rs @@ -45,6 +45,7 @@ pub struct ListState<'a> { term_height: u16, separator_line: Vec, show_footer: bool, + pub search_query: String, } impl<'a> ListState<'a> { @@ -78,6 +79,7 @@ impl<'a> ListState<'a> { term_height: 0, separator_line: Vec::new(), show_footer: true, + search_query: String::new(), }; slf.set_term_size(width, height); @@ -356,6 +358,41 @@ impl<'a> ListState<'a> { Ok(()) } + + pub fn select_if_matches_search_query(&mut self) { + eprintln!("search query: {:?}", self.search_query); + + let idx = self + .app_state + .exercises() + .iter() + .enumerate() + .find_map(|(i, s)| { + if s.name.contains(self.search_query.as_str()) { + Some(i) + } else { + None + } + }); + eprintln!("idx: {:?}", idx); + + match idx { + Some(i) => { + // ? do we need this function call? + // let exercise_ind = self.selected_to_exercise_ind(i).unwrap(); + let exercise_ind = i; + self.scroll_state.set_selected(exercise_ind); + eprintln!("exercise_ind: {:?}", exercise_ind); + self.update_rows(); + } + None => { + let msg = String::from("[NOT FOUND]") + &self.message.clone(); + self.message.clear(); + self.message.push_str(&msg); + } + } + + } // Return `true` if there was something to select. pub fn selected_to_current_exercise(&mut self) -> Result {