1use std::env;
27use std::io::{self, Write};
28
29use crate::command::Command;
30use crate::context::ContextBuilder;
31use crate::group::{CommandCollection, CommandLike, Group};
32use crate::parameter::Parameter;
33use crate::types::CompletionItem;
34use crate::utils::split_arg_string;
35
36pub trait ShellComplete: Send + Sync {
45 fn name(&self) -> &str;
47
48 fn source_template(&self) -> &str;
55
56 fn get_completion_args(&self) -> CompletionArgs;
61
62 fn format_completion(&self, item: &CompletionItem) -> String;
66
67 fn get_source(&self, prog_name: &str, complete_var: &str) -> String {
69 let complete_func = format!("_{}_completion", prog_name.replace('-', "_"));
70 self.source_template()
71 .replace("%(prog_name)s", prog_name)
72 .replace("%(complete_func)s", &complete_func)
73 .replace("%(complete_var)s", complete_var)
74 }
75}
76
77#[derive(Debug, Clone)]
79pub struct CompletionArgs {
80 pub args: Vec<String>,
82 pub incomplete: String,
84}
85
86impl Default for CompletionArgs {
87 fn default() -> Self {
88 Self {
89 args: Vec::new(),
90 incomplete: String::new(),
91 }
92 }
93}
94
95#[derive(Debug, Clone, Default)]
104pub struct BashComplete;
105
106impl BashComplete {
107 const SOURCE_TEMPLATE: &'static str = r#"
109%(complete_func)s() {
110 local IFS=$'\n'
111 COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \
112 COMP_CWORD=$COMP_CWORD \
113 %(complete_var)s=bash_complete \
114 %(prog_name)s ) )
115 return 0
116}
117
118%(complete_func)s_setup() {
119 complete -o default -F %(complete_func)s %(prog_name)s
120}
121
122%(complete_func)s_setup
123"#;
124}
125
126impl ShellComplete for BashComplete {
127 fn name(&self) -> &str {
128 "bash"
129 }
130
131 fn source_template(&self) -> &str {
132 Self::SOURCE_TEMPLATE
133 }
134
135 fn get_completion_args(&self) -> CompletionArgs {
136 let comp_words = env::var("COMP_WORDS").unwrap_or_default();
138 let comp_cword: usize = env::var("COMP_CWORD")
139 .ok()
140 .and_then(|s| s.parse().ok())
141 .unwrap_or(0);
142
143 let cwords = split_arg_string(&comp_words);
144
145 let args: Vec<String> = cwords
146 .iter()
147 .skip(1)
148 .take(comp_cword.saturating_sub(1))
149 .cloned()
150 .collect();
151
152 let incomplete = cwords.get(comp_cword).cloned().unwrap_or_default();
153
154 CompletionArgs { args, incomplete }
155 }
156
157 fn format_completion(&self, item: &CompletionItem) -> String {
158 format!("{},{}", item.completion_type, item.value)
160 }
161}
162
163#[derive(Debug, Clone, Default)]
171pub struct ZshComplete;
172
173impl ZshComplete {
174 const SOURCE_TEMPLATE: &'static str = r#"
176#compdef %(prog_name)s
177
178%(complete_func)s() {
179 local -a completions
180 local -a completions_with_descriptions
181 local -a response
182 (( ! $+commands[%(prog_name)s] )) && return 1
183
184 response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) %(complete_var)s=zsh_complete %(prog_name)s)}")
185
186 for key descr in ${(kv)response}; do
187 if [[ "$descr" == "_" ]]; then
188 completions+=("$key")
189 else
190 completions_with_descriptions+=("$key":"$descr")
191 fi
192 done
193
194 if [ -n "$completions_with_descriptions" ]; then
195 _describe -V unsorted completions_with_descriptions -U
196 fi
197
198 if [ -n "$completions" ]; then
199 compadd -U -V unsorted -a completions
200 fi
201}
202
203if [[ $zsh_eval_context[-1] == loadautofun ]]; then
204 %(complete_func)s "$@"
205else
206 compdef %(complete_func)s %(prog_name)s
207fi
208"#;
209}
210
211impl ShellComplete for ZshComplete {
212 fn name(&self) -> &str {
213 "zsh"
214 }
215
216 fn source_template(&self) -> &str {
217 Self::SOURCE_TEMPLATE
218 }
219
220 fn get_completion_args(&self) -> CompletionArgs {
221 let comp_words = env::var("COMP_WORDS").unwrap_or_default();
223 let comp_cword: usize = env::var("COMP_CWORD")
224 .ok()
225 .and_then(|s| s.parse().ok())
226 .unwrap_or(0);
227
228 let cwords = split_arg_string(&comp_words);
229
230 let args: Vec<String> = cwords
231 .iter()
232 .skip(1)
233 .take(comp_cword.saturating_sub(1))
234 .cloned()
235 .collect();
236
237 let incomplete = cwords.get(comp_cword).cloned().unwrap_or_default();
238
239 CompletionArgs { args, incomplete }
240 }
241
242 fn format_completion(&self, item: &CompletionItem) -> String {
243 let help = item
247 .help
248 .as_deref()
249 .filter(|h| !h.is_empty())
250 .unwrap_or("_");
251 let value = if help != "_" {
252 item.value.replace(':', "\\:")
253 } else {
254 item.value.clone()
255 };
256 format!("{}\n{}\n{}", item.completion_type, value, help)
257 }
258}
259
260#[derive(Debug, Clone, Default)]
268pub struct FishComplete;
269
270impl FishComplete {
271 const SOURCE_TEMPLATE: &'static str = r#"
273function %(complete_func)s
274 set -l response (env %(complete_var)s=fish_complete COMP_WORDS=(commandline -cp) COMP_CWORD=(commandline -t) %(prog_name)s)
275
276 for completion in $response
277 set -l metadata (string split "," -- $completion)
278
279 if [ $metadata[1] = "dir" ]
280 __fish_complete_directories $metadata[2]
281 else if [ $metadata[1] = "file" ]
282 __fish_complete_path $metadata[2]
283 else if [ $metadata[1] = "plain" ]
284 echo $metadata[2]
285 end
286 end
287end
288
289complete -c %(prog_name)s -f -a "(%(complete_func)s)"
290"#;
291}
292
293impl ShellComplete for FishComplete {
294 fn name(&self) -> &str {
295 "fish"
296 }
297
298 fn source_template(&self) -> &str {
299 Self::SOURCE_TEMPLATE
300 }
301
302 fn get_completion_args(&self) -> CompletionArgs {
303 let comp_words = env::var("COMP_WORDS").unwrap_or_default();
308 let mut incomplete = env::var("COMP_CWORD").unwrap_or_default();
309 if !incomplete.is_empty() {
310 incomplete = split_arg_string(&incomplete)
311 .into_iter()
312 .next()
313 .unwrap_or_default();
314 }
315
316 let mut args: Vec<String> = split_arg_string(&comp_words).into_iter().skip(1).collect();
317 if !incomplete.is_empty() && args.last().is_some_and(|a| a == &incomplete) {
318 args.pop();
319 }
320
321 CompletionArgs { args, incomplete }
322 }
323
324 fn format_completion(&self, item: &CompletionItem) -> String {
325 if let Some(help) = item.help.as_deref().filter(|h| !h.is_empty()) {
329 format!("{},{}\t{}", item.completion_type, item.value, help)
330 } else {
331 format!("{},{}", item.completion_type, item.value)
332 }
333 }
334}
335
336pub fn get_completion_class(shell: &str) -> Option<Box<dyn ShellComplete>> {
360 match shell.to_lowercase().as_str() {
361 "bash" => Some(Box::new(BashComplete)),
362 "zsh" => Some(Box::new(ZshComplete)),
363 "fish" => Some(Box::new(FishComplete)),
364 _ => None,
365 }
366}
367
368pub fn detect_shell() -> Option<String> {
376 env::var("SHELL").ok().and_then(|shell| {
377 let shell_name = shell.rsplit('/').next()?;
378 match shell_name {
379 "bash" => Some("bash".to_string()),
380 "zsh" => Some("zsh".to_string()),
381 "fish" => Some("fish".to_string()),
382 _ => None,
383 }
384 })
385}
386
387pub fn list_shells() -> Vec<&'static str> {
389 vec!["bash", "zsh", "fish"]
390}
391
392pub fn shell_complete(cmd: &dyn CommandLike, prog_name: &str, complete_var: &str) {
424 let shell_type = match env::var(complete_var) {
426 Ok(val) => {
427 val.split('_').next().unwrap_or("bash").to_string()
429 }
430 Err(_) => return,
431 };
432
433 let completer = match get_completion_class(&shell_type) {
434 Some(c) => c,
435 None => return,
436 };
437
438 let comp_args = completer.get_completion_args();
440
441 let completions = get_completions(cmd, prog_name, &comp_args.args, &comp_args.incomplete);
443
444 let stdout = io::stdout();
446 let mut handle = stdout.lock();
447 for item in completions {
448 let _ = writeln!(handle, "{}", completer.format_completion(&item));
449 }
450}
451
452pub fn get_completions(
468 cmd: &dyn CommandLike,
469 prog_name: &str,
470 args: &[String],
471 incomplete: &str,
472) -> Vec<CompletionItem> {
473 let mut completions = Vec::new();
474
475 let ctx = ContextBuilder::new()
477 .info_name(prog_name)
478 .resilient_parsing(true)
479 .build();
480
481 if let Some(group) = cmd.as_any().downcast_ref::<Group>() {
483 if !args.is_empty() {
485 if let Some(subcmd) = group.get_command(&args[0]) {
486 let remaining_args: Vec<String> = args[1..].to_vec();
487 return get_completions(subcmd, &args[0], &remaining_args, incomplete);
488 }
489 }
490
491 for name in group.list_commands() {
493 if name.starts_with(incomplete) {
494 let subcmd = group.get_command(name);
495 let help = subcmd.map(|c| c.get_short_help());
496 let mut item = CompletionItem::new(name);
497 if let Some(h) = help {
498 if !h.is_empty() {
499 item = item.with_help(h);
500 }
501 }
502 completions.push(item);
503 }
504 }
505 }
506 if let Some(collection) = cmd.as_any().downcast_ref::<CommandCollection>() {
507 if !args.is_empty() {
508 if let Some(subcmd) = collection.get_command(&args[0]) {
509 let remaining_args: Vec<String> = args[1..].to_vec();
510 return get_completions(subcmd, &args[0], &remaining_args, incomplete);
511 }
512 }
513
514 for name in collection.list_commands() {
515 if name.starts_with(incomplete) {
516 let subcmd = collection.get_command(&name);
517 let help = subcmd.map(|c| c.get_short_help());
518 let mut item = CompletionItem::new(&name);
519 if let Some(h) = help {
520 if !h.is_empty() {
521 item = item.with_help(h);
522 }
523 }
524 completions.push(item);
525 }
526 }
527 }
528
529 if incomplete.starts_with('-') || completions.is_empty() {
531 if let Some(command) = cmd.as_any().downcast_ref::<Command>() {
532 completions.extend(get_option_completions(command, &ctx, args, incomplete));
533 } else if let Some(group) = cmd.as_any().downcast_ref::<Group>() {
534 completions.extend(get_option_completions(
535 &group.command,
536 &ctx,
537 args,
538 incomplete,
539 ));
540 } else if let Some(collection) = cmd.as_any().downcast_ref::<CommandCollection>() {
541 completions.extend(get_option_completions(
542 &collection.base.command,
543 &ctx,
544 args,
545 incomplete,
546 ));
547 }
548 }
549
550 if !incomplete.starts_with('-') {
552 if let Some(command) = cmd.as_any().downcast_ref::<Command>() {
553 completions.extend(get_argument_completions(command, &ctx, args, incomplete));
554 } else if let Some(group) = cmd.as_any().downcast_ref::<Group>() {
555 completions.extend(get_argument_completions(
556 &group.command,
557 &ctx,
558 args,
559 incomplete,
560 ));
561 } else if let Some(collection) = cmd.as_any().downcast_ref::<CommandCollection>() {
562 completions.extend(get_argument_completions(
563 &collection.base.command,
564 &ctx,
565 args,
566 incomplete,
567 ));
568 }
569 }
570
571 completions
572}
573
574fn get_argument_completions(
580 cmd: &Command,
581 ctx: &crate::context::Context,
582 args: &[String],
583 incomplete: &str,
584) -> Vec<CompletionItem> {
585 let positional_count = args.iter().filter(|a| !a.starts_with('-')).count();
588
589 if let Some(arg) = cmd.arguments.get(positional_count) {
591 return arg.get_completions(ctx, incomplete);
592 }
593
594 if let Some(last_arg) = cmd.arguments.last() {
596 if last_arg.multiple() {
597 return last_arg.get_completions(ctx, incomplete);
598 }
599 }
600
601 Vec::new()
602}
603
604fn get_option_completions(
606 cmd: &Command,
607 ctx: &crate::context::Context,
608 args: &[String],
609 incomplete: &str,
610) -> Vec<CompletionItem> {
611 if let Some((opt_name, value_prefix)) = incomplete.split_once('=') {
613 if opt_name.starts_with("--") {
614 if let Some(opt) = cmd
615 .options
616 .iter()
617 .find(|o| o.long.iter().any(|long| long == opt_name) && option_accepts_value(o))
618 {
619 return opt
620 .get_completions(ctx, value_prefix)
621 .into_iter()
622 .map(|item| {
623 let mut with_prefix =
624 CompletionItem::new(format!("{}={}", opt_name, item.value));
625 if let Some(help) = item.help {
626 with_prefix = with_prefix.with_help(help);
627 }
628 with_prefix
629 })
630 .collect();
631 }
632 }
633 }
634
635 if let Some(last_arg) = args.last() {
637 if let Some(opt) = cmd.options.iter().find(|o| {
638 option_accepts_value(o)
639 && (o.long.iter().any(|long| long == last_arg)
640 || o.short.iter().any(|short| short == last_arg))
641 }) {
642 return opt.get_completions(ctx, incomplete);
643 }
644 }
645
646 let mut completions = Vec::new();
647
648 for opt in &cmd.options {
649 for long in &opt.long {
651 if long.starts_with(incomplete) {
652 let mut item = CompletionItem::new(long);
653 if let Some(help) = opt.help() {
654 item = item.with_help(help.to_string());
655 }
656 completions.push(item);
657 }
658 }
659
660 for short in &opt.short {
662 if short.starts_with(incomplete) {
663 let mut item = CompletionItem::new(short);
664 if let Some(help) = opt.help() {
665 item = item.with_help(help.to_string());
666 }
667 completions.push(item);
668 }
669 }
670 }
671
672 if let Some(help_opt) = cmd.get_help_option(ctx) {
674 for long in &help_opt.long {
675 if long.starts_with(incomplete) {
676 let mut item = CompletionItem::new(long);
677 if let Some(help) = help_opt.help() {
678 if !help.is_empty() {
679 item = item.with_help(help.to_string());
680 }
681 }
682 completions.push(item);
683 }
684 }
685 for short in &help_opt.short {
686 if short.starts_with(incomplete) {
687 let mut item = CompletionItem::new(short);
688 if let Some(help) = help_opt.help() {
689 if !help.is_empty() {
690 item = item.with_help(help.to_string());
691 }
692 }
693 completions.push(item);
694 }
695 }
696 }
697
698 completions
699}
700
701fn option_accepts_value(opt: &crate::option::ClickOption) -> bool {
702 !opt.is_flag && !opt.count
703}
704
705pub fn make_completion_option(complete_var: &str) -> CompletionOption {
719 CompletionOption {
720 complete_var: complete_var.to_string(),
721 }
722}
723
724#[derive(Debug, Clone)]
726pub struct CompletionOption {
727 pub complete_var: String,
729}
730
731impl CompletionOption {
732 pub fn is_completion_requested(&self) -> bool {
734 env::var(&self.complete_var).is_ok()
735 }
736
737 pub fn get_completion_shell(&self) -> Option<String> {
739 env::var(&self.complete_var).ok().and_then(|val| {
740 let parts: Vec<&str> = val.split('_').collect();
742 if parts.len() >= 2 {
743 Some(parts[0].to_string())
744 } else {
745 None
746 }
747 })
748 }
749
750 pub fn is_source_request(&self) -> bool {
752 env::var(&self.complete_var)
753 .map(|v| v.ends_with("_source"))
754 .unwrap_or(false)
755 }
756
757 pub fn handle_completion(&self, cmd: &dyn CommandLike, prog_name: &str) -> bool {
761 if !self.is_completion_requested() {
762 return false;
763 }
764
765 if let Some(shell) = self.get_completion_shell() {
766 if self.is_source_request() {
767 if let Some(completer) = get_completion_class(&shell) {
769 println!("{}", completer.get_source(prog_name, &self.complete_var));
770 }
771 } else {
772 shell_complete(cmd, prog_name, &self.complete_var);
774 }
775 return true;
776 }
777
778 false
779 }
780}
781
782#[cfg(test)]
787mod tests {
788 use super::*;
789
790 #[test]
791 fn test_get_completion_class() {
792 assert!(get_completion_class("bash").is_some());
793 assert!(get_completion_class("zsh").is_some());
794 assert!(get_completion_class("fish").is_some());
795 assert!(get_completion_class("unknown").is_none());
796
797 assert!(get_completion_class("BASH").is_some());
799 assert!(get_completion_class("Zsh").is_some());
800 }
801
802 #[test]
803 fn test_shell_names() {
804 let bash = BashComplete;
805 assert_eq!(bash.name(), "bash");
806
807 let zsh = ZshComplete;
808 assert_eq!(zsh.name(), "zsh");
809
810 let fish = FishComplete;
811 assert_eq!(fish.name(), "fish");
812 }
813
814 #[test]
815 fn test_list_shells() {
816 let shells = list_shells();
817 assert!(shells.contains(&"bash"));
818 assert!(shells.contains(&"zsh"));
819 assert!(shells.contains(&"fish"));
820 }
821
822 #[test]
823 fn test_bash_source_template() {
824 let bash = BashComplete;
825 let source = bash.get_source("myapp", "_MYAPP_COMPLETE");
826
827 assert!(source.contains("myapp"));
828 assert!(source.contains("_MYAPP_COMPLETE"));
829 assert!(source.contains("_myapp_completion"));
830 assert!(source.contains("COMP_WORDS"));
831 assert!(source.contains("COMP_CWORD"));
832 }
833
834 #[test]
835 fn test_zsh_source_template() {
836 let zsh = ZshComplete;
837 let source = zsh.get_source("myapp", "_MYAPP_COMPLETE");
838
839 assert!(source.contains("#compdef myapp"));
840 assert!(source.contains("_MYAPP_COMPLETE"));
841 assert!(source.contains("_myapp_completion"));
842 }
843
844 #[test]
845 fn test_fish_source_template() {
846 let fish = FishComplete;
847 let source = fish.get_source("myapp", "_MYAPP_COMPLETE");
848
849 assert!(source.contains("function _myapp_completion"));
850 assert!(source.contains("_MYAPP_COMPLETE"));
851 assert!(source.contains("complete -c myapp"));
852 }
853
854 #[test]
855 fn test_bash_format_completion() {
856 let bash = BashComplete;
857
858 let item = CompletionItem::new("--help");
859 assert_eq!(bash.format_completion(&item), "plain,--help");
860
861 let item_with_help = CompletionItem::new("--name").with_help("Specify name");
862 assert_eq!(bash.format_completion(&item_with_help), "plain,--name");
863 }
864
865 #[test]
866 fn test_zsh_format_completion() {
867 let zsh = ZshComplete;
868
869 let item = CompletionItem::new("--help");
870 assert_eq!(zsh.format_completion(&item), "plain\n--help\n_");
871
872 let item_with_help = CompletionItem::new("--name").with_help("Specify name");
873 assert_eq!(
874 zsh.format_completion(&item_with_help),
875 "plain\n--name\nSpecify name"
876 );
877 }
878
879 #[test]
880 fn test_fish_format_completion() {
881 let fish = FishComplete;
882
883 let item = CompletionItem::new("--help");
884 assert_eq!(fish.format_completion(&item), "plain,--help");
885
886 let item_file = CompletionItem::with_type("path", "file");
887 assert_eq!(fish.format_completion(&item_file), "file,path");
888 }
889
890 #[test]
891 fn test_completion_args_default() {
892 let args = CompletionArgs::default();
893 assert!(args.args.is_empty());
894 assert!(args.incomplete.is_empty());
895 }
896
897 #[test]
898 fn test_get_completions_empty() {
899 let cmd = Command::new("test").build();
900 let completions = get_completions(&cmd, "test", &[], "");
901
902 assert!(completions.iter().any(|c| c.value == "--help"));
904 }
905
906 #[test]
907 fn test_get_completions_options() {
908 let cmd = Command::new("test")
909 .option(
910 crate::option::ClickOption::new(&["--name", "-n"])
911 .help("The name")
912 .build(),
913 )
914 .build();
915
916 let completions = get_completions(&cmd, "test", &[], "--");
917
918 assert!(completions.iter().any(|c| c.value == "--name"));
919 assert!(completions.iter().any(|c| c.value == "--help"));
920 }
921
922 #[test]
923 fn test_get_completions_subcommands() {
924 let group = Group::new("cli")
925 .command(Command::new("init").help("Initialize").build())
926 .command(Command::new("build").help("Build").build())
927 .build();
928
929 let completions = get_completions(&group, "cli", &[], "");
930
931 assert!(completions.iter().any(|c| c.value == "init"));
932 assert!(completions.iter().any(|c| c.value == "build"));
933 }
934
935 #[test]
936 fn test_get_completions_subcommand_prefix() {
937 let group = Group::new("cli")
938 .command(Command::new("init").build())
939 .command(Command::new("install").build())
940 .command(Command::new("build").build())
941 .build();
942
943 let completions = get_completions(&group, "cli", &[], "in");
944
945 assert!(completions.iter().any(|c| c.value == "init"));
946 assert!(completions.iter().any(|c| c.value == "install"));
947 assert!(!completions.iter().any(|c| c.value == "build"));
948 }
949
950 #[test]
951 fn test_completion_option() {
952 let opt = make_completion_option("_TEST_COMPLETE");
953 assert_eq!(opt.complete_var, "_TEST_COMPLETE");
954 }
955
956 #[test]
957 fn test_completion_option_not_requested() {
958 env::remove_var("_TEST_COMPLETE");
960
961 let opt = make_completion_option("_TEST_COMPLETE");
962 assert!(!opt.is_completion_requested());
963 assert!(opt.get_completion_shell().is_none());
964 assert!(!opt.is_source_request());
965 }
966
967 #[test]
968 fn test_prog_name_with_dash() {
969 let bash = BashComplete;
970 let source = bash.get_source("my-app", "_MY_APP_COMPLETE");
971
972 assert!(source.contains("_my_app_completion"));
974 }
975
976 #[test]
981 fn test_get_completions_argument_with_choice_type() {
982 use crate::argument::Argument;
983 use crate::types::Choice;
984
985 let cmd = Command::new("test")
986 .argument(
987 Argument::new("format")
988 .type_(Choice::new(["json", "xml", "yaml"]))
989 .build(),
990 )
991 .build();
992
993 let completions = get_completions(&cmd, "test", &[], "j");
994
995 assert_eq!(completions.len(), 1);
996 assert_eq!(completions[0].value, "json");
997 }
998
999 #[test]
1000 fn test_get_completions_argument_with_custom_callback() {
1001 use crate::argument::Argument;
1002
1003 let cmd = Command::new("test")
1004 .argument(
1005 Argument::new("filename")
1006 .shell_complete(|_ctx, incomplete| {
1007 vec![
1008 CompletionItem::new(format!("{}.txt", incomplete)),
1009 CompletionItem::new(format!("{}.md", incomplete)),
1010 ]
1011 })
1012 .build(),
1013 )
1014 .build();
1015
1016 let completions = get_completions(&cmd, "test", &[], "file");
1017
1018 assert_eq!(completions.len(), 2);
1019 assert!(completions.iter().any(|c| c.value == "file.txt"));
1020 assert!(completions.iter().any(|c| c.value == "file.md"));
1021 }
1022
1023 #[test]
1024 fn test_get_completions_second_argument() {
1025 use crate::argument::Argument;
1026 use crate::types::Choice;
1027
1028 let cmd = Command::new("test")
1029 .argument(Argument::new("first").build())
1030 .argument(
1031 Argument::new("second")
1032 .type_(Choice::new(["a", "b", "c"]))
1033 .build(),
1034 )
1035 .build();
1036
1037 let completions = get_completions(&cmd, "test", &[], "x");
1039 assert!(completions.is_empty());
1040
1041 let completions = get_completions(&cmd, "test", &["value1".to_string()], "a");
1043 assert_eq!(completions.len(), 1);
1044 assert_eq!(completions[0].value, "a");
1045 }
1046
1047 #[test]
1048 fn test_get_completions_variadic_argument() {
1049 use crate::argument::Argument;
1050 use crate::types::Choice;
1051
1052 let cmd = Command::new("test")
1053 .argument(
1054 Argument::new("files")
1055 .multiple()
1056 .type_(Choice::new(["foo.txt", "bar.txt", "baz.txt"]))
1057 .build(),
1058 )
1059 .build();
1060
1061 let completions = get_completions(&cmd, "test", &[], "f");
1063 assert_eq!(completions.len(), 1);
1064 assert_eq!(completions[0].value, "foo.txt");
1065
1066 let completions = get_completions(&cmd, "test", &["foo.txt".to_string()], "b");
1068 assert_eq!(completions.len(), 2);
1069 assert!(completions.iter().any(|c| c.value == "bar.txt"));
1070 assert!(completions.iter().any(|c| c.value == "baz.txt"));
1071 }
1072
1073 #[test]
1074 fn test_get_completions_no_more_arguments() {
1075 use crate::argument::Argument;
1076
1077 let cmd = Command::new("test")
1078 .argument(Argument::new("single").build())
1079 .build();
1080
1081 let completions = get_completions(&cmd, "test", &["value".to_string()], "x");
1083 assert!(completions.is_empty());
1086 }
1087}