Skip to content

Commit

Permalink
Merge pull request #5576 from shannmu/option_value_new
Browse files Browse the repository at this point in the history
feat(clap_complete): Support `-fbar` and `-f=bar` in native completions
  • Loading branch information
epage authored Jul 25, 2024
2 parents 4a00677 + 36c849b commit afecaa4
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 39 deletions.
139 changes: 100 additions & 39 deletions clap_complete/src/dynamic/completer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,47 +109,17 @@ pub fn complete(
} else {
state = ParseState::ValueDone;
}
} else if let Some(mut short) = arg.to_short() {
let mut takes_value = false;
loop {
if let Some(Ok(opt)) = short.next_flag() {
let opt = current_cmd.get_arguments().find(|a| {
let shorts = a.get_short_and_visible_aliases();
let is_find = shorts.map(|v| {
let mut iter = v.into_iter();
let c = iter.find(|c| *c == opt);
c.is_some()
});
is_find.unwrap_or(false)
});

state = match opt.map(|o| o.get_action()) {
Some(clap::ArgAction::Set) | Some(clap::ArgAction::Append) => {
takes_value = true;
if short.next_value_os().is_some() {
ParseState::ValueDone
} else {
ParseState::Opt(opt.unwrap().clone())
}
}
Some(clap::ArgAction::SetTrue) | Some(clap::ArgAction::SetFalse) => {
ParseState::ValueDone
}
Some(clap::ArgAction::Count) => ParseState::ValueDone,
Some(clap::ArgAction::Version) => ParseState::ValueDone,
Some(clap::ArgAction::Help)
| Some(clap::ArgAction::HelpShort)
| Some(clap::ArgAction::HelpLong) => ParseState::ValueDone,
} else if let Some(short) = arg.to_short() {
let (_, takes_value_opt, mut short) = parse_shortflags(current_cmd, short);
match takes_value_opt {
Some(opt) => {
state = match short.next_value_os() {
Some(_) => ParseState::ValueDone,
None => ParseState::ValueDone,
None => ParseState::Opt(opt.clone()),
};

if takes_value {
break;
}
} else {
}
None => {
state = ParseState::ValueDone;
break;
}
}
} else {
Expand Down Expand Up @@ -242,7 +212,7 @@ fn complete_arg(
completions.extend(hidden_longs_aliases(cmd));
}

if arg.is_empty() || arg.is_stdio() || arg.is_short() {
if arg.is_empty() || arg.is_stdio() {
let dash_or_arg = if arg.is_empty() {
"-".into()
} else {
Expand All @@ -263,6 +233,50 @@ fn complete_arg(
.visible(true)
}),
);
} else if let Some(short) = arg.to_short() {
if !short.is_negative_number() {
// Find the first takes_value option.
let (leading_flags, takes_value_opt, mut short) = parse_shortflags(&cmd, short);

// Clone `short` to `peek_short` to peek whether the next flag is a `=`.
if let Some(opt) = takes_value_opt {
let mut peek_short = short.clone();
let has_equal = if let Some(Ok('=')) = peek_short.next_flag() {
short.next_flag();
true
} else {
false
};

let value = short.next_value_os().unwrap_or(OsStr::new(""));
completions.extend(
complete_arg_value(value.to_str().ok_or(value), &opt, current_dir)
.into_iter()
.map(|comp| {
CompletionCandidate::new(format!(
"-{}{}{}",
leading_flags.to_string_lossy(),
if has_equal { "=" } else { "" },
comp.get_content().to_string_lossy()
))
.help(comp.get_help().cloned())
.visible(comp.is_visible())
}),
);
} else {
completions.extend(shorts_and_visible_aliases(cmd).into_iter().map(
|comp| {
CompletionCandidate::new(format!(
"-{}{}",
leading_flags.to_string_lossy(),
comp.get_content().to_string_lossy()
))
.help(comp.get_help().cloned())
.visible(comp.is_visible())
},
));
}
}
}

if let Some(positional) = cmd
Expand Down Expand Up @@ -521,6 +535,53 @@ fn subcommands(p: &clap::Command) -> Vec<CompletionCandidate> {
.collect()
}

/// Parse the short flags and find the first takes_value option.
fn parse_shortflags<'s>(
cmd: &clap::Command,
mut short: clap_lex::ShortFlags<'s>,
) -> (OsString, Option<clap::Arg>, clap_lex::ShortFlags<'s>) {
let takes_value_opt;
let mut leading_flags = OsString::new();
// Find the first takes_value option.
loop {
match short.next_flag() {
Some(Ok(opt)) => {
leading_flags.push(opt.to_string());
let opt = cmd.get_arguments().find(|a| {
let shorts = a.get_short_and_visible_aliases();
let is_find = shorts.map(|v| {
let mut iter = v.into_iter();
let c = iter.find(|c| *c == opt);
c.is_some()
});
is_find.unwrap_or(false)
});
match opt.map(|o| o.get_action()) {
Some(clap::ArgAction::Set) | Some(clap::ArgAction::Append) => {
takes_value_opt = opt;
break;
}
Some(clap::ArgAction::SetTrue)
| Some(clap::ArgAction::SetFalse)
| Some(clap::ArgAction::Count)
| Some(clap::ArgAction::Version)
| Some(clap::ArgAction::Help)
| Some(clap::ArgAction::HelpShort)
| Some(clap::ArgAction::HelpLong) => (),
Some(_) => (),
None => (),
}
}
Some(Err(_)) | None => {
takes_value_opt = None;
break;
}
}
}

(leading_flags, takes_value_opt.cloned(), short)
}

/// A completion candidate definition
///
/// This makes it easier to add more fields to completion candidate,
Expand Down
35 changes: 35 additions & 0 deletions clap_complete/tests/testsuite/dynamic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,41 @@ pos_b
pos_c"
]
);

assert_data_eq!(
complete!(cmd, "-ci[TAB]", current_dir = Some(testdir_path)),
snapbox::str![
"-cia_file
-cib_file
-cic_dir/
-cid_dir/"
]
);

assert_data_eq!(
complete!(cmd, "-ci=[TAB]", current_dir = Some(testdir_path)),
snapbox::str![
"-ci=a_file
-ci=b_file
-ci=c_dir/
-ci=d_dir/"
]
);

assert_data_eq!(
complete!(cmd, "-ci=a[TAB]", current_dir = Some(testdir_path)),
snapbox::str!["-ci=a_file"]
);

assert_data_eq!(
complete!(cmd, "-ciF[TAB]", current_dir = Some(testdir_path)),
snapbox::str![""]
);

assert_data_eq!(
complete!(cmd, "-ciF=[TAB]", current_dir = Some(testdir_path)),
snapbox::str![""]
)
}

fn complete(cmd: &mut Command, args: impl AsRef<str>, current_dir: Option<&Path>) -> String {
Expand Down

0 comments on commit afecaa4

Please sign in to comment.