@@ -4,34 +4,19 @@ use crate::{
44 completions:: { Completer , CompletionOptions } ,
55 SuggestionKind ,
66} ;
7- use nu_parser:: FlatShape ;
87use nu_protocol:: {
9- engine:: { CachedFile , Stack , StateWorkingSet } ,
8+ engine:: { Stack , StateWorkingSet } ,
109 Span ,
1110} ;
1211use reedline:: Suggestion ;
1312
1413use super :: { completion_options:: NuMatcher , SemanticSuggestion } ;
1514
1615pub struct CommandCompletion {
17- flattened : Vec < ( Span , FlatShape ) > ,
18- flat_shape : FlatShape ,
19- force_completion_after_space : bool ,
16+ pub find_externals : bool ,
2017}
2118
2219impl CommandCompletion {
23- pub fn new (
24- flattened : Vec < ( Span , FlatShape ) > ,
25- flat_shape : FlatShape ,
26- force_completion_after_space : bool ,
27- ) -> Self {
28- Self {
29- flattened,
30- flat_shape,
31- force_completion_after_space,
32- }
33- }
34-
3520 fn external_command_completion (
3621 & self ,
3722 working_set : & StateWorkingSet ,
@@ -71,23 +56,24 @@ impl CommandCompletion {
7156 if suggs. contains_key ( & value) {
7257 continue ;
7358 }
74- if matcher . matches ( & name ) && is_executable:: is_executable ( item. path ( ) ) {
59+ if is_executable:: is_executable ( item. path ( ) ) {
7560 // If there's an internal command with the same name, adds ^cmd to the
7661 // matcher so that both the internal and external command are included
77- matcher. add ( & name, value. clone ( ) ) ;
78- suggs. insert (
79- value. clone ( ) ,
80- SemanticSuggestion {
81- suggestion : Suggestion {
82- value,
83- span : sugg_span,
84- append_whitespace : true ,
85- ..Default :: default ( )
62+ if matcher. add ( & name, value. clone ( ) ) {
63+ suggs. insert (
64+ value. clone ( ) ,
65+ SemanticSuggestion {
66+ suggestion : Suggestion {
67+ value,
68+ span : sugg_span,
69+ append_whitespace : true ,
70+ ..Default :: default ( )
71+ } ,
72+ // TODO: is there a way to create a test?
73+ kind : None ,
8674 } ,
87- // TODO: is there a way to create a test?
88- kind : None ,
89- } ,
90- ) ;
75+ ) ;
76+ }
9177 }
9278 }
9379 }
@@ -97,13 +83,17 @@ impl CommandCompletion {
9783
9884 suggs
9985 }
86+ }
10087
101- fn complete_commands (
102- & self ,
88+ impl Completer for CommandCompletion {
89+ fn fetch (
90+ & mut self ,
10391 working_set : & StateWorkingSet ,
92+ _stack : & Stack ,
93+ _prefix : & [ u8 ] ,
10494 span : Span ,
10595 offset : usize ,
106- find_externals : bool ,
96+ _pos : usize ,
10797 options : & CompletionOptions ,
10898 ) -> Vec < SemanticSuggestion > {
10999 let partial = working_set. get_span_contents ( span) ;
@@ -136,7 +126,7 @@ impl CommandCompletion {
136126 ) ;
137127 }
138128
139- let mut external_suggs = if find_externals {
129+ let external_suggs = if self . find_externals {
140130 self . external_command_completion (
141131 working_set,
142132 sugg_span,
@@ -147,191 +137,10 @@ impl CommandCompletion {
147137 HashMap :: new ( )
148138 } ;
149139
150- let mut res = Vec :: new ( ) ;
151- for cmd_name in matcher. results ( ) {
152- if let Some ( sugg) = internal_suggs
153- . remove ( & cmd_name)
154- . or_else ( || external_suggs. remove ( & cmd_name) )
155- {
156- res. push ( sugg) ;
157- }
158- }
159- res
160- }
161- }
162-
163- impl Completer for CommandCompletion {
164- fn fetch (
165- & mut self ,
166- working_set : & StateWorkingSet ,
167- _stack : & Stack ,
168- _prefix : & [ u8 ] ,
169- span : Span ,
170- offset : usize ,
171- pos : usize ,
172- options : & CompletionOptions ,
173- ) -> Vec < SemanticSuggestion > {
174- let last = self
175- . flattened
176- . iter ( )
177- . rev ( )
178- . skip_while ( |x| x. 0 . end > pos)
179- . take_while ( |x| {
180- matches ! (
181- x. 1 ,
182- FlatShape :: InternalCall ( _)
183- | FlatShape :: External
184- | FlatShape :: ExternalArg
185- | FlatShape :: Literal
186- | FlatShape :: String
187- )
188- } )
189- . last ( ) ;
190-
191- // The last item here would be the earliest shape that could possible by part of this subcommand
192- let subcommands = if let Some ( last) = last {
193- self . complete_commands (
194- working_set,
195- Span :: new ( last. 0 . start , pos) ,
196- offset,
197- false ,
198- options,
199- )
200- } else {
201- vec ! [ ]
202- } ;
203-
204- if !subcommands. is_empty ( ) {
205- return subcommands;
206- }
207-
208- let config = working_set. get_config ( ) ;
209- if matches ! ( self . flat_shape, nu_parser:: FlatShape :: External )
210- || matches ! ( self . flat_shape, nu_parser:: FlatShape :: InternalCall ( _) )
211- || ( ( span. end - span. start ) == 0 )
212- || is_passthrough_command ( working_set. delta . get_file_contents ( ) )
213- {
214- // we're in a gap or at a command
215- if working_set. get_span_contents ( span) . is_empty ( ) && !self . force_completion_after_space
216- {
217- return vec ! [ ] ;
218- }
219- self . complete_commands (
220- working_set,
221- span,
222- offset,
223- config. completions . external . enable ,
224- options,
225- )
226- } else {
227- vec ! [ ]
228- }
229- }
230- }
231-
232- pub fn find_non_whitespace_index ( contents : & [ u8 ] , start : usize ) -> usize {
233- match contents. get ( start..) {
234- Some ( contents) => {
235- contents
236- . iter ( )
237- . take_while ( |x| x. is_ascii_whitespace ( ) )
238- . count ( )
239- + start
240- }
241- None => start,
242- }
243- }
244-
245- pub fn is_passthrough_command ( working_set_file_contents : & [ CachedFile ] ) -> bool {
246- for cached_file in working_set_file_contents {
247- let contents = & cached_file. content ;
248- let last_pipe_pos_rev = contents. iter ( ) . rev ( ) . position ( |x| x == & b'|' ) ;
249- let last_pipe_pos = last_pipe_pos_rev. map ( |x| contents. len ( ) - x) . unwrap_or ( 0 ) ;
250-
251- let cur_pos = find_non_whitespace_index ( contents, last_pipe_pos) ;
252-
253- let result = match contents. get ( cur_pos..) {
254- Some ( contents) => contents. starts_with ( b"sudo " ) || contents. starts_with ( b"doas " ) ,
255- None => false ,
256- } ;
257- if result {
258- return true ;
259- }
260- }
261- false
262- }
263-
264- #[ cfg( test) ]
265- mod command_completions_tests {
266- use super :: * ;
267- use nu_protocol:: engine:: EngineState ;
268- use std:: sync:: Arc ;
269-
270- #[ test]
271- fn test_find_non_whitespace_index ( ) {
272- let commands = [
273- ( " hello" , 4 ) ,
274- ( "sudo " , 0 ) ,
275- ( " sudo " , 2 ) ,
276- ( " sudo " , 2 ) ,
277- ( " hello " , 1 ) ,
278- ( " hello " , 3 ) ,
279- ( " hello | sudo " , 4 ) ,
280- ( " sudo|sudo" , 5 ) ,
281- ( "sudo | sudo " , 0 ) ,
282- ( " hello sud" , 1 ) ,
283- ] ;
284- for ( idx, ele) in commands. iter ( ) . enumerate ( ) {
285- let index = find_non_whitespace_index ( ele. 0 . as_bytes ( ) , 0 ) ;
286- assert_eq ! ( index, ele. 1 , "Failed on index {}" , idx) ;
287- }
288- }
289-
290- #[ test]
291- fn test_is_last_command_passthrough ( ) {
292- let commands = [
293- ( " hello" , false ) ,
294- ( " sudo " , true ) ,
295- ( "sudo " , true ) ,
296- ( " hello" , false ) ,
297- ( " sudo" , false ) ,
298- ( " sudo " , true ) ,
299- ( " sudo " , true ) ,
300- ( " sudo " , true ) ,
301- ( " hello " , false ) ,
302- ( " hello | sudo " , true ) ,
303- ( " sudo|sudo" , false ) ,
304- ( "sudo | sudo " , true ) ,
305- ( " hello sud" , false ) ,
306- ( " sudo | sud " , false ) ,
307- ( " sudo|sudo " , true ) ,
308- ( " sudo | sudo ls | sudo " , true ) ,
309- ] ;
310- for ( idx, ele) in commands. iter ( ) . enumerate ( ) {
311- let input = ele. 0 . as_bytes ( ) ;
312-
313- let mut engine_state = EngineState :: new ( ) ;
314- engine_state. add_file ( "test.nu" . into ( ) , Arc :: new ( [ ] ) ) ;
315-
316- let delta = {
317- let mut working_set = StateWorkingSet :: new ( & engine_state) ;
318- let _ = working_set. add_file ( "child.nu" . into ( ) , input) ;
319- working_set. render ( )
320- } ;
321-
322- let result = engine_state. merge_delta ( delta) ;
323- assert ! (
324- result. is_ok( ) ,
325- "Merge delta has failed: {}" ,
326- result. err( ) . unwrap( )
327- ) ;
328-
329- let is_passthrough_command = is_passthrough_command ( engine_state. get_file_contents ( ) ) ;
330- assert_eq ! (
331- is_passthrough_command, ele. 1 ,
332- "index for '{}': {}" ,
333- ele. 0 , idx
334- ) ;
335- }
140+ internal_suggs
141+ . values ( )
142+ . chain ( external_suggs. values ( ) )
143+ . cloned ( )
144+ . collect ( )
336145 }
337146}
0 commit comments