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.help.as_deref().filter(|h| !h.is_empty()).unwrap_or("_");
247 let value = if help != "_" {
248 item.value.replace(':', "\\:")
249 } else {
250 item.value.clone()
251 };
252 format!("{}\n{}\n{}", item.completion_type, value, help)
253 }
254}
255
256#[derive(Debug, Clone, Default)]
264pub struct FishComplete;
265
266impl FishComplete {
267 const SOURCE_TEMPLATE: &'static str = r#"
269function %(complete_func)s
270 set -l response (env %(complete_var)s=fish_complete COMP_WORDS=(commandline -cp) COMP_CWORD=(commandline -t) %(prog_name)s)
271
272 for completion in $response
273 set -l metadata (string split "," -- $completion)
274
275 if [ $metadata[1] = "dir" ]
276 __fish_complete_directories $metadata[2]
277 else if [ $metadata[1] = "file" ]
278 __fish_complete_path $metadata[2]
279 else if [ $metadata[1] = "plain" ]
280 echo $metadata[2]
281 end
282 end
283end
284
285complete -c %(prog_name)s -f -a "(%(complete_func)s)"
286"#;
287}
288
289impl ShellComplete for FishComplete {
290 fn name(&self) -> &str {
291 "fish"
292 }
293
294 fn source_template(&self) -> &str {
295 Self::SOURCE_TEMPLATE
296 }
297
298 fn get_completion_args(&self) -> CompletionArgs {
299 let comp_words = env::var("COMP_WORDS").unwrap_or_default();
304 let mut incomplete = env::var("COMP_CWORD").unwrap_or_default();
305 if !incomplete.is_empty() {
306 incomplete = split_arg_string(&incomplete)
307 .into_iter()
308 .next()
309 .unwrap_or_default();
310 }
311
312 let mut args: Vec<String> = split_arg_string(&comp_words).into_iter().skip(1).collect();
313 if !incomplete.is_empty() && args.last().is_some_and(|a| a == &incomplete) {
314 args.pop();
315 }
316
317 CompletionArgs { args, incomplete }
318 }
319
320 fn format_completion(&self, item: &CompletionItem) -> String {
321 if let Some(help) = item.help.as_deref().filter(|h| !h.is_empty()) {
325 format!("{},{}\t{}", item.completion_type, item.value, help)
326 } else {
327 format!("{},{}", item.completion_type, item.value)
328 }
329 }
330}
331
332pub fn get_completion_class(shell: &str) -> Option<Box<dyn ShellComplete>> {
356 match shell.to_lowercase().as_str() {
357 "bash" => Some(Box::new(BashComplete)),
358 "zsh" => Some(Box::new(ZshComplete)),
359 "fish" => Some(Box::new(FishComplete)),
360 _ => None,
361 }
362}
363
364pub fn detect_shell() -> Option<String> {
372 env::var("SHELL").ok().and_then(|shell| {
373 let shell_name = shell.rsplit('/').next()?;
374 match shell_name {
375 "bash" => Some("bash".to_string()),
376 "zsh" => Some("zsh".to_string()),
377 "fish" => Some("fish".to_string()),
378 _ => None,
379 }
380 })
381}
382
383pub fn list_shells() -> Vec<&'static str> {
385 vec!["bash", "zsh", "fish"]
386}
387
388pub fn shell_complete(cmd: &dyn CommandLike, prog_name: &str, complete_var: &str) {
420 let shell_type = match env::var(complete_var) {
422 Ok(val) => {
423 val.split('_').next().unwrap_or("bash").to_string()
425 }
426 Err(_) => return,
427 };
428
429 let completer = match get_completion_class(&shell_type) {
430 Some(c) => c,
431 None => return,
432 };
433
434 let comp_args = completer.get_completion_args();
436
437 let completions = get_completions(cmd, prog_name, &comp_args.args, &comp_args.incomplete);
439
440 let stdout = io::stdout();
442 let mut handle = stdout.lock();
443 for item in completions {
444 let _ = writeln!(handle, "{}", completer.format_completion(&item));
445 }
446}
447
448pub fn get_completions(
464 cmd: &dyn CommandLike,
465 prog_name: &str,
466 args: &[String],
467 incomplete: &str,
468) -> Vec<CompletionItem> {
469 let mut completions = Vec::new();
470
471 let ctx = ContextBuilder::new()
473 .info_name(prog_name)
474 .resilient_parsing(true)
475 .build();
476
477 if let Some(group) = cmd.as_any().downcast_ref::<Group>() {
479 if !args.is_empty() {
481 if let Some(subcmd) = group.get_command(&args[0]) {
482 let remaining_args: Vec<String> = args[1..].to_vec();
483 return get_completions(subcmd, &args[0], &remaining_args, incomplete);
484 }
485 }
486
487 for name in group.list_commands() {
489 if name.starts_with(incomplete) {
490 let subcmd = group.get_command(name);
491 let help = subcmd.map(|c| c.get_short_help());
492 let mut item = CompletionItem::new(name);
493 if let Some(h) = help {
494 if !h.is_empty() {
495 item = item.with_help(h);
496 }
497 }
498 completions.push(item);
499 }
500 }
501 }
502 if let Some(collection) = cmd.as_any().downcast_ref::<CommandCollection>() {
503 if !args.is_empty() {
504 if let Some(subcmd) = collection.get_command(&args[0]) {
505 let remaining_args: Vec<String> = args[1..].to_vec();
506 return get_completions(subcmd, &args[0], &remaining_args, incomplete);
507 }
508 }
509
510 for name in collection.list_commands() {
511 if name.starts_with(incomplete) {
512 let subcmd = collection.get_command(&name);
513 let help = subcmd.map(|c| c.get_short_help());
514 let mut item = CompletionItem::new(&name);
515 if let Some(h) = help {
516 if !h.is_empty() {
517 item = item.with_help(h);
518 }
519 }
520 completions.push(item);
521 }
522 }
523 }
524
525 if incomplete.starts_with('-') || completions.is_empty() {
527 if let Some(command) = cmd.as_any().downcast_ref::<Command>() {
528 completions.extend(get_option_completions(command, &ctx, args, incomplete));
529 } else if let Some(group) = cmd.as_any().downcast_ref::<Group>() {
530 completions.extend(get_option_completions(&group.command, &ctx, args, incomplete));
531 } else if let Some(collection) = cmd.as_any().downcast_ref::<CommandCollection>() {
532 completions.extend(get_option_completions(
533 &collection.base.command,
534 &ctx,
535 args,
536 incomplete,
537 ));
538 }
539 }
540
541 if !incomplete.starts_with('-') {
543 if let Some(command) = cmd.as_any().downcast_ref::<Command>() {
544 completions.extend(get_argument_completions(command, &ctx, args, incomplete));
545 } else if let Some(group) = cmd.as_any().downcast_ref::<Group>() {
546 completions.extend(get_argument_completions(&group.command, &ctx, args, incomplete));
547 } else if let Some(collection) = cmd.as_any().downcast_ref::<CommandCollection>() {
548 completions.extend(get_argument_completions(&collection.base.command, &ctx, args, incomplete));
549 }
550 }
551
552 completions
553}
554
555fn get_argument_completions(
561 cmd: &Command,
562 ctx: &crate::context::Context,
563 args: &[String],
564 incomplete: &str,
565) -> Vec<CompletionItem> {
566 let positional_count = args
569 .iter()
570 .filter(|a| !a.starts_with('-'))
571 .count();
572
573 if let Some(arg) = cmd.arguments.get(positional_count) {
575 return arg.get_completions(ctx, incomplete);
576 }
577
578 if let Some(last_arg) = cmd.arguments.last() {
580 if last_arg.multiple() {
581 return last_arg.get_completions(ctx, incomplete);
582 }
583 }
584
585 Vec::new()
586}
587
588fn get_option_completions(
590 cmd: &Command,
591 ctx: &crate::context::Context,
592 args: &[String],
593 incomplete: &str,
594) -> Vec<CompletionItem> {
595 if let Some((opt_name, value_prefix)) = incomplete.split_once('=') {
597 if opt_name.starts_with("--") {
598 if let Some(opt) = cmd
599 .options
600 .iter()
601 .find(|o| o.long.iter().any(|long| long == opt_name) && option_accepts_value(o))
602 {
603 return opt
604 .get_completions(ctx, value_prefix)
605 .into_iter()
606 .map(|item| {
607 let mut with_prefix = CompletionItem::new(format!(
608 "{}={}",
609 opt_name, item.value
610 ));
611 if let Some(help) = item.help {
612 with_prefix = with_prefix.with_help(help);
613 }
614 with_prefix
615 })
616 .collect();
617 }
618 }
619 }
620
621 if let Some(last_arg) = args.last() {
623 if let Some(opt) = cmd.options.iter().find(|o| {
624 option_accepts_value(o)
625 && (o.long.iter().any(|long| long == last_arg)
626 || o.short.iter().any(|short| short == last_arg))
627 }) {
628 return opt.get_completions(ctx, incomplete);
629 }
630 }
631
632 let mut completions = Vec::new();
633
634 for opt in &cmd.options {
635 for long in &opt.long {
637 if long.starts_with(incomplete) {
638 let mut item = CompletionItem::new(long);
639 if let Some(help) = opt.help() {
640 item = item.with_help(help.to_string());
641 }
642 completions.push(item);
643 }
644 }
645
646 for short in &opt.short {
648 if short.starts_with(incomplete) {
649 let mut item = CompletionItem::new(short);
650 if let Some(help) = opt.help() {
651 item = item.with_help(help.to_string());
652 }
653 completions.push(item);
654 }
655 }
656 }
657
658 if let Some(help_opt) = cmd.get_help_option(ctx) {
660 for long in &help_opt.long {
661 if long.starts_with(incomplete) {
662 let mut item = CompletionItem::new(long);
663 if let Some(help) = help_opt.help() {
664 if !help.is_empty() {
665 item = item.with_help(help.to_string());
666 }
667 }
668 completions.push(item);
669 }
670 }
671 for short in &help_opt.short {
672 if short.starts_with(incomplete) {
673 let mut item = CompletionItem::new(short);
674 if let Some(help) = help_opt.help() {
675 if !help.is_empty() {
676 item = item.with_help(help.to_string());
677 }
678 }
679 completions.push(item);
680 }
681 }
682 }
683
684 completions
685}
686
687fn option_accepts_value(opt: &crate::option::ClickOption) -> bool {
688 !opt.is_flag && !opt.count
689}
690
691pub fn make_completion_option(complete_var: &str) -> CompletionOption {
705 CompletionOption {
706 complete_var: complete_var.to_string(),
707 }
708}
709
710#[derive(Debug, Clone)]
712pub struct CompletionOption {
713 pub complete_var: String,
715}
716
717impl CompletionOption {
718 pub fn is_completion_requested(&self) -> bool {
720 env::var(&self.complete_var).is_ok()
721 }
722
723 pub fn get_completion_shell(&self) -> Option<String> {
725 env::var(&self.complete_var).ok().and_then(|val| {
726 let parts: Vec<&str> = val.split('_').collect();
728 if parts.len() >= 2 {
729 Some(parts[0].to_string())
730 } else {
731 None
732 }
733 })
734 }
735
736 pub fn is_source_request(&self) -> bool {
738 env::var(&self.complete_var)
739 .map(|v| v.ends_with("_source"))
740 .unwrap_or(false)
741 }
742
743 pub fn handle_completion(
747 &self,
748 cmd: &dyn CommandLike,
749 prog_name: &str,
750 ) -> bool {
751 if !self.is_completion_requested() {
752 return false;
753 }
754
755 if let Some(shell) = self.get_completion_shell() {
756 if self.is_source_request() {
757 if let Some(completer) = get_completion_class(&shell) {
759 println!("{}", completer.get_source(prog_name, &self.complete_var));
760 }
761 } else {
762 shell_complete(cmd, prog_name, &self.complete_var);
764 }
765 return true;
766 }
767
768 false
769 }
770}
771
772#[cfg(test)]
777mod tests {
778 use super::*;
779
780 #[test]
781 fn test_get_completion_class() {
782 assert!(get_completion_class("bash").is_some());
783 assert!(get_completion_class("zsh").is_some());
784 assert!(get_completion_class("fish").is_some());
785 assert!(get_completion_class("unknown").is_none());
786
787 assert!(get_completion_class("BASH").is_some());
789 assert!(get_completion_class("Zsh").is_some());
790 }
791
792 #[test]
793 fn test_shell_names() {
794 let bash = BashComplete;
795 assert_eq!(bash.name(), "bash");
796
797 let zsh = ZshComplete;
798 assert_eq!(zsh.name(), "zsh");
799
800 let fish = FishComplete;
801 assert_eq!(fish.name(), "fish");
802 }
803
804 #[test]
805 fn test_list_shells() {
806 let shells = list_shells();
807 assert!(shells.contains(&"bash"));
808 assert!(shells.contains(&"zsh"));
809 assert!(shells.contains(&"fish"));
810 }
811
812 #[test]
813 fn test_bash_source_template() {
814 let bash = BashComplete;
815 let source = bash.get_source("myapp", "_MYAPP_COMPLETE");
816
817 assert!(source.contains("myapp"));
818 assert!(source.contains("_MYAPP_COMPLETE"));
819 assert!(source.contains("_myapp_completion"));
820 assert!(source.contains("COMP_WORDS"));
821 assert!(source.contains("COMP_CWORD"));
822 }
823
824 #[test]
825 fn test_zsh_source_template() {
826 let zsh = ZshComplete;
827 let source = zsh.get_source("myapp", "_MYAPP_COMPLETE");
828
829 assert!(source.contains("#compdef myapp"));
830 assert!(source.contains("_MYAPP_COMPLETE"));
831 assert!(source.contains("_myapp_completion"));
832 }
833
834 #[test]
835 fn test_fish_source_template() {
836 let fish = FishComplete;
837 let source = fish.get_source("myapp", "_MYAPP_COMPLETE");
838
839 assert!(source.contains("function _myapp_completion"));
840 assert!(source.contains("_MYAPP_COMPLETE"));
841 assert!(source.contains("complete -c myapp"));
842 }
843
844 #[test]
845 fn test_bash_format_completion() {
846 let bash = BashComplete;
847
848 let item = CompletionItem::new("--help");
849 assert_eq!(bash.format_completion(&item), "plain,--help");
850
851 let item_with_help = CompletionItem::new("--name").with_help("Specify name");
852 assert_eq!(bash.format_completion(&item_with_help), "plain,--name");
853 }
854
855 #[test]
856 fn test_zsh_format_completion() {
857 let zsh = ZshComplete;
858
859 let item = CompletionItem::new("--help");
860 assert_eq!(zsh.format_completion(&item), "plain\n--help\n_");
861
862 let item_with_help = CompletionItem::new("--name").with_help("Specify name");
863 assert_eq!(
864 zsh.format_completion(&item_with_help),
865 "plain\n--name\nSpecify name"
866 );
867 }
868
869 #[test]
870 fn test_fish_format_completion() {
871 let fish = FishComplete;
872
873 let item = CompletionItem::new("--help");
874 assert_eq!(fish.format_completion(&item), "plain,--help");
875
876 let item_file = CompletionItem::with_type("path", "file");
877 assert_eq!(fish.format_completion(&item_file), "file,path");
878 }
879
880 #[test]
881 fn test_completion_args_default() {
882 let args = CompletionArgs::default();
883 assert!(args.args.is_empty());
884 assert!(args.incomplete.is_empty());
885 }
886
887 #[test]
888 fn test_get_completions_empty() {
889 let cmd = Command::new("test").build();
890 let completions = get_completions(&cmd, "test", &[], "");
891
892 assert!(completions.iter().any(|c| c.value == "--help"));
894 }
895
896 #[test]
897 fn test_get_completions_options() {
898 let cmd = Command::new("test")
899 .option(
900 crate::option::ClickOption::new(&["--name", "-n"])
901 .help("The name")
902 .build(),
903 )
904 .build();
905
906 let completions = get_completions(&cmd, "test", &[], "--");
907
908 assert!(completions.iter().any(|c| c.value == "--name"));
909 assert!(completions.iter().any(|c| c.value == "--help"));
910 }
911
912 #[test]
913 fn test_get_completions_subcommands() {
914 let group = Group::new("cli")
915 .command(Command::new("init").help("Initialize").build())
916 .command(Command::new("build").help("Build").build())
917 .build();
918
919 let completions = get_completions(&group, "cli", &[], "");
920
921 assert!(completions.iter().any(|c| c.value == "init"));
922 assert!(completions.iter().any(|c| c.value == "build"));
923 }
924
925 #[test]
926 fn test_get_completions_subcommand_prefix() {
927 let group = Group::new("cli")
928 .command(Command::new("init").build())
929 .command(Command::new("install").build())
930 .command(Command::new("build").build())
931 .build();
932
933 let completions = get_completions(&group, "cli", &[], "in");
934
935 assert!(completions.iter().any(|c| c.value == "init"));
936 assert!(completions.iter().any(|c| c.value == "install"));
937 assert!(!completions.iter().any(|c| c.value == "build"));
938 }
939
940 #[test]
941 fn test_completion_option() {
942 let opt = make_completion_option("_TEST_COMPLETE");
943 assert_eq!(opt.complete_var, "_TEST_COMPLETE");
944 }
945
946 #[test]
947 fn test_completion_option_not_requested() {
948 env::remove_var("_TEST_COMPLETE");
950
951 let opt = make_completion_option("_TEST_COMPLETE");
952 assert!(!opt.is_completion_requested());
953 assert!(opt.get_completion_shell().is_none());
954 assert!(!opt.is_source_request());
955 }
956
957 #[test]
958 fn test_prog_name_with_dash() {
959 let bash = BashComplete;
960 let source = bash.get_source("my-app", "_MY_APP_COMPLETE");
961
962 assert!(source.contains("_my_app_completion"));
964 }
965
966 #[test]
971 fn test_get_completions_argument_with_choice_type() {
972 use crate::argument::Argument;
973 use crate::types::Choice;
974
975 let cmd = Command::new("test")
976 .argument(
977 Argument::new("format")
978 .type_(Choice::new(["json", "xml", "yaml"]))
979 .build()
980 )
981 .build();
982
983 let completions = get_completions(&cmd, "test", &[], "j");
984
985 assert_eq!(completions.len(), 1);
986 assert_eq!(completions[0].value, "json");
987 }
988
989 #[test]
990 fn test_get_completions_argument_with_custom_callback() {
991 use crate::argument::Argument;
992
993 let cmd = Command::new("test")
994 .argument(
995 Argument::new("filename")
996 .shell_complete(|_ctx, incomplete| {
997 vec![
998 CompletionItem::new(format!("{}.txt", incomplete)),
999 CompletionItem::new(format!("{}.md", incomplete)),
1000 ]
1001 })
1002 .build()
1003 )
1004 .build();
1005
1006 let completions = get_completions(&cmd, "test", &[], "file");
1007
1008 assert_eq!(completions.len(), 2);
1009 assert!(completions.iter().any(|c| c.value == "file.txt"));
1010 assert!(completions.iter().any(|c| c.value == "file.md"));
1011 }
1012
1013 #[test]
1014 fn test_get_completions_second_argument() {
1015 use crate::argument::Argument;
1016 use crate::types::Choice;
1017
1018 let cmd = Command::new("test")
1019 .argument(Argument::new("first").build())
1020 .argument(
1021 Argument::new("second")
1022 .type_(Choice::new(["a", "b", "c"]))
1023 .build()
1024 )
1025 .build();
1026
1027 let completions = get_completions(&cmd, "test", &[], "x");
1029 assert!(completions.is_empty());
1030
1031 let completions = get_completions(&cmd, "test", &["value1".to_string()], "a");
1033 assert_eq!(completions.len(), 1);
1034 assert_eq!(completions[0].value, "a");
1035 }
1036
1037 #[test]
1038 fn test_get_completions_variadic_argument() {
1039 use crate::argument::Argument;
1040 use crate::types::Choice;
1041
1042 let cmd = Command::new("test")
1043 .argument(
1044 Argument::new("files")
1045 .multiple()
1046 .type_(Choice::new(["foo.txt", "bar.txt", "baz.txt"]))
1047 .build()
1048 )
1049 .build();
1050
1051 let completions = get_completions(&cmd, "test", &[], "f");
1053 assert_eq!(completions.len(), 1);
1054 assert_eq!(completions[0].value, "foo.txt");
1055
1056 let completions = get_completions(&cmd, "test", &["foo.txt".to_string()], "b");
1058 assert_eq!(completions.len(), 2);
1059 assert!(completions.iter().any(|c| c.value == "bar.txt"));
1060 assert!(completions.iter().any(|c| c.value == "baz.txt"));
1061 }
1062
1063 #[test]
1064 fn test_get_completions_no_more_arguments() {
1065 use crate::argument::Argument;
1066
1067 let cmd = Command::new("test")
1068 .argument(Argument::new("single").build())
1069 .build();
1070
1071 let completions = get_completions(&cmd, "test", &["value".to_string()], "x");
1073 assert!(completions.is_empty());
1076 }
1077}