@@ -25,7 +25,7 @@ mod parse {
2525 use nom:: {
2626 branch:: alt,
2727 bytes:: complete:: { escaped_transform, is_not, tag} ,
28- character:: complete:: { char, space1} ,
28+ character:: complete:: { char, space0 , space1} ,
2929 combinator:: { cut, eof, opt, value} ,
3030 error:: { ErrorKind , ParseError } ,
3131 IResult ,
@@ -144,20 +144,32 @@ mod parse {
144144 }
145145
146146 fn parse_last_arg ( input : & str ) -> IResult < & str , ( String , & str ) > {
147- let ( input, _) = space1 ( input) ?;
147+ let ( input, _) = space0 ( input) ?;
148148
149149 let old_input = input;
150150 let ( input, arg) = opt ( parse_unclosed_quote) ( input) ?;
151151
152152 Ok ( ( input, ( arg. unwrap_or_default ( ) , old_input) ) )
153153 }
154154
155+ fn parse_trailing_last_arg ( input : & str ) -> IResult < & str , ( String , & str ) > {
156+ let ( input, _) = space1 ( input) ?;
157+
158+ parse_last_arg ( input)
159+ }
160+
155161 /// Returns a list with the parsed strings and a raw version of the last string to be stripped
156162 /// from the input before completing.
157163 pub fn parse_started_strings ( input : & str ) -> IResult < & str , ( Vec < String > , & str ) > {
158164 let ( input, ( mut args, mut last_arg_raw) ) =
159165 separated_list0_last_raw ( space1, parse_string) ( input) ?;
160- let ( input, end_arg) = opt ( parse_last_arg) ( input) ?;
166+
167+ let ( input, end_arg) = if args. is_empty ( ) {
168+ opt ( parse_last_arg) ( input) ?
169+ } else {
170+ opt ( parse_trailing_last_arg) ( input) ?
171+ } ;
172+
161173 let ( input, _) = eof ( input) ?;
162174
163175 if let Some ( ( arg, end_arg_raw) ) = end_arg {
@@ -171,6 +183,45 @@ mod parse {
171183
172184 Ok ( ( input, ( args, last_arg_raw) ) )
173185 }
186+
187+ #[ cfg( test) ]
188+ mod tests {
189+ use super :: * ;
190+
191+ #[ test]
192+ fn parse_strings ( ) {
193+ let text = "some normal args" ;
194+ let parsed = parse_started_strings ( text) . unwrap ( ) ;
195+ assert_eq ! ( ( "" , ( vec![ "some" . into( ) , "normal" . into( ) , "args" . into( ) ] , "args" ) ) , parsed) ;
196+
197+ let text = "started " ;
198+ let parsed = parse_started_strings ( text) . unwrap ( ) ;
199+ assert_eq ! ( ( "" , ( vec![ "started" . into( ) , "" . into( ) ] , "" ) ) , parsed) ;
200+
201+ let text = "args \" with quotes\" " ;
202+ let parsed = parse_started_strings ( text) . unwrap ( ) ;
203+ assert_eq ! (
204+ ( "" , ( vec![ "args" . into( ) , "with quotes" . into( ) ] , "\" with quotes\" " ) ) ,
205+ parsed
206+ ) ;
207+
208+ let text = "and \" started " ;
209+ let parsed = parse_started_strings ( text) . unwrap ( ) ;
210+ assert_eq ! ( ( "" , ( vec![ "and" . into( ) , "started " . into( ) ] , "\" started " ) ) , parsed) ;
211+
212+ let text = "\" only started " ;
213+ let parsed = parse_started_strings ( text) . unwrap ( ) ;
214+ assert_eq ! ( ( "" , ( vec![ "only started " . into( ) ] , "\" only started " ) ) , parsed) ;
215+
216+ let text = " " ;
217+ let parsed = parse_started_strings ( text) . unwrap ( ) ;
218+ assert_eq ! ( ( "" , ( vec![ "" . into( ) ] , "" ) ) , parsed) ;
219+
220+ let text = "escaped\\ spaces here" ;
221+ let parsed = parse_started_strings ( text) . unwrap ( ) ;
222+ assert_eq ! ( ( "" , ( vec![ "escaped spaces" . into( ) , "here" . into( ) ] , "here" ) ) , parsed) ;
223+ }
224+ }
174225}
175226
176227/// Tab completion for user IDs.
@@ -405,7 +456,7 @@ fn complete_cmdarg(
405456 let orig_cursor = cursor. clone ( ) ;
406457 * cursor = new_cursor;
407458
408- let completions = match cmd. name . as_str ( ) {
459+ let mut completions = match cmd. name . as_str ( ) {
409460 "invite" => complete_iamb_invite ( args, store) ,
410461
411462 "keys" => complete_iamb_keys ( args, input, orig_cursor, cursor) ,
@@ -443,8 +494,14 @@ fn complete_cmdarg(
443494 "space" => complete_iamb_space ( args, store) ,
444495
445496 "upload" | "download" | "open" => {
446- * cursor = orig_cursor;
447- complete_path ( input, cursor)
497+ if input. get_char_at_cursor ( cursor) == Some ( '"' ) {
498+ // Use the escaped instead of the qouted filename.
499+ let mut args = args;
500+ vec ! [ args. pop( ) . unwrap( ) ]
501+ } else {
502+ * cursor = orig_cursor;
503+ return complete_path ( input, cursor) ;
504+ }
448505 } ,
449506
450507 "logout" => complete_iamb_logout ( args, store) ,
@@ -458,11 +515,22 @@ fn complete_cmdarg(
458515 #[ cfg( test) ]
459516 panic ! ( "trying to complete unknown subcommand `{}`" , _cmd) ;
460517
518+ #[ cfg( not( test) ) ]
461519 vec ! [ ]
462520 } ,
463521 } ;
464522
465- // TODO: escape stuff including with paths
523+ completions. iter_mut ( ) . for_each ( |completion| {
524+ if completion. contains ( [ '\\' , ' ' , '#' , '%' , '"' , '|' ] ) {
525+ * completion = completion
526+ . replace ( '\\' , "\\ \\ " )
527+ . replace ( ' ' , "\\ " )
528+ . replace ( '#' , "\\ #" )
529+ . replace ( '%' , "\\ %" )
530+ . replace ( '"' , "\\ \" " )
531+ . replace ( '|' , "\\ |" ) ;
532+ }
533+ } ) ;
466534
467535 completions
468536}
0 commit comments