@@ -47,32 +47,73 @@ impl<'a> Stream<'a> {
47
47
None
48
48
}
49
49
50
+ fn match_acronym ( & self , path : & str , keywords_last : & str , keywords : & [ String ] ) -> bool {
51
+ let basename = match path. rsplit ( path:: is_separator) . next ( ) {
52
+ Some ( name) => name,
53
+ None => return false ,
54
+ } ;
55
+
56
+ let words: Vec < & str > = basename
57
+ . split ( |c : char | c == '-' || c == '_' || c == ' ' || c == '.' )
58
+ . filter ( |s| !s. is_empty ( ) )
59
+ . collect ( ) ;
60
+
61
+ if words. len ( ) < 2 {
62
+ return false ;
63
+ }
64
+
65
+ let acronym: String = words. iter ( )
66
+ . filter_map ( |word| word. chars ( ) . next ( ) )
67
+ . collect ( ) ;
68
+
69
+ let acronym_lower = util:: to_lowercase ( & acronym) ;
70
+
71
+ let mut user_input = String :: new ( ) ;
72
+ for kw in keywords {
73
+ user_input. push_str ( kw) ;
74
+ }
75
+ user_input. push_str ( keywords_last) ;
76
+
77
+ acronym_lower == util:: to_lowercase ( & user_input)
78
+ }
79
+
50
80
fn filter_by_keywords ( & self , path : & str ) -> bool {
51
81
let ( keywords_last, keywords) = match self . options . keywords . split_last ( ) {
52
82
Some ( split) => split,
53
83
None => return true ,
54
84
} ;
55
-
56
- let path = util:: to_lowercase ( path) ;
57
- let mut path = path. as_str ( ) ;
58
- match path. rfind ( keywords_last) {
59
- Some ( idx) => {
60
- if path[ idx + keywords_last. len ( ) ..] . contains ( path:: is_separator) {
61
- return false ;
85
+
86
+ let path_lower = util:: to_lowercase ( path) ;
87
+ let mut path_str = path_lower. as_str ( ) ;
88
+
89
+ let regular_match = {
90
+ let mut matched = false ;
91
+ match path_str. rfind ( keywords_last) {
92
+ Some ( idx) => {
93
+ if path_str[ idx + keywords_last. len ( ) ..] . contains ( path:: is_separator) {
94
+ return false ;
95
+ }
96
+ path_str = & path_str[ ..idx] ;
97
+ matched = true ;
62
98
}
63
- path = & path [ ..idx ] ;
99
+ None => { }
64
100
}
65
- None => return false ,
66
- }
67
-
68
- for keyword in keywords. iter ( ) . rev ( ) {
69
- match path. rfind ( keyword) {
70
- Some ( idx) => path = & path[ ..idx] ,
71
- None => return false ,
101
+
102
+ if !matched {
103
+ return self . match_acronym ( path, keywords_last, keywords) ;
72
104
}
73
- }
74
-
75
- true
105
+
106
+ for keyword in keywords. iter ( ) . rev ( ) {
107
+ match path_str. rfind ( keyword) {
108
+ Some ( idx) => path_str = & path_str[ ..idx] ,
109
+ None => return self . match_acronym ( path, keywords_last, keywords) ,
110
+ }
111
+ }
112
+
113
+ true
114
+ } ;
115
+
116
+ regular_match
76
117
}
77
118
78
119
fn filter_by_exclude ( & self , path : & str ) -> bool {
@@ -185,4 +226,35 @@ mod tests {
185
226
let stream = Stream :: new ( db, options) ;
186
227
assert_eq ! ( is_match, stream. filter_by_keywords( path) ) ;
187
228
}
229
+
230
+ #[ rstest]
231
+ #[ case( & [ "hick" ] , "/home/bachman/Documents/hooli-interactive-computer-keyboard" , true ) ]
232
+ #[ case( & [ "HICK" ] , "/home/bachman/Documents/hooli-interactive-computer-keyboard" , true ) ] // Case insensitive
233
+ #[ case( & [ "hick" ] , "/home/bachman/Documents/hooli_interactive_computer_keyboard" , true ) ] // Different separators
234
+ #[ case( & [ "hick" ] , "/home/bachman/Documents/hooli interactive.computer-keyboard" , true ) ] // Mixed separators
235
+ #[ case( & [ "hick" ] , "/home/bachman/Documents/hooli-interactive-keyboard" , false ) ] // Incomplete acronym
236
+ #[ case( & [ "hik" ] , "/home/bachman/Documents/hooli-interactive-keyboard" , true ) ] // Correct acronym for shorter name
237
+ #[ case( & [ "h" ] , "/home/bachman/Documents/hooli" , false ) ] // Single letter - not an acronym
238
+ #[ case( & [ "abc" ] , "/home/bachman/Documents/a-b-c" , true ) ] // Short words
239
+ #[ case( & [ "abc" ] , "/home/bachman/Documents/a-b" , false ) ] // Partial match
240
+ fn acronym_match ( #[ case] keywords : & [ & str ] , #[ case] path : & str , #[ case] is_match : bool ) {
241
+ let db = & mut Database :: new ( PathBuf :: new ( ) , Vec :: new ( ) , |_| Vec :: new ( ) , false ) ;
242
+ let options = StreamOptions :: new ( 0 ) . with_keywords ( keywords. iter ( ) ) ;
243
+ let stream = Stream :: new ( db, options) ;
244
+ let last_keyword = keywords. last ( ) . unwrap ( ) ;
245
+ let other_keywords: Vec < String > = keywords[ ..keywords. len ( ) -1 ] . iter ( ) . map ( |& s| s. to_string ( ) ) . collect ( ) ;
246
+ assert_eq ! ( is_match, stream. match_acronym( path, last_keyword, & other_keywords) ) ;
247
+ }
248
+
249
+ // Ensure the filter_by_keywords function correctly handles acronyms
250
+ #[ rstest]
251
+ #[ case( & [ "hick" ] , "/home/bachman/Documents/hooli-interactive-computer-keyboard" , true ) ]
252
+ #[ case( & [ "hooli" ] , "/home/bachman/Documents/hooli-interactive-computer-keyboard" , true ) ] // Regular match still works
253
+ #[ case( & [ "keyb" ] , "/home/bachman/Documents/hooli-interactive-computer-keyboard" , true ) ] // Regular match still works
254
+ fn integrated_acronym_keyword_filter ( #[ case] keywords : & [ & str ] , #[ case] path : & str , #[ case] is_match : bool ) {
255
+ let db = & mut Database :: new ( PathBuf :: new ( ) , Vec :: new ( ) , |_| Vec :: new ( ) , false ) ;
256
+ let options = StreamOptions :: new ( 0 ) . with_keywords ( keywords. iter ( ) ) ;
257
+ let stream = Stream :: new ( db, options) ;
258
+ assert_eq ! ( is_match, stream. filter_by_keywords( path) ) ;
259
+ }
188
260
}
0 commit comments