#![cfg(feature = "unstable-dynamic")] use std::fs; use std::path::Path; use clap::{builder::PossibleValue, Command}; use clap_complete::engine::{ ArgValueCandidates, ArgValueCompleter, CompletionCandidate, PathCompleter, }; use snapbox::assert_data_eq; macro_rules! complete { ($cmd:expr, $input:expr$(, current_dir = $current_dir:expr)? $(,)?) => { { #[allow(unused)] let current_dir: Option<&Path> = None; $(let current_dir = $current_dir;)? complete(&mut $cmd, $input, current_dir) } } } #[test] fn suggest_subcommand_subset() { let mut cmd = Command::new("exhaustive") .subcommand(Command::new("hello-world")) .subcommand(Command::new("hello-moon")) .subcommand(Command::new("goodbye-world")); assert_data_eq!( complete!(cmd, "he"), snapbox::str![[r#" hello-moon hello-world help Print this message or the help of the given subcommand(s) "#]], ); } #[test] fn suggest_hidden_long_flags() { let mut cmd = Command::new("exhaustive") .arg(clap::Arg::new("hello-world-visible").long("hello-world-visible")) .arg( clap::Arg::new("hello-world-hidden") .long("hello-world-hidden") .hide(true), ); assert_data_eq!( complete!(cmd, "--hello-world"), snapbox::str!["--hello-world-visible"] ); assert_data_eq!( complete!(cmd, "--hello-world-h"), snapbox::str!["--hello-world-hidden"] ); } #[test] fn suggest_hidden_subcommand_and_aliases() { let mut cmd = Command::new("exhaustive") .subcommand( Command::new("test_visible") .visible_alias("test_visible-alias_visible") .alias("test_visible-alias_hidden"), ) .subcommand( Command::new("test_hidden") .visible_alias("test_hidden-alias_visible") .alias("test_hidden-alias_hidden") .hide(true), ); assert_data_eq!( complete!(cmd, "test"), snapbox::str![[r#" test_visible test_visible-alias_visible "#]] ); assert_data_eq!( complete!(cmd, "test_h"), snapbox::str![[r#" test_hidden test_hidden-alias_hidden test_hidden-alias_visible "#]] ); assert_data_eq!( complete!(cmd, "test_hidden-alias_h"), snapbox::str!["test_hidden-alias_hidden"] ); } #[test] fn suggest_subcommand_aliases() { let mut cmd = Command::new("exhaustive") .subcommand( Command::new("hello-world") .visible_alias("hello-world-foo") .alias("hidden-world"), ) .subcommand( Command::new("hello-moon") .visible_alias("hello-moon-foo") .alias("hidden-moon"), ) .subcommand( Command::new("goodbye-world") .visible_alias("goodbye-world-foo") .alias("hidden-goodbye"), ); assert_data_eq!( complete!(cmd, "hello"), snapbox::str![[r#" hello-moon hello-moon-foo hello-world hello-world-foo "#]], ); } #[test] fn suggest_hidden_possible_value() { let mut cmd = Command::new("exhaustive").arg( clap::Arg::new("possible_value").long("test").value_parser([ PossibleValue::new("test-visible").help("Say hello to the world"), PossibleValue::new("test-hidden") .help("Say hello to the moon") .hide(true), ]), ); assert_data_eq!( complete!(cmd, "--test=test"), snapbox::str!["--test=test-visible Say hello to the world"] ); assert_data_eq!( complete!(cmd, "--test=test-h"), snapbox::str!["--test=test-hidden Say hello to the moon"] ); } #[test] fn suggest_hidden_long_flag_aliases() { let mut cmd = Command::new("exhaustive") .arg( clap::Arg::new("test_visible") .long("test_visible") .visible_alias("test_visible-alias_visible") .alias("test_visible-alias_hidden"), ) .arg( clap::Arg::new("test_hidden") .long("test_hidden") .visible_alias("test_hidden-alias_visible") .alias("test_hidden-alias_hidden") .hide(true), ); assert_data_eq!( complete!(cmd, "--test"), snapbox::str![[r#" --test_visible --test_visible-alias_visible "#]] ); assert_data_eq!( complete!(cmd, "--test_h"), snapbox::str![[r#" --test_hidden --test_hidden-alias_visible --test_hidden-alias_hidden "#]] ); assert_data_eq!( complete!(cmd, "--test_visible-alias_h"), snapbox::str!["--test_visible-alias_hidden"] ); assert_data_eq!( complete!(cmd, "--test_hidden-alias_h"), snapbox::str!["--test_hidden-alias_hidden"] ); } #[test] fn suggest_long_flag_subset() { let mut cmd = Command::new("exhaustive") .arg( clap::Arg::new("hello-world") .long("hello-world") .action(clap::ArgAction::Count), ) .arg( clap::Arg::new("hello-moon") .long("hello-moon") .action(clap::ArgAction::Count), ) .arg( clap::Arg::new("goodbye-world") .long("goodbye-world") .action(clap::ArgAction::Count), ); assert_data_eq!( complete!(cmd, "--he"), snapbox::str![[r#" --hello-world --hello-moon --help Print help "#]], ); } #[test] fn suggest_possible_value_subset() { let name = "exhaustive"; let mut cmd = Command::new(name).arg(clap::Arg::new("hello-world").value_parser([ PossibleValue::new("hello-world").help("Say hello to the world"), "hello-moon".into(), "goodbye-world".into(), ])); assert_data_eq!( complete!(cmd, "hello"), snapbox::str![[r#" hello-world Say hello to the world hello-moon "#]], ); } #[test] fn suggest_additional_short_flags() { let mut cmd = Command::new("exhaustive") .arg( clap::Arg::new("a") .short('a') .action(clap::ArgAction::Count), ) .arg( clap::Arg::new("b") .short('b') .action(clap::ArgAction::Count), ) .arg( clap::Arg::new("c") .short('c') .action(clap::ArgAction::Count), ); assert_data_eq!( complete!(cmd, "-a"), snapbox::str![[r#" -aa -ab -ac -ah Print help "#]], ); } #[test] fn suggest_subcommand_positional() { let mut cmd = Command::new("exhaustive").subcommand(Command::new("hello-world").arg( clap::Arg::new("hello-world").value_parser([ PossibleValue::new("hello-world").help("Say hello to the world"), "hello-moon".into(), "goodbye-world".into(), ]), )); assert_data_eq!( complete!(cmd, "hello-world [TAB]"), snapbox::str![[r#" --help Print help (see more with '--help') -h Print help (see more with '--help') hello-world Say hello to the world hello-moon goodbye-world "#]], ); } #[test] fn suggest_argument_value() { let mut cmd = Command::new("dynamic") .arg( clap::Arg::new("format") .long("format") .short('F') .value_parser(["json", "yaml", "toml"]), ) .arg( clap::Arg::new("stream") .long("stream") .short('S') .value_parser(["stdout", "stderr"]), ) .arg( clap::Arg::new("count") .long("count") .short('c') .action(clap::ArgAction::Count), ) .arg(clap::Arg::new("positional").value_parser(["pos_a", "pos_b", "pos_c"])) .args_conflicts_with_subcommands(true); assert_data_eq!( complete!(cmd, "--format [TAB]"), snapbox::str![[r#" json yaml toml "#]], ); assert_data_eq!( complete!(cmd, "-F [TAB]"), snapbox::str![[r#" json yaml toml "#]], ); assert_data_eq!(complete!(cmd, "--format j[TAB]"), snapbox::str!["json"],); assert_data_eq!(complete!(cmd, "-F j[TAB]"), snapbox::str!["json"],); assert_data_eq!(complete!(cmd, "--format t[TAB]"), snapbox::str!["toml"],); assert_data_eq!(complete!(cmd, "-F t[TAB]"), snapbox::str!["toml"],); assert_data_eq!( complete!(cmd, "-cccF [TAB]"), snapbox::str![[r#" json yaml toml "#]] ); assert_data_eq!( complete!(cmd, "--format toml [TAB]"), snapbox::str![[r#" --format --stream --count --help Print help -F -S -c -h Print help pos_a pos_b pos_c "#]] ); assert_data_eq!( complete!(cmd, "-cS[TAB]"), snapbox::str![[r#" -cSstdout -cSstderr "#]] ); assert_data_eq!( complete!(cmd, "-cS=[TAB]"), snapbox::str![[r#" -cS=stdout -cS=stderr "#]] ); assert_data_eq!(complete!(cmd, "-cS=stdo[TAB]"), snapbox::str!["-cS=stdout"]); assert_data_eq!(complete!(cmd, "-cSF[TAB]"), snapbox::str![]); assert_data_eq!(complete!(cmd, "-cSF=[TAB]"), snapbox::str![]); } #[test] fn suggest_argument_multi_values() { let mut cmd = Command::new("dynamic") .arg( clap::Arg::new("certain-num") .long("certain-num") .short('Y') .value_parser(["val1", "val2", "val3"]) .num_args(3), ) .arg( clap::Arg::new("uncertain-num") .long("uncertain-num") .short('N') .value_parser(["val1", "val2", "val3"]) .num_args(1..=3), ); assert_data_eq!( complete!(cmd, "--certain-num [TAB]"), snapbox::str![[r#" val1 val2 val3 "#]] ); assert_data_eq!( complete!(cmd, "--certain-num val1 [TAB]"), snapbox::str![[r#" val1 val2 val3 "#]] ); assert_data_eq!( complete!(cmd, "--certain-num val1 val2 val3 [TAB]"), snapbox::str![[r#" --certain-num --uncertain-num --help Print help -Y -N -h Print help "#]] ); assert_data_eq!( complete!(cmd, "--uncertain-num [TAB]"), snapbox::str![[r#" val1 val2 val3 "#]] ); assert_data_eq!( complete!(cmd, "--uncertain-num val1 [TAB]"), snapbox::str![[r#" val1 val2 val3 --certain-num --uncertain-num --help Print help -Y -N -h Print help "#]] ); assert_data_eq!( complete!(cmd, "--uncertain-num val1 val2 val3 [TAB]"), snapbox::str![[r#" --certain-num --uncertain-num --help Print help -Y -N -h Print help "#]] ); assert_data_eq!( complete!(cmd, "-Y [TAB]"), snapbox::str![[r#" val1 val2 val3 "#]] ); assert_data_eq!( complete!(cmd, "-Y val1 [TAB]"), snapbox::str![[r#" val1 val2 val3 "#]] ); assert_data_eq!( complete!(cmd, "-Y val1 val2 val3 [TAB]"), snapbox::str![[r#" --certain-num --uncertain-num --help Print help -Y -N -h Print help "#]] ); assert_data_eq!( complete!(cmd, "-N [TAB]"), snapbox::str![[r#" val1 val2 val3 "#]] ); assert_data_eq!( complete!(cmd, "-N val1 [TAB]"), snapbox::str![[r#" val1 val2 val3 --certain-num --uncertain-num --help Print help -Y -N -h Print help "#]] ); assert_data_eq!( complete!(cmd, "-N val1 val2 val3 [TAB]"), snapbox::str![[r#" --certain-num --uncertain-num --help Print help -Y -N -h Print help "#]] ); } #[test] fn suggest_value_hint_file_path() { let mut cmd = Command::new("dynamic") .arg( clap::Arg::new("input") .long("input") .short('i') .value_hint(clap::ValueHint::FilePath), ) .args_conflicts_with_subcommands(true); let testdir = snapbox::dir::DirRoot::mutable_temp().unwrap(); let testdir_path = testdir.path().unwrap(); fs::write(testdir_path.join("a_file"), "").unwrap(); fs::write(testdir_path.join("b_file"), "").unwrap(); fs::create_dir_all(testdir_path.join("c_dir")).unwrap(); fs::create_dir_all(testdir_path.join("d_dir")).unwrap(); assert_data_eq!( complete!(cmd, "--input [TAB]", current_dir = Some(testdir_path)), snapbox::str![[r#" a_file b_file c_dir/ d_dir/ "#]], ); assert_data_eq!( complete!(cmd, "--input a[TAB]", current_dir = Some(testdir_path)), snapbox::str!["a_file"], ); } #[test] fn suggest_value_path_file() { let testdir = snapbox::dir::DirRoot::mutable_temp().unwrap(); let testdir_path = testdir.path().unwrap(); fs::write(testdir_path.join("a_file"), "").unwrap(); fs::write(testdir_path.join("b_file"), "").unwrap(); fs::create_dir_all(testdir_path.join("c_dir")).unwrap(); fs::create_dir_all(testdir_path.join("d_dir")).unwrap(); let mut cmd = Command::new("dynamic") .arg( clap::Arg::new("input") .long("input") .short('i') .add(ArgValueCompleter::new( PathCompleter::file() .stdio() .current_dir(testdir_path.to_owned()), )), ) .args_conflicts_with_subcommands(true); assert_data_eq!( complete!(cmd, "--input [TAB]", current_dir = Some(testdir_path)), snapbox::str![[r#" a_file b_file c_dir/ d_dir/ - stdio "#]], ); assert_data_eq!( complete!(cmd, "--input a[TAB]", current_dir = Some(testdir_path)), snapbox::str!["a_file"], ); } #[test] fn suggest_value_path_dir() { let testdir = snapbox::dir::DirRoot::mutable_temp().unwrap(); let testdir_path = testdir.path().unwrap(); fs::write(testdir_path.join("a_file"), "").unwrap(); fs::write(testdir_path.join("b_file"), "").unwrap(); fs::create_dir_all(testdir_path.join("c_dir")).unwrap(); fs::create_dir_all(testdir_path.join("d_dir")).unwrap(); let mut cmd = Command::new("dynamic") .arg( clap::Arg::new("input") .long("input") .short('i') .add(ArgValueCompleter::new( PathCompleter::dir().current_dir(testdir_path.to_owned()), )), ) .args_conflicts_with_subcommands(true); assert_data_eq!( complete!(cmd, "--input [TAB]", current_dir = Some(testdir_path)), snapbox::str![[r#" . c_dir/ d_dir/ "#]], ); assert_data_eq!( complete!(cmd, "--input c[TAB]", current_dir = Some(testdir_path)), snapbox::str!["c_dir/"], ); } #[test] fn suggest_custom_arg_value() { fn custom_completer() -> Vec { vec![ CompletionCandidate::new("foo"), CompletionCandidate::new("bar"), CompletionCandidate::new("baz"), ] } let mut cmd = Command::new("dynamic").arg( clap::Arg::new("custom") .long("custom") .add(ArgValueCandidates::new(custom_completer)), ); assert_data_eq!( complete!(cmd, "--custom [TAB]"), snapbox::str![[r#" foo bar baz "#]], ); assert_data_eq!( complete!(cmd, "--custom b[TAB]"), snapbox::str![[r#" bar baz "#]], ); } #[test] fn suggest_custom_arg_completer() { fn custom_completer(current: &std::ffi::OsStr) -> Vec { let mut completions = vec![]; let Some(current) = current.to_str() else { return completions; }; if "foo".starts_with(current) { completions.push(CompletionCandidate::new("foo")); } if "bar".starts_with(current) { completions.push(CompletionCandidate::new("bar")); } if "baz".starts_with(current) { completions.push(CompletionCandidate::new("baz")); } completions } let mut cmd = Command::new("dynamic").arg( clap::Arg::new("custom") .long("custom") .add(ArgValueCompleter::new(custom_completer)), ); assert_data_eq!( complete!(cmd, "--custom [TAB]"), snapbox::str![[r#" foo bar baz "#]] ); assert_data_eq!( complete!(cmd, "--custom b[TAB]"), snapbox::str![[r#" bar baz "#]] ); } #[test] fn suggest_multi_positional() { let mut cmd = Command::new("dynamic") .arg( clap::Arg::new("positional") .value_parser(["pos_1, pos_2, pos_3"]) .index(1), ) .arg( clap::Arg::new("positional-2") .value_parser(["pos_a", "pos_b", "pos_c"]) .index(2) .num_args(3), ) .arg( clap::Arg::new("--format") .long("format") .short('F') .value_parser(["json", "yaml", "toml"]), ); assert_data_eq!( complete!(cmd, "pos_1 pos_a [TAB]"), snapbox::str![[r#" pos_a pos_b pos_c "#]] ); assert_data_eq!( complete!(cmd, "pos_1 pos_a pos_b [TAB]"), snapbox::str![[r#" pos_a pos_b pos_c "#]] ); assert_data_eq!( complete!(cmd, "--format json pos_1 [TAB]"), snapbox::str![[r#" --format --help Print help -F -h Print help pos_a pos_b pos_c "#]] ); assert_data_eq!( complete!(cmd, "--format json pos_1 pos_a [TAB]"), snapbox::str![[r#" pos_a pos_b pos_c "#]] ); assert_data_eq!( complete!(cmd, "--format json pos_1 pos_a pos_b pos_c [TAB]"), snapbox::str![[r#" --format --help Print help -F -h Print help "#]] ); assert_data_eq!( complete!(cmd, "--format json -- pos_1 pos_a [TAB]"), snapbox::str![[r#" pos_a pos_b pos_c "#]] ); assert_data_eq!( complete!(cmd, "--format json -- pos_1 pos_a pos_b [TAB]"), snapbox::str![[r#" pos_a pos_b pos_c "#]] ); assert_data_eq!( complete!(cmd, "--format json -- pos_1 pos_a pos_b pos_c [TAB]"), snapbox::str![] ); } #[test] fn suggest_delimiter_values() { let mut cmd = Command::new("delimiter") .arg( clap::Arg::new("delimiter") .long("delimiter") .short('D') .value_parser([ PossibleValue::new("comma"), PossibleValue::new("space"), PossibleValue::new("tab"), ]) .value_delimiter(','), ) .arg( clap::Arg::new("pos") .index(1) .value_parser(["a_pos", "b_pos", "c_pos"]) .value_delimiter(','), ); assert_data_eq!(complete!(cmd, "--delimiter [TAB]"), snapbox::str![[r#" comma space tab "#]]); assert_data_eq!(complete!(cmd, "--delimiter=[TAB]"), snapbox::str![[r#" --delimiter=comma --delimiter=space --delimiter=tab "#]]); assert_data_eq!(complete!(cmd, "--delimiter c[TAB]"), snapbox::str!["comma"]); assert_data_eq!( complete!(cmd, "--delimiter=c[TAB]"), snapbox::str!["--delimiter=comma"] ); assert_data_eq!(complete!(cmd, "--delimiter comma,[TAB]"), snapbox::str![[r#" comma,comma comma,space comma,tab "#]]); assert_data_eq!(complete!(cmd, "--delimiter=comma,[TAB]"), snapbox::str![[r#" --delimiter=comma,comma --delimiter=comma,space --delimiter=comma,tab --delimiter=comma,a_pos --delimiter=comma,b_pos --delimiter=comma,c_pos "#]]); assert_data_eq!( complete!(cmd, "--delimiter comma,s[TAB]"), snapbox::str!["comma,space"] ); assert_data_eq!( complete!(cmd, "--delimiter=comma,s[TAB]"), snapbox::str!["--delimiter=comma,space"] ); assert_data_eq!(complete!(cmd, "-D [TAB]"), snapbox::str![[r#" comma space tab "#]]); assert_data_eq!(complete!(cmd, "-D=[TAB]"), snapbox::str![[r#" -D=comma -D=space -D=tab "#]]); assert_data_eq!(complete!(cmd, "-D c[TAB]"), snapbox::str!["comma"]); assert_data_eq!(complete!(cmd, "-D=c[TAB]"), snapbox::str!["-D=comma"]); assert_data_eq!(complete!(cmd, "-D comma,[TAB]"), snapbox::str![[r#" comma,comma comma,space comma,tab "#]]); assert_data_eq!(complete!(cmd, "-D=comma,[TAB]"), snapbox::str![[r#" -D=comma,comma -D=comma,space -D=comma,tab -D=comma,a_pos -D=comma,b_pos -D=comma,c_pos "#]]); assert_data_eq!( complete!(cmd, "-D comma,s[TAB]"), snapbox::str!["comma,space"] ); assert_data_eq!( complete!(cmd, "-D=comma,s[TAB]"), snapbox::str!["-D=comma,space"] ); assert_data_eq!(complete!(cmd, "-- [TAB]"), snapbox::str![[r#" --delimiter --help Print help -D -h Print help a_pos b_pos c_pos "#]]); assert_data_eq!(complete!(cmd, " -- a_pos,[TAB]"), snapbox::str![[r#" a_pos,a_pos a_pos,b_pos a_pos,c_pos "#]]); assert_data_eq!( complete!(cmd, "-- a_pos,b[TAB]"), snapbox::str!["a_pos,b_pos"] ); } #[test] fn suggest_allow_hyphen() { let mut cmd = Command::new("exhaustive") .arg( clap::Arg::new("format") .long("format") .short('F') .allow_hyphen_values(true) .value_parser(["--json", "--toml", "--yaml"]), ) .arg(clap::Arg::new("json").long("json")); assert_data_eq!(complete!(cmd, "--format --j[TAB]"), snapbox::str!["--json"]); assert_data_eq!(complete!(cmd, "-F --j[TAB]"), snapbox::str!["--json"]); assert_data_eq!(complete!(cmd, "--format --t[TAB]"), snapbox::str!["--toml"]); assert_data_eq!(complete!(cmd, "-F --t[TAB]"), snapbox::str!["--toml"]); assert_data_eq!(complete!(cmd, "--format --[TAB]"), snapbox::str![[r#" --json --toml --yaml "#]]); assert_data_eq!(complete!(cmd, "-F --[TAB]"), snapbox::str![[r#" --json --toml --yaml "#]]); assert_data_eq!( complete!(cmd, "--format --json --j[TAB]"), snapbox::str!["--json"] ); assert_data_eq!( complete!(cmd, "-F --json --j[TAB]"), snapbox::str!["--json"] ); } #[test] fn suggest_positional_long_allow_hyphen() { let mut cmd = Command::new("exhaustive") .arg( clap::Arg::new("format") .long("format") .short('F') .allow_hyphen_values(true) .value_parser(["--json", "--toml", "--yaml"]), ) .arg( clap::Arg::new("positional_a") .value_parser(["--pos_a"]) .index(1) .allow_hyphen_values(true), ) .arg( clap::Arg::new("positional_b") .index(2) .value_parser(["pos_b"]), ); assert_data_eq!( complete!(cmd, "--format --json --pos[TAB]"), snapbox::str!["--pos_a"] ); assert_data_eq!( complete!(cmd, "-F --json --pos[TAB]"), snapbox::str!["--pos_a"] ); assert_data_eq!( complete!(cmd, "--format --json --pos_a [TAB]"), snapbox::str![[r#" --format --help Print help -F -h Print help pos_b "#]] ); assert_data_eq!(complete!(cmd, "-F --json --pos_a [TAB]"), snapbox::str![[r#" --format --help Print help -F -h Print help pos_b "#]]); assert_data_eq!( complete!(cmd, "--format --json --pos_a p[TAB]"), snapbox::str!["pos_b"] ); assert_data_eq!( complete!(cmd, "-F --json --pos_a p[TAB]"), snapbox::str!["pos_b"] ); } #[test] fn suggest_positional_short_allow_hyphen() { let mut cmd = Command::new("exhaustive") .arg( clap::Arg::new("format") .long("format") .short('F') .allow_hyphen_values(true) .value_parser(["--json", "--toml", "--yaml"]), ) .arg( clap::Arg::new("positional_a") .value_parser(["-a"]) .index(1) .allow_hyphen_values(true), ) .arg( clap::Arg::new("positional_b") .index(2) .value_parser(["pos_b"]), ); assert_data_eq!(complete!(cmd, "--format --json -a [TAB]"), snapbox::str![[r#" --format --help Print help -F -h Print help pos_b "#]]); assert_data_eq!(complete!(cmd, "-F --json -a [TAB]"), snapbox::str![[r#" --format --help Print help -F -h Print help pos_b "#]]); assert_data_eq!( complete!(cmd, "--format --json -a p[TAB]"), snapbox::str!["pos_b"] ); assert_data_eq!( complete!(cmd, "-F --json -a p[TAB]"), snapbox::str!["pos_b"] ); } #[test] fn sort_and_filter() { let mut cmd = Command::new("exhaustive") .args([ clap::Arg::new("required-flag") .long("required-flag") .visible_alias("required-flag2") .short('r') .required(true), clap::Arg::new("optional-flag") .long("optional-flag") .visible_alias("2optional-flag") .short('o'), clap::Arg::new("long-flag").long("long-flag"), clap::Arg::new("short-flag").short('s'), clap::Arg::new("positional").value_parser(["pos-a", "pos-b", "pos-c"]), ]) .subcommands([Command::new("sub")]); assert_data_eq!( complete!(cmd, " [TAB]"), snapbox::str![[r#" --required-flag --required-flag2 --optional-flag --2optional-flag --long-flag --help Print help -r -o -s -h Print help pos-a pos-b pos-c help Print this message or the help of the given subcommand(s) sub "#]] ); assert_data_eq!( complete!(cmd, "-[TAB]"), snapbox::str![[r#" --required-flag --required-flag2 --optional-flag --2optional-flag --long-flag --help Print help -r -o -s -h Print help "#]] ); } fn complete(cmd: &mut Command, args: impl AsRef, current_dir: Option<&Path>) -> String { let input = args.as_ref(); let mut args = vec![std::ffi::OsString::from(cmd.get_name())]; let arg_index; if let Some((prior, after)) = input.split_once("[TAB]") { args.extend(prior.split_whitespace().map(From::from)); if prior.ends_with(char::is_whitespace) { args.push(std::ffi::OsString::default()); } arg_index = args.len() - 1; // HACK: this cannot handle in-word '[TAB]' args.extend(after.split_whitespace().map(From::from)); } else { args.extend(input.split_whitespace().map(From::from)); if input.ends_with(char::is_whitespace) { args.push(std::ffi::OsString::default()); } arg_index = args.len() - 1; } clap_complete::engine::complete(cmd, args, arg_index, current_dir) .unwrap() .into_iter() .map(|candidate| { let compl = candidate.get_value().to_str().unwrap(); if let Some(help) = candidate.get_help() { format!("{compl}\t{help}") } else { compl.to_owned() } }) .collect::>() .join("\n") }