1 use { 2 super::*, 3 crate::{ 4 content_search::ContentMatch, 5 errors::PatternError, 6 }, 7 bet::BeTree, 8 std::{ 9 path::Path, 10 }, 11 }; 12 13 /// a pattern for filtering and sorting files. 14 #[derive(Debug, Clone)] 15 pub enum Pattern { 16 None, 17 NameExact(ExactPattern), 18 NameFuzzy(FuzzyPattern), 19 NameRegex(RegexPattern), 20 NameTokens(TokPattern), 21 PathExact(ExactPattern), 22 PathFuzzy(FuzzyPattern), 23 PathRegex(RegexPattern), 24 PathTokens(TokPattern), 25 ContentExact(ContentExactPattern), 26 ContentRegex(ContentRegexPattern), 27 Composite(CompositePattern), 28 } 29 30 impl Pattern { 31 new( raw_expr: &BeTree<PatternOperator, PatternParts>, search_modes: &SearchModeMap, ) -> Result<Self, PatternError>32 pub fn new( 33 raw_expr: &BeTree<PatternOperator, PatternParts>, 34 search_modes: &SearchModeMap, 35 ) -> Result<Self, PatternError> { 36 let expr: BeTree<PatternOperator, Pattern> = raw_expr 37 .try_map_atoms::<_, PatternError, _>(|pattern_parts| { 38 let core = pattern_parts.core(); 39 Ok( 40 if core.is_empty() { 41 Pattern::None 42 } else { 43 let parts_mode = pattern_parts.mode(); 44 let mode = search_modes.search_mode(parts_mode)?; 45 let flags = pattern_parts.flags(); 46 match mode { 47 SearchMode::NameExact => Self::NameExact( 48 ExactPattern::from(core) 49 ), 50 SearchMode::NameFuzzy => Self::NameFuzzy( 51 FuzzyPattern::from(core) 52 ), 53 SearchMode::NameRegex => Self::NameRegex( 54 RegexPattern::from(core, flags.unwrap_or(""))? 55 ), 56 SearchMode::NameTokens => Self::NameTokens( 57 TokPattern::new(core) 58 ), 59 SearchMode::PathExact => Self::PathExact( 60 ExactPattern::from(core) 61 ), 62 SearchMode::PathFuzzy => Self::PathFuzzy( 63 FuzzyPattern::from(core) 64 ), 65 SearchMode::PathRegex => Self::PathRegex( 66 RegexPattern::from(core, flags.unwrap_or(""))? 67 ), 68 SearchMode::PathTokens => Self::PathTokens( 69 TokPattern::new(core) 70 ), 71 SearchMode::ContentExact => Self::ContentExact( 72 ContentExactPattern::from(core) 73 ), 74 SearchMode::ContentRegex => Self::ContentRegex( 75 ContentRegexPattern::from(core, flags.unwrap_or(""))? 76 ), 77 } 78 } 79 ) 80 })?; 81 Ok(if expr.is_empty() { 82 Pattern::None 83 } else if expr.is_atomic() { 84 expr.atoms().pop().unwrap() 85 } else { 86 Self::Composite(CompositePattern::new(expr)) 87 }) 88 } 89 object(&self) -> PatternObject90 pub fn object(&self) -> PatternObject { 91 let mut object = PatternObject::default(); 92 match self { 93 Self::None => {} 94 Self::NameExact(_) | Self::NameFuzzy(_) | Self::NameRegex(_) | Self::NameTokens(_) => { 95 object.name = true; 96 } 97 Self::PathExact(_) | Self::PathFuzzy(_) | Self::PathRegex(_) | Self::PathTokens(_) => { 98 object.subpath = true; 99 } 100 Self::ContentExact(_) | Self::ContentRegex(_) => { 101 object.content = true; 102 } 103 Self::Composite(cp) => { 104 for atom in cp.expr.iter_atoms() { 105 object |= atom.object(); 106 } 107 } 108 } 109 object 110 } 111 search_string( &self, candidate: &str, ) -> Option<NameMatch>112 pub fn search_string( 113 &self, 114 candidate: &str, 115 ) -> Option<NameMatch> { 116 match self { 117 Self::NameExact(ep) | Self::PathExact(ep) => ep.find(candidate), 118 Self::NameFuzzy(fp) | Self::PathFuzzy(fp) => fp.find(candidate), 119 Self::NameRegex(rp) | Self::PathRegex(rp) => rp.find(candidate), 120 Self::NameTokens(tp) | Self::PathTokens(tp) => tp.find(candidate), 121 Self::Composite(cp) => cp.search_string(candidate), 122 _ => None, 123 } 124 } 125 126 /// find the content to show next to the name of the file 127 /// when the search involved a content filtering search_content( &self, candidate: &Path, desired_len: usize, ) -> Option<ContentMatch>128 pub fn search_content( 129 &self, 130 candidate: &Path, 131 desired_len: usize, // available space for content match display 132 ) -> Option<ContentMatch> { 133 match self { 134 Self::ContentExact(cp) => cp.get_content_match(candidate, desired_len), 135 Self::ContentRegex(cp) => cp.get_content_match(candidate, desired_len), 136 Self::Composite(cp) => cp.search_content(candidate, desired_len), 137 _ => None, 138 } 139 } 140 141 /// get the line of the first match, if any get_match_line_count( &self, path: &Path, ) -> Option<usize>142 pub fn get_match_line_count( 143 &self, 144 path: &Path, 145 ) -> Option<usize> { 146 match self { 147 Self::ContentExact(cp) => cp.get_match_line_count(path), 148 Self::ContentRegex(cp) => cp.get_match_line_count(path), 149 Self::Composite(cp) => cp.get_match_line_count(path), 150 _ => None, 151 } 152 } 153 score_of(&self, candidate: Candidate) -> Option<i32>154 pub fn score_of(&self, candidate: Candidate) -> Option<i32> { 155 match self { 156 Self::NameExact(ep) => ep.score_of(candidate.name), 157 Self::NameFuzzy(fp) => fp.score_of(candidate.name), 158 Self::NameRegex(rp) => rp.find(candidate.name).map(|m| m.score), 159 Self::NameTokens(tp) => tp.score_of(candidate.name), 160 Self::PathExact(ep) => ep.score_of(candidate.subpath), 161 Self::PathFuzzy(fp) => fp.score_of(candidate.subpath), 162 Self::PathRegex(rp) => rp.find(candidate.subpath).map(|m| m.score), 163 Self::PathTokens(tp) => tp.score_of(candidate.subpath), 164 Self::ContentExact(cp) => cp.score_of(candidate), 165 Self::ContentRegex(cp) => cp.score_of(candidate), 166 Self::Composite(cp) => cp.score_of(candidate), 167 Self::None => Some(1), 168 } 169 } 170 score_of_string(&self, candidate: &str) -> Option<i32>171 pub fn score_of_string(&self, candidate: &str) -> Option<i32> { 172 match self { 173 Self::NameExact(ep) => ep.score_of(candidate), 174 Self::NameFuzzy(fp) => fp.score_of(candidate), 175 Self::NameRegex(rp) => rp.find(candidate).map(|m| m.score), 176 Self::NameTokens(tp) => tp.score_of(candidate), 177 Self::PathExact(ep) => ep.score_of(candidate), 178 Self::PathFuzzy(fp) => fp.score_of(candidate), 179 Self::PathRegex(rp) => rp.find(candidate).map(|m| m.score), 180 Self::PathTokens(tp) => tp.score_of(candidate), 181 Self::ContentExact(_) => None, // this isn't suitable 182 Self::ContentRegex(_) => None, // this isn't suitable 183 Self::Composite(cp) => cp.score_of_string(candidate), 184 Self::None => Some(1), 185 } 186 } 187 is_some(&self) -> bool188 pub fn is_some(&self) -> bool { 189 !self.is_empty() 190 } 191 192 /// an empty pattern is one which doesn't discriminate 193 /// (it accepts everything) is_empty(&self) -> bool194 pub fn is_empty(&self) -> bool { 195 match self { 196 Self::NameExact(ep) | Self::PathExact(ep) => ep.is_empty(), 197 Self::ContentExact(ep) => ep.is_empty(), 198 Self::NameFuzzy(fp) | Self::PathFuzzy(fp) => fp.is_empty(), 199 Self::NameRegex(rp) | Self::PathRegex(rp) => rp.is_empty(), 200 Self::ContentRegex(rp) => rp.is_empty(), 201 Self::NameTokens(tp) | Self::PathTokens(tp) => tp.is_empty(), 202 Self::Composite(cp) => cp.is_empty(), 203 Self::None => true, 204 } 205 } 206 207 /// whether the scores are more than just 0 or 1. 208 /// When it's the case, the tree builder will look for more matching results 209 /// in order to select the best ones. has_real_scores(&self) -> bool210 pub fn has_real_scores(&self) -> bool { 211 match self { 212 Self::NameExact(_) | Self::NameFuzzy(_) => true, 213 Self::PathExact(_) | Self::PathFuzzy(_) => true, 214 Self::Composite(cp) => cp.has_real_scores(), 215 _ => false, 216 } 217 } 218 219 } 220 221