mod algo;
mod atom;
mod banding;
mod constants;
mod helpers;
mod matrix;
mod prefilter;
#[cfg(test)]
mod tests;
use std::cell::RefCell;
use thread_local::ThreadLocal;
use self::algo::{full_dp, range_dp};
use self::atom::Atom;
use self::constants::*;
use self::prefilter::cheap_typo_prefilter;
use self::matrix::{CELL_ZERO, Cell, Dir, SWMatrix};
use crate::{
CaseMatching,
fuzzy_matcher::{FuzzyMatcher, MatchIndices, ScoreType},
};
type Score = i16;
fn precompute_bonuses<C: Atom>(cho: &[C], buf: &mut Vec<Score>) {
buf.clear();
let bonus_iter = std::iter::once(START_OF_STRING_BONUS).chain(cho.windows(2).map(|w| {
let prev = w[0];
let cur = w[1];
prev.separator_bonus() + CAMEL_CASE_BONUS * ((prev.is_lowercase() && !cur.is_lowercase()) as Score)
}));
buf.extend(bonus_iter);
}
#[derive(Debug, Default)]
pub struct ArinaeMatcher {
pub(crate) case: CaseMatching,
pub(crate) allow_typos: bool,
pub(crate) use_last_match: bool,
full_buf: ThreadLocal<RefCell<SWMatrix>>,
indices_buf: ThreadLocal<RefCell<MatchIndices>>,
#[allow(clippy::type_complexity)]
char_buf: ThreadLocal<RefCell<(Vec<char>, Vec<char>)>>,
bonus_buf: ThreadLocal<RefCell<Vec<Score>>>,
}
impl ArinaeMatcher {
pub fn new(case: CaseMatching, allow_typos: bool, use_last_match: bool) -> Self {
Self {
case,
allow_typos,
use_last_match,
..Default::default()
}
}
#[inline]
fn respect_case<C: Atom>(&self, pattern: &[C]) -> bool {
self.case == CaseMatching::Respect
|| (self.case == CaseMatching::Smart && !pattern.iter().all(|b| b.is_lowercase()))
}
fn dispatch_dp<C: Atom>(
&self,
cho: &[C],
pat: &[C],
bonuses: &[Score],
respect_case: bool,
compute_indices: bool,
) -> Option<(ScoreType, MatchIndices)> {
#[rustfmt::skip]
let res = match (self.allow_typos, compute_indices) {
(true, true) => full_dp::<true , true , _>(cho, pat, bonuses, respect_case, &self.full_buf, &self.indices_buf, self.use_last_match),
(true, false) => full_dp::<true , false, _>(cho, pat, bonuses, respect_case, &self.full_buf, &self.indices_buf, self.use_last_match),
(false, true) => full_dp::<false, true , _>(cho, pat, bonuses, respect_case, &self.full_buf, &self.indices_buf, self.use_last_match),
(false, false) => full_dp::<false, false, _>(cho, pat, bonuses, respect_case, &self.full_buf, &self.indices_buf, self.use_last_match),
};
res.map(|(s, idx)| (s as ScoreType, idx))
}
fn match_slices<C: Atom>(&self, cho: &[C], pat: &[C], compute_indices: bool) -> Option<(ScoreType, MatchIndices)> {
if pat.is_empty() {
return Some((0, MatchIndices::new()));
}
if cho.is_empty() {
return None;
}
let respect_case = self.respect_case(pat);
if self.allow_typos && !cheap_typo_prefilter(pat, cho, respect_case) {
return None;
}
let mut bonus_buf = self.bonus_buf.get_or(|| RefCell::new(Vec::new())).borrow_mut();
precompute_bonuses(cho, &mut bonus_buf);
self.dispatch_dp(cho, pat, &bonus_buf, respect_case, compute_indices)
}
fn run(&self, choice: &str, pattern: &str, compute_indices: bool) -> Option<(ScoreType, MatchIndices)> {
if pattern.is_empty() {
return Some((0, MatchIndices::new()));
}
if choice.is_empty() {
return None;
}
if choice.is_ascii() && pattern.is_ascii() {
let cho = choice.as_bytes();
let pat = pattern.as_bytes();
return self.match_slices(cho, pat, compute_indices);
}
let mut bufs = self
.char_buf
.get_or(|| RefCell::new((Vec::new(), Vec::new())))
.borrow_mut();
let (ref mut pat_buf, ref mut cho_buf) = *bufs;
pat_buf.clear();
pat_buf.extend(pattern.chars());
cho_buf.clear();
cho_buf.extend(choice.chars());
let respect_case = self.respect_case(pat_buf);
if self.allow_typos && !cheap_typo_prefilter(pat_buf, cho_buf, respect_case) {
return None;
}
let mut bonus_buf = self.bonus_buf.get_or(|| RefCell::new(Vec::new())).borrow_mut();
precompute_bonuses(cho_buf, &mut bonus_buf);
self.dispatch_dp(cho_buf, pat_buf, &bonus_buf, respect_case, compute_indices)
}
fn run_range(&self, choice: &str, pattern: &str) -> Option<(ScoreType, usize, usize)> {
if pattern.is_empty() {
return Some((0, 0, 0));
}
if choice.is_empty() {
return None;
}
let range = if choice.is_ascii() && pattern.is_ascii() {
let cho = choice.as_bytes();
let pat = pattern.as_bytes();
let respect_case = self.respect_case(pat);
if self.allow_typos && !cheap_typo_prefilter(pat, cho, respect_case) {
return None;
}
let mut bonus_buf = self.bonus_buf.get_or(|| RefCell::new(Vec::new())).borrow_mut();
precompute_bonuses(cho, &mut bonus_buf);
if self.allow_typos {
range_dp::<true, _>(cho, pat, &bonus_buf, respect_case, &self.full_buf, self.use_last_match)
} else {
range_dp::<false, _>(cho, pat, &bonus_buf, respect_case, &self.full_buf, self.use_last_match)
}
} else {
let mut bufs = self
.char_buf
.get_or(|| RefCell::new((Vec::new(), Vec::new())))
.borrow_mut();
let (ref mut pat_buf, ref mut cho_buf) = *bufs;
pat_buf.clear();
pat_buf.extend(pattern.chars());
cho_buf.clear();
cho_buf.extend(choice.chars());
let respect_case = self.respect_case(pat_buf);
if self.allow_typos && !cheap_typo_prefilter(pat_buf, cho_buf, respect_case) {
return None;
}
let mut bonus_buf = self.bonus_buf.get_or(|| RefCell::new(Vec::new())).borrow_mut();
precompute_bonuses(cho_buf, &mut bonus_buf);
if self.allow_typos {
range_dp::<true, _>(
cho_buf,
pat_buf,
&bonus_buf,
respect_case,
&self.full_buf,
self.use_last_match,
)
} else {
range_dp::<false, _>(
cho_buf,
pat_buf,
&bonus_buf,
respect_case,
&self.full_buf,
self.use_last_match,
)
}
};
range.map(|(s, b, e)| (s as ScoreType, b, e))
}
}
impl FuzzyMatcher for ArinaeMatcher {
fn fuzzy_match(&self, choice: &str, pattern: &str) -> Option<ScoreType> {
let result = self.run(choice, pattern, false);
result.map(|x| x.0)
}
fn fuzzy_match_range(&self, choice: &str, pattern: &str) -> Option<(ScoreType, usize, usize)> {
self.run_range(choice, pattern)
}
fn fuzzy_indices(&self, choice: &str, pattern: &str) -> Option<(ScoreType, MatchIndices)> {
self.run(choice, pattern, true)
}
}