1use std::fmt::{Display, Error, Formatter};
2use std::sync::Arc;
3
4use fuzzy_matcher::clangd::ClangdMatcher;
5use fuzzy_matcher::simple::SimpleMatcher;
6use fuzzy_matcher::skim::SkimMatcherV2;
7use fuzzy_matcher::FuzzyMatcher;
8
9use crate::item::RankBuilder;
10use crate::{CaseMatching, MatchEngine};
11use crate::{MatchRange, MatchResult, SkimItem};
12
13#[derive(Debug, Copy, Clone, Default)]
15pub enum FuzzyAlgorithm {
16 SkimV1,
17 #[default]
18 SkimV2,
19 Clangd,
20 Simple,
21}
22
23impl FuzzyAlgorithm {
24 pub fn of(algorithm: &str) -> Self {
25 match algorithm.to_ascii_lowercase().as_ref() {
26 "skim_v1" => FuzzyAlgorithm::SkimV1,
27 "skim_v2" | "skim" => FuzzyAlgorithm::SkimV2,
28 "clangd" => FuzzyAlgorithm::Clangd,
29 "simple" => FuzzyAlgorithm::Simple,
30 _ => FuzzyAlgorithm::SkimV2,
31 }
32 }
33}
34
35const BYTES_1M: usize = 1024 * 1024 * 1024;
36
37#[derive(Default)]
40pub struct FuzzyEngineBuilder {
41 query: String,
42 case: CaseMatching,
43 algorithm: FuzzyAlgorithm,
44 rank_builder: Arc<RankBuilder>,
45}
46
47impl FuzzyEngineBuilder {
48 pub fn query(mut self, query: &str) -> Self {
49 self.query = query.to_string();
50 self
51 }
52
53 pub fn case(mut self, case: CaseMatching) -> Self {
54 self.case = case;
55 self
56 }
57
58 pub fn algorithm(mut self, algorithm: FuzzyAlgorithm) -> Self {
59 self.algorithm = algorithm;
60 self
61 }
62
63 pub fn rank_builder(mut self, rank_builder: Arc<RankBuilder>) -> Self {
64 self.rank_builder = rank_builder;
65 self
66 }
67
68 #[allow(deprecated)]
69 pub fn build(self) -> FuzzyEngine {
70 let matcher: Box<dyn FuzzyMatcher> = match self.algorithm {
71 FuzzyAlgorithm::SkimV1 => Box::<fuzzy_matcher::skim::SkimMatcher>::default(),
72 FuzzyAlgorithm::SkimV2 => {
73 let matcher = SkimMatcherV2::default().element_limit(BYTES_1M);
74 let matcher = match self.case {
75 CaseMatching::Respect => matcher.respect_case(),
76 CaseMatching::Ignore => matcher.ignore_case(),
77 CaseMatching::Smart => matcher.smart_case(),
78 };
79 Box::new(matcher)
80 }
81 FuzzyAlgorithm::Clangd => {
82 let matcher = ClangdMatcher::default();
83 let matcher = match self.case {
84 CaseMatching::Respect => matcher.respect_case(),
85 CaseMatching::Ignore => matcher.ignore_case(),
86 CaseMatching::Smart => matcher.smart_case(),
87 };
88 Box::new(matcher)
89 }
90 FuzzyAlgorithm::Simple => {
91 let matcher = SimpleMatcher::default();
92 let matcher = match self.case {
93 CaseMatching::Respect => matcher.respect_case(),
94 CaseMatching::Ignore => matcher.ignore_case(),
95 CaseMatching::Smart => matcher.smart_case(),
96 };
97 Box::new(matcher)
98 }
99 };
100
101 FuzzyEngine {
102 matcher,
103 query: self.query,
104 rank_builder: self.rank_builder,
105 }
106 }
107}
108
109pub struct FuzzyEngine {
110 query: String,
111 matcher: Box<dyn FuzzyMatcher>,
112 rank_builder: Arc<RankBuilder>,
113}
114
115impl FuzzyEngine {
116 pub fn builder() -> FuzzyEngineBuilder {
117 FuzzyEngineBuilder::default()
118 }
119
120 fn fuzzy_match(&self, choice: &str, pattern: &str) -> Option<(i64, Vec<usize>)> {
121 self.matcher.fuzzy_indices(choice, pattern)
122 }
123}
124
125impl MatchEngine for FuzzyEngine {
126 fn match_item(&self, item: &dyn SkimItem, item_idx: usize) -> Option<MatchResult> {
127 let item_text = item.text();
129 let item_len = item_text.len();
130 let query_text = &self.query;
131 let default_range = [(0, item_len)];
132
133 let matched_result: Option<(i64, Vec<usize>)> = item
134 .get_matching_ranges()
135 .unwrap_or(&default_range)
136 .iter()
137 .map(|(start, end)| {
138 let start = std::cmp::min(*start, item_len);
139 let end = std::cmp::min(*end, item_len);
140 let choice_range = &item_text[start..end];
141 (start, choice_range)
142 })
143 .find_map(|(start, choice_range)| {
144 self.fuzzy_match(choice_range, query_text).map(|(score, indices)| {
145 if start != 0 {
146 let start_char = &item_text[..start].len();
147 return (score, indices.iter().map(|x| x + start_char).collect());
148 }
149
150 (score, indices)
151 })
152 });
153
154 matched_result.map(|(score, matched_range)| {
155 let begin = *matched_range.first().unwrap_or(&0);
156 let end = *matched_range.last().unwrap_or(&0);
157
158 MatchResult {
159 rank: self
160 .rank_builder
161 .build_rank(score as i32, begin, end, item_len, item_idx),
162 matched_range: MatchRange::Chars(matched_range.into()),
163 }
164 })
165 }
166}
167
168impl Display for FuzzyEngine {
169 fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
170 write!(f, "(Fuzzy: {})", self.query)
171 }
172}