1use std::{cell::RefCell, fmt, rc::Rc};
2
3use nucleo::{
4 pattern::{AtomKind, CaseMatching, Normalization, Pattern},
5 Utf32Str,
6};
7
8#[derive(Clone)]
9pub struct Matcher {
10 matcher: Rc<RefCell<nucleo::Matcher>>,
11 needle: Option<Pattern>,
12 buf: Rc<RefCell<Vec<char>>>,
14}
15
16impl Default for Matcher {
17 fn default() -> Self {
18 Self::new()
19 }
20}
21
22impl Matcher {
23 pub fn new() -> Self {
24 Self {
25 matcher: Rc::new(RefCell::new(nucleo::Matcher::default())),
26 needle: None,
27 buf: Rc::new(RefCell::new(Vec::new())),
28 }
29 }
30
31 pub fn update_needle(&mut self, needle: &str) {
32 if needle.is_empty() {
33 self.needle = None;
34 } else {
35 let needle = Pattern::new(
36 needle,
37 CaseMatching::Smart,
38 Normalization::Smart,
39 AtomKind::Substring,
40 );
41 self.needle = Some(needle);
42 }
43 }
44
45 pub fn r#match(&self, haystack: &str) -> bool {
46 match self.needle.as_ref() {
47 Some(needle) => {
48 let mut buf = self.buf.borrow_mut();
49 let haystack = Utf32Str::new(haystack, &mut buf);
50 needle
51 .score(haystack, &mut self.matcher.borrow_mut())
52 .unwrap_or(0)
53 > 0
54 }
55 None => true,
56 }
57 }
58}
59
60impl fmt::Debug for Matcher {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 f.debug_struct("Matcher")
63 .field("needle", &self.needle)
64 .finish_non_exhaustive()
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 #[test]
73 fn should_match() {
74 let mut m = Matcher::new();
75
76 let cases = vec![
77 ("rustsec", "rustsec"),
78 ("rustsec", "RUSTSEC"),
79 ("rustsec", "RustSec"),
80 ("this week in rust", "This Week in Rust"),
81 ];
82
83 for case in cases {
84 m.update_needle(case.0);
85 assert!(m.r#match(case.1));
86 }
87 }
88}