@@ -2,7 +2,7 @@ use crate::{
22 constraints:: apply_constraints,
33 git:: is_modified_status,
44 path_utils:: calculate_distance_penalty,
5- simd_path:: ArenaPtr ,
5+ simd_path:: { ArenaPtr , SimdChunk } ,
66 sort_buffer:: { sort_by_key_with_buffer, sort_with_buffer} ,
77 types:: { FileItem , Score , ScoringContext } ,
88} ;
@@ -26,6 +26,21 @@ impl<'a> FileItems<'a> {
2626 }
2727}
2828
29+ /// Resolve a FileItem's chunked path into frizbee's pointer buffer.
30+ /// Returns `Some((chunk_count, byte_len))` or `None` for deleted files.
31+ #[ inline]
32+ fn resolve_file_chunks (
33+ file : & FileItem ,
34+ arena : ArenaPtr ,
35+ buf : & mut [ * const u8 ; 32 ] ,
36+ ) -> Option < ( usize , u16 ) > {
37+ if file. is_deleted ( ) {
38+ return None ;
39+ }
40+ let ptrs = file. path . resolve_ptrs ( arena, buf) ;
41+ Some ( ( ptrs. len ( ) , file. path . byte_len ) )
42+ }
43+
2944#[ inline]
3045fn match_fuzzy_parts (
3146 fuzzy_parts : & [ & str ] ,
@@ -45,39 +60,34 @@ fn match_fuzzy_parts(
4560 return vec ! [ ] ;
4661 }
4762
48- // Collect paths from the arena into a temporary Vec<String> for frizbee matching.
49- // This is the bridge between arena-stored paths and frizbee's Matchable trait.
50- // Deleted files get an empty string so their index stays aligned.
51- let paths: Vec < String > = match working_files {
52- FileItems :: All ( files) => files
53- . iter ( )
54- . map ( |f| {
55- if f. is_deleted ( ) {
56- String :: new ( )
57- } else {
58- f. relative_path_from_arena ( arena)
59- }
60- } )
61- . collect ( ) ,
62- FileItems :: Filtered ( files) => files
63- . iter ( )
64- . map ( |f| {
65- if f. is_deleted ( ) {
66- String :: new ( )
67- } else {
68- f. relative_path_from_arena ( arena)
69- }
70- } )
71- . collect ( ) ,
63+ let resolve = |file : & FileItem , buf : & mut [ * const u8 ; 32 ] | -> Option < ( usize , u16 ) > {
64+ resolve_file_chunks ( file, arena, buf)
7265 } ;
7366
74- let first_part_matches =
75- neo_frizbee:: match_list_parallel ( valid_parts[ 0 ] , & paths, options, max_threads) ;
67+ let first_part_matches = match working_files {
68+ FileItems :: All ( files) => neo_frizbee:: match_list_parallel_resolved (
69+ valid_parts[ 0 ] ,
70+ files,
71+ & resolve,
72+ options,
73+ max_threads,
74+ ) ,
75+ FileItems :: Filtered ( files) => neo_frizbee:: match_list_parallel_resolved (
76+ valid_parts[ 0 ] ,
77+ files. as_slice ( ) ,
78+ & |file_ref : & & FileItem , buf : & mut [ * const u8 ; 32 ] | {
79+ resolve_file_chunks ( file_ref, arena, buf)
80+ } ,
81+ options,
82+ max_threads,
83+ ) ,
84+ } ;
7685
7786 if valid_parts. len ( ) == 1 {
7887 return first_part_matches;
7988 }
8089
90+ let mut path_buf = [ 0u8 ; crate :: simd_path:: PATH_BUF_SIZE ] ;
8191 let mut matches = first_part_matches;
8292 for part in valid_parts[ 1 ..] . iter ( ) {
8393 let mut part_options = * options;
@@ -87,9 +97,8 @@ fn match_fuzzy_parts(
8797 . into_iter ( )
8898 . filter_map ( |mut m| {
8999 let file = working_files. index ( m. index as usize ) ;
90- let mut buf = String :: with_capacity ( 64 ) ;
91- file. write_relative_path_from_arena ( arena, & mut buf) ;
92- let part_matches = neo_frizbee:: match_list ( part, & [ buf. as_str ( ) ] , & part_options) ;
100+ let path_str = file. path . read_to_buf ( arena, & mut path_buf) ;
101+ let part_matches = neo_frizbee:: match_list ( part, & [ path_str] , & part_options) ;
93102 let part_match = part_matches. first ( ) ?;
94103
95104 let total = ( m. score as u32 ) . saturating_add ( part_match. score as u32 ) ;
@@ -194,7 +203,7 @@ fn match_and_score_in_arena<'a>(
194203 {
195204 vec ! [ ]
196205 } else {
197- let mut fallback_filenames : Vec < String > = Vec :: new ( ) ;
206+ let mut fallback_items : Vec < & FileItem > = Vec :: new ( ) ;
198207
199208 for ( i, path_match) in path_matches. iter ( ) . enumerate ( ) {
200209 let file = working_files. index ( path_match. index as usize ) ;
@@ -203,20 +212,38 @@ fn match_and_score_in_arena<'a>(
203212
204213 if match_start_approx < filename_start {
205214 fallback_indices. push ( i as u32 ) ;
206- fallback_filenames . push ( file. file_name_from_arena ( arena ) ) ;
215+ fallback_items . push ( file) ;
207216 }
208217 }
209218
210- if fallback_filenames . is_empty ( ) {
219+ if fallback_items . is_empty ( ) {
211220 vec ! [ ]
212221 } else {
213- let fallback_refs: Vec < & str > = fallback_filenames. iter ( ) . map ( String :: as_str) . collect ( ) ;
214- let mut matches = neo_frizbee:: match_list_parallel (
222+ let mut matches = neo_frizbee:: match_list_parallel_resolved (
215223 fuzzy_parts[ 0 ] ,
216- & fallback_refs,
224+ & fallback_items,
225+ & |item, chunk_buf| -> Option < ( usize , u16 ) > {
226+ // pretty ugly way to express the map_init but it's fine for now
227+ // can't do stack here as the buffer needs to escape this region but can be one
228+ // per thread
229+ thread_local ! {
230+ static SCRATCH : std:: cell:: UnsafeCell <SimdChunk > =
231+ const { std:: cell:: UnsafeCell :: new( SimdChunk ( [ 0u8 ; crate :: simd_path:: SIMD_CHUNK_BYTES ] ) ) } ;
232+ }
233+
234+ SCRATCH . with ( |cell| {
235+ let scratch = unsafe { & mut ( * cell. get ( ) ) . 0 } ;
236+ let ( ptrs, fname_len) =
237+ item. path . resolve_filename_ptrs ( arena, chunk_buf, scratch) ;
238+ if fname_len == 0 {
239+ return None ;
240+ }
241+ Some ( ( ptrs. len ( ) , fname_len) )
242+ } )
243+ } ,
217244 & options,
218- if path_matches. len ( ) > 10_000 {
219- context. max_threads
245+ if path_matches. len ( ) > 4096 {
246+ context. max_threads . div_ceil ( 2048 )
220247 } else {
221248 1
222249 } ,
@@ -233,6 +260,8 @@ fn match_and_score_in_arena<'a>(
233260 let mut next_filename_match_cursor = 0 ;
234261 let mut dir_buf = String :: with_capacity ( 64 ) ;
235262 let mut fname_buf = String :: with_capacity ( 32 ) ;
263+ let mut path_buf = [ 0u8 ; crate :: simd_path:: PATH_BUF_SIZE ] ;
264+
236265 let results: Vec < _ > = path_matches
237266 . into_iter ( )
238267 . enumerate ( )
@@ -340,7 +369,7 @@ fn match_and_score_in_arena<'a>(
340369 // Uses suffix overlap — bytes matching from the end. A full prefix
341370 // match is just the 100% coverage case, so no separate branch needed.
342371 let path_alignment_bonus = if query_contains_path_separator {
343- let rel_path = file. relative_path_from_arena ( arena) ;
372+ let rel_path = file. path . read_to_buf ( arena, & mut path_buf ) ;
344373 let path_bytes = rel_path. as_bytes ( ) ;
345374 let common_suffix = main_needle
346375 . iter ( )
@@ -595,28 +624,7 @@ mod tests {
595624 use crate :: types:: PaginationArgs ;
596625 use fff_query_parser:: QueryParser ;
597626
598- // ── Helpers ──────────────────────────────────────────────────────────
599-
600- fn create_test_file ( path : & str , score : i32 , modified : u64 ) -> ( FileItem , Score ) {
601- let file = FileItem :: new_for_test ( path, 0 , modified, None , false ) ;
602- let score_obj = Score {
603- total : score,
604- base_score : score,
605- filename_bonus : 0 ,
606- distance_penalty : 0 ,
607- special_filename_bonus : 0 ,
608- current_file_penalty : 0 ,
609- frecency_boost : 0 ,
610- git_status_boost : 0 ,
611- exact_match : false ,
612- match_type : "test" ,
613- combo_match_boost : 0 ,
614- path_alignment_bonus : 0 ,
615- } ;
616- ( file, score_obj)
617- }
618-
619- fn build_test_files ( specs : & [ ( & str , i32 , u64 ) ] ) -> ( Vec < ( FileItem , Score ) > , ArenaPtr ) {
627+ fn make_test_files ( specs : & [ ( & str , i32 , u64 ) ] ) -> ( Vec < ( FileItem , Score ) > , ArenaPtr ) {
620628 let path_strings: Vec < String > = specs. iter ( ) . map ( |( p, _, _) | p. to_string ( ) ) . collect ( ) ;
621629 let items: Vec < FileItem > = specs
622630 . iter ( )
@@ -659,7 +667,7 @@ mod tests {
659667 #[ test]
660668 fn test_partial_sort_descending ( ) {
661669 // Create test data with known scores
662- let ( test_data, arena) = build_test_files ( & [
670+ let ( test_data, arena) = make_test_files ( & [
663671 ( "file1.rs" , 100 , 1000 ) ,
664672 ( "file2.rs" , 200 , 2000 ) ,
665673 ( "file3.rs" , 50 , 3000 ) ,
@@ -716,7 +724,7 @@ mod tests {
716724 #[ test]
717725 fn test_partial_sort_with_same_scores ( ) {
718726 // Test tiebreaker with modified time
719- let ( test_data, _arena) = build_test_files ( & [
727+ let ( test_data, _arena) = make_test_files ( & [
720728 ( "file1.rs" , 100 , 5000 ) , // Same score, older
721729 ( "file2.rs" , 100 , 8000 ) , // Same score, newer
722730 ( "file3.rs" , 100 , 3000 ) , // Same score, oldest
@@ -767,7 +775,7 @@ mod tests {
767775 #[ test]
768776 fn test_no_partial_sort_for_small_results ( ) {
769777 // When results.len() <= threshold, should use regular sort
770- let ( test_data, arena) = build_test_files ( & [
778+ let ( test_data, arena) = make_test_files ( & [
771779 ( "file1.rs" , 100 , 1000 ) ,
772780 ( "file2.rs" , 200 , 2000 ) ,
773781 ( "file3.rs" , 50 , 3000 ) ,
0 commit comments