Skip to content

Commit

Permalink
refactor and fix
Browse files Browse the repository at this point in the history
  • Loading branch information
SimonThormeyer committed Nov 1, 2024
1 parent 4fd6c45 commit 8c4a877
Show file tree
Hide file tree
Showing 2 changed files with 281 additions and 266 deletions.
305 changes: 266 additions & 39 deletions litt/src/interactive_search.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -35,20 +42,245 @@ pub(super) enum InteractiveSearchInput {
LastSearchResult(u32),
}

fn read(history: &mut Vec<String>) -> Result<InteractiveSearchInput, LittError> {
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::<u32>() {
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<String> = 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!(
Expand All @@ -73,35 +305,32 @@ 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) => {}
(_, Quit) => {
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.
(WaitingForInitialInput { .. }, BrowseBackward | BrowseForward) => {
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 {
Expand All @@ -110,9 +339,7 @@ impl InteractiveSearch {
}
// Change options or fuzzy search
(
WaitingForInitialInput
| SearchInProgress { .. }
| OpenPdf { .. },
WaitingForInitialInput | SearchInProgress { .. } | OpenPdf { .. },
SearchOptionUpdate(update),
) => match update {
Limit(limit) => {
Expand All @@ -134,10 +361,10 @@ impl InteractiveSearch {
SearchTerm(term),
) => {
self.state = SearchInProgress {
search_term: term.to_string()
search_term: term.to_string(),
};
}
(Finished, _) => unreachable!(),
}
}
}
}
Loading

0 comments on commit 8c4a877

Please sign in to comment.