/rust/registry/src/index.crates.io-1949cf8c6b5b557f/fuzzy-matcher-0.3.7/src/util.rs
Line | Count | Source |
1 | | use crate::{FuzzyMatcher, IndexType, ScoreType}; |
2 | | |
3 | 0 | pub fn cheap_matches( |
4 | 0 | choice: &[char], |
5 | 0 | pattern: &[char], |
6 | 0 | case_sensitive: bool, |
7 | 0 | ) -> Option<Vec<usize>> { |
8 | 0 | let mut first_match_indices = vec![]; |
9 | 0 | let mut pattern_iter = pattern.iter().peekable(); |
10 | 0 | for (idx, &c) in choice.iter().enumerate() { |
11 | 0 | match pattern_iter.peek() { |
12 | 0 | Some(&&p) => { |
13 | 0 | if char_equal(c, p, case_sensitive) { |
14 | 0 | first_match_indices.push(idx); |
15 | 0 | let _ = pattern_iter.next(); |
16 | 0 | } |
17 | | } |
18 | 0 | None => break, |
19 | | } |
20 | | } |
21 | | |
22 | 0 | if pattern_iter.peek().is_none() { |
23 | 0 | Some(first_match_indices) |
24 | | } else { |
25 | 0 | None |
26 | | } |
27 | 0 | } |
28 | | |
29 | | /// Given 2 character, check if they are equal (considering ascii case) |
30 | | /// e.g. ('a', 'A', true) => false |
31 | | /// e.g. ('a', 'A', false) => true |
32 | | #[inline] |
33 | 0 | pub fn char_equal(a: char, b: char, case_sensitive: bool) -> bool { |
34 | 0 | if case_sensitive { |
35 | 0 | a == b |
36 | | } else { |
37 | 0 | a.eq_ignore_ascii_case(&b) |
38 | | } |
39 | 0 | } |
40 | | |
41 | | #[derive(Debug, PartialEq)] |
42 | | pub enum CharType { |
43 | | NonWord, |
44 | | Lower, |
45 | | Upper, |
46 | | Number, |
47 | | } |
48 | | |
49 | | #[inline] |
50 | 0 | pub fn char_type_of(ch: char) -> CharType { |
51 | 0 | if ch.is_lowercase() { |
52 | 0 | CharType::Lower |
53 | 0 | } else if ch.is_uppercase() { |
54 | 0 | CharType::Upper |
55 | 0 | } else if ch.is_numeric() { |
56 | 0 | CharType::Number |
57 | | } else { |
58 | 0 | CharType::NonWord |
59 | | } |
60 | 0 | } |
61 | | |
62 | | #[derive(Debug, PartialEq)] |
63 | | pub enum CharRole { |
64 | | Tail, |
65 | | Head, |
66 | | } |
67 | | |
68 | | // checkout https://github.com/llvm-mirror/clang-tools-extra/blob/master/clangd/FuzzyMatch.cpp |
69 | | // The Role can be determined from the Type of a character and its neighbors: |
70 | | // |
71 | | // Example | Chars | Type | Role |
72 | | // ---------+--------------+----- |
73 | | // F(o)oBar | Foo | Ull | Tail |
74 | | // Foo(B)ar | oBa | lUl | Head |
75 | | // (f)oo | ^fo | Ell | Head |
76 | | // H(T)TP | HTT | UUU | Tail |
77 | | // |
78 | | // Curr= Empty Lower Upper Separ |
79 | | // Prev=Empty 0x00, 0xaa, 0xaa, 0xff, // At start, Lower|Upper->Head |
80 | | // Prev=Lower 0x00, 0x55, 0xaa, 0xff, // In word, Upper->Head;Lower->Tail |
81 | | // Prev=Upper 0x00, 0x55, 0x59, 0xff, // Ditto, but U(U)U->Tail |
82 | | // Prev=Separ 0x00, 0xaa, 0xaa, 0xff, // After separator, like at start |
83 | 0 | pub fn char_role(prev: char, cur: char) -> CharRole { |
84 | | use self::CharRole::*; |
85 | | use self::CharType::*; |
86 | 0 | match (char_type_of(prev), char_type_of(cur)) { |
87 | 0 | (Lower, Upper) | (NonWord, Lower) | (NonWord, Upper) => Head, |
88 | 0 | _ => Tail, |
89 | | } |
90 | 0 | } |
91 | | |
92 | | #[allow(dead_code)] |
93 | 0 | pub fn assert_order(matcher: &dyn FuzzyMatcher, pattern: &str, choices: &[&'static str]) { |
94 | 0 | let result = filter_and_sort(matcher, pattern, choices); |
95 | | |
96 | 0 | if result != choices { |
97 | | // debug print |
98 | 0 | println!("pattern: {}", pattern); |
99 | 0 | for &choice in choices.iter() { |
100 | 0 | if let Some((score, indices)) = matcher.fuzzy_indices(choice, pattern) { |
101 | 0 | println!("{}: {:?}", score, wrap_matches(choice, &indices)); |
102 | 0 | } else { |
103 | 0 | println!("NO MATCH for {}", choice); |
104 | 0 | } |
105 | | } |
106 | 0 | } |
107 | | |
108 | 0 | assert_eq!(result, choices); |
109 | 0 | } |
110 | | |
111 | | #[allow(dead_code)] |
112 | 0 | pub fn filter_and_sort( |
113 | 0 | matcher: &dyn FuzzyMatcher, |
114 | 0 | pattern: &str, |
115 | 0 | lines: &[&'static str], |
116 | 0 | ) -> Vec<&'static str> { |
117 | 0 | let mut lines_with_score: Vec<(ScoreType, &'static str)> = lines |
118 | 0 | .iter() |
119 | 0 | .filter_map(|&s| matcher.fuzzy_match(s, pattern).map(|score| (score, s))) |
120 | 0 | .collect(); |
121 | 0 | lines_with_score.sort_by_key(|(score, _)| -score); |
122 | 0 | lines_with_score |
123 | 0 | .into_iter() |
124 | 0 | .map(|(_, string)| string) |
125 | 0 | .collect() |
126 | 0 | } |
127 | | |
128 | | #[allow(dead_code)] |
129 | 0 | pub fn wrap_matches(line: &str, indices: &[IndexType]) -> String { |
130 | 0 | let mut ret = String::new(); |
131 | 0 | let mut peekable = indices.iter().peekable(); |
132 | 0 | for (idx, ch) in line.chars().enumerate() { |
133 | 0 | let next_id = **peekable.peek().unwrap_or(&&(line.len() as IndexType)); |
134 | 0 | if next_id == (idx as IndexType) { |
135 | 0 | ret.push_str(format!("[{}]", ch).as_str()); |
136 | 0 | peekable.next(); |
137 | 0 | } else { |
138 | 0 | ret.push(ch); |
139 | 0 | } |
140 | | } |
141 | | |
142 | 0 | ret |
143 | 0 | } |