diff --git a/src/fuzzysearch.rs b/src/fuzzysearch.rs index 5a253328e4..d01e725d02 100644 --- a/src/fuzzysearch.rs +++ b/src/fuzzysearch.rs @@ -14,8 +14,8 @@ pub fn interactive_fuzzy_search(lines: &Vec<&str>, max_results: usize) -> Select enum State { Selecting, Quit, - Selected(usize), - Edit(usize), + Selected(String), + Edit(String), } let mut state = State::Selecting; if let Ok(_raw) = RawScreen::into_raw_mode() { @@ -29,11 +29,9 @@ pub fn interactive_fuzzy_search(lines: &Vec<&str>, max_results: usize) -> Select let mut sync_stdin = input.read_sync(); while state == State::Selecting { - let search_result = fuzzy_search(&searchinput, &lines, max_results); - let selected_lines: Vec<&str> = search_result - .iter() - .map(|item| &item.highlighted_text as &str) - .collect(); + let mut search_result = fuzzy_search(&searchinput, &lines, max_results); + let selected_lines: Vec = + search_result.iter().map(|item| highlight(&item)).collect(); paint_selection_list(&selected_lines, selected); if let Some(ev) = sync_stdin.next() { match ev { @@ -52,10 +50,10 @@ pub fn interactive_fuzzy_search(lines: &Vec<&str>, max_results: usize) -> Select } } KeyEvent::Char('\n') => { - state = State::Selected(search_result[selected].text_idx); + state = State::Selected(search_result.remove(selected).text); } KeyEvent::Char('\t') | KeyEvent::Right => { - state = State::Edit(search_result[selected].text_idx); + state = State::Edit(search_result.remove(selected).text); } KeyEvent::Char(ch) => { searchinput.push(ch); @@ -66,7 +64,7 @@ pub fn interactive_fuzzy_search(lines: &Vec<&str>, max_results: usize) -> Select selected = 0; } _ => { - // println!("{}", format!("OTHER InputEvent: {:?}\n\n", k)); + // println!("OTHER InputEvent: {:?}", k); } }, _ => {} @@ -84,26 +82,25 @@ pub fn interactive_fuzzy_search(lines: &Vec<&str>, max_results: usize) -> Select terminal.clear(ClearType::FromCursorDown).unwrap(); match state { - State::Selected(idx) => SelectionResult::Selected(lines[idx].to_string()), - State::Edit(idx) => SelectionResult::Edit(lines[idx].to_string()), + State::Selected(line) => SelectionResult::Selected(line), + State::Edit(line) => SelectionResult::Edit(line), _ => SelectionResult::NoSelection, } } -struct Match { - highlighted_text: String, - text_idx: usize, +pub struct Match { + text: String, + char_matches: Vec<(usize, usize)>, } -fn fuzzy_search(searchstr: &str, lines: &Vec<&str>, max_results: usize) -> Vec { +pub fn fuzzy_search(searchstr: &str, lines: &Vec<&str>, max_results: usize) -> Vec { if searchstr.is_empty() { return lines .iter() .take(max_results) - .enumerate() - .map(|(i, line)| Match { - highlighted_text: line.to_string(), - text_idx: i, + .map(|line| Match { + text: line.to_string(), + char_matches: Vec::new(), }) .collect(); } @@ -117,41 +114,43 @@ fn fuzzy_search(searchstr: &str, lines: &Vec<&str>, max_results: usize) -> Vec>(); matches.sort_by(|a, b| b.1.score().cmp(&a.1.score())); - let highlight = Colour::Cyan; let results: Vec = matches .iter() .take(max_results) - .map(|(i, m)| { - let r = &lines[*i]; - let mut outstr = String::with_capacity(r.len()); - let mut idx = 0; - for (match_idx, len) in m.continuous_matches() { - outstr.push_str(&r[idx..match_idx]); - idx = match_idx + len; - outstr.push_str(&format!("{}", highlight.paint(&r[match_idx..idx]))); - } - if idx < r.len() { - outstr.push_str(&r[idx..r.len()]); - } - Match { - highlighted_text: outstr, - text_idx: *i, - } + .map(|(i, m)| Match { + text: lines[*i].to_string(), + char_matches: m.continuous_matches(), }) .collect(); results } -fn paint_selection_list(lines: &Vec<&str>, selected: usize) { +fn highlight(textmatch: &Match) -> String { + let hlcol = Colour::Cyan; + let text = &textmatch.text; + let mut outstr = String::with_capacity(text.len()); + let mut idx = 0; + for (match_idx, len) in &textmatch.char_matches { + outstr.push_str(&text[idx..*match_idx]); + idx = match_idx + len; + outstr.push_str(&format!("{}", hlcol.paint(&text[*match_idx..idx]))); + } + if idx < text.len() { + outstr.push_str(&text[idx..text.len()]); + } + outstr +} + +fn paint_selection_list(lines: &Vec, selected: usize) { let dimmed = Colour::White.dimmed(); let cursor = cursor(); let (_x, y) = cursor.pos(); for (i, line) in lines.iter().enumerate() { let _ = cursor.goto(0, y + (i as u16)); if selected == i { - println!("{}", *line); + println!("{}", line); } else { - println!("{}", dimmed.paint(*line)); + println!("{}", dimmed.paint(line)); } } let _ = cursor.goto(0, y + (lines.len() as u16)); @@ -164,3 +163,9 @@ fn paint_selection_list(lines: &Vec<&str>, selected: usize) { // Clear additional lines from previous selection terminal().clear(ClearType::FromCursorDown).unwrap(); } + +#[test] +fn fuzzy_match() { + let matches = fuzzy_search("cb", &vec!["abc", "cargo build"], 1); + assert_eq!(matches[0].text, "cargo build"); +}