@@ -172,6 +172,10 @@ pub struct SearchQuery {
172172 pub exclude_dir_names : Option < Vec < String > > ,
173173 #[ serde( default = "default_limit" ) ]
174174 pub limit : u32 ,
175+ /// Per-query case sensitivity override.
176+ /// `None` = platform default (false on macOS, true on Linux).
177+ #[ serde( default ) ]
178+ pub case_sensitive : Option < bool > ,
175179}
176180
177181fn default_limit ( ) -> u32 {
@@ -257,6 +261,13 @@ pub fn summarize_query(query: &SearchQuery) -> String {
257261 None => { }
258262 }
259263
264+ // Case sensitivity (only show when explicitly set)
265+ match query. case_sensitive {
266+ Some ( true ) => parts. push ( "case-sensitive" . to_string ( ) ) ,
267+ Some ( false ) => parts. push ( "case-insensitive" . to_string ( ) ) ,
268+ None => { }
269+ }
270+
260271 if parts. is_empty ( ) {
261272 "(all entries)" . to_string ( )
262273 } else {
@@ -549,10 +560,12 @@ fn prepare_scope_filter(query: &SearchQuery, index: &SearchIndex) -> ScopeFilter
549560 } else {
550561 // Bare name: compile as glob pattern
551562 let regex_str = glob_to_regex ( pattern) ;
552- if let Ok ( re) = RegexBuilder :: new ( & regex_str)
553- . case_insensitive ( cfg ! ( target_os = "macos" ) )
554- . build ( )
555- {
563+ let case_insensitive = match query. case_sensitive {
564+ Some ( true ) => false ,
565+ Some ( false ) => true ,
566+ None => cfg ! ( target_os = "macos" ) ,
567+ } ;
568+ if let Ok ( re) = RegexBuilder :: new ( & regex_str) . case_insensitive ( case_insensitive) . build ( ) {
556569 exclude_name_patterns. push ( re) ;
557570 }
558571 }
@@ -633,8 +646,13 @@ pub fn search(index: &SearchIndex, query: &SearchQuery) -> Result<SearchResult,
633646 }
634647 PatternType :: Regex => pattern. to_string ( ) ,
635648 } ;
649+ let case_insensitive = match query. case_sensitive {
650+ Some ( true ) => false ,
651+ Some ( false ) => true ,
652+ None => cfg ! ( target_os = "macos" ) ,
653+ } ;
636654 let re = RegexBuilder :: new ( & regex_str)
637- . case_insensitive ( cfg ! ( target_os = "macos" ) )
655+ . case_insensitive ( case_insensitive )
638656 . build ( )
639657 . map_err ( |e| format ! ( "Invalid pattern: {e}" ) ) ?;
640658 Some ( re)
@@ -969,6 +987,7 @@ mod tests {
969987 include_paths : None ,
970988 exclude_dir_names : None ,
971989 limit : 30 ,
990+ case_sensitive : None ,
972991 } ;
973992 let result = search ( & index, & query) . unwrap ( ) ;
974993 // "ote" should match "notes.txt" as a substring
@@ -990,6 +1009,7 @@ mod tests {
9901009 include_paths : None ,
9911010 exclude_dir_names : None ,
9921011 limit : 30 ,
1012+ case_sensitive : None ,
9931013 } ;
9941014 let result = search ( & index, & query) . unwrap ( ) ;
9951015 // "repo" should match "report.pdf" and "Q1-report.pdf"
@@ -1012,6 +1032,7 @@ mod tests {
10121032 include_paths : None ,
10131033 exclude_dir_names : None ,
10141034 limit : 30 ,
1035+ case_sensitive : None ,
10151036 } ;
10161037 let result = search ( & index, & query) . unwrap ( ) ;
10171038 // "report*" matches "report.pdf" but NOT "Q1-report.pdf"
@@ -1145,6 +1166,7 @@ mod tests {
11451166 include_paths : None ,
11461167 exclude_dir_names : None ,
11471168 limit : 30 ,
1169+ case_sensitive : None ,
11481170 } ;
11491171 let result = search ( & index, & query) . unwrap ( ) ;
11501172 assert_eq ! ( result. total_count, 2 ) ;
@@ -1165,6 +1187,7 @@ mod tests {
11651187 include_paths : None ,
11661188 exclude_dir_names : None ,
11671189 limit : 30 ,
1190+ case_sensitive : None ,
11681191 } ;
11691192 let result = search ( & index, & query) . unwrap ( ) ;
11701193 assert_eq ! ( result. total_count, 1 ) ;
@@ -1188,6 +1211,7 @@ mod tests {
11881211 include_paths : None ,
11891212 exclude_dir_names : None ,
11901213 limit : 30 ,
1214+ case_sensitive : None ,
11911215 } ;
11921216 let result = search ( & index, & query) . unwrap ( ) ;
11931217 // On macOS, matching is case-insensitive
@@ -1211,6 +1235,7 @@ mod tests {
12111235 include_paths : None ,
12121236 exclude_dir_names : None ,
12131237 limit : 30 ,
1238+ case_sensitive : None ,
12141239 } ;
12151240 let result = search ( & index, & query) . unwrap ( ) ;
12161241 assert_eq ! ( result. total_count, 1 ) ;
@@ -1231,6 +1256,7 @@ mod tests {
12311256 include_paths : None ,
12321257 exclude_dir_names : None ,
12331258 limit : 30 ,
1259+ case_sensitive : None ,
12341260 } ;
12351261 let result = search ( & index, & query) ;
12361262 assert ! ( result. is_err( ) ) ;
@@ -1253,6 +1279,7 @@ mod tests {
12531279 include_paths : None ,
12541280 exclude_dir_names : None ,
12551281 limit : 30 ,
1282+ case_sensitive : None ,
12561283 } ;
12571284 let result = search ( & index, & query) . unwrap ( ) ;
12581285 // photo.jpg (5M) and Q1-report.pdf (2M)
@@ -1274,6 +1301,7 @@ mod tests {
12741301 include_paths : None ,
12751302 exclude_dir_names : None ,
12761303 limit : 30 ,
1304+ case_sensitive : None ,
12771305 } ;
12781306 let result = search ( & index, & query) . unwrap ( ) ;
12791307 assert_eq ! ( result. total_count, 1 ) ;
@@ -1294,6 +1322,7 @@ mod tests {
12941322 include_paths : None ,
12951323 exclude_dir_names : None ,
12961324 limit : 30 ,
1325+ case_sensitive : None ,
12971326 } ;
12981327 let result = search ( & index, & query) . unwrap ( ) ;
12991328 // report.pdf (1M) and Q1-report.pdf (2M)
@@ -1316,6 +1345,7 @@ mod tests {
13161345 include_paths : None ,
13171346 exclude_dir_names : None ,
13181347 limit : 30 ,
1348+ case_sensitive : None ,
13191349 } ;
13201350 let result = search ( & index, & query) . unwrap ( ) ;
13211351 // photo.jpg (4000), notes.txt (5000), Q1-report.pdf (6000)
@@ -1336,6 +1366,7 @@ mod tests {
13361366 include_paths : None ,
13371367 exclude_dir_names : None ,
13381368 limit : 30 ,
1369+ case_sensitive : None ,
13391370 } ;
13401371 let result = search ( & index, & query) . unwrap ( ) ;
13411372 // Users (1000), alice (2000), Documents (1500)
@@ -1356,6 +1387,7 @@ mod tests {
13561387 include_paths : None ,
13571388 exclude_dir_names : None ,
13581389 limit : 30 ,
1390+ case_sensitive : None ,
13591391 } ;
13601392 let result = search ( & index, & query) . unwrap ( ) ;
13611393 // report.pdf (3000), photo.jpg (4000), notes.txt (5000)
@@ -1378,6 +1410,7 @@ mod tests {
13781410 include_paths : None ,
13791411 exclude_dir_names : None ,
13801412 limit : 30 ,
1413+ case_sensitive : None ,
13811414 } ;
13821415 let result = search ( & index, & query) . unwrap ( ) ;
13831416 assert_eq ! ( result. total_count, 1 ) ;
@@ -1400,6 +1433,7 @@ mod tests {
14001433 include_paths : None ,
14011434 exclude_dir_names : None ,
14021435 limit : 30 ,
1436+ case_sensitive : None ,
14031437 } ;
14041438 let result = search ( & index, & query) . unwrap ( ) ;
14051439 // All entries except root sentinel (7 entries)
@@ -1424,6 +1458,7 @@ mod tests {
14241458 include_paths : None ,
14251459 exclude_dir_names : None ,
14261460 limit : 3 ,
1461+ case_sensitive : None ,
14271462 } ;
14281463 let result = search ( & index, & query) . unwrap ( ) ;
14291464 assert_eq ! ( result. entries. len( ) , 3 ) ;
@@ -1446,6 +1481,7 @@ mod tests {
14461481 include_paths : None ,
14471482 exclude_dir_names : None ,
14481483 limit : 30 ,
1484+ case_sensitive : None ,
14491485 } ;
14501486 let result = search ( & index, & query) . unwrap ( ) ;
14511487 // Users, alice, Documents (root excluded)
@@ -1467,6 +1503,7 @@ mod tests {
14671503 include_paths : None ,
14681504 exclude_dir_names : None ,
14691505 limit : 30 ,
1506+ case_sensitive : None ,
14701507 } ;
14711508 let result = search ( & index, & query) . unwrap ( ) ;
14721509 assert_eq ! ( result. total_count, 4 ) ;
@@ -1533,6 +1570,7 @@ mod tests {
15331570 include_paths : None ,
15341571 exclude_dir_names : None ,
15351572 limit : 30 ,
1573+ case_sensitive : None ,
15361574 } ;
15371575 let json = serde_json:: to_string ( & query) . unwrap ( ) ;
15381576 assert ! ( json. contains( "namePattern" ) ) ;
@@ -1643,6 +1681,7 @@ mod tests {
16431681 include_paths : None ,
16441682 exclude_dir_names : None ,
16451683 limit : 30 ,
1684+ case_sensitive : None ,
16461685 } ;
16471686 let result = search ( & index, & query) . unwrap ( ) ;
16481687 assert_eq ! ( result. total_count, 1 ) ;
@@ -1687,6 +1726,7 @@ mod tests {
16871726 include_paths : None ,
16881727 exclude_dir_names : None ,
16891728 limit : 30 ,
1729+ case_sensitive : None ,
16901730 }
16911731 }
16921732
@@ -2038,6 +2078,7 @@ mod tests {
20382078 include_paths : Some ( vec ! [ "/Users/alice/projects" . to_string( ) ] ) ,
20392079 exclude_dir_names : None ,
20402080 limit : 30 ,
2081+ case_sensitive : None ,
20412082 } ;
20422083 let result = search ( & index, & query) . unwrap ( ) ;
20432084 // Should find app.rs and pkg.json (both under /Users/alice/projects)
@@ -2062,6 +2103,7 @@ mod tests {
20622103 include_paths : None ,
20632104 exclude_dir_names : Some ( vec ! [ "node_modules" . to_string( ) ] ) ,
20642105 limit : 30 ,
2106+ case_sensitive : None ,
20652107 } ;
20662108 let result = search ( & index, & query) . unwrap ( ) ;
20672109 // Should find app.rs and config, but NOT pkg.json (under node_modules)
@@ -2086,6 +2128,7 @@ mod tests {
20862128 include_paths : Some ( vec ! [ "/Users/alice/projects" . to_string( ) ] ) ,
20872129 exclude_dir_names : Some ( vec ! [ "node_modules" . to_string( ) ] ) ,
20882130 limit : 30 ,
2131+ case_sensitive : None ,
20892132 } ;
20902133 let result = search ( & index, & query) . unwrap ( ) ;
20912134 // Only app.rs: under projects but not under node_modules
@@ -2107,6 +2150,7 @@ mod tests {
21072150 include_paths : None ,
21082151 exclude_dir_names : Some ( vec ! [ ".*" . to_string( ) ] ) ,
21092152 limit : 30 ,
2153+ case_sensitive : None ,
21102154 } ;
21112155 let result = search ( & index, & query) . unwrap ( ) ;
21122156 // Should exclude config (under .git) but keep app.rs and pkg.json
0 commit comments