1use core::ops::Range;
2
3use super::{query::*, *};
4use crate::utils::CharEq;
5use crate::*;
6
7pub(super) trait Fzf {
9 fn alloc_chars<'a>(&mut self, candidate: &str) -> &'a [char];
11
12 fn char_eq(&self, pattern: Pattern) -> CharEq;
14
15 fn scheme(&self) -> &Scheme;
17
18 fn fuzzy<const RANGES: bool>(
20 &mut self,
21 pattern: Pattern,
22 candidate: Candidate,
23 ranges: &mut MatchedRanges,
24 ) -> Option<Score>;
25
26 fn score<const RANGES: bool>(
28 &mut self,
29 pattern: Pattern,
30 candidate: Candidate,
31 ranges: &mut MatchedRanges,
32 ) -> Option<Score> {
33 let score = match pattern.match_type {
34 MatchType::Fuzzy => {
35 if pattern.is_inverse {
36 self.fuzzy::<false>(pattern, candidate, ranges)
37 } else {
38 self.fuzzy::<RANGES>(pattern, candidate, ranges)
39 }
40 },
41
42 MatchType::Exact => {
43 let char_eq = self.char_eq(pattern);
44
45 if pattern.is_inverse {
46 exact_match::<false>(
47 pattern,
48 candidate,
49 char_eq,
50 self.scheme(),
51 ranges,
52 )
53 } else {
54 exact_match::<RANGES>(
55 pattern,
56 candidate,
57 char_eq,
58 self.scheme(),
59 ranges,
60 )
61 }
62 },
63
64 MatchType::PrefixExact => {
65 let char_eq = self.char_eq(pattern);
66
67 if pattern.is_inverse {
68 prefix_match::<false>(
69 pattern,
70 candidate,
71 char_eq,
72 self.scheme(),
73 ranges,
74 )
75 } else {
76 prefix_match::<RANGES>(
77 pattern,
78 candidate,
79 char_eq,
80 self.scheme(),
81 ranges,
82 )
83 }
84 },
85
86 MatchType::SuffixExact => {
87 let char_eq = self.char_eq(pattern);
88
89 if pattern.is_inverse {
90 suffix_match::<false>(
91 pattern,
92 candidate,
93 char_eq,
94 self.scheme(),
95 ranges,
96 )
97 } else {
98 suffix_match::<RANGES>(
99 pattern,
100 candidate,
101 char_eq,
102 self.scheme(),
103 ranges,
104 )
105 }
106 },
107
108 MatchType::EqualExact => {
109 let char_eq = self.char_eq(pattern);
110
111 if pattern.is_inverse {
112 equal_match::<false>(
113 pattern,
114 candidate,
115 char_eq,
116 self.scheme(),
117 ranges,
118 )
119 } else {
120 equal_match::<RANGES>(
121 pattern,
122 candidate,
123 char_eq,
124 self.scheme(),
125 ranges,
126 )
127 }
128 },
129 };
130
131 match (score.is_some(), pattern.is_inverse) {
132 (true, false) => score,
133 (false, true) => Some(0),
134 _ => None,
135 }
136 }
137
138 #[inline(always)]
140 fn distance<const RANGES: bool>(
141 &mut self,
142 query: FzfQuery,
143 candidate: &str,
144 ranges: &mut Vec<Range<usize>>,
145 ) -> Option<FzfDistance> {
146 if query.is_empty() {
147 return Some(FzfDistance::from_score(0));
148 }
149
150 let candidate = if candidate.is_ascii() {
151 Candidate::Ascii(candidate.as_bytes())
152 } else {
153 Candidate::Unicode(self.alloc_chars(candidate))
154 };
155
156 let ranges = &mut ranges.into();
157
158 match query.search_mode {
159 SearchMode::NotExtended(pattern) => self
160 .fuzzy::<RANGES>(pattern, candidate, ranges)
161 .map(FzfDistance::from_score),
162
163 SearchMode::Extended(conditions) => {
164 let mut total_score: Score = 0;
165 for condition in conditions {
166 total_score += condition.iter().find_map(|pattern| {
167 self.score::<RANGES>(pattern, candidate, ranges)
168 })?;
169 }
170 Some(FzfDistance::from_score(total_score))
171 },
172 }
173 }
174}
175
176#[inline]
178fn exact_match<const RANGES: bool>(
179 pattern: Pattern,
180 candidate: Candidate,
181 char_eq: CharEq,
182 scheme: &Scheme,
183 ranges: &mut MatchedRanges,
184) -> Option<Score> {
185 if pattern.is_empty() {
186 return Some(0);
187 }
188
189 let mut best_bonus: i64 = -1;
191
192 let mut best_bonus_char_start = 0;
194
195 let mut best_bonus_char_end = 0;
197
198 let mut matched = false;
200
201 let mut prev_class = scheme.initial_char_class;
202
203 let mut start_offset = 0;
204
205 'outer: loop {
206 let current_start_offset = start_offset;
207 let mut bonus_start = 0;
208 let mut current_bonus: Score = 0;
209 let mut pattern_char_idx = 0;
210
211 let mut chars = candidate.chars_from(start_offset).enumerate();
212
213 for (char_offset, candidate_ch) in chars.by_ref() {
214 let pattern_ch = pattern.char(pattern_char_idx);
215
216 let char_class = char_class(candidate_ch, scheme);
217
218 if (char_eq)(pattern_ch, candidate_ch) {
219 if pattern_char_idx == 0 {
220 bonus_start = current_start_offset + char_offset;
221 start_offset += char_offset + 1;
222 current_bonus =
223 compute_bonus(prev_class, char_class, scheme);
224 }
225
226 pattern_char_idx += 1;
227
228 if pattern_char_idx == pattern.char_len() {
229 matched = true;
230
231 if current_bonus as i64 > best_bonus {
232 best_bonus = current_bonus as _;
233
234 best_bonus_char_start = bonus_start;
235
236 best_bonus_char_end =
237 current_start_offset + char_offset + 1;
238 }
239
240 if current_bonus >= bonus::BOUNDARY {
241 break 'outer;
242 }
243
244 break;
245 }
246 } else if pattern_char_idx > 0 {
247 break;
248 }
249
250 prev_class = char_class;
251 }
252
253 if chars.next().is_none() {
254 break;
255 }
256 }
257
258 if !matched {
259 return None;
260 }
261
262 let matched_range = best_bonus_char_start..best_bonus_char_end;
263
264 let score = compute_score::<false>(
265 pattern,
266 candidate,
267 matched_range.clone(),
268 char_eq,
269 scheme,
270 ranges,
271 );
272
273 if RANGES {
274 ranges.insert(candidate.to_byte_range(matched_range));
275 }
276
277 Some(score)
278}
279
280#[inline]
282fn prefix_match<const RANGES: bool>(
283 pattern: Pattern,
284 candidate: Candidate,
285 char_eq: CharEq,
286 scheme: &Scheme,
287 ranges: &mut MatchedRanges,
288) -> Option<Score> {
289 if pattern.is_empty() {
290 return Some(0);
291 }
292
293 let mut pattern_chars = pattern.chars();
294
295 let ignored_leading_spaces =
296 ignored_candidate_leading_spaces(pattern, candidate)?;
297
298 for (candidate_ch, pattern_ch) in candidate
299 .chars_from(ignored_leading_spaces)
300 .zip(pattern_chars.by_ref())
301 {
302 if !char_eq(pattern_ch, candidate_ch) {
303 return None;
304 }
305 }
306
307 if pattern_chars.next().is_some() {
308 return None;
309 }
310
311 let matched_range = {
312 let start = ignored_leading_spaces;
313 let end = start + pattern.char_len();
314 start..end
315 };
316
317 let score = compute_score::<false>(
318 pattern,
319 candidate,
320 matched_range.clone(),
321 char_eq,
322 scheme,
323 ranges,
324 );
325
326 if RANGES {
327 ranges.insert(candidate.to_byte_range(matched_range));
328 }
329
330 Some(score)
331}
332
333#[inline]
335fn suffix_match<const RANGES: bool>(
336 pattern: Pattern,
337 candidate: Candidate,
338 char_eq: CharEq,
339 scheme: &Scheme,
340 ranges: &mut MatchedRanges,
341) -> Option<Score> {
342 if pattern.is_empty() {
343 return Some(0);
344 }
345
346 let mut pattern_chars = pattern.chars().rev();
347
348 let chars_up_to_ignored_spaces = candidate.char_len()
349 - ignored_candidate_trailing_spaces(pattern, candidate)?;
350
351 for (candidate_ch, pattern_ch) in candidate
352 .slice(0..chars_up_to_ignored_spaces)
353 .chars()
354 .rev()
355 .zip(pattern_chars.by_ref())
356 {
357 if !char_eq(pattern_ch, candidate_ch) {
358 return None;
359 }
360 }
361
362 if pattern_chars.next().is_some() {
363 return None;
364 }
365
366 let matched_range = {
367 let end = chars_up_to_ignored_spaces;
368 let start = end - pattern.char_len();
369 start..end
370 };
371
372 let score = compute_score::<false>(
373 pattern,
374 candidate,
375 matched_range.clone(),
376 char_eq,
377 scheme,
378 ranges,
379 );
380
381 if RANGES {
382 ranges.insert(candidate.to_byte_range(matched_range));
383 }
384
385 Some(score)
386}
387
388#[inline]
390fn equal_match<const RANGES: bool>(
391 pattern: Pattern,
392 candidate: Candidate,
393 char_eq: CharEq,
394 scheme: &Scheme,
395 ranges: &mut MatchedRanges,
396) -> Option<Score> {
397 if pattern.is_empty() {
398 return Some(0);
399 }
400
401 let ignored_leading_spaces =
402 ignored_candidate_leading_spaces(pattern, candidate)?;
403
404 if ignored_leading_spaces == candidate.char_len() {
406 return None;
407 }
408
409 let ignored_trailing_spaces =
410 ignored_candidate_trailing_spaces(pattern, candidate)?;
411
412 let matched_char_range =
413 ignored_leading_spaces..candidate.char_len() - ignored_trailing_spaces;
414
415 if matched_char_range.len() < pattern.char_len() {
416 return None;
417 }
418
419 let mut pattern_chars = pattern.chars();
420
421 let mut candidate_chars =
422 candidate.slice(matched_char_range.clone()).chars();
423
424 for (pattern_ch, candidate_ch) in
425 pattern_chars.by_ref().zip(candidate_chars.by_ref())
426 {
427 if !char_eq(pattern_ch, candidate_ch) {
428 return None;
429 }
430 }
431
432 if pattern_chars.next().is_some() || candidate_chars.next().is_some() {
433 return None;
434 }
435
436 let score = compute_score::<false>(
437 pattern,
438 candidate,
439 matched_char_range.clone(),
440 char_eq,
441 scheme,
442 ranges,
443 );
444
445 if RANGES {
446 ranges.insert(candidate.to_byte_range(matched_char_range));
447 }
448
449 Some(score)
450}
451
452#[inline(always)]
454fn ignored_candidate_leading_spaces(
455 pattern: Pattern,
456 candidate: Candidate,
457) -> Option<usize> {
458 let candidate_leading_spaces = candidate.leading_spaces();
459
460 if pattern.leading_spaces() > candidate_leading_spaces {
461 None
462 } else {
463 Some(candidate_leading_spaces - pattern.leading_spaces())
464 }
465}
466
467#[inline(always)]
469fn ignored_candidate_trailing_spaces(
470 pattern: Pattern,
471 candidate: Candidate,
472) -> Option<usize> {
473 let candidate_trailing_spaces = candidate.trailing_spaces();
474
475 if pattern.trailing_spaces() > candidate_trailing_spaces {
476 None
477 } else {
478 Some(candidate_trailing_spaces - pattern.trailing_spaces())
479 }
480}
481
482#[inline]
484pub(super) fn compute_score<const RANGES: bool>(
485 pattern: Pattern,
486 candidate: Candidate,
487 candidate_char_range: Range<usize>,
488 char_eq: CharEq,
489 scheme: &Scheme,
490 ranges: &mut MatchedRanges,
491) -> Score {
492 let mut is_in_gap = false;
494
495 let mut is_first_pattern_char = true;
497
498 let mut first_bonus: Score = 0;
500
501 let mut consecutive = 0u32;
503
504 let byte_range_start = if RANGES {
505 candidate.to_byte_offset(candidate_char_range.start)
506 } else {
507 0
508 };
509
510 let mut byte_offset = 0;
511
512 let mut prev_class = if candidate_char_range.start == 0 {
513 scheme.initial_char_class
514 } else {
515 char_class(candidate.char(candidate_char_range.start - 1), scheme)
516 };
517
518 let mut pattern_chars = pattern.chars();
519
520 let mut pattern_char = pattern_chars.next().expect("pattern is not empty");
521
522 let mut score: Score = 0;
523
524 for candidate_ch in candidate.slice(candidate_char_range).chars() {
525 let ch_class = char_class(candidate_ch, scheme);
526
527 if char_eq(pattern_char, candidate_ch) {
528 score += bonus::MATCH;
529
530 let mut bonus = compute_bonus(prev_class, ch_class, scheme);
531
532 if consecutive == 0 {
533 first_bonus = bonus;
534 } else {
535 if bonus >= bonus::BOUNDARY && bonus > first_bonus {
536 first_bonus = bonus
537 }
538 bonus = bonus.max(first_bonus).max(bonus::CONSECUTIVE);
539 }
540
541 score += if is_first_pattern_char {
542 bonus * bonus::FIRST_QUERY_CHAR_MULTIPLIER
543 } else {
544 bonus
545 };
546
547 if RANGES {
548 let start = byte_range_start + byte_offset;
549 let end = start + candidate_ch.len_utf8();
550 ranges.insert(start..end);
551 }
552
553 is_in_gap = false;
554
555 is_first_pattern_char = false;
556
557 consecutive += 1;
558
559 if let Some(next_char) = pattern_chars.next() {
560 pattern_char = next_char;
561 } else {
562 break;
563 };
564 } else {
565 score -= if is_in_gap {
566 penalty::GAP_EXTENSION
567 } else {
568 penalty::GAP_START
569 };
570
571 is_in_gap = true;
572
573 consecutive = 0;
574
575 first_bonus = 0;
576 }
577
578 prev_class = ch_class;
579
580 if RANGES {
581 byte_offset += candidate_ch.len_utf8();
582 }
583 }
584
585 score
586}
587
588#[cfg(test)]
589mod tests {
590 #![allow(clippy::single_range_in_vec_init)]
591
592 use super::*;
593
594 fn candidate(s: &str) -> Candidate {
595 assert!(s.is_ascii());
596 Candidate::Ascii(s.as_bytes())
597 }
598
599 #[test]
600 fn equal_match_1() {
601 let pattern =
602 Pattern::parse("^AbC$".chars().collect::<Vec<_>>().leak())
603 .unwrap();
604
605 let mut ranges_buf = Vec::new();
606
607 assert!(exact_match::<true>(
608 pattern,
609 candidate("ABC"),
610 utils::char_eq(true, false),
611 &Scheme::default(),
612 &mut ((&mut ranges_buf).into())
613 )
614 .is_none());
615
616 {
617 ranges_buf.clear();
618
619 assert!(exact_match::<true>(
620 pattern,
621 candidate("AbC"),
622 utils::char_eq(true, false),
623 &Scheme::default(),
624 &mut ((&mut ranges_buf).into())
625 )
626 .is_some());
627
628 assert_eq!(ranges_buf.as_slice(), [0..3]);
629 }
630
631 {
632 ranges_buf.clear();
633
634 assert!(exact_match::<true>(
635 pattern,
636 candidate("AbC "),
637 utils::char_eq(true, false),
638 &Scheme::default(),
639 &mut ((&mut ranges_buf).into())
640 )
641 .is_some());
642
643 assert_eq!(ranges_buf.as_slice(), [0..3]);
644 }
645
646 {
647 ranges_buf.clear();
648
649 assert!(exact_match::<true>(
650 pattern,
651 candidate(" AbC "),
652 utils::char_eq(true, false),
653 &Scheme::default(),
654 &mut ((&mut ranges_buf).into())
655 )
656 .is_some());
657
658 assert_eq!(ranges_buf.as_slice(), [1..4]);
659 }
660
661 {
662 ranges_buf.clear();
663
664 assert!(exact_match::<true>(
665 pattern,
666 candidate(" AbC"),
667 utils::char_eq(true, false),
668 &Scheme::default(),
669 &mut ((&mut ranges_buf).into())
670 )
671 .is_some());
672
673 assert_eq!(ranges_buf.as_slice(), [2..5]);
674 }
675 }
676
677 #[test]
678 fn exact_match_1() {
679 let pattern =
680 Pattern::parse("abc".chars().collect::<Vec<_>>().leak()).unwrap();
681
682 let mut ranges_buf = Vec::new();
683
684 assert!(exact_match::<true>(
685 pattern,
686 candidate("aabbcc abc"),
687 utils::char_eq(true, false),
688 &Scheme::default(),
689 &mut ((&mut ranges_buf).into())
690 )
691 .is_some());
692
693 assert_eq!(ranges_buf, [7..10]);
694 }
695}