@@ -143,6 +143,13 @@ impl SuggestionDescription {
143143 }
144144}
145145
146+ #[ derive( Debug , Clone , Copy , PartialEq , Eq , serde:: Serialize , serde:: Deserialize ) ]
147+ pub enum SuggestionType {
148+ Folder ,
149+ RegularFile ,
150+ Misc ,
151+ }
152+
146153#[ derive( Debug , Clone , PartialEq , Eq , serde:: Serialize , serde:: Deserialize ) ]
147154pub struct ProcessedSuggestion {
148155 pub s : String ,
@@ -152,6 +159,8 @@ pub struct ProcessedSuggestion {
152159 pub style : Option < Style > ,
153160 /// Description to display as a visual suffix (not inserted).
154161 pub description : SuggestionDescription ,
162+ /// The type of suggestion (Folder, RegularFile, or Misc).
163+ pub sug_type : SuggestionType ,
155164}
156165
157166#[ derive( Debug , Clone , PartialEq , Eq ) ]
@@ -599,6 +608,7 @@ mod description_tests {
599608 std:: time:: Duration :: from_millis ( 0 ) ,
600609 true , // auto_started
601610 crate :: settings:: SuggestionSortOrder :: default ( ) ,
611+ crate :: settings:: FuzzyMode :: default ( ) ,
602612 ) ;
603613
604614 // Grid width/rows logic, let's call into_list with max_rows = 2
@@ -669,6 +679,7 @@ mod description_tests {
669679 std:: time:: Duration :: from_millis ( 0 ) ,
670680 true , // auto_started
671681 crate :: settings:: SuggestionSortOrder :: default ( ) ,
682+ crate :: settings:: FuzzyMode :: default ( ) ,
672683 ) ;
673684
674685 // Initially, in auto_started, no selection is active.
@@ -706,6 +717,7 @@ mod description_tests {
706717 std:: time:: Duration :: from_millis ( 0 ) ,
707718 true , // auto_started
708719 crate :: settings:: SuggestionSortOrder :: default ( ) ,
720+ crate :: settings:: FuzzyMode :: default ( ) ,
709721 ) ;
710722 assert_eq ! ( active_no_sel. selected_coord, None ) ;
711723 active_no_sel. update_word_under_cursor ( & SubString :: new ( "cl" , "cl" ) . unwrap ( ) ) ;
@@ -746,6 +758,7 @@ mod description_tests {
746758 std:: time:: Duration :: from_millis ( 0 ) ,
747759 false , // auto_started
748760 crate :: settings:: SuggestionSortOrder :: Mtime ,
761+ crate :: settings:: FuzzyMode :: default ( ) ,
749762 ) ;
750763
751764 let filtered_names: Vec < String > = active
@@ -783,6 +796,7 @@ mod description_tests {
783796 std:: time:: Duration :: from_millis ( 0 ) ,
784797 false ,
785798 crate :: settings:: SuggestionSortOrder :: Alphabetical ,
799+ crate :: settings:: FuzzyMode :: default ( ) ,
786800 ) ;
787801
788802 let filtered_names_alpha: Vec < String > = active_alpha
@@ -821,6 +835,7 @@ mod description_tests {
821835 std:: time:: Duration :: from_millis ( 0 ) ,
822836 false ,
823837 crate :: settings:: SuggestionSortOrder :: Alphabetical ,
838+ crate :: settings:: FuzzyMode :: default ( ) ,
824839 ) ;
825840
826841 let filtered_names: Vec < String > = active
@@ -879,6 +894,7 @@ mod description_tests {
879894 std:: time:: Duration :: from_millis ( 0 ) ,
880895 false ,
881896 crate :: settings:: SuggestionSortOrder :: Alphabetical ,
897+ crate :: settings:: FuzzyMode :: default ( ) ,
882898 ) ;
883899 assert ! ( !active_boundary. nosort) ;
884900
@@ -904,6 +920,7 @@ mod description_tests {
904920 std:: time:: Duration :: from_millis ( 0 ) ,
905921 false ,
906922 crate :: settings:: SuggestionSortOrder :: Alphabetical ,
923+ crate :: settings:: FuzzyMode :: default ( ) ,
907924 ) ;
908925 assert ! ( active_large. nosort) ;
909926 }
@@ -951,13 +968,68 @@ mod description_tests {
951968 auto_started : false ,
952969 nosort : false ,
953970 sort_order : crate :: settings:: SuggestionSortOrder :: default ( ) ,
971+ fuzzy_mode : crate :: settings:: FuzzyMode :: default ( ) ,
954972 formatted_cache : vec ! [ None , None , None ] ,
955973 max_width_cache : std:: cell:: Cell :: new ( None ) ,
956974 } ;
957975
958976 suggestions. accept_all_filtered_items ( & mut buffer) ;
959977 assert_eq ! ( buffer. buffer( ) , "mycmd foo bar baz " ) ;
960978 }
979+
980+ #[ test]
981+ fn test_fuzzy_mode_no_folders ( ) {
982+ let builder = ActiveSuggestionsBuilder {
983+ processed : vec ! [
984+ ProcessedSuggestion :: new( "foobar" , "" , "" ) . with_type( SuggestionType :: Folder ) ,
985+ ProcessedSuggestion :: new( "foobar" , "" , "" ) . with_type( SuggestionType :: RegularFile ) ,
986+ ProcessedSuggestion :: new( "foobar" , "" , "" ) . with_type( SuggestionType :: Misc ) ,
987+ ] ,
988+ unprocessed : std:: collections:: VecDeque :: new ( ) ,
989+ common_prefix : None ,
990+ auto_accept_if_solo : false ,
991+ insert_common_prefix : false ,
992+ comp_type : crate :: tab_completion_context:: CompType :: FirstWord ,
993+ nosort : false ,
994+ compspec_was_useful : Some ( true ) ,
995+ should_run_flycomp : false ,
996+ } ;
997+
998+ // Case 1: pattern "oo" - should only match RegularFile and Misc, but NOT Folder (as "oo" is not a prefix of "foobar").
999+ let active = ActiveSuggestions :: new (
1000+ builder. clone ( ) ,
1001+ SubString :: new ( "oo" , "oo" ) . unwrap ( ) ,
1002+ std:: time:: Duration :: from_millis ( 0 ) ,
1003+ false ,
1004+ crate :: settings:: SuggestionSortOrder :: Alphabetical ,
1005+ crate :: settings:: FuzzyMode :: FolderPrefixes ,
1006+ ) ;
1007+
1008+ let filtered: Vec < usize > = active
1009+ . filtered_suggestions
1010+ . iter ( )
1011+ . map ( |f| f. suggestion_idx )
1012+ . collect ( ) ;
1013+ // Index 0 is Folder (no match), index 1 is RegularFile (match), index 2 is Misc (match)
1014+ assert_eq ! ( filtered, vec![ 1 , 2 ] ) ;
1015+
1016+ // Case 2: pattern "foo" - prefix matches Folder, and fuzzy matches all.
1017+ let active2 = ActiveSuggestions :: new (
1018+ builder,
1019+ SubString :: new ( "foo" , "foo" ) . unwrap ( ) ,
1020+ std:: time:: Duration :: from_millis ( 0 ) ,
1021+ false ,
1022+ crate :: settings:: SuggestionSortOrder :: Alphabetical ,
1023+ crate :: settings:: FuzzyMode :: FolderPrefixes ,
1024+ ) ;
1025+ let filtered2: Vec < usize > = active2
1026+ . filtered_suggestions
1027+ . iter ( )
1028+ . map ( |f| f. suggestion_idx )
1029+ . collect ( ) ;
1030+ // Since "foo" is a prefix of "foobar", it matches Folder, RegularFile, and Misc.
1031+ assert_eq ! ( filtered2. len( ) , 3 ) ;
1032+ }
9611033}
9621034
9631035impl ProcessedSuggestion {
@@ -979,9 +1051,16 @@ impl ProcessedSuggestion {
9791051 suffix : suffix. into ( ) ,
9801052 style : None ,
9811053 description : SuggestionDescription :: Static ( vec ! [ ] ) ,
1054+ sug_type : SuggestionType :: Misc ,
9821055 }
9831056 }
9841057
1058+ /// Set the type on this suggestion.
1059+ pub fn with_type ( mut self , suggestion_type : SuggestionType ) -> Self {
1060+ self . sug_type = suggestion_type;
1061+ self
1062+ }
1063+
9851064 /// Set the description on this suggestion.
9861065 pub fn with_description ( mut self , description : SuggestionDescription ) -> Self {
9871066 self . description = description;
@@ -1191,9 +1270,18 @@ impl UnprocessedSuggestion {
11911270 SuggestionDescription :: Static ( vec ! [ ] )
11921271 } ;
11931272
1273+ let suggestion_type = if path_to_use. as_ref ( ) . is_some_and ( |p| p. is_dir ( ) ) {
1274+ SuggestionType :: Folder
1275+ } else if comp_result_flags. filename_completion_desired || path_to_use. is_some ( ) {
1276+ SuggestionType :: RegularFile
1277+ } else {
1278+ SuggestionType :: Misc
1279+ } ;
1280+
11941281 let suffix_str = suffix_char. map ( |f| f. to_string ( ) ) . unwrap_or_default ( ) ;
11951282 let suggestion = ProcessedSuggestion :: new ( quoted_no_prefix, prefix, & suffix_str)
1196- . with_description ( description) ;
1283+ . with_description ( description)
1284+ . with_type ( suggestion_type) ;
11971285 match style {
11981286 Some ( s) => suggestion. with_style ( s) ,
11991287 None => suggestion,
@@ -1426,6 +1514,8 @@ pub struct ActiveSuggestions {
14261514 pub nosort : bool ,
14271515 /// How to sort suggestions when fuzzy scores are tied.
14281516 pub sort_order : crate :: settings:: SuggestionSortOrder ,
1517+ /// Controls fuzzy matching behavior for suggestions.
1518+ pub fuzzy_mode : crate :: settings:: FuzzyMode ,
14291519 formatted_cache : Vec < Option < SuggestionFormatted > > ,
14301520 max_width_cache : std:: cell:: Cell < Option < usize > > ,
14311521}
@@ -1437,6 +1527,7 @@ impl ActiveSuggestions {
14371527 load_time : std:: time:: Duration ,
14381528 auto_started : bool ,
14391529 sort_order : crate :: settings:: SuggestionSortOrder ,
1530+ fuzzy_mode : crate :: settings:: FuzzyMode ,
14401531 ) -> Self {
14411532 let ActiveSuggestionsBuilder {
14421533 processed : processed_suggestions,
@@ -1473,6 +1564,7 @@ impl ActiveSuggestions {
14731564 auto_started,
14741565 nosort : nosort || sug_len > crate :: FILENAME_INFERENCE_LIMIT ,
14751566 sort_order,
1567+ fuzzy_mode,
14761568 formatted_cache : vec ! [ ] ,
14771569 max_width_cache : std:: cell:: Cell :: new ( Some ( initial_max_width) ) ,
14781570 } ;
@@ -1944,6 +2036,34 @@ impl ActiveSuggestions {
19442036 . strip_prefix ( & sug. prefix )
19452037 . unwrap_or ( pattern_with_prefix) ;
19462038
2039+ let fuzzy_enabled = match self . fuzzy_mode {
2040+ crate :: settings:: FuzzyMode :: All => true ,
2041+ crate :: settings:: FuzzyMode :: None => false ,
2042+ crate :: settings:: FuzzyMode :: FolderPrefixes => match sug. sug_type {
2043+ crate :: active_suggestions:: SuggestionType :: Folder => false ,
2044+ _ => true ,
2045+ } ,
2046+ } ;
2047+
2048+ if !fuzzy_enabled {
2049+ if pattern. is_empty ( ) {
2050+ return Some ( FilteredItem {
2051+ score : 0 ,
2052+ suggestion_idx : idx,
2053+ matching_indices : Vec :: new ( ) ,
2054+ } ) ;
2055+ }
2056+ if sug. s . to_lowercase ( ) . starts_with ( & pattern. to_lowercase ( ) ) {
2057+ let match_count = pattern. chars ( ) . count ( ) ;
2058+ return Some ( FilteredItem {
2059+ score : 1000 ,
2060+ suggestion_idx : idx,
2061+ matching_indices : ( 0 ..match_count) . collect ( ) ,
2062+ } ) ;
2063+ }
2064+ return None ;
2065+ }
2066+
19472067 // Try the fuzzy matcher first
19482068 if let Some ( ( score, indices) ) = crate :: content_utils:: fuzzy_indices_with_threshold (
19492069 & self . fuzzy_matcher ,
0 commit comments