From 2abe6bdc720f2d208d7b7af7905473b4015a5674 Mon Sep 17 00:00:00 2001 From: SimonThormeyer Date: Fri, 1 Nov 2024 09:48:49 +0100 Subject: [PATCH] refactor and fix --- litt/src/interactive_search.rs | 305 ++++++++++++++++++++++++++++----- litt/src/main.rs | 242 ++------------------------ 2 files changed, 281 insertions(+), 266 deletions(-) diff --git a/litt/src/interactive_search.rs b/litt/src/interactive_search.rs index 8689c9a..8ebc1e4 100644 --- a/litt/src/interactive_search.rs +++ b/litt/src/interactive_search.rs @@ -1,31 +1,38 @@ -use crate::interactive_search::InteractiveSearchState::*; -use crate::interactive_search::SearchOptionUpdate::*; -use crate::InteractiveSearchInput::{*}; -use crate::SearchOptions; +use crate::tracker::IndexTracker; +use crate::{fast_open_result, search_litt_index, LittError, SearchOptions}; +use crossterm::cursor::MoveToColumn; +use crossterm::event::{Event, KeyCode}; +use crossterm::{event, execute, terminal}; +use litt_search::search::Search; +use std::io; +use std::io::Write; +use std::path::Path; +use tantivy::Searcher; +use unicode_segmentation::UnicodeSegmentation; +use InteractiveSearchInput::*; +use InteractiveSearchInput::*; +use InteractiveSearchState::*; +use SearchOptionUpdate::*; -pub(super) struct InteractiveSearch { +pub struct InteractiveSearch { state: InteractiveSearchState, options: SearchOptions, } -pub(super) enum InteractiveSearchState { +enum InteractiveSearchState { WaitingForInitialInput, - SearchInProgress { - search_term: String, - }, -OpenPdf { - last_result_num: u32, - }, + SearchInProgress { search_term: String }, + OpenPdf { last_result_num: u32 }, Finished, } -pub(super) enum SearchOptionUpdate { +enum SearchOptionUpdate { Limit(usize), Fuzzy(String), Distance(u8), } -pub(super) enum InteractiveSearchInput { +enum InteractiveSearchInput { BrowseBackward, BrowseForward, Quit, @@ -35,20 +42,245 @@ pub(super) enum InteractiveSearchInput { LastSearchResult(u32), } +fn read(history: &mut Vec) -> Result { + terminal::enable_raw_mode()?; + let mut stdout = io::stdout(); + let mut input_in_progress = String::new(); + let mut input = InteractiveSearchInput::Empty; + let mut index = history.len(); + print!("> "); + stdout.flush()?; + + fn clear_and_print( + stdout: &mut io::Stdout, + line: String, + adjust_cursor: bool, + ) -> Result<(), LittError> { + execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine))?; + execute!(stdout, MoveToColumn(0))?; + print!("{}", line); + if adjust_cursor { + execute!(stdout, MoveToColumn(line.len() as u16))?; + } + stdout.flush()?; + Ok(()) + } + + loop { + if event::poll(std::time::Duration::from_millis(500))? { + if let Event::Key(key_event) = event::read()? { + match key_event.code { + KeyCode::Left => { + if input_in_progress.is_empty() { + execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine))?; + input = BrowseBackward; + break; + } else if let Ok(cur_position) = crossterm::cursor::position() { + if cur_position.0 > 2 { + execute!(stdout, MoveToColumn(cur_position.0 - 1))?; + } + } + } + KeyCode::Right => { + if input_in_progress.is_empty() { + execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine))?; + input = BrowseForward; + break; + } else if let Ok(cur_position) = crossterm::cursor::position() { + if cur_position.0 - 2 < (input_in_progress.len() as u16) { + execute!(stdout, MoveToColumn(cur_position.0 + 1))?; + } + } + } + KeyCode::Up => { + if index > 0 { + index -= 1; + input_in_progress = history.get(index).unwrap().to_string(); + clear_and_print(&mut stdout, format!("> {}", input_in_progress), true)?; + stdout.flush()?; + } + } + KeyCode::Down => { + if history.len() > index + 1 { + index += 1; + input_in_progress = history.get(index).unwrap().to_string(); + clear_and_print(&mut stdout, format!("> {}", input_in_progress), true)?; + } else if history.len() > index { + index += 1; + input_in_progress = "".to_string(); + clear_and_print(&mut stdout, "> ".to_string(), false)?; + } + } + KeyCode::Char(c) => { + if let Ok(cur_position) = crossterm::cursor::position() { + let pos: usize = (cur_position.0 - 2) as usize; + if input_in_progress.len() >= pos { + input_in_progress.insert_str(pos, &c.to_string()); + clear_and_print( + &mut stdout, + format!("> {}", input_in_progress), + false, + )?; + execute!(stdout, MoveToColumn(cur_position.0 + 1))?; + } + } + stdout.flush()?; + } + KeyCode::Backspace => { + if let Ok(cur_position) = crossterm::cursor::position() { + input_in_progress = input_in_progress + .as_str() + .graphemes(true) + .enumerate() + .filter_map(|(i, g)| { + if i == (cur_position.0 as usize) - 3 { + None + } else { + Some(g) + } + }) + .collect(); + clear_and_print( + &mut stdout, + format!("> {}", input_in_progress), + false, + )?; + execute!(stdout, MoveToColumn(cur_position.0 - 1))?; + } + } + KeyCode::Enter => { + if input_in_progress == "q" { + input = Quit; + } else if let Ok(last_result) = &input_in_progress.trim().parse::() { + input = LastSearchResult(*last_result); + } else if input_in_progress.starts_with('~') { + input = SearchOptionUpdate(Fuzzy( + input_in_progress + .strip_prefix('~') + .unwrap_or(&input_in_progress) + .to_string(), + )); + } else if input_in_progress.starts_with('#') { + let parts: Vec<&str> = input_in_progress.split(' ').collect(); + let mut search_option_input = Empty; + let mut error_message: Option<&str> = None; + match (parts.get(1), parts.get(2)) { + (Some(&"limit"), None) => { + error_message = Some("Enter a limit."); + } + (Some(&"limit"), Some(limit_to_parse)) => { + let parse_result = limit_to_parse.parse(); + match parse_result { + Ok(limit) => { + search_option_input = SearchOptionUpdate(Limit(limit)); + } + Err(_) => { + error_message = Some("Error: limit must be a number."); + } + } + } + (Some(&"distance"), None) => { + error_message = Some("Enter a distance."); + } + (Some(&"distance"), Some(distance_to_parse)) => { + let parse_result = distance_to_parse.parse(); + match parse_result { + Ok(distance) => { + search_option_input = + SearchOptionUpdate(Distance(distance)); + } + Err(_) => { + error_message = + Some("Error: Distance must be a number."); + } + } + } + _ => { + error_message = Some( + "You can only set \"limit\", \"fuzzy\" or \"distance\"...", + ); + } + } + if let Some(error_message) = error_message { + println!(); + println!("{error_message}"); + } + input = search_option_input; + } else if !input_in_progress.is_empty() { + input = SearchTerm(input_in_progress.clone()); + }; + break; + } + _ => {} + } + } + } + } + terminal::disable_raw_mode()?; + println!(); + if history.is_empty() || (history.last().unwrap() != &input_in_progress) { + history.push(input_in_progress); + } + Ok(input) +} + impl InteractiveSearch { - pub(super) fn new(options: SearchOptions) -> Self { + pub fn new(options: SearchOptions) -> Self { Self { state: WaitingForInitialInput, - options + options, } } - - pub(super) fn state(&self) -> &InteractiveSearchState { - &self.state + + pub fn search( + &mut self, + search: &Search, + index_tracker: &mut IndexTracker, + index_path: &Path, + searcher: &Searcher, + index_name: &String, + ) -> Result<(), LittError> { + let mut history: Vec = Vec::new(); + loop { + self.display_instructions(&index_name); + let inp = read(&mut history)?; + self.state_transition(&inp); + let opts = &mut self.options; + match &self.state { + WaitingForInitialInput => {} + Finished => { + break; + } + OpenPdf { + last_result_num: last_res, + } => match fast_open_result(&index_tracker, last_res) { + Ok(_) => println!(), + Err(e) => return Err(e), + }, + SearchInProgress { + search_term: final_term, + } => match search_litt_index( + &search, + index_tracker, + &index_path, + &searcher, + &index_name, + final_term.to_string(), + &opts, + ) { + Ok(_) => { + opts.fuzzy = false; + println!(); + } + Err(e) => return Err(e), + }, + } + } + Ok(()) } - pub(super) fn display_instructions(&self, index_name: &str) { - let opts = self.options; + fn display_instructions(&self, index_name: &str) { + let opts = &self.options; match &self.state { WaitingForInitialInput => { println!( @@ -73,8 +305,8 @@ impl InteractiveSearch { } /// Transition the interactive search state machine. - pub(super) fn state_transition(&mut self, input: &InteractiveSearchInput) { - let mut options = &mut self.options; + fn state_transition(&mut self, input: &InteractiveSearchInput) { + let options = &mut self.options; match (&mut self.state, input) { // No state change when input is empty (_, Empty) => {} @@ -82,9 +314,12 @@ impl InteractiveSearch { self.state = Finished; } // Open pdf/ result - (WaitingForInitialInput | SearchInProgress {..} | OpenPdf { .. }, LastSearchResult(last_number_num)) => { - self.state = OpenPdf{ - last_result_num: *last_number_num, + ( + WaitingForInitialInput | SearchInProgress { .. } | OpenPdf { .. }, + LastSearchResult(last_number_num), + ) => { + self.state = OpenPdf { + last_result_num: *last_number_num, } } // Trying to browse results without having searched; print warning and do nothing. @@ -92,16 +327,10 @@ impl InteractiveSearch { println!("No search term specified! Enter search term first..."); } // Browsing results - ( - SearchInProgress { .. } | OpenPdf { .. }, - BrowseForward, - ) => { + (SearchInProgress { .. } | OpenPdf { .. }, BrowseForward) => { options.offset += options.limit; } - ( - SearchInProgress { .. } | OpenPdf { .. }, - BrowseBackward, - ) => { + (SearchInProgress { .. } | OpenPdf { .. }, BrowseBackward) => { if options.offset == 0 { println!("Offset is already zero..."); } else { @@ -110,9 +339,7 @@ impl InteractiveSearch { } // Change options or fuzzy search ( - WaitingForInitialInput - | SearchInProgress { .. } - | OpenPdf { .. }, + WaitingForInitialInput | SearchInProgress { .. } | OpenPdf { .. }, SearchOptionUpdate(update), ) => match update { Limit(limit) => { @@ -134,10 +361,10 @@ impl InteractiveSearch { SearchTerm(term), ) => { self.state = SearchInProgress { - search_term: term.to_string() + search_term: term.to_string(), }; } (Finished, _) => unreachable!(), } } -} \ No newline at end of file +} diff --git a/litt/src/main.rs b/litt/src/main.rs index 1fe94b2..6081914 100644 --- a/litt/src/main.rs +++ b/litt/src/main.rs @@ -1,24 +1,21 @@ use std::collections::HashMap; use std::fs; -use std::io::Write; use std::path::Path; use std::time::Instant; use std::{env, io}; -use unicode_segmentation::UnicodeSegmentation; use clap::CommandFactory; use clap::Parser; extern crate litt_search; -use crossterm::cursor::MoveToColumn; use litt_index::index::Index; use litt_search::search::Search; use litt_shared::search_schema::SearchSchema; use litt_shared::LITT_DIRECTORY_NAME; mod cli; -mod tracker; mod interactive_search; +mod tracker; use cli::Cli; use tantivy::Searcher; @@ -27,14 +24,7 @@ use tracker::IndexTracker; use colored::*; use thiserror::Error; -use interactive_search::InteractiveSearchInput::*; -use interactive_search::InteractiveSearchState::*; -use interactive_search::SearchOptionUpdate::*; -use crossterm::{ - event::{self, Event, KeyCode}, - execute, terminal, -}; -use interactive_search::{InteractiveSearch, InteractiveSearchInput}; +use interactive_search::InteractiveSearch; #[derive(Debug, Error)] enum LittError { @@ -48,7 +38,7 @@ enum LittError { LittIndexTrackerError(#[from] tracker::LittIndexTrackerError), } -#[derive(Copy, Clone)] +#[derive(Clone)] pub struct SearchOptions { limit: usize, offset: usize, @@ -112,178 +102,6 @@ fn show_failed_documents_error(index: &Index) { } } -fn read(history: &mut Vec) -> Result { - terminal::enable_raw_mode()?; - let mut stdout = io::stdout(); - let mut input_in_progress = String::new(); - let mut input = InteractiveSearchInput::Empty; - let mut index = history.len(); - print!("> "); - stdout.flush()?; - - fn clear_and_print( - stdout: &mut io::Stdout, - line: String, - adjust_cursor: bool, - ) -> Result<(), LittError> { - execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine))?; - execute!(stdout, MoveToColumn(0))?; - print!("{}", line); - if adjust_cursor { - execute!(stdout, MoveToColumn(line.len() as u16))?; - } - stdout.flush()?; - Ok(()) - } - - loop { - if event::poll(std::time::Duration::from_millis(500))? { - if let Event::Key(key_event) = event::read()? { - match key_event.code { - KeyCode::Left => { - if input_in_progress.is_empty() { - execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine))?; - input = BrowseBackward; - break; - } else if let Ok(cur_position) = crossterm::cursor::position() { - if cur_position.0 > 2 { - execute!(stdout, MoveToColumn(cur_position.0 - 1))?; - } - } - } - KeyCode::Right => { - if input_in_progress.is_empty() { - execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine))?; - input = BrowseForward; - break; - } else if let Ok(cur_position) = crossterm::cursor::position() { - if cur_position.0-2 < (input_in_progress.len() as u16) { - execute!(stdout, MoveToColumn(cur_position.0 + 1))?; - } - } - } - KeyCode::Up => { - if index > 0 { - index -= 1; - input_in_progress = history.get(index).unwrap().to_string(); - clear_and_print(&mut stdout, format!("> {}", input_in_progress), true)?; - stdout.flush()?; - } - } - KeyCode::Down => { - if history.len() > index + 1 { - index += 1; - input_in_progress = history.get(index).unwrap().to_string(); - clear_and_print(&mut stdout, format!("> {}", input_in_progress), true)?; - } else if history.len() > index { - index += 1; - input_in_progress = "".to_string(); - clear_and_print(&mut stdout, "> ".to_string(), false)?; - } - } - KeyCode::Char(c) => { - if let Ok(cur_position) = crossterm::cursor::position() { - let pos: usize = (cur_position.0-2) as usize; - if input_in_progress.len() >= pos { - input_in_progress.insert_str(pos, &c.to_string()); - clear_and_print(&mut stdout, format!("> {}", input_in_progress), false)?; - execute!(stdout, MoveToColumn(cur_position.0 + 1))?; - } - } - stdout.flush()?; - } - KeyCode::Backspace => { - if let Ok(cur_position) = crossterm::cursor::position() { - input_in_progress = input_in_progress.as_str() - .graphemes(true) - .enumerate() - .filter_map(|(i, g)| if i == (cur_position.0 as usize)-3 { None } else { Some(g) }) - .collect(); - clear_and_print( - &mut stdout, - format!("> {}", input_in_progress), - false, - )?; - execute!(stdout, MoveToColumn(cur_position.0 - 1))?; - } - } - KeyCode::Enter => { - if input_in_progress == "q" { - input = Quit; - } else if let Ok(last_result) = &input_in_progress.trim().parse::() { - input = LastSearchResult(*last_result); - } - else if input_in_progress.starts_with('~') { - input = SearchOptionUpdate(Fuzzy( - input_in_progress - .strip_prefix('~') - .unwrap_or(&input_in_progress) - .to_string(), - )); - } else if input_in_progress.starts_with('#') { - let parts: Vec<&str> = input_in_progress.split(' ').collect(); - let mut search_option_input = Empty; - let mut error_message: Option<&str> = None; - match (parts.get(1), parts.get(2)) { - (Some(&"limit"), None) => { - error_message = Some("Enter a limit."); - } - (Some(&"limit"), Some(limit_to_parse)) => { - let parse_result = limit_to_parse.parse(); - match parse_result { - Ok(limit) => { - search_option_input = SearchOptionUpdate(Limit(limit)); - } - Err(_) => { - error_message = Some("Error: limit must be a number."); - } - } - } - (Some(&"distance"), None) => { - error_message = Some("Enter a distance."); - } - (Some(&"distance"), Some(distance_to_parse)) => { - let parse_result = distance_to_parse.parse(); - match parse_result { - Ok(distance) => { - search_option_input = - SearchOptionUpdate(Distance(distance)); - } - Err(_) => { - error_message = - Some("Error: Distance must be a number."); - } - } - } - _ => { - error_message = Some( - "You can only set \"limit\", \"fuzzy\" or \"distance\"...", - ); - } - } - if let Some(error_message) = error_message { - println!(); - println!("{error_message}"); - } - input = search_option_input; - } else if !input_in_progress.is_empty() { - input = SearchTerm(input_in_progress.clone()); - }; - break; - } - _ => {} - } - } - } - } - terminal::disable_raw_mode()?; - println!(); - if history.is_empty() || (history.last().unwrap() != &input_in_progress) { - history.push(input_in_progress); - } - Ok(input) -} - /* * Open fast result */ @@ -387,11 +205,11 @@ fn remove_litt_index( }; let index_path = path.join(LITT_DIRECTORY_NAME); let msg = match fs::remove_dir_all(index_path) { - //. expect("Could not remove index-file") - Ok(()) => "Ok.", - Err(e) if e.kind() == io::ErrorKind::NotFound => "Index directory didn't exist.", - Err(e) => return Err(LittError::General(e.to_string())), - };// remove litt-index from tracker. + //. expect("Could not remove index-file") + Ok(()) => "Ok.", + Err(e) if e.kind() == io::ErrorKind::NotFound => "Index directory didn't exist.", + Err(e) => return Err(LittError::General(e.to_string())), + }; // remove litt-index from tracker. if let Err(e) = index_tracker.remove(index_name.clone()) { return Err(LittError::General(e.to_string())); } @@ -617,43 +435,13 @@ fn main() -> Result<(), LittError> { fuzzy: false, distance: 2, }; - let mut history: Vec = Vec::new(); let mut interactive_search = InteractiveSearch::new(opts); - loop { - interactive_search.display_instructions(&index_name); - let inp = read(&mut history)?; - interactive_search.state_transition(&inp); - match interactive_search.state() { - WaitingForInitialInput { .. } => {} - Finished => { - break; - } - OpenPdf { - last_result_num: last_res, - options: _, - } => match fast_open_result(&index_tracker, last_res) { - Ok(_) => println!(), - Err(e) => return Err(e), - } - SearchInProgress { - search_term: final_term, - options: mut opts, - } => match search_litt_index( - &search, - &mut index_tracker, - &index_path, - &searcher, - &index_name, - final_term.to_string(), - &opts, - ) { - Ok(_) => { - opts.fuzzy = false; - println!(); - } - Err(e) => return Err(e), - }, - } - } + interactive_search.search( + &search, + &mut index_tracker, + &index_path, + &searcher, + &index_name, + )?; Ok(()) }