use std::cmp::Ordering;
#[derive(Debug, Clone)]
pub struct Match {
pub str: String,
pub index: usize,
pub matched_indexes: Vec<usize>,
pub score: i32,
}
const FIRST_CHAR_MATCH_BONUS: i32 = 10;
const MATCH_FOLLOWING_SEPARATOR_BONUS: i32 = 20;
const CAMEL_CASE_MATCH_BONUS: i32 = 20;
const ADJACENT_MATCH_BONUS: i32 = 5;
const UNMATCHED_LEADING_CHAR_PENALTY: i32 = -5;
const MAX_UNMATCHED_LEADING_CHAR_PENALTY: i32 = -15;
const SEPARATORS: [char; 6] = ['/', '-', '_', '.', ' ', '\\'];
pub fn find(pattern: &str, data: &[String]) -> Vec<Match> {
let mut matches = find_no_sort(pattern, data);
matches.sort_by(|a, b| a.score.cmp(&b.score).reverse());
matches
}
pub fn find_no_sort(pattern: &str, data: &[String]) -> Vec<Match> {
if pattern.is_empty() {
return vec![];
}
let runes: Vec<char> = pattern.chars().collect();
let mut matches: Vec<Match> = Vec::new();
let mut matched_indexes: Option<Vec<usize>> = None;
for (i, s) in data.iter().enumerate() {
let mut match_ = Match {
str: s.clone(),
index: i,
matched_indexes: matched_indexes
.take()
.unwrap_or_else(|| Vec::with_capacity(runes.len())),
score: 0,
};
let mut pattern_index = 0usize;
let mut best_score = -1i32;
let mut matched_index: isize = -1;
let mut curr_adjacent_match_bonus = 0i32;
let mut last: char = '\0';
let mut last_index = 0usize;
let chars: Vec<char> = s.chars().collect();
let mut j = 0usize;
while j < chars.len() {
let candidate = chars[j];
if let Some(pc) = runes.get(pattern_index).copied() {
if equal_fold(candidate, pc) {
let mut score = 0i32;
if j == 0 {
score += FIRST_CHAR_MATCH_BONUS;
}
if last.is_lowercase() && candidate.is_uppercase() {
score += CAMEL_CASE_MATCH_BONUS;
}
if j != 0 && is_separator(last) {
score += MATCH_FOLLOWING_SEPARATOR_BONUS;
}
if let Some(&last_match) = match_.matched_indexes.last() {
let bonus =
adjacent_char_bonus(last_index, last_match, curr_adjacent_match_bonus);
score += bonus;
curr_adjacent_match_bonus += bonus;
}
if score > best_score {
best_score = score;
matched_index = j as isize;
}
}
}
let nextp = if pattern_index + 1 < runes.len() {
Some(runes[pattern_index + 1])
} else {
None
};
let nextc = if j + 1 < chars.len() {
Some(chars[j + 1])
} else {
None
};
if ((nextp.is_some() && nextc.is_some() && equal_fold(nextp.unwrap(), nextc.unwrap()))
|| nextc.is_none())
&& matched_index > -1
{
if match_.matched_indexes.is_empty() {
let penalty = matched_index as i32 * UNMATCHED_LEADING_CHAR_PENALTY;
best_score += max(penalty, MAX_UNMATCHED_LEADING_CHAR_PENALTY);
}
match_.score += best_score;
match_.matched_indexes.push(matched_index as usize);
best_score = -1;
pattern_index += 1;
}
last_index = j;
last = candidate;
j += 1;
}
let penalty = match_.matched_indexes.len() as i32 - chars.len() as i32;
match_.score += penalty;
if match_.matched_indexes.len() == runes.len() {
matches.push(match_);
matched_indexes = None;
} else {
matched_indexes = Some(match_.matched_indexes.clone());
}
}
matches
}
fn equal_fold(tr: char, sr: char) -> bool {
if tr == sr {
return true;
}
if tr.to_lowercase().collect::<String>() == sr.to_lowercase().collect::<String>() {
return true;
}
let tr_lower = tr.to_ascii_lowercase();
let sr_lower = sr.to_ascii_lowercase();
tr_lower == sr_lower && (tr.is_ascii_alphabetic() || sr.is_ascii_alphabetic())
}
fn adjacent_char_bonus(i: usize, last_match: usize, current_bonus: i32) -> i32 {
if last_match == i {
return current_bonus * 2 + ADJACENT_MATCH_BONUS;
}
0
}
fn is_separator(s: char) -> bool {
SEPARATORS.contains(&s)
}
fn max(x: i32, y: i32) -> i32 {
if x > y {
x
} else {
y
}
}
pub fn score_cmp(a: &Match, b: &Match) -> Ordering {
b.score.cmp(&a.score)
}