1- use std:: collections:: HashMap ;
1+ use std:: collections:: HashSet ;
22
33use crate :: {
44 SuggestionKind ,
@@ -26,9 +26,9 @@ impl CommandCompletion {
2626 working_set : & StateWorkingSet ,
2727 sugg_span : reedline:: Span ,
2828 matched_internal : impl Fn ( & str ) -> bool ,
29- matcher : & mut NuMatcher < String > ,
30- ) -> HashMap < String , SemanticSuggestion > {
31- let mut suggs = HashMap :: new ( ) ;
29+ matcher : & mut NuMatcher < SemanticSuggestion > ,
30+ ) {
31+ let mut external_commands = HashSet :: new ( ) ;
3232
3333 let paths = working_set. permanent_state . get_env_var_insensitive ( "path" ) ;
3434
@@ -46,50 +46,43 @@ impl CommandCompletion {
4646 . completions
4747 . external
4848 . max_results
49- <= suggs . len ( ) as i64
49+ <= external_commands . len ( ) as i64
5050 {
5151 break ;
5252 }
5353 let Ok ( name) = item. file_name ( ) . into_string ( ) else {
5454 continue ;
5555 } ;
56+ // If there's an internal command with the same name, adds ^cmd to the
57+ // matcher so that both the internal and external command are included
5658 let value = if matched_internal ( & name) {
5759 format ! ( "^{name}" )
5860 } else {
5961 name. clone ( )
6062 } ;
61- if suggs. contains_key ( & value) {
63+ // If a command of the same name already exists, skip
64+ if external_commands. contains ( & value) {
6265 continue ;
6366 }
6467 // TODO: check name matching before a relative heavy IO involved
6568 // `is_executable` for performance consideration, should avoid
6669 // duplicated `match_aux` call for matched items in the future
67- if matcher. matches ( & name) && is_executable:: is_executable ( item. path ( ) ) {
68- // If there's an internal command with the same name, adds ^cmd to the
69- // matcher so that both the internal and external command are included
70- matcher. add ( & name, value. clone ( ) ) ;
71- suggs. insert (
72- value. clone ( ) ,
73- SemanticSuggestion {
74- suggestion : Suggestion {
75- value,
76- span : sugg_span,
77- append_whitespace : true ,
78- ..Default :: default ( )
79- } ,
80- kind : Some ( SuggestionKind :: Command (
81- CommandType :: External ,
82- None ,
83- ) ) ,
70+ if matcher. matches ( & name) . 0 && is_executable:: is_executable ( item. path ( ) ) {
71+ external_commands. insert ( value. clone ( ) ) ;
72+ matcher. add_semantic_suggestion ( SemanticSuggestion {
73+ suggestion : Suggestion {
74+ value,
75+ span : sugg_span,
76+ append_whitespace : true ,
77+ ..Default :: default ( )
8478 } ,
85- ) ;
79+ kind : Some ( SuggestionKind :: Command ( CommandType :: External , None ) ) ,
80+ } ) ;
8681 }
8782 }
8883 }
8984 }
9085 }
91-
92- suggs
9386 }
9487}
9588
@@ -103,58 +96,42 @@ impl Completer for CommandCompletion {
10396 offset : usize ,
10497 options : & CompletionOptions ,
10598 ) -> Vec < SemanticSuggestion > {
106- let mut matcher = NuMatcher :: new ( prefix, options) ;
99+ let mut matcher = NuMatcher :: new ( prefix. as_ref ( ) , options) ;
107100
108101 let sugg_span = reedline:: Span :: new ( span. start - offset, span. end - offset) ;
109102
110- let mut internal_suggs = HashMap :: new ( ) ;
103+ let mut internal_commands = HashSet :: new ( ) ;
111104 if self . internals {
112- let filtered_commands = working_set. find_commands_by_predicate (
113- |name| {
114- let name = String :: from_utf8_lossy ( name) ;
115- matcher. add ( & name, name. to_string ( ) )
116- } ,
117- true ,
118- ) ;
105+ let filtered_commands =
106+ working_set. find_commands_by_predicate ( |name| matcher. matches ( name) . 0 , true ) ;
119107 for ( decl_id, name, description, typ) in filtered_commands {
120- let name = String :: from_utf8_lossy ( & name) ;
121- internal_suggs. insert (
122- name. to_string ( ) ,
123- SemanticSuggestion {
124- suggestion : Suggestion {
125- value : name. to_string ( ) ,
126- description,
127- span : sugg_span,
128- append_whitespace : true ,
129- ..Suggestion :: default ( )
130- } ,
131- kind : Some ( SuggestionKind :: Command ( typ, Some ( decl_id) ) ) ,
108+ matcher. add_semantic_suggestion ( SemanticSuggestion {
109+ suggestion : Suggestion {
110+ value : name. to_string ( ) ,
111+ description,
112+ span : sugg_span,
113+ append_whitespace : true ,
114+ ..Suggestion :: default ( )
132115 } ,
133- ) ;
116+ kind : Some ( SuggestionKind :: Command ( typ, Some ( decl_id) ) ) ,
117+ } ) ;
118+ internal_commands. insert ( name) ;
134119 }
135120 }
136121
137- let mut external_suggs = if self . externals {
122+ let mut ans = matcher. results ( ) ;
123+ if self . externals {
124+ // Create another matcher so that externals always come after internals
125+ let mut external_matcher =
126+ NuMatcher :: new_with_customized_trimming ( prefix. as_ref ( ) , options, & [ '^' ] ) ;
138127 self . external_command_completion (
139128 working_set,
140129 sugg_span,
141- |name| internal_suggs. contains_key ( name) ,
142- & mut matcher,
143- )
144- } else {
145- HashMap :: new ( )
146- } ;
147-
148- let mut res = Vec :: new ( ) ;
149- for ( cmd_name, indices) in matcher. results ( ) {
150- if let Some ( mut sugg) = internal_suggs
151- . remove ( & cmd_name)
152- . or_else ( || external_suggs. remove ( & cmd_name) )
153- {
154- sugg. suggestion . match_indices = indices;
155- res. push ( sugg) ;
156- }
130+ |name| internal_commands. contains ( name) ,
131+ & mut external_matcher,
132+ ) ;
133+ ans. extend ( external_matcher. results ( ) ) ;
157134 }
158- res
135+ ans
159136 }
160137}
0 commit comments