diff --git a/clap_complete/src/dynamic/completer.rs b/clap_complete/src/dynamic/completer.rs index e2f4a77fb0d7..01d18d982421 100644 --- a/clap_complete/src/dynamic/completer.rs +++ b/clap_complete/src/dynamic/completer.rs @@ -1,3 +1,4 @@ +use core::num; use std::ffi::OsStr; use std::ffi::OsString; @@ -71,8 +72,37 @@ pub fn complete( } if is_escaped { - pos_index += 1; - state = ParseState::Pos(pos_index); + match state { + ParseState::ValueDone => { + // Begin parsing a new positional argument after `--`. + let num_args = parse_positional(current_cmd, pos_index); + match num_args { + Some(num_args) => { + state = ParseState::Pos(1); + if num_args <= 1 { + pos_index += 1; + } + } + None => { + state = ParseState::ValueDone; + pos_index += 1; + } + } + } + ParseState::Pos(num_arg) => { + // HACK: Assuming `num_args` is fixed. + let num_args = parse_positional(current_cmd, pos_index).unwrap_or(1); + if num_arg + 1 < num_args { + state = ParseState::Pos(num_arg + 1); + } else { + state = ParseState::Pos(1); + pos_index += 1; + } + } + ParseState::Opt(..) => { + unreachable!("reason it won't be hit") + } + } } else if arg.is_escape() { is_escaped = true; state = ParseState::ValueDone; @@ -124,9 +154,32 @@ pub fn complete( } } else { match state { - ParseState::ValueDone | ParseState::Pos(_) => { - pos_index += 1; - state = ParseState::ValueDone; + ParseState::ValueDone => { + let num_args = parse_positional(current_cmd, pos_index); + match num_args { + Some(num_args) => { + if num_args > 1 { + state = ParseState::Pos(1); + } else { + state = ParseState::ValueDone; + pos_index += 1; + } + } + None => { + state = ParseState::ValueDone; + pos_index += 1; + } + } + } + ParseState::Pos(num_arg) => { + // HACK: Assuming `num_args` is fixed. + let num_args = parse_positional(current_cmd, pos_index).unwrap_or(1); + if num_arg + 1 < num_args { + state = ParseState::Pos(num_arg + 1); + } else { + state = ParseState::ValueDone; + pos_index += 1; + } } ParseState::Opt((ref opt, count)) => match opt.get_num_args() { Some(range) => { @@ -156,7 +209,7 @@ enum ParseState { /// Parsing a value done, there is no state to record. ValueDone, - /// Parsing a positional argument after `--` + /// Parsing a positional argument after `--`. Pos(takes_num_args) Pos(usize), /// Parsing a optional flag argument @@ -603,6 +656,13 @@ fn parse_shortflags<'s>( (leading_flags, takes_value_opt.cloned(), short) } +fn parse_positional(cmd: &clap::Command, pos_index: usize) -> Option { + let pos_arg = cmd + .get_positionals() + .find(|p| p.get_index() == Some(pos_index)); + pos_arg.and_then(|a| a.get_num_args().and_then(|r| Some(r.max_values()))) +} + /// A completion candidate definition /// /// This makes it easier to add more fields to completion candidate, diff --git a/clap_complete/tests/testsuite/dynamic.rs b/clap_complete/tests/testsuite/dynamic.rs index 8d51b94d4424..fce104808f91 100644 --- a/clap_complete/tests/testsuite/dynamic.rs +++ b/clap_complete/tests/testsuite/dynamic.rs @@ -609,20 +609,18 @@ fn suggest_multi_positional() { assert_data_eq!( complete!(cmd, "pos_a [TAB]"), snapbox::str![ - "--format ---help\tPrint help --F --h\tPrint help" + "pos_a +pos_b +pos_c" ] ); assert_data_eq!( complete!(cmd, "pos_a pos_b [TAB]"), snapbox::str![ - "--format ---help\tPrint help --F --h\tPrint help" + "pos_a +pos_b +pos_c" ] ); @@ -642,10 +640,9 @@ pos_c" assert_data_eq!( complete!(cmd, "--format json pos_a [TAB]"), snapbox::str![ - "--format ---help\tPrint help --F --h\tPrint help" + "pos_a +pos_b +pos_c" ] ); @@ -661,12 +658,20 @@ pos_c" assert_data_eq!( complete!(cmd, "--format json -- pos_a [TAB]"), - snapbox::str![""] + snapbox::str![ + "pos_a +pos_b +pos_c" + ] ); assert_data_eq!( complete!(cmd, "--format json -- pos_a pos_b [TAB]"), - snapbox::str![""] + snapbox::str![ + "pos_a +pos_b +pos_c" + ] ); assert_data_eq!(