mirror of
https://github.com/lotabout/skim.git
synced 2026-09-10 07:16:23 -04:00
[fuzzy] add fuzzy search algorithm
Compare the query and choice/item character by character. sorting is not implemented yet.
This commit is contained in:
parent
6816c22d00
commit
74b7ed6da0
20
src/item.rs
20
src/item.rs
|
|
@ -1,6 +1,7 @@
|
|||
// An item is line of text that read from `find` command or stdin together with
|
||||
// the internal states, such as selected or not
|
||||
|
||||
use std;
|
||||
pub struct Item {
|
||||
pub text: String,
|
||||
pub selected: bool,
|
||||
|
|
@ -22,26 +23,29 @@ impl Item {
|
|||
}
|
||||
}
|
||||
|
||||
pub type Score = (usize, usize); // score (matched-len, start pos)
|
||||
pub type Range = (usize, usize); // (start, end), end is excluded
|
||||
|
||||
pub struct MatchedItem {
|
||||
pub index: usize, // index of current item in items
|
||||
pub rank: [i32; 5], // the scores in different criteria
|
||||
pub matched_range_bytes: (i32, i32), // range of bytes that metched the pattern
|
||||
pub score: Score,
|
||||
pub matched_range_chars: Range, // range of chars that metched the pattern
|
||||
}
|
||||
|
||||
impl MatchedItem {
|
||||
pub fn new(index: usize) -> Self {
|
||||
MatchedItem {
|
||||
index: index,
|
||||
rank: [0, 0, 0, 0, 0],
|
||||
matched_range_bytes: (0, 0),
|
||||
score: (std::usize::MAX, 0),
|
||||
matched_range_chars: (0, 0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_matched_range(&mut self, start: i32, end: i32) {
|
||||
self.matched_range_bytes = (start, end);
|
||||
pub fn set_matched_range(&mut self, range: Range) {
|
||||
self.matched_range_chars = range;
|
||||
}
|
||||
|
||||
pub fn set_rank(&mut self, pos: usize, val: i32) {
|
||||
self.rank[pos] = val;
|
||||
pub fn set_score(&mut self, score: Score) {
|
||||
self.score = score;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ mod input;
|
|||
mod matcher;
|
||||
mod event;
|
||||
mod model;
|
||||
mod score;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use std::sync::mpsc::Sender;
|
|||
use event::Event;
|
||||
use item::{Item, MatchedItem};
|
||||
use util::eventbox::EventBox;
|
||||
use score;
|
||||
|
||||
pub struct Matcher {
|
||||
tx_output: Sender<MatchedItem>, // channel to send output to
|
||||
|
|
@ -41,14 +42,28 @@ impl Matcher {
|
|||
item.starts_with(&self.query)
|
||||
}
|
||||
|
||||
fn match_item(&self, index: usize, item: &str) -> Option<MatchedItem> {
|
||||
let matched_result = score::compute_match_length(item, &self.query);
|
||||
if matched_result == None {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (matched_start, matched_len) = matched_result.unwrap();
|
||||
|
||||
let mut item = MatchedItem::new(index);
|
||||
item.set_matched_range((matched_start as usize, (matched_start + matched_len) as usize));
|
||||
item.set_score((matched_len, matched_start));
|
||||
Some(item)
|
||||
}
|
||||
|
||||
pub fn process(&mut self) {
|
||||
let items = self.items.read().unwrap();
|
||||
for item in items[self.item_pos..].into_iter() {
|
||||
// process the matcher
|
||||
//self.tx_output.send(string.clone());
|
||||
if self.match_str(&item.text) {
|
||||
if let Some(matched) = self.match_item(self.item_pos, &item.text) {
|
||||
self.num_matched += 1;
|
||||
let _ = self.tx_output.send(MatchedItem::new(self.item_pos));
|
||||
let _ = self.tx_output.send(matched);
|
||||
}
|
||||
|
||||
self.item_pos += 1;
|
||||
|
|
|
|||
64
src/score.rs
Normal file
64
src/score.rs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/// score is responsible for calculating the scores of the similarity between
|
||||
/// the query and the choice.
|
||||
///
|
||||
/// It is modeled after https://github.com/felipesere/icepick.git
|
||||
|
||||
// return (start, matched_len)
|
||||
pub fn compute_match_length(choice: &str, query: &str) -> Option<(usize, usize)> {
|
||||
if query.len() <= 0 {
|
||||
return Some((0, 0));
|
||||
}
|
||||
|
||||
let impossible_match = choice.len() + 1;
|
||||
let mut matched_start = impossible_match;
|
||||
let mut matched_end = impossible_match;
|
||||
|
||||
let mut choice_chars = choice.chars().enumerate().peekable();
|
||||
let mut query_chars = query.chars().enumerate().peekable();
|
||||
|
||||
loop {
|
||||
if query_chars.peek() == None {
|
||||
return Some((matched_start, matched_end - matched_start+1));
|
||||
}
|
||||
|
||||
if choice_chars.peek() == None {
|
||||
return None;
|
||||
}
|
||||
|
||||
let &(idx_choice, c) = choice_chars.peek().unwrap();
|
||||
let &(_, q) = query_chars.peek().unwrap();
|
||||
|
||||
|
||||
if c == q {
|
||||
if matched_start == impossible_match { matched_start = idx_choice; }
|
||||
let _ = query_chars.next();
|
||||
}
|
||||
|
||||
matched_end = idx_choice;
|
||||
let _ = choice_chars.next();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_compute_match_length() {
|
||||
let choice_1 = "I am a 中国人.";
|
||||
let query_1 = "a人";
|
||||
assert_eq!(super::compute_match_length(&choice_1, &query_1), Some((2, 8)));
|
||||
|
||||
let choice_2 = "Choice did not matter";
|
||||
let query_2 = "";
|
||||
assert_eq!(super::compute_match_length(&choice_2, &query_2), Some((0, 0)));
|
||||
|
||||
let choice_3 = "abcdefg";
|
||||
let query_3 = "hi";
|
||||
assert_eq!(super::compute_match_length(&choice_3, &query_3), None);
|
||||
|
||||
let choice_4 = "Partial match did not count";
|
||||
let query_4 = "PP";
|
||||
assert_eq!(compute_match_length(&choice_4, &query_4), None);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue