@@ -24,12 +24,17 @@ use mimalloc::MiMalloc;
2424#[ global_allocator]
2525static GLOBAL : MiMalloc = MiMalloc ;
2626
27- /// Helper to convert C string to Rust string
28- unsafe fn cstr_to_string ( s : * const c_char ) -> Option < String > {
27+ /// Helper to convert C string to Rust &str.
28+ ///
29+ /// Returns `None` if the pointer is null or the string is not valid UTF-8.
30+ /// This is more efficient than `to_string_lossy()` as it returns a borrowed
31+ /// `&str` directly without `Cow` overhead, and avoids replacement character
32+ /// scanning since callers are expected to provide valid UTF-8.
33+ unsafe fn cstr_to_str < ' a > ( s : * const c_char ) -> Option < & ' a str > {
2934 if s. is_null ( ) {
3035 None
3136 } else {
32- unsafe { CStr :: from_ptr ( s) . to_str ( ) . ok ( ) . map ( |s| s . to_string ( ) ) }
37+ unsafe { CStr :: from_ptr ( s) . to_str ( ) . ok ( ) }
3338 }
3439}
3540
@@ -39,42 +44,23 @@ unsafe fn cstr_to_string(s: *const c_char) -> Option<String> {
3944/// `opts_json` must be a valid null-terminated UTF-8 string
4045#[ unsafe( no_mangle) ]
4146pub unsafe extern "C" fn fff_init ( opts_json : * const c_char ) -> * mut FffResult {
42- let opts_str = match unsafe { cstr_to_string ( opts_json) } {
47+ let opts_str = match unsafe { cstr_to_str ( opts_json) } {
4348 Some ( s) => s,
4449 None => return FffResult :: err ( "Options JSON is null or invalid UTF-8" ) ,
4550 } ;
4651
47- let opts: InitOptions = match serde_json:: from_str ( & opts_str) {
52+ let opts: InitOptions = match serde_json:: from_str ( opts_str) {
4853 Ok ( o) => o,
4954 Err ( e) => return FffResult :: err ( & format ! ( "Failed to parse options: {}" , e) ) ,
5055 } ;
5156
52- // Initialize databases if not skipped
53- if !opts. skip_databases {
54- let frecency_path = opts. frecency_db_path . unwrap_or_else ( || {
55- dirs:: home_dir ( )
56- . unwrap_or_else ( || PathBuf :: from ( "." ) )
57- . join ( ".fff" )
58- . join ( "frecency.mdb" )
59- . to_string_lossy ( )
60- . to_string ( )
61- } ) ;
62-
63- let history_path = opts. history_db_path . unwrap_or_else ( || {
64- dirs:: home_dir ( )
65- . unwrap_or_else ( || PathBuf :: from ( "." ) )
66- . join ( ".fff" )
67- . join ( "history.mdb" )
68- . to_string_lossy ( )
69- . to_string ( )
70- } ) ;
71-
57+ // Initialize frecency tracker if path is provided
58+ if let Some ( frecency_path) = opts. frecency_db_path {
7259 // Ensure directory exists
7360 if let Some ( parent) = PathBuf :: from ( & frecency_path) . parent ( ) {
7461 let _ = std:: fs:: create_dir_all ( parent) ;
7562 }
7663
77- // Initialize frecency tracker
7864 let mut frecency = match FRECENCY . write ( ) {
7965 Ok ( f) => f,
8066 Err ( e) => return FffResult :: err ( & format ! ( "Failed to acquire frecency lock: {}" , e) ) ,
@@ -85,8 +71,15 @@ pub unsafe extern "C" fn fff_init(opts_json: *const c_char) -> *mut FffResult {
8571 Err ( e) => return FffResult :: err ( & format ! ( "Failed to init frecency db: {}" , e) ) ,
8672 }
8773 drop ( frecency) ;
74+ }
75+
76+ // Initialize query tracker if path is provided
77+ if let Some ( history_path) = opts. history_db_path {
78+ // Ensure directory exists
79+ if let Some ( parent) = PathBuf :: from ( & history_path) . parent ( ) {
80+ let _ = std:: fs:: create_dir_all ( parent) ;
81+ }
8882
89- // Initialize query tracker
9083 let mut query_tracker = match QUERY_TRACKER . write ( ) {
9184 Ok ( q) => q,
9285 Err ( e) => {
@@ -159,16 +152,16 @@ pub unsafe extern "C" fn fff_search(
159152 query : * const c_char ,
160153 opts_json : * const c_char ,
161154) -> * mut FffResult {
162- let query_str = match unsafe { cstr_to_string ( query) } {
155+ let query_str = match unsafe { cstr_to_str ( query) } {
163156 Some ( s) => s,
164157 None => return FffResult :: err ( "Query is null or invalid UTF-8" ) ,
165158 } ;
166159
167160 let opts: SearchOptions = if opts_json. is_null ( ) {
168161 SearchOptions :: default ( )
169162 } else {
170- unsafe { cstr_to_string ( opts_json) }
171- . and_then ( |s| serde_json:: from_str ( & s) . ok ( ) )
163+ unsafe { cstr_to_str ( opts_json) }
164+ . and_then ( |s| serde_json:: from_str ( s) . ok ( ) )
172165 . unwrap_or_default ( )
173166 } ;
174167
@@ -194,19 +187,19 @@ pub unsafe extern "C" fn fff_search(
194187
195188 query_tracker. as_ref ( ) . and_then ( |tracker| {
196189 tracker
197- . get_last_query_entry ( & query_str, base_path, min_combo_count)
190+ . get_last_query_entry ( query_str, base_path, min_combo_count)
198191 . ok ( )
199192 . flatten ( )
200193 } )
201194 } ;
202195
203196 // Parse the query
204197 let parser = QueryParser :: default ( ) ;
205- let parsed = parser. parse ( & query_str) ;
198+ let parsed = parser. parse ( query_str) ;
206199
207200 let results = FilePicker :: fuzzy_search (
208201 picker. get_files ( ) ,
209- & query_str,
202+ query_str,
210203 parsed,
211204 FuzzySearchOptions {
212205 max_threads : opts. max_threads . unwrap_or ( 0 ) ,
@@ -322,7 +315,7 @@ pub extern "C" fn fff_wait_for_scan(timeout_ms: u64) -> *mut FffResult {
322315/// `new_path` must be a valid null-terminated UTF-8 string
323316#[ unsafe( no_mangle) ]
324317pub unsafe extern "C" fn fff_restart_index ( new_path : * const c_char ) -> * mut FffResult {
325- let path_str = match unsafe { cstr_to_string ( new_path) } {
318+ let path_str = match unsafe { cstr_to_str ( new_path) } {
326319 Some ( s) => s,
327320 None => return FffResult :: err ( "Path is null or invalid UTF-8" ) ,
328321 } ;
@@ -367,7 +360,7 @@ pub unsafe extern "C" fn fff_restart_index(new_path: *const c_char) -> *mut FffR
367360/// `file_path` must be a valid null-terminated UTF-8 string
368361#[ unsafe( no_mangle) ]
369362pub unsafe extern "C" fn fff_track_access ( file_path : * const c_char ) -> * mut FffResult {
370- let path_str = match unsafe { cstr_to_string ( file_path) } {
363+ let path_str = match unsafe { cstr_to_str ( file_path) } {
371364 Some ( s) => s,
372365 None => return FffResult :: err ( "File path is null or invalid UTF-8" ) ,
373366 } ;
@@ -439,12 +432,12 @@ pub unsafe extern "C" fn fff_track_query(
439432 query : * const c_char ,
440433 file_path : * const c_char ,
441434) -> * mut FffResult {
442- let query_str = match unsafe { cstr_to_string ( query) } {
435+ let query_str = match unsafe { cstr_to_str ( query) } {
443436 Some ( s) => s,
444437 None => return FffResult :: err ( "Query is null or invalid UTF-8" ) ,
445438 } ;
446439
447- let path_str = match unsafe { cstr_to_string ( file_path) } {
440+ let path_str = match unsafe { cstr_to_str ( file_path) } {
448441 Some ( s) => s,
449442 None => return FffResult :: err ( "File path is null or invalid UTF-8" ) ,
450443 } ;
@@ -471,7 +464,7 @@ pub unsafe extern "C" fn fff_track_query(
471464 } ;
472465
473466 if let Some ( ref mut tracker) = * query_tracker
474- && let Err ( e) = tracker. track_query_completion ( & query_str, & project_path, & file_path)
467+ && let Err ( e) = tracker. track_query_completion ( query_str, & project_path, & file_path)
475468 {
476469 return FffResult :: err ( & format ! ( "Failed to track query: {}" , e) ) ;
477470 }
@@ -519,7 +512,7 @@ pub extern "C" fn fff_get_historical_query(offset: u64) -> *mut FffResult {
519512/// `test_path` can be null or a valid null-terminated UTF-8 string
520513#[ unsafe( no_mangle) ]
521514pub unsafe extern "C" fn fff_health_check ( test_path : * const c_char ) -> * mut FffResult {
522- let test_path = unsafe { cstr_to_string ( test_path) }
515+ let test_path = unsafe { cstr_to_str ( test_path) }
523516 . filter ( |s| !s. is_empty ( ) )
524517 . map ( PathBuf :: from)
525518 . unwrap_or_else ( || std:: env:: current_dir ( ) . unwrap_or_default ( ) ) ;
@@ -689,14 +682,13 @@ pub unsafe extern "C" fn fff_shorten_path(
689682 max_size : u64 ,
690683 strategy : * const c_char ,
691684) -> * mut FffResult {
692- let path_str = match unsafe { cstr_to_string ( path) } {
685+ let path_str = match unsafe { cstr_to_str ( path) } {
693686 Some ( s) => s,
694687 None => return FffResult :: err ( "Path is null or invalid UTF-8" ) ,
695688 } ;
696689
697- let strategy_str =
698- unsafe { cstr_to_string ( strategy) } . unwrap_or_else ( || "middle_number" . to_string ( ) ) ;
699- let strategy = fff_core:: PathShortenStrategy :: from_name ( & strategy_str) ;
690+ let strategy_str = unsafe { cstr_to_str ( strategy) } . unwrap_or ( "middle_number" ) ;
691+ let strategy = fff_core:: PathShortenStrategy :: from_name ( strategy_str) ;
700692
701693 match fff_core:: shorten_path ( strategy, max_size as usize , & PathBuf :: from ( path_str) ) {
702694 Ok ( shortened) => {
0 commit comments